diff --git a/.gitignore b/.gitignore index 2ce32f0b1..c804447ea 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ build/ .DS_Store .gradle/ + +.codegraph/ + +TEST_SUITE_PLAN.md diff --git a/README.md b/README.md index b7e00b79f..55eb4018c 100644 --- a/README.md +++ b/README.md @@ -47,17 +47,17 @@ plugin [Manifold](https://plugins.jetbrains.com/plugin/10057-manifold) ## Preface: if you need help compiling and you are a developer / intend to help out in the community or with development we would love to help you regardless in the discord! however do not come to the discord asking for free copies, or a tutorial on how to compile. -1. Install [Java JDK 21](https://www.oracle.com/java/technologies/downloads/#java21) +1. Install [Java JDK 25](https://adoptium.net/temurin/releases/?version=25) 2. Set the JDK installation path to `JAVA_HOME` as an environment variable. * Windows 1. Start > Type `env` and press Enter 2. Advanced > Environment Variables 3. Under System Variables, click `New...` 4. Variable Name: `JAVA_HOME` - 5. Variable Value: `C:\Program Files\Java\jdk-21` (verify this exists after installing java don't just copy + 5. Variable Value: `C:\Program Files\Java\jdk-25` (verify this exists after installing java don't just copy the example text) * MacOS - 1. Run `/usr/libexec/java_home -V` and look for Java 21 + 1. Run `/usr/libexec/java_home -V` and look for Java 25 2. Run `sudo nano ~/.zshenv` 3. Add `export JAVA_HOME=$(/usr/libexec/java_home)` as a new line 4. Use `CTRL + X`, then Press `Y`, Then `ENTER` @@ -69,7 +69,7 @@ plugin [Manifold](https://plugins.jetbrains.com/plugin/10057-manifold) Gradle Setup * Run `gradlew setup` any time you get dependency issues with craftbukkit -* Configure ITJ Gradle to use JDK 21 (in settings, search for gradle) +* Configure ITJ Gradle to use JDK 25 (in settings, search for gradle) * Resync the project & run your newly created task (under the development folder in gradle tasks!) diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..dea81c8f7 --- /dev/null +++ b/build.gradle @@ -0,0 +1,302 @@ +/* + * Adapt is Copyright (c) 2021 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +buildscript { + repositories { + mavenCentral() + gradlePluginPortal() + } + dependencies { + classpath('io.papermc.paperweight:paperweight-userdev:2.0.0-beta.21') + } +} + +import io.freefair.gradle.plugins.lombok.LombokPlugin +import io.github.slimjar.resolver.data.Mirror +import io.papermc.paperweight.userdev.PaperweightUser +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.jvm.toolchain.JavaLanguageVersion + +plugins { + id 'java-library' + alias(libs.plugins.lombok) + alias(libs.plugins.shadow) + alias(libs.plugins.idea) + alias(libs.plugins.slimjar) +} + +apply plugin: PaperweightUser + +version = '2.0.0-26.2' +def apiVersion = '26.2' +def main = 'art.arcane.adapt.Adapt' +String paperDevBundleVersion = '26.2.build.25-alpha' +String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') + .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') + .get() +String hiddenOreCoordinate = providers.gradleProperty('hiddenOreCoordinate') + .orElse('com.github.VolmitSoftware:HiddenOre:master-SNAPSHOT') + .get() + +def shadowJarTask = tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) +tasks.named('slimJar').get() + +// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED +// ======================== WINDOWS ============================= +registerCustomOutputTask('Cyberpwn', 'C://Users/cyberpwn/Documents/development/server/plugins') +registerCustomOutputTask('Psycho', 'C://Dan/MinecraftDevelopment/Server/plugins') +registerCustomOutputTask('ArcaneArts', 'C://Users/arcane/Documents/development/server/plugins') +registerCustomOutputTask('Vatuu', 'D://Minecraft/Servers/1.20/plugins') +registerCustomOutputTask('Nowhere', 'E://Desktop/server/plugins') +registerCustomOutputTask('CrazyDev22', 'C://Users/Julian/Desktop/server/plugins') +registerCustomOutputTask('Pixel', 'D://Iris Dimension Engine//1.20.4 - Development//plugins') +// ========================== UNIX ============================== +registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins') +registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins') +registerCustomOutputTaskUnix('the456gamer', '/home/the456gamer/projects/minecraft/adapt-testserver/plugins/update/', false) +// ============================================================== + +/** + * Expand properties into plugin yml + */ +tasks.named('processResources').configure { + def pluginProperties = [ + name : rootProject.name, + version : project.version, + main : main, + apiVersion: apiVersion, + ] + + inputs.properties(pluginProperties) + filesMatching('**/plugin.yml') { + expand(pluginProperties) + } +} + +allprojects { + apply plugin: 'java' + apply plugin: LombokPlugin + + java { + toolchain.languageVersion.set(JavaLanguageVersion.of(25)) + } + + repositories { + mavenCentral() + maven { url = uri('https://hub.spigotmc.org/nexus/content/repositories/snapshots/') } + maven { url = uri('https://repo.papermc.io/repository/maven-public/') } + maven { url = uri('https://repo.codemc.org/repository/maven-public') } + maven { url = uri('https://mvn.lumine.io/repository/maven-public/') } + maven { url = uri('https://nexus.frengor.com/repository/public/') } + maven { url = uri('https://repo.extendedclip.com/content/repositories/placeholderapi/') } + maven { url = uri('https://repo.glaremasters.me/repository/bloodshot/') } + maven { url = uri('https://maven.enginehub.org/repo/') } + maven { url = uri('https://repo.oraxen.com/releases') } + maven { url = uri('https://repo.alessiodp.com/releases') } + maven { url = uri('https://jitpack.io') } + } + + /** + * We need parameter meta for the decree command system + */ + tasks.withType(JavaCompile).configureEach { + options.compilerArgs.add('-parameters') + options.encoding = 'UTF-8' + options.debugOptions.debugLevel = 'none' + options.release.set(25) + } +} + +dependencies { + paperweight.paperDevBundle(paperDevBundleVersion) + + implementation(project(':velocity')) + implementation(volmLibCoordinate) { + changing = true + transitive = false + } + compileOnly(hiddenOreCoordinate) { + changing = true + transitive = false + } + implementation('de.crazydev22.slimjar.helper:spigot:2.1.9') + implementation('de.crazydev22.slimjar.helper:velocity:2.1.9') + slimApi(libs.platformUtils) { + transitive = false + } + + compileOnly('io.papermc.paper:paper-api:26.2.build.25-alpha') + + // Cancer + slimApi(libs.fukkit) { + exclude(group: 'org.spigotmc', module: 'spigot-api') + } + slimApi(libs.amulet) + slimApi(libs.chrono) + slimApi(libs.spatial) + slimApi(libs.kotlin.coroutines) + + // Dynamically Loaded + slimApi(libs.adventure.minimessage) + slimApi(libs.adventure.platform) + slimApi(libs.adventure.gson) + slimApi(libs.adventure.legacy) + slimApi(libs.lettuce) + slimApi(libs.particle) + implementation(libs.ultimateAdvancementApi) + slimApi(libs.customBlockData) + slimApi(libs.lur) + slimApi(libs.lang3) + slimApi(libs.effectLib) + slimApi(libs.gson) + slimApi(libs.toml4j) + slimApi(libs.fastutil) + implementation(libs.glowingentities) + slimApi(libs.caffeine) + + //Random Api's + compileOnlyApi('me.clip:placeholderapi:2.11.6') + compileOnlyApi('com.github.DeadSilenceIV:AdvancedChestsAPI:2.9-BETA') + compileOnlyApi('com.sk89q.worldguard:worldguard-bukkit:7.0.8') + compileOnlyApi('com.github.FrancoBM12:API-MagicCosmetics:2.2.8') + compileOnlyApi('com.massivecraft:Factions:1.6.9.5-U0.6.21') + compileOnlyApi('com.github.angeschossen:ChestProtectAPI:3.9.1') + compileOnlyApi('com.github.TechFortress:GriefPrevention:16.18.1') + compileOnlyApi('com.griefdefender:api:2.1.0-SNAPSHOT') + compileOnlyApi(fileTree('libs') { include('*.jar') }) + + testImplementation('io.papermc.paper:paper-api:26.2.build.25-alpha') + testImplementation('org.junit.jupiter:junit-jupiter:5.11.4') + testImplementation('org.mockito:mockito-core:5.14.2') + testImplementation('org.mockito:mockito-junit-jupiter:5.14.2') + testImplementation('org.assertj:assertj-core:3.26.3') + testRuntimeOnly('org.junit.platform:junit-platform-launcher') +} + +tasks.named('test', Test) { + useJUnitPlatform() + jvmArgs('-Dnet.bytebuddy.experimental=true') + testLogging { + events('passed', 'skipped', 'failed') + showStandardStreams = true + exceptionFormat = 'full' + } +} + +def lib = 'art.arcane.adapt.util' +slimJar { + mirrors = [ + new Mirror( + uri('https://maven-central.storage-download.googleapis.com/maven2').toURL(), + uri('https://repo.maven.apache.org/maven2/').toURL() + ) + ] + + relocate('manifold', "${lib}.manifold") + relocate('art.arcane.volmlib', "${lib}.arcane.volmlib") + relocate('Fukkit.extensions', "${lib}.extensions") + relocate('Amulet.extensions', "${lib}.extensions") + relocate('com.fren_gor.ultimateAdvancementAPI', "${lib}.advancements") + relocate('net.byteflux.libby', "${lib}.libby") + relocate('com.jeff_media.customblockdata', "${lib}.customblocks") +} + +/** + * Configure Adapt for shading + */ +shadowJarTask.configure { +// minimize() + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + exclude('net/kyori/**') + exclude('com/google/gson/**') +} + +configurations.configureEach { + resolutionStrategy.cacheChangingModulesFor(0, 'seconds') + resolutionStrategy.cacheDynamicVersionsFor(0, 'seconds') +} + +if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_25)) { + System.err.println() + System.err.println('=========================================================================================================') + System.err.println('You must run gradle on Java 25 or newer. You are using ' + JavaVersion.current()) + System.err.println() + System.err.println('=== For IDEs ===') + System.err.println('1. Configure the project for Java 25') + System.err.println('2. Configure the bundled gradle to use Java 25 in settings') + System.err.println() + System.err.println('=== For Command Line (gradlew) ===') + System.err.println('1. Install JDK 25 from https://adoptium.net/temurin/releases/?version=25') + System.err.println('2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:/Program Files/Java/jdk-25') + System.err.println('3. Open a new command prompt window to get the new environment variables if need be.') + System.err.println('=========================================================================================================') + System.err.println() + System.exit(69) +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } +} + +tasks.named('build').configure { + dependsOn(shadowJarTask) +} + +tasks.register('adapt', Copy) { + from(shadowJarTask.flatMap { it.archiveFile }) + into(layout.buildDirectory) + rename { String ignored -> "Adapt-${version}.jar" } +} + +void registerCustomOutputTask(String name, String path, boolean doRename = true) { + if (!System.getProperty('os.name').toLowerCase().contains('windows')) { + return + } + createOutputTask(name, path, doRename) +} + +void registerCustomOutputTaskUnix(String name, String path, boolean doRename = true) { + if (System.getProperty('os.name').toLowerCase().contains('windows')) { + return + } + + createOutputTask(name, path, doRename) +} + +void createOutputTask(String name, String path, boolean doRename = true) { + def shadowJarTask = tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) + tasks.register("build${name}", Copy) { + group = 'development' + outputs.upToDateWhen { false } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn(shadowJarTask) + from(shadowJarTask.flatMap { it.archiveFile }) + into(file(path)) + if (doRename) { + rename { String ignored -> 'Adapt.jar' } + } + } +} + +idea.project.settings { + taskTriggers { + afterSync(':velocity:generateTemplates') + } +} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index 03fae830a..000000000 --- a/build.gradle.kts +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Adapt is Copyright (c) 2021 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import io.freefair.gradle.plugins.lombok.LombokPlugin -import io.github.slimjar.func.slimjarHelper -import io.github.slimjar.resolver.data.Mirror -import org.gradle.api.plugins.JavaPluginExtension -import org.jetbrains.gradle.ext.settings -import org.jetbrains.gradle.ext.taskTriggers -import xyz.jpenilla.runpaper.task.RunServer -import kotlin.system.exitProcess - -plugins { - `java-library` - alias(libs.plugins.lombok) - alias(libs.plugins.shadow) - alias(libs.plugins.runPaper) - alias(libs.plugins.runVelocity) - alias(libs.plugins.idea) - alias(libs.plugins.slimjar) -} - -version = "1.18.0-1.20.2-1.21.11" -val apiVersion = "1.20" -val main = "com.volmit.adapt.Adapt" - -// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED -// ======================== WINDOWS ============================= -registerCustomOutputTask("Cyberpwn", "C://Users/cyberpwn/Documents/development/server/plugins") -registerCustomOutputTask("Psycho", "C://Dan/MinecraftDevelopment/Server/plugins") -registerCustomOutputTask("ArcaneArts", "C://Users/arcane/Documents/development/server/plugins") -registerCustomOutputTask("Vatuu", "D://Minecraft/Servers/1.20/plugins") -registerCustomOutputTask("Nowhere", "E://Desktop/server/plugins") -registerCustomOutputTask("CrazyDev22", "C://Users/Julian/Desktop/server/plugins") -registerCustomOutputTask("Pixel", "D://Iris Dimension Engine//1.20.4 - Development//plugins") -// ========================== UNIX ============================== -registerCustomOutputTaskUnix("CyberpwnLT", "/Users/danielmills/development/server/plugins") -registerCustomOutputTaskUnix("PsychoLT", "/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/plugins") -registerCustomOutputTaskUnix("the456gamer", "/home/the456gamer/projects/minecraft/adapt-testserver/plugins/update/", false) -// ============================================================== - -val supported = listOf("1.20.2", "1.20.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4", "1.21.5", "1.21.8", "1.21.10", "1.21.11") -val jdk = listOf("1.20.2", "1.20.4") - -val MIN_HEAP_SIZE = "2G" -val MAX_HEAP_SIZE = "8G" -//Valid values are: none, truecolor, indexed256, indexed16, indexed8 -val COLOR = "truecolor" - -supported.forEach { version -> - tasks.register("runServer-$version") { - group = "servers" - minecraftVersion(version) - minHeapSize = MIN_HEAP_SIZE - maxHeapSize = MAX_HEAP_SIZE - systemProperty("disable.watchdog", "") - systemProperty("net.kyori.ansi.colorLevel", COLOR) - systemProperty("com.mojang.eula.agree", true) - pluginJars(tasks.shadowJar.flatMap { it.archiveFile} ) - runDirectory.convention(layout.buildDirectory.dir("run/$version")) - - if (!jdk.contains(version)) { - javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(21)} - } - } -} - -tasks.runVelocity { - group = "servers" - velocityVersion(libs.versions.velocity.get()) - runDirectory.convention(layout.buildDirectory.dir("run/velocity")) -} - -/** - * Expand properties into plugin yml - */ -tasks.processResources { - inputs.properties( - "name" to rootProject.name, - "version" to version, - "main" to main, - "apiVersion" to apiVersion, - ) - - filesMatching("**/plugin.yml") { - expand(inputs.properties) - } -} - -allprojects { - apply() - apply() - - extensions.configure { - toolchain.languageVersion.set(JavaLanguageVersion.of(21)) - } - - repositories { - mavenCentral() - maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") - maven("https://repo.papermc.io/repository/maven-public/") - maven("https://repo.codemc.org/repository/maven-public") - maven("https://mvn.lumine.io/repository/maven-public/") - maven("https://nexus.frengor.com/repository/public/") - maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") - maven("https://repo.glaremasters.me/repository/bloodshot/") - maven("https://maven.enginehub.org/repo/") - maven("https://repo.oraxen.com/releases") - maven("https://repo.alessiodp.com/releases") - maven("https://jitpack.io") - } - - /** - * We need parameter meta for the decree command system - */ - tasks.compileJava { - options.compilerArgs.add("-parameters") - options.encoding = "UTF-8" - options.release.set(21) - } -} - -dependencies { - implementation(project(":velocity")) - implementation(slimjarHelper("spigot")) - implementation(slimjarHelper("velocity")) - implementation(libs.platformUtils) { - isTransitive = false - } - - compileOnly(libs.spigot) - - // Cancer - slimApi(libs.fukkit) - slimApi(libs.amulet) - slimApi(libs.chrono) - slimApi(libs.spatial) - - // Dynamically Loaded - slimApi(libs.adventure.minimessage) - slimApi(libs.adventure.platform) - slimApi(libs.adventure.gson) - slimApi(libs.adventure.legacy) - slimApi(libs.lettuce) - slimApi(libs.particle) - slimApi(libs.ultimateAdvancementApi) - slimApi(libs.customBlockData) - slimApi(libs.lur) - slimApi(libs.lang3) - slimApi(libs.effectLib) - slimApi(libs.gson) - slimApi(libs.toml4j) - slimApi(libs.fastutil) - slimApi(libs.glowingentities) - slimApi(libs.caffeine) - - //Random Api's - compileOnlyApi("me.clip:placeholderapi:2.11.6") - compileOnlyApi("com.github.DeadSilenceIV:AdvancedChestsAPI:2.9-BETA") - compileOnlyApi("com.sk89q.worldguard:worldguard-bukkit:7.0.8") - compileOnlyApi("com.github.FrancoBM12:API-MagicCosmetics:2.2.8") - compileOnlyApi("com.massivecraft:Factions:1.6.9.5-U0.6.21") - compileOnlyApi("com.github.angeschossen:ChestProtectAPI:3.9.1") - compileOnlyApi("com.github.TechFortress:GriefPrevention:16.18.1") - compileOnlyApi("com.griefdefender:api:2.1.0-SNAPSHOT") - compileOnlyApi(fileTree("libs") { include("*.jar") }) -} - -val lib = "com.volmit.adapt.util" -slimJar { - mirrors = listOf(Mirror( - uri("https://maven-central.storage-download.googleapis.com/maven2").toURL(), - uri("https://repo.maven.apache.org/maven2/").toURL() - )) - - relocate("manifold", "$lib.manifold") - relocate("art.arcane", "$lib.arcane") - relocate("Fukkit.extensions", "$lib.extensions") - relocate("Amulet.extensions", "$lib.extensions") - relocate("com.fren_gor.ultimateAdvancementAPI", "$lib.advancements") - relocate("net.byteflux.libby", "$lib.libby") - relocate("com.jeff_media.customblockdata", "$lib.customblocks") -} - -/** - * Configure Adapt for shading - */ -tasks.shadowJar { -// minimize() - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} - -configurations.configureEach { - resolutionStrategy.cacheChangingModulesFor(60, "minutes") - resolutionStrategy.cacheDynamicVersionsFor(60, "minutes") -} - -if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_21)) { - System.err.println() - System.err.println("=========================================================================================================") - System.err.println("You must run gradle on Java 21 or newer. You are using " + JavaVersion.current()) - System.err.println() - System.err.println("=== For IDEs ===") - System.err.println("1. Configure the project for Java 21") - System.err.println("2. Configure the bundled gradle to use Java 21 in settings") - System.err.println() - System.err.println("=== For Command Line (gradlew) ===") - System.err.println("1. Install JDK 21 from https://www.oracle.com/java/technologies/downloads/#java21") - System.err.println("2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:/Program Files/Java/jdk-21") - System.err.println("3. Open a new command prompt window to get the new environment variables if need be.") - System.err.println("=========================================================================================================") - System.err.println() - exitProcess(69) -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } -} - -tasks { - build { dependsOn(shadowJar) } - - register("adapt") { - from(shadowJar.flatMap { it.archiveFile }) - into(layout.buildDirectory) - rename { "Adapt-$version.jar" } - } -} - -fun registerCustomOutputTask(name: String, path: String, doRename: Boolean = true) { - if (!System.getProperty("os.name").lowercase().contains("windows")) { - return - } - createOutputTask(name, path, doRename) -} - -fun registerCustomOutputTaskUnix(name: String, path: String, doRename: Boolean = true) { - if (System.getProperty("os.name").lowercase().contains("windows")) { - return - } - - createOutputTask(name, path, doRename) -} - -fun createOutputTask(name: String, path: String, doRename: Boolean = true) { - tasks.register("build$name") { - group = "development" - outputs.upToDateWhen { false } - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - dependsOn(tasks.shadowJar) - from(tasks.shadowJar.flatMap { it.archiveFile }) - into(file(path)) - if (doRename) rename { "Adapt.jar" } - } -} - -idea.project.settings.taskTriggers { - afterSync(":velocity:generateTemplates") -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0a71650af..5d2cebedc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,12 @@ [versions] # Plugins -lombok = "8.14" +lombok = "9.2.0" shadow = "9.0.0-rc3" runTask = "2.3.1" idea = "1.2" -slimjar = "2.1.7" +slimjar = "2.1.9" +kotlin = "2.2.0" +kotlin-coroutines = "1.10.2" # Cancer fukkit = "23.6.1" @@ -13,11 +15,11 @@ chrono = "22.9.10" spatial = "22.11.2" velocity = "3.4.0-SNAPSHOT" -spigot = "1.21.11-R0.2-SNAPSHOT" +spigot = "26.2-R0.1-SNAPSHOT" platformUtils = "e17ac2c698" # Dynamically Loaded -adventure-api = "4.24.0" # https://github.com/KyoriPowered/adventure +adventure-api = "5.1.1" # https://github.com/KyoriPowered/adventure adventure-platform = "4.4.1" # https://github.com/KyoriPowered/adventure-platform lettuce = "6.5.1.RELEASE" @@ -60,6 +62,7 @@ toml4j = { module = "com.moandjiezana.toml:toml4j", version.ref = "toml4j" } fastutil = { module = "it.unimi.dsi:fastutil", version.ref = "fastutil" } glowingentities = { module = "fr.skytasul:glowingentities", version.ref = "glowingentities" } caffeine = { module = "com.github.ben-manes.caffeine:caffeine", version.ref = "caffeine" } +kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } [plugins] lombok = { id = "io.freefair.lombok", version.ref = "lombok" } @@ -68,3 +71,4 @@ runPaper = { id = "xyz.jpenilla.run-paper", version.ref = "runTask" } runVelocity = { id = "xyz.jpenilla.run-velocity", version.ref = "runTask" } idea = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "idea" } slimjar = { id = "de.crazydev22.slimjar", version.ref = "slimjar" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f24029cc4..fcccf27e1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -17,7 +17,7 @@ # distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..b7d9aca51 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,87 @@ +import java.io.File + +rootProject.name = 'Adapt' + +boolean hasVolmLibSettings(File directory) { + new File(directory, 'settings.gradle.kts').exists() || new File(directory, 'settings.gradle').exists() +} + +File resolveLocalVolmLibDirectory() { + String configuredPath = providers.gradleProperty('localVolmLibDirectory') + .orElse(providers.environmentVariable('VOLMLIB_DIR')) + .orNull + if (configuredPath != null && !configuredPath.isBlank()) { + File configuredDirectory = file(configuredPath) + if (hasVolmLibSettings(configuredDirectory)) { + return configuredDirectory + } + } + + File currentDirectory = settingsDir + while (currentDirectory != null) { + File candidate = new File(currentDirectory, 'VolmLib') + if (hasVolmLibSettings(candidate)) { + return candidate + } + + currentDirectory = currentDirectory.parentFile + } + + null +} + +boolean useLocalVolmLib = providers.gradleProperty('useLocalVolmLib') + .orElse('true') + .map { String value -> value.equalsIgnoreCase('true') } + .get() +File localVolmLibDirectory = resolveLocalVolmLibDirectory() + +if (useLocalVolmLib && localVolmLibDirectory != null) { + includeBuild(localVolmLibDirectory) { + dependencySubstitution { + substitute(module('com.github.VolmitSoftware:VolmLib')).using(project(':shared')) + substitute(module('com.github.VolmitSoftware.VolmLib:shared')).using(project(':shared')) + substitute(module('com.github.VolmitSoftware.VolmLib:volmlib-shared')).using(project(':shared')) + } + } +} + +File resolveLocalHiddenOreDirectory() { + String configuredPath = providers.gradleProperty('localHiddenOreDirectory') + .orElse(providers.environmentVariable('HIDDENORE_DIR')) + .orNull + if (configuredPath != null && !configuredPath.isBlank()) { + File configuredDirectory = file(configuredPath) + if (hasVolmLibSettings(configuredDirectory)) { + return configuredDirectory + } + } + + File currentDirectory = settingsDir + while (currentDirectory != null) { + File candidate = new File(currentDirectory, 'HiddenOre') + if (hasVolmLibSettings(candidate)) { + return candidate + } + + currentDirectory = currentDirectory.parentFile + } + + null +} + +boolean useLocalHiddenOre = providers.gradleProperty('useLocalHiddenOre') + .orElse('true') + .map { String value -> value.equalsIgnoreCase('true') } + .get() +File localHiddenOreDirectory = resolveLocalHiddenOreDirectory() + +if (useLocalHiddenOre && localHiddenOreDirectory != null) { + includeBuild(localHiddenOreDirectory) { + dependencySubstitution { + substitute(module('com.github.VolmitSoftware:HiddenOre')).using(project(':')) + } + } +} + +include(':velocity') diff --git a/settings.gradle.kts b/settings.gradle.kts deleted file mode 100644 index 0712309ed..000000000 --- a/settings.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -rootProject.name = "Adapt" - -include( - ":velocity", -) diff --git a/src/main/java/art/arcane/adapt/Adapt.java b/src/main/java/art/arcane/adapt/Adapt.java new file mode 100644 index 000000000..e606f6c9b --- /dev/null +++ b/src/main/java/art/arcane/adapt/Adapt.java @@ -0,0 +1,651 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdvancementManager; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingManager; +import art.arcane.adapt.api.protection.ProtectorRegistry; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.tick.Ticker; +import art.arcane.adapt.api.value.MaterialValue; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.api.world.AdaptServer; +import art.arcane.adapt.api.world.PlayerDataPersistenceQueue; +import art.arcane.adapt.api.xp.XpNoveltyListener; +import art.arcane.adapt.api.xp.XpProvenanceListener; +import art.arcane.adapt.content.integration.hiddenore.HiddenOreLink; +import art.arcane.adapt.content.protector.ChestProtectProtector; +import art.arcane.adapt.content.protector.FactionsClaimProtector; +import art.arcane.adapt.content.protector.GriefDefenderProtector; +import art.arcane.adapt.content.protector.GriefPreventionProtector; +import art.arcane.adapt.content.protector.LocketteProProtector; +import art.arcane.adapt.content.protector.ResidenceProtector; +import art.arcane.adapt.content.protector.WorldGuardProtector; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.io.SQLManager; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.plugin.AdaptService; +import art.arcane.adapt.util.common.plugin.Metrics; +import art.arcane.adapt.util.common.plugin.VolmitPlugin; +import art.arcane.adapt.util.common.plugin.VolmitSender; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigFileSupport; +import art.arcane.adapt.util.config.ConfigMigrationManager; +import art.arcane.adapt.util.project.redis.RedisSync; +import art.arcane.adapt.util.secret.SecretSplash; +import art.arcane.volmlib.integration.ReloadAware; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.inventorygui.UIWindow; +import art.arcane.volmlib.util.io.JarScanner; +import com.jeff_media.customblockdata.CustomBlockData; +import de.crazydev22.platformutils.AudienceProvider; +import de.crazydev22.platformutils.Platform; +import de.crazydev22.platformutils.PlatformUtils; +import de.slikey.effectlib.EffectManager; +import fr.skytasul.glowingentities.GlowingEntities; +import io.github.slimjar.app.builder.SpigotApplicationBuilder; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.net.URL; +import java.text.MessageFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +import static art.arcane.adapt.util.director.context.AdaptationListingHandler.initializeAdaptationListings; + +public class Adapt extends VolmitPlugin implements ReloadAware { + private static final long STARTUP_SLOW_PHASE_MS = 1500L; + private final AtomicBoolean alreadyDrained = new AtomicBoolean(false); + private static final boolean SLIMJAR_DEBUG = Boolean.getBoolean("adapt.debug-slimjar"); + private static final boolean DISABLE_REMAPPER = Boolean.getBoolean("adapt.disable-remapper"); + public static Adapt instance; + public static HashMap wordKey = new HashMap<>(); + public static Platform platform; + public static AudienceProvider audiences; + private static VolmitSender sender; + public final EffectManager adaptEffectManager; + private final KList postShutdown = new KList<>(); + private KMap, AdaptService> services; + @Getter + private GlowingEntities glowingEntities; + @Getter + private Ticker ticker; + @Getter + private AdaptServer adaptServer; + @Getter + private SQLManager sqlManager; + @Getter + private ProtectorRegistry protectorRegistry; + @Getter + private Map guiLeftovers = new HashMap<>(); + @Getter + private AdvancementManager manager; + @Getter + private RedisSync redisSync; + @Getter + private PlayerDataPersistenceQueue playerDataPersistenceQueue; + + + public Adapt() { + instance = this; + long libraryLoadStart = System.currentTimeMillis(); + getLogger().info("Loading Libraries..."); + new SpigotApplicationBuilder(this) + .debug(SLIMJAR_DEBUG) + .remap(!DISABLE_REMAPPER) + .build(); + long libraryLoadElapsed = System.currentTimeMillis() - libraryLoadStart; + if (DISABLE_REMAPPER) { + getLogger().warning("SlimJar remapper disabled via -Dadapt.disable-remapper=true."); + } + getLogger().info("Libraries Loaded! (" + libraryLoadElapsed + "ms)"); + adaptEffectManager = new EffectManager(this); + } + + @SuppressWarnings("unchecked") + public static T service(Class c) { + return (T) instance.services.get(c); + } + + private static void runStartupPhaseVoid(String phase, Runnable action) { + runStartupPhase(phase, () -> { + action.run(); + return null; + }); + } + + private static T runStartupPhase(String phase, Supplier action) { + if (phase == null || phase.isBlank()) { + return action.get(); + } + + info("Startup phase: " + phase); + long start = System.currentTimeMillis(); + try { + return action.get(); + } finally { + long elapsed = System.currentTimeMillis() - start; + if (elapsed >= STARTUP_SLOW_PHASE_MS) { + warn("Startup phase '" + phase + "' took " + elapsed + "ms."); + } else { + verbose("Startup phase '" + phase + "' took " + elapsed + "ms."); + } + } + } + + public static VolmitSender getSender() { + if (sender == null) { + sender = new VolmitSender(Bukkit.getConsoleSender()); + sender.setTag(instance.getTag()); + } + return sender; + } + + public static List initialize(String s) { + return initialize(s, null); + } + + public static KList initialize(String s, Class slicedClass) { + JarScanner js = new JarScanner(instance.getFile(), s); + KList v = new KList<>(); + J.attempt(js::scan); + for (Class i : js.getClasses()) { + if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { + try { + Adapt.verbose("Found class: " + i.getName()); + v.add(i.getDeclaredConstructor().newInstance()); + } catch (Throwable e) { + Adapt.verbose("Failed to load class: " + i.getName()); + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + for (String line : writer.toString().split("\n")) { + verbose(line); + } + } + } + } + + return v; + } + + public static int getJavaVersion() { + String version = System.getProperty("java.version"); + if (version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf("."); + if (dot != -1) { + version = version.substring(0, dot); + } + } + return Integer.parseInt(version); + } + + private static String getServerVersion() { + String version = Bukkit.getVersion(); + int mcMarkerIndex = version.indexOf(" (MC:"); + if (mcMarkerIndex != -1) { + version = version.substring(0, mcMarkerIndex); + } + return version; + } + + private static String getStartupDate() { + return LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + private static String getReleaseTrain(String version) { + String value = version; + int suffixIndex = value.indexOf('-'); + if (suffixIndex >= 0) { + value = value.substring(0, suffixIndex); + } + String[] split = value.split("\\."); + if (split.length >= 2) { + return split[0] + "." + split[1]; + } + return value; + } + + public static void printInformation() { + debug("XP Curve: " + AdaptConfig.get().getXpCurve()); + debug("XP/Level base: " + AdaptConfig.get().getPlayerXpPerSkillLevelUpBase()); + debug("XP/Level multiplier: " + AdaptConfig.get().getPlayerXpPerSkillLevelUpLevelMultiplier()); + info("Language: " + AdaptConfig.get().getLanguage() + " - Language Fallback: " + AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing()); + } + + public static void autoUpdateCheck() { + String localVersion = instance.getDescription().getVersion(); + if (localVersion.contains("development")) { + info("Development build detected. Skipping update check."); + return; + } + + info("Checking for updates..."); + String remoteVersion = fetchRemoteVersion(); + if (remoteVersion == null) { + error("Failed to check for updates."); + return; + } + + int comparison = compareVersionPrefixes(localVersion, remoteVersion); + if (comparison < 0) { + info(MessageFormat.format("Please update your Adapt plugin to the latest version! (Current: {0} Latest: {1})", localVersion, remoteVersion)); + } else if (comparison > 0) { + info("Running a build ahead of the published release. (Current: " + localVersion + " Published: " + remoteVersion + ")"); + } else { + info("You are running the latest version of Adapt!"); + } + } + + private static String fetchRemoteVersion() { + String[] sources = { + "https://raw.githubusercontent.com/VolmitSoftware/Adapt/main/build.gradle.kts", + "https://raw.githubusercontent.com/VolmitSoftware/Adapt/main/build.gradle" + }; + Pattern versionPattern = Pattern.compile("^version\\s*=?\\s*['\"]([^'\"]+)['\"]"); + + for (String source : sources) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(new URL(source).openStream()))) { + String line; + while ((line = in.readLine()) != null) { + Matcher matcher = versionPattern.matcher(line.trim()); + if (matcher.find()) { + return matcher.group(1); + } + } + } catch (Throwable ignored) { + } + } + + return null; + } + + private static int compareVersionPrefixes(String local, String remote) { + int[] a = parseVersionPrefix(local); + int[] b = parseVersionPrefix(remote); + for (int i = 0; i < 3; i++) { + if (a[i] != b[i]) { + return Integer.compare(a[i], b[i]); + } + } + + return 0; + } + + private static int[] parseVersionPrefix(String version) { + int[] parts = new int[3]; + Matcher matcher = Pattern.compile("^(\\d+)\\.(\\d+)(?:\\.(\\d+))?").matcher(version); + if (matcher.find()) { + parts[0] = Integer.parseInt(matcher.group(1)); + parts[1] = Integer.parseInt(matcher.group(2)); + parts[2] = matcher.group(3) == null ? 0 : Integer.parseInt(matcher.group(3)); + } + + return parts; + } + + public static void actionbar(Player p, String msg) { + new VolmitSender(p).sendAction(msg); + } + + public static void debug(String string) { + if (AdaptConfig.get().isDebug()) { + msg(C.DARK_PURPLE + string); + } + } + + public static void warn(String string) { + msg(C.YELLOW + string); + } + + public static void error(String string) { + msg(C.RED + string); + } + + public static void verbose(String string) { + if (AdaptConfig.get().isVerbose()) { + msg(C.LIGHT_PURPLE + string); + } + } + + public static void success(String string) { + msg(C.GREEN + string); + } + + public static void info(String string) { + msg(C.WHITE + string); + } + + public static void messagePlayer(Player p, String string) { + String msg = C.GRAY + "[" + C.DARK_RED + "Adapt" + C.GRAY + "]: " + string; + p.sendMessage(msg); + } + + public static void msg(String string) { + try { + if (instance == null) { + System.out.println("[Adapt]: " + string); + return; + } + + String msg = C.GRAY + "[" + C.DARK_RED + "Adapt" + C.GRAY + "]: " + string; + Bukkit.getConsoleSender().sendMessage(msg); + } catch (Throwable e) { + System.out.println("[Adapt]: " + string); + } + } + + @Override + public void onLoad() { + manager = new AdvancementManager(); + if (getServer().getPluginManager().getPlugin("WorldGuard") != null) { + WorldGuardProtector.registerFlag(); + } + } + + @Override + public void start() { + runStartupPhaseVoid("backup-legacy-configs", ConfigMigrationManager::backupLegacyJsonConfigsOnce); + platform = PlatformUtils.createPlatform(this); + audiences = platform.getAudienceProvider(); + services = new KMap<>(); + runStartupPhaseVoid("discover-services", () -> initialize("art.arcane.adapt.service") + .forEach((i) -> services.put((Class) i.getClass(), (AdaptService) i))); + + runStartupPhaseVoid("language-update", Localizer::updateLanguageFile); + if (!runStartupPhase("models-load", CustomModel::reloadFromDisk)) { + Adapt.warn("Failed to load models config during startup migration."); + } + if (!AdaptConfig.get().isCustomModels()) { + CustomModel.clear(); + } + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { + new PapiExpansion().register(); + } + printInformation(); + sqlManager = new SQLManager(); + if (AdaptConfig.get().isUseSql()) { + runStartupPhase("sql-connect", () -> { + sqlManager.establishConnection(); + return null; + }); + } + redisSync = new RedisSync(); + playerDataPersistenceQueue = new PlayerDataPersistenceQueue(); + runStartupPhase("start-sim", () -> { + startSim(); + return null; + }); + runStartupPhase("config-canonicalization", () -> { + migrateAllSkillAndAdaptationConfigs(); + return null; + }); + CustomBlockData.registerListener(this); + registerListener(new BrewingManager()); + registerListener(new XpProvenanceListener()); + registerListener(new XpNoveltyListener()); + registerListener(Version.get()); + setupMetrics(); + startupPrint(); // Splash screen + if (AdaptConfig.get().isAutoUpdateCheck()) { + autoUpdateCheck(); + } + protectorRegistry = new ProtectorRegistry(); + if (getServer().getPluginManager().getPlugin("WorldGuard") != null) { + protectorRegistry.registerProtector(new WorldGuardProtector()); + } + if (getServer().getPluginManager().getPlugin("Factions") != null) { + protectorRegistry.registerProtector(new FactionsClaimProtector()); + } + if (getServer().getPluginManager().getPlugin("ChestProtect") != null) { + protectorRegistry.registerProtector(new ChestProtectProtector()); + } + if (getServer().getPluginManager().getPlugin("Residence") != null) { + protectorRegistry.registerProtector(new ResidenceProtector()); + } + if (getServer().getPluginManager().getPlugin("GriefDefender") != null) { + protectorRegistry.registerProtector(new GriefDefenderProtector()); + } + if (getServer().getPluginManager().getPlugin("GriefPrevention") != null) { + protectorRegistry.registerProtector(new GriefPreventionProtector()); + } + if (getServer().getPluginManager().getPlugin("LockettePro") != null) { + protectorRegistry.registerProtector(new LocketteProProtector()); + } + if (getServer().getPluginManager().getPlugin("HiddenOre") != null) { + HiddenOreLink.activate(this); + } + initializeGlowingEntities(); + initializeAdaptationListings(); + services.values().forEach(AdaptService::onEnable); + services.values().forEach(this::registerListener); + ConfigFileSupport.flushCreatedConfigSummary(); + } + + private static final Logger GLOWING_ENTITIES_LOGGER = Logger.getLogger("GlowingEntities"); + + private void initializeGlowingEntities() { + GLOWING_ENTITIES_LOGGER.setFilter(record -> record.getLevel().intValue() >= Level.WARNING.intValue()); + try { + glowingEntities = new GlowingEntities(this); + } catch (Throwable t) { + glowingEntities = null; + warn("GlowingEntities is unavailable: " + summarizeThrowable(t) + ". Glow-based effects will be disabled."); + } + } + + private String summarizeThrowable(Throwable throwable) { + if (throwable == null) { + return "unknown"; + } + + Throwable root = throwable; + while (root.getCause() != null && root.getCause() != root) { + root = root.getCause(); + } + + StringBuilder summary = new StringBuilder(throwable.getClass().getSimpleName()); + appendMessage(summary, throwable.getMessage()); + if (root != throwable) { + summary.append(" | cause=").append(root.getClass().getSimpleName()); + appendMessage(summary, root.getMessage()); + } + return summary.toString(); + } + + private void appendMessage(StringBuilder builder, String message) { + if (message != null && !message.isBlank()) { + builder.append(": ").append(message); + } + } + + private void migrateAllSkillAndAdaptationConfigs() { + if (adaptServer == null || adaptServer.getSkillRegistry() == null) { + return; + } + + if (!ConfigMigrationManager.hasLegacySkillOrAdaptationJsonFiles()) { + int deletedLegacyJson = ConfigMigrationManager.deleteMigratedLegacyJsonFiles(); + Adapt.info("Skipped skill/adaptation canonicalization (legacy json not found). deletedLegacyJson=" + deletedLegacyJson + "."); + return; + } + + int migratedSkills = 0; + int migratedAdaptations = 0; + for (Skill skill : adaptServer.getSkillRegistry().getSkills()) { + if (skill instanceof SimpleSkill simpleSkill) { + if (simpleSkill.reloadConfigFromDisk(false)) { + migratedSkills++; + } + } + + for (Adaptation adaptation : skill.getAdaptations()) { + if (adaptation instanceof SimpleAdaptation simpleAdaptation) { + if (simpleAdaptation.reloadConfigFromDisk(false)) { + migratedAdaptations++; + } + } + } + } + + int deletedLegacyJson = ConfigMigrationManager.deleteMigratedLegacyJsonFiles(); + Adapt.info("Canonicalized skill/adaptation configs to TOML (skills=" + migratedSkills + ", adaptations=" + migratedAdaptations + ", deletedLegacyJson=" + deletedLegacyJson + ")."); + } + + public void startSim() { + long startTicker = System.currentTimeMillis(); + ticker = new Ticker(); + verbose("start-sim detail: ticker init in " + (System.currentTimeMillis() - startTicker) + "ms"); + + long startServer = System.currentTimeMillis(); + adaptServer = new AdaptServer(); + long serverMs = System.currentTimeMillis() - startServer; + if (serverMs >= STARTUP_SLOW_PHASE_MS) { + warn("start-sim detail: AdaptServer init took " + serverMs + "ms."); + } else { + verbose("start-sim detail: AdaptServer init in " + serverMs + "ms"); + } + + long startAdv = System.currentTimeMillis(); + manager.enable(); + verbose("start-sim detail: advancement manager enable in " + (System.currentTimeMillis() - startAdv) + "ms"); + } + + public void postShutdown(Runnable r) { + postShutdown.add(r); + } + + public void stopSim() { + if (ticker != null) { + ticker.clear(); + } + postShutdown.forEach(Runnable::run); + if (adaptServer != null) { + adaptServer.unregister(); + } + if (manager != null) { + manager.disable(); + } + MaterialValue.save(); + WorldData.stop(); + CustomModel.clear(); + } + + @Override + public void stop() { + if (!alreadyDrained.compareAndSet(false, true)) { + return; + } + if (services != null) { + services.values().forEach(AdaptService::onDisable); + } + stopSim(); + if (playerDataPersistenceQueue != null) { + playerDataPersistenceQueue.flushAndShutdown(30_000L); + playerDataPersistenceQueue = null; + } + if (redisSync != null) { + try { + redisSync.close(); + } catch (Exception e) { + Adapt.verbose("Failed to close redis sync: " + e.getMessage()); + } finally { + redisSync = null; + } + } + if (sqlManager != null) { + sqlManager.closeConnection(); + } + if (glowingEntities != null) { + glowingEntities.disable(); + } + if (protectorRegistry != null) { + protectorRegistry.unregisterAll(); + } + if (services != null) { + services.clear(); + } + } + + @Override + public void onPreUnload(ReloadAware.PreUnloadReason reason) { + Adapt.info("BileTools pre-unload hook fired (" + reason + "). Draining Adapt (persistence flush + services)."); + stop(); + } + + private void startupPrint() { + if (!AdaptConfig.get().isSplashScreen()) { + return; + } + String supportedMcVersion = "26.2"; + Random r = new Random(); + int game = r.nextInt(100); + if (game < 90) { + Adapt.info("\n" + C.DARK_GRAY + " █████" + C.DARK_RED + "╗ " + C.DARK_GRAY + "██████" + C.DARK_RED + "╗ " + C.DARK_GRAY + "█████" + C.DARK_RED + "╗ " + C.DARK_GRAY + "██████" + C.DARK_RED + "╗ " + C.DARK_GRAY + "████████" + C.DARK_RED + "╗\n" + + C.DARK_GRAY + "██" + C.DARK_RED + "╔══" + C.DARK_GRAY + "██" + C.DARK_RED + "╗" + C.DARK_GRAY + "██" + C.DARK_RED + "╔══" + C.DARK_GRAY + "██" + C.DARK_RED + "╗" + C.DARK_GRAY + "██" + C.DARK_RED + "╔══" + C.DARK_GRAY + "██" + C.DARK_RED + "╗" + C.DARK_GRAY + "██" + C.DARK_RED + "╔══" + C.DARK_GRAY + "██" + C.DARK_RED + "╗╚══" + C.DARK_GRAY + "██" + C.DARK_RED + "╔══╝" + C.DARK_RED + " Adapt, " + C.RED + "Abilities Refined" + C.RED + "[" + getReleaseTrain(instance.getDescription().getVersion()) + " RELEASE]\n" + + C.DARK_GRAY + "███████" + C.DARK_RED + "║" + C.DARK_GRAY + "██" + C.DARK_RED + "║ " + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.DARK_GRAY + "███████" + C.DARK_RED + "║" + C.DARK_GRAY + "██████" + C.DARK_RED + "╔╝ " + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.WHITE + " Version: " + C.DARK_RED + instance.getDescription().getVersion() + " \n" + + C.DARK_GRAY + "██" + C.DARK_RED + "╔══" + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.DARK_GRAY + "██" + C.DARK_RED + "║ " + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.DARK_GRAY + "██" + C.DARK_RED + "╔══" + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.DARK_GRAY + "██" + C.DARK_RED + "╔═══╝ " + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.WHITE + " By: " + C.WHITE + "Volmit Software (Arcane Arts)\n" + + C.DARK_GRAY + "██" + C.DARK_RED + "║ " + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.DARK_GRAY + "██████" + C.DARK_RED + "╔╝" + C.DARK_GRAY + "██" + C.DARK_RED + "║ " + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.DARK_GRAY + "██" + C.DARK_RED + "║ " + C.DARK_GRAY + "██" + C.DARK_RED + "║" + C.WHITE + " Server: " + C.DARK_RED + getServerVersion() + C.WHITE + " | MC Support: " + C.DARK_RED + supportedMcVersion + "\n" + + C.DARK_RED + "╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ " + C.WHITE + " Java: " + C.DARK_RED + getJavaVersion() + C.WHITE + " | Date: " + C.DARK_RED + getStartupDate() + "\n"); + } else { + info(SecretSplash.getSecretSplash().getRandom()); + } + } + + public File getJarFile() { + return getFile(); + } + + @Override + public String getTag(String subTag) { + return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.DARK_RED + "Adapt" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; + } + + private void setupMetrics() { + if (AdaptConfig.get().isMetrics()) { + new Metrics(this, 24221); + } + } + +} diff --git a/src/main/java/art/arcane/adapt/AdaptConfig.java b/src/main/java/art/arcane/adapt/AdaptConfig.java new file mode 100644 index 000000000..c9f6abb8d --- /dev/null +++ b/src/main/java/art/arcane/adapt/AdaptConfig.java @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt; + +import art.arcane.adapt.api.xp.Curves; +import art.arcane.adapt.util.config.ConfigFileSupport; +import art.arcane.adapt.util.project.redis.RedisConfig; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Material; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("ALL") +@Getter +public class AdaptConfig { + private static final Object CONFIG_LOCK = new Object(); + private static AdaptConfig config = null; + public boolean debug = false; + public boolean autoUpdateCheck = true; + public boolean autoUpdateLanguage = true; + public boolean splashScreen = true; + public boolean xpInCreative = false; + public boolean allowAdaptationsInCreative = false; + public String adaptActivatorBlock = "BOOKSHELF"; + public String adaptActivatorBlockName = "a Bookshelf"; + public boolean adaptActivatorAllowVerticalFaces = false; + public List blacklistedWorlds = List.of("some_world_adapt_should_not_run_in", "anotherWorldFolderName"); + public int experienceMaxLevel = 1000; + boolean preventHunterSkillsWhenHungerApplied = true; + private ValueConfig value = new ValueConfig(); + private boolean metrics = true; + private String language = "en_US"; + private String fallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing = "en_US"; + private Curves xpCurve = Curves.ADAPT_BALANCED; + private double playerXpPerSkillLevelUpBase = 489; + private double playerXpPerSkillLevelUpLevelMultiplier = 44; + private double powerPerLevel = 0.65; + private boolean hardcoreResetOnPlayerDeath = false; + private boolean hardcoreNoRefunds = false; + private boolean loginBonus = true; + private boolean welcomeMessage = true; + private boolean advancements = true; + private boolean useSql = false; + private boolean useRedis = false; + private int sqlSecondsCheckverify = 30; + private boolean useEnchantmentTableParticleForActiveEffects = true; + private boolean escClosesAllGuis = false; + private boolean guiBackButton = false; + private boolean customModels = true; + private boolean automaticGradients = false; + private int learnUnlearnButtonDelayTicks = 14; + private int maxRecipeListPrecaution = 25; + private boolean actionbarNotifyXp = true; + private boolean actionbarNotifyLevel = true; + private boolean unlearnAllButton = false; + private Effects effects = new Effects(); + private FarmPrevention farmPrevention = new FarmPrevention(); + private AdaptationXp adaptationXp = new AdaptationXp(); + private XpIntegrity xpIntegrity = new XpIntegrity(); + private RedisConfig redis = new RedisConfig(); + private SqlSettings sql = new SqlSettings(); + private Protector protectorSupport = new Protector(); + private Map> adaptationUsageConflicts = defaultAdaptationUsageConflicts(); + private Map> protectionOverrides = Map.of( + "adaptation-name", Map.of( + "WorldGuard", true + ) + ); + + @Setter + private boolean verbose = false; + + public static AdaptConfig get() { + synchronized (CONFIG_LOCK) { + try { + if (config == null) { + config = loadConfig(new AdaptConfig(), true); + } + } catch (Throwable e) { + e.printStackTrace(); + config = new AdaptConfig(); + } + + return config; + } + } + + public static boolean reload() { + synchronized (CONFIG_LOCK) { + try { + config = loadConfig(config == null ? new AdaptConfig() : config, false); + return true; + } catch (Throwable e) { + return false; + } + } + } + + private static AdaptConfig loadConfig(AdaptConfig fallback, boolean overwriteOnFailure) throws IOException { + File canonicalFile = Adapt.instance.getDataFile("adapt", "adapt.toml"); + File legacyFile = Adapt.instance.getDataFile("adapt", "adapt.json"); + return ConfigFileSupport.load( + canonicalFile, + legacyFile, + AdaptConfig.class, + fallback, + overwriteOnFailure, + "core-config", + "Created missing config [adapt/adapt.toml] from defaults." + ); + } + + private static Map> defaultAdaptationUsageConflicts() { + return new HashMap<>(); + } + + @Getter + public static class Protector { + private boolean worldguard = true; + private boolean griefdefender = true; + private boolean factionsClaim = false; + private boolean residence = true; + private boolean chestProtect = true; + private boolean griefprevention = true; + private boolean lockettePro = true; + } + + @Getter + public static class SqlSettings { + private String host = "localhost"; + private int port = 1337; + private String database = "adapt"; + private String username = "user"; + private String password = "password"; + private int poolSize = 10; + private long connectionTimeout = 5000; + } + + @Getter + public static class ValueConfig { + private double baseValue = 1; + private Map valueMutlipliers = defaultValueMultipliersOverrides(); + + private Map defaultValueMultipliersOverrides() { + Map f = new HashMap<>(); + f.put(Material.BLAZE_ROD.name(), 50D); + f.put(Material.ENDER_PEARL.name(), 75D); + f.put(Material.GHAST_TEAR.name(), 100D); + f.put(Material.LEATHER.name(), 1.5D); + f.put(Material.BEEF.name(), 1.125D); + f.put(Material.PORKCHOP.name(), 1.125D); + f.put(Material.EGG.name(), 1.335D); + f.put(Material.CHICKEN.name(), 1.13D); + f.put(Material.MUTTON.name(), 1.125D); + f.put(Material.WHEAT.name(), 1.25D); + f.put(Material.BEETROOT.name(), 1.25D); + f.put(Material.CARROT.name(), 1.25D); + f.put(Material.FLINT.name(), 1.35D); + f.put(Material.IRON_ORE.name(), 1.75D); + f.put(Material.DIAMOND_ORE.name(), 5D); + f.put(Material.GOLD_ORE.name(), 4D); + f.put(Material.LAPIS_ORE.name(), 3.5D); + f.put(Material.COAL_ORE.name(), 1.35D); + f.put(Material.REDSTONE_ORE.name(), 4.5D); + f.put(Material.NETHER_GOLD_ORE.name(), 4.5D); + f.put(Material.NETHER_QUARTZ_ORE.name(), 1.11D); + return f; + } + } + + @Getter + public static class FarmPrevention { + private boolean enabled = true; + private boolean perActivityTracking = true; + private long skillRecoveryMillis = 180000; + private long activityRecoveryMillis = 300000; + private long activityStateTtlMillis = 1800000; + private double skillBasePressure = 1.0; + private double skillXpPressure = 0.02; + private double skillDecayCurve = 14.0; + private double skillFloorMultiplier = 0.08; + private double activityBasePressure = 1.0; + private double activityXpPressure = 0.03; + private double activityDecayCurve = 9.0; + private double activityFloorMultiplier = 0.12; + private double crossSkillRecoveryFactor = 0.9; + } + + @Getter + public static class XpIntegrity { + private boolean provenanceEnabled = true; + private long placedBlockTtlMillis = 86400000; + private long replaceDenyTtlMillis = 900000; + private boolean bonemealTrackingEnabled = true; + private long bonemealTtlMillis = 600000; + private double bonemealHarvestMultiplier = 0.5; + private boolean noveltyEnabled = true; + private int spatialCellShift = 2; + private int spatialCellCap = 256; + private long spatialCellTtlMillis = 900000; + private double spatialRepeatDecay = 0.3; + private double spatialFloorMultiplier = 0.25; + private int entropyWindow = 48; + private double entropyFloorMultiplier = 0.7; + private boolean adjacencyBonusEnabled = true; + private double adjacencyBonusMax = 0.25; + private double adjacencyBonusPerStreak = 0.05; + private boolean stillnessEnabled = true; + private long stillnessWindowMillis = 60000; + private int stillnessMinEvents = 20; + private double stillnessEpsilon = 0.75; + private double stillnessFloorMultiplier = 0.25; + private long fieldCycleMillis = 240000; + private double fieldCycleFloorMultiplier = 0.15; + private boolean pooledPayoutEnabled = true; + private long pooledWindowMillis = 30000; + private long pooledIdleFlushMillis = 8000; + private boolean inspiredNotifyEnabled = true; + } + + @Getter + public static class Effects { + private boolean particlesEnabled = true; + private boolean soundsEnabled = true; + private Map adaptationParticleOverrides = Map.of( + "adaptation-name", true + ); + private Map skillParticleOverrides = Map.of( + "skill-name", true + ); + } + + @Getter + public static class AdaptationXp { + private boolean usageBaselineEnabled = true; + private double usageBaselineXp = 0.8; + private double usageBaselineXpPerLevel = 0.18; + private long usageBaselineCooldownMillis = 12000; + } + +} diff --git a/src/main/java/art/arcane/adapt/PapiExpansion.java b/src/main/java/art/arcane/adapt/PapiExpansion.java new file mode 100644 index 000000000..97a0c66d9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/PapiExpansion.java @@ -0,0 +1,461 @@ +package art.arcane.adapt; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.skill.SkillRegistry; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.util.common.format.Localizer; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class PapiExpansion extends PlaceholderExpansion { + private static final Locale LOCALE = Locale.ROOT; + private volatile CatalogSnapshot catalogSnapshot = CatalogSnapshot.empty(); + + @Override + public @NotNull String getIdentifier() { + return Adapt.instance.getDescription().getName().toLowerCase(LOCALE); + } + + @Override + public @NotNull String getAuthor() { + return String.join(", ", Adapt.instance.getDescription().getAuthors()); + } + + @Override + public @NotNull String getVersion() { + return Adapt.instance.getDescription().getVersion(); + } + + @Override + public boolean persist() { + return true; + } + + @Override + public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) { + if (player == null || player.getUniqueId() == null || params == null || params.isBlank()) { + return ""; + } + if (Adapt.instance == null || Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { + return ""; + } + + PlayerData playerData = Adapt.instance.getAdaptServer().peekData(player.getUniqueId()); + if (playerData == null) { + return ""; + } + + String[] args = params.split("_"); + if (args.length == 0) { + return ""; + } + + String root = args[0].toLowerCase(LOCALE); + if ("player".equals(root)) { + String attr = args.length > 1 ? join(args, 1) : ""; + return resolvePlayerAttribute(playerData, attr); + } + + if ("skill".equals(root)) { + if (args.length < 3) { + return "0"; + } + String skillId = args[1]; + String attr = join(args, 2); + Skill skill = resolveSkill(skillId); + if (skill == null) { + return "0"; + } + PlayerSkillLine line = playerData.getSkillLineNullable(skill.getName()); + return resolveSkillAttribute(playerData, skill, line, attr); + } + + if ("adaptation".equals(root)) { + if (args.length < 3) { + return "0"; + } + String adaptationId = args[1]; + String attr = join(args, 2); + Adaptation adaptation = resolveAdaptation(adaptationId); + if (adaptation == null) { + return "0"; + } + return resolveAdaptationAttribute(playerData, adaptation, attr); + } + + if ("skills".equals(root) && args.length > 1 && "count".equalsIgnoreCase(args[1])) { + return String.valueOf(countSkills()); + } + + if ("adaptations".equals(root) && args.length > 1 && "count".equalsIgnoreCase(args[1])) { + return String.valueOf(countAdaptations()); + } + + if (args.length >= 2) { + Skill directSkill = resolveSkill(args[0]); + if (directSkill != null) { + String attr = join(args, 1); + PlayerSkillLine line = playerData.getSkillLineNullable(directSkill.getName()); + return resolveSkillAttribute(playerData, directSkill, line, attr); + } + + Adaptation directAdaptation = resolveAdaptation(args[0]); + if (directAdaptation != null) { + String attr = join(args, 1); + return resolveAdaptationAttribute(playerData, directAdaptation, attr); + } + } + + return null; + } + + private String resolvePlayerAttribute(PlayerData playerData, String rawAttr) { + String attr = rawAttr == null ? "" : rawAttr.toLowerCase(LOCALE); + return switch (attr) { + case "level" -> String.valueOf(playerData.getLevel()); + case "multiplier" -> format2(playerData.getMultiplier()); + case "availablepower" -> String.valueOf(playerData.getAvailablePower()); + case "maxpower" -> String.valueOf(playerData.getMaxPower()); + case "usedpower" -> String.valueOf(playerData.getUsedPower()); + case "wisdom" -> String.valueOf(playerData.getWisdom()); + case "masterxp" -> format2(playerData.getMasterXp()); + case "seenthings" -> String.valueOf(countSeenThings(playerData)); + case "skills", "skillcount" -> String.valueOf(countSkills()); + case "knownskills" -> String.valueOf(countKnownSkills(playerData)); + case "adaptations", "adaptationcount" -> + String.valueOf(countAdaptations()); + case "learnedadaptations" -> + String.valueOf(countLearnedAdaptations(playerData)); + default -> "0"; + }; + } + + private String resolveSkillAttribute(PlayerData playerData, Skill skill, PlayerSkillLine line, String rawAttr) { + String attr = rawAttr == null ? "" : rawAttr.toLowerCase(LOCALE); + int currentLevel = line == null ? 0 : line.getLevel(); + + if (attr.startsWith("can_claim_") || attr.startsWith("has_level_")) { + Integer targetLevel = parseTrailingInt(attr); + if (targetLevel == null) { + return "false"; + } + return String.valueOf(targetLevel >= 0 && targetLevel <= 100 && currentLevel >= targetLevel); + } + + int learnedAdaptations = 0; + if (line != null) { + for (PlayerAdaptation adaptation : line.getAdaptations().values()) { + if (adaptation != null && adaptation.getLevel() > 0) { + learnedAdaptations++; + } + } + } + + return switch (attr) { + case "id" -> skill.getName(); + case "enabled" -> String.valueOf(skill.isEnabled()); + case "name" -> Localizer.dLocalize("skill." + skill.getName() + ".name"); + case "display_name", "displayname" -> skill.getDisplayName(); + case "short_name", "shortname" -> skill.getShortName(); + case "level" -> String.valueOf(currentLevel); + case "knowledge" -> + String.valueOf(line == null ? 0 : line.getKnowledge()); + case "xp" -> format2(line == null ? 0 : line.getXp()); + case "freshness" -> format2(line == null ? 0 : line.getFreshness()); + case "multiplier" -> format2(line == null ? 0 : line.getMultiplier()); + case "absolute_level", "absolutelevel" -> + format4(line == null ? 0 : line.getAbsoluteLevel()); + case "progress" -> format4(line == null ? 0 : line.getLevelProgress()); + case "progress_percent", "progresspercent" -> + format2((line == null ? 0 : line.getLevelProgress()) * 100D); + case "xp_to_next", "xptonext", "xp_needed", "xpneeded" -> + format2(line == null ? 0 : XP.getXpUntilLevelUp(line.getXp())); + case "current_level_xp", "currentlevelxp" -> + format2(XP.getXpForLevel(currentLevel)); + case "next_level_xp", "nextlevelxp" -> + format2(XP.getXpForLevel(currentLevel + 1)); + case "adaptation_count", "adaptationcount" -> + String.valueOf(skill.getAdaptations().size()); + case "learned_adaptations", "learnedadaptations" -> + String.valueOf(learnedAdaptations); + case "can_use" -> String.valueOf(currentLevel > 0); + case "line" -> skill.getName(); + case "maxlevel", "max_level" -> + String.valueOf(AdaptConfig.get().experienceMaxLevel); + default -> "0"; + }; + } + + private String resolveAdaptationAttribute(PlayerData playerData, Adaptation adaptation, String rawAttr) { + String attr = rawAttr == null ? "" : rawAttr.toLowerCase(LOCALE); + PlayerSkillLine line = playerData.getSkillLineNullable(adaptation.getSkill().getName()); + int currentLevel = line == null ? 0 : line.getAdaptationLevel(adaptation.getName()); + + if (attr.startsWith("can_claim_")) { + Integer target = parseTrailingInt(attr); + if (target == null) { + return "false"; + } + int targetLevel = clampAdaptationTarget(adaptation, target); + return String.valueOf(canClaimTarget(playerData, line, adaptation, currentLevel, targetLevel)); + } + + if (attr.startsWith("cost_to_") || attr.startsWith("knowledge_to_")) { + Integer target = parseTrailingInt(attr); + if (target == null) { + return "0"; + } + int targetLevel = clampAdaptationTarget(adaptation, target); + return String.valueOf(adaptation.getCostFor(targetLevel, currentLevel)); + } + + if (attr.startsWith("power_to_")) { + Integer target = parseTrailingInt(attr); + if (target == null) { + return "0"; + } + int targetLevel = clampAdaptationTarget(adaptation, target); + int powerCost = adaptation.getPowerCostFor(targetLevel, currentLevel); + return String.valueOf(Math.max(0, powerCost)); + } + + if (attr.startsWith("refund_to_")) { + Integer target = parseTrailingInt(attr); + if (target == null) { + return "0"; + } + int targetLevel = clampAdaptationTarget(adaptation, target); + return String.valueOf(adaptation.getRefundCostFor(targetLevel, currentLevel)); + } + + int nextLevel = Math.min(adaptation.getMaxLevel(), currentLevel + 1); + return switch (attr) { + case "id" -> adaptation.getName(); + case "level" -> String.valueOf(currentLevel); + case "maxlevel", "max_level" -> String.valueOf(adaptation.getMaxLevel()); + case "name", "display_name", "displayname" -> adaptation.getDisplayName(); + case "raw_name", "rawname" -> adaptation.getName(); + case "skill", "skill_id", "skillid" -> adaptation.getSkill().getName(); + case "enabled" -> String.valueOf(adaptation.isEnabled()); + case "learned" -> String.valueOf(currentLevel > 0); + case "can_use" -> + String.valueOf(currentLevel > 0 && adaptation.isEnabled() && adaptation.getSkill().isEnabled()); + case "cost_next", "costnext", "knowledge_next", "knowledgenext" -> + String.valueOf(currentLevel >= adaptation.getMaxLevel() ? 0 : adaptation.getCostFor(nextLevel, currentLevel)); + case "power_next", "powernext" -> + String.valueOf(currentLevel >= adaptation.getMaxLevel() ? 0 : Math.max(0, adaptation.getPowerCostFor(nextLevel, currentLevel))); + case "refund_next", "refundnext" -> + String.valueOf(currentLevel <= 0 ? 0 : adaptation.getRefundCostFor(Math.max(0, currentLevel - 1), currentLevel)); + case "can_claim_next", "canclaimnext" -> + String.valueOf(canClaimTarget(playerData, line, adaptation, currentLevel, nextLevel)); + default -> "0"; + }; + } + + private boolean canClaimTarget(PlayerData playerData, PlayerSkillLine line, Adaptation adaptation, int currentLevel, int targetLevel) { + if (targetLevel == currentLevel) { + return true; + } + + if (targetLevel > currentLevel) { + int powerCost = Math.max(0, adaptation.getPowerCostFor(targetLevel, currentLevel)); + int knowledgeCost = adaptation.getCostFor(targetLevel, currentLevel); + if (!playerData.hasPowerAvailable(powerCost)) { + return false; + } + return line != null && line.getKnowledge() >= knowledgeCost; + } + + if (adaptation.isPermanent()) { + return false; + } + + return currentLevel > 0; + } + + private int clampAdaptationTarget(Adaptation adaptation, int requested) { + int clamped = Math.max(0, Math.min(requested, 100)); + return Math.min(clamped, adaptation.getMaxLevel()); + } + + private Skill resolveSkill(String rawSkillId) { + if (rawSkillId == null || rawSkillId.isBlank()) { + return null; + } + + String skillId = rawSkillId.trim(); + SkillRegistry registry = Adapt.instance.getAdaptServer().getSkillRegistry(); + return registry.getAnySkill(skillId); + } + + private Adaptation resolveAdaptation(String rawAdaptationId) { + if (rawAdaptationId == null || rawAdaptationId.isBlank()) { + return null; + } + + String adaptationId = normalizeCatalogKey(rawAdaptationId); + return getCatalogSnapshot().findAdaptation(adaptationId); + } + + private int countSeenThings(PlayerData playerData) { + int total = 0; + total += playerData.getSeenBlocks().getSeen().size(); + total += playerData.getSeenBiomes().getSeen().size(); + total += playerData.getSeenEnchants().getSeen().size(); + total += playerData.getSeenEnvironments().getSeen().size(); + total += playerData.getSeenFoods().getSeen().size(); + total += playerData.getSeenItems().getSeen().size(); + total += playerData.getSeenMobs().getSeen().size(); + total += playerData.getSeenPeople().getSeen().size(); + total += playerData.getSeenPotionEffects().getSeen().size(); + total += playerData.getSeenRecipes().getSeen().size(); + total += playerData.getSeenWorlds().getSeen().size(); + return total; + } + + private int countSkills() { + return getCatalogSnapshot().skillCount(); + } + + private int countKnownSkills(PlayerData playerData) { + int total = 0; + for (PlayerSkillLine line : playerData.getSkillLines().values()) { + if (line != null && line.getLevel() > 0) { + total++; + } + } + return total; + } + + private int countAdaptations() { + return getCatalogSnapshot().adaptationCount(); + } + + private int countLearnedAdaptations(PlayerData playerData) { + int total = 0; + for (PlayerSkillLine line : playerData.getSkillLines().values()) { + if (line == null) { + continue; + } + for (PlayerAdaptation adaptation : line.getAdaptations().values()) { + if (adaptation != null && adaptation.getLevel() > 0) { + total++; + } + } + } + return total; + } + + private Integer parseTrailingInt(String attr) { + int index = attr.lastIndexOf('_'); + if (index == -1 || index >= attr.length() - 1) { + return null; + } + + try { + return Integer.parseInt(attr.substring(index + 1)); + } catch (NumberFormatException ignored) { + return null; + } + } + + private String join(String[] args, int start) { + if (args == null || start >= args.length) { + return ""; + } + StringBuilder builder = new StringBuilder(); + for (int i = start; i < args.length; i++) { + if (builder.length() > 0) { + builder.append('_'); + } + builder.append(args[i]); + } + return builder.toString(); + } + + private String format2(double value) { + return String.format(LOCALE, "%.2f", value); + } + + private String format4(double value) { + return String.format(LOCALE, "%.4f", value); + } + + private CatalogSnapshot getCatalogSnapshot() { + SkillRegistry registry = Adapt.instance.getAdaptServer().getSkillRegistry(); + long revision = registry.getCatalogRevision(); + CatalogSnapshot snapshot = catalogSnapshot; + if (snapshot.revision() == revision) { + return snapshot; + } + + CatalogSnapshot rebuilt = CatalogSnapshot.create(revision, registry.getAllSkills()); + catalogSnapshot = rebuilt; + return rebuilt; + } + + private static String normalizeCatalogKey(String raw) { + return raw.trim().toLowerCase(LOCALE); + } + + private static void indexAdaptationIdentifier(Map> index, String rawKey, Adaptation adaptation) { + if (rawKey == null || rawKey.isBlank() || adaptation == null) { + return; + } + + index.putIfAbsent(normalizeCatalogKey(rawKey), adaptation); + } + + private static CatalogSnapshot buildCatalogSnapshot(long revision, List> skills) { + Map> adaptationsById = new HashMap<>(); + int adaptationCount = 0; + for (Skill skill : skills) { + if (skill == null) { + continue; + } + + List> adaptations = skill.getAdaptations(); + adaptationCount += adaptations.size(); + for (Adaptation adaptation : adaptations) { + if (adaptation == null) { + continue; + } + + indexAdaptationIdentifier(adaptationsById, adaptation.getName(), adaptation); + String fullId = adaptation.getId(); + indexAdaptationIdentifier(adaptationsById, fullId, adaptation); + if (fullId != null && fullId.length() > 37) { + indexAdaptationIdentifier(adaptationsById, fullId.substring(37), adaptation); + } + indexAdaptationIdentifier(adaptationsById, adaptation.getSkill().getName() + ":" + adaptation.getName(), adaptation); + } + } + + return new CatalogSnapshot(revision, skills.size(), adaptationCount, Map.copyOf(adaptationsById)); + } + + private record CatalogSnapshot(long revision, int skillCount, int adaptationCount, Map> adaptationsById) { + private static CatalogSnapshot empty() { + return new CatalogSnapshot(-1L, 0, 0, Map.of()); + } + + private static CatalogSnapshot create(long revision, List> skills) { + return buildCatalogSnapshot(revision, skills); + } + + private Adaptation findAdaptation(String normalizedId) { + return adaptationsById.get(normalizedId); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/Component.java b/src/main/java/art/arcane/adapt/api/Component.java new file mode 100644 index 000000000..c95b8d13c --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/Component.java @@ -0,0 +1,1185 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.value.MaterialValue; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import com.francobm.magicosmetics.api.CosmeticType; +import com.francobm.magicosmetics.api.MagicAPI; +import com.google.common.collect.Lists; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.IntConsumer; +import java.util.function.Predicate; + +import static art.arcane.adapt.util.reflect.registries.Particles.ENCHANTMENT_TABLE; +import static art.arcane.adapt.util.reflect.registries.Particles.REDSTONE; +import static xyz.xenondevs.particle.utils.MathUtils.RANDOM; + +public interface Component { + Set NON_ADAPTABLE_DAMAGE_CAUSES = Set.of( + EntityDamageEvent.DamageCause.VOID, + EntityDamageEvent.DamageCause.LAVA, + EntityDamageEvent.DamageCause.HOT_FLOOR, + EntityDamageEvent.DamageCause.CRAMMING, + EntityDamageEvent.DamageCause.MELTING, + EntityDamageEvent.DamageCause.SUFFOCATION, + EntityDamageEvent.DamageCause.SUICIDE, + EntityDamageEvent.DamageCause.WITHER, + EntityDamageEvent.DamageCause.FLY_INTO_WALL, + EntityDamageEvent.DamageCause.FALL, + EntityDamageEvent.DamageCause.SONIC_BOOM, + EntityDamageEvent.DamageCause.THORNS + ); + + default boolean areParticlesEnabled() { + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + return effects == null || effects.isParticlesEnabled(); + } + + default boolean areSoundsEnabled() { + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + return effects == null || effects.isSoundsEnabled(); + } + + default void wisdom(Player p, long w) { + XP.wisdom(p, w); + } + + /** + * Attempts to "damage" an item. 1. If the item is null, null is returned 2. + * If the item doesnt have durability, (damage) amount will be consumed from + * the stack, null will be returned if more consumed than amount 3. If the + * item has durability, the damage will be consuemd and return the item + * affected, OR null if it broke + * + * @param item the item (tool) + * @param damage the damage to cause + * @return the damaged item or null if destroyed + */ + default ItemStack damage(ItemStack item, int damage) { + if (item == null) { + return null; + } + + if (item.getItemMeta() == null) { + if (item.getAmount() == 1) { + return null; + } + + item = item.clone(); + item.setAmount(item.getAmount() - 1); + return item; + } + + if (item.getItemMeta() instanceof Damageable d) { + if (d.getDamage() + 1 > item.getType().getMaxDurability()) { + return null; + } + + d.setDamage(d.getDamage() + 1); + item = item.clone(); + item.setItemMeta(d); + return item; + } else { + if (item.getAmount() == 1) { + return null; + } + + item = item.clone(); + item.setAmount(item.getAmount() - 1); + + return item; + } + } + + default void decrementItemstack(ItemStack hand, Player p) { + if (hand.getAmount() > 1) { + hand.setAmount(hand.getAmount() - 1); + } else { + p.getInventory().setItemInMainHand(null); + } + } + + default double getArmorValue(Player player) { + org.bukkit.inventory.PlayerInventory inv = player.getInventory(); + ItemStack boots = inv.getBoots(); + ItemStack helmet = inv.getHelmet(); + ItemStack chest = inv.getChestplate(); + ItemStack pants = inv.getLeggings(); + double armorValue = 0.0; + if (helmet == null) armorValue = armorValue + 0.0; + else if (Bukkit.getServer().getPluginManager().getPlugin("MagicCosmetics") != null && MagicAPI.hasEquipCosmetic(player, CosmeticType.HAT)) { + armorValue = armorValue + 0; + } else if (helmet.getType() == Material.LEATHER_HELMET) + armorValue = armorValue + 0.04; + else if (helmet.getType() == Material.GOLDEN_HELMET) + armorValue = armorValue + 0.08; + else if (helmet.getType() == Material.TURTLE_HELMET) + armorValue = armorValue + 0.08; + else if (helmet.getType() == Material.CHAINMAIL_HELMET) + armorValue = armorValue + 0.08; + else if (helmet.getType() == Material.IRON_HELMET) + armorValue = armorValue + 0.08; + else if (helmet.getType() == Material.DIAMOND_HELMET) + armorValue = armorValue + 0.12; + else if (helmet.getType() == Material.NETHERITE_HELMET) + armorValue = armorValue + 0.12; + // + if (boots == null) armorValue = armorValue + 0.0; + else if (boots.getType() == Material.LEATHER_BOOTS) + armorValue = armorValue + 0.04; + else if (boots.getType() == Material.GOLDEN_BOOTS) + armorValue = armorValue + 0.04; + else if (boots.getType() == Material.CHAINMAIL_BOOTS) + armorValue = armorValue + 0.04; + else if (boots.getType() == Material.IRON_BOOTS) + armorValue = armorValue + 0.08; + else if (boots.getType() == Material.DIAMOND_BOOTS) + armorValue = armorValue + 0.12; + else if (boots.getType() == Material.NETHERITE_BOOTS) + armorValue = armorValue + 0.12; + // + if (pants == null) armorValue = armorValue + 0.0; + else if (pants.getType() == Material.LEATHER_LEGGINGS) + armorValue = armorValue + 0.08; + else if (pants.getType() == Material.GOLDEN_LEGGINGS) + armorValue = armorValue + 0.12; + else if (pants.getType() == Material.CHAINMAIL_LEGGINGS) + armorValue = armorValue + 0.16; + else if (pants.getType() == Material.IRON_LEGGINGS) + armorValue = armorValue + 0.20; + else if (pants.getType() == Material.DIAMOND_LEGGINGS) + armorValue = armorValue + 0.24; + else if (pants.getType() == Material.NETHERITE_LEGGINGS) + armorValue = armorValue + 0.24; + // + if (chest == null) armorValue = armorValue + 0.0; + else if (Bukkit.getServer().getPluginManager().getPlugin("MagicCosmetics") != null && MagicAPI.hasEquipCosmetic(player, CosmeticType.BAG)) { + armorValue = armorValue + 0; + } else if (chest.getType() == Material.LEATHER_CHESTPLATE) + armorValue = armorValue + 0.12; + else if (chest.getType() == Material.GOLDEN_CHESTPLATE) + armorValue = armorValue + 0.20; + else if (chest.getType() == Material.CHAINMAIL_CHESTPLATE) + armorValue = armorValue + 0.20; + else if (chest.getType() == Material.IRON_CHESTPLATE) + armorValue = armorValue + 0.24; + else if (chest.getType() == Material.DIAMOND_CHESTPLATE) + armorValue = armorValue + 0.32; + else if (chest.getType() == Material.NETHERITE_CHESTPLATE) + armorValue = armorValue + 0.32; + return armorValue; + } + + default boolean isAdaptableDamageCause(EntityDamageEvent event) { + return !NON_ADAPTABLE_DAMAGE_CAUSES.contains(event.getCause()); + } + + default void addPotionStacks(Player p, PotionEffectType potionEffect, int amplifier, int duration, boolean overlap) { + List activeEffects = new ArrayList<>(p.getActivePotionEffects()); + SoundPlayer sp = SoundPlayer.of(p); + for (PotionEffect activeEffect : activeEffects) { + if (activeEffect.getType() == potionEffect) { + if (!overlap) { + return; // don't modify the effect if overlap is false + } + // modify the effect if overlap is true + int newDuration = activeEffect.getDuration() + duration; + int newAmplifier = Math.max(activeEffect.getAmplifier(), amplifier); + p.removePotionEffect(potionEffect); + p.addPotionEffect(new PotionEffect(potionEffect, newDuration, newAmplifier)); + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.25f); + return; + } + } + // if we didn't find an existing effect, add a new one + J.s(() -> { + p.addPotionEffect(new PotionEffect(potionEffect, duration, amplifier)); + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.25f); + }, 1); + + } + + + default void potion(Player p, PotionEffectType type, int power, int duration) { + p.addPotionEffect(new PotionEffect(type, power, duration, true, false, false)); + } + + default double blockXP(Block block, double xp) { + try { + return Math.round(xp * getBlockMultiplier(block)); + } catch (Exception e) { + Adapt.verbose("Error in blockXP: " + e.getMessage()); + } + return xp; + } + + default double getBlockMultiplier(Block block) { + return WorldData.of(block.getWorld()).reportEarnings(block); + } + + default double getValue(Material material) { + return MaterialValue.getValue(material); + } + + default double getValue(BlockData block) { + return MaterialValue.getValue(block.getMaterial()); + } + + default double getValue(ItemStack f) { + return MaterialValue.getValue(f.getType()); + } + + default double getValue(Block block) { + return MaterialValue.getValue(block.getType()); + } + + default void runVisualLoop(int durationTicks, IntConsumer onTick) { + if (durationTicks <= 0) { + return; + } + + int[] tick = {0}; + Runnable[] loop = new Runnable[1]; + loop[0] = () -> { + if (tick[0] >= durationTicks) { + return; + } + + onTick.accept(tick[0]); + tick[0]++; + if (tick[0] < durationTicks) { + J.s(loop[0], 1); + } + }; + J.s(loop[0]); + } + + default void vfxMovingSphere(Location startLocation, Location endLocation, int ticks, Color color, double size, double density) { + if (!areParticlesEnabled()) { + return; + } + + if (ticks <= 0) { + return; + } + + int durationTicks = ticks; + World world = startLocation.getWorld(); + double startX = startLocation.getX(); + double startY = startLocation.getY(); + double startZ = startLocation.getZ(); + double endX = endLocation.getX(); + double endY = endLocation.getY(); + double endZ = endLocation.getZ(); + double deltaX = (endX - startX) / durationTicks; + double deltaY = (endY - startY) / durationTicks; + double deltaZ = (endZ - startZ) / durationTicks; + Particle.DustOptions dustOptions = new Particle.DustOptions(color, (float) size); + + runVisualLoop(durationTicks, tick -> { + double x = startX + deltaX * tick; + double y = startY + deltaY * tick; + double z = startZ + deltaZ * tick; + Location particleLocation = new Location(world, x, y, z); + + for (double i = 0; i < Math.PI; i += Math.PI / density) { + double radius = Math.sin(i) * size; + double yCoord = Math.cos(i) * size; + for (double j = 0; j < Math.PI * 2; j += Math.PI / density) { + double xCoord = Math.sin(j) * radius; + double zCoord = Math.cos(j) * radius; + + Location loc = particleLocation.clone().add(xCoord, yCoord, zCoord); + world.spawnParticle(REDSTONE, loc, 0, 0, 0, 0, dustOptions); + } + } + }); + } + + default void vfxMovingSwirlingSphere(Location startLocation, Location endLocation, int ticks, Color color, double size, double swirlRadius, double density) { + if (!areParticlesEnabled()) { + return; + } + + if (ticks <= 0) { + return; + } + + int durationTicks = ticks; + World world = startLocation.getWorld(); + double startX = startLocation.getX(); + double startY = startLocation.getY(); + double startZ = startLocation.getZ(); + double endX = endLocation.getX(); + double endY = endLocation.getY(); + double endZ = endLocation.getZ(); + double deltaX = (endX - startX) / durationTicks; + double deltaY = (endY - startY) / durationTicks; + double deltaZ = (endZ - startZ) / durationTicks; + Particle.DustOptions dustOptions = new Particle.DustOptions(color, (float) size); + + runVisualLoop(durationTicks, tick -> { + double x = startX + deltaX * tick; + double y = startY + deltaY * tick; + double z = startZ + deltaZ * tick; + + // Add swirling effect + double swirlAngle = 2 * Math.PI * tick / durationTicks; + x += swirlRadius * Math.cos(swirlAngle); + z += swirlRadius * Math.sin(swirlAngle); + + Location particleLocation = new Location(world, x, y, z); + + for (double i = 0; i < Math.PI; i += Math.PI / density) { + double radius = Math.sin(i) * size; + double yCoord = Math.cos(i) * size; + for (double j = 0; j < Math.PI * 2; j += Math.PI / density) { + double xCoord = Math.sin(j) * radius; + double zCoord = Math.cos(j) * radius; + + Location loc = particleLocation.clone().add(xCoord, yCoord, zCoord); + world.spawnParticle(REDSTONE, loc, 0, 0, 0, 0, dustOptions); + } + } + }); + } + + default void vfxPlayerBoundingBoxOutline(Player player, Color color, int ticks, int particleCount) { + if (!areParticlesEnabled()) { + return; + } + + World world = player.getWorld(); + Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f); + + runVisualLoop(ticks, tick -> { + BoundingBox boundingBox = player.getBoundingBox(); + double minX = boundingBox.getMinX(); + double minY = boundingBox.getMinY(); + double minZ = boundingBox.getMinZ(); + double maxX = boundingBox.getMaxX(); + double maxY = boundingBox.getMaxY(); + double maxZ = boundingBox.getMaxZ(); + + for (int i = 0; i < particleCount; i++) { + double t = (double) i / (particleCount - 1); + + // Edges along X-axis + world.spawnParticle(REDSTONE, minX + t * (maxX - minX), minY, minZ, 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, minX + t * (maxX - minX), maxY, minZ, 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, minX + t * (maxX - minX), minY, maxZ, 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, minX + t * (maxX - minX), maxY, maxZ, 0, 0, 0, 0, dustOptions); + + // Edges along Y-axis + world.spawnParticle(REDSTONE, minX, minY + t * (maxY - minY), minZ, 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, maxX, minY + t * (maxY - minY), minZ, 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, minX, minY + t * (maxY - minY), maxZ, 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, maxX, minY + t * (maxY - minY), maxZ, 0, 0, 0, 0, dustOptions); + + // Edges along Z-axis + world.spawnParticle(REDSTONE, minX, minY, minZ + t * (maxZ - minZ), 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, maxX, minY, minZ + t * (maxZ - minZ), 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, minX, maxY, minZ + t * (maxZ - minZ), 0, 0, 0, 0, dustOptions); + world.spawnParticle(REDSTONE, maxX, maxY, minZ + t * (maxZ - minZ), 0, 0, 0, 0, dustOptions); + } + }); + } + + default void vfxVortexSphere(Location startLocation, Location endLocation, int ticks, Color color, double radius) { + if (!areParticlesEnabled()) { + return; + } + + if (ticks <= 0) { + return; + } + + int durationTicks = ticks; + World world = startLocation.getWorld(); + Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f); + + double startX = startLocation.getX(); + double startY = startLocation.getY(); + double startZ = startLocation.getZ(); + double endX = endLocation.getX(); + double endY = endLocation.getY(); + double endZ = endLocation.getZ(); + double deltaX = (endX - startX) / durationTicks; + double deltaY = (endY - startY) / durationTicks; + double deltaZ = (endZ - startZ) / durationTicks; + + runVisualLoop(durationTicks, tick -> { + double x = startX + deltaX * tick; + double y = startY + deltaY * tick; + double z = startZ + deltaZ * tick; + Location particleLocation = new Location(world, x, y, z); + + double currentRadius = radius * (1 - (double) tick / durationTicks); + + for (double theta = 0; theta < 2 * Math.PI; theta += Math.PI / 10) { + for (double phi = 0; phi < Math.PI; phi += Math.PI / 10) { + double xCoord = currentRadius * Math.sin(phi) * Math.cos(theta); + double yCoord = currentRadius * Math.sin(phi) * Math.sin(theta); + double zCoord = currentRadius * Math.cos(phi); + + Location loc = particleLocation.clone().add(xCoord, yCoord, zCoord); + world.spawnParticle(REDSTONE, loc, 0, 0, 0, 0, dustOptions); + } + } + }); + } + + + default void vfxDome(Location center, double range, Color color, int particleCount) { + if (!areParticlesEnabled()) { + return; + } + + Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1); + World world = center.getWorld(); + + for (int i = 0; i < particleCount; i++) { + double theta = 2 * Math.PI * RANDOM.nextDouble(); + double phi = Math.PI / 2 * RANDOM.nextDouble(); // Adjusted range of phi to create a dome + double x = range * Math.sin(phi) * Math.cos(theta); + double y = range * Math.sin(phi) * Math.sin(theta); + double z = range * Math.cos(phi); + + Location particleLocation = center.clone().add(x, y, z); + world.spawnParticle(REDSTONE, particleLocation, 0, 0, 0, 0, dustOptions); + } + } + + default void vfxSphereV1(Player p, Location l, double radius, Particle particle, int verticalDensity, int radialDensity) { + if (!areParticlesEnabled()) { + return; + } + + for (double phi = 0; phi <= Math.PI; phi += Math.PI / verticalDensity) { + for (double theta = 0; theta <= 2 * Math.PI; theta += Math.PI / radialDensity) { + double x = radius * Math.cos(theta) * Math.sin(phi); + double y = radius * Math.cos(phi) + 1.5; + double z = radius * Math.sin(theta) * Math.sin(phi); + + l.add(x, y, z); + p.getWorld().spawnParticle(particle, l, 1, 0F, 0F, 0F, 0.001); + l.subtract(x, y, z); + } + } + } + + + default void vfxZuck(Location from, Location to) { + if (!areParticlesEnabled()) { + return; + } + + Vector v = from.clone().subtract(to).toVector(); + double l = v.length(); + v.normalize(); + if (AdaptConfig.get().isUseEnchantmentTableParticleForActiveEffects()) { + from.getWorld().spawnParticle(ENCHANTMENT_TABLE, to, 1, 6, 6, 6, 0.6); + } + } + + default void vfxZuck(Location from, Location to, Particle particle) { + if (!areParticlesEnabled()) { + return; + } + + Vector v = from.clone().subtract(to).toVector(); + double l = v.length(); + v.normalize(); + if (AdaptConfig.get().isUseEnchantmentTableParticleForActiveEffects()) { + from.getWorld().spawnParticle(particle, to, 1, 6, 6, 6, 0.6); + } + } + + default boolean safeGiveItem(Player player, Entity itemEntity, ItemStack is) { + if (!(itemEntity instanceof Item item) || !item.isValid() || is == null || is.getType().isAir() || is.getAmount() <= 0) { + return false; + } + + EntityPickupItemEvent e = new EntityPickupItemEvent(player, item, 0); + Bukkit.getPluginManager().callEvent(e); + if (e.isCancelled()) { + return false; + } + + int requested = is.getAmount(); + Map leftover = player.getInventory().addItem(is.clone()); + if (leftover.isEmpty()) { + item.remove(); + return true; + } + + ItemStack remaining = leftover.values().iterator().next(); + if (remaining == null || remaining.getAmount() >= requested) { + return false; + } + + item.setItemStack(remaining); + return true; + } + + default boolean canSnatchItem(Player player, Item item) { + if (!item.isValid() || item.isInvulnerable()) { + return false; + } + + if (item.getPickupDelay() >= Short.MAX_VALUE) { + return false; + } + + UUID owner = item.getOwner(); + if (owner != null && !owner.equals(player.getUniqueId())) { + return false; + } + + if (item.hasMetadata("NPC") || item.hasMetadata("shopitem") || item.hasMetadata("hologram")) { + return false; + } + + try { + return item.canPlayerPickup(); + } catch (NoSuchMethodError ignored) { + return true; + } + } + + + default void safeGiveItem(Player player, ItemStack item) { + if (!player.getInventory().addItem(item).isEmpty()) { + player.getWorld().dropItem(player.getLocation(), item); + } + } + + + default void vfxParticleLine(Location start, Location end, Particle particle, int pointsPerLine, int particleCount, double offsetX, double offsetY, double offsetZ, double extra, @Nullable Double data, boolean forceDisplay, + @Nullable Predicate operationPerPoint) { + if (!areParticlesEnabled()) { + return; + } + + double d = start.distance(end) / pointsPerLine; + for (int i = 0; i < pointsPerLine; i++) { + Location l = start.clone(); + Vector direction = end.toVector().subtract(start.toVector()).normalize(); + Vector v = direction.multiply(i * d); + l.add(v.getX(), v.getY(), v.getZ()); + if (operationPerPoint == null) { + start.getWorld().spawnParticle(particle, l, particleCount, offsetX, offsetY, offsetZ, extra, data, forceDisplay); + continue; + } + boolean allowed; + try { + allowed = operationPerPoint.test(l); + } catch (IllegalStateException ex) { + // Folia region ownership checks can reject block probes off-thread. + // Skip this point instead of failing the entire visual effect. + Adapt.verbose("Skipping particle line point due to region ownership rejection: " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + allowed = false; + } + + if (allowed) { + start.getWorld().spawnParticle(particle, l, particleCount, offsetX, offsetY, offsetZ, extra, data, forceDisplay); + } + } + } + + default void vfxParticleLine(Location start, Location end, int particleCount, Particle particle) { + if (!areParticlesEnabled()) { + return; + } + + World world = start.getWorld(); + double distance = start.distance(end); + Vector direction = end.toVector().subtract(start.toVector()).normalize(); + double step = distance / (particleCount - 1); + + for (int i = 0; i < particleCount; i++) { + Location particleLocation = start.clone().add(direction.clone().multiply(i * step)); + world.spawnParticle(particle, particleLocation, 1); + } + } + + + private List getHollowCuboid(Location loc, double particleDistance) { + List result = Lists.newArrayList(); + World world = loc.getWorld(); + double minX = loc.getBlockX(); + double minY = loc.getBlockY(); + double minZ = loc.getBlockZ(); + double maxX = loc.getBlockX() + 1; + double maxY = loc.getBlockY() + 1; + double maxZ = loc.getBlockZ() + 1; + + for (double x = minX; x <= maxX; x += particleDistance) { + for (double y = minY; y <= maxY; y += particleDistance) { + for (double z = minZ; z <= maxZ; z += particleDistance) { + int components = 0; + if (x == minX || x == maxX) components++; + if (y == minY || y == maxY) components++; + if (z == minZ || z == maxZ) components++; + if (components >= 2) { + result.add(new Location(world, x, y, z)); + } + } + } + } + return result; + } + + private List getHollowCuboid(Location loc, Location loc2, double particleDistance) { + List result = Lists.newArrayList(); + World world = loc.getWorld(); + + double minX = loc.getBlockX(); + double minY = loc.getBlockY(); + double minZ = loc.getBlockZ(); + double maxX = loc2.getBlockX() + 1; + double maxY = loc2.getBlockY() + 1; + double maxZ = loc2.getBlockZ() + 1; + + for (double x = minX; x <= maxX; x += particleDistance) { + for (double y = minY; y <= maxY; y += particleDistance) { + for (double z = minZ; z <= maxZ; z += particleDistance) { + int components = 0; + if (x == minX || x == maxX) components++; + if (y == minY || y == maxY) components++; + if (z == minZ || z == maxZ) components++; + if (components >= 2) { + result.add(new Location(world, x, y, z)); + } + } + } + } + return result; + } + + default void vfxCuboidOutline(Block block, Particle particle) { + if (!areParticlesEnabled()) { + return; + } + + List hollowCube = getHollowCuboid(block.getLocation(), 0.25); + for (Location l : hollowCube) { + block.getWorld().spawnParticle(particle, l, 1, 0F, 0F, 0F, 0.000); + } + } + + default void vfxCuboidOutline(Block blockStart, Block blockEnd, Particle particle) { + if (!areParticlesEnabled()) { + return; + } + + List hollowCube = getHollowCuboid(blockStart.getLocation(), blockEnd.getLocation(), 0.25); + for (Location l : hollowCube) { + blockStart.getWorld().spawnParticle(particle, l, 2, 0F, 0F, 0F, 0.000); + } + } + + default void vfxCuboidOutline(Block blockStart, Block blockEnd, Color color, int size) { + if (!areParticlesEnabled()) { + return; + } + + List hollowCube = getHollowCuboid(blockStart.getLocation(), blockEnd.getLocation(), 0.25); + Particle.DustOptions dustOptions = new Particle.DustOptions(color, size); + for (Location l : hollowCube) { + blockStart.getWorld().spawnParticle(REDSTONE, l, 2, 0F, 0F, 0F, 0.000, dustOptions); + } + } + + default void vfxPrismOutline(Location placer, double outset, Particle particle, int particleCount) { + if (!areParticlesEnabled()) { + return; + } + + + Location top = new Location(placer.getWorld(), placer.getX(), placer.getY() + outset, placer.getZ()); + Location baseCorner1 = new Location(placer.getWorld(), placer.getX() - outset, placer.getY(), placer.getZ() - outset); + Location baseCorner2 = new Location(placer.getWorld(), placer.getX() + outset, placer.getY(), placer.getZ() - outset); + Location baseCorner3 = new Location(placer.getWorld(), placer.getX() + outset, placer.getY(), placer.getZ() + outset); + Location baseCorner4 = new Location(placer.getWorld(), placer.getX() - outset, placer.getY(), placer.getZ() + outset); + + vfxParticleLine(baseCorner1, baseCorner2, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); + vfxParticleLine(baseCorner2, baseCorner3, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); + vfxParticleLine(baseCorner3, baseCorner4, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); + vfxParticleLine(baseCorner4, baseCorner1, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); + + for (Location location : Arrays.asList(baseCorner1, baseCorner2, baseCorner3, baseCorner4)) { + vfxParticleLine(location, top, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); + } + } + + default void vfxFastSphere(Location center, double range, Color color, int particleCount) { + if (!areParticlesEnabled()) { + return; + } + + Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1); + World world = center.getWorld(); + + for (int i = 0; i < particleCount; i++) { + double x, y, z; + do { + x = RANDOM.nextDouble() * 2 - 1; + y = RANDOM.nextDouble() * 2 - 1; + z = RANDOM.nextDouble() * 2 - 1; + } while (x * x + y * y + z * z > 1); + + double magnitude = Math.sqrt(x * x + y * y + z * z); + x = x / magnitude * range; + y = y / magnitude * range; + z = z / magnitude * range; + + Location particleLocation = center.clone().add(x, y, z); + world.spawnParticle(REDSTONE, particleLocation, 0, 0, 0, 0, dustOptions); + } + } + + default void vfxLoadingRing(Location center, double radius, Color color, int durationTicks, int particleCount) { + if (!areParticlesEnabled()) { + return; + } + + World world = center.getWorld(); + Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f); + + runVisualLoop(durationTicks, tick -> { + double angle = 2 * Math.PI * tick / durationTicks; + double x = radius * Math.cos(angle); + double z = radius * Math.sin(angle); + Location particleLocation = center.clone().add(x, 0, z); + world.spawnParticle(REDSTONE, particleLocation, particleCount, 0, 0, 0, dustOptions); + }); + } + + default void vfxLoadingRing(Location center, double radius, Particle particle, int durationTicks, int particleCount) { + if (!areParticlesEnabled()) { + return; + } + + World world = center.getWorld(); + + runVisualLoop(durationTicks, tick -> { + double angle = 2 * Math.PI * tick / durationTicks; + double x = radius * Math.cos(angle); + double z = radius * Math.sin(angle); + Location particleLocation = center.clone().add(x, 0, z); + world.spawnParticle(particle, particleLocation, particleCount, 0, 0, 0); + }); + } + + + default void vfxLevelUp(Player p) { + if (!areParticlesEnabled()) { + return; + } + + p.spawnParticle(Particle.REVERSE_PORTAL, p.getLocation().clone().add(0, 1.7, 0), 100, 0.1, 0.1, 0.1, 4.1); + } + + default void vfxFastRing(Location location, double radius, Color color) { + if (!areParticlesEnabled()) { + return; + } + + for (int d = 0; d <= 90; d += 1) { + Location particleLoc = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); + particleLoc.setX(location.getX() + Math.cos(d) * radius); + particleLoc.setZ(location.getZ() + Math.sin(d) * radius); + location.getWorld().spawnParticle(REDSTONE, particleLoc, 1, new Particle.DustOptions(color, 1)); + } + } + + default void vfxFastRing(Location location, double radius, Particle particle) { + if (!areParticlesEnabled()) { + return; + } + + for (int d = 0; d <= 90; d += 1) { + Location particleLoc = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); + particleLoc.setX(location.getX() + Math.cos(d) * radius); + particleLoc.setZ(location.getZ() + Math.sin(d) * radius); + location.getWorld().spawnParticle(particle, particleLoc, 1); + } + } + + default void vfxFastRing(Location location, double radius, Particle particle, int angle) { + if (!areParticlesEnabled()) { + return; + } + + for (int d = 0; d <= 90; d += angle) { + Location particleLoc = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); + particleLoc.setX(location.getX() + Math.cos(d) * radius); + particleLoc.setZ(location.getZ() + Math.sin(d) * radius); + location.getWorld().spawnParticle(particle, particleLoc, 1); + } + } + + default void vfxShootParticle(Player player, Particle particle, double velocity, int count) { + if (!areParticlesEnabled()) { + return; + } + + Location location = player.getEyeLocation(); + Vector direction = location.getDirection(); + for (int i = 0; i < count; i++) { + player.getWorld().spawnParticle(particle, location.getX(), location.getY(), location.getZ(), 0, (float) direction.getX(), (float) direction.getY(), (float) direction.getZ(), velocity, null); + } + } + + default void vfxParticleSpiral(Location center, int radius, int height, Particle type) { + if (!areParticlesEnabled()) { + return; + } + + double angle = 0; + for (int i = 0; i <= height; i++) { + double x = center.getX() + (radius * Math.cos(angle)); + double z = center.getZ() + (radius * Math.sin(angle)); + center.getWorld().spawnParticle(type, x, +center.getY(), z, 1, 0, 0, 0, 0); + angle += 0.1; + } + } + + + default void vfxXP(Player p, Location l, int amt) { + if (!areParticlesEnabled()) { + return; + } + + if (AdaptConfig.get().isUseEnchantmentTableParticleForActiveEffects()) { + p.spawnParticle(ENCHANTMENT_TABLE, l, Math.min(amt / 10, 20), 0.5, 0.5, 0.5, 1); + } + } + + default void vfxXP(Location l) { + if (!areParticlesEnabled()) { + return; + } + + if (AdaptConfig.get().isUseEnchantmentTableParticleForActiveEffects()) { + l.getWorld().spawnParticle(ENCHANTMENT_TABLE, l.add(0, 1.7, 0), 3, 0.1, 0.1, 0.1, 3); + } + } + + default void damageHand(Player p, int damage) { + ItemStack is = p.getInventory().getItemInMainHand(); + ItemMeta im = is.getItemMeta(); + + if (im == null) { + return; + } + + if (im.isUnbreakable()) { + return; + } + + Damageable dm = (Damageable) im; + dm.setDamage(dm.getDamage() + damage); + + if (dm.getDamage() > is.getType().getMaxDurability()) { + p.getInventory().setItemInMainHand(new ItemStack(Material.AIR)); + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f); + return; + } + + is.setItemMeta(im); + p.getInventory().setItemInMainHand(is); + } + + default void damageOffHand(Player p, int damage) { + ItemStack is = p.getInventory().getItemInOffHand(); + ItemMeta im = is.getItemMeta(); + + if (im == null) { + return; + } + + if (im.isUnbreakable()) { + return; + } + + Damageable dm = (Damageable) im; + dm.setDamage(dm.getDamage() + damage); + + if (dm.getDamage() > is.getType().getMaxDurability()) { + p.getInventory().setItemInOffHand(new ItemStack(Material.AIR)); + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f); + return; + } + + is.setItemMeta(im); + p.getInventory().setItemInOffHand(is); + } + + default Block getRightBlock(Player p, Block b) { + Location l = p.getLocation(); + float yaw = l.getYaw(); + // Make sure yaw is in the range 0 to 360 + while (yaw < 0) { + yaw += 360; + } + yaw = yaw % 360; + // The player's yaw is their rotation in the world, + // so, we can use that to get the right face of a block! + BlockFace rightFace; + // if the player is facing SE to SW + if (yaw < 45 || yaw >= 315) { + rightFace = BlockFace.EAST; + return b.getRelative(rightFace); + } + // if the player is facing SW to NW + else if (yaw < 135) { + rightFace = BlockFace.SOUTH; + return b.getRelative(rightFace); + } + // if the player is facing NW to NE + else if (yaw < 225) { + rightFace = BlockFace.WEST; + return b.getRelative(rightFace); + } + // if the player is facing NE to SE + else if (yaw < 315) { + rightFace = BlockFace.NORTH; + return b.getRelative(rightFace); + } else { + return null; + } + } + + default Block getLeftBlock(Player p, Block b) { + Location l = p.getLocation(); + float yaw = l.getYaw(); + + // Make sure yaw is in the range 0 to 360 + while (yaw < 0) { + yaw += 360; + } + yaw = yaw % 360; + // The player's yaw is their rotation in the world, + // so, we can use that to get the right face of a block! + BlockFace leftFace; + // if the player is facing SE to SW + if (yaw < 45 || yaw >= 315) { + leftFace = BlockFace.WEST; + return b.getRelative(leftFace); + } + // if the player is facing SW to NW + else if (yaw < 135) { + leftFace = BlockFace.NORTH; + return b.getRelative(leftFace); + } + // if the player is facing NW to NE + else if (yaw < 225) { + leftFace = BlockFace.EAST; + return b.getRelative(leftFace); + } + // if the player is facing NE to SE + else if (yaw < 315) { + leftFace = BlockFace.SOUTH; + return b.getRelative(leftFace); + } else { + return null; + } + } + + + default void setExp(Player p, int exp) { + p.setExp(0); + p.setLevel(0); + p.setTotalExperience(0); + + if (exp <= 0) { + return; + } + + giveExp(p, exp); + } + + default void giveExp(Player p, int exp) { + while (exp > 0) { + int xp = getExpToLevel(p) - getExp(p); + if (xp > exp) { + xp = exp; + } + p.giveExp(xp); + exp -= xp; + } + } + + default void takeExp(Player p, int exp) { + takeExp(p, exp, true); + } + + default void takeExp(Player p, int exp, boolean fromTotal) { + int xp = getTotalExp(p); + + if (fromTotal) { + xp -= exp; + } else { + int m = getExp(p) - exp; + if (m < 0) { + m = 0; + } + xp -= getExp(p) + m; + } + + setExp(p, xp); + } + + default int getExp(Player p) { + return (int) (getExpToLevel(p) * p.getExp()); + } + + default int getTotalExp(Player p) { + return getTotalExp(p, false); + } + + default int getTotalExp(Player p, boolean recalc) { + if (recalc) { + recalcTotalExp(p); + } + return p.getTotalExperience(); + } + + default int getLevel(Player p) { + return p.getLevel(); + } + + default int getExpToLevel(Player p) { + return p.getExpToLevel(); + } + + default int getExpToLevel(int level) { + return level >= 30 ? 62 + (level - 30) * 7 : (level >= 15 ? 17 + (level - 15) * 3 : 17); + } + + default void recalcTotalExp(Player p) { + int total = getExp(p); + for (int i = 0; i < p.getLevel(); i++) { + total += getExpToLevel(i); + } + p.setTotalExperience(total); + } + + /** + * Takes a custom amount of the item stack exact type (Ignores the item + * amount) + * + * @param inv the inv + * @param is the item ignore the amount + * @param amount the amount to use + * @return true if taken, false if not (missing) + */ + default boolean takeAll(Inventory inv, ItemStack is, int amount) { + ItemStack isf = is.clone(); + isf.setAmount(amount); + return takeAll(inv, is); + } + + /** + * Take one of an exact type ignoring the item stack amount + * + * @param inv the inv + * @param is the item ignoring the amount + * @return true if taken, false if diddnt + */ + default boolean takeOne(Inventory inv, ItemStack is, int amount) { + return takeAll(inv, is, 1); + } + + /** + * Take a specific amount of an EXACT META TYPE from an inventory + * + * @param inv the inv + * @param is uses the amount + * @return returns false if it couldnt get enough (and none was taken) + */ + default boolean takeAll(Inventory inv, ItemStack is) { + ItemStack[] items = inv.getStorageContents(); + + int take = is.getAmount(); + + for (int ii = 0; ii < items.length; ii++) { + ItemStack i = items[ii]; + + if (i == null) { + continue; + } + + if (i.isSimilar(is)) { + if (take > i.getAmount()) { + i.setAmount(i.getAmount() - take); + items[ii] = i; + take = 0; + break; + } else { + items[ii] = null; + take -= i.getAmount(); + } + } + } + + if (take > 0) { + return false; + } + + inv.setStorageContents(items); + return true; + } +} diff --git a/src/main/java/art/arcane/adapt/api/EventHandlerInvoker.java b/src/main/java/art/arcane/adapt/api/EventHandlerInvoker.java new file mode 100644 index 000000000..23c556e50 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/EventHandlerInvoker.java @@ -0,0 +1,90 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api; + +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.plugin.EventExecutor; + +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.BiConsumer; + +public final class EventHandlerInvoker { + private EventHandlerInvoker() { + } + + public static EventExecutor createExecutor(Method method, Class eventType) { + BiConsumer direct = tryCreateDirectInvoker(method); + if (direct != null) { + return (target, event) -> { + if (!eventType.isAssignableFrom(event.getClass())) { + return; + } + + try { + direct.accept(target, event); + } catch (Throwable ex) { + throw new EventException(ex); + } + }; + } + + return (target, event) -> { + if (!eventType.isAssignableFrom(event.getClass())) { + return; + } + + try { + method.invoke(target, event); + } catch (InvocationTargetException ex) { + throw new EventException(ex.getCause()); + } catch (Throwable ex) { + throw new EventException(ex); + } + }; + } + + @SuppressWarnings("unchecked") + private static BiConsumer tryCreateDirectInvoker(Method method) { + try { + MethodHandles.Lookup caller = MethodHandles.lookup(); + MethodHandles.Lookup lookup; + try { + lookup = MethodHandles.privateLookupIn(method.getDeclaringClass(), caller); + } catch (IllegalAccessException e) { + lookup = caller; + } + + MethodHandle handle = lookup.unreflect(method); + MethodType invokedType = MethodType.methodType(BiConsumer.class); + MethodType samType = MethodType.methodType(void.class, Object.class, Object.class); + MethodType instantiatedType = MethodType.methodType(void.class, method.getDeclaringClass(), method.getParameterTypes()[0]); + return (BiConsumer) LambdaMetafactory.metafactory( + lookup, "accept", invokedType, samType, handle, instantiatedType + ).getTarget().invokeExact(); + } catch (Throwable ignored) { + return null; + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/adaptation/Adaptation.java b/src/main/java/art/arcane/adapt/api/adaptation/Adaptation.java new file mode 100644 index 000000000..5a7998947 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/adaptation/Adaptation.java @@ -0,0 +1,964 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.adaptation; + +import art.arcane.adapt.api.Component; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.protection.Protector; +import art.arcane.adapt.api.protection.WorldPolicyLatencyTelemetry; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.tick.Ticked; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.Cancellable; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; + +import java.util.List; +import java.util.Set; +import java.util.function.IntConsumer; +import java.util.function.Predicate; + +/** + * Public API for one adaptation under a skill line. + */ +public interface Adaptation extends Ticked, Component { + + /** + * @return maximum unlockable level for this adaptation. + */ + int getMaxLevel(); + + /** + * Grants adaptation-attributed xp to a player. + */ + default void xp(Player p, double amount) { + xp(p, amount, null); + } + + /** + * Grants adaptation-attributed xp to a player with an optional reward key + * suffix. + */ + default void xp(Player p, double amount, String rewardKey) { + getSkill().xp(p, amount, adaptationRewardKey(rewardKey)); + } + + /** + * Grants adaptation-attributed xp and renders it at a world location. + */ + default void xp(Player p, Location l, double amount) { + xp(p, l, amount, null); + } + + /** + * Grants adaptation-attributed xp at a location with an optional reward key + * suffix. + */ + default void xp(Player p, Location l, double amount, String rewardKey) { + getSkill().xp(p, l, amount, adaptationRewardKey(rewardKey)); + } + + /** + * Grants silent adaptation-attributed xp with an optional reward key suffix. + */ + default void xpSilent(Player p, double amount, String rewardKey) { + getSkill().xpSilent(p, amount, adaptationRewardKey(rewardKey)); + } + + /** + * Grants silent adaptation-attributed xp. + */ + default void xpSilent(Player p, double amount) { + xpSilent(p, amount, null); + } + + /** + * Ensures this logic runs on the player's owned thread/region (Folia-safe). + */ + default void withPlayerThread(Player p, Runnable runnable) { + AdaptationRuntimeGuards.withPlayerThread(this, p, runnable); + } + + /** + * Thread/region guard with optional cancellable event gating. + */ + default void withPlayerThread(Player p, Cancellable cancellable, Runnable runnable) { + AdaptationRuntimeGuards.withPlayerThread(this, p, cancellable, runnable); + } + + /** + * Runs only when player-thread-safe and the player owns this adaptation. + */ + default void withAdaptedPlayer(Player p, Runnable runnable) { + AdaptationRuntimeGuards.withAdaptedPlayer(this, p, runnable); + } + + /** + * Runs only when event is not cancelled, player is thread-safe, and + * adaptation is owned. + */ + default void withAdaptedPlayer(Player p, Cancellable cancellable, Runnable runnable) { + AdaptationRuntimeGuards.withAdaptedPlayer(this, p, cancellable, runnable); + } + + /** + * Resolves active runtime level and invokes consumer only when level > 0. + */ + default void withActiveLevel(Player p, IntConsumer consumer) { + AdaptationRuntimeGuards.withActiveLevel(this, p, consumer); + } + + /** + * Active-level helper with cancellable event gating. + */ + default void withActiveLevel(Player p, Cancellable cancellable, IntConsumer consumer) { + AdaptationRuntimeGuards.withActiveLevel(this, p, cancellable, consumer); + } + + /** + * Builds an adaptation-scoped reward key (`adaptation::`). + */ + default String adaptationRewardKey(String rewardKey) { + return AdaptationRuntimeGuards.adaptationRewardKey(this, rewardKey); + } + + /** + * Adaptation-aware particle toggle check (global + skill + adaptation config + * aware). + */ + @Override + default boolean areParticlesEnabled() { + return AdaptationGuiSupport.areParticlesEnabled(this, Component.super.areParticlesEnabled()); + } + + /** + * Adaptation-aware sound toggle check (global + skill + adaptation config + * aware). + */ + @Override + default boolean areSoundsEnabled() { + return AdaptationGuiSupport.areSoundsEnabled(this, Component.super.areSoundsEnabled()); + } + + /** + * Grants small anti-abuse baseline xp for successful adaptation use. + */ + default void awardUsageBaselineXp(Player p, int level) { + AdaptationRuntimeGuards.awardUsageBaselineXp(this, p, level); + } + + /** + * Reads adaptation-scoped per-player storage, falling back to default value. + */ + default F getStorage(Player p, String key, F defaultValue) { + return AdaptationRuntimeGuards.getStorage(this, p, key, defaultValue); + } + + /** + * Reads adaptation-scoped per-player storage. + */ + default F getStorage(Player p, String key) { + return getStorage(p, key, null); + } + + /** + * Writes adaptation-scoped per-player storage. + */ + default boolean setStorage(Player p, String key, Object value) { + return AdaptationRuntimeGuards.setStorage(this, p, key, value); + } + + /** + * Fires use checks/events for this adaptation against an AdaptPlayer + * context. + */ + default boolean canUse(AdaptPlayer player) { + return AdaptationRuntimeGuards.canUse(this, player); + } + + /** + * Fires use checks/events for this adaptation against a Bukkit player. + */ + default boolean canUse(Player player) { + return AdaptationRuntimeGuards.canUse(this, player); + } + + /** + * Returns true when this player has use permission for this adaptation via + * permission. + */ + default boolean hasUsePermission(Player p, Adaptation a) { + return AdaptationRuntimeGuards.hasUsePermission(this, p, a); + } + + /** + * Typed storage convenience helper for strings. + */ + default String getStorageString(Player p, String key, String defaultValue) { + return getStorage(p, key, defaultValue); + } + + /** + * Typed storage convenience helper for strings. + */ + default String getStorageString(Player p, String key) { + return getStorage(p, key); + } + + /** + * Typed storage convenience helper for integers. + */ + default Integer getStorageInt(Player p, String key, Integer defaultValue) { + Object value = getStorage(p, key, (Object) defaultValue); + if (value == null) { + return defaultValue; + } + + if (value instanceof Number number) { + return number.intValue(); + } + + if (value instanceof String stringValue) { + try { + return Integer.parseInt(stringValue.trim()); + } catch (NumberFormatException ignored) { + return defaultValue; + } + } + + return defaultValue; + } + + /** + * Typed storage convenience helper for integers. + */ + default Integer getStorageInt(Player p, String key) { + return getStorageInt(p, key, null); + } + + /** + * Typed storage convenience helper for doubles. + */ + default Double getStorageDouble(Player p, String key, Double defaultValue) { + Object value = getStorage(p, key, (Object) defaultValue); + if (value == null) { + return defaultValue; + } + + if (value instanceof Number number) { + return number.doubleValue(); + } + + if (value instanceof String stringValue) { + try { + return Double.parseDouble(stringValue.trim()); + } catch (NumberFormatException ignored) { + return defaultValue; + } + } + + return defaultValue; + } + + /** + * Typed storage convenience helper for doubles. + */ + default Double getStorageDouble(Player p, String key) { + return getStorageDouble(p, key, null); + } + + /** + * Typed storage convenience helper for booleans. + */ + default Boolean getStorageBoolean(Player p, String key, Boolean defaultValue) { + return getStorage(p, key, defaultValue); + } + + /** + * Typed storage convenience helper for booleans. + */ + default Boolean getStorageBoolean(Player p, String key) { + return getStorage(p, key); + } + + /** + * Typed storage convenience helper for longs. + */ + default Long getStorageLong(Player p, String key, Long defaultValue) { + Object value = getStorage(p, key, (Object) defaultValue); + if (value == null) { + return defaultValue; + } + + if (value instanceof Number number) { + return number.longValue(); + } + + if (value instanceof String stringValue) { + try { + return Long.parseLong(stringValue.trim()); + } catch (NumberFormatException ignored) { + return defaultValue; + } + } + + return defaultValue; + } + + /** + * Typed storage convenience helper for longs. + */ + default Long getStorageLong(Player p, String key) { + return getStorageLong(p, key, null); + } + + /** + * Returns the concrete config class used by this adaptation. + */ + Class getConfigurationClass(); + + /** + * Registers the config class used for load/reload of this adaptation. + */ + void registerConfiguration(Class type); + + /** + * @return true when this adaptation is runtime-enabled. + */ + boolean isEnabled(); + + /** + * @return true when this adaptation cannot be unlearned. + */ + boolean isPermanent(); + + /** + * Returns the live config instance for this adaptation. + */ + T getConfig(); + + /** + * Builds the advancement tree node for this adaptation. + */ + AdaptAdvancement buildAdvancements(); + + /** + * Adds per-level stat lines to GUI elements. + */ + void addStats(int level, Element v); + + /** + * @return base upgrade cost. + */ + int getBaseCost(); + + /** + * @return localized adaptation description. + */ + String getDescription(); + + /** + * @return base icon for this adaptation. + */ + Material getIcon(); + + /** + * @return owning skill of this adaptation. + */ + Skill getSkill(); + + /** + * Assigns the owning skill for this adaptation. + */ + void setSkill(Skill skill); + + /** + * @return internal adaptation key. + */ + String getName(); + + /** + * @return initial unlock/upgrade cost component. + */ + int getInitialCost(); + + /** + * @return per-level cost multiplier. + */ + double getCostFactor(); + + /** + * @return recipes registered by this adaptation. + */ + List getRecipes(); + + /** + * @return brewing recipes registered by this adaptation. + */ + List getBrewingRecipes(); + + /** + * Called while building advancement trees to append custom nodes. + */ + void onRegisterAdvancements(List advancements); + + /** + * Returns the active protector set after applying this adaptation's override + * config. + */ + default Set getProtectors() { + return AdaptationRuntimeGuards.getProtectors(this); + } + + /** + * World-policy check for breaking a block. + */ + default boolean canBlockBreak(Player player, Location blockLocation) { + return evaluateWorldPolicy(protector -> protector.canBlockBreak(player, blockLocation, this)); + } + + /** + * World-policy check for placing a block. + */ + default boolean canBlockPlace(Player player, Location blockLocation) { + return evaluateWorldPolicy(protector -> protector.canBlockPlace(player, blockLocation, this)); + } + + /** + * World-policy check for PVP damage. + */ + default boolean canPVP(Player player, Location victimLocation) { + return evaluateWorldPolicy(protector -> protector.canPVP(player, victimLocation, this)); + } + + /** + * World-policy check for PVE damage. + */ + default boolean canPVE(Player player, Location victimLocation) { + return evaluateWorldPolicy(protector -> protector.canPVE(player, victimLocation, this)); + } + + /** + * World-policy check for generic interaction. + */ + default boolean canInteract(Player player, Location targetLocation) { + return evaluateWorldPolicy(protector -> protector.canInteract(player, targetLocation, this)); + } + + /** + * Returns true when attacker can damage target under current world policies. + */ + default boolean canDamageTarget(Player attacker, Entity target) { + return AdaptationRuntimeGuards.canDamageTarget(this, attacker, target); + } + + default boolean isProtectedFriendly(Player actor, Entity target) { + return AdaptationRuntimeGuards.isProtectedFriendly(actor, target); + } + + /** + * Returns active level only when player is in survival. + */ + default int getActiveSurvivalLevel(Player player) { + return AdaptationRuntimeGuards.getActiveSurvivalLevel(this, player); + } + + /** + * Returns active level only when the player meets the supplied requirement. + */ + default int getActiveLevel(Player player, Predicate requirement) { + return AdaptationRuntimeGuards.getActiveLevel(this, player, requirement); + } + + /** + * Returns active-survival level only when the player meets the supplied + * requirement. + */ + default int getActiveSurvivalLevel(Player player, Predicate requirement) { + return AdaptationRuntimeGuards.getActiveSurvivalLevel(this, player, requirement); + } + + /** + * Returns active level gated by interaction permission at a location. + */ + default int getActiveInteractLevel(Player player, Location location) { + return AdaptationRuntimeGuards.getActiveInteractLevel(this, player, location); + } + + /** + * Returns active level gated by block-break permission at a location. + */ + default int getActiveBlockBreakLevel(Player player, Location location) { + return AdaptationRuntimeGuards.getActiveBlockBreakLevel(this, player, location); + } + + /** + * Returns active level gated by block-place permission at a location. + */ + default int getActiveBlockPlaceLevel(Player player, Location location) { + return AdaptationRuntimeGuards.getActiveBlockPlaceLevel(this, player, location); + } + + /** + * Returns active level gated by PVP/PVE policy for a specific target. + */ + default int getActiveDamageLevel(Player attacker, Entity target) { + return AdaptationRuntimeGuards.getActiveDamageLevel(this, attacker, target); + } + + /** + * Resolves a context only when adaptation is active and interact is allowed. + */ + default BlockActionContext resolveInteractContext(Player player, Location location) { + return AdaptationRuntimeGuards.resolveInteractContext(this, player, location); + } + + /** + * Resolves interact context and applies an additional player requirement. + */ + default BlockActionContext resolveInteractContext(Player player, Location location, Predicate requirement) { + return AdaptationRuntimeGuards.resolveInteractContext(this, player, location, requirement); + } + + /** + * Resolves interact context with optional requirement and survival-only + * gating. + */ + default BlockActionContext resolveInteractContext(Player player, Location location, Predicate requirement, boolean survivalOnly) { + return AdaptationRuntimeGuards.resolveInteractContext(this, player, location, requirement, survivalOnly); + } + + /** + * Resolves a context only when adaptation is active and block break is + * allowed. + */ + default BlockActionContext resolveBlockBreakContext(Player player, Location location) { + return AdaptationRuntimeGuards.resolveBlockBreakContext(this, player, location); + } + + /** + * Resolves block-break context and applies an additional player requirement. + */ + default BlockActionContext resolveBlockBreakContext(Player player, Location location, Predicate requirement) { + return AdaptationRuntimeGuards.resolveBlockBreakContext(this, player, location, requirement); + } + + /** + * Resolves block-break context with optional requirement and survival-only + * gating. + */ + default BlockActionContext resolveBlockBreakContext(Player player, Location location, Predicate requirement, boolean survivalOnly) { + return AdaptationRuntimeGuards.resolveBlockBreakContext(this, player, location, requirement, survivalOnly); + } + + /** + * Resolves a context only when adaptation is active and block place is + * allowed. + */ + default BlockActionContext resolveBlockPlaceContext(Player player, Location location) { + return AdaptationRuntimeGuards.resolveBlockPlaceContext(this, player, location); + } + + /** + * Resolves block-place context and applies an additional player requirement. + */ + default BlockActionContext resolveBlockPlaceContext(Player player, Location location, Predicate requirement) { + return AdaptationRuntimeGuards.resolveBlockPlaceContext(this, player, location, requirement); + } + + /** + * Resolves block-place context with optional requirement and survival-only + * gating. + */ + default BlockActionContext resolveBlockPlaceContext(Player player, Location location, Predicate requirement, boolean survivalOnly) { + return AdaptationRuntimeGuards.resolveBlockPlaceContext(this, player, location, requirement, survivalOnly); + } + + /** + * Resolves a context requiring both interact and block-break permission. + */ + default BlockActionContext resolveInteractBreakContext(Player player, Location location) { + return AdaptationRuntimeGuards.resolveInteractBreakContext(this, player, location); + } + + /** + * Resolves interact+break context and applies an additional player + * requirement. + */ + default BlockActionContext resolveInteractBreakContext(Player player, Location location, Predicate requirement) { + return AdaptationRuntimeGuards.resolveInteractBreakContext(this, player, location, requirement); + } + + /** + * Resolves interact+break context with optional requirement and survival-only + * gating. + */ + default BlockActionContext resolveInteractBreakContext(Player player, Location location, Predicate requirement, boolean survivalOnly) { + return AdaptationRuntimeGuards.resolveInteractBreakContext(this, player, location, requirement, survivalOnly); + } + + /** + * Returns a validated main-hand item when adaptation is active and + * requirement passes. + */ + default ItemStack readyMainHand(Player player, Predicate requirement) { + return AdaptationRuntimeGuards.readyMainHand(this, player, requirement); + } + + /** + * Returns a validated active main-hand item without extra requirement + * checks. + */ + default ItemStack readyMainHand(Player player) { + return readyMainHand(player, null); + } + + /** + * Resolves melee combat context from an entity-damage event. + */ + default MeleeContext resolveMeleeContext(EntityDamageByEntityEvent event, Predicate mainHandRequirement) { + return AdaptationRuntimeGuards.resolveMeleeContext(this, event, mainHandRequirement); + } + + /** + * Resolves melee combat context from an entity-damage event. + */ + default MeleeContext resolveMeleeContext(EntityDamageByEntityEvent event) { + return resolveMeleeContext(event, null); + } + + /** + * Resolves generic player attack context from an entity-damage event. + */ + default AttackContext resolveAttackContext(EntityDamageByEntityEvent event, Predicate mainHandRequirement) { + return AdaptationRuntimeGuards.resolveAttackContext(this, event, mainHandRequirement); + } + + /** + * Resolves generic player attack context from an entity-damage event. + */ + default AttackContext resolveAttackContext(EntityDamageByEntityEvent event) { + return resolveAttackContext(event, null); + } + + /** + * Resolves projectile combat context from an entity-damage event. + */ + default ProjectileContext resolveProjectileContext(EntityDamageByEntityEvent event, Predicate projectileRequirement) { + return AdaptationRuntimeGuards.resolveProjectileContext(this, event, projectileRequirement); + } + + /** + * Resolves projectile combat context from an entity-damage event. + */ + default ProjectileContext resolveProjectileContext(EntityDamageByEntityEvent event) { + return resolveProjectileContext(event, null); + } + + /** + * World-policy check for chest access. + */ + default boolean canAccessChest(Player player, Location chestLocation) { + return evaluateWorldPolicy(protector -> protector.canAccessChest(player, chestLocation, this)); + } + + /** + * World-policy check for generic region access at the player's current + * location. + */ + default boolean checkRegion(Player player) { + return evaluateWorldPolicy(protector -> protector.checkRegion(player, player.getLocation(), this)); + } + + private boolean evaluateWorldPolicy(Predicate evaluator) { + long start = System.nanoTime(); + try { + for (Protector protector : getProtectors()) { + if (!evaluator.test(protector)) { + return false; + } + } + return true; + } finally { + WorldPolicyLatencyTelemetry.recordNanos(System.nanoTime() - start); + } + } + + /** + * Returns true when this adaptation currently conflicts with another active + * adaptation. + */ + default boolean hasUsageConflict(Player p) { + return AdaptationRuntimeGuards.hasUsageConflict(this, p); + } + + /** + * Runtime-ready level check (ownership + enabled + world/protection/conflict + * checks). + */ + default int getActiveLevel(Player p) { + return AdaptationRuntimeGuards.getActiveLevel(this, p); + } + + /** + * Ownership check only (learned level > 0), without runtime gating. + */ + default boolean hasAdaptation(Player p) { + return getLevel(p) > 0; + } + + /** + * Runtime-usable state (ownership + active world/permission/conflict + * checks). + */ + default boolean hasActiveAdaptation(Player p) { + return getActiveLevel(p) > 0; + } + + /** + * Raw learned level (ignores runtime gating such as + * world/protection/conflict). + */ + default int getLevel(Player p) { + return AdaptationRuntimeGuards.getLevel(this, p); + } + + default int getLevel(AdaptPlayer adaptPlayer) { + return AdaptationRuntimeGuards.getLevel(this, adaptPlayer); + } + + default boolean hasLearnedAdaptation(AdaptPlayer adaptPlayer) { + return getLevel(adaptPlayer) > 0; + } + + default List learnedCandidates(long now) { + return AdaptationRuntimeGuards.learnedCandidates(this, now); + } + + /** + * Learned level normalized to 0..1. + */ + default double getLevelPercent(Player p) { + if (!this.isEnabled()) { + return 0; + } + if (!this.getSkill().isEnabled()) { + return 0; + } + if (!p.getClass().getSimpleName().equals("CraftPlayer")) { + return 0.0; + } + return Math.min(Math.max(0, M.lerpInverse(0, getMaxLevel(), getLevel(p))), 1); + } + + /** + * Level normalized to 0..1 using an explicit level value. + */ + default double getLevelPercent(int p) { + return Math.min(Math.max(0, M.lerpInverse(0, getMaxLevel(), p)), 1); + } + + /** + * Cost for purchasing exactly this level step. + */ + default int getCostFor(int level) { + return (int) (Math.max(1, getBaseCost() + (getBaseCost() * (level * getCostFactor())))) + (level == 1 ? getInitialCost() : 0); + } + + /** + * Power cost delta for level transitions. + */ + default int getPowerCostFor(int level, int myLevel) { + return level - myLevel; + } + + /** + * Cumulative cost to move from current level to target level. + */ + default int getCostFor(int level, int myLevel) { + if (myLevel >= level) { + return 0; + } + + + int c = 0; + + for (int i = myLevel + 1; i <= level; i++) { + c += getCostFor(i); + } + + return c; + } + + /** + * Cumulative refund amount when reducing from current level to target level. + */ + default int getRefundCostFor(int level, int myLevel) { + if (myLevel <= level) { + return 0; + } + + int c = 0; + + for (int i = level + 1; i <= myLevel; i++) { + c += getCostFor(i); + } + + return c; + } + + /** + * UI display name for this adaptation. + */ + default String getDisplayName() { + return AdaptationGuiSupport.getDisplayName(this); + } + + /** + * UI display name with level suffix. + */ + default String getDisplayName(int level) { + return AdaptationGuiSupport.getDisplayName(this, level); + } + + /** + * UI display name with numeric level but no roman formatting. + */ + default String getDisplayNameNoRoman(int level) { + return AdaptationGuiSupport.getDisplayNameNoRoman(this, level); + } + + /** + * Returns targeted block face from player look direction. + */ + default BlockFace getBlockFace(Player player, int maxrange) { + return AdaptationGuiSupport.getBlockFace(player, maxrange); + } + + /** + * Returns generated custom model binding for this adaptation icon. + */ + default CustomModel getModel() { + return AdaptationGuiSupport.getModel(this); + } + + /** + * Returns generated custom model binding for a specific adaptation level. + */ + default CustomModel getModel(int level) { + return AdaptationGuiSupport.getModel(this, level); + } + + /** + * Opens adaptation GUI and optionally applies use permission checks. + */ + default boolean openGui(Player player, boolean checkPermissions) { + return AdaptationGuiSupport.openGui(this, player, checkPermissions); + } + + /** + * Opens page 0 of this adaptation GUI. + */ + default void openGui(Player player) { + AdaptationGuiSupport.openGui(this, player); + } + + /** + * Opens a specific page of this adaptation GUI. + */ + default void openGui(Player player, int page) { + AdaptationGuiSupport.openGui(this, player, page); + } + + /** + * Checks whether permanent learn confirmation is pending for this + * player/level. + */ + default boolean isPermanentLearnConfirmationPending(Player player, int level) { + return AdaptationGuiSupport.isPermanentLearnConfirmationPending(player, this, level); + } + + /** + * Consumes pending permanent learn confirmation for this player/level. + */ + default boolean consumePermanentLearnConfirmation(Player player, int level) { + return AdaptationGuiSupport.consumePermanentLearnConfirmation(player, this, level); + } + + /** + * Unlearns levels from this adaptation. + */ + default void unlearn(Player player, int lvl, boolean force) { + AdaptationGuiSupport.unlearn(this, player, lvl, force); + } + + /** + * Learns levels into this adaptation. + */ + default void learn(Player player, int lvl, boolean force) { + AdaptationGuiSupport.learn(this, player, lvl, force); + } + + /** + * Returns true when the provided recipe belongs to this adaptation. + */ + default boolean isAdaptationRecipe(Recipe recipe) { + return AdaptationGuiSupport.isAdaptationRecipe(this, recipe); + } + + /** + * Generic attack context payload. + */ + record AttackContext(Player attacker, Entity target, ItemStack mainHand, + int level) { + } + + /** + * Block-action context payload. + */ + record BlockActionContext(Player player, Location location, int level) { + } + + /** + * Melee-specific context payload. + */ + record MeleeContext(Player attacker, LivingEntity target, ItemStack mainHand, + int level) { + } + + /** + * Projectile-specific context payload. + */ + record ProjectileContext(Player attacker, LivingEntity target, + Projectile projectile, int level) { + } +} diff --git a/src/main/java/art/arcane/adapt/api/adaptation/AdaptationEventRegistrar.java b/src/main/java/art/arcane/adapt/api/adaptation/AdaptationEventRegistrar.java new file mode 100644 index 000000000..a01b3203f --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/adaptation/AdaptationEventRegistrar.java @@ -0,0 +1,107 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.adaptation; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.EventHandlerInvoker; +import org.bukkit.Bukkit; +import org.bukkit.event.*; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class AdaptationEventRegistrar { + private AdaptationEventRegistrar() { + } + + public static boolean register(Plugin plugin, Listener listener) { + if (!(listener instanceof Adaptation)) { + return false; + } + + boolean registeredAny = false; + for (Method method : collectHandlerMethods(listener.getClass()).values()) { + EventHandler annotation = method.getAnnotation(EventHandler.class); + if (annotation == null || method.getParameterCount() != 1 || Modifier.isStatic(method.getModifiers())) { + continue; + } + + Class parameterType = method.getParameterTypes()[0]; + if (!Event.class.isAssignableFrom(parameterType)) { + continue; + } + + @SuppressWarnings("unchecked") + Class eventType = (Class) parameterType; + try { + method.setAccessible(true); + } catch (Throwable ex) { + Adapt.warn("Failed enabling access to adaptation handler " + + listener.getClass().getName() + "#" + method.getName() + + ": " + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + continue; + } + + EventExecutor executor = EventHandlerInvoker.createExecutor(method, eventType); + + boolean ignoreCancelled = shouldIgnoreCancelled(method, annotation, eventType); + Bukkit.getPluginManager().registerEvent(eventType, listener, annotation.priority(), executor, plugin, ignoreCancelled); + registeredAny = true; + } + + return registeredAny; + } + + private static Map collectHandlerMethods(Class type) { + Map methods = new LinkedHashMap<>(); + Class current = type; + while (current != null && current != Object.class) { + for (Method method : current.getDeclaredMethods()) { + if (!method.isAnnotationPresent(EventHandler.class)) { + continue; + } + methods.putIfAbsent(signature(method), method); + } + current = current.getSuperclass(); + } + return methods; + } + + private static String signature(Method method) { + return method.getName() + "|" + Arrays.toString(method.getParameterTypes()); + } + + private static boolean shouldIgnoreCancelled(Method method, EventHandler annotation, Class eventType) { + if (!Cancellable.class.isAssignableFrom(eventType)) { + return annotation.ignoreCancelled(); + } + + if (method.isAnnotationPresent(ReceiveCancelledEvents.class)) { + return false; + } + + return true; + } +} diff --git a/src/main/java/art/arcane/adapt/api/adaptation/AdaptationGuiSupport.java b/src/main/java/art/arcane/adapt/api/adaptation/AdaptationGuiSupport.java new file mode 100644 index 000000000..98aa84bed --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/adaptation/AdaptationGuiSupport.java @@ -0,0 +1,572 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.adaptation; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.inventorygui.GuiEffects; +import art.arcane.adapt.util.common.inventorygui.GuiLayout; +import art.arcane.adapt.util.common.inventorygui.GuiTheme; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.data.MaterialBlock; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.inventorygui.UIElement; +import art.arcane.volmlib.util.inventorygui.UIWindow; +import art.arcane.volmlib.util.math.M; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Recipe; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +final class AdaptationGuiSupport { + private static final Map PERMANENT_LEARN_CONFIRMATIONS = new ConcurrentHashMap<>(); + private static final long PERMANENT_LEARN_CONFIRM_WINDOW_MS = 6_000L; + private static final long CLOSE_SUPPRESS_MS = 1200L; + private static final int CLOSE_SUPPRESS_CLEAR_TICKS = 4; + private static final Map CLOSE_SUPPRESS_UNTIL = new ConcurrentHashMap<>(); + + private AdaptationGuiSupport() { + } + + static boolean areParticlesEnabled(Adaptation adaptation, boolean componentEnabled) { + if (!componentEnabled) { + return false; + } + + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + if (effects != null && effects.getAdaptationParticleOverrides() != null && !effects.getAdaptationParticleOverrides().isEmpty()) { + String key = adaptation.getName(); + Boolean override = effects.getAdaptationParticleOverrides().get(key); + if (override == null && key != null) { + override = effects.getAdaptationParticleOverrides().get(key.toLowerCase(Locale.ROOT)); + } + if (override != null && !override) { + return false; + } + } + + Object config = adaptation.getConfig(); + if (config == null) { + return true; + } + + Boolean directToggle = readBooleanField(config, "showParticles"); + if (directToggle != null) { + return directToggle; + } + + Boolean genericToggle = readBooleanField(config, "showParticleEffects"); + if (genericToggle != null) { + return genericToggle; + } + + return true; + } + + static boolean areSoundsEnabled(Adaptation adaptation, boolean componentEnabled) { + if (!componentEnabled) { + return false; + } + + Object config = adaptation.getConfig(); + if (config == null) { + return true; + } + + Boolean directToggle = readBooleanField(config, "showSounds"); + if (directToggle != null) { + return directToggle; + } + + return true; + } + + static String getDisplayName(Adaptation adaptation) { + if (!adaptation.isEnabled()) { + return C.DARK_GRAY + Form.capitalizeWords(adaptation.getName().replaceAll("\\Q" + adaptation.getSkill().getName() + "-\\E", "").replaceAll("\\Q-\\E", " ")); + } + if (!adaptation.getSkill().isEnabled()) { + return C.DARK_GRAY + Form.capitalizeWords(adaptation.getName().replaceAll("\\Q" + adaptation.getSkill().getName() + "-\\E", "").replaceAll("\\Q-\\E", " ")); + } + return C.RESET + "" + C.BOLD + adaptation.getSkill().getColor().toString() + Form.capitalizeWords(adaptation.getName().replaceAll("\\Q" + adaptation.getSkill().getName() + "-\\E", "").replaceAll("\\Q-\\E", " ")); + } + + static String getDisplayName(Adaptation adaptation, int level) { + if (!adaptation.isEnabled()) { + return adaptation.getDisplayName(); + } + if (!adaptation.getSkill().isEnabled()) { + return adaptation.getDisplayName(); + } + if (level >= 1) { + return adaptation.getDisplayName() + C.RESET + " " + C.UNDERLINE + C.WHITE + Form.toRoman(level) + C.RESET; + } + + return adaptation.getDisplayName(); + } + + static String getDisplayNameNoRoman(Adaptation adaptation, int level) { + if (level >= 1) { + return adaptation.getDisplayName() + C.RESET + " " + C.UNDERLINE + C.WHITE + level + C.RESET; + } + + return adaptation.getDisplayName(); + } + + static BlockFace getBlockFace(Player player, int maxrange) { + List lastTwoTargetBlocks = player.getLastTwoTargetBlocks(null, maxrange); + if (lastTwoTargetBlocks.size() != 2 || !lastTwoTargetBlocks.get(1).getType().isOccluding()) + return null; + Block targetBlock = lastTwoTargetBlocks.get(1); + Block adjacentBlock = lastTwoTargetBlocks.get(0); + return targetBlock.getFace(adjacentBlock); + } + + static CustomModel getModel(Adaptation adaptation) { + return CustomModel.get(adaptation.getIcon(), "adaptation", adaptation.getName(), "icon"); + } + + static CustomModel getModel(Adaptation adaptation, int level) { + CustomModel model = CustomModel.get(adaptation.getIcon(), "adaptation", adaptation.getName(), "level-" + level); + if (model.material() == adaptation.getIcon() && model.model() == 0) + model = CustomModel.get(Material.PAPER, "snippets", "gui", "level", String.valueOf(level)); + if (model.material() == Material.PAPER && model.model() == 0) + model = adaptation.getModel(); + return model; + } + + static boolean openGui(Adaptation adaptation, Player player, boolean checkPermissions) { + if (checkPermissions && !adaptation.hasUsePermission(player, adaptation)) { + return false; + } else { + openGui(adaptation, player); + return true; + } + } + + static void openGui(Adaptation adaptation, Player player) { + openGui(adaptation, player, 0); + } + + static void openGui(Adaptation adaptation, Player player, int page) { + if (!adaptation.isEnabled()) { + return; + } + if (!adaptation.getSkill().isEnabled()) { + return; + } + if (!J.isPrimaryThread()) { + int targetPage = page; + J.runEntity(player, () -> openGui(adaptation, player, targetPage)); + return; + } + + SoundPlayer spw = SoundPlayer.of(player.getWorld()); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 0.655f); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 0.855f); + + boolean reserveNavigation = AdaptConfig.get().isGuiBackButton(); + GuiLayout.PagePlan plan = GuiLayout.plan(adaptation.getMaxLevel(), reserveNavigation); + int currentPage = GuiLayout.clampPage(page, plan.pageCount()); + int start = currentPage * plan.itemsPerPage(); + int end = Math.min(adaptation.getMaxLevel(), start + plan.itemsPerPage()); + + int mylevel = adaptation.getPlayer(player).getSkillLine(adaptation.getSkill().getName()).getAdaptationLevel(adaptation.getName()); + + long k = adaptation.getPlayer(player).getData().getSkillLine(adaptation.getSkill().getName()).getKnowledge(); + + UIWindow w = new UIWindow(Adapt.instance, player); + GuiTheme.apply(w, "skill/" + adaptation.getSkill().getName() + "/" + adaptation.getName()); + w.setViewportHeight(plan.rows()); + + List reveal = new ArrayList<>(); + for (int row = 0; row < plan.contentRows(); row++) { + int rowStart = start + (row * GuiLayout.WIDTH); + if (rowStart >= end) { + break; + } + + int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); + for (int i = 0; i < rowCount; i++) { + int lvl = rowStart + i + 1; + int pos = GuiLayout.centeredPosition(i, rowCount); + int c = adaptation.getCostFor(lvl, mylevel); + int rc = adaptation.getRefundCostFor(lvl - 1, mylevel); + int pc = adaptation.getPowerCostFor(lvl, mylevel); + boolean pendingPermanentConfirm = isPermanentLearnConfirmationPending(player, adaptation, lvl); + Element de = new UIElement("lp-" + lvl + "g") + .setMaterial(new MaterialBlock(adaptation.getIcon())) + .setBaseItemStack(adaptation.getModel(lvl).toItemStack()) + .setName(adaptation.getDisplayName(lvl)) + .setEnchanted(mylevel >= lvl) + .setProgress(1D) + .addLore(C.GRAY + adaptation.getDescription()) + .addLore(mylevel >= lvl ? ("") : ("" + C.WHITE + c + C.GRAY + " " + Localizer.dLocalize("snippets.adapt_menu.knowledge_cost") + " " + (AdaptConfig.get().isHardcoreNoRefunds() ? C.DARK_RED + "" + C.BOLD + Localizer.dLocalize("snippets.adapt_menu.no_refunds") : ""))) + .addLore(mylevel >= lvl ? AdaptConfig.get().isHardcoreNoRefunds() ? (C.GREEN + Localizer.dLocalize("snippets.adapt_menu.already_learned") + " " + C.DARK_RED + "" + C.BOLD + Localizer.dLocalize("snippets.adapt_menu.no_refunds")) : (adaptation.isPermanent() ? "" : (C.GREEN + Localizer.dLocalize("snippets.adapt_menu.already_learned") + " " + C.GRAY + Localizer.dLocalize("snippets.adapt_menu.unlearn_refund") + " " + C.GREEN + rc + " " + Localizer.dLocalize("snippets.adapt_menu.knowledge_cost"))) : (k >= c ? (C.BLUE + Localizer.dLocalize("snippets.adapt_menu.click_learn") + " " + adaptation.getDisplayName(lvl)) : (k == 0 ? (C.RED + Localizer.dLocalize("snippets.adapt_menu.no_knowledge")) : (C.RED + "(" + Localizer.dLocalize("snippets.adapt_menu.you_only_have") + " " + C.WHITE + k + C.RED + " " + Localizer.dLocalize("snippets.adapt_menu.knowledge_available") + ")")))) + .addLore(mylevel < lvl && adaptation.getPlayer(player).getData().hasPowerAvailable(pc) ? C.GREEN + "" + lvl + " " + Localizer.dLocalize("snippets.adapt_menu.power_drain") : mylevel >= lvl ? C.GREEN + "" + lvl + " " + Localizer.dLocalize("snippets.adapt_menu.power_drain") : C.RED + Localizer.dLocalize("snippets.adapt_menu.not_enough_power") + "\n" + C.RED + Localizer.dLocalize("snippets.adapt_menu.how_to_level_up")) + .addLore((adaptation.isPermanent() ? C.RED + "" + C.BOLD + Localizer.dLocalize("snippets.adapt_menu.may_not_unlearn") : "")) + .addLore(adaptation.isPermanent() && mylevel < lvl + ? (pendingPermanentConfirm + ? C.GOLD + "" + C.BOLD + "Click again now to confirm permanent learn." + : C.YELLOW + "Double-click required to confirm permanent learn.") + : "") + .onLeftClick((e) -> { + AdaptPlayer adaptPlayer = adaptation.getPlayer(player); + PlayerSkillLine skillLine = adaptPlayer.getSkillLine(adaptation.getSkill().getName()); + if (skillLine == null) { + spw.play(player.getLocation(), Sound.BLOCK_BAMBOO_HIT, 0.7f, 1.855f); + return; + } + + int delayTicks = AdaptConfig.get().getLearnUnlearnButtonDelayTicks(); + int currentLevel = skillLine.getAdaptationLevel(adaptation.getName()); + if (currentLevel >= lvl) { + adaptation.unlearn(player, lvl, false); + int updatedLevel = skillLine.getAdaptationLevel(adaptation.getName()); + if (updatedLevel < currentLevel) { + spw.play(player.getLocation(), Sound.BLOCK_NETHER_GOLD_ORE_PLACE, 0.7f, 1.355f); + spw.play(player.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.4f, 0.755f); + if (delayTicks != 0) { + player.sendTitle(" ", C.GRAY + Localizer.dLocalize("snippets.adapt_menu.unlearned") + " " + adaptation.getDisplayName(currentLevel), 1, 10, 11); + } + closeAndReopenAfterLevelChange(adaptation, player, currentPage, delayTicks); + return; + } + + spw.play(player.getLocation(), Sound.ENTITY_BLAZE_DEATH, 0.5f, 1.355f); + if (delayTicks != 0) { + player.sendTitle(" ", C.RED + "" + C.BOLD + Localizer.dLocalize("snippets.adapt_menu.may_not_unlearn") + " " + adaptation.getDisplayName(currentLevel), 1, 10, 11); + } + J.runEntity(player, () -> openAdaptationPage(adaptation, player, currentPage), delayTicks); + return; + } + + long currentKnowledge = skillLine.getKnowledge(); + if (currentKnowledge >= c && adaptPlayer.getData().hasPowerAvailable(pc)) { + if (adaptation.isPermanent() && !consumePermanentLearnConfirmation(player, adaptation, lvl)) { + spw.play(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.7f, 0.85f); + player.sendTitle(" ", C.GOLD + "" + C.BOLD + "Click again to confirm permanent learn", 1, 16, 8); + J.runEntity(player, () -> openAdaptationPage(adaptation, player, currentPage), 1); + return; + } + + if (skillLine.spendKnowledge(c)) { + skillLine.setAdaptation(adaptation, lvl); + spw.play(player.getLocation(), Sound.BLOCK_NETHER_GOLD_ORE_PLACE, 0.9f, 1.355f); + spw.play(player.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1.7f, 0.355f); + spw.play(player.getLocation(), Sound.BLOCK_BEACON_POWER_SELECT, 0.4f, 0.155f); + spw.play(player.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 0.2f, 1.455f); + if (adaptation.isPermanent()) { + spw.play(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.7f, 1.355f); + spw.play(player.getLocation(), Sound.ITEM_GOAT_HORN_SOUND_1, 0.7f, 1.355f); + } + if (delayTicks != 0) { + player.sendTitle(" ", C.GRAY + Localizer.dLocalize("snippets.adapt_menu.learned") + " " + adaptation.getDisplayName(lvl), 1, 5, 11); + } + closeAndReopenAfterLevelChange(adaptation, player, currentPage, delayTicks); + } else { + spw.play(player.getLocation(), Sound.BLOCK_BAMBOO_HIT, 0.7f, 1.855f); + } + } else { + spw.play(player.getLocation(), Sound.BLOCK_BAMBOO_HIT, 0.7f, 1.855f); + } + }); + de.addLore(" "); + adaptation.addStats(lvl, de); + reveal.add(new GuiEffects.Placement(pos, row, de)); + } + } + GuiEffects.applyReveal(w, reveal); + + if (plan.hasNavigationRow()) { + int navRow = plan.rows() - 1; + int jumpPages = 5; + int jumpBack = Math.max(0, currentPage - jumpPages); + int jumpForward = Math.min(plan.pageCount() - 1, currentPage + jumpPages); + if (currentPage > 0) { + w.setElement(-4, navRow, new UIElement("adapt-prev") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.WHITE + "Previous") + .addLore(C.GRAY + "Right click: jump -" + jumpPages + " pages") + .onLeftClick((e) -> openAdaptationPage(adaptation, player, currentPage - 1)) + .onRightClick((e) -> openAdaptationPage(adaptation, player, jumpBack))); + w.setElement(-3, navRow, new UIElement("adapt-first") + .setMaterial(new MaterialBlock(Material.LECTERN)) + .setName(C.GRAY + "First") + .onLeftClick((e) -> openAdaptationPage(adaptation, player, 0))); + } + if (currentPage < plan.pageCount() - 1) { + w.setElement(4, navRow, new UIElement("adapt-next") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.WHITE + "Next") + .addLore(C.GRAY + "Right click: jump +" + jumpPages + " pages") + .onLeftClick((e) -> openAdaptationPage(adaptation, player, currentPage + 1)) + .onRightClick((e) -> openAdaptationPage(adaptation, player, jumpForward))); + w.setElement(3, navRow, new UIElement("adapt-last") + .setMaterial(new MaterialBlock(Material.LECTERN)) + .setName(C.GRAY + "Last") + .onLeftClick((e) -> openAdaptationPage(adaptation, player, plan.pageCount() - 1))); + } + + int from = adaptation.getMaxLevel() <= 0 ? 0 : (start + 1); + int to = adaptation.getMaxLevel() <= 0 ? 0 : end; + w.setElement(-1, navRow, new UIElement("adapt-page-info") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.AQUA + "Page " + (currentPage + 1) + "/" + plan.pageCount()) + .addLore(C.GRAY + "Showing " + from + "-" + to + " of " + adaptation.getMaxLevel()) + .setProgress(1D)); + + if (AdaptConfig.get().isGuiBackButton()) { + w.setElement(0, navRow, new UIElement("back") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName("" + C.RESET + C.GRAY + Localizer.dLocalize("snippets.gui.back")) + .onLeftClick((e) -> navigateBack(adaptation, player))); + } + + } + + w.setTitle(adaptation.getDisplayName()); + w.onClosed((vv) -> J.runEntity(player, () -> onGuiClosed(adaptation, player, !AdaptConfig.get().isEscClosesAllGuis()))); + w.open(); + Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); + } + + static boolean isPermanentLearnConfirmationPending(Player player, Adaptation adaptation, int level) { + if (player == null || adaptation == null) { + return false; + } + + Long until = PERMANENT_LEARN_CONFIRMATIONS.get(permanentConfirmKey(player, adaptation, level)); + return until != null && until >= M.ms(); + } + + static boolean consumePermanentLearnConfirmation(Player player, Adaptation adaptation, int level) { + if (player == null) { + return false; + } + + long now = M.ms(); + PERMANENT_LEARN_CONFIRMATIONS.entrySet().removeIf(e -> e.getValue() < now); + + String key = permanentConfirmKey(player, adaptation, level); + Long until = PERMANENT_LEARN_CONFIRMATIONS.get(key); + if (until != null && until >= now) { + PERMANENT_LEARN_CONFIRMATIONS.remove(key); + return true; + } + + String prefix = permanentConfirmPrefix(player, adaptation); + PERMANENT_LEARN_CONFIRMATIONS.keySet().removeIf(existing -> existing.startsWith(prefix)); + PERMANENT_LEARN_CONFIRMATIONS.put(key, now + PERMANENT_LEARN_CONFIRM_WINDOW_MS); + return false; + } + + static void unlearn(Adaptation adaptation, Player player, int lvl, boolean force) { + if (adaptation.isPermanent() && !force) { + return; + } + int myLevel = adaptation.getPlayer(player).getSkillLine(adaptation.getSkill().getName()).getAdaptationLevel(adaptation.getName()); + int rc = adaptation.getRefundCostFor(lvl - 1, myLevel); + if (!AdaptConfig.get().isHardcoreNoRefunds()) { + adaptation.getPlayer(player).getData().getSkillLine(adaptation.getSkill().getName()).giveKnowledge(rc); + } + adaptation.getPlayer(player).getData().getSkillLine(adaptation.getSkill().getName()).setAdaptation(adaptation, lvl - 1); + } + + static void learn(Adaptation adaptation, Player player, int lvl, boolean force) { + int myLevel = adaptation.getPlayer(player).getSkillLine(adaptation.getSkill().getName()).getAdaptationLevel(adaptation.getName()); + int c = adaptation.getCostFor(lvl, myLevel); + if (adaptation.getPlayer(player).getData().hasPowerAvailable(c) || force) { + if (adaptation.getPlayer(player).getData().getSkillLine(adaptation.getSkill().getName()).spendKnowledge(c) || force) { + adaptation.getPlayer(player).getData().getSkillLine(adaptation.getSkill().getName()).setAdaptation(adaptation, lvl); + } + } + } + + static boolean isAdaptationRecipe(Adaptation adaptation, Recipe recipe) { + if (!adaptation.isEnabled()) { + return false; + } + if (!adaptation.getSkill().isEnabled()) { + return false; + } + for (AdaptRecipe i : adaptation.getRecipes()) { + if (i.is(recipe)) { + return true; + } + } + return false; + } + + private static Boolean readBooleanField(Object source, String fieldName) { + if (source == null || fieldName == null || fieldName.isBlank()) { + return null; + } + + Class current = source.getClass(); + while (current != null) { + try { + Field field = current.getDeclaredField(fieldName); + field.setAccessible(true); + Object value = field.get(source); + if (value instanceof Boolean bool) { + return bool; + } + return null; + } catch (NoSuchFieldException ex) { + current = current.getSuperclass(); + } catch (Throwable ex) { + Adapt.verbose("Failed reading boolean field '" + fieldName + "' from " + source.getClass().getName() + + ": " + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + return null; + } + } + + return null; + } + + private static void openAdaptationPage(Adaptation adaptation, Player player, int page) { + suppressClose(player); + openGui(adaptation, player, page); + } + + private static void closeAndReopenAfterLevelChange(Adaptation adaptation, Player player, int page, int delayTicks) { + closeCurrentAdaptationGui(player); + int reopenDelay = Math.max(0, delayTicks); + J.runEntity(player, () -> reopenAdaptationPageIfReady(adaptation, player, page), reopenDelay); + } + + private static void closeCurrentAdaptationGui(Player player) { + if (player == null || !player.isOnline()) { + return; + } + + suppressClose(player); + Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString()); + if (player.getOpenInventory() != null && player.getOpenInventory().getTopInventory().getType() != InventoryType.CRAFTING) { + player.closeInventory(); + } + } + + private static void reopenAdaptationPageIfReady(Adaptation adaptation, Player player, int page) { + if (player == null || !player.isOnline()) { + return; + } + + if (player.getOpenInventory() == null || player.getOpenInventory().getTopInventory().getType() != InventoryType.CRAFTING) { + return; + } + + openAdaptationPage(adaptation, player, page); + } + + private static void navigateBack(Adaptation adaptation, Player player) { + playCloseSound(player); + suppressClose(player); + adaptation.getSkill().openGui(player); + } + + private static void onGuiClosed(Adaptation adaptation, Player player, boolean openPrevGui) { + if (player == null) { + return; + } + + if (consumeCloseSuppression(player)) { + return; + } + + playCloseSound(player); + if (openPrevGui) { + J.runEntity(player, () -> { + if (player.isOnline() && player.getOpenInventory().getTopInventory().getType() == InventoryType.CRAFTING) { + adaptation.getSkill().openGui(player); + } + }, 1); + } else { + Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString()); + } + } + + private static void playCloseSound(Player player) { + SoundPlayer spw = SoundPlayer.of(player.getWorld()); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 0.655f); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 0.855f); + } + + private static void suppressClose(Player player) { + if (player == null) { + return; + } + + UUID playerId = player.getUniqueId(); + long suppressUntil = System.currentTimeMillis() + CLOSE_SUPPRESS_MS; + CLOSE_SUPPRESS_UNTIL.put(playerId, suppressUntil); + J.s(() -> { + Long current = CLOSE_SUPPRESS_UNTIL.get(playerId); + if (current != null && current.longValue() == suppressUntil) { + CLOSE_SUPPRESS_UNTIL.remove(playerId); + } + }, CLOSE_SUPPRESS_CLEAR_TICKS); + } + + private static boolean consumeCloseSuppression(Player player) { + if (player == null) { + return false; + } + + Long until = CLOSE_SUPPRESS_UNTIL.get(player.getUniqueId()); + if (until == null) { + return false; + } + + if (until >= System.currentTimeMillis()) { + CLOSE_SUPPRESS_UNTIL.remove(player.getUniqueId()); + return true; + } + + CLOSE_SUPPRESS_UNTIL.remove(player.getUniqueId()); + return false; + } + + private static String permanentConfirmPrefix(Player player, Adaptation adaptation) { + return player.getUniqueId() + "|" + adaptation.getName() + "|"; + } + + private static String permanentConfirmKey(Player player, Adaptation adaptation, int level) { + return permanentConfirmPrefix(player, adaptation) + level; + } +} diff --git a/src/main/java/art/arcane/adapt/api/adaptation/AdaptationRuntimeGuards.java b/src/main/java/art/arcane/adapt/api/adaptation/AdaptationRuntimeGuards.java new file mode 100644 index 000000000..863af338c --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/adaptation/AdaptationRuntimeGuards.java @@ -0,0 +1,880 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.adaptation; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.protection.Protector; +import art.arcane.adapt.api.telemetry.AbilityCheckTelemetry; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.content.adaptation.tragoul.TragoulSkeletalServant; +import art.arcane.adapt.content.event.AdaptAdaptationUseEvent; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.math.M; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.AnimalTamer; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Display; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Interaction; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Tameable; +import org.bukkit.event.Cancellable; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.IntConsumer; +import java.util.function.Predicate; + +final class AdaptationRuntimeGuards { + private static final Map USAGE_BASELINE_XP_COOLDOWNS = new ConcurrentHashMap<>(); + private static final Map ACTIVE_LEVEL_CACHE = new ConcurrentHashMap<>(); + private static final Map USE_PERMISSION_NODES = new ConcurrentHashMap<>(); + private static final Map PROTECTOR_CACHE = new ConcurrentHashMap<>(); + private static final Map USAGE_CONFLICT_CACHE = new ConcurrentHashMap<>(); + private static final Map LEARNED_CANDIDATE_CACHE = new ConcurrentHashMap<>(); + private static final int ACTIVE_LEVEL_CACHE_SOFT_LIMIT = 16_384; + private static final int ACTIVE_LEVEL_CACHE_SWEEP_INTERVAL_TICKS = 64; + private static final int ACTIVE_LEVEL_CACHE_RETENTION_TICKS = 2; + private static final long LEARNED_CANDIDATE_REFRESH_MS = 250L; + private static volatile long lastActiveCacheSweepTick = Long.MIN_VALUE; + + private AdaptationRuntimeGuards() { + } + + static void withPlayerThread(Adaptation adaptation, Player p, Runnable runnable) { + try { + if (p == null || runnable == null) { + return; + } + + if (J.isFoliaThreading() && !J.isOwnedByCurrentRegion(p)) { + J.runEntity(p, () -> withPlayerThread(adaptation, p, runnable)); + return; + } + + runnable.run(); + } catch (Exception ex) { + Adapt.verbose("Failed guarded player runnable for adaptation " + adaptation.getName() + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + } + + static void withPlayerThread(Adaptation adaptation, Player p, Cancellable cancellable, Runnable runnable) { + try { + if (p == null || cancellable == null || runnable == null) { + return; + } + + if (cancellable.isCancelled()) { + return; + } + + if (J.isFoliaThreading() && !J.isOwnedByCurrentRegion(p)) { + J.runEntity(p, () -> withPlayerThread(adaptation, p, cancellable, runnable)); + return; + } + + if (cancellable.isCancelled()) { + return; + } + + runnable.run(); + } catch (Exception ex) { + Adapt.verbose("Failed guarded cancellable player runnable for adaptation " + adaptation.getName() + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + } + + static void withAdaptedPlayer(Adaptation adaptation, Player p, Runnable runnable) { + withPlayerThread(adaptation, p, () -> { + if (!adaptation.hasActiveAdaptation(p)) { + return; + } + runnable.run(); + }); + } + + static void withAdaptedPlayer(Adaptation adaptation, Player p, Cancellable cancellable, Runnable runnable) { + withPlayerThread(adaptation, p, cancellable, () -> { + if (!adaptation.hasActiveAdaptation(p)) { + return; + } + runnable.run(); + }); + } + + static void withActiveLevel(Adaptation adaptation, Player p, IntConsumer consumer) { + withPlayerThread(adaptation, p, () -> { + int level = getActiveLevel(adaptation, p); + if (level <= 0) { + return; + } + consumer.accept(level); + }); + } + + static void withActiveLevel(Adaptation adaptation, Player p, Cancellable cancellable, IntConsumer consumer) { + withPlayerThread(adaptation, p, cancellable, () -> { + int level = getActiveLevel(adaptation, p); + if (level <= 0) { + return; + } + consumer.accept(level); + }); + } + + static String adaptationRewardKey(Adaptation adaptation, String rewardKey) { + String suffix = rewardKey == null ? "" : rewardKey.trim(); + if (suffix.isEmpty()) { + suffix = "use"; + } + return "adaptation:" + adaptation.getName() + ":" + suffix; + } + + static void awardUsageBaselineXp(Adaptation adaptation, Player p, int level) { + if (p == null || level <= 0 || !p.getClass().getSimpleName().equals("CraftPlayer")) { + return; + } + + AdaptConfig.AdaptationXp cfg = AdaptConfig.get().getAdaptationXp(); + if (cfg == null || !cfg.isUsageBaselineEnabled()) { + return; + } + + long now = M.ms(); + long cooldown = Math.max(250L, cfg.getUsageBaselineCooldownMillis()); + PlayerAdaptationKey key = new PlayerAdaptationKey(p.getUniqueId(), adaptation.getName()); + Long next = USAGE_BASELINE_XP_COOLDOWNS.get(key); + if (next != null && next > now) { + return; + } + + if (USAGE_BASELINE_XP_COOLDOWNS.size() > 6000) { + USAGE_BASELINE_XP_COOLDOWNS.entrySet().removeIf(i -> i.getValue() <= now); + } + + double reward = cfg.getUsageBaselineXp() + ((Math.max(1, level) - 1) * cfg.getUsageBaselineXpPerLevel()); + if (reward <= 0) { + return; + } + + USAGE_BASELINE_XP_COOLDOWNS.put(key, now + cooldown); + adaptation.xpSilent(p, reward, "baseline-use"); + } + + static boolean canUse(Adaptation adaptation, AdaptPlayer player) { + if (AdaptConfig.get().isVerbose()) { + Adapt.verbose("Checking if " + player.getPlayer().getName() + " can use " + adaptation.getName() + "..."); + } + AdaptAdaptationUseEvent e = new AdaptAdaptationUseEvent(!Bukkit.isPrimaryThread(), player, adaptation); + Bukkit.getServer().getPluginManager().callEvent(e); + return (!e.isCancelled()); + } + + static boolean canUse(Adaptation adaptation, Player player) { + return canUse(adaptation, adaptation.getPlayer(player)); + } + + static boolean hasUsePermission(Adaptation adaptation, Player p, Adaptation targetAdaptation) { + if (p == null) { + return false; + } + if (p.isOp()) { + return true; + } + Adaptation target = targetAdaptation == null ? adaptation : targetAdaptation; + if (target == null) { + return false; + } + String usePermission = USE_PERMISSION_NODES.computeIfAbsent(target.getName(), n -> "adapt.use." + n.replace("-", "")); + boolean permissionSet = p.isPermissionSet(usePermission); + if (AdaptConfig.get().isVerbose()) { + Adapt.verbose("Checking use permission " + usePermission + " for " + p.getName() + + " (set=" + permissionSet + ", value=" + p.hasPermission(usePermission) + ")"); + } + if (!permissionSet) { + return true; + } + return p.hasPermission(usePermission); + } + + static boolean canDamageTarget(Adaptation adaptation, Player attacker, Entity target) { + if (attacker == null || target == null) { + return false; + } + + if (isProtectedFriendly(attacker, target)) { + return false; + } + + if (target instanceof Player victim) { + return adaptation.canPVP(attacker, victim.getLocation()); + } + + return adaptation.canPVE(attacker, target.getLocation()); + } + + static boolean isProtectedFriendly(Player actor, Entity target) { + if (target == null) { + return false; + } + + if (target instanceof Display || target instanceof Interaction) { + return true; + } + + if (target instanceof ArmorStand stand && stand.isMarker()) { + return true; + } + + if (target.isInvulnerable()) { + return true; + } + + if (target.hasMetadata("NPC")) { + return true; + } + + if (TragoulSkeletalServant.isServant(target)) { + return true; + } + + if (actor != null && target instanceof Tameable tameable && tameable.isTamed()) { + AnimalTamer tamer = tameable.getOwner(); + return tamer != null && actor.getUniqueId().equals(tamer.getUniqueId()); + } + + return false; + } + + static int getActiveSurvivalLevel(Adaptation adaptation, Player player) { + if (player == null || player.getGameMode() != GameMode.SURVIVAL) { + return 0; + } + + return getActiveLevel(adaptation, player); + } + + static int getActiveLevel(Adaptation adaptation, Player player, Predicate requirement) { + int level = getActiveLevel(adaptation, player); + if (level <= 0) { + return 0; + } + + if (requirement != null && !requirement.test(player)) { + return 0; + } + + return level; + } + + static int getActiveSurvivalLevel(Adaptation adaptation, Player player, Predicate requirement) { + int level = getActiveSurvivalLevel(adaptation, player); + if (level <= 0) { + return 0; + } + + if (requirement != null && !requirement.test(player)) { + return 0; + } + + return level; + } + + static int getActiveInteractLevel(Adaptation adaptation, Player player, Location location) { + int level = getActiveLevel(adaptation, player); + if (level <= 0 || location == null) { + return 0; + } + + return adaptation.canInteract(player, location) ? level : 0; + } + + static int getActiveBlockBreakLevel(Adaptation adaptation, Player player, Location location) { + int level = getActiveLevel(adaptation, player); + if (level <= 0 || location == null) { + return 0; + } + + return adaptation.canBlockBreak(player, location) ? level : 0; + } + + static int getActiveBlockPlaceLevel(Adaptation adaptation, Player player, Location location) { + int level = getActiveLevel(adaptation, player); + if (level <= 0 || location == null) { + return 0; + } + + return adaptation.canBlockPlace(player, location) ? level : 0; + } + + static int getActiveDamageLevel(Adaptation adaptation, Player attacker, Entity target) { + int level = getActiveLevel(adaptation, attacker); + if (level <= 0 || target == null) { + return 0; + } + + return canDamageTarget(adaptation, attacker, target) ? level : 0; + } + + static Adaptation.BlockActionContext resolveInteractContext(Adaptation adaptation, Player player, Location location) { + return resolveInteractContext(adaptation, player, location, null, false); + } + + static Adaptation.BlockActionContext resolveInteractContext(Adaptation adaptation, Player player, Location location, Predicate requirement) { + return resolveInteractContext(adaptation, player, location, requirement, false); + } + + static Adaptation.BlockActionContext resolveInteractContext(Adaptation adaptation, Player player, Location location, Predicate requirement, boolean survivalOnly) { + int level = resolveActionLevel(adaptation, player, requirement, survivalOnly); + if (level <= 0 || location == null || !adaptation.canInteract(player, location)) { + return null; + } + + return new Adaptation.BlockActionContext(player, location, level); + } + + static Adaptation.BlockActionContext resolveBlockBreakContext(Adaptation adaptation, Player player, Location location) { + return resolveBlockBreakContext(adaptation, player, location, null, false); + } + + static Adaptation.BlockActionContext resolveBlockBreakContext(Adaptation adaptation, Player player, Location location, Predicate requirement) { + return resolveBlockBreakContext(adaptation, player, location, requirement, false); + } + + static Adaptation.BlockActionContext resolveBlockBreakContext(Adaptation adaptation, Player player, Location location, Predicate requirement, boolean survivalOnly) { + int level = resolveActionLevel(adaptation, player, requirement, survivalOnly); + if (level <= 0 || location == null || !adaptation.canBlockBreak(player, location)) { + return null; + } + + return new Adaptation.BlockActionContext(player, location, level); + } + + static Adaptation.BlockActionContext resolveBlockPlaceContext(Adaptation adaptation, Player player, Location location) { + return resolveBlockPlaceContext(adaptation, player, location, null, false); + } + + static Adaptation.BlockActionContext resolveBlockPlaceContext(Adaptation adaptation, Player player, Location location, Predicate requirement) { + return resolveBlockPlaceContext(adaptation, player, location, requirement, false); + } + + static Adaptation.BlockActionContext resolveBlockPlaceContext(Adaptation adaptation, Player player, Location location, Predicate requirement, boolean survivalOnly) { + int level = resolveActionLevel(adaptation, player, requirement, survivalOnly); + if (level <= 0 || location == null || !adaptation.canBlockPlace(player, location)) { + return null; + } + + return new Adaptation.BlockActionContext(player, location, level); + } + + static Adaptation.BlockActionContext resolveInteractBreakContext(Adaptation adaptation, Player player, Location location) { + return resolveInteractBreakContext(adaptation, player, location, null, false); + } + + static Adaptation.BlockActionContext resolveInteractBreakContext(Adaptation adaptation, Player player, Location location, Predicate requirement) { + return resolveInteractBreakContext(adaptation, player, location, requirement, false); + } + + static Adaptation.BlockActionContext resolveInteractBreakContext(Adaptation adaptation, Player player, Location location, Predicate requirement, boolean survivalOnly) { + Adaptation.BlockActionContext context = resolveInteractContext(adaptation, player, location, requirement, survivalOnly); + if (context == null || !adaptation.canBlockBreak(context.player(), context.location())) { + return null; + } + + return context; + } + + static ItemStack readyMainHand(Adaptation adaptation, Player player, Predicate requirement) { + if (player == null) { + return null; + } + + ItemStack hand = player.getInventory().getItemInMainHand(); + if (!adaptation.isItem(hand)) { + return null; + } + + if (requirement != null && (!requirement.test(hand) || player.hasCooldown(hand.getType()))) { + return null; + } + + return hand; + } + + static Adaptation.MeleeContext resolveMeleeContext(Adaptation adaptation, EntityDamageByEntityEvent event, Predicate mainHandRequirement) { + Adaptation.AttackContext attack = resolveAttackContext(adaptation, event, mainHandRequirement); + if (attack == null || !(attack.target() instanceof LivingEntity target)) { + return null; + } + + return new Adaptation.MeleeContext(attack.attacker(), target, attack.mainHand(), attack.level()); + } + + static Adaptation.AttackContext resolveAttackContext(Adaptation adaptation, EntityDamageByEntityEvent event, Predicate mainHandRequirement) { + if (event == null || !(event.getDamager() instanceof Player attacker)) { + return null; + } + + int level = getActiveLevel(adaptation, attacker); + if (level <= 0) { + return null; + } + + ItemStack hand = readyMainHand(adaptation, attacker, mainHandRequirement); + if (mainHandRequirement != null && hand == null) { + return null; + } + + Entity target = event.getEntity(); + if (!canDamageTarget(adaptation, attacker, target)) { + return null; + } + + return new Adaptation.AttackContext(attacker, target, hand, level); + } + + static Adaptation.ProjectileContext resolveProjectileContext(Adaptation adaptation, EntityDamageByEntityEvent event, Predicate projectileRequirement) { + if (event == null + || !(event.getDamager() instanceof Projectile projectile) + || !(projectile.getShooter() instanceof Player attacker) + || !(event.getEntity() instanceof LivingEntity target)) { + return null; + } + + if (projectileRequirement != null && !projectileRequirement.test(projectile)) { + return null; + } + + int level = getActiveLevel(adaptation, attacker); + if (level <= 0) { + return null; + } + + if (!canDamageTarget(adaptation, attacker, target)) { + return null; + } + + return new Adaptation.ProjectileContext(attacker, target, projectile, level); + } + + static boolean hasUsageConflict(Adaptation adaptation, Player p) { + if (adaptation == null || p == null) { + return false; + } + + Set denied = resolveUsageConflicts(adaptation); + if (denied.isEmpty()) { + return false; + } + + AdaptPlayer adaptPlayer = adaptation.getPlayer(p); + for (String conflict : denied) { + if (adaptPlayer.hasAdaptation(conflict)) { + Adapt.verbose("Player " + p.getName() + " has conflicting adaptation " + conflict + " and cannot use " + adaptation.getName()); + return true; + } + } + + return false; + } + + static Set getProtectors(Adaptation adaptation) { + if (adaptation == null) { + return Collections.emptySet(); + } + + List defaults = Adapt.instance.getProtectorRegistry().getDefaultProtectors(); + List allProtectors = Adapt.instance.getProtectorRegistry().getAllProtectors(); + Map overrides = AdaptConfig.get().getProtectionOverrides().getOrDefault(adaptation.getName(), Collections.emptyMap()); + int signature = buildProtectorSignature(defaults, allProtectors, overrides); + String cacheKey = adaptation.getName(); + ProtectorCacheEntry cached = PROTECTOR_CACHE.get(cacheKey); + if (cached != null && cached.signature() == signature) { + return cached.protectors(); + } + + Map byName = new HashMap<>(); + for (Protector protector : allProtectors) { + byName.put(protector.getName(), protector); + } + + Set resolved = new HashSet<>(defaults); + for (Map.Entry entry : overrides.entrySet()) { + String protectorName = entry.getKey(); + Boolean enabled = entry.getValue(); + if (protectorName == null || enabled == null) { + continue; + } + + if (enabled) { + Protector protector = byName.get(protectorName); + if (protector == null) { + Adapt.error("Could not find protector " + protectorName + " for adaptation " + adaptation.getName() + ". Skipping..."); + continue; + } + resolved.add(protector); + continue; + } + + resolved.removeIf(existing -> existing.getName().equals(protectorName)); + } + + Set immutable = Collections.unmodifiableSet(new HashSet<>(resolved)); + PROTECTOR_CACHE.put(cacheKey, new ProtectorCacheEntry(signature, immutable)); + return immutable; + } + + static int getActiveLevel(Adaptation adaptation, Player p) { + try { + if (p == null || p.isDead()) { + return 0; + } + + if (J.isFoliaThreading() && !J.isOwnedByCurrentRegion(p)) { + return 0; + } + + long tick = runtimeCacheTick(); + int learnedLevel = getLevel(adaptation, p); + PlayerAdaptationKey key = new PlayerAdaptationKey(p.getUniqueId(), adaptation.getName()); + ActiveLevelCacheEntry cached = ACTIVE_LEVEL_CACHE.get(key); + if (cached != null && cached.tick() == tick && cached.learnedLevel() == learnedLevel) { + AbilityCheckTelemetry.recordCacheHit(); + return cached.level(); + } + + AbilityCheckTelemetry.recordCacheMiss(); + AbilityCheckTelemetry.recordCheckAttempt(); + long startNs = System.nanoTime(); + int level; + try { + level = resolveActiveLevelUncached(adaptation, p, learnedLevel); + } finally { + AbilityCheckTelemetry.recordCheckTimingNanos(System.nanoTime() - startNs); + } + ACTIVE_LEVEL_CACHE.put(key, new ActiveLevelCacheEntry(tick, learnedLevel, level)); + sweepActiveLevelCache(tick); + + if (level > 0) { + AbilityCheckTelemetry.recordSuccessfulCheck(); + } + return level; + } catch (Exception e) { + if (e instanceof IndexOutOfBoundsException) { + Adapt.verbose("Citizens/PacketSpoofing is Messing stuff up again. I hate it."); + Adapt.verbose(e.getMessage()); + } else { + e.printStackTrace(); + } + return 0; + } + } + + static int getLevel(Adaptation adaptation, Player p) { + if (p == null) { + return 0; + } + if (J.isFoliaThreading() && !J.isOwnedByCurrentRegion(p)) { + return 0; + } + if (!p.getClass().getSimpleName().equals("CraftPlayer")) { + Adapt.verbose("Simple name: " + p.getClass().getSimpleName()); + return 0; + } + if (!adaptation.isEnabled()) { + return 0; + } + if (!adaptation.getSkill().isEnabled()) { + return 0; + } + AdaptPlayer adaptPlayer = adaptation.getPlayer(p); + return getLevel(adaptation, adaptPlayer); + } + + static int getLevel(Adaptation adaptation, AdaptPlayer adaptPlayer) { + if (adaptation == null || adaptPlayer == null) { + return 0; + } + if (!adaptation.isEnabled()) { + return 0; + } + if (!adaptation.getSkill().isEnabled()) { + return 0; + } + + PlayerData data = adaptPlayer.getData(); + if (data == null) { + return 0; + } + + PlayerSkillLine line = data.getSkillLineNullable(adaptation.getSkill().getName()); + if (line == null) { + return 0; + } + return line.getAdaptationLevel(adaptation.getName()); + } + + static List learnedCandidates(Adaptation adaptation, long now) { + if (adaptation == null) { + return List.of(); + } + + String key = adaptation.getName(); + LearnedCandidateCacheEntry cached = LEARNED_CANDIDATE_CACHE.get(key); + if (cached != null && now - cached.refreshedAtMs() <= LEARNED_CANDIDATE_REFRESH_MS) { + return cached.players(); + } + + List online = adaptation.getServer().getOnlineAdaptPlayerSnapshot(); + ArrayList candidates = new ArrayList<>(online.size()); + for (AdaptPlayer adaptPlayer : online) { + if (getLevel(adaptation, adaptPlayer) <= 0) { + continue; + } + candidates.add(adaptPlayer); + } + + List immutable = Collections.unmodifiableList(new ArrayList<>(candidates)); + LEARNED_CANDIDATE_CACHE.put(key, new LearnedCandidateCacheEntry(now, immutable)); + return immutable; + } + + static F getStorage(Adaptation adaptation, Player p, String key, F defaultValue) { + PlayerData data = adaptation.getPlayer(p).getData(); + PlayerSkillLine line = data.getSkillLineNullable(adaptation.getSkill().getName()); + if (line == null) return defaultValue; + PlayerAdaptation playerAdaptation = line.getAdaptation(adaptation.getName()); + if (playerAdaptation == null) return defaultValue; + Object o = playerAdaptation.getStorage().get(key); + return o == null ? defaultValue : (F) o; + } + + static boolean setStorage(Adaptation adaptation, Player p, String key, Object value) { + PlayerData data = adaptation.getPlayer(p).getData(); + PlayerSkillLine line = data.getSkillLineNullable(adaptation.getSkill().getName()); + if (line == null) return false; + PlayerAdaptation playerAdaptation = line.getAdaptation(adaptation.getName()); + if (playerAdaptation == null) return false; + if (value == null) { + playerAdaptation.getStorage().remove(key); + return true; + } + + playerAdaptation.getStorage().put(key, value); + return true; + } + + private static int resolveActionLevel(Adaptation adaptation, Player player, Predicate requirement, boolean survivalOnly) { + if (survivalOnly) { + return getActiveSurvivalLevel(adaptation, player, requirement); + } + + return getActiveLevel(adaptation, player, requirement); + } + + private static int resolveActiveLevelUncached(Adaptation adaptation, Player p, int learnedLevel) { + int level = learnedLevel; + if (level <= 0) { + return 0; + } + + boolean verbose = AdaptConfig.get().isVerbose(); + if (AdaptConfig.get().blacklistedWorlds.contains(p.getWorld().getName())) { + if (verbose) { + Adapt.verbose("Player " + p.getName() + " is in a blacklisted world. Skipping adaptation " + adaptation.getName()); + } + return 0; + } + if (p.getGameMode().equals(GameMode.CREATIVE) || p.getGameMode().equals(GameMode.SPECTATOR)) { + if (verbose) { + Adapt.verbose("Player " + p.getName() + " is in creative or spectator mode. Skipping adaptation " + adaptation.getName()); + } + return 0; + } + if (!adaptation.checkRegion(p)) { + if (verbose) { + Adapt.verbose("Player " + p.getName() + " don't have adaptation - " + adaptation.getName() + " permission."); + } + return 0; + } + + if (!adaptation.hasUsePermission(p, adaptation)) { + if (verbose) { + Adapt.verbose("Player " + p.getName() + " is blocked by use permission for adaptation " + adaptation.getName()); + } + return 0; + } + if (hasUsageConflict(adaptation, p)) { + return 0; + } + if (!adaptation.canUse(p)) { + if (verbose) { + Adapt.verbose("Player " + p.getName() + " can't use adaptation, This is an API restriction" + adaptation.getName()); + } + return 0; + } + + awardUsageBaselineXp(adaptation, p, level); + if (verbose) { + Adapt.verbose("Player " + p.getName() + " used adaptation " + adaptation.getName()); + } + return level; + } + + private static long runtimeCacheTick() { + return M.ms() / 50L; + } + + private static void sweepActiveLevelCache(long tick) { + if (ACTIVE_LEVEL_CACHE.size() <= ACTIVE_LEVEL_CACHE_SOFT_LIMIT) { + return; + } + + long previousSweep = lastActiveCacheSweepTick; + if (previousSweep != Long.MIN_VALUE && tick - previousSweep < ACTIVE_LEVEL_CACHE_SWEEP_INTERVAL_TICKS) { + return; + } + lastActiveCacheSweepTick = tick; + + long minTick = tick - ACTIVE_LEVEL_CACHE_RETENTION_TICKS; + ACTIVE_LEVEL_CACHE.entrySet().removeIf(entry -> entry.getValue().tick() < minTick); + } + + private static Set resolveUsageConflicts(Adaptation adaptation) { + Map> conflicts = AdaptConfig.get().getAdaptationUsageConflicts(); + if (conflicts == null || conflicts.isEmpty()) { + return Collections.emptySet(); + } + + String me = adaptation.getName().toLowerCase(Locale.ROOT); + int signature = buildUsageConflictSignature(conflicts); + UsageConflictCacheEntry cached = USAGE_CONFLICT_CACHE.get(me); + if (cached != null && cached.signature() == signature) { + return cached.denied(); + } + + Set denied = new HashSet<>(); + for (Map.Entry> entry : conflicts.entrySet()) { + String key = entry.getKey(); + List values = entry.getValue(); + if (key == null || values == null) { + continue; + } + + if (key.equalsIgnoreCase(me)) { + for (String value : values) { + if (value != null) { + denied.add(value.toLowerCase(Locale.ROOT)); + } + } + continue; + } + + boolean containsThisAdaptation = false; + for (String value : values) { + if (value != null && me.equals(value.toLowerCase(Locale.ROOT))) { + containsThisAdaptation = true; + break; + } + } + if (containsThisAdaptation) { + denied.add(key.toLowerCase(Locale.ROOT)); + } + } + + denied.remove(me); + Set immutable = Collections.unmodifiableSet(new HashSet<>(denied)); + USAGE_CONFLICT_CACHE.put(me, new UsageConflictCacheEntry(signature, immutable)); + return immutable; + } + + private static int buildUsageConflictSignature(Map> conflicts) { + int hash = 1; + for (Map.Entry> entry : conflicts.entrySet()) { + String key = entry.getKey(); + List values = entry.getValue(); + int local = 0; + if (key != null) { + local += key.toLowerCase(Locale.ROOT).hashCode(); + } + if (values != null) { + local += values.size() * 17; + for (String value : values) { + if (value != null) { + local += value.toLowerCase(Locale.ROOT).hashCode(); + } + } + } + hash += local; + } + return hash; + } + + private static int buildProtectorSignature(List defaults, List allProtectors, Map overrides) { + int hash = 1; + hash += defaults.size() * 31; + for (Protector protector : defaults) { + hash += protector.getName().hashCode(); + } + hash += allProtectors.size() * 17; + for (Protector protector : allProtectors) { + hash += protector.getName().hashCode(); + } + hash += overrides.size() * 13; + for (Map.Entry entry : overrides.entrySet()) { + String name = entry.getKey(); + Boolean enabled = entry.getValue(); + if (name != null) { + hash += name.hashCode(); + } + if (enabled != null) { + hash += enabled ? 1 : 2; + } + } + return hash; + } + + private record PlayerAdaptationKey(UUID uuid, String adaptation) { + } + + private record ActiveLevelCacheEntry(long tick, int learnedLevel, int level) { + } + + private record ProtectorCacheEntry(int signature, Set protectors) { + } + + private record UsageConflictCacheEntry(int signature, Set denied) { + } + + private record LearnedCandidateCacheEntry(long refreshedAtMs, List players) { + } +} diff --git a/src/main/java/art/arcane/adapt/api/adaptation/ReceiveCancelledEvents.java b/src/main/java/art/arcane/adapt/api/adaptation/ReceiveCancelledEvents.java new file mode 100644 index 000000000..0b60a0307 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/adaptation/ReceiveCancelledEvents.java @@ -0,0 +1,33 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.adaptation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Opt-out marker for adaptation event handlers that must run even when the + * event is already cancelled. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface ReceiveCancelledEvents { +} diff --git a/src/main/java/art/arcane/adapt/api/adaptation/SimpleAdaptation.java b/src/main/java/art/arcane/adapt/api/adaptation/SimpleAdaptation.java new file mode 100644 index 000000000..b86c850af --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/adaptation/SimpleAdaptation.java @@ -0,0 +1,313 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.adaptation; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdvancementSpec; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.tick.TickedObject; +import art.arcane.adapt.api.world.AdaptStatTracker; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigFileSupport; +import art.arcane.volmlib.util.collection.KList; +import lombok.Data; +import org.bukkit.Material; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Data +public abstract class SimpleAdaptation extends TickedObject implements Adaptation { + private int maxLevel; + private int initialCost; + private int baseCost; + private double costFactor; + private String displayName; + private Skill skill; + private String description; + private Material icon; + private String name; + private List cachedAdvancements; + private List recipes; + private List brewingRecipes; + private KList statTrackers; + private Class configType; + private volatile T config; + + public SimpleAdaptation(String name) { + super("adaptations", UUID.randomUUID() + "-" + name, 1000); + cachedAdvancements = new ArrayList<>(); + recipes = new ArrayList<>(); + brewingRecipes = new ArrayList<>(); + statTrackers = new KList<>(); + setMaxLevel(5); + setCostFactor(0.45); + setBaseCost(4); + setIcon(Material.PAPER); + setInitialCost(2); + setDescription("No Description Provided"); + this.name = name; + } + + @Override + public Class getConfigurationClass() { + return configType; + } + + @Override + public void registerConfiguration(Class type) { + this.configType = type; + } + + protected File getConfigFile() { + return Adapt.instance.getDataFile("adapt", "adaptations", getName() + ".toml"); + } + + protected File getLegacyConfigFile() { + return Adapt.instance.getDataFile("adapt", "adaptations", getName() + ".json"); + } + + protected T createDefaultConfig() { + try { + return getConfigurationClass().getConstructor().newInstance(); + } catch (Throwable e) { + throw new IllegalStateException("Failed to create default config for adaptation " + getName(), e); + } + } + + public synchronized boolean reloadConfigFromDisk(boolean announce) { + if (getConfigurationClass() == null) { + return false; + } + + T previous = config; + File file = getConfigFile(); + try { + T loaded = loadConfig(file, previous == null ? createDefaultConfig() : previous, previous == null); + config = loaded; + applySharedConfigValues(loaded); + onConfigReload(previous, loaded); + if (announce) { + Adapt.info("Hotloaded " + file.getPath()); + } + return true; + } catch (Throwable e) { + Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid config: " + e.getMessage()); + return false; + } + } + + private T loadConfig(File file, T fallback, boolean overwriteOnReadFailure) throws IOException { + return ConfigFileSupport.load( + file, + getLegacyConfigFile(), + getConfigurationClass(), + fallback, + overwriteOnReadFailure, + "adaptation:" + getName(), + "Created missing adaptation config [adapt/adaptations/" + getName() + ".toml] from defaults." + ); + } + + private void applySharedConfigValues(T currentConfig) { + applyIntField(currentConfig, "baseCost", this::setBaseCost); + applyIntField(currentConfig, "initialCost", this::setInitialCost); + applyIntField(currentConfig, "maxLevel", this::setMaxLevel); + applyLongField(currentConfig, "setInterval", this::setInterval); + } + + protected void onConfigReload(T previousConfig, T newConfig) { + applyDoubleField(newConfig, "costFactor", this::setCostFactor); + } + + private void applyIntField(T source, String fieldName, java.util.function.IntConsumer consumer) { + Number number = getNumericField(source, fieldName); + if (number != null) { + consumer.accept(number.intValue()); + } + } + + private void applyLongField(T source, String fieldName, java.util.function.LongConsumer consumer) { + Number number = getNumericField(source, fieldName); + if (number != null) { + consumer.accept(number.longValue()); + } + } + + private void applyDoubleField(T source, String fieldName, java.util.function.DoubleConsumer consumer) { + Number number = getNumericField(source, fieldName); + if (number != null) { + consumer.accept(number.doubleValue()); + } + } + + private Number getNumericField(T source, String fieldName) { + Field f = getField(source.getClass(), fieldName); + if (f == null) { + return null; + } + + try { + f.setAccessible(true); + Object value = f.get(source); + if (value instanceof Number number) { + return number; + } + } catch (Throwable ignored) { + Adapt.verbose("Failed reading config field '" + fieldName + "' for adaptation " + getName()); + } + + return null; + } + + private Field getField(Class type, String name) { + Class current = type; + while (current != null) { + try { + return current.getDeclaredField(name); + } catch (NoSuchFieldException ignored) { + current = current.getSuperclass(); + } + } + + return null; + } + + @Override + public T getConfig() { + T local = config; + if (local != null) { + return local; + } + + synchronized (this) { + local = config; + if (local != null) { + return local; + } + + boolean loaded = reloadConfigFromDisk(false); + local = config; + if (!loaded || local == null) { + local = createDefaultConfig(); + applySharedConfigValues(local); + onConfigReload(null, local); + config = local; + Adapt.warn("Falling back to in-memory defaults for adaptation config " + getName() + "."); + } + } + + return local; + } + + public void registerRecipe(AdaptRecipe r) { + recipes.add(r); + } + + public void registerBrewingRecipe(BrewingRecipe r) { + brewingRecipes.add(r); + } + + @Override + public String getDisplayName() { + try { + return displayName == null ? Adaptation.super.getDisplayName() : (C.RESET + "" + C.BOLD + getSkill().getColor().toString() + displayName); + } catch (Exception ignored) { + Adapt.verbose("Failed to get display name for " + getName()); + return null; + } + } + + public void registerStatTracker(AdaptStatTracker tracker) { + statTrackers.add(tracker); + } + + public KList getStatTrackers() { + return statTrackers; + } + + public void registerAdvancement(AdaptAdvancement a) { + cachedAdvancements.add(a); + } + + protected void registerAdvancementSpec(AdvancementSpec spec) { + if (spec == null) { + return; + } + + registerAdvancement(spec.toAdvancement()); + } + + protected void registerMilestone(AdvancementSpec spec, String stat, double goal, double reward) { + if (spec == null) { + return; + } + + registerAdvancementSpec(spec); + registerStatTracker(spec.statTracker(stat, goal, reward)); + } + + protected void registerMilestone(String advancementKey, String stat, double goal, double reward) { + registerStatTracker(AdaptStatTracker.builder() + .advancement(advancementKey) + .stat(stat) + .goal(goal) + .reward(reward) + .build()); + } + + @Override + public void onRegisterAdvancements(List advancements) { + advancements.addAll(cachedAdvancements); + } + + public AdaptAdvancement buildAdvancements() { + List a = new ArrayList<>(); + onRegisterAdvancements(a); + + return AdaptAdvancement.builder() + .key("adaptation_" + getName()) + .title(C.WHITE + "[ " + getDisplayName() + C.WHITE + " ]") + .description(getDescription() + ". " + Localizer.dLocalize("snippets.gui.unlock_this_by_clicking") + " " + AdaptConfig.get().adaptActivatorBlockName) + .icon(getIcon()) + .children(a) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build(); + } + + @Override + public final boolean equals(Object obj) { + return this == obj; + } + + @Override + public final int hashCode() { + return System.identityHashCode(this); + } +} diff --git a/src/main/java/art/arcane/adapt/api/adaptation/chunk/ChunkLoading.java b/src/main/java/art/arcane/adapt/api/adaptation/chunk/ChunkLoading.java new file mode 100644 index 000000000..879f6e865 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/adaptation/chunk/ChunkLoading.java @@ -0,0 +1,43 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.adaptation.chunk; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.scheduling.J; +import org.bukkit.Chunk; +import org.bukkit.Location; + +import java.util.function.Consumer; + +public class ChunkLoading { + public static void loadChunkAsync(Location l, Consumer chunk) { + if (l.getWorld().isChunkLoaded(l.getBlockX() >> 4, l.getBlockZ() >> 4)) { + chunk.accept(l.getChunk()); + return; + } + Adapt.verbose("Loading chunk async for " + l); + Adapt.platform.getChunkAtAsync(l).thenAccept(c -> { + if (!J.runAt(l, () -> chunk.accept(c))) { + if (!J.isFoliaThreading()) { + J.s(() -> chunk.accept(c)); + } + } + }); + } +} diff --git a/src/main/java/art/arcane/adapt/api/advancement/AdaptAdvancement.java b/src/main/java/art/arcane/adapt/api/advancement/AdaptAdvancement.java new file mode 100644 index 000000000..448712e45 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/advancement/AdaptAdvancement.java @@ -0,0 +1,158 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.advancement; + + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.volmlib.util.collection.KList; +import com.fren_gor.ultimateAdvancementAPI.AdvancementTab; +import com.fren_gor.ultimateAdvancementAPI.advancement.Advancement; +import com.fren_gor.ultimateAdvancementAPI.advancement.BaseAdvancement; +import com.fren_gor.ultimateAdvancementAPI.advancement.RootAdvancement; +import com.fren_gor.ultimateAdvancementAPI.advancement.display.AdvancementDisplay; +import com.fren_gor.ultimateAdvancementAPI.database.TeamProgression; +import lombok.Builder; +import lombok.Data; +import lombok.Singular; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@Builder +@Data +public class AdaptAdvancement { + private String background; + @Builder.Default + private Material icon = Material.EMERALD; + @Builder.Default + private CustomModel model = null; + @Builder.Default + private String title = "MISSING TITLE"; + @Builder.Default + private String description = "MISSING DESCRIPTION"; + @Builder.Default + private AdaptAdvancementFrame frame = AdaptAdvancementFrame.TASK; + @Builder.Default + private boolean toast = false; + @Builder.Default + private boolean announce = false; + @Builder.Default + private AdvancementVisibility visibility = AdvancementVisibility.PARENT_GRANTED; + @Builder.Default + private String key = "root"; + @Singular + private List children; + + private Advancement toAdvancement(Advancement parent, int index, int depth) { + if (children == null) { + children = new ArrayList<>(); + } + + ItemStack icon = getModel() != null ? + getModel().toItemStack() : + new ItemStack(getIcon()); + AdvancementDisplay d = new AdvancementDisplay.Builder(icon, getTitle()) + .description(getDescription()) + .frame(getFrame().toUaaFrame()) + .showToast(toast) + .x(1f + depth) + .y(1f + index) + .build(); + + if (parent == null) { + if (background == null) + throw new IllegalArgumentException("Background cannot be null"); + + return new MainAdvancement(Adapt.instance.getManager().createAdvancementTab(getKey()), getKey(), d, background); + } + + return new SubAdvancement(getKey(), d, parent, getVisibility()); + } + + public KList toAdvancements() { + return toAdvancements(null, 0, 0); + } + + private KList toAdvancements(Advancement p, int index, int depth) { + KList aa = new KList<>(); + Advancement a = toAdvancement(p, index, depth); + if (children != null && !children.isEmpty()) { + for (AdaptAdvancement i : children) { + aa.addAll(i.toAdvancements(a, aa.size(), depth + 1)); + } + } + + aa.add(a); + + return aa; + } + + private static class MainAdvancement extends RootAdvancement { + + public MainAdvancement(@NotNull AdvancementTab advancementTab, @NotNull String key, @NotNull AdvancementDisplay display, @NotNull String backgroundTexture) { + super(advancementTab, key, display, backgroundTexture); + } + + @Override + public void grant(@NotNull Player player, boolean giveRewards) { + super.grant(player, giveRewards); + try { + getAdvancementTab().showTab(player); + } catch (Throwable t) { + Adapt.verbose("Failed to show advancement tab '" + getKey() + "' for " + player.getName() + ": " + + t.getClass().getSimpleName() + + (t.getMessage() == null ? "" : " (" + t.getMessage() + ")")); + } + } + + @Override + public void revoke(@NotNull Player player) { + super.revoke(player); + try { + getAdvancementTab().hideTab(player); + } catch (Throwable t) { + Adapt.verbose("Failed to hide advancement tab '" + getKey() + "' for " + player.getName() + ": " + + t.getClass().getSimpleName() + + (t.getMessage() == null ? "" : " (" + t.getMessage() + ")")); + } + } + } + + private static class SubAdvancement extends BaseAdvancement { + private final AdvancementVisibility visibility; + + public SubAdvancement(@NotNull String key, + @NotNull AdvancementDisplay display, + @NotNull Advancement parent, + @NotNull AdvancementVisibility visibility) { + super(key, display, parent); + this.visibility = visibility; + } + + @Override + public boolean isVisible(@NotNull TeamProgression progression) { + return visibility.isVisible(this, progression); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/advancement/AdaptAdvancementFrame.java b/src/main/java/art/arcane/adapt/api/advancement/AdaptAdvancementFrame.java new file mode 100644 index 000000000..40d795092 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/advancement/AdaptAdvancementFrame.java @@ -0,0 +1,17 @@ +package art.arcane.adapt.api.advancement; + +import com.fren_gor.ultimateAdvancementAPI.advancement.display.AdvancementFrameType; + +public enum AdaptAdvancementFrame { + TASK, + GOAL, + CHALLENGE; + + public AdvancementFrameType toUaaFrame() { + return switch (this) { + case GOAL -> AdvancementFrameType.GOAL; + case CHALLENGE -> AdvancementFrameType.CHALLENGE; + case TASK -> AdvancementFrameType.TASK; + }; + } +} diff --git a/src/main/java/art/arcane/adapt/api/advancement/AdvancementManager.java b/src/main/java/art/arcane/adapt/api/advancement/AdvancementManager.java new file mode 100644 index 000000000..1cb8b0cde --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/advancement/AdvancementManager.java @@ -0,0 +1,418 @@ +package art.arcane.adapt.api.advancement; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.AdvancementHandler; +import art.arcane.adapt.util.common.scheduling.J; +import com.fren_gor.ultimateAdvancementAPI.AdvancementMain; +import com.fren_gor.ultimateAdvancementAPI.AdvancementTab; +import com.fren_gor.ultimateAdvancementAPI.advancement.Advancement; +import com.fren_gor.ultimateAdvancementAPI.advancement.BaseAdvancement; +import com.fren_gor.ultimateAdvancementAPI.advancement.RootAdvancement; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import static art.arcane.adapt.Adapt.instance; + +public class AdvancementManager { + private static final int USER_LOAD_RETRIES = 12; + private final AdvancementMain main; + private final Map advancements; + private final AtomicBoolean loaded = new AtomicBoolean(false); + private final AtomicBoolean enabled = new AtomicBoolean(false); + private final AtomicBoolean runtimeSchedulerUnsupported = new AtomicBoolean(false); + + public AdvancementManager() { + AdvancementMain loadedMain = null; + try { + loadedMain = new AdvancementMain(instance); + loadedMain.load(); + loaded.set(true); + } catch (Throwable e) { + loadedMain = null; + Adapt.warn("UltimateAdvancementAPI is unavailable: " + e.getMessage() + ". Advancements will be disabled."); + } + + main = loadedMain; + advancements = new ConcurrentHashMap<>(); + } + + AdvancementTab createAdvancementTab(String namespace) { + if (main == null) { + throw new IllegalStateException("UltimateAdvancementAPI is unavailable"); + } + + return main.createAdvancementTab(instance, "adapt_" + namespace); + } + + public void grant(AdaptPlayer player, String key, boolean toast) { + player.getData().ensureGranted(key); + Player p = player.getPlayer(); + if (!AdaptConfig.get().isAdvancements() || !enabled.get() || runtimeSchedulerUnsupported.get() || p == null || !p.isOnline()) + return; + Advancement advancement = advancements.get(key); + if (advancement == null) { + Adapt.verbose("Advancement key '" + key + "' is not registered; skipping grant."); + return; + } + + J.runEntity(p, () -> { + if (!p.isOnline()) { + return; + } + + attemptGrant(p, advancement, key, toast, true, true, USER_LOAD_RETRIES); + }, 5); + } + + private void attemptGrant(Player player, Advancement advancement, String key, boolean toast, boolean allowRetryOnGlobal, boolean allowRetryOnEntity, int userLoadRetriesRemaining) { + if (player == null || !player.isOnline()) { + return; + } + + try { + advancement.grant(player, true); + } catch (Throwable t) { + if (isUserNotLoadedError(t)) { + if (userLoadRetriesRemaining > 0) { + J.s(() -> attemptGrant(player, advancement, key, toast, allowRetryOnGlobal, allowRetryOnEntity, userLoadRetriesRemaining - 1), 5); + return; + } + + Adapt.verbose("Skipped advancement grant '" + key + "' because user data is not loaded yet for " + player.getName() + " after retries."); + return; + } + + if (isSchedulerContextMismatch(t)) { + if (allowRetryOnGlobal) { + J.s(() -> attemptGrant(player, advancement, key, toast, false, allowRetryOnEntity, userLoadRetriesRemaining), 1); + return; + } + + if (allowRetryOnEntity && J.runEntity(player, () -> attemptGrant(player, advancement, key, toast, false, false, userLoadRetriesRemaining), 1)) { + return; + } + + markRuntimeSchedulerUnsupported(t); + return; + } + + Adapt.warn("Failed to grant advancement '" + key + "' for " + player.getName() + ": " + summarizeThrowable(t)); + return; + } + + if (!toast) { + return; + } + + try { + advancement.displayToastToPlayer(player); + } catch (Throwable t) { + if (isUserNotLoadedError(t)) { + if (userLoadRetriesRemaining > 0) { + J.s(() -> attemptToast(player, advancement, key, allowRetryOnGlobal, allowRetryOnEntity, userLoadRetriesRemaining - 1), 5); + return; + } + + Adapt.verbose("Skipped advancement toast '" + key + "' because user data is not loaded yet for " + player.getName() + " after retries."); + return; + } + + if (isSchedulerContextMismatch(t)) { + if (allowRetryOnGlobal) { + J.s(() -> attemptToast(player, advancement, key, false, allowRetryOnEntity, userLoadRetriesRemaining), 1); + return; + } + + if (allowRetryOnEntity && J.runEntity(player, () -> attemptToast(player, advancement, key, false, false, userLoadRetriesRemaining), 1)) { + return; + } + + markRuntimeSchedulerUnsupported(t); + return; + } + + Adapt.warn("Failed to display advancement toast '" + key + "' for " + player.getName() + ": " + summarizeThrowable(t)); + } + } + + private void attemptToast(Player player, Advancement advancement, String key, boolean allowRetryOnGlobal, boolean allowRetryOnEntity, int userLoadRetriesRemaining) { + if (player == null || !player.isOnline()) { + return; + } + + try { + advancement.displayToastToPlayer(player); + } catch (Throwable t) { + if (isUserNotLoadedError(t)) { + if (userLoadRetriesRemaining > 0) { + J.s(() -> attemptToast(player, advancement, key, allowRetryOnGlobal, allowRetryOnEntity, userLoadRetriesRemaining - 1), 5); + return; + } + + Adapt.verbose("Skipped advancement toast '" + key + "' because user data is not loaded yet for " + player.getName() + " after retries."); + return; + } + + if (isSchedulerContextMismatch(t)) { + if (allowRetryOnGlobal) { + J.s(() -> attemptToast(player, advancement, key, false, allowRetryOnEntity, userLoadRetriesRemaining), 1); + return; + } + + if (allowRetryOnEntity && J.runEntity(player, () -> attemptToast(player, advancement, key, false, false, userLoadRetriesRemaining), 1)) { + return; + } + + markRuntimeSchedulerUnsupported(t); + return; + } + + Adapt.warn("Failed to display advancement toast '" + key + "' for " + player.getName() + ": " + summarizeThrowable(t)); + } + } + + private void markRuntimeSchedulerUnsupported(Throwable throwable) { + if (!runtimeSchedulerUnsupported.compareAndSet(false, true)) { + return; + } + + Adapt.info("UltimateAdvancementAPI live packet grants/toasts are unavailable on this Folia runtime; stored advancement grants will continue without live packets/toasts."); + if (throwable != null) { + Adapt.warn("UltimateAdvancementAPI live packet fallback cause: " + summarizeThrowable(throwable)); + Adapt.verbose("UltimateAdvancementAPI fallback cause: " + summarizeThrowable(throwable)); + } + } + + private boolean isUserNotLoadedError(Throwable throwable) { + Throwable current = throwable; + while (current != null) { + if ("UserNotLoadedException".equals(current.getClass().getSimpleName())) { + return true; + } + + current = current.getCause(); + } + + return false; + } + + private boolean isSchedulerContextMismatch(Throwable throwable) { + Throwable current = throwable; + while (current != null) { + if (current instanceof UnsupportedOperationException) { + return true; + } + + String message = current.getMessage(); + if (message != null) { + String lower = message.toLowerCase(Locale.ROOT); + if (lower.contains("thread") + || lower.contains("scheduler") + || lower.contains("region") + || lower.contains("primary thread") + || lower.contains("asynchronously")) { + return true; + } + } + + current = current.getCause(); + } + + return false; + } + + private String summarizeThrowable(Throwable throwable) { + if (throwable == null) { + return "unknown"; + } + + Throwable root = throwable; + while (root.getCause() != null && root.getCause() != root) { + root = root.getCause(); + } + + StringBuilder summary = new StringBuilder(throwable.getClass().getSimpleName()); + appendMessage(summary, throwable.getMessage()); + + if (root != throwable) { + summary.append(" | cause=").append(root.getClass().getSimpleName()); + appendMessage(summary, root.getMessage()); + } + + return summary.toString(); + } + + private void appendMessage(StringBuilder builder, String message) { + if (message != null && !message.isBlank()) { + builder.append(": ").append(message); + } + } + + public void unlockExisting(AdaptPlayer player, AdvancementHandler handler) { + if (!AdaptConfig.get().isAdvancements() || !enabled.get()) return; + if (player == null || handler == null) { + return; + } + + Player target = player.getPlayer(); + if (target == null || !target.isOnline()) { + return; + } + + if (runtimeSchedulerUnsupported.get()) { + handler.setReady(true); + return; + } + + J.runEntity(target, () -> { + for (Skill skill : instance.getAdaptServer().getSkillRegistry().getSkills()) { + AdaptAdvancement advancement = skill.buildAdvancements(); + ensureSkillRootGranted(player, advancement); + unlockExisting(player, advancement); + } + + handler.setReady(true); + }, 20); + } + + private void ensureSkillRootGranted(AdaptPlayer player, AdaptAdvancement advancement) { + if (player == null || advancement == null) { + return; + } + + String key = advancement.getKey(); + if (key == null || key.isBlank() || !key.startsWith("skill_")) { + return; + } + + if (player.getData().isGranted(key)) { + return; + } + + grant(player, key, false); + } + + private void unlockExisting(AdaptPlayer player, AdaptAdvancement aa) { + if (aa == null) { + return; + } + + String key = aa.getKey(); + if (key != null && !key.isBlank() && player.getData().isGranted(key)) { + grant(player, key, false); + } + + if (aa.getChildren() != null) { + for (AdaptAdvancement i : aa.getChildren()) { + unlockExisting(player, i); + } + } + } + + public void enable() { + if (main == null) { + return; + } + + runtimeSchedulerUnsupported.set(false); + if (!AdaptConfig.get().isAdvancements() || !enabled.compareAndSet(false, true)) { + return; + } + + try { + if (loaded.compareAndSet(false, true)) { + main.load(); + } + + advancements.clear(); + + if (AdaptConfig.get().isUseSql()) { + AdaptConfig.SqlSettings sql = AdaptConfig.get().getSql(); + main.enableMySQL(sql.getUsername(), sql.getPassword(), sql.getDatabase(), sql.getHost(), sql.getPort(), sql.getPoolSize(), sql.getConnectionTimeout()); + } else { + main.enableSQLite(instance.getDataFile("data", "advancements.db")); + } + + for (Skill i : instance.getAdaptServer().getSkillRegistry().getSkills()) { + AdaptAdvancement aa = i.buildAdvancements(); + Set set = new HashSet<>(); + RootAdvancement root = null; + + for (com.fren_gor.ultimateAdvancementAPI.advancement.Advancement a : aa.toAdvancements().reverse()) { + advancements.put(a.getKey().getKey(), a); + if (a instanceof RootAdvancement r && root == null) root = r; + else if (a instanceof BaseAdvancement b) set.add(b); + } + + if (root == null) { + Adapt.error("Root advancement not found for " + i.getId()); + continue; + } + + root.getAdvancementTab().registerAdvancements(root, set); + } + } catch (Throwable t) { + Adapt.warn("UltimateAdvancementAPI failed during enable: " + summarizeThrowable(t) + ". Advancements will be disabled."); + shutdownMain(t); + } + } + + public void disable() { + if (main == null) { + resetState(); + return; + } + + shutdownMain(null); + } + + private void shutdownMain(Throwable cause) { + advancements.clear(); + + try { + main.disable(); + } catch (Throwable t) { + if (isPartialInitialisationError(t)) { + Adapt.verbose("Skipped UltimateAdvancementAPI disable cleanup after partial initialisation: " + summarizeThrowable(t)); + } else { + Adapt.warn("UltimateAdvancementAPI disable failed: " + summarizeThrowable(t)); + if (cause != null) { + Adapt.verbose("UltimateAdvancementAPI original enable failure: " + summarizeThrowable(cause)); + } + } + } finally { + resetState(); + } + } + + private boolean isPartialInitialisationError(Throwable throwable) { + Throwable current = throwable; + while (current != null) { + if (current instanceof IllegalStateException) { + String message = current.getMessage(); + if (message != null && message.contains("has not been initialised yet")) { + return true; + } + } + + current = current.getCause(); + } + + return false; + } + + private void resetState() { + enabled.set(false); + loaded.set(false); + runtimeSchedulerUnsupported.set(false); + } +} diff --git a/src/main/java/art/arcane/adapt/api/advancement/AdvancementSpec.java b/src/main/java/art/arcane/adapt/api/advancement/AdvancementSpec.java new file mode 100644 index 000000000..0484db034 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/advancement/AdvancementSpec.java @@ -0,0 +1,102 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.advancement; + +import art.arcane.adapt.api.world.AdaptStatTracker; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.Builder; +import lombok.Data; +import lombok.Singular; +import org.bukkit.Material; + +import java.util.List; + +@Builder(toBuilder = true) +@Data +public class AdvancementSpec { + private String key; + private String title; + private String description; + @Builder.Default + private Material icon = Material.EMERALD; + @Builder.Default + private CustomModel model = null; + @Builder.Default + private AdaptAdvancementFrame frame = AdaptAdvancementFrame.TASK; + @Builder.Default + private AdvancementVisibility visibility = AdvancementVisibility.PARENT_GRANTED; + @Builder.Default + private boolean toast = false; + @Builder.Default + private boolean announce = false; + @Singular + private List children; + + public static AdvancementSpec challenge(String key, Material icon, String title, String description) { + return AdvancementSpec.builder() + .key(key) + .icon(icon) + .title(title) + .description(description) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build(); + } + + public AdvancementSpec withChild(AdvancementSpec child) { + if (child == null) { + return this; + } + + return toBuilder().child(child).build(); + } + + public AdaptAdvancement toAdvancement() { + AdaptAdvancement.AdaptAdvancementBuilder builder = AdaptAdvancement.builder() + .key(key) + .title(title) + .description(description) + .icon(icon) + .model(model) + .frame(frame) + .toast(toast) + .announce(announce) + .visibility(visibility); + + if (children != null) { + for (AdvancementSpec child : children) { + if (child == null) { + continue; + } + builder.child(child.toAdvancement()); + } + } + + return builder.build(); + } + + public AdaptStatTracker statTracker(String stat, double goal, double reward) { + return AdaptStatTracker.builder() + .stat(stat) + .goal(goal) + .reward(reward) + .advancement(key) + .build(); + } +} diff --git a/src/main/java/art/arcane/adapt/api/advancement/AdvancementVisibility.java b/src/main/java/art/arcane/adapt/api/advancement/AdvancementVisibility.java new file mode 100644 index 000000000..91c020d6d --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/advancement/AdvancementVisibility.java @@ -0,0 +1,85 @@ +package art.arcane.adapt.api.advancement; + +import com.fren_gor.ultimateAdvancementAPI.advancement.Advancement; +import com.fren_gor.ultimateAdvancementAPI.advancement.BaseAdvancement; +import com.fren_gor.ultimateAdvancementAPI.advancement.multiParents.AbstractMultiParentsAdvancement; +import com.fren_gor.ultimateAdvancementAPI.database.TeamProgression; +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.NotNull; + + +public interface AdvancementVisibility { + + /** + * Advancements with this Visibility will always be visible + */ + AdvancementVisibility ALWAYS = (advancement, progression) -> true; + + /** + * Advancements with this Visibility will be visible once their parent or any + * of their children is granted + */ + AdvancementVisibility PARENT_GRANTED = (advancement, progression) -> { + Preconditions.checkNotNull(advancement, "Advancement is null."); + Preconditions.checkNotNull(progression, "TeamProgression is null."); + if (advancement.getProgression(progression) > 0) + return true; + + if (advancement instanceof AbstractMultiParentsAdvancement multiParent) { + return multiParent.isAnyParentGranted(progression); + } + if (advancement instanceof BaseAdvancement base) { + return base.getParent().isGranted(progression); + } + return false; + }; + + /** + * Advancements with this Visibility will be visible once they are granted or + * any of their children is granted (Similar to Vanilla "hidden") + */ + AdvancementVisibility HIDDEN = (advancement, progression) -> { + Preconditions.checkNotNull(advancement, "Advancement is null."); + Preconditions.checkNotNull(progression, "TeamProgression is null."); + return advancement.getProgression(progression) > 0; + }; + + /** + * Advancements with this Visibility will be visible once their parent or + * grandparent or any of their children is granted (Similar to Vanilla + * behavior) + */ + AdvancementVisibility VANILLA = (advancement, progression) -> { + Preconditions.checkNotNull(advancement, "Advancement is null."); + Preconditions.checkNotNull(progression, "TeamProgression is null."); + if (advancement.getProgression(progression) > 0) + return true; + + if (advancement instanceof AbstractMultiParentsAdvancement multiParent) { + return multiParent.isAnyGrandparentGranted(progression); + } else if (advancement instanceof BaseAdvancement base) { + Advancement parent = base.getParent(); + + if (parent.isGranted(progression)) { + return true; + } + if (parent instanceof AbstractMultiParentsAdvancement multiParent) { + return multiParent.isAnyParentGranted(progression); + } else if (parent instanceof BaseAdvancement baseA) { + return baseA.getParent().isGranted(progression); + } + return false; + } + return false; + }; + + /** + * Do not call this method directly, use {@link AdvancementVisibility} to get + * accurate visibility data + * + * @param advancement Advancement to check + * @param progression Progression to check + * @return true if advancement should be visible + */ + boolean isVisible(@NotNull Advancement advancement, @NotNull TeamProgression progression); +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/api/data/WorldData.java b/src/main/java/art/arcane/adapt/api/data/WorldData.java new file mode 100644 index 000000000..0bf01a010 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/data/WorldData.java @@ -0,0 +1,359 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.data; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.data.unit.Earnings; +import art.arcane.adapt.api.data.unit.PlacementStamp; +import art.arcane.adapt.api.tick.TickedObject; +import art.arcane.adapt.util.common.parallel.MultiBurst; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.spatial.mantle.MantleRegion; +import art.arcane.spatial.matter.ClassReader; +import art.arcane.spatial.matter.Matter; +import art.arcane.spatial.matter.MatterSlice; +import art.arcane.spatial.matter.SpatialMatter; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.io.CountingDataInputStream; +import art.arcane.volmlib.util.mantle.io.IOWorkerCodecSupport; +import art.arcane.volmlib.util.mantle.runtime.*; +import art.arcane.volmlib.util.parallel.HyperLockSupport; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.event.EventHandler; +import org.bukkit.event.world.WorldSaveEvent; +import org.bukkit.event.world.WorldUnloadEvent; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; + +public class WorldData extends TickedObject { + private static final KMap mantles = new KMap<>(); + + static { + SpatialMatter.registerSliceType(new Earnings.EarningsMatter()); + SpatialMatter.registerSliceType(new PlacementStamp.PlacementStampMatter()); + ClassReader.add(WorldData.class.getClassLoader()); + } + + private final World world; + private final int minHeight; + private final Mantle mantle; + + public WorldData(World world) { + super("world-data", world.getUID().toString(), 30_000); + this.world = world; + this.minHeight = world.getMinHeight(); + mantle = createMantle(Adapt.instance.getDataFolder("data", "mantle", world.getName()), world.getMaxHeight()); + } + + private static Mantle createMantle(File dataFolder, int worldHeight) { + MantleDataAdapter adapter = createAdapter(); + MantleHooks hooks = createHooks(); + art.arcane.volmlib.util.mantle.Mantle.RegionIO> regionIO = + createRegionIO(dataFolder, worldHeight, adapter, hooks); + return new Mantle<>( + dataFolder, + worldHeight, + new HyperLockSupport(), + MultiBurst.burst, + regionIO, + adapter, + hooks + ); + } + + private static MantleDataAdapter createAdapter() { + return new MantleDataAdapter<>() { + @Override + public Matter createSection() { + return new SpatialMatter(16, 16, 16); + } + + @Override + public Matter readSection(CountingDataInputStream din) throws IOException { + try { + return Matter.readDin(din); + } catch (ClassNotFoundException e) { + throw new IOException("Failed to deserialize mantle section", e); + } + } + + @Override + public void writeSection(Matter section, DataOutputStream dos) throws IOException { + section.writeDos(dos); + } + + @Override + public void trimSection(Matter section) { + section.trimSlices(); + } + + @Override + public boolean isSectionEmpty(Matter section) { + return section.getSliceMap().isEmpty(); + } + + @Override + public Class classifyValue(Object value) { + return value == null ? Object.class : value.getClass(); + } + + @Override + @SuppressWarnings("unchecked") + public void set(Matter section, int x, int y, int z, Class type, T value) { + MatterSlice slice = (MatterSlice) section.slice(section.getClass(value)); + slice.set(x, y, z, value); + } + + @Override + public void remove(Matter section, int x, int y, int z, Class type) { + MatterSlice slice = section.slice(type); + slice.set(x, y, z, null); + } + + @Override + public T get(Matter section, int x, int y, int z, Class type) { + MatterSlice slice = section.slice(type); + return slice.get(x, y, z); + } + + @Override + public void iterate(Matter section, Class type, art.arcane.volmlib.util.function.Consumer4 iterator) { + MatterSlice slice = section.getSlice(type); + if (slice != null) { + slice.iterateSync((x, y, z, value) -> iterator.accept(x, y, z, value)); + } + } + + @Override + public boolean hasSlice(Matter section, Class type) { + return section.hasSlice(type); + } + + @Override + public void deleteSlice(Matter section, Class type) { + section.deleteSlice(type); + } + }; + } + + private static MantleHooks createHooks() { + return new MantleHooks() { + @Override + public void onReadSectionFailure(int index, + long start, + long end, + CountingDataInputStream din, + IOException error) { + Adapt.warn("Failed to read mantle chunk section, skipping it."); + Adapt.error(error.getMessage() == null ? "Unknown mantle section read error" : error.getMessage()); + error.printStackTrace(); + TectonicPlate.addError(); + } + + @Override + public void onReadChunkFailure(int index, + long start, + long end, + CountingDataInputStream din, + Throwable error) { + Adapt.warn("Failed to read mantle chunk, creating a new chunk instead."); + Adapt.error(error.getMessage() == null ? "Unknown mantle chunk read error" : error.getMessage()); + error.printStackTrace(); + } + + @Override + public String formatDuration(double millis) { + return Form.duration(millis, 0); + } + + @Override + public void onDebug(String message) { + Adapt.debug(message); + } + + @Override + public void onWarn(String message) { + Adapt.warn(message); + } + + @Override + public void onError(Throwable throwable) { + Adapt.error(throwable.getMessage() == null ? "Mantle error" : throwable.getMessage()); + throwable.printStackTrace(); + } + }; + } + + private static art.arcane.volmlib.util.mantle.Mantle.RegionIO> createRegionIO(File dataFolder, + int worldHeight, + MantleDataAdapter adapter, + MantleHooks hooks) { + IOWorker> worker = new IOWorker<>( + dataFolder, + IOWorkerCodecSupport.identity(), + 128, + (name, millis) -> Adapt.debug("Acquired mantle channel for " + name + " in " + millis + "ms") + ); + + return new art.arcane.volmlib.util.mantle.Mantle.RegionIO<>() { + @Override + public TectonicPlate read(String name) throws IOException { + try { + return worker.read(name, (regionName, in) -> TectonicPlate.read(worldHeight, in, true, adapter, hooks)); + } catch (IOException e) { + TectonicPlate migrated = readLegacy(dataFolder, worldHeight, name, adapter, hooks, e); + if (migrated != null) { + Adapt.warn("Migrated legacy mantle region " + name + " to shared mantle format."); + return migrated; + } + + throw e; + } + } + + @Override + public void write(String name, TectonicPlate region) throws IOException { + worker.write(name, "adapt", ".bin", region, TectonicPlate::write); + } + + @Override + public void close() throws IOException { + worker.close(); + } + }; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static TectonicPlate readLegacy(File dataFolder, + int worldHeight, + String name, + MantleDataAdapter adapter, + MantleHooks hooks, + IOException original) { + File file = new File(dataFolder, name); + if (!file.exists()) { + return null; + } + + try { + MantleRegion region = MantleRegion.read(worldHeight, file); + TectonicPlate plate = new TectonicPlate<>(worldHeight, region.getX(), region.getZ(), adapter, hooks); + int sectionCount = worldHeight >> 4; + + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + art.arcane.spatial.mantle.MantleChunk legacyChunk = region.get(x, z); + if (legacyChunk == null) { + continue; + } + + art.arcane.volmlib.util.mantle.runtime.MantleChunk chunk = plate.getOrCreate(x, z); + for (int section = 0; section < sectionCount; section++) { + Matter legacySection = legacyChunk.get(section); + if (legacySection == null || legacySection.getSliceMap().isEmpty()) { + continue; + } + + Matter target = chunk.getOrCreate(section); + target.clearSlices(); + + Matter copy = legacySection.copy(); + for (java.util.Map.Entry, art.arcane.spatial.matter.MatterSlice> entry : copy.getSliceMap().entrySet()) { + target.putSlice(entry.getKey(), (MatterSlice) entry.getValue()); + } + } + } + } + + return plate; + } catch (Throwable t) { + original.addSuppressed(t); + return null; + } + } + + public static void stop() { + mantles.v().forEach(WorldData::unregister); + } + + public static WorldData of(World world) { + return mantles.computeIfAbsent(world, WorldData::new); + } + + public double getEarningsMultiplier(Block block) { + Earnings e = get(block, Earnings.class); + + if (e == null) { + return 1; + } + + return 1 / (double) (e.getEarnings() == 0 ? 1 : e.getEarnings()); + } + + public T get(Block block, Class type) { + return mantle.get(block.getX(), block.getY() - minHeight, block.getZ(), type); + } + + public void set(Block block, T value) { + mantle.set(block.getX(), block.getY() - minHeight, block.getZ(), value); + } + + public void remove(Block block, Class type) { + mantle.remove(block.getX(), block.getY() - minHeight, block.getZ(), type); + } + + public double reportEarnings(Block block) { + Earnings e = get(block, Earnings.class); + e = e == null ? new Earnings(0) : e; + + if (e.getEarnings() >= 127) { + return 1 / (double) (e.getEarnings() == 0 ? 1 : e.getEarnings()); + } + + set(block, e.increment()); + return 1 / (double) (e.getEarnings() == 0 ? 1 : e.getEarnings()); + } + + public void unregister() { + super.unregister(); + mantle.close(); + mantles.remove(world); + } + + @EventHandler + public void on(WorldSaveEvent e) { + if (e.getWorld() != world) return; + J.a(mantle::saveAll); + } + + @EventHandler + public void on(WorldUnloadEvent e) { + if (e.getWorld() != world) return; + unregister(); + } + + @Override + public void onTick() { + mantle.trim(60_000); + } +} diff --git a/src/main/java/art/arcane/adapt/api/data/unit/Earnings.java b/src/main/java/art/arcane/adapt/api/data/unit/Earnings.java new file mode 100644 index 000000000..1dcc9e392 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/data/unit/Earnings.java @@ -0,0 +1,61 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.data.unit; + +import art.arcane.spatial.matter.slices.RawMatter; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +@Data +@AllArgsConstructor +public class Earnings { + private final int earnings; + + public Earnings increment() { + if (earnings >= 127) { + return this; + } + + return new Earnings(getEarnings() + 1); + } + + public static class EarningsMatter extends RawMatter { + public EarningsMatter() { + this(1, 1, 1); + } + + public EarningsMatter(int width, int height, int depth) { + super(width, height, depth, Earnings.class); + } + + @Override + public void writeNode(Earnings earnings, DataOutputStream dataOutputStream) throws IOException { + dataOutputStream.writeByte(earnings.getEarnings()); + } + + @Override + public Earnings readNode(DataInputStream dataInputStream) throws IOException { + return new Earnings(dataInputStream.readByte()); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/data/unit/PlacementStamp.java b/src/main/java/art/arcane/adapt/api/data/unit/PlacementStamp.java new file mode 100644 index 000000000..726f7224d --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/data/unit/PlacementStamp.java @@ -0,0 +1,57 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.data.unit; + +import art.arcane.spatial.matter.slices.RawMatter; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +@Data +@AllArgsConstructor +public class PlacementStamp { + private final int placedAt; + private final int brokenAt; + private final int bonemealedAt; + + public static class PlacementStampMatter extends RawMatter { + public PlacementStampMatter() { + this(1, 1, 1); + } + + public PlacementStampMatter(int width, int height, int depth) { + super(width, height, depth, PlacementStamp.class); + } + + @Override + public void writeNode(PlacementStamp stamp, DataOutputStream dataOutputStream) throws IOException { + dataOutputStream.writeInt(stamp.getPlacedAt()); + dataOutputStream.writeInt(stamp.getBrokenAt()); + dataOutputStream.writeInt(stamp.getBonemealedAt()); + } + + @Override + public PlacementStamp readNode(DataInputStream dataInputStream) throws IOException { + return new PlacementStamp(dataInputStream.readInt(), dataInputStream.readInt(), dataInputStream.readInt()); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/item/DataItem.java b/src/main/java/art/arcane/adapt/api/item/DataItem.java new file mode 100644 index 000000000..448de8705 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/item/DataItem.java @@ -0,0 +1,88 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.item; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.io.BukkitGson; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; + +import java.util.ArrayList; +import java.util.List; + +public interface DataItem { + Material getMaterial(); + + Class getType(); + + void applyLore(T data, List lore); + + void applyMeta(T data, ItemMeta meta); + + default ItemStack blank() { + return new ItemStack(getMaterial()); + } + + default T getData(ItemStack stack) { + if (stack != null + && stack.getType().equals(getMaterial()) + && stack.getItemMeta() != null) { + String r = stack.getItemMeta().getPersistentDataContainer().get(new NamespacedKey(Adapt.instance, getType().getCanonicalName().hashCode() + ""), PersistentDataType.STRING); + if (r != null) { + return BukkitGson.gson.fromJson(r, getType()); + } + } + + return null; + } + + default boolean hasData(ItemStack stack) { + if (stack != null + && stack.getType().equals(getMaterial()) + && stack.getItemMeta() != null) { + return stack.getItemMeta().getPersistentDataContainer().has(new NamespacedKey(Adapt.instance, getType().getCanonicalName().hashCode() + ""), PersistentDataType.STRING); + } + return false; + } + + + default void setData(ItemStack item, T t) { + item.setItemMeta(withData(t).getItemMeta()); + } + + default ItemStack withData(T t) { + ItemStack item = blank(); + ItemMeta meta = item.getItemMeta(); + + if (meta == null) { + return null; + } + + applyMeta(t, meta); + List lore = new ArrayList<>(); + applyLore(t, lore); + meta.setLore(lore); + meta.getPersistentDataContainer().set(new NamespacedKey(Adapt.instance, getType().getCanonicalName().hashCode() + ""), PersistentDataType.STRING, BukkitGson.gson.toJson(t)); + item.setItemMeta(meta); + return item; + } +} diff --git a/src/main/java/art/arcane/adapt/api/item/PotionItem.java b/src/main/java/art/arcane/adapt/api/item/PotionItem.java new file mode 100644 index 000000000..7a4b69e9f --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/item/PotionItem.java @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.item; + +import art.arcane.adapt.util.common.format.C; +import art.arcane.volmlib.util.format.Form; +import lombok.NoArgsConstructor; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.potion.PotionEffectType; + +import java.util.List; + +public abstract class PotionItem implements DataItem { + @Override + public Class getType() { + return Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + lore.add(C.GREEN + "Grants " + data.getType().getName() + " " + Form.toRoman(data.getPower() + 1)); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + + } + + @lombok.Data + @NoArgsConstructor + public static class Data { + private PotionEffectType type; + private int power; + } +} diff --git a/src/main/java/art/arcane/adapt/api/notification/ActionBarNotification.java b/src/main/java/art/arcane/adapt/api/notification/ActionBarNotification.java new file mode 100644 index 000000000..2d85012a9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/notification/ActionBarNotification.java @@ -0,0 +1,60 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.notification; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.volmlib.util.math.M; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ActionBarNotification implements Notification { + @Builder.Default + private final long duration = 750; + @Builder.Default + private final String title = " "; + @Builder.Default + private final String group = "default"; + @Builder.Default + private final long maxTTL = Long.MAX_VALUE; + + @Override + public long getTotalDuration() { + if (M.ms() > maxTTL) { + return 0; + } + return duration; + } + + @Override + public String getGroup() { + return group; + } + + @Override + public void play(AdaptPlayer p) { + if (M.ms() > maxTTL) { + return; + } + + Adapt.actionbar(p.getPlayer(), title); + } +} diff --git a/src/main/java/art/arcane/adapt/api/notification/AdvancementNotification.java b/src/main/java/art/arcane/adapt/api/notification/AdvancementNotification.java new file mode 100644 index 000000000..236ae3e52 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/notification/AdvancementNotification.java @@ -0,0 +1,71 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.notification; + +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.misc.AdvancementUtils; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.Builder; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +@Data +@Builder +public class AdvancementNotification implements Notification { + @Builder.Default + private final Material icon = Material.DIAMOND; + @Builder.Default + private final CustomModel model = null; + @Builder.Default + private final String title = " "; + @Builder.Default + private final String description = " "; + @Builder.Default + private final AdaptAdvancementFrame frameType = AdaptAdvancementFrame.TASK; + @Builder.Default + private final String group = "default"; + + @Override + public long getTotalDuration() { + return 100; // TODO: Actually calculate + } + + @Override + public String getGroup() { + return group; + } + + @Override + public void play(AdaptPlayer p) { + if (p.getPlayer() != null) { + ItemStack icon = getModel() != null ? getModel().toItemStack() : new ItemStack(getIcon()); + AdvancementUtils.displayToast(p.getPlayer(), icon, title, description, frameType); + } + } + + public String buildTitle() { + if (description.trim().isEmpty()) { + return title; + } + + return title + "\n" + description; + } +} diff --git a/src/main/java/com/volmit/adapt/api/notification/Notification.java b/src/main/java/art/arcane/adapt/api/notification/Notification.java similarity index 79% rename from src/main/java/com/volmit/adapt/api/notification/Notification.java rename to src/main/java/art/arcane/adapt/api/notification/Notification.java index 5c941c391..1240aeba7 100644 --- a/src/main/java/com/volmit/adapt/api/notification/Notification.java +++ b/src/main/java/art/arcane/adapt/api/notification/Notification.java @@ -16,18 +16,18 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.notification; +package art.arcane.adapt.api.notification; -import com.volmit.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.AdaptPlayer; public interface Notification { - String DEFAULT_GROUP = "default"; + String DEFAULT_GROUP = "default"; - long getTotalDuration(); + long getTotalDuration(); - void play(AdaptPlayer p); + void play(AdaptPlayer p); - default String getGroup() { - return DEFAULT_GROUP; - } + default String getGroup() { + return DEFAULT_GROUP; + } } diff --git a/src/main/java/art/arcane/adapt/api/notification/Notifier.java b/src/main/java/art/arcane/adapt/api/notification/Notifier.java new file mode 100644 index 000000000..b77dcba6f --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/notification/Notifier.java @@ -0,0 +1,178 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.notification; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.tick.TickedObject; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.format.C; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.math.M; +import lombok.Data; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +@Data +public class Notifier extends TickedObject { + private final Queue queue; + private final AdaptPlayer target; + private final KMap lastSkills; + private final KMap lastSkillValues; + private int busyTicks; + private int delayTicks; + private long lastInstance; + + public Notifier(AdaptPlayer target) { + super("notifications", target.getPlayer().getUniqueId() + "-notify", 97); + queue = new ConcurrentLinkedQueue<>(); + lastSkills = new KMap<>(); + lastSkillValues = new KMap<>(); + busyTicks = 0; + delayTicks = 0; + this.target = target; + lastInstance = 0; + } + + public void notifyXP(String line, double value) { + try { + if (!lastSkills.containsKey(line)) { + lastSkillValues.put(line, 0d); + } + + lastSkills.put(line, M.ms()); + lastSkillValues.put(line, lastSkillValues.get(line) + value); + lastInstance = M.ms(); + + + StringBuilder sb = new StringBuilder(); + + for (String i : lastSkills.sortKNumber().reverse()) { + Skill sk = getServer().getSkillRegistry().getSkill(i); + sb.append(i.equals(line) ? sk.getDisplayName() : sk.getShortName()) + .append(C.RESET).append(C.GRAY) + .append(" +").append(C.WHITE) + .append(line.equals(i) ? C.UNDERLINE : "") + .append(Form.f(lastSkillValues.get(i).intValue())) + .append(C.RESET).append(C.GRAY) + .append("XP "); + } + + while (lastSkills.size() > 5) { + String s = lastSkills.sortKNumber().reverse().get(0); + lastSkills.remove(s); + lastSkillValues.remove(s); + } + + target.getActionBarNotifier().queue(ActionBarNotification.builder() + .duration(0) + .maxTTL(M.ms() + 100) + .title(sb.toString()) + .group("xp") + .build()); + } catch (Throwable e) { + Adapt.verbose("Failed to notify xp: " + e.getMessage()); + } + } + + public void queue(Notification... f) { + queue.addAll(Arrays.asList(f)); + } + + public boolean isBusy() { + return busyTicks > 1 || !queue.isEmpty(); + } + + @Override + public void onTick() { + cleanupSkills(); + + if (busyTicks > 6) { + busyTicks = 6; + } + + if (busyTicks-- > 0) { + return; + } + + if (busyTicks < 0) { + busyTicks = 0; + } + + delayTicks--; + if (delayTicks > 0) { + return; + } + + if (delayTicks < 0) { + delayTicks = 0; + } + + + if (!isBusy()) { + cleanupStackedNotifications(); + } + + Notification n = queue.poll(); + + if (n == null) { + return; + } + + delayTicks += (n.getTotalDuration() / 50D) + 1; + Adapt.verbose("Playing Notification " + n + " --> " + System.identityHashCode(this)); + n.play(target); + } + + private void cleanupStackedNotifications() { + + } + + private void cleanupSkills() { + if (lastSkills.isEmpty()) { + return; + } + + long now = M.ms(); + Iterator> iterator = lastSkills.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + long last = entry.getValue(); + if (now - last > 10000 || (now - lastInstance > 3100 && now - last > 3100)) { + iterator.remove(); + lastSkillValues.remove(entry.getKey()); + } + } + } + + @Override + public final boolean equals(Object obj) { + return this == obj; + } + + @Override + public final int hashCode() { + return System.identityHashCode(this); + } +} diff --git a/src/main/java/art/arcane/adapt/api/notification/SoundNotification.java b/src/main/java/art/arcane/adapt/api/notification/SoundNotification.java new file mode 100644 index 000000000..8075db642 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/notification/SoundNotification.java @@ -0,0 +1,76 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.notification; + +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import lombok.Builder; +import lombok.Data; +import org.bukkit.Sound; + +@Data +@Builder +public class SoundNotification implements Notification { + @Builder.Default + private final long isolation = 0; + @Builder.Default + private final long predelay = 0; + @Builder.Default + private final Sound sound = Sound.BLOCK_LEVER_CLICK; + @Builder.Default + private final float volume = 1F; + @Builder.Default + private final float pitch = 1F; + @Builder.Default + private final String group = "default"; + + public SoundNotification withXP(double xp) { + double sig = xp / 1000D; + float pitch = this.pitch; + float volume = this.volume; + pitch -= sig / 6.6; + pitch = pitch < 0.1 ? (float) 0.1 : pitch; + double vp = sig / 5; + vp = Math.min(vp, 0.8); + volume += vp; + pitch = pitch < 0.1 ? (float) 0.1 : pitch; + + return SoundNotification.builder() + .sound(sound) + .isolation(isolation) + .predelay(predelay) + .volume(volume) + .pitch(pitch) + .build(); + } + + @Override + public long getTotalDuration() { + return isolation; + } + + @Override + public String getGroup() { + return group; + } + + public void play(AdaptPlayer p) { + SoundPlayer.of(p.getPlayer()).play(p.getPlayer().getLocation(), sound, volume, pitch); + } +} diff --git a/src/main/java/art/arcane/adapt/api/notification/TitleNotification.java b/src/main/java/art/arcane/adapt/api/notification/TitleNotification.java new file mode 100644 index 000000000..404925253 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/notification/TitleNotification.java @@ -0,0 +1,55 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.notification; + +import art.arcane.adapt.api.world.AdaptPlayer; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class TitleNotification implements Notification { + @Builder.Default + private final long in = 250; + @Builder.Default + private final long stay = 1450; + @Builder.Default + private final long out = 750; + @Builder.Default + private final String title = " "; + @Builder.Default + private final String subtitle = " "; + @Builder.Default + private final String group = "default"; + + @Override + public long getTotalDuration() { + return in + out + stay; + } + + @Override + public String getGroup() { + return group; + } + + @Override + public void play(AdaptPlayer p) { + p.getPlayer().sendTitle(title.isEmpty() ? " " : title, subtitle, (int) (in / 50D), (int) (stay / 50D), (int) (out / 50D)); + } +} diff --git a/src/main/java/art/arcane/adapt/api/potion/BrewingManager.java b/src/main/java/art/arcane/adapt/api/potion/BrewingManager.java new file mode 100644 index 000000000..59411f674 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/potion/BrewingManager.java @@ -0,0 +1,169 @@ +package art.arcane.adapt.api.potion; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.reflect.Reflect; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.BrewingStand; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionType; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class BrewingManager implements Listener { + + private static final Map> recipes = new ConcurrentHashMap<>(); + private static final Map activeTasks = new ConcurrentHashMap<>(); + + public static void registerRecipe(String adaptation, BrewingRecipe recipe) { + if (adaptation == null || adaptation.isBlank() || recipe == null) { + return; + } + + recipes.computeIfAbsent(recipe, unused -> ConcurrentHashMap.newKeySet()).add(adaptation); + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent e) { + if (e.getView().getTopInventory().getType() != InventoryType.BREWING || e.getView().getTopInventory().getHolder() == null) { + return; + } + Adapt.verbose("Brewing click: " + e.getRawSlot()); + BrewerInventory inv = (BrewerInventory) e.getInventory(); + boolean doTheThing = inv.getIngredient() == null + && e.getCursor() != null + && e.getRawSlot() == 3 + && e.getClickedInventory() != null + && e.getClickedInventory().getType().equals(InventoryType.BREWING) + && (e.getClick() == ClickType.LEFT); + if (doTheThing) { + Adapt.verbose("Brewing Stand Ingredient Clicked"); + e.setCancelled(true); + } + Player clicker = (Player) e.getWhoClicked(); + J.runEntity(clicker, () -> { + if (doTheThing) { + inv.setIngredient(e.getCursor()); + e.setCursor(null); + } + + BrewingStand stand = inv.getHolder(); + if (stand == null) { + return; + } + + Location standLocation = stand.getLocation(); + AdaptPlayer p = Adapt.instance.getAdaptServer().getPlayer(clicker); + BrewingRecipe recipe = findMatchingRecipe(standLocation); + if (recipe == null) { + BrewingTask removed = activeTasks.remove(standLocation); + if (removed != null) { + removed.cancel(); + } + return; + } + + Set requiredAdaptations = recipes.get(recipe); + BrewingTask active = activeTasks.get(standLocation); + if (!playerHasRequiredAdaptation(p, requiredAdaptations)) { + if (active != null && !active.getRecipe().getId().equals(recipe.getId())) { + BrewingTask removed = activeTasks.remove(standLocation); + if (removed != null) { + removed.cancel(); + } + } + return; + } + + if (active != null && active.getRecipe().getId().equals(recipe.getId())) { + return; + } + + if (active != null) { + BrewingTask removed = activeTasks.remove(standLocation); + if (removed != null) { + removed.cancel(); + } + } + + activeTasks.put(standLocation, new BrewingTask(recipe, standLocation)); + }, 1); + } + + @EventHandler + public void onBrew(BrewEvent e) { + ItemStack ingredient = e.getContents().getIngredient(); + if (ingredient == null) { + return; + } + + Material m = ingredient.getType(); + if (m != Material.GUNPOWDER && m != Material.DRAGON_BREATH) { + return; + } + for (int i = 0; i < 3; i++) { + ItemStack s = e.getContents().getItem(i); + if (s == null) continue; + PotionMeta meta = (PotionMeta) s.getItemMeta(); + java.util.Optional opt = Reflect.getEnum(PotionType.class, "UNCRAFTABLE"); + if (opt.isEmpty() && meta.getBasePotionData() != null) + continue; + if (opt.isPresent() && meta.getBasePotionData().getType() == opt.get()) + continue; + ItemStack newStack = s.clone(); + if (m == Material.GUNPOWDER) { + newStack.setType(Material.SPLASH_POTION); + } else { + newStack.setType(Material.LINGERING_POTION); + /*PotionMeta meta = (PotionMeta)newStack.getItemMeta(); + List newEffects = Lists.newArrayList(); + meta.getCustomEffects().forEach(effect -> newEffects.add(new PotionEffect(effect.getType(), effect.getDuration() / 4, effect.getAmplifier()))); + meta.clearCustomEffects(); + newEffects.forEach(effect -> meta.addCustomEffect(effect, true)); + newStack.setItemMeta(meta);*/ + } + e.getResults().set(i, newStack); + } + } + + private BrewingRecipe findMatchingRecipe(Location standLocation) { + if (standLocation == null) { + return null; + } + + for (BrewingRecipe recipe : recipes.keySet()) { + if (BrewingTask.isValid(recipe, standLocation)) { + return recipe; + } + } + + return null; + } + + private boolean playerHasRequiredAdaptation(AdaptPlayer player, Set requiredAdaptations) { + if (player == null || requiredAdaptations == null || requiredAdaptations.isEmpty()) { + return false; + } + + for (String adaptation : requiredAdaptations) { + if (player.hasAdaptation(adaptation)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/art/arcane/adapt/api/potion/BrewingRecipe.java b/src/main/java/art/arcane/adapt/api/potion/BrewingRecipe.java new file mode 100644 index 000000000..7ecbb0d86 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/potion/BrewingRecipe.java @@ -0,0 +1,15 @@ +package art.arcane.adapt.api.potion; + +import lombok.Builder; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +@Data +@Builder +public class BrewingRecipe { + private final String id; + private final Material ingredient; + private final ItemStack basePotion, result; + private final int brewingTime, fuelCost; +} diff --git a/src/main/java/art/arcane/adapt/api/potion/BrewingTask.java b/src/main/java/art/arcane/adapt/api/potion/BrewingTask.java new file mode 100644 index 000000000..242114738 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/potion/BrewingTask.java @@ -0,0 +1,114 @@ +package art.arcane.adapt.api.potion; + +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import lombok.Getter; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.BrewingStand; +import org.bukkit.entity.Player; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.ItemStack; + +public class BrewingTask implements Runnable { + + private static final int DEFAULT_BREW_TIME = 400; + + @Getter + private final BrewingRecipe recipe; + + private final Location location; + private int brewTime; + private volatile boolean cancelled; + + public BrewingTask(BrewingRecipe recipe, Location loc) { + this.recipe = recipe; + this.location = loc; + this.brewTime = recipe.getBrewingTime(); + + BrewingStand block = (BrewingStand) loc.getBlock().getState(); + if (block.getFuelLevel() > recipe.getFuelCost()) { + block.setFuelLevel(block.getFuelLevel() - recipe.getFuelCost()); + } else { + int rest = recipe.getFuelCost() - block.getFuelLevel(); + block.getInventory().setIngredient(decrease(block.getInventory().getFuel(), 1 + rest / 20)); + block.setFuelLevel(20 - rest % 20); + } + + block.setBrewingTime(DEFAULT_BREW_TIME); + block.update(true); + + J.runAt(location, this::run); + } + + public static ItemStack decrease(ItemStack source, int amount) { + if (source.getAmount() > amount) { + source.setAmount(source.getAmount() - amount); + return source; + } else { + return new ItemStack(Material.AIR); + } + } + + public static boolean isValid(BrewingRecipe recipe, Location loc) { + BrewingStand block = (BrewingStand) loc.getBlock().getState(); + BrewerInventory inv = block.getInventory(); + if (inv.getIngredient() == null || recipe.getIngredient() != inv.getIngredient().getType()) { + return false; + } + + int totalFuel = (inv.getFuel() != null && inv.getFuel().getType() != Material.AIR ? inv.getFuel().getAmount() * 20 : 0) + block.getFuelLevel(); + if (totalFuel < recipe.getFuelCost()) { + return false; + } + + for (int i = 0; i < 3; i++) { + if (recipe.getBasePotion().isSimilar(inv.getItem(i))) { + return true; + } + } + + return false; + } + + @Override + public void run() { + if (cancelled) { + return; + } + + BrewingStand block = (BrewingStand) this.location.getBlock().getState(); + BrewerInventory inventory = block.getInventory(); + if (brewTime <= 0) { + inventory.setIngredient(decrease(inventory.getIngredient(), 1)); + + for (int i = 0; i < 3; i++) { + if (recipe.getBasePotion().equals(inventory.getItem(i))) { + inventory.setItem(i, recipe.getResult()); + } + } + + inventory.getViewers().forEach(e -> { + if (e instanceof Player p) { + SoundPlayer sp = SoundPlayer.of(p); + sp.play(block.getLocation(), Sound.BLOCK_BREWING_STAND_BREW, 1, 1); + } + }); + cancel(); + return; + } + brewTime--; + block.setBrewingTime(getRemainingTime()); + block.update(true); + J.runAt(location, this::run, 1); + } + + public void cancel() { + cancelled = true; + } + + private int getRemainingTime() { + return (int) (DEFAULT_BREW_TIME * (brewTime / (float) recipe.getBrewingTime())); + } +} diff --git a/src/main/java/art/arcane/adapt/api/potion/PotionBuilder.java b/src/main/java/art/arcane/adapt/api/potion/PotionBuilder.java new file mode 100644 index 000000000..f002903e1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/potion/PotionBuilder.java @@ -0,0 +1,115 @@ +package art.arcane.adapt.api.potion; + +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.reflect.registries.PotionTypes; +import com.google.common.collect.Lists; +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@Getter +public class PotionBuilder { + + private final List effects = Lists.newArrayList(); + private final Type type; + + private Component name; + private List lore; + private Color color; + private PotionType baseType = PotionTypes.UNCRAFTABLE; + private ItemStack baseItem; + + private PotionBuilder(Type type) { + this.type = type; + } + + public static ItemStack vanilla(@NotNull Type type, @NotNull PotionType potion) { + return of(type) + .setBaseType(potion) + .build(); + } + + public static PotionBuilder of(@NotNull Type type) { + return new PotionBuilder(type); + } + + public static PotionBuilder of(@NotNull ItemStack item) { + return Version.get().editPotion(item); + } + + public PotionBuilder setColor(@Nullable Color color) { + this.color = color; + return this; + } + + public PotionBuilder addEffect(@NotNull PotionEffect effect) { + effects.add(effect); + return this; + } + + public PotionBuilder addEffect(PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, boolean icon) { + effects.add(new PotionEffect(type, duration, amplifier, ambient, particles, icon)); + return this; + } + + public PotionBuilder setName(@Nullable String name) { + this.name = name != null ? Component.text(name) + .decoration(TextDecoration.ITALIC, false) : null; + return this; + } + + public PotionBuilder setName(@Nullable Component name) { + this.name = name; + return this; + } + + public PotionBuilder addLore(@NotNull Component lore) { + if (this.lore == null) { + this.lore = Lists.newArrayList(); + } + + this.lore.add(lore); + return this; + } + + public PotionBuilder setLore(@Nullable List<@NotNull Component> lore) { + this.lore = lore; + return this; + } + + public PotionBuilder setBaseItem(@Nullable ItemStack item) { + this.baseItem = item; + return this; + } + + public PotionBuilder setBaseType(@Nullable PotionType data) { + this.baseType = data; + return this; + } + + @SuppressWarnings("ConstantConditions") + public ItemStack build() { + return Version.get().buildPotion(this); + } + + @Getter + @AllArgsConstructor + public enum Type { + REGULAR(Material.POTION), + SPLASH(Material.SPLASH_POTION), + LINGERING(Material.LINGERING_POTION); + + private final Material material; + } +} diff --git a/src/main/java/art/arcane/adapt/api/protection/Protector.java b/src/main/java/art/arcane/adapt/api/protection/Protector.java new file mode 100644 index 000000000..53a272de9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/protection/Protector.java @@ -0,0 +1,62 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.protection; + +import art.arcane.adapt.api.adaptation.Adaptation; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public interface Protector { + + default boolean canBlockBreak(Player player, Location blockLocation, Adaptation adaptation) { + return true; + } + + default boolean canBlockPlace(Player player, Location blockLocation, Adaptation adaptation) { + return true; + } + + default boolean canPVP(Player player, Location victimLocation, Adaptation adaptation) { + return true; + } + + default boolean canPVE(Player player, Location victimLocation, Adaptation adaptation) { + return true; + } + + default boolean canInteract(Player player, Location targetLocation, Adaptation adaptation) { + return true; + } + + default boolean canAccessChest(Player player, Location chestLocation, Adaptation adaptation) { + return true; + } + + default boolean checkRegion(Player player, Location location, Adaptation adaptation) { + return true; + } + + + String getName(); + + boolean isEnabledByDefault(); + + default void unregister() { + } +} diff --git a/src/main/java/art/arcane/adapt/api/protection/ProtectorRegistry.java b/src/main/java/art/arcane/adapt/api/protection/ProtectorRegistry.java new file mode 100644 index 000000000..0ffe64835 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/protection/ProtectorRegistry.java @@ -0,0 +1,84 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.protection; + +import art.arcane.adapt.Adapt; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ProtectorRegistry { + private final List protectors = new ArrayList<>(); + private volatile List allProtectorsSnapshot = List.of(); + private volatile List defaultProtectorsSnapshot = List.of(); + + public synchronized void registerProtector(Protector protector) { + if (protector == null || protectors.contains(protector)) { + return; + } + + Adapt.verbose("Protector: \"" + protector.getName() + "\" registered."); + protectors.add(protector); + rebuildSnapshots(); + } + + public synchronized void unregisterProtector(Protector protector) { + if (protector == null) { + return; + } + + protector.unregister(); + if (protectors.remove(protector)) { + rebuildSnapshots(); + } + } + + public List getDefaultProtectors() { + return defaultProtectorsSnapshot; + } + + public List getAllProtectors() { + return allProtectorsSnapshot; + } + + public synchronized void unregisterAll() { + protectors.forEach(Protector::unregister); + protectors.clear(); + rebuildSnapshots(); + } + + private void rebuildSnapshots() { + List all = new ArrayList<>(protectors.size()); + List defaults = new ArrayList<>(Math.max(1, protectors.size() / 2)); + + for (Protector protector : protectors) { + if (protector == null) { + continue; + } + all.add(protector); + if (protector.isEnabledByDefault()) { + defaults.add(protector); + } + } + + allProtectorsSnapshot = Collections.unmodifiableList(all); + defaultProtectorsSnapshot = Collections.unmodifiableList(defaults); + } +} diff --git a/src/main/java/art/arcane/adapt/api/protection/WorldPolicyLatencyTelemetry.java b/src/main/java/art/arcane/adapt/api/protection/WorldPolicyLatencyTelemetry.java new file mode 100644 index 000000000..7afb0a1c0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/protection/WorldPolicyLatencyTelemetry.java @@ -0,0 +1,68 @@ +package art.arcane.adapt.api.protection; + +import java.util.ArrayDeque; + +public final class WorldPolicyLatencyTelemetry { + private static final long WINDOW_MS = 60_000L; + private static final int MAX_SAMPLES = 200_000; + + private static final ArrayDeque SAMPLES = new ArrayDeque<>(); + private static long totalNanos = 0L; + + private WorldPolicyLatencyTelemetry() { + } + + public static void recordNanos(long durationNanos) { + if (durationNanos < 0L) { + return; + } + + long now = System.currentTimeMillis(); + synchronized (SAMPLES) { + trim(now); + SAMPLES.addLast(new Sample(now, durationNanos)); + totalNanos += durationNanos; + + while (SAMPLES.size() > MAX_SAMPLES) { + Sample oldest = SAMPLES.removeFirst(); + totalNanos -= oldest.durationNanos; + } + + if (totalNanos < 0L) { + totalNanos = 0L; + } + } + } + + public static double averageMillis(long now) { + synchronized (SAMPLES) { + trim(now); + if (SAMPLES.isEmpty()) { + return 0D; + } + + return (totalNanos / 1_000_000D) / (double) SAMPLES.size(); + } + } + + public static void clear() { + synchronized (SAMPLES) { + SAMPLES.clear(); + totalNanos = 0L; + } + } + + private static void trim(long now) { + while (!SAMPLES.isEmpty() && (now - SAMPLES.peekFirst().timestampMs) > WINDOW_MS) { + Sample oldest = SAMPLES.removeFirst(); + totalNanos -= oldest.durationNanos; + } + + if (totalNanos < 0L) { + totalNanos = 0L; + } + } + + private record Sample(long timestampMs, long durationNanos) { + } +} diff --git a/src/main/java/art/arcane/adapt/api/recipe/AdaptRecipe.java b/src/main/java/art/arcane/adapt/api/recipe/AdaptRecipe.java new file mode 100644 index 000000000..96089c6d5 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/recipe/AdaptRecipe.java @@ -0,0 +1,341 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.recipe; + +import art.arcane.adapt.Adapt; +import lombok.Builder; +import lombok.Data; +import lombok.Singular; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.*; + +import java.util.List; + +public interface AdaptRecipe { + static Shapeless.ShapelessBuilder shapeless() { + return Shapeless.builder(); + } + + static Shaped.ShapedBuilder shaped() { + return Shaped.builder(); + } + + static Smithing.SmithingBuilder smithing() { + return Smithing.builder(); + } + + static Stonecutter.StonecutterBuilder stonecutter() { + return Stonecutter.builder(); + } + + static Smoker.SmokerBuilder smoker() { + return Smoker.builder(); + } + + static Blast.BlastBuilder blast() { + return Blast.builder(); + } + + static Furnace.FurnaceBuilder furnace() { + return Furnace.builder(); + } + + static Campfire.CampfireBuilder campfire() { + return Campfire.builder(); + } + + ItemStack getResult(); + + String getKey(); + + default NamespacedKey getNSKey() { + return new NamespacedKey(Adapt.instance, getKey()); + } + + void register(); + + boolean is(Recipe recipe); + + void unregister(); + + @Builder + @Data + class Smoker implements AdaptRecipe { + private String key; + private ItemStack result; + private Material ingredient; + private float experience; + private int cookTime; + + @Override + public ItemStack getResult() { + return null; + } + + public void register() { + SmokingRecipe s = new SmokingRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient, experience, cookTime); + Bukkit.getServer().addRecipe(s); + Adapt.verbose("Registered Smoker Recipe " + s.getKey()); + } + + @Override + public boolean is(Recipe recipe) { + return recipe instanceof SmokingRecipe s && s.getKey().equals(getNSKey()); + } + + + @Override + public void unregister() { + Bukkit.getServer().removeRecipe(getNSKey()); + Adapt.verbose("Unregistered Smoker Recipe " + getKey()); + } + } + + @Builder + @Data + class Furnace implements AdaptRecipe { + private String key; + private ItemStack result; + private Material ingredient; + // private float experience = 1; +// private int cookTime = 20; + private float experience; + private int cookTime; + + @Override + public ItemStack getResult() { + return null; + } + + public void register() { + FurnaceRecipe s = new FurnaceRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient, experience, cookTime); + Bukkit.getServer().addRecipe(s); + Adapt.verbose("Registered Furnace Recipe " + s.getKey()); + } + + @Override + public boolean is(Recipe recipe) { + return recipe instanceof FurnaceRecipe s && s.getKey().equals(getNSKey()); + } + + + @Override + public void unregister() { + Bukkit.getServer().removeRecipe(getNSKey()); + Adapt.verbose("Unregistered Furnace Recipe " + getKey()); + } + } + + @Builder + @Data + class Campfire implements AdaptRecipe { + private String key; + private ItemStack result; + private Material ingredient; + private float experience; + private int cookTime; + + @Override + public ItemStack getResult() { + return null; + } + + public void register() { + CampfireRecipe s = new CampfireRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient, experience, cookTime); + Bukkit.getServer().addRecipe(s); + Adapt.verbose("Registered Campfire Recipe " + s.getKey()); + } + + @Override + public boolean is(Recipe recipe) { + return recipe instanceof CampfireRecipe s && s.getKey().equals(getNSKey()); + } + + @Override + public void unregister() { + Bukkit.getServer().removeRecipe(getNSKey()); + Adapt.verbose("Unregistered Campfire Recipe " + getKey()); + } + } + + @Builder + @Data + class Blast implements AdaptRecipe { + private String key; + private ItemStack result; + private Material ingredient; + private float experience; + private int cookTime; + + @Override + public ItemStack getResult() { + return null; + } + + public void register() { + BlastingRecipe s = new BlastingRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient, experience, cookTime); + Bukkit.getServer().addRecipe(s); + Adapt.verbose("Registered Blast Furnace Recipe " + s.getKey()); + } + + @Override + public boolean is(Recipe recipe) { + return recipe instanceof BlastingRecipe s && s.getKey().equals(getNSKey()); + } + + + @Override + public void unregister() { + Bukkit.getServer().removeRecipe(getNSKey()); + Adapt.verbose("Unregistered Blast Furnace Recipe " + getKey()); + } + } + + @Builder + @Data + class Shapeless implements AdaptRecipe { + private String key; + private ItemStack result; + @Singular + private List ingredients; + + @Override + public ItemStack getResult() { + return null; + } + + public void register() { + ShapelessRecipe s = new ShapelessRecipe(new NamespacedKey(Adapt.instance, getKey()), result); + ingredients.forEach(s::addIngredient); + Bukkit.getServer().addRecipe(s); + Adapt.verbose("Registered Shapeless Crafting Recipe " + s.getKey()); + } + + @Override + public boolean is(Recipe recipe) { + return recipe instanceof ShapelessRecipe s && s.getKey().equals(getNSKey()); + } + + + @Override + public void unregister() { + Bukkit.getServer().removeRecipe(getNSKey()); + Adapt.verbose("Unregistered Shapeless Crafting Recipe " + getKey()); + } + } + + @Builder + @Data + class Stonecutter implements AdaptRecipe { + private String key; + private ItemStack result; + private Material ingredient; + + @Override + public ItemStack getResult() { + return null; + } + + public void register() { + StonecuttingRecipe s = new StonecuttingRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient); + Bukkit.getServer().addRecipe(s); + Adapt.verbose("Registered Stone Cutter Recipe " + s.getKey()); + } + + @Override + public boolean is(Recipe recipe) { + return recipe instanceof StonecuttingRecipe s && s.getKey().equals(getNSKey()); + } + + + @Override + public void unregister() { + Bukkit.getServer().removeRecipe(getNSKey()); + Adapt.verbose("Unregistered Stone Cutter Recipe " + getKey()); + } + } + + @Builder + @Data + class Shaped implements AdaptRecipe { + private String key; + private ItemStack result; + @Singular + private List ingredients; + @Singular + private List shapes; + + @Override + public ItemStack getResult() { + return null; + } + + public void register() { + ShapedRecipe s = new ShapedRecipe(new NamespacedKey(Adapt.instance, getKey()), result); + s.shape(shapes.toArray(new String[0])); + ingredients.forEach(i -> s.setIngredient(i.getCharacter(), i.getChoice())); + Bukkit.getServer().addRecipe(s); + Adapt.verbose("Registered Shaped Crafting Recipe " + s.getKey()); + } + + @Override + public boolean is(Recipe recipe) { + return recipe instanceof ShapedRecipe s && s.getKey().equals(getNSKey()); + } + + @Override + public void unregister() { + Bukkit.getServer().removeRecipe(getNSKey()); + Adapt.verbose("Unregistered Shaped Crafting Recipe " + getKey()); + } + } + + @Builder + @Data + class Smithing implements AdaptRecipe { + private String key; + private ItemStack result; + private Material base; + private Material addition; + + @Override + public ItemStack getResult() { + return null; + } + + public void register() { + SmithingRecipe s = new SmithingRecipe(new NamespacedKey(Adapt.instance, getKey()), result, new RecipeChoice.ExactChoice(new ItemStack(base)), new RecipeChoice.ExactChoice(new ItemStack(addition))); + Bukkit.getServer().addRecipe(s); + Adapt.verbose("Registered Smithing Table Recipe " + s.getKey()); + } + + @Override + public boolean is(Recipe recipe) { + return recipe instanceof SmithingRecipe s && s.getKey().equals(getNSKey()); + } + + @Override + public void unregister() { + Bukkit.getServer().removeRecipe(getNSKey()); + Adapt.verbose("Unregistered Smithing Table Recipe " + getKey()); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/recipe/MaterialChar.java b/src/main/java/art/arcane/adapt/api/recipe/MaterialChar.java new file mode 100644 index 000000000..d79584918 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/recipe/MaterialChar.java @@ -0,0 +1,48 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.recipe; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.RecipeChoice; + +@AllArgsConstructor +@Data +public class MaterialChar { + private char character; + private RecipeChoice choice; + + public MaterialChar(char character, Tag tag) { + this.character = character; + this.choice = new RecipeChoice.MaterialChoice(tag); + } + + public MaterialChar(char character, Material... material) { + this.character = character; + this.choice = new RecipeChoice.MaterialChoice(material); + } + + public MaterialChar(char character, ItemStack... itemStack) { + this.character = character; + this.choice = new RecipeChoice.ExactChoice(itemStack); + } +} diff --git a/src/main/java/art/arcane/adapt/api/runtime/AdaptationGate.java b/src/main/java/art/arcane/adapt/api/runtime/AdaptationGate.java new file mode 100644 index 000000000..42e640b35 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/runtime/AdaptationGate.java @@ -0,0 +1,71 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.runtime; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.skill.Skill; +import org.bukkit.GameMode; +import org.bukkit.World; +import org.bukkit.entity.Player; + +public final class AdaptationGate { + private AdaptationGate() { + } + + public static boolean shouldSkipPlayer(Player player, Skill skill, boolean hasAdaptPlayer) { + if (player == null || skill == null) { + return true; + } + + if (!player.getClass().getSimpleName().equals("CraftPlayer")) { + return true; + } + + return !skill.isEnabled() + || !skill.hasUsePermission(player, skill) + || isWorldBlacklisted(player) + || isInCreativeOrSpectator(player) + || !hasAdaptPlayer; + } + + public static boolean shouldSkipWorld(World world, Skill skill) { + if (world == null || skill == null) { + return true; + } + + return !skill.isEnabled() || AdaptConfig.get().blacklistedWorlds.contains(world.getName()); + } + + public static boolean isWorldBlacklisted(Player player) { + if (player == null) { + return true; + } + + return AdaptConfig.get().blacklistedWorlds.contains(player.getWorld().getName()); + } + + public static boolean isInCreativeOrSpectator(Player player) { + if (player == null) { + return true; + } + + return !AdaptConfig.get().isXpInCreative() + && (player.getGameMode().equals(GameMode.CREATIVE) || player.getGameMode().equals(GameMode.SPECTATOR)); + } +} diff --git a/src/main/java/art/arcane/adapt/api/skill/SimpleSkill.java b/src/main/java/art/arcane/adapt/api/skill/SimpleSkill.java new file mode 100644 index 000000000..58ec2f967 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/skill/SimpleSkill.java @@ -0,0 +1,600 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.tick.TickedObject; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.AdaptStatTracker; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.AdventureCompat; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigFileSupport; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.format.Form; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Locale; +import java.util.UUID; + +@Data +public abstract class SimpleSkill extends TickedObject implements Skill { + private final String name; + private final String emojiName; + private C color; + private String colorPrefix; + private double minXp; + private String description; + private String displayName; + private Material icon; + private KList> adaptations; + private KList statTrackers; + private KList cachedAdvancements; + private String advancementBackground; + private KList recipes; + private Class configType; + private volatile T config; + + public SimpleSkill(String name, String emojiName) { + super("skill", UUID.randomUUID() + "-skill-" + name, 50); + statTrackers = new KList<>(); + recipes = new KList<>(); + cachedAdvancements = new KList<>(); + this.emojiName = emojiName; + adaptations = new KList<>(); + setColor(C.WHITE); + this.name = name; + setIcon(Material.BOOK); + setDescription("No Description Provided"); + setMinXp(100); + setAdvancementBackground("minecraft:textures/block/deepslate_tiles.png"); + + J.a(() -> { + J.attempt(this::getConfig); + getAdaptations().forEach(i -> J.attempt(i::getConfig)); + }, 1); + } + + @Override + public Class getConfigurationClass() { + return configType; + } + + @Override + public void registerConfiguration(Class type) { + this.configType = type; + } + + protected File getConfigFile() { + return Adapt.instance.getDataFile("adapt", "skills", getName() + ".toml"); + } + + protected File getLegacyConfigFile() { + return Adapt.instance.getDataFile("adapt", "skills", getName() + ".json"); + } + + protected T createDefaultConfig() { + try { + return getConfigurationClass().getConstructor().newInstance(); + } catch (Throwable e) { + throw new IllegalStateException("Failed to create default config for skill " + getName(), e); + } + } + + public synchronized boolean reloadConfigFromDisk(boolean announce) { + if (getConfigurationClass() == null) { + return false; + } + + T previous = config; + File file = getConfigFile(); + try { + T loaded = loadConfig(file, previous == null ? createDefaultConfig() : previous, previous == null); + config = loaded; + onConfigReload(previous, loaded); + if (announce) { + Adapt.info("Hotloaded " + file.getPath()); + } + return true; + } catch (Throwable e) { + Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid config: " + e.getMessage()); + return false; + } + } + + private T loadConfig(File file, T fallback, boolean overwriteOnReadFailure) throws IOException { + return ConfigFileSupport.load( + file, + getLegacyConfigFile(), + getConfigurationClass(), + fallback, + overwriteOnReadFailure, + "skill:" + getName(), + "Created missing skill config [adapt/skills/" + getName() + ".toml] from defaults." + ); + } + + protected void onConfigReload(T previousConfig, T newConfig) { + applyColorField(newConfig); + applyDoubleField(newConfig, "minXp", this::setMinXp); + Number interval = getNumericField(newConfig, "setInterval"); + if (interval != null) { + setInterval(interval.longValue()); + } + } + + private void applyDoubleField(T source, String fieldName, java.util.function.DoubleConsumer consumer) { + Number number = getNumericField(source, fieldName); + if (number != null) { + consumer.accept(number.doubleValue()); + } + } + + private void applyColorField(T source) { + String configuredColor = getStringField(source, "skillColor"); + if (configuredColor == null || configuredColor.isBlank()) { + return; + } + + String prefix = resolveColorPrefix(configuredColor); + if (prefix == null || prefix.isBlank()) { + return; + } + + colorPrefix = prefix; + C resolvedColor = resolveLegacyColor(prefix); + if (resolvedColor != null) { + color = resolvedColor; + } + } + + private Number getNumericField(T source, String fieldName) { + Field f = getField(source.getClass(), fieldName); + if (f == null) { + return null; + } + + try { + f.setAccessible(true); + Object value = f.get(source); + if (value instanceof Number number) { + return number; + } + } catch (Throwable ex) { + Adapt.verbose("Failed reading config field '" + fieldName + "' for skill " + getName() + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + + return null; + } + + private String getStringField(T source, String fieldName) { + Field f = getField(source.getClass(), fieldName); + if (f == null) { + return null; + } + + try { + f.setAccessible(true); + Object value = f.get(source); + if (value instanceof String stringValue) { + return stringValue; + } + } catch (Throwable ex) { + Adapt.verbose("Failed reading config field '" + fieldName + "' for skill " + getName() + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + + return null; + } + + private Field getField(Class type, String name) { + Class current = type; + while (current != null) { + try { + return current.getDeclaredField(name); + } catch (NoSuchFieldException ex) { + current = current.getSuperclass(); + } + } + + return null; + } + + @Override + public T getConfig() { + T local = config; + if (local != null) { + return local; + } + + synchronized (this) { + local = config; + if (local != null) { + return local; + } + + boolean loaded = reloadConfigFromDisk(false); + local = config; + if (!loaded || local == null) { + local = createDefaultConfig(); + onConfigReload(null, local); + config = local; + Adapt.warn("Falling back to in-memory defaults for skill config " + getName() + "."); + } + } + + return local; + } + + public void registerRecipe(AdaptRecipe r) { + recipes.add(r); + } + + public void registerAdvancement(AdaptAdvancement a) { + cachedAdvancements.add(a); + } + + public boolean checkValidEntity(EntityType e) { + if (!e.isAlive() || e.equals(EntityType.PARROT)) { + return false; + } + return !ItemListings.getInvalidDamageableEntities().contains(e); + } + + protected boolean shouldReturnForPlayer(Player p) { + return SkillRuntimeGuards.shouldSkipPlayer(this, p); + } + + protected void shouldReturnForPlayer(Player p, Runnable r) { + SkillRuntimeGuards.withPlayer(this, p, r); + } + + protected void shouldReturnForPlayer(Player p, Cancellable c, Runnable r) { + SkillRuntimeGuards.withPlayer(this, p, c, r); + } + + protected boolean shouldReturnForWorld(World world, Skill skill) { + return SkillRuntimeGuards.shouldSkipWorld(skill, world); + } + + protected boolean isWorldBlacklisted(Player p) { + return SkillRuntimeGuards.isWorldBlacklisted(p); + } + + protected boolean isInCreativeOrSpectator(Player p) { + return SkillRuntimeGuards.isInCreativeOrSpectator(p); + } + + public void setColor(C color) { + C resolved = color == null ? C.WHITE : color; + this.color = resolved; + this.colorPrefix = resolved.toString(); + } + + @Override + public String getDisplayName() { + if (!this.isEnabled()) { + return C.DARK_GRAY + Form.capitalize(getName()); + } + + String shownName = displayName == null ? Form.capitalize(getName()) : displayName; + return C.RESET + "" + C.BOLD + colorPrefixValue() + getEmojiName() + " " + shownName; + } + + @Override + public String getShortName() { + if (!this.isEnabled()) { + return C.DARK_GRAY + Form.capitalize(getName()); + } + + return C.RESET + "" + C.BOLD + colorPrefixValue() + getEmojiName(); + } + + @Override + public String getDisplayName(int level) { + if (!this.isEnabled()) { + return C.DARK_GRAY + Form.capitalize(getName()); + } + + if (level > 0) { + return getDisplayName() + C.RESET + " " + C.UNDERLINE + C.WHITE + level + C.RESET; + } + + return getDisplayName(); + } + + @Override + public void onRegisterAdvancements(KList advancements) { + advancements.addAll(cachedAdvancements); + } + + public AdaptAdvancement buildAdvancements() { + KList a = new KList<>(); + onRegisterAdvancements(a); + + for (Adaptation i : getAdaptations()) { + a.add(i.buildAdvancements()); + } + + return AdaptAdvancement.builder() + .background(getAdvancementBackground()) + .key("skill_" + getName()) + .title(displayName) + .description(getDescription()) + .icon(getIcon()) + .model(getModel()) + .children(a) + .visibility(AdvancementVisibility.HIDDEN) + .build(); + } + + @Override + public void registerStatTracker(AdaptStatTracker tracker) { + getStatTrackers().add(tracker); + } + + protected void registerMilestone(String advancementKey, String stat, double goal, double reward) { + registerStatTracker(AdaptStatTracker.builder() + .advancement(advancementKey) + .stat(stat) + .goal(goal) + .reward(reward) + .build()); + } + + protected void checkStatTrackersForOnlinePlayers() { + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player player = adaptPlayer.getPlayer(); + shouldReturnForPlayer(player, () -> checkStatTrackers(adaptPlayer)); + } + } + + @Override + public KList getStatTrackers() { + return statTrackers; + } + + @Override + public void registerAdaptation(Adaptation a) { + a.setSkill(this); + adaptations.add(a); + } + + @Override + public void unregister() { + super.unregister(); + adaptations.forEach(Adaptation::unregister); + } + + private String colorPrefixValue() { + if (colorPrefix == null || colorPrefix.isBlank()) { + return color == null ? C.WHITE.toString() : color.toString(); + } + return colorPrefix; + } + + private String resolveColorPrefix(String rawInput) { + String raw = rawInput == null ? "" : rawInput.trim(); + if (raw.isBlank()) { + return null; + } + + C named = parseNamedColor(raw); + if (named != null) { + return named.toString(); + } + + String normalizedHex = normalizeHex(raw); + if (normalizedHex != null) { + return C.translateAlternateColorCodes('&', "&#" + normalizedHex); + } + + if (raw.indexOf(C.COLOR_CHAR) >= 0) { + return raw; + } + + String legacy = C.translateAlternateColorCodes('&', raw); + if (legacy != null && legacy.indexOf(C.COLOR_CHAR) >= 0) { + return legacy; + } + + String mini = AdventureCompat.toLegacySection(raw); + if (mini != null && mini.indexOf(C.COLOR_CHAR) >= 0) { + return mini; + } + + return null; + } + + private C parseNamedColor(String raw) { + String normalized = raw.trim().toUpperCase(Locale.ROOT) + .replace('-', '_') + .replace(' ', '_'); + try { + C candidate = C.valueOf(normalized); + if (candidate.isColor()) { + return candidate; + } + } catch (IllegalArgumentException ignored) { + } + + if (raw.length() == 1) { + char code = Character.toLowerCase(raw.charAt(0)); + if (isLegacyColorChar(code)) { + return C.getByChar(code); + } + } + + return null; + } + + private String normalizeHex(String raw) { + String value = raw; + if (value.startsWith("#")) { + value = value.substring(1); + } else if (value.startsWith("0x") || value.startsWith("0X")) { + value = value.substring(2); + } + + if (value.length() != 6) { + return null; + } + + for (int i = 0; i < value.length(); i++) { + if (!isHexChar(value.charAt(i))) { + return null; + } + } + + return value; + } + + private boolean isHexChar(char c) { + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); + } + + private boolean isLegacyColorChar(char code) { + return (code >= '0' && code <= '9') || (code >= 'a' && code <= 'f'); + } + + private C resolveLegacyColor(String prefix) { + if (prefix == null || prefix.isBlank()) { + return color; + } + + for (int i = 0; i < prefix.length() - 1; i++) { + if (prefix.charAt(i) != C.COLOR_CHAR) { + continue; + } + + char code = Character.toLowerCase(prefix.charAt(i + 1)); + if (isLegacyColorChar(code)) { + return C.getByChar(code); + } + + if (code == 'x') { + String hex = parseSectionHex(prefix, i); + if (hex == null) { + continue; + } + + C nearest = nearestLegacyColor(hex); + if (nearest != null) { + return nearest; + } + } + } + + return color; + } + + private String parseSectionHex(String prefix, int start) { + if (start + 13 >= prefix.length()) { + return null; + } + + StringBuilder hex = new StringBuilder(6); + for (int i = 0; i < 6; i++) { + int colorTokenIndex = start + 2 + (i * 2); + int hexIndex = colorTokenIndex + 1; + if (colorTokenIndex >= prefix.length() || hexIndex >= prefix.length()) { + return null; + } + if (prefix.charAt(colorTokenIndex) != C.COLOR_CHAR) { + return null; + } + char hexChar = prefix.charAt(hexIndex); + if (!isHexChar(hexChar)) { + return null; + } + hex.append(hexChar); + } + return hex.toString(); + } + + private C nearestLegacyColor(String hex) { + try { + int rgb = Integer.parseInt(hex, 16); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + C best = null; + long bestDistance = Long.MAX_VALUE; + for (C candidate : C.values()) { + if (!candidate.isColor()) { + continue; + } + + String candidateHex = candidate.hex(); + if (candidateHex == null || candidateHex.length() != 7) { + continue; + } + + int candidateRgb = Integer.parseInt(candidateHex.substring(1), 16); + int cr = (candidateRgb >> 16) & 0xFF; + int cg = (candidateRgb >> 8) & 0xFF; + int cb = candidateRgb & 0xFF; + + long distance = ((long) r - cr) * ((long) r - cr) + + ((long) g - cg) * ((long) g - cg) + + ((long) b - cb) * ((long) b - cb); + if (distance < bestDistance) { + bestDistance = distance; + best = candidate; + } + } + + return best == null ? color : best; + } catch (Throwable ignored) { + return color; + } + } + + @Override + public abstract void onTick(); + + @Override + public final boolean equals(Object obj) { + return this == obj; + } + + @Override + public final int hashCode() { + return System.identityHashCode(this); + } +} diff --git a/src/main/java/art/arcane/adapt/api/skill/Skill.java b/src/main/java/art/arcane/adapt/api/skill/Skill.java new file mode 100644 index 000000000..a79ed034b --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/skill/Skill.java @@ -0,0 +1,295 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.skill; + +import art.arcane.adapt.api.Component; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.tick.Ticked; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.AdaptStatTracker; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.format.Form; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +/** + * Public API for a skill line and its shared behavior. + */ +public interface Skill extends Ticked, Component { + /** + * Builds the root advancement tree for this skill (including child + * adaptations). + */ + AdaptAdvancement buildAdvancements(); + + /** + * Returns the concrete config class used by this skill. + */ + Class getConfigurationClass(); + + /** + * Registers the config class used for load/reload of this skill. + */ + void registerConfiguration(Class type); + + /** + * @return true when this skill is runtime-enabled. + */ + boolean isEnabled(); + + /** + * Returns the live config instance for this skill. + */ + T getConfig(); + + /** + * @return internal skill key (e.g. "swords"). + */ + String getName(); + + /** + * @return icon glyph/emoji prefix used in UI. + */ + String getEmojiName(); + + /** + * @return base icon for this skill. + */ + Material getIcon(); + + /** + * @return localized skill description. + */ + String getDescription(); + + /** + * @return recipes registered by this skill. + */ + KList getRecipes(); + + /** + * Registers an adaptation under this skill. + */ + void registerAdaptation(Adaptation a); + + /** + * Registers a stat tracker/milestone for this skill. + */ + void registerStatTracker(AdaptStatTracker tracker); + + /** + * @return all stat trackers for this skill. + */ + KList getStatTrackers(); + + /** + * Skill-level particle toggle check (global + per-skill config aware). + */ + @Override + default boolean areParticlesEnabled() { + return SkillGuiSupport.areParticlesEnabled(this, Component.super.areParticlesEnabled()); + } + + /** + * Skill-level sound toggle check (global + per-skill config aware). + */ + @Override + default boolean areSoundsEnabled() { + return SkillGuiSupport.areSoundsEnabled(this, Component.super.areSoundsEnabled()); + } + + /** + * Evaluates and grants eligible stat-tracker advancements for one player. + */ + default void checkStatTrackers(AdaptPlayer player) { + SkillRuntimeGuards.checkStatTrackers(this, player); + } + + /** + * @return all adaptations currently attached to this skill. + */ + KList> getAdaptations(); + + /** + * @return primary chat/UI color for this skill. + */ + C getColor(); + + /** + * @return minimum xp tuning value used by this skill. + */ + double getMinXp(); + + /** + * Called while building advancement trees to append custom nodes. + */ + void onRegisterAdvancements(KList advancements); + + /** + * Returns true when this player has use permission for this skill via + * permission. + */ + default boolean hasUsePermission(Player p, Skill s) { + return SkillRuntimeGuards.hasUsePermission(p, s); + } + + /** + * Formatted display name with color + emoji. + */ + default String getDisplayName() { + if (!this.isEnabled()) { + return C.DARK_GRAY + Form.capitalize(getName()); + } + return C.RESET + "" + C.BOLD + getColor().toString() + getEmojiName() + " " + Form.capitalize(getName()); + } + + /** + * Compact formatted display name with color + emoji. + */ + default String getShortName() { + if (!this.isEnabled()) { + return C.DARK_GRAY + Form.capitalize(getName()); + } + return C.RESET + "" + C.BOLD + getColor().toString() + getEmojiName(); + } + + /** + * Display name with optional level suffix. + */ + default String getDisplayName(int level) { + if (!this.isEnabled()) { + return C.DARK_GRAY + Form.capitalize(getName()); + } + if (level > 0) { + return getDisplayName() + C.RESET + " " + C.UNDERLINE + C.WHITE + level + C.RESET; + } + return getDisplayName(); + } + + /** + * Returns the generated custom model binding for this skill icon. + */ + default CustomModel getModel() { + return CustomModel.get(getIcon(), "skill", getName()); + } + + /** + * Grants visible xp at the player's location. + */ + default void xp(Player p, double xp) { + xp(p, xp, null); + } + + /** + * Grants visible xp at the player's location using an optional reward key. + */ + default void xp(Player p, double xp, String rewardKey) { + xp(p, p == null ? null : p.getLocation(), xp, rewardKey); + + } + + /** + * Grants visible xp at a specific location. + */ + default void xp(Player p, Location at, double xp) { + xp(p, at, xp, null); + } + + /** + * Grants visible xp at a specific location using an optional reward key. + */ + default void xp(Player p, Location at, double xp, String rewardKey) { + SkillRuntimeGuards.grantXp(this, p, at, xp, rewardKey, false, true); + } + + /** + * Grants silent xp at a specific location (with optional burst visuals). + */ + default void xpS(Player p, Location at, double xp) { + xpS(p, at, xp, null); + } + + /** + * Grants silent xp at a specific location using an optional reward key. + */ + default void xpS(Player p, Location at, double xp, String rewardKey) { + SkillRuntimeGuards.grantXp(this, p, at, xp, rewardKey, true, true); + } + + /** + * Grants silent xp without visuals. + */ + default void xpSilent(Player p, double xp) { + xpSilent(p, xp, null); + } + + /** + * Grants silent xp without visuals using an optional reward key. + */ + default void xpSilent(Player p, double xp, String rewardKey) { + SkillRuntimeGuards.grantXpSilent(this, p, xp, rewardKey); + } + + /** + * Emits spatial xp pulses around a world location. + */ + default void xp(Location at, double xp, int rad, long duration) { + XP.spatialXP(at, this, xp, rad, duration); + vfxXP(at); + } + + /** + * Grants knowledge points for this skill. + */ + default void knowledge(Player p, long k) { + SkillRuntimeGuards.grantKnowledge(this, p, k); + } + + /** + * Opens the skill GUI and optionally enforces use permission checks. + */ + default boolean openGui(Player player, boolean checkPermissions) { + if (checkPermissions && !hasUsePermission(player, this)) { + return false; + } else { + openGui(player); + return true; + } + } + + /** + * Opens page 0 of this skill GUI. + */ + default void openGui(Player player) { + openGui(player, 0); + } + + /** + * Opens a specific page of this skill GUI. + */ + default void openGui(Player player, int page) { + SkillGuiSupport.openGui(this, player, page); + } +} diff --git a/src/main/java/art/arcane/adapt/api/skill/SkillEventRegistrar.java b/src/main/java/art/arcane/adapt/api/skill/SkillEventRegistrar.java new file mode 100644 index 000000000..095b48987 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/skill/SkillEventRegistrar.java @@ -0,0 +1,108 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.EventHandlerInvoker; +import art.arcane.adapt.api.adaptation.ReceiveCancelledEvents; +import org.bukkit.Bukkit; +import org.bukkit.event.*; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +public final class SkillEventRegistrar { + private SkillEventRegistrar() { + } + + public static boolean register(Plugin plugin, Listener listener) { + if (!(listener instanceof Skill)) { + return false; + } + + boolean registeredAny = false; + for (Method method : collectHandlerMethods(listener.getClass()).values()) { + EventHandler annotation = method.getAnnotation(EventHandler.class); + if (annotation == null || method.getParameterCount() != 1 || Modifier.isStatic(method.getModifiers())) { + continue; + } + + Class parameterType = method.getParameterTypes()[0]; + if (!Event.class.isAssignableFrom(parameterType)) { + continue; + } + + @SuppressWarnings("unchecked") + Class eventType = (Class) parameterType; + try { + method.setAccessible(true); + } catch (Throwable ex) { + Adapt.warn("Failed enabling access to skill handler " + + listener.getClass().getName() + "#" + method.getName() + + ": " + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + continue; + } + + EventExecutor executor = EventHandlerInvoker.createExecutor(method, eventType); + + boolean ignoreCancelled = shouldIgnoreCancelled(method, annotation, eventType); + Bukkit.getPluginManager().registerEvent(eventType, listener, annotation.priority(), executor, plugin, ignoreCancelled); + registeredAny = true; + } + + return registeredAny; + } + + private static Map collectHandlerMethods(Class type) { + Map methods = new LinkedHashMap<>(); + Class current = type; + while (current != null && current != Object.class) { + for (Method method : current.getDeclaredMethods()) { + if (!method.isAnnotationPresent(EventHandler.class)) { + continue; + } + methods.putIfAbsent(signature(method), method); + } + current = current.getSuperclass(); + } + return methods; + } + + private static String signature(Method method) { + return method.getName() + "|" + Arrays.toString(method.getParameterTypes()); + } + + private static boolean shouldIgnoreCancelled(Method method, EventHandler annotation, Class eventType) { + if (!Cancellable.class.isAssignableFrom(eventType)) { + return annotation.ignoreCancelled(); + } + + if (method.isAnnotationPresent(ReceiveCancelledEvents.class)) { + return false; + } + + return true; + } +} diff --git a/src/main/java/art/arcane/adapt/api/skill/SkillGuiSupport.java b/src/main/java/art/arcane/adapt/api/skill/SkillGuiSupport.java new file mode 100644 index 000000000..e1437b5ad --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/skill/SkillGuiSupport.java @@ -0,0 +1,354 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.content.gui.SkillsGui; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.inventorygui.GuiEffects; +import art.arcane.adapt.util.common.inventorygui.GuiLayout; +import art.arcane.adapt.util.common.inventorygui.GuiTheme; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.data.MaterialBlock; +import art.arcane.volmlib.util.format.ColorFormatter; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.inventorygui.UIElement; +import art.arcane.volmlib.util.inventorygui.UIWindow; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; + +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +final class SkillGuiSupport { + private static final long CLOSE_SUPPRESS_MS = 1200L; + private static final int CLOSE_SUPPRESS_CLEAR_TICKS = 4; + private static final Map CLOSE_SUPPRESS_UNTIL = new ConcurrentHashMap<>(); + + private SkillGuiSupport() { + } + + static boolean areParticlesEnabled(Skill skill, boolean componentEnabled) { + if (!componentEnabled) { + return false; + } + + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + if (effects != null && effects.getSkillParticleOverrides() != null && !effects.getSkillParticleOverrides().isEmpty()) { + String key = skill.getName(); + Boolean override = effects.getSkillParticleOverrides().get(key); + if (override == null && key != null) { + override = effects.getSkillParticleOverrides().get(key.toLowerCase(Locale.ROOT)); + } + if (override != null && !override) { + return false; + } + } + + Object config = skill.getConfig(); + if (config != null) { + Boolean directToggle = readBooleanField(config, "showParticles"); + if (directToggle != null && !directToggle) { + return false; + } + + Boolean genericToggle = readBooleanField(config, "showParticleEffects"); + if (genericToggle != null && !genericToggle) { + return false; + } + } + + return true; + } + + static boolean areSoundsEnabled(Skill skill, boolean componentEnabled) { + if (!componentEnabled) { + return false; + } + + Object config = skill.getConfig(); + if (config != null) { + Boolean directToggle = readBooleanField(config, "showSounds"); + if (directToggle != null && !directToggle) { + return false; + } + } + + return true; + } + + static void openGui(Skill skill, Player player, int page) { + if (skill == null || !skill.isEnabled() || !SkillRuntimeGuards.isRuntimePlayer(player)) { + return; + } + + if (!J.isPrimaryThread()) { + int targetPage = page; + J.runEntity(player, () -> openGui(skill, player, targetPage)); + return; + } + + AdaptPlayer adaptPlayer = Adapt.instance.getAdaptServer().getPlayer(player); + if (adaptPlayer == null) { + return; + } + + SoundPlayer spw = SoundPlayer.of(player.getWorld()); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 1.455f); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 1.855f); + + List> visibleAdaptations = new ArrayList<>(); + for (Adaptation adaptation : skill.getAdaptations()) { + if (!adaptation.isEnabled()) { + continue; + } + if (!adaptation.getSkill().isEnabled()) { + continue; + } + if (!adaptation.hasUsePermission(player, adaptation)) { + continue; + } + visibleAdaptations.add(adaptation); + } + visibleAdaptations.sort( + Comparator.comparing((Adaptation adaptation) -> normalizeSortKey(adaptation.getDisplayName())) + .thenComparing(Adaptation::getName, String.CASE_INSENSITIVE_ORDER) + ); + + boolean reserveNavigation = AdaptConfig.get().isGuiBackButton(); + GuiLayout.PagePlan plan = GuiLayout.plan(visibleAdaptations.size(), reserveNavigation); + int currentPage = GuiLayout.clampPage(page, plan.pageCount()); + int start = currentPage * plan.itemsPerPage(); + int end = Math.min(visibleAdaptations.size(), start + plan.itemsPerPage()); + + UIWindow window = new UIWindow(Adapt.instance, player); + GuiTheme.apply(window, "skill/" + skill.getName()); + window.setViewportHeight(plan.rows()); + + if (visibleAdaptations.isEmpty()) { + window.setElement(0, 0, new UIElement("ada-empty") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.GRAY + "No adaptations available")); + } else { + List reveal = new ArrayList<>(); + for (int row = 0; row < plan.contentRows(); row++) { + int rowStart = start + (row * GuiLayout.WIDTH); + if (rowStart >= end) { + break; + } + + int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); + for (int i = 0; i < rowCount; i++) { + Adaptation adaptation = visibleAdaptations.get(rowStart + i); + int level = adaptPlayer.getData().getSkillLine(skill.getName()).getAdaptationLevel(adaptation.getName()); + int pos = GuiLayout.centeredPosition(i, rowCount); + Element element = new UIElement("ada-" + adaptation.getName()) + .setMaterial(new MaterialBlock(adaptation.getIcon())) + .setBaseItemStack(adaptation.getModel().toItemStack()) + .setName(adaptation.getDisplayName(level)) + .addLore(C.GRAY + adaptation.getDescription()) + .addLore(level == 0 ? (C.DARK_GRAY + Localizer.dLocalize("snippets.gui.not_learned")) : (C.GRAY + Localizer.dLocalize("snippets.gui.level") + " " + C.WHITE + Form.toRoman(level))) + .setProgress(1D) + .onLeftClick((e) -> openAdaptation(adaptation, player)); + reveal.add(new GuiEffects.Placement(pos, row, element)); + } + } + GuiEffects.applyReveal(window, reveal); + } + + if (plan.hasNavigationRow()) { + int navRow = plan.rows() - 1; + int jumpPages = 5; + int jumpBack = Math.max(0, currentPage - jumpPages); + int jumpForward = Math.min(plan.pageCount() - 1, currentPage + jumpPages); + if (currentPage > 0) { + window.setElement(-4, navRow, new UIElement("skill-prev") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.WHITE + "Previous") + .addLore(C.GRAY + "Right click: jump -" + jumpPages + " pages") + .onLeftClick((e) -> openSkillPage(skill, player, currentPage - 1)) + .onRightClick((e) -> openSkillPage(skill, player, jumpBack))); + window.setElement(-3, navRow, new UIElement("skill-first") + .setMaterial(new MaterialBlock(Material.LECTERN)) + .setName(C.GRAY + "First") + .onLeftClick((e) -> openSkillPage(skill, player, 0))); + } + if (currentPage < plan.pageCount() - 1) { + window.setElement(4, navRow, new UIElement("skill-next") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.WHITE + "Next") + .addLore(C.GRAY + "Right click: jump +" + jumpPages + " pages") + .onLeftClick((e) -> openSkillPage(skill, player, currentPage + 1)) + .onRightClick((e) -> openSkillPage(skill, player, jumpForward))); + window.setElement(3, navRow, new UIElement("skill-last") + .setMaterial(new MaterialBlock(Material.LECTERN)) + .setName(C.GRAY + "Last") + .onLeftClick((e) -> openSkillPage(skill, player, plan.pageCount() - 1))); + } + + int from = visibleAdaptations.isEmpty() ? 0 : (start + 1); + int to = visibleAdaptations.isEmpty() ? 0 : end; + window.setElement(-1, navRow, new UIElement("skill-page-info") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.AQUA + "Page " + (currentPage + 1) + "/" + plan.pageCount()) + .addLore(C.GRAY + "Showing " + from + "-" + to + " of " + visibleAdaptations.size()) + .setProgress(1D)); + + if (AdaptConfig.get().isGuiBackButton()) { + window.setElement(0, navRow, new UIElement("back") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName("" + C.RESET + C.GRAY + Localizer.dLocalize("snippets.gui.back")) + .onLeftClick((e) -> navigateBack(player))); + } + + } + + window.setTitle(skill.getDisplayName(adaptPlayer.getSkillLine(skill.getName()).getLevel()) + " " + Form.pc(XP.getLevelProgress(adaptPlayer.getSkillLine(skill.getName()).getXp())) + " (" + Form.f((int) XP.getXpUntilLevelUp(adaptPlayer.getSkillLine(skill.getName()).getXp())) + Localizer.dLocalize("snippets.gui.xp") + " " + (adaptPlayer.getSkillLine(skill.getName()).getLevel() + 1) + ")"); + window.onClosed((vv) -> J.runEntity(player, () -> onGuiClosed(player, !AdaptConfig.get().isEscClosesAllGuis()))); + window.open(); + Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), window); + } + + private static void openSkillPage(Skill skill, Player player, int page) { + suppressClose(player); + openGui(skill, player, page); + } + + private static void openAdaptation(Adaptation adaptation, Player player) { + suppressClose(player); + adaptation.openGui(player); + } + + private static void navigateBack(Player player) { + playCloseSound(player); + suppressClose(player); + SkillsGui.open(player); + } + + private static void onGuiClosed(Player player, boolean openPrevGui) { + if (player == null) { + return; + } + + if (consumeCloseSuppression(player)) { + return; + } + + playCloseSound(player); + if (openPrevGui) { + J.runEntity(player, () -> { + if (player.isOnline() && player.getOpenInventory().getTopInventory().getType() == InventoryType.CRAFTING) { + SkillsGui.open(player); + } + }, 1); + } else { + Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString()); + } + } + + private static void playCloseSound(Player player) { + SoundPlayer spw = SoundPlayer.of(player.getWorld()); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 1.455f); + spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 1.855f); + } + + private static void suppressClose(Player player) { + if (player == null) { + return; + } + + UUID playerId = player.getUniqueId(); + long suppressUntil = System.currentTimeMillis() + CLOSE_SUPPRESS_MS; + CLOSE_SUPPRESS_UNTIL.put(playerId, suppressUntil); + J.s(() -> { + Long current = CLOSE_SUPPRESS_UNTIL.get(playerId); + if (current != null && current.longValue() == suppressUntil) { + CLOSE_SUPPRESS_UNTIL.remove(playerId); + } + }, CLOSE_SUPPRESS_CLEAR_TICKS); + } + + private static boolean consumeCloseSuppression(Player player) { + if (player == null) { + return false; + } + + Long until = CLOSE_SUPPRESS_UNTIL.get(player.getUniqueId()); + if (until == null) { + return false; + } + + if (until >= System.currentTimeMillis()) { + CLOSE_SUPPRESS_UNTIL.remove(player.getUniqueId()); + return true; + } + + CLOSE_SUPPRESS_UNTIL.remove(player.getUniqueId()); + return false; + } + + private static String normalizeSortKey(String value) { + if (value == null) { + return ""; + } + + String normalized = ColorFormatter.stripColor(value).toLowerCase(Locale.ROOT).trim(); + return normalized.replaceFirst("^[^\\p{L}\\p{N}]+", ""); + } + + private static Boolean readBooleanField(Object source, String fieldName) { + if (source == null || fieldName == null || fieldName.isBlank()) { + return null; + } + + Class current = source.getClass(); + while (current != null) { + try { + Field field = current.getDeclaredField(fieldName); + field.setAccessible(true); + Object value = field.get(source); + if (value instanceof Boolean bool) { + return bool; + } + return null; + } catch (NoSuchFieldException ex) { + current = current.getSuperclass(); + } catch (Throwable ex) { + Adapt.verbose("Failed reading boolean field '" + fieldName + "' from " + source.getClass().getName() + + ": " + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + return null; + } + } + + return null; + } +} diff --git a/src/main/java/art/arcane/adapt/api/skill/SkillRegistry.java b/src/main/java/art/arcane/adapt/api/skill/SkillRegistry.java new file mode 100644 index 000000000..457032a8c --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/skill/SkillRegistry.java @@ -0,0 +1,582 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.potion.BrewingManager; +import art.arcane.adapt.api.protection.Protector; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.tick.TickedObject; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.api.xp.XPMultiplier; +import art.arcane.adapt.content.gui.SkillsGui; +import art.arcane.adapt.content.skill.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.math.M; +import org.bukkit.*; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerExpChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.bukkit.persistence.PersistentDataType; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +public class SkillRegistry extends TickedObject { + public static final KMap> skills = new KMap<>(); + private static final long SLOW_SKILL_REG_MS = 300L; + private static final int DEFERRED_SKILLS_PER_TICK = 2; + private final KMap> knownSkills = new KMap<>(); + private final KMap>> skillTypes = new KMap<>(); + private final Map> adaptationRecipeIndex = new ConcurrentHashMap<>(); + private final Deque> deferredBootstrapRecipeRegistration = new ArrayDeque<>(); + private final AtomicLong catalogRevision = new AtomicLong(); + private volatile boolean deferredBootstrapRecipeTaskScheduled; + private volatile boolean bootstrapLoading = true; + private volatile boolean foliaRecipeRegistrationWaitWarned; + + public SkillRegistry() { + super("registry", UUID.randomUUID() + "-sk", 1250); + registerSkill(SkillAgility.class); + registerSkill(SkillArchitect.class); + registerSkill(SkillAxes.class); + registerSkill(SkillBlocking.class); + registerSkill(SkillChronos.class); + registerSkill(SkillCrafting.class); + registerSkill(SkillDiscovery.class); + registerSkill(SkillEnchanting.class); + registerSkill(SkillHerbalism.class); + registerSkill(SkillHunter.class); + registerSkill(SkillPickaxes.class); + registerSkill(SkillRanged.class); + registerSkill(SkillRift.class); + registerSkill(SkillSeaborne.class); + registerSkill(SkillStealth.class); + registerSkill(SkillSwords.class); + registerSkill(SkillTaming.class); + registerSkill(SkillTragOul.class); + registerSkill(SkillUnarmed.class); + registerSkill(SkillExcavation.class); + registerSkill(SkillBrewing.class); + registerSkill(SkillNether.class); + bootstrapLoading = false; + scheduleDeferredBootstrapRecipeRegistration(); + } + + @EventHandler + public void on(PlayerExpChangeEvent e) { + Player p = e.getPlayer(); + if (e.getAmount() > 0) { + getPlayer(p).boostXPToRecents(0.03, 10000); + } + } + + private boolean canInteract(Player player, Location targetLocation) { + if (player == null || targetLocation == null) { + return false; + } + + for (Protector protector : Adapt.instance.getProtectorRegistry().getAllProtectors()) { + if (!protector.canInteract(player, targetLocation, null)) { + return false; + } + } + + return true; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + + boolean commonConditions = p.isSneaking() && e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && e.getClickedBlock() != null; + boolean isLectern = commonConditions && e.getClickedBlock().getType().equals(Material.LECTERN); + boolean isObserver = commonConditions && e.getClickedBlock().getType().equals(Material.OBSERVER); + boolean allowVerticalFaces = AdaptConfig.get().adaptActivatorAllowVerticalFaces; + boolean validActivatorFace = e.getBlockFace() != null + && (allowVerticalFaces || (!e.getBlockFace().equals(BlockFace.UP) && !e.getBlockFace().equals(BlockFace.DOWN))); + boolean isAdaptActivator = validActivatorFace && !p.isSneaking() && e.getAction().equals(Action.RIGHT_CLICK_BLOCK) + && e.getClickedBlock() != null + && canInteract(p, e.getClickedBlock().getLocation()) + && e.getClickedBlock().getType().equals(Material.valueOf(AdaptConfig.get().adaptActivatorBlock)) && (p.getInventory().getItemInMainHand().getType().equals(Material.AIR) + || !p.getInventory().getItemInMainHand().getType().isBlock()) && + (p.getInventory().getItemInOffHand().getType().equals(Material.AIR) || !p.getInventory().getItemInOffHand().getType().isBlock()); + + if (isAdaptActivator) { + SoundPlayer spw = SoundPlayer.of(e.getClickedBlock().getWorld()); + spw.play(e.getClickedBlock().getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.5f, 0.72f); + spw.play(e.getClickedBlock().getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 0.35f, 0.755f); + SkillsGui.open(p); + e.setCancelled(true); + p.getWorld().spawnParticle(Particles.CRIT_MAGIC, e.getClickedBlock().getLocation().clone().add(0.5, 1, 0.5), 25, 0, 0, 0, 1.1); + p.getWorld().spawnParticle(Particles.ENCHANTMENT_TABLE, e.getClickedBlock().getLocation().clone().add(0.5, 1, 0.5), 12, 0, 0, 0, 1.1); + } + + if (isLectern) { + ItemStack it = p.getInventory().getItemInMainHand(); + if (it.getItemMeta() != null && !it.getItemMeta().getPersistentDataContainer().getKeys().isEmpty()) { + e.setCancelled(true); + playDebug(p); + it.getItemMeta().getPersistentDataContainer().getKeys().forEach(k -> Bukkit.getServer().getConsoleSender().sendMessage(k + " = " + it.getItemMeta().getPersistentDataContainer().getOrDefault(k, PersistentDataType.STRING, "Not a String"))); + } + } + + if (isObserver) { + ItemStack it = p.getInventory().getItemInMainHand(); + if (it.getType().equals(Material.EXPERIENCE_BOTTLE)) { + e.setCancelled(true); + Bukkit.getServer().getConsoleSender().sendMessage(" "); + p.setCooldown(Material.ENCHANTED_BOOK, 3); + AdaptPlayer a = getPlayer(p); + playDebug(p); + + String xv = a.getData().getMultiplier() - 1d > 0 ? "+" + Form.pc(a.getData().getMultiplier() - 1D) : Form.pc(a.getData().getMultiplier() - 1D); + Bukkit.getServer().getConsoleSender().sendMessage("Global" + C.GRAY + ": " + C.GREEN + xv); + + for (XPMultiplier i : a.getData().getMultipliers()) { + String vv = i.getMultiplier() > 0 ? "+" + Form.pc(i.getMultiplier()) : Form.pc(i.getMultiplier()); + Bukkit.getServer().getConsoleSender().sendMessage(C.GREEN + "* " + vv + C.GRAY + " for " + Form.duration(i.getGoodFor() - M.ms(), 0)); + } + for (XPMultiplier i : Adapt.instance.getAdaptServer().getData().getMultipliers()) { + String vv = i.getMultiplier() > 0 ? "+" + Form.pc(i.getMultiplier()) : Form.pc(i.getMultiplier()); + Bukkit.getServer().getConsoleSender().sendMessage(C.GREEN + "* " + vv + C.GRAY + " for " + Form.duration(i.getGoodFor() - M.ms(), 0)); + } + + for (PlayerSkillLine i : a.getData().getSkillLines().v()) { + Skill s = i.getRawSkill(a); + if (s == null) { + continue; + } + String v = i.getMultiplier() - a.getData().getMultiplier() > 0 ? "+" + Form.pc(i.getMultiplier() - a.getData().getMultiplier()) : Form.pc(i.getMultiplier() - a.getData().getMultiplier()); + Bukkit.getServer().getConsoleSender().sendMessage(" " + s.getDisplayName() + C.GRAY + ": " + s.getColor() + v); + for (XPMultiplier j : i.getMultipliers()) { + String vv = j.getMultiplier() > 0 ? "+" + Form.pc(j.getMultiplier()) : Form.pc(j.getMultiplier()); + Bukkit.getServer().getConsoleSender().sendMessage(" " + s.getShortName() + C.GRAY + " " + vv + " for " + Form.duration(j.getGoodFor() - M.ms(), 0)); + } + } + } + } + } + + private void playDebug(Player p) { + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_BELL_RESONATE, 1f, 0.6f); + sp.play(p.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 1f, 0.1f); + sp.play(p.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1f, 1.6f); + sp.play(p.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1f, 1.2f); + + } + + public Skill getSkill(String i) { + if (i == null) { + return null; + } + + Skill direct = skills.get(i); + if (direct != null) { + return direct; + } + + return skills.get(normalizeSkillName(i)); + } + + public Skill getAnySkill(String i) { + if (i == null) { + return null; + } + + Skill direct = knownSkills.get(i); + if (direct != null) { + return direct; + } + + return knownSkills.get(normalizeSkillName(i)); + } + + public List> getSkills() { + return skills.v(); + } + + public List> getAllSkills() { + return new ArrayList<>(knownSkills.v()); + } + + public long getCatalogRevision() { + return catalogRevision.get(); + } + + public synchronized void registerSkill(Class> skillType) { + long started = System.currentTimeMillis(); + long instantiateStarted = started; + Skill skill = instantiateSkill(skillType); + long instantiateMs = System.currentTimeMillis() - instantiateStarted; + if (skill == null) { + return; + } + + String skillName = normalizeSkillName(skill.getName()); + skillTypes.put(skillName, skillType); + Skill previous = knownSkills.put(skillName, skill); + if (previous != null && previous != skill) { + unregisterRecipes(previous); + previous.unregister(); + } + + if (!skill.isEnabled()) { + skill.unregister(); + skills.remove(skillName); + catalogRevision.incrementAndGet(); + return; + } + + skills.put(skillName, skill); + if (bootstrapLoading) { + deferredBootstrapRecipeRegistration.addLast(skill); + } else { + registerRecipes(skill); + } + + long totalMs = System.currentTimeMillis() - started; + if (totalMs >= SLOW_SKILL_REG_MS || instantiateMs >= SLOW_SKILL_REG_MS) { + Adapt.warn("Skill registration slow-path [" + skillName + "] total=" + totalMs + "ms instantiate=" + instantiateMs + "ms bootstrap=" + bootstrapLoading + "."); + } + catalogRevision.incrementAndGet(); + } + + public synchronized boolean hotReloadSkillConfig(String skillName) { + String normalized = normalizeSkillName(skillName); + Skill loaded = knownSkills.get(normalized); + if (loaded instanceof SimpleSkill simpleSkill) { + boolean wasEnabled = loaded.isEnabled(); + boolean ok = simpleSkill.reloadConfigFromDisk(false); + if (!ok) { + return false; + } + + if (!loaded.isEnabled()) { + unregisterRecipes(loaded); + if (wasEnabled) { + loaded.unregister(); + } + skills.remove(normalized); + catalogRevision.incrementAndGet(); + return true; + } + + if (!wasEnabled) { + return replaceSkillInstance(normalized, inferSkillType(normalized, loaded), loaded); + } + + skills.put(normalized, loaded); + unregisterRecipes(loaded); + registerRecipes(loaded); + catalogRevision.incrementAndGet(); + return true; + } + + Class> skillType = inferSkillType(normalized, loaded); + if (skillType == null) { + Adapt.verbose("No known skill type for config hotload: " + skillName); + return false; + } + + return replaceSkillInstance(normalized, skillType, loaded); + } + + @SuppressWarnings("unchecked") + private Class> inferSkillType(String normalizedSkillName, Skill loaded) { + Class> skillType = skillTypes.get(normalizedSkillName); + if (skillType != null) { + return skillType; + } + + if (loaded != null && Skill.class.isAssignableFrom(loaded.getClass())) { + return (Class>) loaded.getClass(); + } + + return null; + } + + private boolean replaceSkillInstance(String normalizedName, Class> skillType, Skill previousLoaded) { + Skill replacement = instantiateSkill(skillType); + if (replacement == null) { + return false; + } + + Skill previousKnown = knownSkills.put(normalizedName, replacement); + if (previousKnown != null && previousKnown != replacement) { + unregisterRecipes(previousKnown); + previousKnown.unregister(); + } else if (previousLoaded != null && previousLoaded != replacement) { + unregisterRecipes(previousLoaded); + previousLoaded.unregister(); + } + + if (!replacement.isEnabled()) { + replacement.unregister(); + skills.remove(normalizedName); + catalogRevision.incrementAndGet(); + return true; + } + + Skill previous = skills.put(normalizedName, replacement); + if (previous != null && previous != replacement) { + unregisterRecipes(previous); + previous.unregister(); + } + + registerRecipes(replacement); + catalogRevision.incrementAndGet(); + return true; + } + + public synchronized void refreshRecipes(Skill skill) { + if (skill == null) { + return; + } + + unregisterRecipes(skill); + if (skill.isEnabled()) { + registerRecipes(skill); + } + } + + public boolean isKnownSkill(String skillName) { + if (skillName == null) { + return false; + } + + return skillTypes.containsKey(normalizeSkillName(skillName)); + } + + public Adaptation getRequiredAdaptation(Recipe recipe) { + if (!(recipe instanceof Keyed keyed)) { + return null; + } + + return adaptationRecipeIndex.get(keyed.getKey()); + } + + private Skill instantiateSkill(Class> skillType) { + try { + return skillType.getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | + InvocationTargetException | NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } + + private void unregisterRecipes(Skill s) { + s.getRecipes().forEach(AdaptRecipe::unregister); + s.getAdaptations().forEach(adaptation -> { + adaptation.getRecipes().forEach(recipe -> { + removeAdaptationRecipeIndex(recipe, adaptation); + recipe.unregister(); + }); + }); + } + + private void registerRecipes(Skill s) { + if (s == null) { + return; + } + + if (shouldDelayRecipeRegistrationForFolia()) { + enqueueDeferredRecipeRegistration(s); + return; + } + + registerRecipesNow(s); + } + + private void registerRecipesNow(Skill s) { + if (!s.isEnabled()) { + return; + } + s.getRecipes().forEach(AdaptRecipe::register); + s.getAdaptations().forEach(adaptation -> { + if (!adaptation.isEnabled()) { + return; + } + adaptation.getRecipes().forEach(recipe -> { + recipe.register(); + indexAdaptationRecipe(recipe, adaptation); + }); + adaptation.getBrewingRecipes().forEach(r -> BrewingManager.registerRecipe(adaptation.getName(), r)); + }); + } + + private boolean shouldDelayRecipeRegistrationForFolia() { + if (!J.isFoliaThreading()) { + return false; + } + + return !Bukkit.getOnlinePlayers().isEmpty(); + } + + private synchronized void enqueueDeferredRecipeRegistration(Skill skill) { + if (skill == null) { + return; + } + + deferredBootstrapRecipeRegistration.remove(skill); + deferredBootstrapRecipeRegistration.addLast(skill); + scheduleDeferredBootstrapRecipeRegistration(); + } + + @Override + public void unregister() { + deferredBootstrapRecipeTaskScheduled = false; + deferredBootstrapRecipeRegistration.clear(); + for (Skill i : knownSkills.v()) { + i.unregister(); + unregisterRecipes(i); + } + skills.clear(); + knownSkills.clear(); + skillTypes.clear(); + adaptationRecipeIndex.clear(); + catalogRevision.incrementAndGet(); + } + + @Override + public void onTick() { + + } + + private String normalizeSkillName(String raw) { + String normalized = raw.trim().toLowerCase(Locale.ROOT); + if (normalized.startsWith("[skill]-")) { + normalized = normalized.substring("[skill]-".length()); + } + + if (normalized.equals("chrono")) { + normalized = "chronos"; + } + + return normalized; + } + + private void indexAdaptationRecipe(AdaptRecipe recipe, Adaptation adaptation) { + if (recipe == null || adaptation == null) { + return; + } + + NamespacedKey key = recipe.getNSKey(); + Adaptation previous = adaptationRecipeIndex.put(key, adaptation); + if (previous != null && previous != adaptation) { + Adapt.warn("Recipe key conflict for " + key + ": " + previous.getName() + " replaced by " + adaptation.getName()); + } + } + + private void removeAdaptationRecipeIndex(AdaptRecipe recipe, Adaptation adaptation) { + if (recipe == null) { + return; + } + + NamespacedKey key = recipe.getNSKey(); + if (adaptation == null) { + adaptationRecipeIndex.remove(key); + return; + } + + adaptationRecipeIndex.computeIfPresent(key, (k, current) -> current == adaptation ? null : current); + } + + private synchronized void scheduleDeferredBootstrapRecipeRegistration() { + if (deferredBootstrapRecipeRegistration.isEmpty() || deferredBootstrapRecipeTaskScheduled) { + return; + } + + deferredBootstrapRecipeTaskScheduled = true; + Adapt.info("Deferring recipe registration for " + deferredBootstrapRecipeRegistration.size() + " skills."); + J.s(this::runDeferredBootstrapRecipeRegistrationTick, 1); + } + + private void runDeferredBootstrapRecipeRegistrationTick() { + if (shouldDelayRecipeRegistrationForFolia()) { + if (!foliaRecipeRegistrationWaitWarned) { + foliaRecipeRegistrationWaitWarned = true; + Adapt.warn("Delaying Adapt recipe registration on Folia while players are online to avoid unsafe recipe reload races. Recipes will register automatically when the server has no online players."); + } + J.s(this::runDeferredBootstrapRecipeRegistrationTick, 20); + return; + } + + foliaRecipeRegistrationWaitWarned = false; + boolean complete; + + synchronized (this) { + if (!deferredBootstrapRecipeTaskScheduled) { + return; + } + + int processed = 0; + long started = System.currentTimeMillis(); + while (processed < DEFERRED_SKILLS_PER_TICK) { + if (shouldDelayRecipeRegistrationForFolia()) { + break; + } + + Skill skill = deferredBootstrapRecipeRegistration.pollFirst(); + if (skill == null) { + break; + } + registerRecipesNow(skill); + processed++; + if (System.currentTimeMillis() - started > 8L) { + break; + } + } + + complete = deferredBootstrapRecipeRegistration.isEmpty(); + if (complete) { + deferredBootstrapRecipeTaskScheduled = false; + } + } + + if (complete) { + Adapt.info("Deferred recipe registration completed."); + return; + } + + if (shouldDelayRecipeRegistrationForFolia()) { + J.s(this::runDeferredBootstrapRecipeRegistrationTick, 20); + } else { + J.s(this::runDeferredBootstrapRecipeRegistrationTick, 1); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/skill/SkillRuntimeGuards.java b/src/main/java/art/arcane/adapt/api/skill/SkillRuntimeGuards.java new file mode 100644 index 000000000..845731112 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/skill/SkillRuntimeGuards.java @@ -0,0 +1,238 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.runtime.AdaptationGate; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.AdaptStatTracker; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.api.xp.XpNovelty; +import art.arcane.adapt.util.common.scheduling.J; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +final class SkillRuntimeGuards { + private static final Map USE_PERMISSION_NODES = new ConcurrentHashMap<>(); + + private SkillRuntimeGuards() { + } + + static void checkStatTrackers(Skill skill, AdaptPlayer player) { + if (skill == null || player == null || !skill.isEnabled()) { + return; + } + if (!AdaptConfig.get().isAdvancements()) { + return; + } + if (!isRuntimePlayer(player.getPlayer())) { + return; + } + + PlayerData data = player.getData(); + + for (AdaptStatTracker tracker : skill.getStatTrackers()) { + if (!data.isGranted(tracker.getAdvancement()) && data.getStat(tracker.getStat()) >= tracker.getGoal()) { + player.getAdvancementHandler().grant(tracker.getAdvancement()); + skill.xp(player.getPlayer(), tracker.getReward()); + } + } + + for (Adaptation adaptation : skill.getAdaptations()) { + if (!(adaptation instanceof SimpleAdaptation simpleAdaptation)) { + continue; + } + if (!adaptation.isEnabled()) { + continue; + } + for (AdaptStatTracker tracker : simpleAdaptation.getStatTrackers()) { + if (!data.isGranted(tracker.getAdvancement()) && data.getStat(tracker.getStat()) >= tracker.getGoal()) { + player.getAdvancementHandler().grant(tracker.getAdvancement()); + skill.xp(player.getPlayer(), tracker.getReward()); + } + } + } + } + + static boolean hasUsePermission(Player player, Skill skill) { + if (player == null || skill == null) { + return false; + } + if (player.isOp()) { + return true; + } + String usePermission = USE_PERMISSION_NODES.computeIfAbsent(skill.getName(), n -> "adapt.use." + n.replace("-", "")); + boolean permissionSet = player.isPermissionSet(usePermission); + if (AdaptConfig.get().isVerbose()) { + Adapt.verbose("Checking use permission " + usePermission + " for " + player.getName() + + " (set=" + permissionSet + ", value=" + player.hasPermission(usePermission) + ")"); + } + if (!permissionSet) { + return true; + } + return player.hasPermission(usePermission); + } + + static boolean shouldSkipPlayer(Skill skill, Player player) { + try { + if (skill == null || player == null) { + return true; + } + + if (J.isFoliaThreading() && !J.isOwnedByCurrentRegion(player)) { + return true; + } + + return AdaptationGate.shouldSkipPlayer(player, skill, skill.getPlayer(player) != null); + } catch (Exception ex) { + Adapt.verbose("Failed shouldSkipPlayer check for " + (player == null ? "null" : player.getName()) + + " in skill " + (skill == null ? "null" : skill.getName()) + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + return true; + } + } + + static void withPlayer(Skill skill, Player player, Runnable runnable) { + try { + if (skill == null || player == null || runnable == null) { + return; + } + + if (J.isFoliaThreading() && !J.isOwnedByCurrentRegion(player)) { + J.runEntity(player, () -> withPlayer(skill, player, runnable)); + return; + } + + if (shouldSkipPlayer(skill, player)) { + return; + } + + runnable.run(); + } catch (Exception ex) { + Adapt.verbose("Failed guarded player runnable for skill " + (skill == null ? "null" : skill.getName()) + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + } + + static void withPlayer(Skill skill, Player player, Cancellable cancellable, Runnable runnable) { + try { + if (skill == null || player == null || cancellable == null || runnable == null) { + return; + } + + if (cancellable.isCancelled()) { + return; + } + + if (J.isFoliaThreading() && !J.isOwnedByCurrentRegion(player)) { + J.runEntity(player, () -> withPlayer(skill, player, cancellable, runnable)); + return; + } + + if (cancellable.isCancelled() || shouldSkipPlayer(skill, player)) { + return; + } + + runnable.run(); + } catch (Exception ex) { + Adapt.verbose("Failed guarded cancellable player runnable for skill " + (skill == null ? "null" : skill.getName()) + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + } + + static boolean shouldSkipWorld(Skill skill, World world) { + try { + return AdaptationGate.shouldSkipWorld(world, skill); + } catch (Exception ex) { + Adapt.verbose("Failed shouldSkipWorld check for skill " + (skill == null ? "null" : skill.getName()) + + ": " + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + return true; + } + } + + static boolean isWorldBlacklisted(Player player) { + return AdaptationGate.isWorldBlacklisted(player); + } + + static boolean isInCreativeOrSpectator(Player player) { + return AdaptationGate.isInCreativeOrSpectator(player); + } + + static boolean canGrantXp(Skill skill, Player player) { + return skill != null && skill.isEnabled() && isRuntimePlayer(player); + } + + static void grantXp(Skill skill, Player player, Location location, double xp, String rewardKey, boolean silent, boolean visualBurst) { + if (!canGrantXp(skill, player)) { + return; + } + try { + xp *= XpNovelty.noveltyMultiplier(player, location, rewardKey); + if (silent) { + XP.xpSilent(player, skill, xp, rewardKey); + } else { + XP.xp(player, skill, xp, rewardKey); + } + + if (visualBurst && location != null && xp > 50) { + skill.vfxXP(player, location, (int) xp); + } + if (AdaptConfig.get().isVerbose()) { + Adapt.verbose("Gave " + player.getName() + " " + xp + " xp in " + skill.getName() + " " + skill.getClass()); + } + } catch (Exception ex) { + Adapt.verbose("Failed to give xp to " + player.getName() + " for " + skill.getName() + " (" + xp + ")"); + } + } + + static void grantXpSilent(Skill skill, Player player, double xp, String rewardKey) { + if (!canGrantXp(skill, player)) { + return; + } + try { + XP.xpSilent(player, skill, xp, rewardKey); + } catch (Exception ignored) { + Adapt.verbose("Player was Given XP (Likely Teleportation) before i can see it because some plugin has higher priority than me and moves a player. so im not going to throw an error, as i know why it's happening."); + } + } + + static void grantKnowledge(Skill skill, Player player, long knowledge) { + if (skill == null || !skill.isEnabled() || player == null) { + return; + } + XP.knowledge(player, skill, knowledge); + } + + static boolean isRuntimePlayer(Player player) { + return player != null && player.getClass().getSimpleName().equals("CraftPlayer"); + } +} diff --git a/src/main/java/art/arcane/adapt/api/telemetry/AbilityCheckTelemetry.java b/src/main/java/art/arcane/adapt/api/telemetry/AbilityCheckTelemetry.java new file mode 100644 index 000000000..716aca478 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/telemetry/AbilityCheckTelemetry.java @@ -0,0 +1,217 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.telemetry; + +import java.util.concurrent.atomic.AtomicLongArray; + +public final class AbilityCheckTelemetry { + private static final int WINDOW_SECONDS = 60; + private static final AtomicLongArray checkOps = new AtomicLongArray(WINDOW_SECONDS); + private static final AtomicLongArray successfulOps = new AtomicLongArray(WINDOW_SECONDS); + private static final AtomicLongArray cacheHits = new AtomicLongArray(WINDOW_SECONDS); + private static final AtomicLongArray cacheMisses = new AtomicLongArray(WINDOW_SECONDS); + private static final AtomicLongArray timingMicros = new AtomicLongArray(WINDOW_SECONDS); + private static final AtomicLongArray timingSamples = new AtomicLongArray(WINDOW_SECONDS); + + private AbilityCheckTelemetry() { + } + + public static void recordCheckAttempt() { + long now = System.currentTimeMillis(); + increment(checkOps, now, 1); + } + + public static void recordSuccessfulCheck() { + long now = System.currentTimeMillis(); + increment(successfulOps, now, 1); + } + + public static void recordCacheHit() { + long now = System.currentTimeMillis(); + increment(cacheHits, now, 1); + } + + public static void recordCacheMiss() { + long now = System.currentTimeMillis(); + increment(cacheMisses, now, 1); + } + + public static void recordCheckTimingNanos(long nanos) { + if (nanos <= 0L) { + return; + } + + long now = System.currentTimeMillis(); + long microsLong = Math.min(Integer.MAX_VALUE, Math.max(1L, nanos / 1_000L)); + increment(timingMicros, now, (int) microsLong); + increment(timingSamples, now, 1); + } + + public static long checksPerMinute(long now) { + return sumWindow(checkOps, now); + } + + public static long successfulChecksPerMinute(long now) { + return sumWindow(successfulOps, now); + } + + public static long checksPerSecond(long now) { + return currentSecondValue(checkOps, now); + } + + public static long successfulChecksPerSecond(long now) { + return currentSecondValue(successfulOps, now); + } + + public static long cacheHitsPerMinute(long now) { + return sumWindow(cacheHits, now); + } + + public static long cacheMissesPerMinute(long now) { + return sumWindow(cacheMisses, now); + } + + public static double cacheHitRatio(long now) { + long hits = cacheHitsPerMinute(now); + long misses = cacheMissesPerMinute(now); + long total = hits + misses; + if (total <= 0L) { + return 0D; + } + + return hits / (double) total; + } + + public static double averageCheckMicros(long now) { + long samples = sumWindow(timingSamples, now); + if (samples <= 0L) { + return 0D; + } + + long micros = sumWindow(timingMicros, now); + return micros / (double) samples; + } + + public static double estimatedTimingMillisPerSecond(long now) { + double checksPerSecond = checksPerSecond(now); + if (checksPerSecond <= 0D) { + return 0D; + } + + double avgMicros = averageCheckMicros(now); + if (avgMicros <= 0D) { + return 0D; + } + + return (checksPerSecond * avgMicros) / 1_000D; + } + + public static double timingBudgetPercent(long now) { + double millisPerSecond = estimatedTimingMillisPerSecond(now); + if (millisPerSecond <= 0D) { + return 0D; + } + + double percent = (millisPerSecond / 50D) * 100D; + if (!Double.isFinite(percent)) { + return 0D; + } + return Math.max(0D, percent); + } + + public static double checksPerTick(long now) { + return checksPerMinute(now) / 1200D; + } + + public static void clear() { + for (int i = 0; i < WINDOW_SECONDS; i++) { + checkOps.set(i, 0L); + successfulOps.set(i, 0L); + cacheHits.set(i, 0L); + cacheMisses.set(i, 0L); + timingMicros.set(i, 0L); + timingSamples.set(i, 0L); + } + } + + private static void increment(AtomicLongArray buckets, long now, int delta) { + if (delta <= 0) { + return; + } + + long epochSecondLong = now / 1_000L; + int epochSecond = (int) epochSecondLong; + int slot = (int) (epochSecondLong % WINDOW_SECONDS); + int safeDelta = Math.max(0, delta); + while (true) { + long packed = buckets.get(slot); + int slotSecond = unpackSecond(packed); + long slotValue = Integer.toUnsignedLong(unpackValue(packed)); + long nextValueLong = slotSecond == epochSecond + ? Math.min(Integer.MAX_VALUE, slotValue + safeDelta) + : Math.min(Integer.MAX_VALUE, safeDelta); + long next = pack(epochSecond, (int) nextValueLong); + if (buckets.compareAndSet(slot, packed, next)) { + return; + } + } + } + + private static long sumWindow(AtomicLongArray buckets, long now) { + long epochSecondLong = now / 1_000L; + int epochSecond = (int) epochSecondLong; + long total = 0L; + for (int i = 0; i < WINDOW_SECONDS; i++) { + long packed = buckets.get(i); + int slotSecond = unpackSecond(packed); + long age = Integer.toUnsignedLong(epochSecond - slotSecond); + if (age >= WINDOW_SECONDS) { + continue; + } + + total += Integer.toUnsignedLong(unpackValue(packed)); + } + return total; + } + + private static long currentSecondValue(AtomicLongArray buckets, long now) { + long epochSecondLong = now / 1_000L; + int epochSecond = (int) epochSecondLong; + int slot = (int) (epochSecondLong % WINDOW_SECONDS); + long packed = buckets.get(slot); + if (unpackSecond(packed) != epochSecond) { + return 0L; + } + return Integer.toUnsignedLong(unpackValue(packed)); + } + + private static long pack(int epochSecond, int value) { + long epochPart = Integer.toUnsignedLong(epochSecond) << 32; + long valuePart = Integer.toUnsignedLong(value); + return epochPart | valuePart; + } + + private static int unpackSecond(long packed) { + return (int) (packed >>> 32); + } + + private static int unpackValue(long packed) { + return (int) packed; + } +} diff --git a/src/main/java/art/arcane/adapt/api/tick/Ticked.java b/src/main/java/art/arcane/adapt/api/tick/Ticked.java new file mode 100644 index 000000000..c281f99d7 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/tick/Ticked.java @@ -0,0 +1,66 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.tick; + +import art.arcane.adapt.api.world.AdaptComponent; +import art.arcane.volmlib.util.math.M; + +public interface Ticked extends AdaptComponent { + default void retick() { + burst(1); + } + + default void skip() { + skip(1); + } + + void unregister(); + + boolean isBursting(); + + boolean isSkipping(); + + void stopBursting(); + + void stopSkipping(); + + long getTickCount(); + + long getAge(); + + void burst(int ticks); + + void skip(int ticks); + + long getLastTick(); + + long getInterval(); + + void setInterval(long ms); + + void tick(); + + String getGroup(); + + String getId(); + + default boolean shouldTick() { + return M.ms() - getLastTick() > getInterval(); + } +} diff --git a/src/main/java/art/arcane/adapt/api/tick/TickedObject.java b/src/main/java/art/arcane/adapt/api/tick/TickedObject.java new file mode 100644 index 000000000..d292a4368 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/tick/TickedObject.java @@ -0,0 +1,303 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.tick; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.math.M; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import java.lang.reflect.Method; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public abstract class TickedObject implements Ticked, Listener { + private static final Set LISTENER_INTROSPECTION_WARNED = ConcurrentHashMap.newKeySet(); + private static final Set FOLIA_TICK_VIOLATION_WARNED = ConcurrentHashMap.newKeySet(); + + private final AtomicLong lastTick; + private final AtomicLong interval; + private final AtomicInteger skip; + private final AtomicInteger burst; + private final AtomicLong ticks; + private final AtomicInteger dieIn; + private final AtomicBoolean die; + private final AtomicBoolean pendingSyncTick; + private final long start; + private final String group; + private final String id; + private final boolean listenerRegistered; + + public TickedObject() { + this("null"); + } + + public TickedObject(String group, String id) { + this(group, id, 1000); + } + + public TickedObject(String group) { + this(group, UUID.randomUUID().toString(), 1000); + } + + public TickedObject(String group, long interval) { + this(group, UUID.randomUUID().toString(), interval); + } + + public TickedObject(String group, String id, long interval) { + this.group = group; + this.id = id; + this.die = new AtomicBoolean(false); + this.dieIn = new AtomicInteger(0); + this.interval = new AtomicLong(interval); + this.lastTick = new AtomicLong(M.ms()); + this.burst = new AtomicInteger(0); + this.skip = new AtomicInteger(0); + this.ticks = new AtomicLong(0); + this.pendingSyncTick = new AtomicBoolean(false); + this.start = M.ms(); + this.listenerRegistered = shouldRegisterAsListener(); + Adapt.instance.getTicker().register(this); + if (listenerRegistered) { + Adapt.instance.registerListener(this); + } + } + + private static boolean hasEventHandlerMethods(Class type) { + Class current = type; + while (current != null && current != Object.class) { + Method[] methods; + try { + methods = current.getDeclaredMethods(); + } catch (Throwable e) { + warnListenerIntrospectionFailure(current, e); + return false; + } + + for (Method method : methods) { + try { + if (method.isAnnotationPresent(EventHandler.class)) { + return true; + } + } catch (Throwable e) { + warnListenerIntrospectionFailure(current, e); + return false; + } + } + current = current.getSuperclass(); + } + return false; + } + + private static void warnListenerIntrospectionFailure(Class type, Throwable error) { + if (type == null) { + return; + } + + String key = type.getName() + ":" + error.getClass().getName() + ":" + (error.getMessage() == null ? "" : error.getMessage()); + if (LISTENER_INTROSPECTION_WARNED.add(key)) { + Adapt.warn("Skipping listener registration for " + type.getName() + " due to missing/incompatible event class: " + error.getClass().getSimpleName() + (error.getMessage() == null ? "" : " (" + error.getMessage() + ")")); + } + } + + public void dieAfter(int ticks) { + dieIn.set(ticks); + die.set(true); + } + + @Override + public void unregister() { + Adapt.instance.getTicker().unregister(this); + if (listenerRegistered) { + Adapt.instance.unregisterListener(this); + } + } + + @Override + public long getLastTick() { + return lastTick.get(); + } + + @Override + public long getInterval() { + if (burst.get() > 0) { + return 0; + } + + return interval.get(); + } + + @Override + public void setInterval(long ms) { + interval.set(ms); + } + + @Override + public void tick() { + if (!J.isPrimaryThread()) { + if (pendingSyncTick.compareAndSet(false, true)) { + J.s(() -> { + try { + tick(); + } finally { + pendingSyncTick.set(false); + } + }); + } + return; + } + + if (skip.getAndDecrement() > 0) { + return; + } + + if (die.get() && dieIn.decrementAndGet() <= 0) { + unregister(); + return; + } + + lastTick.set(M.ms()); + burst.decrementAndGet(); + try { + onTick(); + } catch (IllegalStateException ex) { + if (J.isFoliaThreading() && isFoliaThreadOwnershipViolation(ex)) { + warnFoliaTickViolation(ex); + return; + } + throw ex; + } catch (NullPointerException ex) { + if (J.isFoliaThreading() && isFoliaTransientWorldStateNpe(ex)) { + warnFoliaTickViolation(ex); + return; + } + throw ex; + } + } + + public abstract void onTick(); + + protected boolean shouldRegisterAsListener() { + try { + return hasEventHandlerMethods(getClass()); + } catch (Throwable e) { + warnListenerIntrospectionFailure(getClass(), e); + return false; + } + } + + @Override + public String getGroup() { + return group; + } + + @Override + public String getId() { + return id; + } + + @Override + public long getTickCount() { + return ticks.get(); + } + + @Override + public long getAge() { + return M.ms() - start; + } + + @Override + public boolean isBursting() { + return burst.get() > 0; + } + + @Override + public void burst(int ticks) { + if (burst.get() < 0) { + burst.set(ticks); + return; + } + + burst.addAndGet(ticks); + } + + @Override + public boolean isSkipping() { + return skip.get() > 0; + } + + @Override + public void stopBursting() { + burst.set(0); + } + + @Override + public void stopSkipping() { + skip.set(0); + } + + @Override + public void skip(int ticks) { + if (skip.get() < 0) { + skip.set(ticks); + return; + } + + skip.addAndGet(ticks); + } + + private boolean isFoliaThreadOwnershipViolation(Throwable throwable) { + if (throwable == null) { + return false; + } + + String message = throwable.getMessage(); + if (message == null) { + return false; + } + + String lower = message.toLowerCase(Locale.ROOT); + return lower.contains("thread failed main thread check") + || lower.contains("cannot read world asynchronously") + || lower.contains("accessing entity state off owning region"); + } + + private boolean isFoliaTransientWorldStateNpe(NullPointerException throwable) { + if (throwable == null || throwable.getMessage() == null) { + return false; + } + + String lower = throwable.getMessage().toLowerCase(Locale.ROOT); + return lower.contains("getcurrentworlddata"); + } + + private void warnFoliaTickViolation(Throwable throwable) { + String message = throwable == null || throwable.getMessage() == null ? throwable.getClass().getSimpleName() : throwable.getMessage(); + String key = getClass().getName() + ":" + throwable.getClass().getName() + ":" + message; + if (FOLIA_TICK_VIOLATION_WARNED.add(key)) { + Adapt.warn("Suppressed unsafe Folia tick execution in " + getClass().getName() + ": " + message); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/tick/Ticker.java b/src/main/java/art/arcane/adapt/api/tick/Ticker.java new file mode 100644 index 000000000..b9eb42a57 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/tick/Ticker.java @@ -0,0 +1,191 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.tick; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.collection.KList; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +public class Ticker { + private final KList ticklist; + private final KList newTicks; + private final KList removeTicks; + private final Map metrics; + private final AtomicLong windowStartMs; + private volatile boolean ticking; + + public Ticker() { + this.ticklist = new KList<>(4096); + this.newTicks = new KList<>(128); + this.removeTicks = new KList<>(128); + this.metrics = new ConcurrentHashMap<>(); + this.windowStartMs = new AtomicLong(System.currentTimeMillis()); + ticking = false; + J.sr(() -> { + if (!ticking) { + tick(); + } + }, 1); + } + + public void register(Ticked ticked) { + synchronized (newTicks) { + newTicks.add(ticked); + } + } + + public void unregister(Ticked ticked) { + synchronized (removeTicks) { + removeTicks.add(ticked.getId()); + } + } + + public void clear() { + synchronized (ticklist) { + ticklist.clear(); + } + synchronized (removeTicks) { + removeTicks.clear(); + } + synchronized (newTicks) { + newTicks.clear(); + } + metrics.clear(); + windowStartMs.set(System.currentTimeMillis()); + + } + + public void resetMetrics() { + metrics.clear(); + windowStartMs.set(System.currentTimeMillis()); + } + + public long getMetricsWindowMs() { + return Math.max(0, System.currentTimeMillis() - windowStartMs.get()); + } + + public double getWindowLoadPercent() { + long windowMs = getMetricsWindowMs(); + if (windowMs <= 0L) { + return 0D; + } + + double totalMs = 0D; + for (TickMetric metric : metrics.values()) { + totalMs += metric.totalNanos.get() / 1_000_000D; + } + double percent = (totalMs / (double) windowMs) * 100D; + if (!Double.isFinite(percent)) { + return 0D; + } + + return Math.max(0D, percent); + } + + public List topMetrics(int limit) { + int safeLimit = Math.max(1, limit); + ArrayList entries = new ArrayList<>(metrics.values()); + entries.sort(Comparator.comparingLong((TickMetric m) -> m.totalNanos.get()).reversed()); + + int outputSize = Math.min(safeLimit, entries.size()); + ArrayList top = new ArrayList<>(outputSize); + for (int i = 0; i < outputSize; i++) { + TickMetric metric = entries.get(i); + top.add(formatMetric(metric.label, metric)); + } + return top; + } + + private void tick() { + ticking = true; + for (int i = 0; i < ticklist.size(); i++) { + Ticked t = ticklist.get(i); + if (t != null && t.shouldTick()) { + long start = System.nanoTime(); + try { + t.tick(); + } catch (Throwable exxx) { + Adapt.error("Exception ticking " + t.getGroup() + ":" + t.getId()); + exxx.printStackTrace(); + } finally { + recordMetric(t, System.nanoTime() - start); + } + } + } + + synchronized (newTicks) { + while (newTicks.isNotEmpty()) { + ticklist.add(newTicks.popRandom()); + } + } + + synchronized (removeTicks) { + if (removeTicks.isNotEmpty()) { + Set idsToRemove = new HashSet<>(removeTicks); + removeTicks.clear(); + ticklist.removeIf(t -> { + if (t != null && idsToRemove.contains(t.getId())) { + metrics.remove(t); + return true; + } + return false; + }); + } + } + + ticking = false; + } + + private void recordMetric(Ticked ticked, long durationNs) { + if (ticked == null || durationNs < 0) { + return; + } + + TickMetric metric = metrics.computeIfAbsent(ticked, t -> new TickMetric(t.getGroup() + ":" + t.getId())); + metric.calls.incrementAndGet(); + metric.totalNanos.addAndGet(durationNs); + metric.maxNanos.updateAndGet(old -> Math.max(old, durationNs)); + } + + private String formatMetric(String key, TickMetric metric) { + long calls = Math.max(1, metric.calls.get()); + double totalMs = metric.totalNanos.get() / 1_000_000D; + double avgMs = totalMs / (double) calls; + double maxMs = metric.maxNanos.get() / 1_000_000D; + return key + " total=" + String.format(Locale.US, "%.3fms", totalMs) + + " avg=" + String.format(Locale.US, "%.3fms", avgMs) + + " max=" + String.format(Locale.US, "%.3fms", maxMs) + + " calls=" + calls; + } + + private static class TickMetric { + private final String label; + private final AtomicLong calls = new AtomicLong(); + private final AtomicLong totalNanos = new AtomicLong(); + private final AtomicLong maxNanos = new AtomicLong(); + + private TickMetric(String label) { + this.label = label; + } + } +} diff --git a/src/main/java/com/volmit/adapt/api/value/MaterialCount.java b/src/main/java/art/arcane/adapt/api/value/MaterialCount.java similarity index 92% rename from src/main/java/com/volmit/adapt/api/value/MaterialCount.java rename to src/main/java/art/arcane/adapt/api/value/MaterialCount.java index 238de8fe8..da2bc414e 100644 --- a/src/main/java/com/volmit/adapt/api/value/MaterialCount.java +++ b/src/main/java/art/arcane/adapt/api/value/MaterialCount.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.value; +package art.arcane.adapt.api.value; import lombok.AllArgsConstructor; import lombok.Data; @@ -25,6 +25,6 @@ @AllArgsConstructor @Data public class MaterialCount { - private Material material; - private int amount; + private Material material; + private int amount; } diff --git a/src/main/java/com/volmit/adapt/api/value/MaterialRecipe.java b/src/main/java/art/arcane/adapt/api/value/MaterialRecipe.java similarity index 91% rename from src/main/java/com/volmit/adapt/api/value/MaterialRecipe.java rename to src/main/java/art/arcane/adapt/api/value/MaterialRecipe.java index e50e1c052..da30c1627 100644 --- a/src/main/java/com/volmit/adapt/api/value/MaterialRecipe.java +++ b/src/main/java/art/arcane/adapt/api/value/MaterialRecipe.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.value; +package art.arcane.adapt.api.value; import lombok.AllArgsConstructor; import lombok.Builder; @@ -28,6 +28,6 @@ @Builder @Data public class MaterialRecipe { - private List input; - private MaterialCount output; + private List input; + private MaterialCount output; } diff --git a/src/main/java/art/arcane/adapt/api/value/MaterialValue.java b/src/main/java/art/arcane/adapt/api/value/MaterialValue.java new file mode 100644 index 000000000..82711893e --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/value/MaterialValue.java @@ -0,0 +1,256 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.value; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.util.common.io.Json; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.io.IO; +import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +@Getter +public class MaterialValue { + private static final Map valueMultipliers = new HashMap<>(); + private static MaterialValue valueCache = null; + + static { + AdaptConfig.get().getValue().getValueMutlipliers().forEach((k, v) -> { + try { + Material m = Material.valueOf(k.toUpperCase()); + valueMultipliers.put(m, v); + } catch (Exception e) { + Adapt.verbose("Invalid material value multiplier: " + k); + } + }); + } + + private final Map value = new HashMap<>(); + + public static void save() { + if (valueCache == null) { + return; + } + + File l = Adapt.instance.getDataFile("data", "value-cache.json"); + try { + IO.writeAll(l, Json.toJson(valueCache, true)); + } catch (IOException e) { + Adapt.verbose("Failed to save value cache"); + } + } + + public static MaterialValue get() { + if (valueCache == null) { + MaterialValue dummy = new MaterialValue(); + File l = Adapt.instance.getDataFile("data", "value-cache.json"); + + if (!l.exists()) { + try { + IO.writeAll(l, Json.toJson(dummy, true)); + } catch (IOException e) { + e.printStackTrace(); + valueCache = dummy; + return dummy; + } + } + + try { + valueCache = Json.fromJson(IO.readAll(l), MaterialValue.class); + } catch (IOException e) { + e.printStackTrace(); + valueCache = new MaterialValue(); + } + } + + return valueCache; + } + + public static void debugValue(Material m) { + debugValue(m, 0, 1, new HashSet<>()); + } + + private static void debugValue(Material m, int ind, int x, Set ignore) { + PrecisionStopwatch p = PrecisionStopwatch.start(); + Adapt.verbose(Form.repeat(" ", ind) + m.name() + ": " + getValue(m) + (x == 1 ? "" : " (x" + x + ")")); + + int r = 0; + for (MaterialRecipe i : getRecipes(m)) { + if (ignore.contains(i)) { + continue; + } + + ignore.add(i); + if (ignore.size() > AdaptConfig.get().getMaxRecipeListPrecaution()) { + Adapt.verbose("Avoiding infinite loop"); + return; + } + + int o = i.getOutput().getAmount(); + Adapt.verbose(Form.repeat(" ", ind) + "# Recipe [" + ind + "x" + r + (o == 1 ? "]" : "] (x" + o + ") ")); + + for (MaterialCount j : i.getInput()) { + debugValue(j.getMaterial(), ind + 1, j.getAmount(), ignore); + } + + r++; + } + Adapt.verbose(Form.repeat(" ", ind) + " took " + Form.duration(p.getMilliseconds(), 0)); + } + + private static double getMultiplier(Material m) { + Double d = AdaptConfig.get().getValue().getValueMutlipliers().get(m); + return d == null ? 1 : d; + } + + public static double getValue(Material m) { + try { + return getValue(m, new HashSet<>()); + } catch (Exception ignored) { + return 1; + } + } + + private static double getValue(Material m, Set ignore) { + if (get().value.containsKey(m)) { + if (m.isBlock() && m.getHardness() == 0) { + return 0; + } + return get().value.get(m); + } + double v = AdaptConfig.get().getValue().getBaseValue(); + List recipes = getRecipes(m); + if (recipes.isEmpty()) { + get().value.put(m, v * getMultiplier(m)); // No recipes, just use base value, if no base value then 1 + } else { + List d = new ArrayList<>(); + for (MaterialRecipe i : recipes) { + if (ignore.contains(i)) { + continue; + } + ignore.add(i); + double vx = v; + for (MaterialCount j : i.getInput()) { + vx += getValue(j.getMaterial(), ignore); + } + d.add(vx / i.getOutput().getAmount()); + } + if (d.size() > 0) { + v += d.stream().mapToDouble(i -> i).average().getAsDouble(); + } + if (v > AdaptConfig.get().getMaxRecipeListPrecaution()) { + get().value.put(m, (v / 10 + 1) * getMultiplier(m)); + } else { + get().value.put(m, v); + } + + } + if (m.isBlock() && m.getHardness() == 0) { + return 0; + } + return get().value.get(m); + } + + private static List getRecipes(Material mat) { + List r = new ArrayList<>(); + try { + ItemStack is = new ItemStack(mat); + try { + is.setDurability((short) -1); + } catch (Throwable e) { + Adapt.verbose("Failed to set durability of " + mat.name()); + } + Bukkit.getRecipesFor(is).forEach(i -> { + if (i instanceof AdaptRecipe) { + Adapt.verbose("Skipping Adapt Recipe to prevent duplicates, " + mat.name() + " -> " + ((AdaptRecipe) i).getKey() + ""); + return; + } + MaterialRecipe rx = toMaterial(i); + if (rx != null) { + r.add(rx); + } + }); + } catch (Throwable e) { + Adapt.verbose("Failed to get recipes for " + mat.name()); + } + return r; + } + + private static MaterialRecipe toMaterial(Recipe r) { + try { + if (r instanceof ShapelessRecipe recipe) { + return MaterialRecipe.builder() + .input(new ArrayList<>(recipe.getIngredientList().stream().map(i -> new MaterialCount(i.getType(), 1)).toList())) + .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) + .build(); + } else if (r instanceof ShapedRecipe recipe) { + MaterialRecipe re = MaterialRecipe.builder() + .input(new ArrayList<>()) + .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) + .build(); + Map f = new HashMap<>(); + for (ItemStack i : recipe.getIngredientMap().values()) { + if (i == null || i.getType().isAir()) { + continue; + } + + f.compute(i.getType(), (k, v) -> v == null ? 1 : v + 1); + } + + f.forEach((k, v) -> re.getInput().add(new MaterialCount(k, v))); + + return re; + } else if (r instanceof CookingRecipe recipe) { + List a = new ArrayList<>(); + a.add(new MaterialCount(recipe.getInput().getType(), 1)); + + return MaterialRecipe.builder() + .input(a) + .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) + .build(); + } else if (r instanceof MerchantRecipe recipe) { + return MaterialRecipe.builder() + .input(new ArrayList<>(recipe.getIngredients().stream().map(i -> new MaterialCount(i.getType(), 1)).toList())) + .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) + .build(); + } else if (r instanceof StonecuttingRecipe recipe) { + List a = new ArrayList<>(); + a.add(new MaterialCount(recipe.getInput().getType(), 1)); + + return MaterialRecipe.builder() + .input(a) + .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) + .build(); + } + } catch (Throwable e) { + e.printStackTrace(); + } + + return null; + } +} diff --git a/src/main/java/art/arcane/adapt/api/version/IAttribute.java b/src/main/java/art/arcane/adapt/api/version/IAttribute.java new file mode 100644 index 000000000..0adcfd3fd --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/version/IAttribute.java @@ -0,0 +1,56 @@ +package art.arcane.adapt.api.version; + +import art.arcane.volmlib.util.collection.KList; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.AttributeModifier; + +import java.util.Optional; +import java.util.UUID; + +public interface IAttribute { + + double getValue(); + + double getDefaultValue(); + + double getBaseValue(); + + void setBaseValue(double baseValue); + + default void setModifier(UUID uuid, NamespacedKey key, double amount, AttributeModifier.Operation operation) { + removeModifier(uuid, key); + addModifier(uuid, key, amount, operation); + } + + void addModifier(UUID uuid, NamespacedKey key, double amount, AttributeModifier.Operation operation); + + boolean hasModifier(UUID uuid, NamespacedKey key); + + void removeModifier(UUID uuid, NamespacedKey key); + + KList getModifier(UUID uuid, NamespacedKey key); + + @ToString + @EqualsAndHashCode + @AllArgsConstructor + class Modifier { + private final UUID uuid; + private final NamespacedKey key; + @Getter + private final double amount; + @Getter + private final AttributeModifier.Operation operation; + + public Optional getUUID() { + return Optional.ofNullable(uuid); + } + + public Optional getKey() { + return Optional.ofNullable(key); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/version/IBindings.java b/src/main/java/art/arcane/adapt/api/version/IBindings.java new file mode 100644 index 000000000..30961ee12 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/version/IBindings.java @@ -0,0 +1,77 @@ +package art.arcane.adapt.api.version; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.api.potion.PotionBuilder.Type; +import art.arcane.adapt.util.common.misc.CustomModel; +import org.bukkit.attribute.Attributable; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.EntityType; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.List; + +public interface IBindings extends Listener { + + default void applyModel(CustomModel model, ItemMeta meta) { + meta.setCustomModelData(model.model()); + } + + IAttribute getAttribute(Attributable attributable, Attribute modifier); + + default ItemStack buildPotion(PotionBuilder builder) { + ItemStack stack = builder.getBaseItem(); + if (stack == null) stack = new ItemStack(builder.getType().getMaterial()); + else if (stack.getType() != builder.getType().getMaterial()) + stack.setType(builder.getType().getMaterial()); + PotionMeta meta = (PotionMeta) stack.getItemMeta(); + assert meta != null; + meta.clearCustomEffects(); + builder.getEffects().forEach(e -> meta.addCustomEffect(e, true)); + if (builder.getColor() != null) + meta.setColor(builder.getColor()); + stack.setItemMeta(meta); + + Adapt.platform.editItem(stack) + .lore(builder.getLore()) + .customName(builder.getName()) + .build(); + return stack; + } + + default PotionBuilder editPotion(ItemStack stack) { + Type type = null; + for (final art.arcane.adapt.api.potion.PotionBuilder.Type val : Type.values()) { + if (val.getMaterial() == stack.getType()) { + type = val; + break; + } + } + + if (type == null) { + throw new IllegalArgumentException("Invalid potion type!"); + } + final de.crazydev22.platformutils.ItemEditor editor = Adapt.platform.editItem(stack); + final art.arcane.adapt.api.potion.PotionBuilder builder = PotionBuilder.of(type) + .setBaseItem(stack); + + PotionMeta meta = (PotionMeta) stack.getItemMeta(); + assert meta != null; + builder.setBaseType(meta.getBasePotionType()) + .setLore(editor.lore()) + .setColor(meta.getColor()) + .setName(editor.customName()); + for (org.bukkit.potion.PotionEffect effect : meta.getCustomEffects()) { + builder.addEffect(effect); + } + + return builder; + } + + @Unmodifiable + List getInvalidDamageableEntities(); +} diff --git a/src/main/java/art/arcane/adapt/api/version/RuntimeAttribute.java b/src/main/java/art/arcane/adapt/api/version/RuntimeAttribute.java new file mode 100644 index 000000000..718cc510e --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/version/RuntimeAttribute.java @@ -0,0 +1,250 @@ +package art.arcane.adapt.api.version; + +import art.arcane.volmlib.util.collection.KList; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.attribute.AttributeModifier; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public record RuntimeAttribute( + AttributeInstance instance) implements IAttribute { + private static final Method GET_KEY_METHOD = findMethod("getKey"); + private static final Method GET_UUID_METHOD = findMethod("getUniqueId"); + private static final Method GET_NAME_METHOD = findMethod("getName"); + + private static AttributeModifier createModifier(UUID uuid, NamespacedKey key, double amount, AttributeModifier.Operation operation) { + for (Constructor constructor : AttributeModifier.class.getConstructors()) { + AttributeModifier modifier = tryCreateKeyed(constructor, key, amount, operation); + if (modifier != null) { + return modifier; + } + } + + String legacyName = key.getNamespace() + "-" + key.getKey(); + for (Constructor constructor : AttributeModifier.class.getConstructors()) { + AttributeModifier modifier = tryCreateLegacy(constructor, uuid, legacyName, amount, operation); + if (modifier != null) { + return modifier; + } + } + + throw new IllegalStateException("No compatible AttributeModifier constructor found"); + } + + private static AttributeModifier tryCreateKeyed(Constructor constructor, NamespacedKey key, double amount, AttributeModifier.Operation operation) { + Class[] params = constructor.getParameterTypes(); + if (params.length < 3 || params.length > 4 || params[0] != NamespacedKey.class || params[1] != double.class || params[2] != AttributeModifier.Operation.class) { + return null; + } + + Object[] args = new Object[params.length]; + args[0] = key; + args[1] = amount; + args[2] = operation; + if (params.length == 4) { + Object slot = resolveEnum(params[3]); + if (slot == null) { + return null; + } + args[3] = slot; + } + + try { + return (AttributeModifier) constructor.newInstance(args); + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + private static AttributeModifier tryCreateLegacy(Constructor constructor, UUID uuid, String name, double amount, AttributeModifier.Operation operation) { + Class[] params = constructor.getParameterTypes(); + if (params.length < 4 || params.length > 5 || params[0] != UUID.class || params[1] != String.class || params[2] != double.class || params[3] != AttributeModifier.Operation.class) { + return null; + } + + Object[] args = new Object[params.length]; + args[0] = uuid; + args[1] = name; + args[2] = amount; + args[3] = operation; + if (params.length == 5) { + Object slot = resolveEnum(params[4]); + if (slot == null) { + return null; + } + args[4] = slot; + } + + try { + return (AttributeModifier) constructor.newInstance(args); + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + private static boolean matches(AttributeModifier modifier, UUID uuid, NamespacedKey key) { + NamespacedKey modifierKey = readKey(modifier); + if (modifierKey != null && modifierKey.equals(key)) { + return true; + } + + UUID modifierUuid = readUuid(modifier); + if (modifierUuid != null && modifierUuid.equals(uuid)) { + return true; + } + + String modifierName = readName(modifier); + return modifierName != null && modifierName.equals(key.getNamespace() + "-" + key.getKey()); + } + + private static Modifier wrap(AttributeModifier modifier) { + return new Modifier(readUuid(modifier), readKey(modifier), modifier.getAmount(), modifier.getOperation()); + } + + private static NamespacedKey readKey(AttributeModifier modifier) { + if (GET_KEY_METHOD == null) { + return null; + } + + try { + return (NamespacedKey) GET_KEY_METHOD.invoke(modifier); + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + private static UUID readUuid(AttributeModifier modifier) { + if (GET_UUID_METHOD == null) { + return null; + } + + try { + return (UUID) GET_UUID_METHOD.invoke(modifier); + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + private static String readName(AttributeModifier modifier) { + if (GET_NAME_METHOD == null) { + return null; + } + + try { + return (String) GET_NAME_METHOD.invoke(modifier); + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + private static Method findMethod(String methodName) { + try { + return AttributeModifier.class.getMethod(methodName); + } catch (NoSuchMethodException ignored) { + return null; + } + } + + private static Object resolveEnum(Class type) { + if (!type.isEnum()) { + return null; + } + + Object any = enumConstant(type, "ANY"); + if (any != null) { + return any; + } + + Object hand = enumConstant(type, "HAND"); + if (hand != null) { + return hand; + } + + Object[] constants = type.getEnumConstants(); + return constants == null || constants.length == 0 ? null : constants[0]; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static Object enumConstant(Class type, String name) { + try { + return Enum.valueOf((Class) type, name); + } catch (IllegalArgumentException ignored) { + return null; + } + } + + @Override + public double getValue() { + return instance.getValue(); + } + + @Override + public double getDefaultValue() { + return instance.getDefaultValue(); + } + + @Override + public double getBaseValue() { + return instance.getBaseValue(); + } + + @Override + public void setBaseValue(double baseValue) { + instance.setBaseValue(baseValue); + } + + @Override + public void addModifier(UUID uuid, NamespacedKey key, double amount, AttributeModifier.Operation operation) { + instance.addModifier(createModifier(uuid, key, amount, operation)); + } + + @Override + public boolean hasModifier(UUID uuid, NamespacedKey key) { + for (AttributeModifier modifier : instance.getModifiers()) { + if (matches(modifier, uuid, key)) { + return true; + } + } + + return false; + } + + @Override + public void removeModifier(UUID uuid, NamespacedKey key) { + List toRemove = null; + for (AttributeModifier modifier : instance.getModifiers()) { + if (!matches(modifier, uuid, key)) { + continue; + } + + if (toRemove == null) { + toRemove = new ArrayList<>(); + } + toRemove.add(modifier); + } + + if (toRemove == null) { + return; + } + + for (AttributeModifier modifier : toRemove) { + instance.removeModifier(modifier); + } + } + + @Override + public KList getModifier(UUID uuid, NamespacedKey key) { + KList modifiers = new KList<>(); + for (AttributeModifier modifier : instance.getModifiers()) { + if (matches(modifier, uuid, key)) { + modifiers.add(wrap(modifier)); + } + } + return modifiers; + } +} diff --git a/src/main/java/art/arcane/adapt/api/version/RuntimeBindings.java b/src/main/java/art/arcane/adapt/api/version/RuntimeBindings.java new file mode 100644 index 000000000..3325d0a98 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/version/RuntimeBindings.java @@ -0,0 +1,131 @@ +package art.arcane.adapt.api.version; + +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.util.common.misc.CustomModel; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.Attributable; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionType; +import org.jetbrains.annotations.Unmodifiable; + +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class RuntimeBindings implements IBindings { + private static final Method SET_ITEM_MODEL_METHOD = findMethod(ItemMeta.class, "setItemModel", NamespacedKey.class); + private static final Method SET_BASE_POTION_TYPE_METHOD = findMethod(PotionMeta.class, "setBasePotionType", PotionType.class); + private static final List INVALID_DAMAGEABLE_ENTITIES = detectInvalidDamageableEntities(); + + private static List detectInvalidDamageableEntities() { + Set entities = new LinkedHashSet<>(); + + addIfPresent(entities, + "ARMOR_STAND", + "ITEM_FRAME", + "GLOW_ITEM_FRAME", + "PAINTING", + "LEASH_HITCH", + "LEASH_KNOT", + "EVOKER_FANGS", + "MARKER", + "BOAT", + "CHEST_BOAT", + "MINECART" + ); + addByPrefix(entities, "MINECART_"); + addBySuffix(entities, "_MINECART"); + addBySuffix(entities, "_BOAT"); + addBySuffix(entities, "_CHEST_BOAT"); + addBySuffix(entities, "_RAFT"); + addBySuffix(entities, "_CHEST_RAFT"); + + return List.copyOf(entities); + } + + private static void addIfPresent(Set entities, String... names) { + for (String name : names) { + try { + entities.add(EntityType.valueOf(name)); + } catch (IllegalArgumentException ignored) { + // Entity was renamed/removed in this API version. + } + } + } + + private static void addByPrefix(Set entities, String prefix) { + for (EntityType entity : EntityType.values()) { + if (entity.name().startsWith(prefix)) { + entities.add(entity); + } + } + } + + private static void addBySuffix(Set entities, String suffix) { + for (EntityType entity : EntityType.values()) { + if (entity.name().endsWith(suffix)) { + entities.add(entity); + } + } + } + + private static Method findMethod(Class type, String name, Class... parameters) { + try { + return type.getMethod(name, parameters); + } catch (NoSuchMethodException ignored) { + return null; + } + } + + @Override + public void applyModel(CustomModel model, ItemMeta meta) { + NamespacedKey modelKey = model.modelKey(); + if (modelKey != null && !CustomModel.EMPTY_KEY.equals(modelKey) && SET_ITEM_MODEL_METHOD != null) { + try { + SET_ITEM_MODEL_METHOD.invoke(meta, modelKey); + return; + } catch (ReflectiveOperationException ignored) { + // Fallback is custom model data for older API variants. + } + } + + meta.setCustomModelData(model.model()); + } + + @Override + public IAttribute getAttribute(Attributable attributable, Attribute modifier) { + return Optional.ofNullable(attributable.getAttribute(modifier)) + .map(RuntimeAttribute::new) + .orElse(null); + } + + @Override + public ItemStack buildPotion(PotionBuilder builder) { + ItemStack stack = IBindings.super.buildPotion(builder); + PotionMeta meta = (PotionMeta) stack.getItemMeta(); + if (meta == null || builder.getBaseType() == null || SET_BASE_POTION_TYPE_METHOD == null) { + return stack; + } + + try { + SET_BASE_POTION_TYPE_METHOD.invoke(meta, builder.getBaseType()); + stack.setItemMeta(meta); + } catch (ReflectiveOperationException ignored) { + // Older APIs may not expose base potion type mutators. + } + + return stack; + } + + @Override + @Unmodifiable + public List getInvalidDamageableEntities() { + return INVALID_DAMAGEABLE_ENTITIES; + } +} diff --git a/src/main/java/art/arcane/adapt/api/version/Version.java b/src/main/java/art/arcane/adapt/api/version/Version.java new file mode 100644 index 000000000..818958cb1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/version/Version.java @@ -0,0 +1,22 @@ +package art.arcane.adapt.api.version; + +import org.bukkit.inventory.InventoryView; + +public class Version { + public static final boolean SET_TITLE; + private static final IBindings bindings = new RuntimeBindings(); + + static { + boolean titleMethod = false; + try { + InventoryView.class.getDeclaredMethod("setTitle", String.class); + titleMethod = true; + } catch (Throwable ignored) { + } + SET_TITLE = titleMethod; + } + + public static IBindings get() { + return bindings; + } +} diff --git a/src/main/java/art/arcane/adapt/api/world/AdaptComponent.java b/src/main/java/art/arcane/adapt/api/world/AdaptComponent.java new file mode 100644 index 000000000..a8a608ad0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/world/AdaptComponent.java @@ -0,0 +1,268 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.world; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.reflect.registries.Materials; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +import static org.bukkit.Material.*; + +public interface AdaptComponent { + default AdaptServer getServer() { + return Adapt.instance.getAdaptServer(); + } + + default AdaptPlayer getPlayer(Player p) { + return getServer().getPlayer(p); + } + + default boolean isItem(ItemStack is) { + return is != null && !is.getType().equals(Material.AIR); + } + + default boolean isTool(ItemStack is) { + return isAxe(is) || isPickaxe(is) || isHoe(is) || isShovel(is) || isSword(is) || isTrident(is) || isMace(is); + } + + default boolean isMelee(ItemStack is) { + return isTool(is); + } + + default boolean isMace(ItemStack is) { + return is.getType() == Materials.MACE; + } + + default boolean isShield(ItemStack is) { + return is.getType().equals(Material.SHIELD); + } + + default boolean isXpBlock(Material material) { + return material.equals(Material.EXPERIENCE_BOTTLE); + } + + default boolean isRanged(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case BOW, CROSSBOW -> true; + default -> false; + }; + } + + return false; + } + + default boolean isSword(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case DIAMOND_SWORD, GOLDEN_SWORD, IRON_SWORD, NETHERITE_SWORD, + STONE_SWORD, WOODEN_SWORD -> true; + default -> false; + }; + } + + return false; + } + + default boolean isTrident(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case TRIDENT, SEA_PICKLE -> true; + default -> false; + }; + } + + return false; + } + + default boolean isAxe(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case DIAMOND_AXE, GOLDEN_AXE, IRON_AXE, NETHERITE_AXE, STONE_AXE, + WOODEN_AXE -> true; + default -> false; + }; + } + + return false; + } + + default boolean isPickaxe(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case DIAMOND_PICKAXE, GOLDEN_PICKAXE, IRON_PICKAXE, NETHERITE_PICKAXE, + STONE_PICKAXE, WOODEN_PICKAXE -> true; + default -> false; + }; + } + + return false; + } + + default boolean isShovel(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case DIAMOND_SHOVEL, GOLDEN_SHOVEL, IRON_SHOVEL, NETHERITE_SHOVEL, + STONE_SHOVEL, WOODEN_SHOVEL -> true; + default -> false; + }; + } + return false; + } + + default boolean isLog(ItemStack it) { + if (isItem(it)) { + return List.of(MUSHROOM_STEM, BROWN_MUSHROOM_BLOCK, RED_MUSHROOM_BLOCK, MANGROVE_ROOTS, MUDDY_MANGROVE_ROOTS).contains(it.getType()) + || it.getType().name().endsWith("_LOG") + || it.getType().name().endsWith("_WOOD"); + } + + return false; + } + + default boolean isLeaves(ItemStack it) { + if (isItem(it)) { + return List.of(Material.MANGROVE_ROOTS, Material.MUDDY_MANGROVE_ROOTS).contains(it.getType()) + || it.getType().name().endsWith("_LEAVES"); + } + + return false; + } + + default boolean isBoots(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case DIAMOND_BOOTS, GOLDEN_BOOTS, IRON_BOOTS, NETHERITE_BOOTS, + CHAINMAIL_BOOTS, LEATHER_BOOTS -> true; + default -> false; + }; + } + + return false; + } + + default boolean isHelmet(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case CHAINMAIL_HELMET, DIAMOND_HELMET, GOLDEN_HELMET, IRON_HELMET, + LEATHER_HELMET, NETHERITE_HELMET, TURTLE_HELMET -> true; + default -> false; + }; + } + + return false; + } + + default boolean isLeggings(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case DIAMOND_LEGGINGS, GOLDEN_LEGGINGS, IRON_LEGGINGS, + NETHERITE_LEGGINGS, CHAINMAIL_LEGGINGS, LEATHER_LEGGINGS -> true; + default -> false; + }; + } + + return false; + } + + default boolean isChestplate(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case DIAMOND_CHESTPLATE, GOLDEN_CHESTPLATE, IRON_CHESTPLATE, + NETHERITE_CHESTPLATE, CHAINMAIL_CHESTPLATE, LEATHER_CHESTPLATE -> + true; + default -> false; + }; + } + + return false; + } + + default boolean isElytra(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case ELYTRA, LEGACY_ELYTRA -> true; + default -> false; + }; + } + + return false; + } + + default boolean isHoe(ItemStack it) { + if (isItem(it)) { + return switch (it.getType()) { + case DIAMOND_HOE, GOLDEN_HOE, IRON_HOE, NETHERITE_HOE, STONE_HOE, + WOODEN_HOE -> true; + default -> false; + }; + } + + return false; + } + + default boolean isOre(BlockData b) { + return switch (b.getMaterial()) { + case COPPER_ORE, DEEPSLATE_COPPER_ORE, COAL_ORE, GOLD_ORE, IRON_ORE, + DIAMOND_ORE, LAPIS_ORE, EMERALD_ORE, NETHER_QUARTZ_ORE, + NETHER_GOLD_ORE, REDSTONE_ORE, DEEPSLATE_COAL_ORE, + DEEPSLATE_IRON_ORE, DEEPSLATE_GOLD_ORE, DEEPSLATE_LAPIS_ORE, + DEEPSLATE_DIAMOND_ORE, DEEPSLATE_EMERALD_ORE, + DEEPSLATE_REDSTONE_ORE -> true; + default -> false; + }; + } + + default boolean isStorage(BlockData b) { + return switch (b.getMaterial()) { + case CHEST, + SMOKER, + TRAPPED_CHEST, + SHULKER_BOX, + WHITE_SHULKER_BOX, + ORANGE_SHULKER_BOX, + MAGENTA_SHULKER_BOX, + LIGHT_BLUE_SHULKER_BOX, + YELLOW_SHULKER_BOX, + LIME_SHULKER_BOX, + PINK_SHULKER_BOX, + GRAY_SHULKER_BOX, + LIGHT_GRAY_SHULKER_BOX, + CYAN_SHULKER_BOX, + PURPLE_SHULKER_BOX, + BLUE_SHULKER_BOX, + BROWN_SHULKER_BOX, + GREEN_SHULKER_BOX, + RED_SHULKER_BOX, + BLACK_SHULKER_BOX, + BARREL, + DISPENSER, + DROPPER, + FURNACE, + BLAST_FURNACE, + HOPPER -> true; + default -> false; + }; + } +} diff --git a/src/main/java/art/arcane/adapt/api/world/AdaptPlayer.java b/src/main/java/art/arcane/adapt/api/world/AdaptPlayer.java new file mode 100644 index 000000000..a1111c609 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/world/AdaptPlayer.java @@ -0,0 +1,442 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.world; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.notification.AdvancementNotification; +import art.arcane.adapt.api.notification.Notifier; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.tick.TickedObject; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.io.IO; +import art.arcane.volmlib.util.math.M; +import art.arcane.volmlib.util.math.RollingSequence; +import art.arcane.volmlib.util.scheduling.ChronoLatch; +import lombok.Data; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.io.File; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +@Data +public class AdaptPlayer extends TickedObject { + private static final Set LOAD_FAILURE_GUARD = ConcurrentHashMap.newKeySet(); + + private final Player player; + private final PlayerData data; + private ChronoLatch savelatch; + private ChronoLatch updatelatch; + private Notifier not; + private Notifier actionBarNotifier; + private AdvancementHandler advancementHandler; + private RollingSequence speed; + private long lastloc; + private Vector velocity; + private Location lastpos; + private long lastSeen; + private volatile boolean pendingDataDeletion; + private volatile boolean runtimeReady; + + public AdaptPlayer(Player p) { + this(p, null); + } + + public AdaptPlayer(Player p, PlayerData prefetchedData) { + super("players", p.getUniqueId().toString(), 50); + this.player = p; + data = prefetchedData == null ? loadPlayerData(p.getUniqueId()) : prefetchedData; + updatelatch = new ChronoLatch(1000); + savelatch = new ChronoLatch(60000); + not = new Notifier(this); + actionBarNotifier = new Notifier(this); + advancementHandler = new AdvancementHandler(this); + speed = new RollingSequence(7); + lastloc = M.ms(); + lastSeen = M.ms(); + velocity = new Vector(); + runtimeReady = true; + } + + public static PlayerData loadPlayerData(UUID uuid) { + boolean upload = false; + if (AdaptConfig.get().isUseSql()) { + if (Adapt.instance.getRedisSync() != null) { + java.util.Optional opt = Adapt.instance.getRedisSync().cachedData(uuid); + if (opt.isPresent()) { + Adapt.verbose("Using cached data for player: " + uuid); + LOAD_FAILURE_GUARD.remove(uuid); + return opt.get(); + } + } + + if (Adapt.instance.getSqlManager() != null) { + String sqlData = Adapt.instance.getSqlManager().fetchData(uuid); + if (sqlData != null) { + try { + PlayerData parsed = PlayerData.fromJson(sqlData); + LOAD_FAILURE_GUARD.remove(uuid); + return parsed; + } catch (Throwable e) { + LOAD_FAILURE_GUARD.add(uuid); + Adapt.warn("Failed to parse SQL player data for " + uuid + ": " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " (" + e.getMessage() + ")")); + } + } + upload = true; + } + } + + File f = getPlayerDataFile(uuid); + if (f.exists()) { + try { + String text = IO.readAll(f); + if (upload) { + PlayerDataPersistenceQueue queue = Adapt.instance.getPlayerDataPersistenceQueue(); + if (queue != null) { + queue.queueSave(uuid, text, f); + } else if (Adapt.instance.getSqlManager() != null) { + Adapt.instance.getSqlManager().updateData(uuid, text); + } + } + PlayerData parsed = PlayerData.fromJson(text); + LOAD_FAILURE_GUARD.remove(uuid); + return parsed; + } catch (Throwable e) { + LOAD_FAILURE_GUARD.add(uuid); + Adapt.warn("Failed to load player data for " + uuid + " from " + f.getAbsolutePath() + ": " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " (" + e.getMessage() + ")")); + } + } + + LOAD_FAILURE_GUARD.remove(uuid); + return new PlayerData(); + } + + private static File getPlayerDataFile(UUID uuid) { + return new File(Adapt.instance.getDataFolder("data", "players"), uuid.toString() + ".json"); + } + + public boolean canConsumeFood(double cost, int minFood) { + return (player.getFoodLevel() + player.getSaturation()) - minFood > cost; + } + + public boolean consumeFood(double cost, int minFood) { + if (canConsumeFood(cost, minFood)) { + int food = player.getFoodLevel(); + double sat = player.getSaturation(); + + if (sat >= cost) { + sat = (player.getSaturation() - cost); + cost = 0; + } else if (player.getSaturation() > 0) { + cost -= sat; + sat = 0; + } + + if (cost >= 1) { + food -= (int) Math.floor(cost); + cost = Math.floor(cost); + } + + if (cost > 0) { + if (sat >= cost) { + sat -= cost; + cost = 0; + } else { + sat++; + food--; + } + } + + if (sat >= cost && cost > 0) { + sat -= cost; + cost = 0; + } + + player.setFoodLevel(food); + player.setSaturation((float) sat); + + return true; + } + + return false; + } + + public boolean isBusy() { + return not.isBusy(); + } + + public PlayerSkillLine getSkillLine(String l) { + return getData().getSkillLine(l); + } + + private void save() { + save(false); + } + + private void save(boolean synchronous) { + UUID uuid = player.getUniqueId(); + File playerDataFile = getPlayerDataFile(uuid); + + if (pendingDataDeletion) { + queueDelete(uuid, playerDataFile); + return; + } + + if (LOAD_FAILURE_GUARD.contains(uuid)) { + Adapt.warn("Skipping save for " + uuid + " because player data failed to load earlier. Existing file is preserved."); + return; + } + + String json = this.data.toJson(AdaptConfig.get().isUseSql()); + if (synchronous) { + if (AdaptConfig.get().isUseSql()) { + if (Adapt.instance.getRedisSync() != null) { + Adapt.instance.getRedisSync().publish(uuid, json); + } + if (Adapt.instance.getSqlManager() != null) { + Adapt.instance.getSqlManager().updateData(uuid, json); + } + } else { + J.attempt(() -> IO.writeAll(playerDataFile, json)); + } + return; + } + + PlayerDataPersistenceQueue queue = Adapt.instance.getPlayerDataPersistenceQueue(); + if (queue != null) { + queue.queueSave(uuid, json, playerDataFile); + return; + } + + if (AdaptConfig.get().isUseSql()) { + if (Adapt.instance.getRedisSync() != null) { + Adapt.instance.getRedisSync().publish(uuid, json); + } + if (Adapt.instance.getSqlManager() != null) { + Adapt.instance.getSqlManager().updateData(uuid, json); + } + } else { + J.attempt(() -> IO.writeAll(playerDataFile, json)); + } + } + + @Override + public void unregister() { + super.unregister(); + save(true); + } + + public void delete(UUID uuid) { + pendingDataDeletion = true; + File local = getPlayerDataFile(uuid); + Adapt.warn("Deleting Player Data: " + local.getAbsolutePath()); + queueDelete(uuid, local); + + Player p = player; + if (!p.isOnline()) { + return; + } + + J.runEntity(p, () -> p.kickPlayer("Your data has been deleted."), 20); + } + + public boolean shouldUnload() { + if (player.isOnline()) { + lastSeen = M.ms(); + return false; + } + + return lastSeen + 60_000 < System.currentTimeMillis(); + } + + @Override + public void onTick() { + if (!runtimeReady) { + return; + } + + if (updatelatch == null) { + updatelatch = new ChronoLatch(1000); + } + if (savelatch == null) { + savelatch = new ChronoLatch(60000); + } + if (speed == null) { + speed = new RollingSequence(7); + } + if (velocity == null) { + velocity = new Vector(); + } + + if (updatelatch.flip()) { + getData().update(this); + } + + if (savelatch.flip()) { + save(); + } + + getServer().takeSpatial(this); + + Location at = player.getLocation(); + long now = M.ms(); + + if (lastpos != null && lastpos.getWorld().equals(at.getWorld())) { + double dx = at.getX() - lastpos.getX(); + double dy = at.getY() - lastpos.getY(); + double dz = at.getZ() - lastpos.getZ(); + double distanceSquared = (dx * dx) + (dy * dy) + (dz * dz); + + if (distanceSquared <= 7 * 7) { + speed.put(Math.sqrt(distanceSquared) / ((double) (now - lastloc) / 50D)); + double vx = (velocity.getX() + dx) * 0.5; + double vy = (velocity.getY() + dy) * 0.5; + double vz = (velocity.getZ() + dz) * 0.5; + velocity.setX(Math.abs(vx) < 0.01 ? 0 : vx); + velocity.setY(Math.abs(vy) < 0.01 ? 0 : vy); + velocity.setZ(Math.abs(vz) < 0.01 ? 0 : vz); + } + } + + lastpos = at; + lastloc = now; + } + + public double getSpeed() { + if (!runtimeReady || speed == null) { + return 0D; + } + + return speed.getAverage(); + } + + public boolean hasAdaptation(String id) { + if (id == null || id.isBlank()) { + return false; + } + + int separator = id.indexOf('-'); + if (separator <= 0) { + return false; + } + + String skillLine = id.substring(0, separator); + if (skillLine.isBlank()) { + return false; + } + + PlayerSkillLine line = getData().getSkillLineNullable(skillLine); + if (line == null) { + return false; + } + + PlayerAdaptation adaptation = line.getAdaptation(id); + return adaptation != null && adaptation.getLevel() > 0; + } + + public void giveXPToRecents(AdaptPlayer p, double xpGained, int ms) { + for (PlayerSkillLine i : p.getData().getSkillLines().v()) { + if (M.ms() - i.getLast() < ms) { + i.giveXP(not, xpGained); + } + } + } + + public void giveXPToRandom(AdaptPlayer p, double xpGained) { + p.getData().getSkillLines().v().getRandom().giveXP(p.getNot(), xpGained); + } + + public void boostXPToRandom(AdaptPlayer p, double boost, int ms) { + p.getData().getSkillLines().v().getRandom().boost(boost, ms); + } + + public void boostXPToRecents(double boost, int ms) { + for (PlayerSkillLine i : this.getData().getSkillLines().v()) { + if (M.ms() - i.getLast() < ms) { + i.boost(boost, ms); + } + } + } + + public void loggedIn() { + lastSeen = M.ms(); + if (AdaptConfig.get().isLoginBonus()) { + long timeGone = M.ms() - getData().getLastLogin(); + boolean first = getData().getLastLogin() == 0; + getData().setLastLogin(M.ms()); + long boostTime = (long) Math.min(timeGone / 12D, TimeUnit.HOURS.toMillis(1)); + if (boostTime < TimeUnit.MINUTES.toMillis(5)) { + return; + } + double boostAmount = M.lerp(0.1, 0.25, (double) boostTime / (double) TimeUnit.HOURS.toMillis(1)); + getData().globalXPMultiplier(boostAmount, (int) boostTime); + if (!AdaptConfig.get().isWelcomeMessage()) + return; + getNot().queue(AdvancementNotification.builder() + .title(first ? Localizer.dLocalize("snippets.gui.welcome") : Localizer.dLocalize("snippets.gui.welcome_back")) + .description("+" + C.GREEN + Form.pc(boostAmount, 0) + C.GRAY + " " + Localizer.dLocalize("snippets.gui.xp_bonus_for_time") + " " + C.AQUA + Form.duration(boostTime, 0)) + .model(CustomModel.get(Material.DIAMOND, "snippets", "gui", first ? "welcome" : "welcomeback")) + .build()); + } + } + + public boolean hasSkill(Skill s) { + if (s == null) { + return false; + } + + PlayerSkillLine line = getData().getSkillLine(s.getName()); + return line != null && line.getXp() > 1; + } + + private void queueDelete(UUID uuid, File localFile) { + PlayerDataPersistenceQueue queue = Adapt.instance.getPlayerDataPersistenceQueue(); + if (queue != null) { + queue.queueDelete(uuid, localFile); + return; + } + + if (localFile.exists() && !localFile.delete()) { + Adapt.verbose("Failed to delete local player data file " + localFile.getAbsolutePath()); + } + if (AdaptConfig.get().isUseSql() && Adapt.instance.getSqlManager() != null) { + Adapt.instance.getSqlManager().delete(uuid); + } + } + + @Override + public final boolean equals(Object obj) { + return this == obj; + } + + @Override + public final int hashCode() { + return System.identityHashCode(this); + } +} diff --git a/src/main/java/com/volmit/adapt/api/world/AdaptPlayerTracker.java b/src/main/java/art/arcane/adapt/api/world/AdaptPlayerTracker.java similarity index 96% rename from src/main/java/com/volmit/adapt/api/world/AdaptPlayerTracker.java rename to src/main/java/art/arcane/adapt/api/world/AdaptPlayerTracker.java index db4f95d3e..d36dda705 100644 --- a/src/main/java/com/volmit/adapt/api/world/AdaptPlayerTracker.java +++ b/src/main/java/art/arcane/adapt/api/world/AdaptPlayerTracker.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.world; +package art.arcane.adapt.api.world; public class AdaptPlayerTracker { diff --git a/src/main/java/art/arcane/adapt/api/world/AdaptServer.java b/src/main/java/art/arcane/adapt/api/world/AdaptServer.java new file mode 100644 index 000000000..34d621491 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/world/AdaptServer.java @@ -0,0 +1,428 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.world; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.notification.AdvancementNotification; +import art.arcane.adapt.api.notification.SoundNotification; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.skill.SkillRegistry; +import art.arcane.adapt.api.tick.TickedObject; +import art.arcane.adapt.api.xp.SpatialXP; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.api.xp.XPMultiplier; +import art.arcane.adapt.content.gui.SkillsGui; +import art.arcane.adapt.content.item.ExperienceOrb; +import art.arcane.adapt.content.item.KnowledgeOrb; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.io.Json; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.volmlib.util.io.IO; +import art.arcane.volmlib.util.math.M; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import lombok.Getter; +import lombok.NonNull; +import lombok.SneakyThrows; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.entity.Snowball; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.io.File; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +public class AdaptServer extends TickedObject { + private final ReentrantLock clearLock = new ReentrantLock(); + private final Map players = new ConcurrentHashMap<>(); + private final Cache prefetchedPlayerData = Caffeine.newBuilder() + .expireAfterWrite(2, TimeUnit.MINUTES) + .maximumSize(2048) + .build(); + @Getter + private final List spatialTickets = new ArrayList<>(); + private volatile int spatialTicketCount; + @Getter + private final SkillRegistry skillRegistry = new SkillRegistry(); + @Getter + private volatile List onlinePlayerSnapshot = List.of(); + @Getter + private volatile List onlineAdaptPlayerSnapshot = List.of(); + @Getter + private AdaptServerData data = new AdaptServerData(); + + public AdaptServer() { + super("core", UUID.randomUUID().toString(), 1000); + load(); + + Bukkit.getOnlinePlayers().forEach(this::join); + refreshOnlinePlayerSnapshots(); + } + + public void offer(SpatialXP xp) { + if (xp == null || xp.getSkill() == null || xp.getLocation() == null) { + return; + } + if (xp.getRadius() <= 0 || xp.getXp() <= 0 || xp.getMs() <= M.ms()) { + return; + } + synchronized (spatialTickets) { + spatialTickets.add(xp); + spatialTicketCount = spatialTickets.size(); + } + } + + public void takeSpatial(AdaptPlayer p) { + if (spatialTicketCount == 0) { + return; + } + + try { + SpatialXP x; + synchronized (spatialTickets) { + int size = spatialTickets.size(); + if (size == 0) { + return; + } + x = spatialTickets.get(size - 1); + } + + if (M.ms() > x.getMs()) { + synchronized (spatialTickets) { + spatialTickets.remove(x); + spatialTicketCount = spatialTickets.size(); + } + return; + } + + if (!p.getPlayer().getClass().getSimpleName().equals("CraftPlayer")) { + synchronized (spatialTickets) { + spatialTickets.remove(x); + spatialTicketCount = spatialTickets.size(); + } + return; + } + + if (p.getPlayer().getWorld().equals(x.getLocation().getWorld())) { + double c = p.getPlayer().getLocation().distanceSquared(x.getLocation()); + if (c < x.getRadius() * x.getRadius()) { + double distl = M.lerpInverse(0, x.getRadius() * x.getRadius(), c); + double xp = x.getXp() / (1.5D * ((distl * 9) + 1)); + synchronized (spatialTickets) { + x.setXp(x.getXp() - xp); + + if (x.getXp() < 10) { + xp += x.getXp(); + spatialTickets.remove(x); + spatialTicketCount = spatialTickets.size(); + } + } + + XP.xp(p, x.getSkill(), xp); + } + } + } catch (Throwable ignored) { + } + } + + public void join(Player p) { + AdaptPlayer existing = players.get(p.getUniqueId()); + if (existing != null) { + if (existing.getPlayer() == p) { + existing.loggedIn(); + refreshOnlinePlayerSnapshots(); + return; + } + + players.remove(p.getUniqueId()); + } + + PlayerData prefetched = takePrefetchedData(p.getUniqueId()); + AdaptPlayer a = new AdaptPlayer(p, prefetched); + players.put(p.getUniqueId(), a); + refreshOnlinePlayerSnapshots(); + a.loggedIn(); + } + + public void quit(UUID p) { + AdaptPlayer a = players.get(p); + if (a == null) return; + a.unregister(); + // Keep the entry briefly after quit so late quit listeners/tasks do not + // re-create a new AdaptPlayer for an offline player. + prefetchedPlayerData.invalidate(p); + refreshOnlinePlayerSnapshots(); + } + + @Override + public void unregister() { + new HashSet<>(players.keySet()).forEach(this::quit); + prefetchedPlayerData.invalidateAll(); + onlinePlayerSnapshot = List.of(); + onlineAdaptPlayerSnapshot = List.of(); + skillRegistry.unregister(); + save(); + super.unregister(); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void on(ProjectileLaunchEvent e) { + if (e.getEntity() instanceof Snowball s && e.getEntity().getShooter() instanceof Player p) { + KnowledgeOrb.Data data = KnowledgeOrb.get(s.getItem()); + if (data != null) { + Skill skill = getSkillRegistry().getSkill(data.getSkill()); + data.apply(p); + SoundNotification.builder() + .sound(Sound.ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM) + .volume(0.35f).pitch(1.455f) + .build().play(getPlayer(p)); + SoundNotification.builder() + .sound(Sound.ENTITY_SHULKER_OPEN) + .volume(1f).pitch(1.655f) + .build().play(getPlayer(p)); + getPlayer(p).getNot().queue(AdvancementNotification.builder() + .icon(Material.BOOK) + .model(CustomModel.get(Material.BOOK, "snippets", "gui", "knowledge")) + .title(C.GRAY + "+ " + C.WHITE + data.getKnowledge() + " " + skill.getDisplayName() + " Knowledge") + .build()); + } else { + ExperienceOrb.Data datax = ExperienceOrb.get(s.getItem()); + if (datax != null) { + datax.apply(p); + SoundNotification.builder() + .sound(Sound.ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM) + .volume(0.35f).pitch(1.455f) + .build().play(getPlayer(p)); + SoundNotification.builder() + .sound(Sound.ENTITY_SHULKER_OPEN) + .volume(1f).pitch(1.655f) + .build().play(getPlayer(p)); + } + } + } + + } + + @EventHandler(priority = EventPriority.LOWEST) + public void on(PlayerJoinEvent e) { + Player p = e.getPlayer(); + join(p); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(AsyncPlayerPreLoginEvent e) { + if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { + return; + } + + UUID uuid = e.getUniqueId(); + if (players.containsKey(uuid) || prefetchedPlayerData.getIfPresent(uuid) != null) { + return; + } + + try { + prefetchedPlayerData.put(uuid, AdaptPlayer.loadPlayerData(uuid)); + } catch (Throwable ignored) { + Adapt.verbose("Failed to prefetch player data for " + uuid); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + Player p = e.getPlayer(); + quit(p.getUniqueId()); + } + + @EventHandler + public void on(CraftItemEvent e) { + if (e.getWhoClicked() instanceof Player p) { + Adaptation required = getSkillRegistry().getRequiredAdaptation(e.getRecipe()); + if (required == null || required.hasAdaptation(p)) { + return; + } + + Skill requiredSkill = required.getSkill(); + String skillName = requiredSkill == null ? "Unknown Skill" : requiredSkill.getDisplayName(); + SoundPlayer sp = SoundPlayer.of(p); + Adapt.actionbar(p, C.RED + "Requires " + required.getDisplayName() + C.RED + " from " + skillName); + sp.play(p.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.5f, 1.8f); + e.setCancelled(true); + } + } + + @Override + public void onTick() { + data.getMultipliers().removeIf(multiplier -> multiplier == null || multiplier.isExpired()); + + synchronized (spatialTickets) { + spatialTickets.removeIf(ticket -> M.ms() > ticket.getMs()); + spatialTicketCount = spatialTickets.size(); + } + + if (!clearLock.tryLock()) + return; + + try { + int sizeBefore = players.size(); + Iterator> iterator = players.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + AdaptPlayer player = entry.getValue(); + if (player == null) { + iterator.remove(); + prefetchedPlayerData.invalidate(entry.getKey()); + continue; + } + + if (!player.shouldUnload()) { + continue; + } + + player.unregister(); + iterator.remove(); + prefetchedPlayerData.invalidate(entry.getKey()); + } + + if (players.size() != sizeBefore) { + refreshOnlinePlayerSnapshots(); + } + } finally { + clearLock.unlock(); + } + } + + public PlayerData peekData(UUID player) { + if (Bukkit.getPlayer(player) != null) { + return getPlayer(Bukkit.getPlayer(player)).getData(); + } + + if (AdaptConfig.get().isUseSql()) { + String sqlData = Adapt.instance.getSqlManager().fetchData(player); + if (sqlData != null) { + return Json.fromJson(sqlData, PlayerData.class); + } + } + + File f = new File(Adapt.instance.getDataFolder("data", "players"), player + ".json"); + if (f.exists()) { + try { + return Json.fromJson(IO.readAll(f), PlayerData.class); + } catch (Throwable ignored) { + Adapt.verbose("Failed to load player data for " + player); + } + } + + return new PlayerData(); + } + + @NonNull + public Optional getPlayerData(@NonNull UUID uuid) { + return Optional.ofNullable(players.get(uuid)) + .map(AdaptPlayer::getData); + } + + public AdaptPlayer getPlayer(Player p) { + AdaptPlayer existing = players.get(p.getUniqueId()); + if (existing != null) { + return existing; + } + + AdaptPlayer created = players.computeIfAbsent(p.getUniqueId(), player -> { + Adapt.warn("Failed to find AdaptPlayer for " + p.getName() + " (" + p.getUniqueId() + ")"); + Adapt.warn("Loading new AdaptPlayer..."); + return new AdaptPlayer(p, takePrefetchedData(player)); + }); + refreshOnlinePlayerSnapshots(); + return created; + } + + private PlayerData takePrefetchedData(UUID uuid) { + PlayerData prefetched = prefetchedPlayerData.getIfPresent(uuid); + if (prefetched != null) { + prefetchedPlayerData.invalidate(uuid); + } + return prefetched; + } + + private void refreshOnlinePlayerSnapshots() { + ArrayList adaptPlayers = new ArrayList<>(players.size()); + ArrayList playerSnapshot = new ArrayList<>(players.size()); + + for (AdaptPlayer adaptPlayer : players.values()) { + if (adaptPlayer == null) { + continue; + } + Player online = adaptPlayer.getPlayer(); + if (online == null || !online.isOnline()) { + continue; + } + adaptPlayers.add(adaptPlayer); + playerSnapshot.add(online); + } + + onlineAdaptPlayerSnapshot = Collections.unmodifiableList(adaptPlayers); + onlinePlayerSnapshot = Collections.unmodifiableList(playerSnapshot); + } + + public void openSkillGUI(Skill skill, Player p) { + skill.openGui(p); + } + + public void openAdaptGui(Player p) { + SkillsGui.open(p); + } + + public void openAdaptationGUI(Adaptation adaptation, Player p) { + adaptation.openGui(p); + } + + public void boostXP(double boost, int ms) { + data.getMultipliers().add(new XPMultiplier(boost, ms)); + } + + public void load() { + File f = new File(Adapt.instance.getDataFolder("data"), "server-data.json"); + if (f.exists()) { + try { + data = Json.fromJson(IO.readAll(f), AdaptServerData.class); + } catch (Throwable ignored) { + Adapt.verbose("Failed to load global boosts data"); + } + } + } + + @SneakyThrows + public void save() { + IO.writeAll(new File(Adapt.instance.getDataFolder("data"), "server-data.json"), Json.toJson(data, true)); + } +} diff --git a/src/main/java/com/volmit/adapt/api/world/AdaptServerData.java b/src/main/java/art/arcane/adapt/api/world/AdaptServerData.java similarity index 85% rename from src/main/java/com/volmit/adapt/api/world/AdaptServerData.java rename to src/main/java/art/arcane/adapt/api/world/AdaptServerData.java index f18e62893..c57115819 100644 --- a/src/main/java/com/volmit/adapt/api/world/AdaptServerData.java +++ b/src/main/java/art/arcane/adapt/api/world/AdaptServerData.java @@ -16,15 +16,15 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.world; +package art.arcane.adapt.api.world; -import com.volmit.adapt.api.xp.XPMultiplier; -import com.volmit.adapt.util.collection.KList; +import art.arcane.adapt.api.xp.XPMultiplier; +import art.arcane.volmlib.util.collection.KList; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor public class AdaptServerData { - private KList multipliers = new KList<>(); + private KList multipliers = new KList<>(); } diff --git a/src/main/java/com/volmit/adapt/api/world/AdaptStatTracker.java b/src/main/java/art/arcane/adapt/api/world/AdaptStatTracker.java similarity index 87% rename from src/main/java/com/volmit/adapt/api/world/AdaptStatTracker.java rename to src/main/java/art/arcane/adapt/api/world/AdaptStatTracker.java index 61bf89f3b..2e3dcdc6d 100644 --- a/src/main/java/com/volmit/adapt/api/world/AdaptStatTracker.java +++ b/src/main/java/art/arcane/adapt/api/world/AdaptStatTracker.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.world; +package art.arcane.adapt.api.world; import lombok.Builder; import lombok.Data; @@ -24,8 +24,8 @@ @Data @Builder public class AdaptStatTracker { - private String stat; - private double goal; - private double reward; - private String advancement; + private String stat; + private double goal; + private double reward; + private String advancement; } diff --git a/src/main/java/art/arcane/adapt/api/world/AdvancementHandler.java b/src/main/java/art/arcane/adapt/api/world/AdvancementHandler.java new file mode 100644 index 000000000..0403c3df3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/world/AdvancementHandler.java @@ -0,0 +1,45 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.world; + +import art.arcane.adapt.AdaptConfig; +import lombok.Data; + +import static art.arcane.adapt.Adapt.instance; + +@Data +public class AdvancementHandler { + private AdaptPlayer player; + private boolean ready; + + public AdvancementHandler(AdaptPlayer player) { + this.player = player; + ready = false; + instance.getManager().unlockExisting(player, this); + } + + public void grant(String key, boolean toast) { + if (!AdaptConfig.get().isAdvancements()) return; + instance.getManager().grant(getPlayer(), key, toast); + } + + public void grant(String key) { + grant(key, true); + } +} diff --git a/src/main/java/com/volmit/adapt/api/world/Discovery.java b/src/main/java/art/arcane/adapt/api/world/Discovery.java similarity index 81% rename from src/main/java/com/volmit/adapt/api/world/Discovery.java rename to src/main/java/art/arcane/adapt/api/world/Discovery.java index c0bc95988..fa8bca5e3 100644 --- a/src/main/java/com/volmit/adapt/api/world/Discovery.java +++ b/src/main/java/art/arcane/adapt/api/world/Discovery.java @@ -16,16 +16,16 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.world; +package art.arcane.adapt.api.world; -import com.volmit.adapt.util.collection.KList; +import art.arcane.volmlib.util.collection.KList; import lombok.Getter; public class Discovery { - @Getter - private final KList seen = new KList<>(); + @Getter + private final KList seen = new KList<>(); - public boolean isNewDiscovery(T t) { - return seen.addIfMissing(t); - } + public boolean isNewDiscovery(T t) { + return seen.addIfMissing(t); + } } diff --git a/src/main/java/com/volmit/adapt/api/world/PlayerAdaptation.java b/src/main/java/art/arcane/adapt/api/world/PlayerAdaptation.java similarity index 80% rename from src/main/java/com/volmit/adapt/api/world/PlayerAdaptation.java rename to src/main/java/art/arcane/adapt/api/world/PlayerAdaptation.java index 5431576ac..b63a8b23b 100644 --- a/src/main/java/com/volmit/adapt/api/world/PlayerAdaptation.java +++ b/src/main/java/art/arcane/adapt/api/world/PlayerAdaptation.java @@ -16,17 +16,14 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.world; +package art.arcane.adapt.api.world; -import com.volmit.adapt.util.collection.KMap; +import art.arcane.volmlib.util.collection.KMap; import lombok.Data; -import java.util.HashMap; -import java.util.Map; - @Data public class PlayerAdaptation { - private String id; - private int level; - private final KMap storage = new KMap<>(); + private final KMap storage = new KMap<>(); + private String id; + private int level; } diff --git a/src/main/java/art/arcane/adapt/api/world/PlayerData.java b/src/main/java/art/arcane/adapt/api/world/PlayerData.java new file mode 100644 index 000000000..5efe3a2ff --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/world/PlayerData.java @@ -0,0 +1,391 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.world; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.notification.ActionBarNotification; +import art.arcane.adapt.api.notification.SoundNotification; +import art.arcane.adapt.api.notification.TitleNotification; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.api.xp.XPMultiplier; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.io.Json; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.collection.KSet; +import art.arcane.volmlib.util.format.Form; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.entity.EntityType; + +@Data +@NoArgsConstructor +public class PlayerData { + private final KMap skillLines = new KMap<>(); + private KMap stats = new KMap<>(); + private String last = "none"; + private KSet advancements = new KSet<>(); + private Discovery seenBiomes = new Discovery<>(); + private Discovery seenMobs = new Discovery<>(); + private Discovery seenFoods = new Discovery<>(); + private Discovery seenItems = new Discovery<>(); + private Discovery seenRecipes = new Discovery<>(); + private Discovery seenEnchants = new Discovery<>(); + private Discovery seenWorlds = new Discovery<>(); + private Discovery seenPeople = new Discovery<>(); + private Discovery seenEnvironments = new Discovery<>(); + private Discovery seenPotionEffects = new Discovery<>(); + private Discovery seenBlocks = new Discovery<>(); + private KList multipliers = new KList<>(); + private long wisdom = 0; + private double multiplier = 0; + private long lastLogin = 0; + private double masterXp = 1; + private double lastMasterXp = 0; + + public static PlayerData fromJson(String json) { + return Json.fromJson(json, PlayerData.class); + } + + public void giveMasterXp(double xp) { + masterXp += xp; + } + + public void globalXPMultiplier(double v, int duration) { + multipliers.add(new XPMultiplier(v, duration)); + } + + public boolean isGranted(String advancement) { + return advancements.contains(advancement); + } + + public void ensureGranted(String advancement) { + advancements.add(advancement); + } + + public double getStat(String key) { + Double d = stats.get(key); + return d == null ? 0 : d; + } + + public void addStat(String key, double amt) { + stats.merge(key, amt, Double::sum); + } + + public void update(AdaptPlayer p) { + double m = 1D; + m += collectActivePlayerMultiplierBonus(); + m += collectGlobalMultiplierBonus(); + + if (m <= 0) { + m = 0.01; + } + + if (m > 1000) { + m = 1000; + } + + multiplier = m; + + for (java.util.Map.Entry entry : skillLines.entrySet()) { + String lineId = entry.getKey(); + Skill loadedSkill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(lineId); + if (loadedSkill == null) { + // Never prune unknown lines automatically; missing skills can be transient + // during startup/reload or due temporary config disables. + continue; + } + + PlayerSkillLine lineData = entry.getValue(); + if (lineData == null) { + skillLines.remove(lineId); + continue; + } + + if (lineData.getXp() == 0 && lineData.getKnowledge() == 0 && lineData.getPooledXp() == 0) { + skillLines.remove(lineId, lineData); + continue; + } + + lineData.update(p, lineId, this); + } + + int oldLevel = (int) XP.getLevelForXp(getLastMasterXp()); + int level = (int) XP.getLevelForXp(getMasterXp()); + + if (oldLevel != level) { + setLastMasterXp(getMasterXp()); + p.getNot().queue(SoundNotification.builder() + .sound(Sound.BLOCK_ENCHANTMENT_TABLE_USE) + .volume(1f) + .pitch(0.54f) + .group("lvl") + .build(), + SoundNotification.builder() + .sound(Sound.BLOCK_AMETHYST_BLOCK_CHIME) + .volume(1f) + .pitch(0.44f) + .group("lvl") + .build(), + SoundNotification.builder() + .sound(Sound.BLOCK_AMETHYST_BLOCK_CHIME) + .volume(1f) + .pitch(0.74f) + .group("lvl") + .build(), + SoundNotification.builder() + .sound(Sound.BLOCK_AMETHYST_BLOCK_CHIME) + .volume(1f) + .pitch(1.34f) + .group("lvl") + .build(), + TitleNotification.builder() + .in(250) + .stay(1450) + .out(2250) + .group("lvl") + .title("") + .subtitle(C.GOLD + Localizer.dLocalize("snippets.gui.level") + " " + level)// I'm sorry I missed this! + .build()); + p.getActionBarNotifier().queue( + ActionBarNotification.builder() + .duration(450) + .group("power") + .title(C.GOLD + "" + Form.f(level * AdaptConfig.get().getPowerPerLevel(), 0) + C.GRAY + " " + Localizer.dLocalize("snippets.gui.max_ability_power")) // I'm sorry I missed this! + .build()); + + } + } + + private double collectActivePlayerMultiplierBonus() { + double bonus = 0D; + for (int i = multipliers.size() - 1; i >= 0; i--) { + XPMultiplier active = multipliers.get(i); + if (active == null || active.isExpired()) { + multipliers.remove(i); + continue; + } + bonus += active.getMultiplier(); + } + return bonus; + } + + private double collectGlobalMultiplierBonus() { + double bonus = 0D; + KList globalMultipliers = Adapt.instance.getAdaptServer().getData().getMultipliers(); + for (int i = 0; i < globalMultipliers.size(); i++) { + XPMultiplier active = globalMultipliers.get(i); + if (active == null || active.isExpired()) { + continue; + } + bonus += active.getMultiplier(); + } + return bonus; + } + + public int getAvailablePower() { + return getMaxPower() - getUsedPower(); + } + + public boolean hasPowerAvailable() { + return hasPowerAvailable(1); + } + + public boolean hasPowerAvailable(int amount) { + return getAvailablePower() >= amount; + } + + public int getUsedPower() { + int usedPower = 0; + for (PlayerSkillLine line : skillLines.values()) { + if (line == null) { + continue; + } + + for (PlayerAdaptation adaptation : line.getAdaptations().values()) { + if (adaptation == null) { + continue; + } + usedPower += adaptation.getLevel(); + } + } + return usedPower; + } + + public int getLevel() { + return (int) XP.getLevelForXp(getMasterXp()); + } + + public int getMaxPower() { + return (int) (XP.getLevelForXp(getMasterXp()) * AdaptConfig.get().getPowerPerLevel()); + } + + public PlayerSkillLine getSkillLine(String skillLine) { + if (Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skillLine) == null) { + return null; + } + + synchronized (skillLines) { + try { + PlayerSkillLine s = skillLines.get(skillLine); + + if (s != null) { + return s; + } + } catch (Throwable e) { + e.printStackTrace(); + Adapt.error("Failed to get skill line " + skillLine); + } + + PlayerSkillLine s = new PlayerSkillLine(); + s.setLine(skillLine); + skillLines.put(skillLine, s); + return s; + } + } + + public PlayerSkillLine getSkillLineNullable(String skillLine) { + return skillLines.get(skillLine); + } + + public void resetMonotonyForOtherSkills(String currentSkill) { + for (PlayerSkillLine line : skillLines.values()) { + if (!line.getLine().equals(currentSkill)) { + line.relaxStalenessForActivitySwitch(); + } + } + } + + public void addWisdom() { + wisdom++; + } + + public void clearXp() { + for (PlayerSkillLine line : skillLines.values()) { + line.setXp(0); + line.setLastXP(0); + line.setLastLevel(0); + line.setPooledXp(0); + line.setPooledNotifyXp(0); + line.setPoolStartedAt(0); + line.setPoolLastEarnAt(0); + line.setMonotonyCounter(0); + line.setMonotonyMultiplier(1.0); + line.setLastXpTimestamp(0); + line.setSkillStaleness(new PlayerSkillLine.RewardStalenessState()); + line.getActivityStaleness().clear(); + line.getAdaptations().clear(); + } + masterXp = 1; + lastMasterXp = 0; + } + + public void clearKnowledge() { + for (PlayerSkillLine line : skillLines.values()) { + line.setKnowledge(0); + } + } + + public void clearAdaptations() { + for (PlayerSkillLine line : skillLines.values()) { + line.getAdaptations().clear(); + } + } + + public void clearStats() { + stats.clear(); + } + + public void clearDiscoveries() { + seenBiomes = new Discovery<>(); + seenMobs = new Discovery<>(); + seenFoods = new Discovery<>(); + seenItems = new Discovery<>(); + seenRecipes = new Discovery<>(); + seenEnchants = new Discovery<>(); + seenWorlds = new Discovery<>(); + seenPeople = new Discovery<>(); + seenEnvironments = new Discovery<>(); + seenPotionEffects = new Discovery<>(); + seenBlocks = new Discovery<>(); + } + + public void pruneAdaptationsForPowerBudget() { + int usedPower = getUsedPower(); + int maxPower = getMaxPower(); + + while (usedPower > maxPower) { + String worstSkill = null; + String worstAdaptation = null; + int worstLevel = Integer.MAX_VALUE; + + for (java.util.Map.Entry skillEntry : skillLines.entrySet()) { + for (java.util.Map.Entry adaptEntry : skillEntry.getValue().getAdaptations().entrySet()) { + int level = adaptEntry.getValue().getLevel(); + if (level > 0 && level < worstLevel) { + worstLevel = level; + worstSkill = skillEntry.getKey(); + worstAdaptation = adaptEntry.getKey(); + } + } + } + + if (worstSkill == null) { + break; + } + + PlayerAdaptation adapt = skillLines.get(worstSkill).getAdaptations().get(worstAdaptation); + if (adapt.getLevel() <= 1) { + skillLines.get(worstSkill).getAdaptations().remove(worstAdaptation); + usedPower -= 1; + } else { + adapt.setLevel(adapt.getLevel() - 1); + usedPower -= 1; + } + } + } + + public void clearAll() { + clearXp(); + clearKnowledge(); + clearAdaptations(); + clearStats(); + clearDiscoveries(); + advancements.clear(); + multipliers.clear(); + wisdom = 0; + } + + public String toJson(boolean raw) { + synchronized (skillLines) { + for (PlayerSkillLine line : skillLines.values()) { + if (line != null) { + line.flushXpPool(null); + } + } + return Json.toJson(this, !raw); + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/world/PlayerDataPersistenceQueue.java b/src/main/java/art/arcane/adapt/api/world/PlayerDataPersistenceQueue.java new file mode 100644 index 000000000..bb866bdbf --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/world/PlayerDataPersistenceQueue.java @@ -0,0 +1,104 @@ +package art.arcane.adapt.api.world; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.volmlib.util.io.IO; + +import java.io.File; +import java.util.UUID; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class PlayerDataPersistenceQueue implements AutoCloseable { + private static final long DEFAULT_SHUTDOWN_TIMEOUT_MS = 30_000L; + + private final ExecutorService ioExecutor; + private final AtomicBoolean acceptingTasks = new AtomicBoolean(true); + + public PlayerDataPersistenceQueue() { + ioExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { + private int tid = 0; + + @Override + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable, "Adapt PlayerData IO " + (++tid)); + thread.setDaemon(true); + thread.setUncaughtExceptionHandler((t, e) -> + Adapt.warn("Uncaught async persistence exception in " + t.getName() + ": " + e.getMessage())); + return thread; + } + }); + } + + public void queueSave(UUID uuid, String json, File localFile) { + submit("save", uuid, () -> { + if (AdaptConfig.get().isUseSql()) { + if (Adapt.instance.getRedisSync() != null) { + Adapt.instance.getRedisSync().publish(uuid, json); + } + if (Adapt.instance.getSqlManager() != null) { + Adapt.instance.getSqlManager().updateData(uuid, json); + } + return; + } + + IO.writeAll(localFile, json); + }); + } + + public void queueDelete(UUID uuid, File localFile) { + submit("delete", uuid, () -> { + if (localFile.exists() && !localFile.delete()) { + Adapt.verbose("Failed to delete local player data file " + localFile.getAbsolutePath()); + } + + if (AdaptConfig.get().isUseSql() && Adapt.instance.getSqlManager() != null) { + Adapt.instance.getSqlManager().delete(uuid); + } + }); + } + + public void flushAndShutdown(long timeoutMs) { + acceptingTasks.set(false); + ioExecutor.shutdown(); + try { + if (!ioExecutor.awaitTermination(timeoutMs, TimeUnit.MILLISECONDS)) { + Adapt.warn("Timed out waiting for player data persistence queue to drain. Forcing shutdown."); + ioExecutor.shutdownNow(); + ioExecutor.awaitTermination(Math.max(1000, timeoutMs / 2), TimeUnit.MILLISECONDS); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Adapt.warn("Interrupted while shutting down player data persistence queue."); + ioExecutor.shutdownNow(); + } + } + + @Override + public void close() { + flushAndShutdown(DEFAULT_SHUTDOWN_TIMEOUT_MS); + } + + private void submit(String operation, UUID uuid, ThrowingRunnable runnable) { + if (!acceptingTasks.get()) { + return; + } + + try { + ioExecutor.execute(() -> { + try { + runnable.run(); + } catch (Throwable e) { + Adapt.warn("Failed to " + operation + " player data for " + uuid + ": " + e.getMessage()); + } + }); + } catch (RejectedExecutionException ignored) { + Adapt.verbose("Rejected player data " + operation + " task for " + uuid + " because the queue is shutting down."); + } + } + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } +} diff --git a/src/main/java/art/arcane/adapt/api/world/PlayerSkillLine.java b/src/main/java/art/arcane/adapt/api/world/PlayerSkillLine.java new file mode 100644 index 000000000..bce01355d --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/world/PlayerSkillLine.java @@ -0,0 +1,559 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.world; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.notification.ActionBarNotification; +import art.arcane.adapt.api.notification.Notifier; +import art.arcane.adapt.api.notification.SoundNotification; +import art.arcane.adapt.api.notification.TitleNotification; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.api.xp.XPMultiplier; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.math.M; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bukkit.Sound; + +@Data +@NoArgsConstructor +public class PlayerSkillLine { + private static final KMap SKILL_ADVANCEMENT_KEYS = new KMap<>(); + private static final KMap ADAPTATION_ADVANCEMENT_KEYS = new KMap<>(); + private final KMap storage = new KMap<>(); + private final KMap adaptations = new KMap<>(); + private final KList multipliers = new KList<>(); + private final KMap activityStaleness = new KMap<>(); + private String line = ""; + private double xp = 0; + private double lastXP = 0; + private long knowledge = 0; + private double multiplier = 1D; + private double freshness = 1D; + private double rfreshness = 1D; + private int lastLevel = 0; + private long last = M.ms(); + private int monotonyCounter = 0; + private long lastXpTimestamp = 0; + private double monotonyMultiplier = 1.0; + private RewardStalenessState skillStaleness = new RewardStalenessState(); + private long lastStalenessCleanup = 0; + private transient double pooledXp = 0; + private transient double pooledNotifyXp = 0; + private transient long poolStartedAt = 0; + private transient long poolLastEarnAt = 0; + private transient long inspiredPendingAt = 0; + private transient long inspiredLastNotifyAt = 0; + + private static double diff(long a, long b) { + return Math.abs(a - b / (double) (a == 0 ? 1 : a)); + } + + public void update(AdaptPlayer p, String line, PlayerData data) { + flushPoolIfReady(p); + pulseInspired(p); + grantSkillsAndAdaptations(p, line); + checkMaxLevel(p, line); + updateFreshness(); + updateMultiplier(data); + updateEarnedXP(p, line); + updateLevel(p, line, data); + } + + private void grantSkillsAndAdaptations(AdaptPlayer p, String line) { + if (!AdaptConfig.get().isAdvancements()) { + return; + } + + String skillAdvancement = SKILL_ADVANCEMENT_KEYS.computeIfAbsent(line, l -> "skill_" + l); + if (!p.getData().isGranted(skillAdvancement)) { + p.getAdvancementHandler().grant(skillAdvancement); + } + + for (String i : getAdaptations().keySet()) { + String adaptationAdvancement = ADAPTATION_ADVANCEMENT_KEYS.computeIfAbsent(i, a -> "adaptation_" + a); + if (!p.getData().isGranted(adaptationAdvancement)) { + p.getAdvancementHandler().grant(adaptationAdvancement); + } + } + } + + private void checkMaxLevel(AdaptPlayer p, String line) { + if (!p.isBusy() && getXp() > XP.getXpForLevel(AdaptConfig.get().experienceMaxLevel)) { + p.getData().addWisdom(); + Adapt.warn("A Player has reached the maximum level of " + AdaptConfig.get().experienceMaxLevel + " and has been granted 1 wisdom, Dropping Level to " + lastLevel); + setXp(XP.getXpForLevel(AdaptConfig.get().experienceMaxLevel - 1)); + } + } + + private void updateFreshness() { + double max = 1D + (getLevel() * 0.004); + + freshness += (0.08 * freshness) + 0.003; + if (freshness > max) freshness = max; + if (freshness < 0.01) freshness = 0.01; + if (freshness < rfreshness) + rfreshness -= ((rfreshness - freshness) * 0.003); + if (freshness > rfreshness) rfreshness += (freshness - rfreshness) * 0.265; + } + + private void updateMultiplier(PlayerData data) { + double m = rfreshness; + for (int i = multipliers.size() - 1; i >= 0; i--) { + XPMultiplier active = multipliers.get(i); + if (active == null || active.isExpired()) { + multipliers.remove(i); + continue; + } + m += active.getMultiplier(); + } + + m = Math.max(0.01, Math.min(m, 1000)); + multiplier = m * data.getMultiplier(); + } + + private void updateEarnedXP(AdaptPlayer p, String line) { + double earned = xp - lastXP; + if (earned > p.getServer().getSkillRegistry().getSkill(line).getMinXp()) + lastXP = xp; + } + + private void updateLevel(AdaptPlayer p, String line, PlayerData data) { + if (lastLevel < getLevel()) { + long kb = getKnowledge(); + for (int i = lastLevel; i < getLevel(); i++) { + giveKnowledge((i / 13) + 1); + p.getData().giveMasterXp((i * AdaptConfig.get().getPlayerXpPerSkillLevelUpLevelMultiplier()) + AdaptConfig.get().getPlayerXpPerSkillLevelUpBase()); + } + + if (AdaptConfig.get().isActionbarNotifyLevel()) + notifyLevel(p, getLevel(), getKnowledge()); + lastLevel = getLevel(); + } + } + + public void giveXP(Notifier p, double xp) { + giveXP(p, xp, null); + } + + public void giveXP(Notifier p, double xp, String rewardKey) { + freshness -= 0.012 + (xp * 0.00025); + + long now = System.currentTimeMillis(); + lastXpTimestamp = now; + monotonyCounter++; + monotonyMultiplier = computeStalenessMultiplier(xp, rewardKey, now); + + xp = multiplier * monotonyMultiplier * xp; + + if (AdaptConfig.get().getXpIntegrity().isPooledPayoutEnabled()) { + if (pooledXp == 0) { + poolStartedAt = now; + } + pooledXp += xp; + poolLastEarnAt = now; + if (p != null) { + last = M.ms(); + pooledNotifyXp += xp; + } + return; + } + + this.xp += xp; + + if (p != null) { + last = M.ms(); + if (AdaptConfig.get().isActionbarNotifyXp()) { + p.notifyXP(line, xp); + } + } + } + + private void flushPoolIfReady(AdaptPlayer p) { + if (pooledXp == 0 && pooledNotifyXp == 0) { + return; + } + + if (pooledXp != 0) { + AdaptConfig.XpIntegrity integrity = AdaptConfig.get().getXpIntegrity(); + long now = System.currentTimeMillis(); + if (now - poolStartedAt < integrity.getPooledWindowMillis() && now - poolLastEarnAt < integrity.getPooledIdleFlushMillis()) { + return; + } + } + + flushXpPool(p.getNot()); + } + + public void flushXpPool(Notifier p) { + if (pooledXp == 0 && pooledNotifyXp == 0) { + return; + } + + double gained = pooledXp; + pooledXp = 0; + poolStartedAt = 0; + poolLastEarnAt = 0; + this.xp += gained; + + if (p == null) { + return; + } + + double display = pooledNotifyXp; + pooledNotifyXp = 0; + if (display != 0 && AdaptConfig.get().isActionbarNotifyXp()) { + p.notifyXP(line, display); + } + } + + private void pulseInspired(AdaptPlayer p) { + if (inspiredPendingAt == 0) { + return; + } + + inspiredPendingAt = 0; + long now = System.currentTimeMillis(); + if (now - inspiredLastNotifyAt < 30000) { + return; + } + + Skill skill = p.getServer().getSkillRegistry().getSkill(line); + if (skill == null) { + return; + } + + inspiredLastNotifyAt = now; + p.getActionBarNotifier().queue(ActionBarNotification.builder() + .duration(1250) + .group("inspired" + line) + .title(skill.getDisplayName() + C.RESET + " " + C.GREEN + Localizer.dLocalize("snippets.xp.inspired")) + .build()); + } + + public void relaxStalenessForActivitySwitch() { + AdaptConfig.FarmPrevention prevention = AdaptConfig.get().getFarmPrevention(); + if (prevention == null || !prevention.isEnabled()) { + monotonyCounter = 0; + monotonyMultiplier = 1.0; + return; + } + + double factor = clamp(prevention.getCrossSkillRecoveryFactor(), 0.0, 1.0); + double curve = prevention.getSkillDecayCurve(); + RewardStalenessState state = ensureSkillStaleness(); + if (factor < 1.0 && curve > 0 && state.getPressure() > curve * 0.25 && AdaptConfig.get().getXpIntegrity().isInspiredNotifyEnabled()) { + inspiredPendingAt = System.currentTimeMillis(); + } + applyRecoveryFactor(state, factor); + for (RewardStalenessState activityState : activityStaleness.values()) { + applyRecoveryFactor(activityState, factor); + } + monotonyCounter = 0; + } + + private double computeStalenessMultiplier(double awardXp, String rewardKey, long now) { + if (awardXp <= 0) { + return 1.0; + } + + AdaptConfig.FarmPrevention prevention = AdaptConfig.get().getFarmPrevention(); + if (prevention == null || !prevention.isEnabled()) { + return 1.0; + } + + double skillGain = prevention.getSkillBasePressure() + (awardXp * prevention.getSkillXpPressure()); + double skillMultiplier = applyStaleness( + ensureSkillStaleness(), + now, + skillGain, + prevention.getSkillRecoveryMillis(), + prevention.getSkillDecayCurve(), + prevention.getSkillFloorMultiplier() + ); + + double activityMultiplier = 1.0; + if (prevention.isPerActivityTracking()) { + String normalizedRewardKey = normalizeRewardKey(rewardKey); + if (normalizedRewardKey != null) { + cleanupActivityStaleness(now, prevention.getActivityStateTtlMillis()); + RewardStalenessState activityState = activityStaleness.computeIfAbsent(normalizedRewardKey, k -> new RewardStalenessState()); + double activityGain = prevention.getActivityBasePressure() + (awardXp * prevention.getActivityXpPressure()); + activityMultiplier = applyStaleness( + activityState, + now, + activityGain, + prevention.getActivityRecoveryMillis(), + prevention.getActivityDecayCurve(), + prevention.getActivityFloorMultiplier() + ); + } + } + + double floor = clamp(prevention.getSkillFloorMultiplier(), 0.0, 1.0); + if (prevention.isPerActivityTracking()) { + floor = clamp(floor * prevention.getActivityFloorMultiplier(), 0.0, 1.0); + } + return clamp(skillMultiplier * activityMultiplier, floor, 1.0); + } + + private RewardStalenessState ensureSkillStaleness() { + if (skillStaleness == null) { + skillStaleness = new RewardStalenessState(); + } + return skillStaleness; + } + + private double applyStaleness(RewardStalenessState state, long now, double gain, long recoveryMillis, double curve, double floor) { + if (state == null) { + return 1.0; + } + + decayState(state, now, recoveryMillis); + state.setPressure(clamp(state.getPressure() + Math.max(0.0, gain), 0.0, 100000.0)); + state.setLastAwardAt(now); + + double clampedFloor = clamp(floor, 0.0, 1.0); + if (curve <= 0) { + return 1.0; + } + + double scaled = Math.exp(-state.getPressure() / curve); + return clamp(clampedFloor + ((1.0 - clampedFloor) * scaled), clampedFloor, 1.0); + } + + private void decayState(RewardStalenessState state, long now, long recoveryMillis) { + if (state == null || recoveryMillis <= 0) { + return; + } + + long lastAward = state.getLastAwardAt(); + if (lastAward <= 0) { + state.setLastAwardAt(now); + return; + } + + long elapsed = Math.max(0, now - lastAward); + if (elapsed == 0) { + return; + } + + double decay = Math.exp(-(double) elapsed / (double) recoveryMillis); + state.setPressure(Math.max(0.0, state.getPressure() * decay)); + } + + private void cleanupActivityStaleness(long now, long ttl) { + if (ttl <= 0) { + return; + } + if (now - lastStalenessCleanup < 15000) { + return; + } + + activityStaleness.entrySet().removeIf(entry -> { + RewardStalenessState state = entry.getValue(); + return state == null || (state.getLastAwardAt() > 0 && now - state.getLastAwardAt() > ttl); + }); + lastStalenessCleanup = now; + } + + private void applyRecoveryFactor(RewardStalenessState state, double factor) { + if (state == null) { + return; + } + state.setPressure(Math.max(0.0, state.getPressure() * factor)); + } + + private String normalizeRewardKey(String rewardKey) { + if (rewardKey == null) { + return null; + } + String normalized = rewardKey.trim(); + return normalized.isEmpty() ? null : normalized; + } + + private double clamp(double value, double min, double max) { + return Math.max(min, Math.min(max, value)); + } + + public void giveXPFresh(Notifier p, double xp) { + xp = multiplier * xp; + this.xp += xp; + + if (p != null) { + last = M.ms(); + if (AdaptConfig.get().isActionbarNotifyXp()) { + p.notifyXP(line, xp); + } + } + } + + public boolean hasEarnedWithin(long ms) { + return M.ms() - last < ms; + } + + public PlayerAdaptation getAdaptation(String id) { + return adaptations.get(id); + } + + public int getAdaptationLevel(String id) { + PlayerAdaptation a = getAdaptation(id); + + if (a == null) { + return 0; + } + + return a.getLevel(); + } + + public void setAdaptation(Adaptation a, int level) { + if (a == null) { + return; + } + + int clamped = Math.max(0, Math.min(level, a.getMaxLevel())); + if (clamped <= 0) { + adaptations.remove(a.getName()); + return; + } + + PlayerAdaptation v = new PlayerAdaptation(); + v.setId(a.getName()); + v.setLevel(clamped); + adaptations.put(a.getName(), v); + } + + public Skill getRawSkill(AdaptPlayer p) { + return p.getServer().getSkillRegistry().getSkill(line); + } + + private void notifyLevel(AdaptPlayer p, double lvl, long kn) { +// Skill s = p.getServer().getSkillRegistry().getSkill(getLine()); + if (lvl % 10 == 0) { + p.getNot().queue(SoundNotification.builder() + .sound(Sound.UI_TOAST_CHALLENGE_COMPLETE) + .volume(1f) + .pitch(1.35f) + .group("lvl" + getLine()) + .build(), SoundNotification.builder() + .sound(Sound.UI_TOAST_CHALLENGE_COMPLETE) + .volume(1f) + .pitch(0.75f) + .group("lvl" + getLine()) + .build(), TitleNotification.builder() + .in(250) + .stay(1450) + .out(2250) + .group("lvl" + getLine()) + .title("") + .subtitle(p.getServer().getSkillRegistry().getSkill(getLine()).getDisplayName(getLevel())) + .build()); + p.getActionBarNotifier().queue( + ActionBarNotification.builder() + .duration(450) + .group("know" + getLine()) + .title(kn + " " + p.getServer().getSkillRegistry().getSkill(getLine()).getShortName() + " Knowledge") + .build()); + + } else { + p.getActionBarNotifier().queue( + SoundNotification.builder() + .sound(Sound.BLOCK_AMETHYST_BLOCK_BREAK) + .volume(1f) + .pitch(1.74f) + .group("lvl" + getLine()) + .build(), + SoundNotification.builder() + .sound(Sound.BLOCK_AMETHYST_BLOCK_CHIME) + .volume(1f) + .pitch(0.74f) + .group("lvl" + getLine()) + .build(), + ActionBarNotification.builder() + .duration(450) + .group("lvl" + getLine()) + .title(p.getServer().getSkillRegistry().getSkill(getLine()).getDisplayName(getLevel())) + .build()); + } + + lastLevel = (int) Math.floor(XP.getLevelForXp(getXp())); + } + + public void giveKnowledge(long points) { + this.knowledge += points; + } + + public double getMinimumXPForLevel() { + return XP.getXpForLevel(getLevel()); + } + + public double getXPForLevelUpAbsolute() { + return getMaximumXPForLevel() - getXp(); + } + + public double getXPForLevelUp() { + return getMaximumXPForLevel() - getMinimumXPForLevel(); + } + + public double getMaximumXPForLevel() { + return XP.getXpForLevel(getLevel()); + } + + public double getAbsoluteLevel() { + return XP.getLevelForXp(xp); + } + + public double getLevelProgress() { + return getAbsoluteLevel() - getLevel(); + } + + public double getLevelProgressRemaining() { + return 1D - getLevelProgress(); + } + + public int getLevel() { + return (int) Math.floor(getAbsoluteLevel()); + } + + public void boost(double v, int i) { + multipliers.add(new XPMultiplier(v, i)); + } + + public boolean spendKnowledge(int c) { + if (getKnowledge() >= c) { + setKnowledge(getKnowledge() - c); + return true; + } + + return false; + } + + @Data + @NoArgsConstructor + public static class RewardStalenessState { + private double pressure = 0; + private long lastAwardAt = 0; + } +} diff --git a/src/main/java/art/arcane/adapt/api/xp/Curves.java b/src/main/java/art/arcane/adapt/api/xp/Curves.java new file mode 100644 index 000000000..2ccd1469a --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/Curves.java @@ -0,0 +1,204 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import lombok.Getter; + +import java.util.function.Function; + +public enum Curves { + // Strange ones + QLOG(resolved(level -> Math.pow(level, 2) * Math.log(level), xp -> Math.sqrt(xp / Math.log(xp)), 0.001)), + ELIN(resolved(level -> 1000 * Math.exp(0.001 * level), xp -> Math.log(xp / 1000) / 0.001, 0.001)), + CUBRT(resolved(level -> Math.pow(level, 1 / 3.0), xp -> Math.pow(xp, 3), 0.001)), + HYPER(resolved(level -> 1000 / (2 - level), xp -> 2 - (1000 / xp), 0.001)), + SIGM(resolved(level -> 1000 / (1 + Math.exp(-0.01 * (level - 50))), xp -> 50 + Math.log(xp / (1000 - xp)) / -0.01, 0.001)), + + // Normal ones + X1D2(resolved(level -> Math.pow(level, 1.2), xp -> Math.pow(xp, 1D / 1.2D), 0.001)), + X1D5(resolved(level -> Math.pow(level, 1.5), xp -> Math.pow(xp, 1D / 1.5D), 0.001)), + X2(resolved(level -> Math.pow(level, 2), xp -> Math.pow(xp, 1D / 2D), 0.001)), + X3(resolved(level -> Math.pow(level, 3), xp -> Math.pow(xp, 1D / 3D), 0.001)), + X4(resolved(level -> Math.pow(level, 4), xp -> Math.pow(xp, 1D / 4D), 0.001)), + X5(resolved(level -> Math.pow(level, 5), xp -> Math.pow(xp, 1D / 5D), 0.001)), + X6(resolved(level -> Math.pow(level, 6), xp -> Math.pow(xp, 1D / 6D), 0.001)), + X7(resolved(level -> Math.pow(level, 7), xp -> Math.pow(xp, 1D / 7D), 0.001)), + L1K(resolved(level -> level * 1000D, xp -> xp / 1000D, 0.001)), + L4K(resolved(level -> level * 4000D, xp -> xp / 4000D, 0.001)), + L8K(resolved(level -> level * 8000D, xp -> xp / 8000D, 0.001)), + L16K(resolved(level -> level * 16000D, xp -> xp / 16000D, 0.001)), + + // Game ones + SKYRIM(SkyrimNewtonCurve.create()), + WOW(WOWNewtonCurve.create()), + + // Adapt ones + XL05L7(level -> ((537 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL1L7(level -> ((1337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL15L7(level -> ((1837 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL2L7(level -> ((2337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL3L7(level -> ((3337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL4L7(level -> ((4337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL5L7(level -> ((5337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL6L7(level -> ((6337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL7L7(level -> ((7337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL8L7(level -> ((8337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL9L7(level -> ((9337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL20L7(level -> ((20337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL40L7(level -> ((40337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL80L7(level -> ((80337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL160L7(level -> ((160337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + XL100L7(level -> ((1000337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), + ADAPT_BALANCED(resolved( + level -> 1200 * level + 100 * Math.pow(level, 2), + xp -> (-1200 + Math.sqrt(1440000 + 400 * xp)) / 200, + 0.001 + )), + + LINEAR_EXPONENTIAL_1(resolved(level -> 1000 * level + 100 * Math.pow(level, 2), xp -> { + double a = 1000; + double b = 100; + return (-a + Math.sqrt(a * a + 4 * b * xp)) / (2 * b); + }, 0.001)), + + LINEAR_EXPONENTIAL_2(resolved(level -> 2000 * level + 50 * Math.pow(level, 2.5), xp -> { + double a = 2000; + double b = 50; + double lvl = (-a + Math.sqrt(a * a + 4 * b * xp)) / (2 * b); + return Math.pow((xp - a * lvl) / b, 1 / 2.5); + }, 0.001)), + + LINEAR_EXPONENTIAL_3(resolved(level -> 500 * level + 200 * Math.pow(level, 1.5), xp -> { + double a = 500; + double b = 200; + double lvl = (-a + Math.sqrt(a * a + 4 * b * xp)) / (2 * b); + return Math.pow((xp - a * lvl) / b, 1 / 1.5); + }, 0.001)); + + + @Getter + private final NewtonCurve curve; + + Curves(NewtonCurve curve) { + this.curve = curve; + } + + private static NewtonCurve resolved(Function xpForLevel, Function levelForXP, double maxError) { + return new NewtonCurve() { + @Override + public double getXPForLevel(double level) { + return xpForLevel.apply(level); + } + + @Override + public double computeLevelForXP(double xp, double maxError) { + return levelForXP.apply(xp); + } + }; + } + + public static class SkyrimNewtonCurve { + public static NewtonCurve create() { + Function xpForLevel = level -> { + double f = 0; + for (int i = 1; i < level; i++) { + f += getNextLevelCost(i); + } + return f; + }; + + Function levelForXP = xp -> { + double currentLevel = 1; + while (xp >= getNextLevelCost(currentLevel)) { + xp -= getNextLevelCost(currentLevel); + currentLevel++; + } + return currentLevel; + }; + + return Curves.resolved(xpForLevel, levelForXP, 0.001); + } + + private static double getNextLevelCost(double currentLevel) { + return Math.pow(currentLevel - 1, 1.95) + 300; + } + } + + + public class WOWNewtonCurve { + public static NewtonCurve create() { + Function xpForLevel = level -> { + double f = 0; + for (int i = 1; i < level; i++) { + f += getNextLevelCost(i); + } + return f; + }; + + Function levelForXP = xp -> { + double currentLevel = 1; + while (xp >= getNextLevelCost(currentLevel)) { + xp -= getNextLevelCost(currentLevel); + currentLevel++; + } + return currentLevel; + }; + + return Curves.resolved(xpForLevel, levelForXP, 0.001); + } + + private static double getNextLevelCost(double currentLevel) { + return ((8 * currentLevel) + getDiff(currentLevel)) * getMXP(currentLevel) * getDRF(currentLevel); + } + + private static double getMXP(double currentLevel) { + return 235 + (5 * currentLevel); + } + + private static double getDRF(double currentLevel) { + if (currentLevel >= 11 && currentLevel <= 27) { + return (1 - (currentLevel - 10) / 100); + } + + if (currentLevel >= 28 && currentLevel <= 59) { + return 0.82; + } + + return 1; + } + + private static double getDiff(double currentLevel) { + if (currentLevel <= 28) { + return 0; + } + if (currentLevel == 29) { + return 1; + } + if (currentLevel == 30) { + return 3; + } + if (currentLevel == 31) { + return 6; + } + + return 5 * (Math.min(currentLevel, 59) - 30); + } + } + +} diff --git a/src/main/java/art/arcane/adapt/api/xp/NewtonCurve.java b/src/main/java/art/arcane/adapt/api/xp/NewtonCurve.java new file mode 100644 index 000000000..f9a986425 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/NewtonCurve.java @@ -0,0 +1,60 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import art.arcane.adapt.AdaptConfig; + +@FunctionalInterface +public interface NewtonCurve { + double getXPForLevel(double level); + + default double computeLevelForXP(double xp, double maxError) { + double div = 2; + int iterations = 0; + double jumpSize = 100; + double cursor = 0; + double test; + boolean last = false; + + while (jumpSize > maxError && iterations < 100) { + iterations++; + test = getXPForLevel(cursor); + if (test < xp) { + if (last) { + jumpSize /= div; + } + last = false; + cursor += jumpSize; + } else { + if (!last) { + jumpSize /= div; + } + + last = true; + cursor -= jumpSize; + } + // Check if the level has exceeded the maximum allowed (1000) + if (cursor > AdaptConfig.get().experienceMaxLevel) { + cursor = AdaptConfig.get().experienceMaxLevel; + break; + } + } + return cursor; + } +} diff --git a/src/main/java/com/volmit/adapt/api/xp/ResolvedNewtonCurve.java b/src/main/java/art/arcane/adapt/api/xp/ResolvedNewtonCurve.java similarity index 85% rename from src/main/java/com/volmit/adapt/api/xp/ResolvedNewtonCurve.java rename to src/main/java/art/arcane/adapt/api/xp/ResolvedNewtonCurve.java index af3f673d4..a5a3e4684 100644 --- a/src/main/java/com/volmit/adapt/api/xp/ResolvedNewtonCurve.java +++ b/src/main/java/art/arcane/adapt/api/xp/ResolvedNewtonCurve.java @@ -16,12 +16,12 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.api.xp; +package art.arcane.adapt.api.xp; public interface ResolvedNewtonCurve extends NewtonCurve { - double getLevelForXP(double xp); + double getLevelForXP(double xp); - default double computeLevelForXP(double xp, double maxError) { - return getLevelForXP(xp); - } + default double computeLevelForXP(double xp, double maxError) { + return getLevelForXP(xp); + } } diff --git a/src/main/java/art/arcane/adapt/api/xp/SpatialXP.java b/src/main/java/art/arcane/adapt/api/xp/SpatialXP.java new file mode 100644 index 000000000..54fde9b94 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/SpatialXP.java @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import art.arcane.adapt.api.skill.Skill; +import art.arcane.volmlib.util.math.M; +import lombok.Data; +import org.bukkit.Location; + +@Data +public class SpatialXP { + private Location location; + private double radius; + private Skill skill; + private double xp; + private long ms; + + public SpatialXP(Location l, Skill s, double xp, double radius, long duration) { + this.location = l; + this.skill = s; + this.xp = xp; + this.ms = M.ms() + duration; + this.radius = radius; + } +} diff --git a/src/main/java/art/arcane/adapt/api/xp/XP.java b/src/main/java/art/arcane/adapt/api/xp/XP.java new file mode 100644 index 000000000..22dba40f3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/XP.java @@ -0,0 +1,117 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.volmlib.util.math.M; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class XP { + public static void xp(Player p, Skill skill, double xp) { + xp(Adapt.instance.getAdaptServer().getPlayer(p), skill, xp, null); + } + + public static void xp(Player p, Skill skill, double xp, String rewardKey) { + xp(Adapt.instance.getAdaptServer().getPlayer(p), skill, xp, rewardKey); + } + + public static void xp(AdaptPlayer p, Skill skill, double xp) { + xp(p, skill, xp, null); + } + + public static void xp(AdaptPlayer p, Skill skill, double xp, String rewardKey) { + PlayerSkillLine skillLine = p.getSkillLine(skill.getName()); + if (skillLine != null) { + p.getData().resetMonotonyForOtherSkills(skill.getName()); + skillLine.giveXP(p.getNot(), xp, rewardKey); + } + } + + public static void xpSilent(Player p, Skill skill, double xp) { + xpSilent(Adapt.instance.getAdaptServer().getPlayer(p), skill, xp, null); + } + + public static void xpSilent(Player p, Skill skill, double xp, String rewardKey) { + xpSilent(Adapt.instance.getAdaptServer().getPlayer(p), skill, xp, rewardKey); + } + + public static void xpSilent(AdaptPlayer p, Skill skill, double xp) { + xpSilent(p, skill, xp, null); + } + + public static void xpSilent(AdaptPlayer p, Skill skill, double xp, String rewardKey) { + if (p.getSkillLine(skill.getName()) != null) { + p.getData().resetMonotonyForOtherSkills(skill.getName()); + p.getSkillLine(skill.getName()).giveXP(null, xp, rewardKey); + } + } + + public static void spatialXP(Location l, Skill skill, double xp, int rad, long duration) { + Adapt.instance.getAdaptServer().offer(new SpatialXP(l, skill, xp, rad, duration)); + } + + public static void wisdom(Player p, long k) { + wisdom(Adapt.instance.getAdaptServer().getPlayer(p), k); + } + + public static void wisdom(AdaptPlayer p, long k) { + p.getData().setWisdom(p.getData().getWisdom() + k); + } + + public static void knowledge(Player p, Skill skill, long k) { + knowledge(Adapt.instance.getAdaptServer().getPlayer(p), skill, k); + } + + public static void knowledge(AdaptPlayer p, Skill skill, long k) { + p.getSkillLine(skill.getName()).giveKnowledge(k); + } + + public static void boostXP(Player p, Skill skill, double percentChange, int durationMS) { + boostXP(Adapt.instance.getAdaptServer().getPlayer(p), skill, percentChange, durationMS); + } + + public static void boostXP(AdaptPlayer p, Skill skill, double percentChange, int durationMS) { + p.getSkillLine(skill.getName()).boost(percentChange, durationMS); + } + + public static double getXpUntilLevelUp(double xp) { + double level = getLevelForXp(xp); + double xa = getXpForLevel((int) level); + double xb = getXpForLevel((int) level + 1); + return M.lerp(xb - xa, 0, level - (int) level); + } + + public static double getLevelProgress(double xp) { + double level = getLevelForXp(xp); + return level - (int) level; + } + + public static double getXpForLevel(double level) { + return AdaptConfig.get().getXpCurve().getCurve().getXPForLevel(level); + } + + public static double getLevelForXp(double xp) { + return AdaptConfig.get().getXpCurve().getCurve().computeLevelForXP(xp, 0.000001); + } +} diff --git a/src/main/java/art/arcane/adapt/api/xp/XPMultiplier.java b/src/main/java/art/arcane/adapt/api/xp/XPMultiplier.java new file mode 100644 index 000000000..72026e468 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/XPMultiplier.java @@ -0,0 +1,39 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import art.arcane.volmlib.util.math.M; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Data +public class XPMultiplier { + private double multiplier = 0D; + private long goodFor = M.ms() + 10000; + + public XPMultiplier(double percentChange, long duration) { + this.multiplier = percentChange; + this.goodFor = M.ms() + duration; + } + + public boolean isExpired() { + return M.ms() > goodFor; + } +} diff --git a/src/main/java/art/arcane/adapt/api/xp/XpNovelty.java b/src/main/java/art/arcane/adapt/api/xp/XpNovelty.java new file mode 100644 index 000000000..80efad49d --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/XpNovelty.java @@ -0,0 +1,350 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.volmlib.util.math.M; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public final class XpNovelty { + private static final Map STATES = new ConcurrentHashMap<>(); + private static final String NULL_KEY = "null"; + private static final int STILLNESS_RING = 32; + private static final float STILLNESS_YAW_RANGE = 10.0f; + private static final double ENTROPY_SATURATION_KEYS = 3.0; + + private XpNovelty() { + } + + public static double noveltyMultiplier(Player p, Location at, String rewardKey) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isNoveltyEnabled() || p == null) { + return 1.0; + } + + State state = stateOf(p.getUniqueId(), config); + long now = M.ms(); + synchronized (state) { + double spatial = 1.0; + if (at != null && at.getWorld() != null) { + long key = cellKey(at.getWorld(), at.getBlockX(), at.getBlockY(), at.getBlockZ(), config.getSpatialCellShift()); + spatial = state.spatialVisit(key, now, config); + } + + String entropyKey = rewardKey == null ? NULL_KEY : rewardKey; + double entropy = state.entropySample(entropyKey, config); + double combined = spatial * entropy; + + if (config.isStillnessEnabled()) { + Location pos = p.getLocation(state.scratch); + if (state.stillnessSample(pos.getX(), pos.getY(), pos.getZ(), pos.getYaw(), now, config)) { + combined = Math.min(combined, config.getStillnessFloorMultiplier()); + } + } + + double floor = config.getSpatialFloorMultiplier() * config.getEntropyFloorMultiplier() * config.getStillnessFloorMultiplier(); + return Math.max(combined, floor); + } + } + + public static double adjacencyBonusMultiplier(Player p, Block placed) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isAdjacencyBonusEnabled() || p == null || placed == null) { + return 1.0; + } + + boolean touching = XpProvenance.isPlayerPlaced(placed.getRelative(BlockFace.DOWN)) + || XpProvenance.isPlayerPlaced(placed.getRelative(BlockFace.UP)) + || XpProvenance.isPlayerPlaced(placed.getRelative(BlockFace.NORTH)) + || XpProvenance.isPlayerPlaced(placed.getRelative(BlockFace.SOUTH)) + || XpProvenance.isPlayerPlaced(placed.getRelative(BlockFace.EAST)) + || XpProvenance.isPlayerPlaced(placed.getRelative(BlockFace.WEST)); + State state = stateOf(p.getUniqueId(), config); + double perStreak = config.getAdjacencyBonusPerStreak(); + double max = config.getAdjacencyBonusMax(); + synchronized (state) { + if (touching) { + long key = cellKey(placed.getWorld(), placed.getX(), placed.getY(), placed.getZ(), config.getSpatialCellShift()); + int visits = state.spatialPeek(key, M.ms(), config); + if (perStreak > 0.0 && visits * config.getSpatialRepeatDecay() <= 1.0 && state.adjacencyStreak * perStreak < max) { + state.adjacencyStreak++; + } + } else { + state.adjacencyStreak >>= 1; + } + + double bonus = Math.min(max, state.adjacencyStreak * perStreak); + return 1.0 + Math.max(0.0, bonus); + } + } + + public static double fieldCycleMultiplier(Player p, Block crop) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isNoveltyEnabled() || p == null || crop == null) { + return 1.0; + } + + State state = stateOf(p.getUniqueId(), config); + long now = M.ms(); + long key = cellKey(crop.getWorld(), crop.getX(), crop.getY(), crop.getZ(), config.getSpatialCellShift()); + synchronized (state) { + return state.fieldCycle(key, now, config); + } + } + + public static void clear(UUID id) { + STATES.remove(id); + } + + private static State stateOf(UUID id, AdaptConfig.XpIntegrity config) { + State state = STATES.get(id); + if (state != null) { + return state; + } + + State created = new State(config); + State prior = STATES.putIfAbsent(id, created); + return prior == null ? created : prior; + } + + private static long cellKey(World world, int x, int y, int z, int shift) { + long cx = x >> shift; + long cy = y >> shift; + long cz = z >> shift; + long key = ((cx & 0x3FFFFFFL) << 38) | ((cy & 0xFFFL) << 26) | (cz & 0x3FFFFFFL); + return key ^ ((long) world.hashCode() * 0x9E3779B97F4A7C15L); + } + + private static final class Cell { + private int count; + private long touch; + } + + private static final class LruMap extends LinkedHashMap { + private final int cap; + + private LruMap(int cap) { + super(16, 0.75f, true); + this.cap = Math.max(1, cap); + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > cap; + } + } + + private static final class State { + private final LruMap spatialCells; + private final LruMap fieldCells; + private final String[] window; + private final HashMap counts; + private final long[] times; + private final Location scratch; + private int entropyHead; + private int entropyFilled; + private int distinct; + private int stillHead; + private int stillCount; + private int runEvents; + private double minX; + private double maxX; + private double minY; + private double maxY; + private double minZ; + private double maxZ; + private float minYaw; + private float maxYaw; + private int adjacencyStreak; + + private State(AdaptConfig.XpIntegrity config) { + this.spatialCells = new LruMap(config.getSpatialCellCap()); + this.fieldCells = new LruMap(config.getSpatialCellCap()); + int windowSize = config.getEntropyWindow(); + this.window = windowSize > 1 ? new String[windowSize] : null; + this.counts = new HashMap<>(); + this.times = new long[STILLNESS_RING]; + this.scratch = new Location(null, 0.0, 0.0, 0.0); + } + + private double spatialVisit(long key, long now, AdaptConfig.XpIntegrity config) { + Cell cell = spatialCells.get(key); + if (cell == null) { + cell = new Cell(); + spatialCells.put(key, cell); + } + + long ttl = config.getSpatialCellTtlMillis(); + if (ttl > 0 && now - cell.touch > ttl) { + cell.count = 0; + } + + int n = cell.count; + cell.count = n + 1; + cell.touch = now; + double m = 1.0 / (1.0 + config.getSpatialRepeatDecay() * n); + return Math.max(config.getSpatialFloorMultiplier(), m); + } + + private int spatialPeek(long key, long now, AdaptConfig.XpIntegrity config) { + Cell cell = spatialCells.get(key); + if (cell == null) { + return 0; + } + + long ttl = config.getSpatialCellTtlMillis(); + if (ttl > 0 && now - cell.touch > ttl) { + return 0; + } + + return cell.count; + } + + private double entropySample(String key, AdaptConfig.XpIntegrity config) { + if (window == null) { + return 1.0; + } + + if (entropyFilled == window.length) { + String old = window[entropyHead]; + Integer oldCount = counts.get(old); + if (oldCount != null) { + if (oldCount.intValue() <= 1) { + counts.remove(old); + distinct--; + } else { + counts.put(old, oldCount.intValue() - 1); + } + } + } + + window[entropyHead] = key; + entropyHead = entropyHead + 1 == window.length ? 0 : entropyHead + 1; + if (entropyFilled < window.length) { + entropyFilled++; + } + + Integer newCount = counts.get(key); + if (newCount == null) { + counts.put(key, 1); + distinct++; + } else { + counts.put(key, newCount.intValue() + 1); + } + + if (entropyFilled < window.length) { + return 1.0; + } + + double floor = config.getEntropyFloorMultiplier(); + double normalized = (distinct - 1) / (ENTROPY_SATURATION_KEYS - 1.0); + double smooth = normalized >= 1.0 ? 1.0 : Math.sqrt(Math.max(0.0, normalized)); + return floor + (1.0 - floor) * smooth; + } + + private boolean stillnessSample(double x, double y, double z, float yaw, long now, AdaptConfig.XpIntegrity config) { + double eps = config.getStillnessEpsilon(); + if (runEvents == 0) { + beginRun(x, y, z, yaw, now); + return false; + } + + double nMinX = Math.min(minX, x); + double nMaxX = Math.max(maxX, x); + double nMinY = Math.min(minY, y); + double nMaxY = Math.max(maxY, y); + double nMinZ = Math.min(minZ, z); + double nMaxZ = Math.max(maxZ, z); + float nMinYaw = Math.min(minYaw, yaw); + float nMaxYaw = Math.max(maxYaw, yaw); + if (nMaxX - nMinX >= eps || nMaxY - nMinY >= eps || nMaxZ - nMinZ >= eps || nMaxYaw - nMinYaw >= STILLNESS_YAW_RANGE) { + beginRun(x, y, z, yaw, now); + return false; + } + + minX = nMinX; + maxX = nMaxX; + minY = nMinY; + maxY = nMaxY; + minZ = nMinZ; + maxZ = nMaxZ; + minYaw = nMinYaw; + maxYaw = nMaxYaw; + times[stillHead] = now; + stillHead = stillHead + 1 == STILLNESS_RING ? 0 : stillHead + 1; + if (stillCount < STILLNESS_RING) { + stillCount++; + } + runEvents++; + + if (runEvents < config.getStillnessMinEvents()) { + return false; + } + + long oldest = stillCount == STILLNESS_RING ? times[stillHead] : times[0]; + return now - oldest >= config.getStillnessWindowMillis(); + } + + private void beginRun(double x, double y, double z, float yaw, long now) { + minX = x; + maxX = x; + minY = y; + maxY = y; + minZ = z; + maxZ = z; + minYaw = yaw; + maxYaw = yaw; + times[0] = now; + stillHead = 1; + stillCount = 1; + runEvents = 1; + } + + private double fieldCycle(long key, long now, AdaptConfig.XpIntegrity config) { + Cell cell = fieldCells.get(key); + double result = 1.0; + if (cell == null) { + cell = new Cell(); + fieldCells.put(key, cell); + } else { + long cycle = config.getFieldCycleMillis(); + if (cycle > 0) { + long elapsed = now - cell.touch; + if (elapsed < cycle) { + double floor = config.getFieldCycleFloorMultiplier(); + result = floor + (1.0 - floor) * ((double) elapsed / (double) cycle); + } + } + } + + cell.touch = now; + return result; + } + } +} diff --git a/src/main/java/art/arcane/adapt/api/xp/XpNoveltyListener.java b/src/main/java/art/arcane/adapt/api/xp/XpNoveltyListener.java new file mode 100644 index 000000000..fc366961d --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/XpNoveltyListener.java @@ -0,0 +1,31 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +public class XpNoveltyListener implements Listener { + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + XpNovelty.clear(e.getPlayer().getUniqueId()); + } +} diff --git a/src/main/java/art/arcane/adapt/api/xp/XpProvenance.java b/src/main/java/art/arcane/adapt/api/xp/XpProvenance.java new file mode 100644 index 000000000..8c559b30a --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/XpProvenance.java @@ -0,0 +1,127 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.data.unit.PlacementStamp; +import art.arcane.volmlib.util.math.M; +import org.bukkit.block.Block; + +public final class XpProvenance { + private XpProvenance() { + } + + public static void recordPlacement(Block block) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isProvenanceEnabled()) { + return; + } + + WorldData data = WorldData.of(block.getWorld()); + PlacementStamp stamp = data.get(block, PlacementStamp.class); + int brokenAt = stamp == null ? 0 : stamp.getBrokenAt(); + int bonemealedAt = stamp == null ? 0 : stamp.getBonemealedAt(); + data.set(block, new PlacementStamp(nowSeconds(), brokenAt, bonemealedAt)); + } + + public static boolean isPlayerPlaced(Block block) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isProvenanceEnabled()) { + return false; + } + + PlacementStamp stamp = WorldData.of(block.getWorld()).get(block, PlacementStamp.class); + return stamp != null && withinTtl(stamp.getPlacedAt(), config.getPlacedBlockTtlMillis()); + } + + public static void recordPlayerPlacedBreak(Block block) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isProvenanceEnabled()) { + return; + } + + WorldData data = WorldData.of(block.getWorld()); + PlacementStamp stamp = data.get(block, PlacementStamp.class); + int bonemealedAt = stamp == null ? 0 : stamp.getBonemealedAt(); + data.set(block, new PlacementStamp(0, nowSeconds(), bonemealedAt)); + } + + public static boolean isReplaceDenied(Block block) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isProvenanceEnabled()) { + return false; + } + + PlacementStamp stamp = WorldData.of(block.getWorld()).get(block, PlacementStamp.class); + return stamp != null && withinTtl(stamp.getBrokenAt(), config.getReplaceDenyTtlMillis()); + } + + public static void recordBonemeal(Block block) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isBonemealTrackingEnabled()) { + return; + } + + WorldData data = WorldData.of(block.getWorld()); + PlacementStamp stamp = data.get(block, PlacementStamp.class); + int placedAt = stamp == null ? 0 : stamp.getPlacedAt(); + int brokenAt = stamp == null ? 0 : stamp.getBrokenAt(); + data.set(block, new PlacementStamp(placedAt, brokenAt, nowSeconds())); + } + + public static boolean isBonemealed(Block block) { + AdaptConfig.XpIntegrity config = AdaptConfig.get().getXpIntegrity(); + if (!config.isBonemealTrackingEnabled()) { + return false; + } + + PlacementStamp stamp = WorldData.of(block.getWorld()).get(block, PlacementStamp.class); + return stamp != null && withinTtl(stamp.getBonemealedAt(), config.getBonemealTtlMillis()); + } + + public static double placeXpMultiplier(Block block) { + return isReplaceDenied(block) ? 0.0 : 1.0; + } + + public static double breakXpMultiplier(Block block) { + return isPlayerPlaced(block) || isReplaceDenied(block) ? 0.0 : 1.0; + } + + public static double harvestXpMultiplier(Block block) { + return isBonemealed(block) ? AdaptConfig.get().getXpIntegrity().getBonemealHarvestMultiplier() : 1.0; + } + + private static boolean withinTtl(int stampSeconds, long ttlMillis) { + if (stampSeconds == 0) { + return false; + } + + if (ttlMillis <= 0) { + return true; + } + + long ageMillis = (nowSeconds() - (long) stampSeconds) * 1000L; + return ageMillis <= ttlMillis; + } + + private static int nowSeconds() { + return (int) (M.ms() / 1000L); + } +} diff --git a/src/main/java/art/arcane/adapt/api/xp/XpProvenanceListener.java b/src/main/java/art/arcane/adapt/api/xp/XpProvenanceListener.java new file mode 100644 index 000000000..c9831c9f8 --- /dev/null +++ b/src/main/java/art/arcane/adapt/api/xp/XpProvenanceListener.java @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.api.xp; + +import art.arcane.adapt.AdaptConfig; +import org.bukkit.block.BlockState; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockFertilizeEvent; +import org.bukkit.event.block.BlockPlaceEvent; + +import java.util.List; + +public class XpProvenanceListener implements Listener { + private static final int FERTILIZE_BLOCK_CAP = 64; + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockPlaceEvent e) { + if (!AdaptConfig.get().getXpIntegrity().isProvenanceEnabled()) { + return; + } + + XpProvenance.recordPlacement(e.getBlock()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (!AdaptConfig.get().getXpIntegrity().isProvenanceEnabled()) { + return; + } + + if (XpProvenance.isPlayerPlaced(e.getBlock())) { + XpProvenance.recordPlayerPlacedBreak(e.getBlock()); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockFertilizeEvent e) { + if (!AdaptConfig.get().getXpIntegrity().isBonemealTrackingEnabled()) { + return; + } + + List blocks = e.getBlocks(); + int limit = Math.min(blocks.size(), FERTILIZE_BLOCK_CAP); + for (int i = 0; i < limit; i++) { + XpProvenance.recordBonemeal(blocks.get(i).getBlock()); + } + } +} diff --git a/src/main/java/art/arcane/adapt/command/CommandAdapt.java b/src/main/java/art/arcane/adapt/command/CommandAdapt.java new file mode 100644 index 000000000..2b23ed23c --- /dev/null +++ b/src/main/java/art/arcane/adapt/command/CommandAdapt.java @@ -0,0 +1,530 @@ +package art.arcane.adapt.command; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.skill.SkillRegistry; +import art.arcane.adapt.api.world.AdaptServer; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.content.gui.ConfigGui; +import art.arcane.adapt.content.gui.SkillsGui; +import art.arcane.adapt.content.item.ExperienceOrb; +import art.arcane.adapt.content.item.KnowledgeOrb; +import art.arcane.adapt.util.command.FConst; +import art.arcane.adapt.util.config.ConfigMigrationManager; +import art.arcane.adapt.util.director.context.AdaptationListingHandler; +import art.arcane.adapt.util.director.specialhandlers.NullablePlayerHandler; +import art.arcane.volmlib.util.director.DirectorOrigin; +import art.arcane.volmlib.util.director.annotations.Director; +import art.arcane.volmlib.util.director.annotations.Param; +import art.arcane.volmlib.util.director.compat.BukkitDirectorContext; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +@Director(name = "adapt", description = "Basic Command") +public class CommandAdapt { + private CommandDebug debug; + private CommandClear clear; + private CommandReset reset; + private CommandDefault defaults; + + @Director(description = "Boost Target player Experience gain.") + public void boost( + @Param(aliases = "seconds", description = "Amount of seconds", defaultValue = "10") + int seconds, + @Param(aliases = "multiplier", description = "Strength of the boost ", defaultValue = "10") + double multiplier, + @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + if (!BukkitDirectorContext.hasPermission("adapt.boost")) { + FConst.error("You lack the Permission 'adapt.boost'").send(BukkitDirectorContext.sender()); + return; + } + + Player targetPlayer = player; + if (targetPlayer == null && BukkitDirectorContext.isConsole()) { + FConst.error("You must specify a player when using this command from console.").send(BukkitDirectorContext.sender()); + return; + } else if (targetPlayer == null) { + targetPlayer = BukkitDirectorContext.player(); + } + + AdaptServer adaptServer = Adapt.instance.getAdaptServer(); + PlayerData playerData = adaptServer.getPlayer(targetPlayer).getData(); + playerData.globalXPMultiplier(multiplier, seconds * 1000); + + FConst.success("Boosted XP by " + multiplier + " for " + seconds + " seconds").send(BukkitDirectorContext.sender()); + } + + @Director(description = "Boost Global Experience gain.", name = "global-boost") + public void globalBoost( + @Param(aliases = "seconds", description = "Amount of seconds", defaultValue = "10") + int seconds, + @Param(aliases = "multiplier", description = "Strength of the boost ", defaultValue = "10") + double multiplier + ) { + if (!BukkitDirectorContext.hasPermission("adapt.boost.global")) { + FConst.error("You lack the Permission 'adapt.boost.global'").send(BukkitDirectorContext.sender()); + return; + } + + AdaptServer adaptServer = Adapt.instance.getAdaptServer(); + adaptServer.boostXP(multiplier, seconds * 1000); + + FConst.success("Boosted XP by " + multiplier + " for " + seconds + " seconds").send(BukkitDirectorContext.sender()); + } + + @Director(description = "Open the Adapt GUI") + public void gui( + @Param(aliases = "target", defaultValue = "[Main]") + AdaptationListingHandler.AdaptationList guiTarget, + @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player, + @Param(aliases = "force", defaultValue = "false") + boolean force + ) { + if (!BukkitDirectorContext.hasPermission("adapt.gui")) { + FConst.error("You lack the Permission 'adapt.gui'").send(BukkitDirectorContext.sender()); + return; + } + + Player targetPlayer = player; + if (targetPlayer == null && BukkitDirectorContext.isConsole()) { + FConst.error("You must specify a player when using this command from console.").send(BukkitDirectorContext.sender()); + return; + } else if (targetPlayer == null) { + targetPlayer = BukkitDirectorContext.player(); + } + + if (guiTarget.equals("[Main]")) { + SkillsGui.open(targetPlayer); + return; + } + + if (guiTarget.startsWith("[Skill]-")) { + for (Skill skill : SkillRegistry.skills.sortV()) { + if (guiTarget.equals("[Skill]-" + skill.getName())) { + if (force || skill.openGui(targetPlayer, true)) { + FConst.success("Opened GUI for " + skill.getName() + " for " + targetPlayer.getName()).send(BukkitDirectorContext.sender()); + } else { + FConst.error("Failed to open GUI for " + skill.getName() + " for " + targetPlayer.getName() + " - Permission denied by adapt.use node.").send(BukkitDirectorContext.sender()); + } + return; + } + } + } + + if (guiTarget.startsWith("[Adaptation]-")) { + for (Skill skill : SkillRegistry.skills.sortV()) { + for (Adaptation adaptation : skill.getAdaptations()) { + if (!adaptation.isEnabled()) { + continue; + } + if (guiTarget.equals("[Adaptation]-" + adaptation.getName())) { + if (force || adaptation.openGui(targetPlayer, true)) { + FConst.success("Opened GUI for " + adaptation.getName() + " for " + targetPlayer.getName()).send(BukkitDirectorContext.sender()); + } else { + FConst.error("Failed to open GUI for " + adaptation.getName() + " for " + targetPlayer.getName() + " - Permission denied by adapt.use node.").send(BukkitDirectorContext.sender()); + } + return; + } + } + } + } + } + + @Director(name = "configure", aliases = {"config", "cfg"}, origin = DirectorOrigin.PLAYER, description = "Open the in-game Adapt config editor") + public void configure() { + if (!ConfigGui.canConfigure(BukkitDirectorContext.player())) { + FConst.error("You need operator status or the permission 'adapt.configurator'").send(BukkitDirectorContext.sender()); + return; + } + + ConfigGui.open(BukkitDirectorContext.player()); + } + + @Director(description = "Give yourself an experience orb") + public void experience( + @Param(aliases = "skill") + AdaptationListingHandler.AdaptationSkillList skillName, + @Param(aliases = "amount", defaultValue = "10") + int amount, + @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + + ) { + if (!BukkitDirectorContext.hasPermission("adapt.cheatitem")) { + FConst.error("You lack the Permission 'adapt.cheatitem'").send(BukkitDirectorContext.sender()); + return; + } + + Player targetPlayer = player; + + if (targetPlayer == null) { + if (BukkitDirectorContext.isPlayer()) { + targetPlayer = BukkitDirectorContext.player(); + } else { + FConst.error("You must be a player to use this command, or Reference a player").send(BukkitDirectorContext.sender()); + return; + } + } + + if (skillName.equals("[all]")) { + Map experienceMap = new HashMap<>(); + for (Skill skill : allSkillSnapshot()) { + experienceMap.put(skill.getName(), (double) amount); + } + targetPlayer.getInventory().addItem(ExperienceOrb.with(experienceMap)); + FConst.success("Giving all orbs").send(BukkitDirectorContext.sender()); + return; + } + + if (skillName.equals("[random]")) { + List> skills = allSkillSnapshot(); + if (skills.isEmpty()) { + FConst.error("No skills are registered.").send(BukkitDirectorContext.sender()); + return; + } + + targetPlayer.getInventory().addItem(ExperienceOrb.with(skills.get(ThreadLocalRandom.current().nextInt(skills.size())).getName(), amount)); + FConst.success("Giving random orb").send(BukkitDirectorContext.sender()); + return; + } + + Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getAnySkill(skillName.name()); + if (skill != null) { + targetPlayer.getInventory().addItem(ExperienceOrb.with(skill.getName(), amount)); + FConst.success("Giving " + skill.getName() + " orb").send(BukkitDirectorContext.sender()); + } + } + + @Director(description = "Give yourself a knowledge orb") + public void knowledge( + @Param(aliases = "skill") + AdaptationListingHandler.AdaptationSkillList skillName, + @Param(aliases = "amount", defaultValue = "10") + int amount, + @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + if (!BukkitDirectorContext.hasPermission("adapt.cheatitem")) { + FConst.error("You lack the Permission 'adapt.cheatitem'").send(BukkitDirectorContext.sender()); + return; + } + Player targetPlayer = player; + + if (targetPlayer == null) { + if (BukkitDirectorContext.isPlayer()) { + targetPlayer = BukkitDirectorContext.player(); + } else { + FConst.error("You must be a player to use this command").send(BukkitDirectorContext.sender()); + return; + } + } + + if (skillName.equals("[all]")) { + Map knowledgeMap = new HashMap<>(); + for (Skill skill : allSkillSnapshot()) { + knowledgeMap.put(skill.getName(), amount); + } + targetPlayer.getInventory().addItem(KnowledgeOrb.with(knowledgeMap)); + FConst.success("Giving all orbs").send(BukkitDirectorContext.sender()); + return; + } + + if (skillName.equals("[random]")) { + List> skills = allSkillSnapshot(); + if (skills.isEmpty()) { + FConst.error("No skills are registered.").send(BukkitDirectorContext.sender()); + return; + } + + targetPlayer.getInventory().addItem(KnowledgeOrb.with(skills.get(ThreadLocalRandom.current().nextInt(skills.size())).getName(), amount)); + FConst.success("Giving random orb").send(BukkitDirectorContext.sender()); + return; + } + + Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getAnySkill(skillName.name()); + if (skill != null) { + targetPlayer.getInventory().addItem(KnowledgeOrb.with(skill.getName(), amount)); + FConst.success("Giving " + skill.getName() + " orb").send(BukkitDirectorContext.sender()); + } + } + + @Director(description = "Assign a skill, or UnAssign a skill as if you are learning / unlearning a skill.") + public void determine( + @Param(aliases = "adaptationTarget") + AdaptationListingHandler.AdaptationProvider adaptationTarget, + @Param(aliases = "assign") + boolean assign, + @Param(aliases = "force") + boolean force, + @Param(aliases = "level") + int level, + @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + + ) { + if (!BukkitDirectorContext.hasPermission("adapt.determine")) { + FConst.error("You lack the Permission 'adapt.determine'").send(BukkitDirectorContext.sender()); + return; + } + + Player targetPlayer = player; + if (targetPlayer == null && BukkitDirectorContext.isConsole()) { + FConst.error("You must specify a player when using this command from console.").send(BukkitDirectorContext.sender()); + return; + } else if (targetPlayer == null) { + targetPlayer = BukkitDirectorContext.player(); + } + + //the format is skillname:adaptationname + String[] split = adaptationTarget.name().split(":", 2); + if (split.length != 2) { + FConst.error("Invalid adaptation target format. Use skill:adaptation").send(BukkitDirectorContext.sender()); + return; + } + String skillname = split[0]; + String adaptationname = split[1]; + + for (Skill skill : SkillRegistry.skills.sortV()) { + if (skill.getName().equalsIgnoreCase(skillname)) { + for (Adaptation adaptation : skill.getAdaptations()) { + if (adaptation.getName().equalsIgnoreCase(adaptationname)) { + if (targetPlayer != null) { + if (assign) { + adaptation.learn(targetPlayer, level, force); + } else { + adaptation.unlearn(targetPlayer, level, force); + } + } else { + FConst.error("You must specify a player when using this command from console.").send(BukkitDirectorContext.sender()); + } + return; + } + } + return; + } + } + } + + @Director(name = "claim-skill", description = "Set a player's skill line level between 0 and 100 for custom UI integration.") + public void claimSkill( + @Param(aliases = "skill") + AdaptationListingHandler.SkillProvider skillTarget, + @Param(aliases = "level") + int level, + @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + if (!BukkitDirectorContext.hasPermission("adapt.determine")) { + FConst.error("You lack the Permission 'adapt.determine'").send(BukkitDirectorContext.sender()); + return; + } + + Player targetPlayer = resolveTargetPlayer(player); + if (targetPlayer == null) { + return; + } + + if (level < 0 || level > 100) { + FConst.error("Skill claim level must be between 0 and 100.").send(BukkitDirectorContext.sender()); + return; + } + + Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getAnySkill(skillTarget.name()); + if (skill == null) { + FConst.error("Unknown skill: " + skillTarget.name()).send(BukkitDirectorContext.sender()); + return; + } + + PlayerData playerData = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); + PlayerSkillLine skillLine = playerData.getSkillLine(skill.getName()); + if (skillLine == null) { + FConst.error("Failed to resolve skill line for " + skill.getName() + ".").send(BukkitDirectorContext.sender()); + return; + } + + double targetXp = XP.getXpForLevel(level); + skillLine.setXp(targetXp); + if (skillLine.getLastXP() > targetXp) { + skillLine.setLastXP(targetXp); + } + if (skillLine.getLastLevel() > level) { + skillLine.setLastLevel(level); + } + + FConst.success("Set " + targetPlayer.getName() + " " + skill.getName() + " level to " + level + ".").send(BukkitDirectorContext.sender()); + } + + @Director(name = "claim-adaptation", description = "Set an adaptation level between 0 and 100 if the player can afford it.") + public void claimAdaptation( + @Param(aliases = "adaptationTarget") + AdaptationListingHandler.AdaptationProvider adaptationTarget, + @Param(aliases = "level") + int level, + @Param(aliases = "force", defaultValue = "false") + boolean force, + @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + if (!BukkitDirectorContext.hasPermission("adapt.determine")) { + FConst.error("You lack the Permission 'adapt.determine'").send(BukkitDirectorContext.sender()); + return; + } + + Player targetPlayer = resolveTargetPlayer(player); + if (targetPlayer == null) { + return; + } + + if (level < 0 || level > 100) { + FConst.error("Adaptation claim level must be between 0 and 100.").send(BukkitDirectorContext.sender()); + return; + } + + String[] split = adaptationTarget.name().split(":", 2); + if (split.length != 2) { + FConst.error("Invalid adaptation target format. Use skill:adaptation").send(BukkitDirectorContext.sender()); + return; + } + + Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getAnySkill(split[0]); + if (skill == null) { + FConst.error("Unknown skill: " + split[0]).send(BukkitDirectorContext.sender()); + return; + } + + Adaptation adaptation = null; + for (Adaptation candidate : skill.getAdaptations()) { + if (candidate.getName().equalsIgnoreCase(split[1])) { + adaptation = candidate; + break; + } + } + + if (adaptation == null) { + FConst.error("Unknown adaptation: " + split[1] + " in skill " + skill.getName()).send(BukkitDirectorContext.sender()); + return; + } + + PlayerData playerData = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); + PlayerSkillLine skillLine = playerData.getSkillLine(skill.getName()); + if (skillLine == null) { + FConst.error("Failed to resolve skill line for " + skill.getName() + ".").send(BukkitDirectorContext.sender()); + return; + } + + int currentLevel = skillLine.getAdaptationLevel(adaptation.getName()); + int targetLevel = Math.max(0, Math.min(level, adaptation.getMaxLevel())); + if (targetLevel == currentLevel) { + FConst.success("No change: " + adaptation.getName() + " is already at level " + currentLevel + ".").send(BukkitDirectorContext.sender()); + return; + } + + if (targetLevel > currentLevel) { + int knowledgeCost = adaptation.getCostFor(targetLevel, currentLevel); + int powerCost = adaptation.getPowerCostFor(targetLevel, currentLevel); + + if (!force) { + if (!playerData.hasPowerAvailable(powerCost)) { + FConst.error("Not enough available power. Need " + powerCost + ", have " + playerData.getAvailablePower() + ".").send(BukkitDirectorContext.sender()); + return; + } + + if (skillLine.getKnowledge() < knowledgeCost) { + FConst.error("Not enough knowledge in " + skill.getName() + ". Need " + knowledgeCost + ", have " + skillLine.getKnowledge() + ".").send(BukkitDirectorContext.sender()); + return; + } + + if (!skillLine.spendKnowledge(knowledgeCost)) { + FConst.error("Failed to spend required knowledge (" + knowledgeCost + ").").send(BukkitDirectorContext.sender()); + return; + } + } + + skillLine.setAdaptation(adaptation, targetLevel); + FConst.success("Set " + targetPlayer.getName() + " " + adaptation.getName() + " to level " + targetLevel + ".").send(BukkitDirectorContext.sender()); + return; + } + + if (adaptation.isPermanent() && !force) { + FConst.error(adaptation.getName() + " is permanent and cannot be lowered without force=true.").send(BukkitDirectorContext.sender()); + return; + } + + int refund = AdaptConfig.get().isHardcoreNoRefunds() ? 0 : adaptation.getRefundCostFor(targetLevel, currentLevel); + skillLine.setAdaptation(adaptation, targetLevel); + if (refund > 0) { + skillLine.giveKnowledge(refund); + } + + FConst.success("Set " + targetPlayer.getName() + " " + adaptation.getName() + " to level " + targetLevel + ".").send(BukkitDirectorContext.sender()); + } + + @Director(name = "migrate-configs", description = "Force migrate and rewrite all skill/adaptation configs to canonical TOML with comments.") + public void migrateConfigs() { + if (!BukkitDirectorContext.hasPermission("adapt.debug")) { + FConst.error("You lack the Permission 'adapt.debug'").send(BukkitDirectorContext.sender()); + return; + } + + if (Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { + FConst.error("Adapt server is not ready yet. Try again in a few seconds.").send(BukkitDirectorContext.sender()); + return; + } + + int migratedSkills = 0; + int migratedAdaptations = 0; + for (Skill skill : Adapt.instance.getAdaptServer().getSkillRegistry().getSkills()) { + if (skill instanceof SimpleSkill simpleSkill) { + if (simpleSkill.reloadConfigFromDisk(false)) { + migratedSkills++; + } + } + + for (Adaptation adaptation : skill.getAdaptations()) { + if (adaptation instanceof SimpleAdaptation simpleAdaptation) { + if (simpleAdaptation.reloadConfigFromDisk(false)) { + migratedAdaptations++; + } + } + } + } + + int deletedLegacyJson = ConfigMigrationManager.deleteMigratedLegacyJsonFiles(); + FConst.success("Canonicalized TOML configs. skills=" + migratedSkills + ", adaptations=" + migratedAdaptations + ", deletedLegacyJson=" + deletedLegacyJson).send(BukkitDirectorContext.sender()); + } + + private List> allSkillSnapshot() { + if (Adapt.instance != null + && Adapt.instance.getAdaptServer() != null + && Adapt.instance.getAdaptServer().getSkillRegistry() != null) { + return Adapt.instance.getAdaptServer().getSkillRegistry().getAllSkills(); + } + + return SkillRegistry.skills.sortV(); + } + + private Player resolveTargetPlayer(Player player) { + Player targetPlayer = player; + if (targetPlayer == null && BukkitDirectorContext.isConsole()) { + FConst.error("You must specify a player when using this command from console.").send(BukkitDirectorContext.sender()); + return null; + } + if (targetPlayer == null) { + targetPlayer = BukkitDirectorContext.player(); + } + return targetPlayer; + } +} diff --git a/src/main/java/art/arcane/adapt/command/CommandClear.java b/src/main/java/art/arcane/adapt/command/CommandClear.java new file mode 100644 index 000000000..93d8ff010 --- /dev/null +++ b/src/main/java/art/arcane/adapt/command/CommandClear.java @@ -0,0 +1,111 @@ +package art.arcane.adapt.command; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.util.command.FConst; +import art.arcane.adapt.util.director.specialhandlers.NullablePlayerHandler; +import art.arcane.volmlib.util.director.DirectorOrigin; +import art.arcane.volmlib.util.director.annotations.Director; +import art.arcane.volmlib.util.director.annotations.Param; +import art.arcane.volmlib.util.director.compat.BukkitDirectorContext; +import org.bukkit.entity.Player; + +@Director(name = "clear", origin = DirectorOrigin.BOTH, description = "Clear player progression data") +public class CommandClear { + + @Director(description = "Clear all player data (XP, knowledge, adaptations, stats, discoveries, advancements, wisdom)") + public void all( + @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + Player targetPlayer = resolveTarget(player); + if (targetPlayer == null) return; + + PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); + data.clearAll(); + FConst.success("Cleared all data for " + targetPlayer.getName()).send(BukkitDirectorContext.sender()); + } + + @Director(description = "Clear XP across all skill lines") + public void xp( + @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + Player targetPlayer = resolveTarget(player); + if (targetPlayer == null) return; + + PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); + data.clearXp(); + FConst.success("Cleared XP for " + targetPlayer.getName()).send(BukkitDirectorContext.sender()); + } + + @Director(description = "Clear knowledge across all skill lines") + public void knowledge( + @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + Player targetPlayer = resolveTarget(player); + if (targetPlayer == null) return; + + PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); + data.clearKnowledge(); + FConst.success("Cleared knowledge for " + targetPlayer.getName()).send(BukkitDirectorContext.sender()); + } + + @Director(description = "Unlearn all adaptations across all skill lines") + public void adaptations( + @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + Player targetPlayer = resolveTarget(player); + if (targetPlayer == null) return; + + PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); + data.clearAdaptations(); + FConst.success("Cleared adaptations for " + targetPlayer.getName()).send(BukkitDirectorContext.sender()); + } + + @Director(description = "Clear the stats map") + public void stats( + @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + Player targetPlayer = resolveTarget(player); + if (targetPlayer == null) return; + + PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); + data.clearStats(); + FConst.success("Cleared stats for " + targetPlayer.getName()).send(BukkitDirectorContext.sender()); + } + + @Director(description = "Clear all discovery data (biomes, mobs, foods, items, recipes, etc.)") + public void discoveries( + @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + Player targetPlayer = resolveTarget(player); + if (targetPlayer == null) return; + + PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); + data.clearDiscoveries(); + FConst.success("Cleared discoveries for " + targetPlayer.getName()).send(BukkitDirectorContext.sender()); + } + + private Player resolveTarget(Player player) { + if (!BukkitDirectorContext.hasPermission("adapt.clear")) { + FConst.error("You lack the Permission 'adapt.clear'").send(BukkitDirectorContext.sender()); + return null; + } + + if (player != null) { + return player; + } + + if (BukkitDirectorContext.isConsole()) { + FConst.error("You must specify a player when using this command from console.").send(BukkitDirectorContext.sender()); + return null; + } + + return BukkitDirectorContext.player(); + } +} diff --git a/src/main/java/art/arcane/adapt/command/CommandDebug.java b/src/main/java/art/arcane/adapt/command/CommandDebug.java new file mode 100644 index 000000000..e6cc90fde --- /dev/null +++ b/src/main/java/art/arcane/adapt/command/CommandDebug.java @@ -0,0 +1,140 @@ +package art.arcane.adapt.command; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.telemetry.AbilityCheckTelemetry; +import art.arcane.adapt.util.command.FConst; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.volmlib.util.director.DirectorOrigin; +import art.arcane.volmlib.util.director.annotations.Director; +import art.arcane.volmlib.util.director.annotations.Param; +import art.arcane.volmlib.util.director.compat.BukkitDirectorContext; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Locale; + +@Director(name = "debug", origin = DirectorOrigin.BOTH, description = "Adapt Debug Command", aliases = {"dev"}) +public class CommandDebug { + + @Director(description = "Toggle verbose mode") + public void verbose() { + if (!BukkitDirectorContext.hasPermission("adapt.idontknowwhatimdoingiswear")) { + FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(BukkitDirectorContext.sender()); + return; + } + + AdaptConfig.get().setVerbose(!AdaptConfig.get().isVerbose()); + FConst.success("Verbose is now " + (AdaptConfig.get().isVerbose() ? "enabled" : "disabled")).send(BukkitDirectorContext.sender()); + } + + @Director(name = "pap", description = "Generate Perms for Adaptations!") + public void pap() { + if (!BukkitDirectorContext.hasPermission("adapt.idontknowwhatimdoingiswear")) { + FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(BukkitDirectorContext.sender()); + return; + } + + StringBuilder builder = new StringBuilder(); + Adapt.instance.getAdaptServer().getSkillRegistry().getSkills().forEach(skill -> skill.getAdaptations().forEach(adaptation -> builder + .append("adapt.use.") + .append(adaptation.getName() + .replaceAll("-", "")) + .append("\n"))); + Adapt.info("Permissions: \n" + builder); + FConst.success("Permissions have been printed to console.").send(BukkitDirectorContext.sender()); + } + + @Director(name = "psp", description = "Generate Perms for Skills!") + public void psp() { + if (!BukkitDirectorContext.hasPermission("adapt.idontknowwhatimdoingiswear")) { + FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(BukkitDirectorContext.sender()); + return; + } + + StringBuilder builder = new StringBuilder(); + Adapt.instance.getAdaptServer().getSkillRegistry().getSkills().forEach(skill -> builder + .append("adapt.use.") + .append(skill.getName() + .replaceAll("-", "")) + .append("\n")); + Adapt.info("Permissions: \n" + builder); + FConst.success("Permissions have been printed to console.").send(BukkitDirectorContext.sender()); + } + + @Director(name = "particle", origin = DirectorOrigin.PLAYER, description = "Summon a particle in front of you for testing!") + public void particle(@Param Particle particle) { + if (!BukkitDirectorContext.hasPermission("adapt.idontknowwhatimdoingiswear")) { + FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(BukkitDirectorContext.sender()); + return; + } + + Player player = BukkitDirectorContext.player(); + player.spawnParticle(particle, player.getLocation(), 10, 10); + } + + @Director(name = "particle", origin = DirectorOrigin.PLAYER, description = "Summon a particle in front of you for testing!") + public void particle(@Param Sound sound) { + if (!BukkitDirectorContext.hasPermission("adapt.idontknowwhatimdoingiswear")) { + FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(BukkitDirectorContext.sender()); + return; + } + + SoundPlayer sp = SoundPlayer.of(BukkitDirectorContext.player()); + sp.play(BukkitDirectorContext.player().getLocation(), sound, 1, 1); + } + + @Director(description = "Show Adapt ticker hotspots") + public void perf( + @Param(description = "Top results to print", defaultValue = "12") + int top, + @Param(description = "Reset metrics after printing", defaultValue = "false") + boolean reset + ) { + if (!BukkitDirectorContext.hasPermission("adapt.idontknowwhatimdoingiswear")) { + FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(BukkitDirectorContext.sender()); + return; + } + + long now = System.currentTimeMillis(); + long checksPerSecond = AbilityCheckTelemetry.checksPerSecond(now); + long successfulPerSecond = AbilityCheckTelemetry.successfulChecksPerSecond(now); + long checksPerMinute = AbilityCheckTelemetry.checksPerMinute(now); + long successfulPerMinute = AbilityCheckTelemetry.successfulChecksPerMinute(now); + long cacheHits = AbilityCheckTelemetry.cacheHitsPerMinute(now); + long cacheMisses = AbilityCheckTelemetry.cacheMissesPerMinute(now); + double cacheHitRatio = AbilityCheckTelemetry.cacheHitRatio(now) * 100D; + double averageMicros = AbilityCheckTelemetry.averageCheckMicros(now); + double timingMillisPerSecond = AbilityCheckTelemetry.estimatedTimingMillisPerSecond(now); + double timingBudgetPercent = AbilityCheckTelemetry.timingBudgetPercent(now); + + FConst.info("Ability checks: " + checksPerSecond + "/s (" + checksPerMinute + "/m)").send(BukkitDirectorContext.sender()); + FConst.info("Successful checks: " + successfulPerSecond + "/s (" + successfulPerMinute + "/m)").send(BukkitDirectorContext.sender()); + FConst.info("Active-level cache hit ratio: " + + String.format(Locale.US, "%.1f%%", cacheHitRatio) + + " (" + cacheHits + " hit, " + cacheMisses + " miss)") + .send(BukkitDirectorContext.sender()); + FConst.info("Ability check timing budget: " + + String.format(Locale.US, "%.2f%%", timingBudgetPercent) + + " (" + String.format(Locale.US, "%.2fms/s", timingMillisPerSecond) + + ", " + String.format(Locale.US, "%.1fus/check", averageMicros) + ")") + .send(BukkitDirectorContext.sender()); + + List lines = Adapt.instance.getTicker().topMetrics(top); + long windowMs = Adapt.instance.getTicker().getMetricsWindowMs(); + FConst.success("Ticker window: " + windowMs + "ms").send(BukkitDirectorContext.sender()); + if (lines.isEmpty()) { + FConst.success("No tick metrics collected yet.").send(BukkitDirectorContext.sender()); + } else { + lines.forEach(line -> FConst.info(line).send(BukkitDirectorContext.sender())); + } + + if (reset) { + Adapt.instance.getTicker().resetMetrics(); + AbilityCheckTelemetry.clear(); + FConst.success("Ticker and ability telemetry reset.").send(BukkitDirectorContext.sender()); + } + } +} diff --git a/src/main/java/art/arcane/adapt/command/CommandDefault.java b/src/main/java/art/arcane/adapt/command/CommandDefault.java new file mode 100644 index 000000000..b9dab0baa --- /dev/null +++ b/src/main/java/art/arcane/adapt/command/CommandDefault.java @@ -0,0 +1,195 @@ +package art.arcane.adapt.command; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.util.command.FConst; +import art.arcane.adapt.util.director.context.AdaptationListingHandler; +import art.arcane.volmlib.util.director.DirectorOrigin; +import art.arcane.volmlib.util.director.annotations.Director; +import art.arcane.volmlib.util.director.annotations.Param; +import art.arcane.volmlib.util.director.compat.BukkitDirectorContext; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.text.SimpleDateFormat; +import java.util.Date; + +@Director(name = "default", origin = DirectorOrigin.BOTH, description = "Reset configs to defaults") +public class CommandDefault { + + @Director(description = "Reset a skill config to defaults") + public void skill( + @Param(description = "skill to reset") + AdaptationListingHandler.SkillProvider skillTarget + ) { + if (!BukkitDirectorContext.sender().isOp()) { + FConst.error("This command can only be run by server operators.").send(BukkitDirectorContext.sender()); + return; + } + + Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skillTarget.name()); + if (skill == null) { + FConst.error("Unknown skill: " + skillTarget.name()).send(BukkitDirectorContext.sender()); + return; + } + + if (!(skill instanceof SimpleSkill simpleSkill)) { + FConst.error("Skill " + skill.getName() + " does not support config reset.").send(BukkitDirectorContext.sender()); + return; + } + + File configFile = Adapt.instance.getDataFile("adapt", "skills", skill.getName() + ".toml"); + if (configFile.exists() && !configFile.delete()) { + FConst.error("Failed to delete config file for " + skill.getName()).send(BukkitDirectorContext.sender()); + return; + } + + simpleSkill.reloadConfigFromDisk(false); + FConst.success("Reset config for skill " + skill.getName() + " to defaults.").send(BukkitDirectorContext.sender()); + } + + @Director(description = "Reset an adaptation config to defaults") + public void adaptation( + @Param(description = "adaptation to reset (skill:adaptation)") + AdaptationListingHandler.AdaptationProvider adaptationTarget + ) { + if (!BukkitDirectorContext.sender().isOp()) { + FConst.error("This command can only be run by server operators.").send(BukkitDirectorContext.sender()); + return; + } + + String[] split = adaptationTarget.name().split(":"); + if (split.length != 2) { + FConst.error("Invalid format. Use skill:adaptation").send(BukkitDirectorContext.sender()); + return; + } + + Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(split[0]); + if (skill == null) { + FConst.error("Unknown skill: " + split[0]).send(BukkitDirectorContext.sender()); + return; + } + + Adaptation adaptation = null; + for (Adaptation a : skill.getAdaptations()) { + if (a.getName().equalsIgnoreCase(split[1])) { + adaptation = a; + break; + } + } + + if (adaptation == null) { + FConst.error("Unknown adaptation: " + split[1] + " in skill " + skill.getName()).send(BukkitDirectorContext.sender()); + return; + } + + if (!(adaptation instanceof SimpleAdaptation simpleAdaptation)) { + FConst.error("Adaptation " + adaptation.getName() + " does not support config reset.").send(BukkitDirectorContext.sender()); + return; + } + + File configFile = Adapt.instance.getDataFile("adapt", "adaptations", adaptation.getName() + ".toml"); + if (configFile.exists() && !configFile.delete()) { + FConst.error("Failed to delete config file for " + adaptation.getName()).send(BukkitDirectorContext.sender()); + return; + } + + simpleAdaptation.reloadConfigFromDisk(false); + FConst.success("Reset config for adaptation " + adaptation.getName() + " to defaults.").send(BukkitDirectorContext.sender()); + } + + @Director(description = "Reset ALL configs to defaults and archive the old settings") + public void all() { + if (!BukkitDirectorContext.sender().isOp()) { + FConst.error("This command can only be run by server operators.").send(BukkitDirectorContext.sender()); + return; + } + + String timestamp = new SimpleDateFormat("yyyy-MM-dd_HHmmss").format(new Date()); + File archiveDir = Adapt.instance.getDataFolder("config-archive", timestamp); + + int archived = 0; + int reset = 0; + + // Archive and reset main config + File mainConfig = Adapt.instance.getDataFile("adapt", "adapt.toml"); + if (mainConfig.exists()) { + if (archiveFile(mainConfig, new File(archiveDir, "adapt.toml"))) { + archived++; + } + mainConfig.delete(); + } + + // Archive and reset skill configs + File skillsDir = Adapt.instance.getDataFolder("adapt", "skills"); + if (skillsDir.exists()) { + File archiveSkillsDir = new File(archiveDir, "skills"); + archiveSkillsDir.mkdirs(); + File[] skillFiles = skillsDir.listFiles((dir, name) -> name.endsWith(".toml")); + if (skillFiles != null) { + for (File f : skillFiles) { + if (archiveFile(f, new File(archiveSkillsDir, f.getName()))) { + archived++; + } + f.delete(); + } + } + } + + // Archive and reset adaptation configs + File adaptationsDir = Adapt.instance.getDataFolder("adapt", "adaptations"); + if (adaptationsDir.exists()) { + File archiveAdaptationsDir = new File(archiveDir, "adaptations"); + archiveAdaptationsDir.mkdirs(); + File[] adaptationFiles = adaptationsDir.listFiles((dir, name) -> name.endsWith(".toml")); + if (adaptationFiles != null) { + for (File f : adaptationFiles) { + if (archiveFile(f, new File(archiveAdaptationsDir, f.getName()))) { + archived++; + } + f.delete(); + } + } + } + + // Reload main config from defaults + AdaptConfig.reload(); + + // Reload all skill and adaptation configs from defaults + for (Skill skill : Adapt.instance.getAdaptServer().getSkillRegistry().getSkills()) { + if (skill instanceof SimpleSkill simpleSkill) { + if (simpleSkill.reloadConfigFromDisk(false)) { + reset++; + } + } + + for (Adaptation adaptation : skill.getAdaptations()) { + if (adaptation instanceof SimpleAdaptation simpleAdaptation) { + if (simpleAdaptation.reloadConfigFromDisk(false)) { + reset++; + } + } + } + } + + FConst.success("Archived " + archived + " config files to config-archive/" + timestamp + "/").send(BukkitDirectorContext.sender()); + FConst.success("Reset " + reset + " configs to defaults.").send(BukkitDirectorContext.sender()); + } + + private boolean archiveFile(File source, File destination) { + try { + destination.getParentFile().mkdirs(); + Files.copy(source.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING); + return true; + } catch (IOException e) { + Adapt.warn("Failed to archive " + source.getPath() + ": " + e.getMessage()); + return false; + } + } +} diff --git a/src/main/java/art/arcane/adapt/command/CommandReset.java b/src/main/java/art/arcane/adapt/command/CommandReset.java new file mode 100644 index 000000000..e57636d3c --- /dev/null +++ b/src/main/java/art/arcane/adapt/command/CommandReset.java @@ -0,0 +1,70 @@ +package art.arcane.adapt.command; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.command.FConst; +import art.arcane.adapt.util.director.specialhandlers.NullablePlayerHandler; +import art.arcane.volmlib.util.director.DirectorOrigin; +import art.arcane.volmlib.util.director.annotations.Director; +import art.arcane.volmlib.util.director.annotations.Param; +import art.arcane.volmlib.util.director.compat.BukkitDirectorContext; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Director(name = "reset", origin = DirectorOrigin.BOTH, description = "Permanently delete all Adapt data for a player") +public class CommandReset { + private static final Map pendingConfirmations = new HashMap<>(); + private static final long CONFIRMATION_TIMEOUT_MS = 30_000; + + @Director(description = "Permanently delete all Adapt data for a player. Requires op. Run twice to confirm.") + public void confirm( + @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) + Player player + ) { + if (!BukkitDirectorContext.sender().isOp()) { + FConst.error("This command can only be run by server operators.").send(BukkitDirectorContext.sender()); + return; + } + + Player targetPlayer = player; + if (targetPlayer == null && BukkitDirectorContext.isConsole()) { + FConst.error("You must specify a player when using this command from console.").send(BukkitDirectorContext.sender()); + return; + } else if (targetPlayer == null) { + targetPlayer = BukkitDirectorContext.player(); + } + + UUID senderUuid = BukkitDirectorContext.isPlayer() ? BukkitDirectorContext.player().getUniqueId() : new UUID(0, 0); + UUID targetUuid = targetPlayer.getUniqueId(); + long now = System.currentTimeMillis(); + + PendingReset pending = pendingConfirmations.get(senderUuid); + if (pending != null && pending.targetUuid.equals(targetUuid) && now - pending.timestamp < CONFIRMATION_TIMEOUT_MS) { + pendingConfirmations.remove(senderUuid); + + AdaptPlayer adaptPlayer = Adapt.instance.getAdaptServer().getPlayer(targetPlayer); + adaptPlayer.delete(targetUuid); + Adapt.info("Operator " + BukkitDirectorContext.name() + " reset all Adapt data for " + targetPlayer.getName()); + FConst.success("All Adapt data for " + targetPlayer.getName() + " has been permanently deleted.").send(BukkitDirectorContext.sender()); + return; + } + + pendingConfirmations.put(senderUuid, new PendingReset(targetUuid, now)); + FConst.error("WARNING: This will permanently delete ALL Adapt data for " + targetPlayer.getName() + ".").send(BukkitDirectorContext.sender()); + FConst.error("This includes XP, skills, adaptations, discoveries, stats, and advancements.").send(BukkitDirectorContext.sender()); + FConst.error("Run this command again within 30 seconds to confirm.").send(BukkitDirectorContext.sender()); + } + + private static class PendingReset { + final UUID targetUuid; + final long timestamp; + + PendingReset(UUID targetUuid, long timestamp) { + this.targetUuid = targetUuid; + this.timestamp = timestamp; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityArmorUp.java b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityArmorUp.java new file mode 100644 index 000000000..d8977129d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityArmorUp.java @@ -0,0 +1,204 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.agility; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class AgilityArmorUp extends SimpleAdaptation { + private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-armor-up".getBytes()); + private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString("adapt:armor-up"); + private final Map ticksRunning; + + + public AgilityArmorUp() { + super("agility-armor-up"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("agility.armor_up.description")); + setIcon(Material.IRON_CHESTPLATE); + setDisplayName(Localizer.dLocalize("agility.armor_up.name")); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setInitialCost(getConfig().initialCost); + setInterval(50); + ticksRunning = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_CHESTPLATE) + .key("challenge_agility_armor_up_30min") + .title(Localizer.dLocalize("advancement.challenge_agility_armor_up_30min.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_armor_up_30min.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_CHESTPLATE) + .key("challenge_agility_armor_up_5hr") + .title(Localizer.dLocalize("advancement.challenge_agility_armor_up_5hr.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_armor_up_5hr.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_agility_armor_up_30min", "agility.armor-up.ticks-armored", 36000, 500); + registerMilestone("challenge_agility_armor_up_5hr", "agility.armor-up.ticks-armored", 360000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getWindupArmor(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("agility.armor_up.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getWindupTicks(getLevelPercent(level)) * 50D, 1) + " " + C.GRAY + Localizer.dLocalize("agility.armor_up.lore2")); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + ticksRunning.remove(e.getPlayer().getUniqueId()); + } + + private double getWindupTicks(double factor) { + return M.lerp(getConfig().windupTicksSlowest, getConfig().windupTicksFastest, factor); + } + + private double getWindupArmor(double factor) { + return getConfig().windupArmorBase + (factor * getConfig().windupArmorLevelMultiplier); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + withPlayerThread(p, () -> updatePlayer(p)); + } + } + + private void updatePlayer(Player p) { + if (p == null || !p.isOnline()) { + return; + } + + UUID id = p.getUniqueId(); + art.arcane.adapt.api.version.IAttribute attribute = Version.get().getAttribute(p, Attributes.GENERIC_ARMOR); + if (attribute == null) { + ticksRunning.remove(id); + return; + } + + try { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + } catch (Exception e) { + Adapt.verbose("Failed to remove windup modifier: " + e.getMessage()); + } + + if (!hasActiveAdaptation(p) || p.isSwimming() || p.isFlying() || p.isGliding() || p.isSneaking()) { + ticksRunning.remove(id); + return; + } + + if (!p.isSprinting()) { + ticksRunning.remove(id); + return; + } + + ticksRunning.compute(id, (k, v) -> v == null ? 1 : v + 1); + int tr = ticksRunning.getOrDefault(id, 0); + if (tr <= 0) { + return; + } + + double factor = getLevelPercent(p); + double ticksToMax = getWindupTicks(factor); + double progress = Math.min(M.lerpInverse(0, ticksToMax, tr), 1); + double armorInc = M.lerp(0, getWindupArmor(factor), progress); + + if (areParticlesEnabled()) { + if (M.r(0.2 * progress)) { + p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation(), 1); + } + + if (M.r(0.25 * progress)) { + p.getWorld().spawnParticle(Particle.WAX_ON, p.getLocation(), 1, 0, 0, 0, 0); + } + } + + attribute.setModifier(MODIFIER, MODIFIER_KEY, armorInc * 10, AttributeModifier.Operation.MULTIPLY_SCALAR_1); + getPlayer(p).getData().addStat("agility.armor-up.ticks-armored", 1); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + + @NoArgsConstructor + @ConfigDescription("Gain more armor the longer you sprint.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Agility Armor Up adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Windup Ticks Slowest for the Agility Armor Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windupTicksSlowest = 180; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Windup Ticks Fastest for the Agility Armor Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windupTicksFastest = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Windup Armor Base for the Agility Armor Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windupArmorBase = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Windup Armor Level Multiplier for the Agility Armor Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windupArmorLevelMultiplier = 0.525; + } + +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityLadderSlide.java b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityLadderSlide.java new file mode 100644 index 000000000..ab37b1eaa --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityLadderSlide.java @@ -0,0 +1,278 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.agility; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class AgilityLadderSlide extends SimpleAdaptation { + private final Map upwardStates; + + public AgilityLadderSlide() { + super("agility-ladder-slide"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("agility.ladder_slide.description")); + setDisplayName(Localizer.dLocalize("agility.ladder_slide.name")); + setIcon(Material.LADDER); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(50); + upwardStates = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LADDER) + .key("challenge_agility_ladder_500") + .title(Localizer.dLocalize("advancement.challenge_agility_ladder_500.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_ladder_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_CHAIN) + .key("challenge_agility_ladder_10k") + .title(Localizer.dLocalize("advancement.challenge_agility_ladder_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_ladder_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_agility_ladder_500", "agility.ladder-slide.blocks-climbed", 500, 300); + registerMilestone("challenge_agility_ladder_10k", "agility.ladder-slide.blocks-climbed", 10000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getConfig().speedMultiplier, 1) + "x " + C.GRAY + Localizer.dLocalize("agility.ladder_slide.lore1")); + v.addLore(C.YELLOW + "Downward speed boost coming in a future update."); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerMoveEvent e) { + if (e.getTo() == null) { + return; + } + + Player p = e.getPlayer(); + withPlayerThread(p, e, () -> { + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveInteractContext(p, p.getLocation(), player -> !player.isFlying() && !player.isGliding() && !player.isSwimming()); + if (context == null) { + clearUpwardState(p); + return; + } + + Location location = context.location(); + + Block activeLadder = getActiveLadderBlock(location); + if (activeLadder == null) { + clearUpwardState(p); + return; + } + + double dy = e.getTo().getY() - e.getFrom().getY(); + boolean lookingUp = p.getLocation().getPitch() <= -Math.abs(getConfig().lookUpPitchThreshold); + if (!lookingUp) { + clearUpwardState(p); + return; + } + + double epsilon = Math.abs(getConfig().movementDirectionEpsilonUpward); + Vector velocity = p.getVelocity(); + if (p.isSneaking()) { + clearUpwardState(p); + applyVerticalVelocity(p, velocity, 0); + return; + } + + boolean movingUp = dy > epsilon; + if (!movingUp) { + clearUpwardState(p); + return; + } + + double baseUp = Math.max(0, getConfig().normalUpwardLadderSpeed); + double targetUp = isNearLadderEnd(activeLadder, true) ? baseUp : getUpwardSpeed(); + applySmoothUpwardVelocity(p, velocity, baseUp, targetUp); + p.setFallDistance(0); + getPlayer(p).getData().addStat("agility.ladder-slide.blocks-climbed", 1); + }); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + clearUpwardState(e.getPlayer()); + } + + private void applySmoothUpwardVelocity(Player p, Vector velocity, double baseUpwardSpeed, double targetUpwardSpeed) { + double minUp = Math.max(0, baseUpwardSpeed); + double target = Math.max(minUp, targetUpwardSpeed); + long now = System.currentTimeMillis(); + UpwardState state = upwardStates.get(p.getUniqueId()); + if (state == null || now - state.lastSeenAt > Math.max(0, getConfig().upwardStateResetMs)) { + state = new UpwardState(Math.max(minUp, Math.max(0, velocity.getY())), now); + upwardStates.put(p.getUniqueId(), state); + } + + double smoothing = clamp(getConfig().upwardAccelerationSmoothing, 0.01, 1.0); + double current = Math.max(minUp, state.currentSpeed); + double next = current + ((target - current) * smoothing); + state.currentSpeed = Math.min(target, Math.max(minUp, next)); + state.lastSeenAt = now; + applyVerticalVelocity(p, velocity, state.currentSpeed); + } + + private void clearUpwardState(Player p) { + if (p == null) { + return; + } + upwardStates.remove(p.getUniqueId()); + } + + private double clamp(double v, double min, double max) { + return Math.max(min, Math.min(max, v)); + } + + private void applyVerticalVelocity(Player p, Vector velocity, double targetY) { + if (Math.abs(velocity.getY() - targetY) <= getConfig().velocityEpsilon) { + return; + } + + p.setVelocity(velocity.setY(targetY)); + } + + private Block getActiveLadderBlock(Location location) { + Block feet = location.getBlock(); + if (feet.getType() == Material.LADDER) { + return feet; + } + + Block head = location.clone().add(0, 1, 0).getBlock(); + if (head.getType() == Material.LADDER) { + return head; + } + + return null; + } + + private boolean isNearLadderEnd(Block ladder, boolean upward) { + int laddersAhead = 0; + Block cursor = ladder; + BlockFace direction = upward ? BlockFace.UP : BlockFace.DOWN; + int limit = Math.max(1, getConfig().maxLadderScanDistance); + for (int i = 0; i < limit; i++) { + cursor = cursor.getRelative(direction); + if (cursor.getType() != Material.LADDER) { + break; + } + + laddersAhead++; + if (laddersAhead > getConfig().revertDistanceBlocks) { + return false; + } + } + + return laddersAhead <= getConfig().revertDistanceBlocks; + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private double getUpwardSpeed() { + return getConfig().baseUpwardLadderSpeed * getConfig().speedMultiplier; + } + + @NoArgsConstructor + @ConfigDescription("Climb and slide ladders much faster in both directions.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Multiplier applied to baseUpwardLadderSpeed to compute the target climb speed.", impact = "Higher values increase final ladder climb speed after the ramp-up phase.") + double speedMultiplier = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Velocity difference threshold used to skip tiny Y-velocity adjustments.", impact = "Lower values apply more frequent micro-updates; higher values reduce minor velocity writes.") + double velocityEpsilon = 0.003; + @art.arcane.adapt.util.config.ConfigDoc(value = "Baseline climb speed used before the speed multiplier is applied.", impact = "Higher values raise the base climb profile and increase total ladder ascent speed.") + double baseUpwardLadderSpeed = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Vanilla-like upward speed used near ladder endpoints to avoid overshooting.", impact = "Higher values make endpoint climbing snappier; lower values keep transitions conservative.") + double normalUpwardLadderSpeed = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum positive Y movement treated as intentional upward ladder motion.", impact = "Lower values are more sensitive to slight upward input; higher values require clearer upward movement.") + double movementDirectionEpsilonUpward = 0.0004; + @art.arcane.adapt.util.config.ConfigDoc(value = "Smoothing factor for blending current upward velocity toward target ladder speed.", impact = "Values near 1.0 ramp quickly; lower values create a slower curve-like acceleration profile.") + double upwardAccelerationSmoothing = 0.28; + @art.arcane.adapt.util.config.ConfigDoc(value = "How long to retain previous upward speed state between ladder movement samples.", impact = "Lower values reset ramp-up sooner; higher values preserve momentum between short interruptions.") + long upwardStateResetMs = 200; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum upward look angle required to activate upward ladder acceleration.", impact = "Larger values require players to look farther upward before acceleration engages.") + double lookUpPitchThreshold = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Distance from ladder top where motion reverts toward normal upward speed.", impact = "Higher values begin fallback earlier near ladder ends; lower values keep boosted speed longer.") + int revertDistanceBlocks = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum blocks scanned to detect ladder continuity when checking ladder endpoints.", impact = "Higher values support taller ladders at slightly higher per-check cost.") + int maxLadderScanDistance = 64; + } + + private static class UpwardState { + private double currentSpeed; + private long lastSeenAt; + + private UpwardState(double currentSpeed, long lastSeenAt) { + this.currentSpeed = currentSpeed; + this.lastSeenAt = lastSeenAt; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityParkourMomentum.java b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityParkourMomentum.java new file mode 100644 index 000000000..fcf3de8f0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityParkourMomentum.java @@ -0,0 +1,378 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.agility; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.math.VelocitySpeed; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class AgilityParkourMomentum extends SimpleAdaptation { + private final Map momentum = new ConcurrentHashMap<>(); + private final Map wasOnGround = new ConcurrentHashMap<>(); + private final Map speedBoosting = new ConcurrentHashMap<>(); + + public AgilityParkourMomentum() { + super("agility-parkour-momentum"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("agility.parkour_momentum.description")); + setDisplayName(Localizer.dLocalize("agility.parkour_momentum.name")); + setIcon(Material.RABBIT_FOOT); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(10); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.RABBIT_FOOT) + .key("challenge_agility_parkour_500") + .title(Localizer.dLocalize("advancement.challenge_agility_parkour_500.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_parkour_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_agility_parkour_500", "agility.parkour-momentum.ledge-landings", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getMaxMomentum(level) + C.GRAY + " " + Localizer.dLocalize("agility.parkour_momentum.lore1")); + v.addLore(C.GREEN + "+ " + getMaxSpeedAmplifier(level) + C.GRAY + " " + Localizer.dLocalize("agility.parkour_momentum.lore2")); + v.addLore(C.GREEN + "+ " + getMaxJumpAmplifier(level) + C.GRAY + " " + Localizer.dLocalize("agility.parkour_momentum.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + momentum.remove(id); + wasOnGround.remove(id); + speedBoosting.remove(id); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerMoveEvent e) { + if (e.getTo() == null) { + return; + } + + if (e.getFrom().getWorld() == e.getTo().getWorld() + && e.getFrom().distanceSquared(e.getTo()) < getConfig().minimumMoveSquared) { + return; + } + + Player p = e.getPlayer(); + withPlayerThread(p, e, () -> { + UUID id = p.getUniqueId(); + int level = getActiveLevel(p); + if (level <= 0) { + momentum.remove(id); + wasOnGround.remove(id); + return; + } + + boolean onGroundNow = p.isOnGround(); + boolean onGroundBefore = wasOnGround.getOrDefault(id, onGroundNow); + int current = momentum.getOrDefault(id, 0); + + if (!onGroundBefore && onGroundNow) { + if (isMomentumLanding(p) && isOnLedge(p)) { + current += getConfig().landingGain; + getPlayer(p).getData().addStat("agility.parkour-momentum.ledge-landings", 1); + } else { + current -= getConfig().failedLandingPenalty; + } + } else if (onGroundNow && !p.isSprinting()) { + current -= getConfig().groundDecayOnMove; + } + + current = clampMomentum(current, getMaxMomentum(level)); + momentum.put(id, current); + wasOnGround.put(id, onGroundNow); + }); + } + + @Override + public void onTick() { + Set tracked = trackedPlayerIds(); + for (UUID id : tracked) { + Player p = Bukkit.getPlayer(id); + if (p == null || !p.isOnline()) { + momentum.remove(id); + wasOnGround.remove(id); + speedBoosting.remove(id); + continue; + } + + withPlayerThread(p, () -> { + int level = getActiveLevel(p); + if (level <= 0) { + momentum.remove(id); + wasOnGround.remove(id); + invalidateMomentumSpeed(p, id, true); + return; + } + + int maxMomentum = getMaxMomentum(level); + int current = momentum.getOrDefault(id, 0); + if (current <= 0) { + invalidateMomentumSpeed(p, id, false); + return; + } + + if (p.isOnGround() && !isOnLedge(p)) { + current -= getConfig().offLedgeDecayPerTick; + momentum.put(id, clampMomentum(current, maxMomentum)); + brakeMomentumSpeed(p, id); + return; + } + + int speedAmp = Math.max(0, Math.min(getMaxSpeedAmplifier(level), (int) Math.floor((current / (double) maxMomentum) * (getMaxSpeedAmplifier(level) + 1)) - 1)); + int jumpAmp = Math.max(0, Math.min(getMaxJumpAmplifier(level), (int) Math.floor((current / (double) maxMomentum) * (getMaxJumpAmplifier(level) + 1)) - 1)); + + p.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, 25, jumpAmp, false, false)); + + if (speedAmp <= 0) { + brakeMomentumSpeed(p, id); + } else if (!isVelocityEligible(p)) { + invalidateMomentumSpeed(p, id, true); + } else { + VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); + if (!input.hasHorizontal()) { + brakeMomentumSpeed(p, id); + } else { + applyMomentumSpeed(p, id, input, speedAmp); + } + } + + if (p.isOnGround() && !p.isSprinting()) { + current -= getConfig().passiveGroundDecayPerTick; + } + + momentum.put(id, clampMomentum(current, maxMomentum)); + }); + } + } + + private Set trackedPlayerIds() { + Set tracked = new HashSet<>(); + tracked.addAll(momentum.keySet()); + tracked.addAll(wasOnGround.keySet()); + tracked.addAll(speedBoosting.keySet()); + return tracked; + } + + private void applyMomentumSpeed(Player p, UUID id, VelocitySpeed.InputSnapshot input, int speedAmp) { + Vector direction = VelocitySpeed.resolveHorizontalDirection(p, input); + if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { + brakeMomentumSpeed(p, id); + return; + } + + double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, + Math.max(0, getConfig().baseHorizontalSpeed * VelocitySpeed.speedAmplifierScalar(speedAmp))); + Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); + Vector targetHorizontal = direction.multiply(targetSpeed); + Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); + nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); + VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); + speedBoosting.put(id, true); + } + + private void brakeMomentumSpeed(Player p, UUID id) { + if (!speedBoosting.getOrDefault(id, false)) { + return; + } + + Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); + double stopThreshold = Math.max(0, getConfig().stopThreshold); + if (horizontal.lengthSquared() <= stopThreshold * stopThreshold) { + VelocitySpeed.hardStopHorizontal(p); + speedBoosting.put(id, false); + return; + } + + Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); + if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { + VelocitySpeed.hardStopHorizontal(p); + speedBoosting.put(id, false); + return; + } + + VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); + } + + private void invalidateMomentumSpeed(Player p, UUID id, boolean invalidState) { + if (!speedBoosting.getOrDefault(id, false)) { + return; + } + + if (invalidState && getConfig().hardStopOnInvalidState) { + VelocitySpeed.hardStopHorizontal(p); + } + + speedBoosting.put(id, false); + } + + private boolean isVelocityEligible(Player p) { + GameMode mode = p.getGameMode(); + if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { + return false; + } + + return !p.isDead() && !p.isFlying() && !p.isGliding() && !p.isSwimming() && p.getVehicle() == null; + } + + private boolean isMomentumLanding(Player p) { + return p.isSprinting() && !p.isSwimming() && !p.isGliding() && !p.isFlying(); + } + + private boolean isOnLedge(Player p) { + if (!p.isOnGround()) { + return false; + } + + Block feet = p.getLocation().getBlock(); + Block below = feet.getRelative(BlockFace.DOWN); + if (!below.getType().isSolid()) { + return false; + } + + BlockFace[] sides = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}; + for (BlockFace side : sides) { + Block sideAtFeet = feet.getRelative(side); + Block sideBelow = below.getRelative(side); + if (!sideAtFeet.getType().isSolid() && !sideBelow.getType().isSolid()) { + return true; + } + } + + return false; + } + + private int clampMomentum(int value, int max) { + return Math.max(0, Math.min(max, value)); + } + + private int getMaxMomentum(int level) { + return Math.max(3, (int) Math.round(getConfig().momentumBase + (getLevelPercent(level) * getConfig().momentumFactor))); + } + + private int getMaxSpeedAmplifier(int level) { + return Math.max(0, (int) Math.round(getConfig().speedAmplifierBase + (getLevelPercent(level) * getConfig().speedAmplifierFactor))); + } + + private int getMaxJumpAmplifier(int level) { + return Math.max(0, (int) Math.round(getConfig().jumpAmplifierBase + (getLevelPercent(level) * getConfig().jumpAmplifierFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Build momentum by chaining sprint-jumps and landings to gain speed and jump boosts.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Momentum Base for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double momentumBase = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Momentum Factor for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double momentumFactor = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Amplifier Base for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedAmplifierBase = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Amplifier Factor for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedAmplifierFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Jump Amplifier Base for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double jumpAmplifierBase = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Jump Amplifier Factor for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double jumpAmplifierFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Landing Gain for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int landingGain = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Failed Landing Penalty for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int failedLandingPenalty = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ground Decay On Move for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int groundDecayOnMove = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Passive Ground Decay Per Tick for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int passiveGroundDecayPerTick = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Off Ledge Decay Per Tick for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int offLedgeDecayPerTick = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Move Squared for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minimumMoveSquared = 0.0025; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for momentum velocity scaling.", impact = "Higher values increase movement speed when momentum speed is active.") + double baseHorizontalSpeed = 0.13; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent excessive momentum carry.") + double maxHorizontalSpeed = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the momentum target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") + double accelPerTick = 0.04; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast velocity decays when movement input is released.", impact = "Higher values stop faster and reduce carry momentum.") + double brakePerTick = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny motion longer.") + double stopThreshold = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "If true, speed velocity is force-cleared when entering invalid states.", impact = "Prevents retained boosts if state transitions skip expected checks.") + boolean hardStopOnInvalidState = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") + double fallbackInputVelocityThreshold = 0.0008; + + double fallbackInputVelocityThresholdSquared() { + double threshold = Math.max(0, fallbackInputVelocityThreshold); + return threshold * threshold; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityRollLanding.java b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityRollLanding.java new file mode 100644 index 000000000..47aa53f92 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityRollLanding.java @@ -0,0 +1,292 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.agility; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class AgilityRollLanding extends SimpleAdaptation { + private final Map rollInputs = new ConcurrentHashMap<>(); + private final Map proneUntilMillis = new ConcurrentHashMap<>(); + + public AgilityRollLanding() { + super("agility-roll-landing"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("agility.roll_landing.description")); + setDisplayName(Localizer.dLocalize("agility.roll_landing.name")); + setIcon(Material.HAY_BLOCK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1200); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.HAY_BLOCK) + .key("challenge_agility_roll_100") + .title(Localizer.dLocalize("advancement.challenge_agility_roll_100.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_roll_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SLIME_BLOCK) + .key("challenge_agility_roll_1000") + .title(Localizer.dLocalize("advancement.challenge_agility_roll_1000.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_roll_1000.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ELYTRA) + .key("challenge_agility_fearless") + .title(Localizer.dLocalize("advancement.challenge_agility_fearless.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_fearless.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.HIDDEN) + .build()); + registerMilestone("challenge_agility_roll_100", "agility.roll-landing.damage-prevented", 100, 300); + registerMilestone("challenge_agility_roll_1000", "agility.roll-landing.damage-prevented", 1000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getFallReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("agility.roll_landing.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getInputWindowMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("agility.roll_landing.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("agility.roll_landing.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + rollInputs.remove(e.getPlayer().getUniqueId()); + proneUntilMillis.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> { + if (e.isSneaking()) { + recordRollInput(p, null, null); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> recordRollInput(p, e.getFrom().getY(), e.getTo() == null ? null : e.getTo().getY())); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p) || e.getCause() != EntityDamageEvent.DamageCause.FALL) { + return; + } + + withAdaptedPlayer(p, e, () -> { + if (p.hasCooldown(Material.HAY_BLOCK)) { + return; + } + + long now = System.currentTimeMillis(); + long input = rollInputs.getOrDefault(p.getUniqueId(), 0L); + int level = getActiveLevel(p); + if (now - input > getInputWindowMillis(level)) { + return; + } + + double absorbCap = e.getDamage() * getFallReduction(level); + int hungerNeeded = (int) Math.ceil(absorbCap * getHungerPerDamage(level)); + if (hungerNeeded <= 0 || p.getFoodLevel() <= 0) { + return; + } + + int usableFood = Math.min(p.getFoodLevel(), hungerNeeded); + double absorbed = usableFood / getHungerPerDamage(level); + if (absorbed <= 0) { + return; + } + + p.setFoodLevel(Math.max(0, p.getFoodLevel() - usableFood)); + e.setDamage(Math.max(0, e.getDamage() - absorbed)); + if (e.getDamage() <= 0.01) { + e.setCancelled(true); + } + + p.setCooldown(Material.HAY_BLOCK, getCooldownTicks(level)); + triggerRollPose(p, level); + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_PLAYER_SMALL_FALL, 0.8f, 0.7f); + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1.0f, 0.89f); + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_WOOL_BREAK, 0.55f, 0.9f); + getPlayer(p).getData().addStat("agility.roll-landing.damage-prevented", absorbed); + if (p.getFallDistance() >= 30 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_agility_fearless")) { + getPlayer(p).getAdvancementHandler().grant("challenge_agility_fearless"); + } + xp(p, absorbed * getConfig().xpPerDamagePrevented); + }); + } + + private void recordRollInput(Player p, Double fromY, Double toY) { + if (!p.isSneaking()) { + return; + } + + if (p.isOnGround() || p.isFlying() || p.isGliding()) { + return; + } + + boolean descending = p.getVelocity().getY() <= getConfig().maxVerticalVelocityForRollInput; + if (!descending && fromY != null && toY != null) { + descending = toY < fromY; + } + + if (!descending) { + return; + } + + rollInputs.put(p.getUniqueId(), System.currentTimeMillis()); + } + + private void triggerRollPose(Player p, int level) { + int proneTicks = getProneTicks(level); + long until = System.currentTimeMillis() + (proneTicks * 50L); + UUID id = p.getUniqueId(); + proneUntilMillis.put(id, until); + p.setSwimming(true); + + J.runEntity(p, () -> { + if (!p.isOnline() || p.isDead()) { + proneUntilMillis.remove(id); + return; + } + + long expectedUntil = proneUntilMillis.getOrDefault(id, 0L); + if (expectedUntil > System.currentTimeMillis()) { + return; + } + + proneUntilMillis.remove(id); + if (!p.isInWater()) { + p.setSwimming(false); + } + }, proneTicks); + } + + private double getFallReduction(int level) { + return Math.min(getConfig().maxReduction, getConfig().reductionBase + (getLevelPercent(level) * getConfig().reductionFactor)); + } + + private long getInputWindowMillis(int level) { + return Math.max(60L, Math.round(getConfig().inputWindowMillisBase + (getLevelPercent(level) * getConfig().inputWindowMillisFactor))); + } + + private double getHungerPerDamage(int level) { + return Math.max(0.1, getConfig().hungerPerDamageBase - (getLevelPercent(level) * getConfig().hungerPerDamageReduction)); + } + + private int getCooldownTicks(int level) { + return Math.max(4, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + private int getProneTicks(int level) { + return Math.max(2, (int) Math.round(getConfig().proneTicksBase + (getLevelPercent(level) * getConfig().proneTicksFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Timed sneak before landing converts part of fall damage into hunger cost.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.62; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reduction Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reductionBase = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reduction Factor for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reductionFactor = 0.43; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Reduction for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxReduction = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Input Window Millis Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double inputWindowMillisBase = 190; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Input Window Millis Factor for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double inputWindowMillisFactor = 260; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hunger Per Damage Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hungerPerDamageBase = 1.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hunger Per Damage Reduction for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hungerPerDamageReduction = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Vertical Velocity For Roll Input for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxVerticalVelocityForRollInput = -0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Prone Ticks Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double proneTicksBase = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Prone Ticks Factor for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double proneTicksFactor = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Damage Prevented for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerDamagePrevented = 4.2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilitySuperJump.java b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilitySuperJump.java new file mode 100644 index 000000000..9ab608a5c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilitySuperJump.java @@ -0,0 +1,192 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.agility; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class AgilitySuperJump extends SimpleAdaptation { + private final Map lastJump; + + public AgilitySuperJump() { + super("agility-super-jump"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("agility.super_jump.description")); + setDisplayName(Localizer.dLocalize("agility.super_jump.name")); + setIcon(Material.LEATHER_BOOTS); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(9999); + lastJump = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEATHER_BOOTS) + .key("challenge_agility_super_jump_100") + .title(Localizer.dLocalize("advancement.challenge_agility_super_jump_100.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_super_jump_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GOLDEN_BOOTS) + .key("challenge_agility_super_jump_5k") + .title(Localizer.dLocalize("advancement.challenge_agility_super_jump_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_super_jump_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_agility_super_jump_100", "agility.super-jump.jumps", 100, 300); + registerMilestone("challenge_agility_super_jump_5k", "agility.super-jump.jumps", 5000, 1500); + } + + private double getJumpHeight(int level) { + return getConfig().baseJumpMultiplier + (getConfig().jumpLevelMultiplier * level); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getJumpHeight(level), 0) + C.GRAY + " " + Localizer.dLocalize("agility.super_jump.lore1")); + v.addLore(C.LIGHT_PURPLE + " " + Localizer.dLocalize("agility.super_jump.lore2")); + + } + + @EventHandler + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> { + if (e.isSneaking() && p.isOnGround()) { + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 0.3f, 0.35f); + } + }); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + Player p = e.getPlayer(); + lastJump.remove(p.getUniqueId()); + } + + @EventHandler + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + if (p.isSwimming() || p.isFlying() || p.isGliding() || p.isSprinting()) { + return; + } + + withAdaptedPlayer(p, e, () -> { + if (!p.isSneaking() || !canUse(getPlayer(p))) { + return; + } + + Vector velocity = p.getVelocity(); + + if (velocity.getY() > 0) { + double jumpVelocity = 0.4; + PotionEffect jumpPotion = p.getPotionEffect(PotionEffectTypes.JUMP); + + if (jumpPotion != null) { + jumpVelocity += (double) ((float) jumpPotion.getAmplifier() + 1) * 0.1F; + } + + if (lastJump.get(p.getUniqueId()) != null && M.ms() - lastJump.get(p.getUniqueId()) < 1000) { + return; + } else if (lastJump.get(p.getUniqueId()) != null && M.ms() - lastJump.get(p.getUniqueId()) > 1500) { + lastJump.remove(p.getUniqueId()); + } + if (p.getLocation().getBlock().getType() != Material.LADDER && velocity.getY() > jumpVelocity && p.isOnline()) { + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1.25f, 0.7f); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1.25f, 1.7f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particles.BLOCK_CRACK, p.getLocation().clone().add(0, 0.3, 0), 15, 0.1, 0.8, 0.1, 0.1, p.getLocation().getBlock().getRelative(BlockFace.DOWN).getBlockData()); + } + p.setVelocity(p.getVelocity().setY(getJumpHeight(getLevel(p)))); + lastJump.put(p.getUniqueId(), M.ms()); + getPlayer(p).getData().addStat("agility.super-jump.jumps", 1); + } + } + }); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak and jump for exceptional height advantage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Agility Super Jump adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Jump Multiplier for the Agility Super Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseJumpMultiplier = 0.23; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Jump Level Multiplier for the Agility Super Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double jumpLevelMultiplier = 0.23; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityWallJump.java b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityWallJump.java new file mode 100644 index 000000000..8453177f1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityWallJump.java @@ -0,0 +1,352 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.agility; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class AgilityWallJump extends SimpleAdaptation { + private final Map airjumps; + private final Map horizontalIntent; + private final Map horizontalIntentTime; + private final Map sneakState; + + public AgilityWallJump() { + super("agility-wall-jump"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("agility.wall_jump.description")); + setDisplayName(Localizer.dLocalize("agility.wall_jump.name")); + setIcon(Material.VINE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(50); + airjumps = new ConcurrentHashMap<>(); + horizontalIntent = new ConcurrentHashMap<>(); + horizontalIntentTime = new ConcurrentHashMap<>(); + sneakState = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LADDER) + .key("challenge_agility_wall_jump_500") + .title(Localizer.dLocalize("advancement.challenge_agility_wall_jump_500.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_wall_jump_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FEATHER) + .key("challenge_agility_parkour_master") + .title(Localizer.dLocalize("advancement.challenge_agility_parkour_master.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_parkour_master.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.HIDDEN) + .build()); + registerMilestone("challenge_agility_wall_jump_500", "agility.wall-jump.air-jumps", 500, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getMaxJumps(level) + C.GRAY + " " + Localizer.dLocalize("agility.wall_jump.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getJumpHeight(level), 0) + C.GRAY + " " + Localizer.dLocalize("agility.wall_jump.lore2")); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + airjumps.remove(id); + horizontalIntent.remove(id); + horizontalIntentTime.remove(id); + sneakState.remove(id); + } + + @EventHandler + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + withPlayerThread(p, e, () -> sneakState.put(p.getUniqueId(), e.isSneaking())); + } + + private int getMaxJumps(int level) { + return (int) (level + (level / getConfig().maxJumpsLevelBonusDivisor)); + } + + private double getJumpHeight(int level) { + return getConfig().jumpHeightBase + (getLevelPercent(level) * getConfig().jumpHeightBonusLevelMultiplier); + } + + @EventHandler + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + sneakState.put(id, p.isSneaking()); + if (resolveInteractContext(p, p.getLocation()) == null) { + return; + } + if (airjumps.containsKey(id)) { + if (p.isOnGround() && !p.getLocation().getBlock().getRelative(BlockFace.DOWN).getBlockData().getMaterial().isAir()) { + airjumps.remove(id); + } + } + + if (e.getTo() == null || e.getFrom().getWorld() == null || e.getTo().getWorld() == null || !e.getFrom().getWorld().equals(e.getTo().getWorld())) { + return; + } + + Vector delta = e.getTo().toVector().subtract(e.getFrom().toVector()); + delta.setY(0); + double movementThresholdSq = getConfig().inputMovementThreshold * getConfig().inputMovementThreshold; + if (delta.lengthSquared() >= movementThresholdSq) { + horizontalIntent.put(id, delta.normalize()); + horizontalIntentTime.put(id, M.ms()); + } + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + withPlayerThread(p, () -> updatePlayer(p)); + } + } + + private void updatePlayer(Player p) { + if (p == null || !p.isOnline()) { + return; + } + + UUID id = p.getUniqueId(); + int level = getActiveInteractLevel(p, p.getLocation()); + if (level <= 0) { + return; + } + + Double j = airjumps.get(id); + + if (j != null && j - 0.25 >= getMaxJumps(level)) { + p.setGravity(true); + return; + } + + if (p.isOnGround()) { + airjumps.remove(id); + if (!p.hasGravity()) { + p.setGravity(true); + } + return; + } + + Block stickBlock = stickToWall(p); + if (p.isFlying() || !isSneaking(id, p)) { + boolean jumped = false; + + if (!p.hasGravity() && p.getFallDistance() > 0.45 && stickBlock != null) { + j = j == null ? 0 : j; + j++; + + if (j - 0.25 <= getMaxJumps(level)) { + jumped = true; + Vector launch = p.getVelocity().clone().setY(getJumpHeight(level)); + if (isBackwardLaunch(p)) { + Vector direction = p.getLocation().getDirection().clone().setY(0); + if (direction.lengthSquared() > 0.000001) { + direction.normalize().multiply(-getConfig().backwardPushSpeed); + launch.setX(direction.getX()); + launch.setZ(direction.getZ()); + } + } + p.setVelocity(launch); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particles.BLOCK_CRACK, p.getLocation().clone().add(0, 0.3, 0), 15, 0.1, 0.8, 0.1, 0.1, stickBlock.getBlockData()); + } + getPlayer(p).getData().addStat("agility.wall-jump.air-jumps", 1); + if (j >= 5 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_agility_parkour_master")) { + getPlayer(p).getAdvancementHandler().grant("challenge_agility_parkour_master"); + } + } + airjumps.put(id, j); + } + + if (!jumped && !p.hasGravity()) { + p.setGravity(true); + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1f, 0.439f); + } + return; + } + + if (stickBlock != null) { + if (p.hasGravity()) { + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1f, 0.89f); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_CHAIN, 1f, 1.39f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particles.BLOCK_CRACK, p.getLocation().clone().add(0, 0.3, 0), 15, 0.1, 0.2, 0.1, 0.1, stickBlock.getBlockData()); + } + } + + applyWallStickForce(p, stickBlock); + p.setGravity(false); + Vector c = p.getVelocity(); + p.setVelocity(p.getVelocity().setY((c.getY() * 0.35) - 0.0025)); + Double vv = airjumps.get(id); + vv = vv == null ? 0 : vv; + vv += 0.0127; + airjumps.put(id, vv); + } + + if (stickBlock == null && !p.hasGravity()) { + p.setGravity(true); + } + } + + private boolean isBackwardLaunch(Player p) { + UUID id = p.getUniqueId(); + Long at = horizontalIntentTime.get(id); + Vector intent = horizontalIntent.get(id); + if (at == null || intent == null || M.ms() - at > getConfig().inputWindowMs) { + return false; + } + + Vector facing = p.getLocation().getDirection().clone().setY(0); + if (facing.lengthSquared() <= 0.000001) { + return false; + } + + facing.normalize(); + return intent.dot(facing) <= -Math.abs(getConfig().backwardIntentDotThreshold); + } + + private boolean isSneaking(UUID id, Player p) { + Boolean cached = sneakState.get(id); + return cached != null ? cached : p.isSneaking(); + } + + private Block stickToWall(Player p) { + for (Block wall : getBlocks(p)) { + if (wall.getBlockData().getMaterial().isSolid()) { + return wall; + } + } + + return null; + } + + private void applyWallStickForce(Player p, Block wall) { + Vector velocity = p.getVelocity(); + Vector shift = p.getLocation().toVector().subtract(wall.getLocation().clone().add(0.5, 0.5, 0.5).toVector()); + velocity.setX(velocity.getX() - (shift.getX() / 16)); + velocity.setZ(velocity.getZ() - (shift.getZ() / 16)); + p.setVelocity(velocity); + } + + private Block[] getBlocks(Player p) { + Block base = p.getLocation().getBlock(); + return new Block[]{ + base.getRelative(BlockFace.NORTH), + base.getRelative(BlockFace.SOUTH), + base.getRelative(BlockFace.EAST), + base.getRelative(BlockFace.WEST), + base.getRelative(BlockFace.NORTH_EAST), + base.getRelative(BlockFace.SOUTH_EAST), + base.getRelative(BlockFace.NORTH_WEST), + base.getRelative(BlockFace.SOUTH_WEST), + base.getRelative(BlockFace.NORTH_EAST).getRelative(BlockFace.UP), + base.getRelative(BlockFace.SOUTH_EAST).getRelative(BlockFace.UP), + base.getRelative(BlockFace.NORTH_WEST).getRelative(BlockFace.UP), + base.getRelative(BlockFace.SOUTH_WEST).getRelative(BlockFace.UP), + base.getRelative(BlockFace.NORTH).getRelative(BlockFace.UP), + base.getRelative(BlockFace.SOUTH).getRelative(BlockFace.UP), + base.getRelative(BlockFace.EAST).getRelative(BlockFace.UP), + base.getRelative(BlockFace.WEST).getRelative(BlockFace.UP), + }; + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Hold shift while mid-air against a wall to latch and jump.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Agility Wall Jump adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Jumps Level Bonus Divisor for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxJumpsLevelBonusDivisor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Jump Height Base for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double jumpHeightBase = 0.625; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Jump Height Bonus Level Multiplier for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double jumpHeightBonusLevelMultiplier = 0.225; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Backward Push Speed for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double backwardPushSpeed = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Backward Intent Dot Threshold for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double backwardIntentDotThreshold = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Input Movement Threshold for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double inputMovementThreshold = 0.0025; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Input Window Ms for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long inputWindowMs = 450; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityWindUp.java b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityWindUp.java new file mode 100644 index 000000000..eae5d6a19 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/agility/AgilityWindUp.java @@ -0,0 +1,329 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.agility; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.events.api.ReflectiveHandler; +import art.arcane.adapt.util.reflect.events.api.entity.EntityDismountEvent; +import art.arcane.adapt.util.reflect.events.api.entity.EntityMountEvent; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class AgilityWindUp extends SimpleAdaptation { + private final Map ticksRunning; + private final Map states; + + public AgilityWindUp() { + super("agility-wind-up"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("agility.wind_up.description")); + setDisplayName(Localizer.dLocalize("agility.wind_up.name")); + setIcon(Material.POWERED_RAIL); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setInitialCost(getConfig().initialCost); + setInterval(50); + ticksRunning = new ConcurrentHashMap<>(); + states = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.POWERED_RAIL) + .key("challenge_agility_wind_up_10min") + .title(Localizer.dLocalize("advancement.challenge_agility_wind_up_10min.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_wind_up_10min.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ACTIVATOR_RAIL) + .key("challenge_agility_wind_up_2hr") + .title(Localizer.dLocalize("advancement.challenge_agility_wind_up_2hr.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_wind_up_2hr.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_agility_wind_up_10min", "agility.wind-up.max-speed-ticks", 12000, 400); + registerMilestone("challenge_agility_wind_up_2hr", "agility.wind-up.max-speed-ticks", 144000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getWindupSpeed(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("agility.wind_up.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getWindupTicks(getLevelPercent(level)) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("agility.wind_up.lore2")); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + clearAndRemoveState(e.getPlayer()); + } + + @EventHandler + public void on(PlayerDeathEvent e) { + clearAndRemoveState(e.getEntity()); + } + + @ReflectiveHandler + public void on(EntityMountEvent event) { + if (event.getEntity().getType() != EntityType.PLAYER) { + return; + } + + Player p = (Player) event.getEntity(); + UUID id = p.getUniqueId(); + ticksRunning.remove(id); + RuntimeState state = states.get(id); + if (state != null) { + clearBoost(p, state); + } + } + + @ReflectiveHandler + public void on(EntityDismountEvent event) { + if (event.getEntity().getType() != EntityType.PLAYER) { + return; + } + + Player p = (Player) event.getEntity(); + UUID id = p.getUniqueId(); + ticksRunning.remove(id); + RuntimeState state = states.get(id); + if (state != null) { + clearBoost(p, state); + } + } + + private double getWindupTicks(double factor) { + return M.lerp(getConfig().windupTicksSlowest, getConfig().windupTicksFastest, factor); + } + + private double getWindupSpeed(double factor) { + return getConfig().windupSpeedBase + (factor * getConfig().windupSpeedLevelMultiplier); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + withPlayerThread(p, () -> updatePlayer(p)); + } + } + + private void updatePlayer(Player p) { + if (p == null || !p.isOnline()) { + return; + } + + UUID id = p.getUniqueId(); + RuntimeState state = states.computeIfAbsent(id, key -> new RuntimeState()); + if (!hasActiveAdaptation(p) || !isWindupEligible(p) || !p.isSprinting()) { + ticksRunning.remove(id); + clearBoost(p, state); + return; + } + + double factor = getLevelPercent(p); + if (factor <= 0) { + ticksRunning.remove(id); + clearBoost(p, state); + return; + } + + ticksRunning.compute(id, (k, v) -> v == null ? 1 : v + 1); + int tr = ticksRunning.getOrDefault(id, 0); + if (tr <= 0) { + return; + } + + double ticksToMax = Math.max(1D, getWindupTicks(factor)); + double progress = Math.min(M.lerpInverse(0, ticksToMax, tr), 1); + double speedIncrease = M.lerp(0, getWindupSpeed(factor), progress); + applyBoost(p, state, speedIncrease); + + if (areParticlesEnabled()) { + if (M.r(0.2 * progress)) { + p.getWorld().spawnParticle(Particle.LAVA, p.getLocation(), 1); + } + + if (M.r(0.25 * progress)) { + p.getWorld().spawnParticle(Particle.FLAME, p.getLocation(), 1, 0, 0, 0, 0); + } + } + + if (progress >= 1.0 && isMovingHorizontally(p, getConfig().movementVelocityThreshold)) { + getPlayer(p).getData().addStat("agility.wind-up.max-speed-ticks", 1); + } + } + + private void applyBoost(Player p, RuntimeState state, double speedIncrease) { + if (!state.boosting) { + state.boosting = true; + state.originalWalkSpeed = p.getWalkSpeed(); + } + + float baseWalkSpeed = clampWalkSpeed(state.originalWalkSpeed); + double bonus = Math.max(0D, speedIncrease) * Math.max(0D, getConfig().walkSpeedBonusScalar); + float target = clampWalkSpeed((float) (baseWalkSpeed * (1D + bonus))); + float configuredMaxWalkSpeed = clampWalkSpeed((float) Math.max(0D, getConfig().maxWalkSpeed)); + float maxWalkSpeed = Math.max(baseWalkSpeed, configuredMaxWalkSpeed); + if (target > maxWalkSpeed) { + target = maxWalkSpeed; + } + + float smoothing = (float) Math.max(0D, Math.min(1D, getConfig().walkSpeedLerpPerTick)); + float current = p.getWalkSpeed(); + float next = current + ((target - current) * smoothing); + if (Math.abs(target - next) < 0.0005f) { + next = target; + } + + if (Math.abs(current - next) > 0.0001f) { + p.setWalkSpeed(next); + } + } + + private void clearBoost(Player p, RuntimeState state) { + if (!state.boosting) { + return; + } + + state.boosting = false; + float restore = clampWalkSpeed(state.originalWalkSpeed); + float current = p.getWalkSpeed(); + if (Math.abs(current - restore) > 0.0001f) { + p.setWalkSpeed(restore); + } + } + + private void clearAndRemoveState(Player p) { + if (p == null) { + return; + } + + UUID id = p.getUniqueId(); + ticksRunning.remove(id); + RuntimeState state = states.remove(id); + if (state != null) { + clearBoost(p, state); + } + } + + private boolean isMovingHorizontally(Player p, double velocityThreshold) { + Vector movement = new Vector(p.getVelocity().getX(), 0, p.getVelocity().getZ()); + double threshold = Math.max(0D, velocityThreshold); + return movement.lengthSquared() > (threshold * threshold); + } + + private boolean isWindupEligible(Player p) { + GameMode mode = p.getGameMode(); + if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { + return false; + } + + return !p.isDead() + && !p.isSwimming() + && !p.isFlying() + && !p.isGliding() + && !p.isSneaking() + && p.getVehicle() == null; + } + + private float clampWalkSpeed(float speed) { + if (speed < 0f) { + return 0f; + } + if (speed > 1f) { + return 1f; + } + return speed; + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + + @NoArgsConstructor + @ConfigDescription("Get faster the longer you sprint.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Agility Wind Up adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Windup Ticks Slowest for the Agility Wind Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windupTicksSlowest = 180; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Windup Ticks Fastest for the Agility Wind Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windupTicksFastest = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Windup Speed Base for the Agility Wind Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windupSpeedBase = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Windup Speed Level Multiplier for the Agility Wind Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windupSpeedLevelMultiplier = 0.225; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scales walk-speed gain from windup speed increase while sprinting.", impact = "Higher values produce stronger land-speed acceleration.") + double walkSpeedBonusScalar = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Smooths walk-speed changes toward windup target each tick.", impact = "Higher values ramp faster; lower values feel softer.") + double walkSpeedLerpPerTick = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum walk speed this adaptation can set while windup is active.", impact = "Higher values allow faster grounded sprinting before clamping.") + double maxWalkSpeed = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum horizontal movement speed required for max-speed stat credit.", impact = "Higher values require clearer movement before counting max-speed ticks.") + double movementVelocityThreshold = 0.015; + } + + private static class RuntimeState { + private boolean boosting; + private float originalWalkSpeed; + } + +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectChalkLine.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectChalkLine.java new file mode 100644 index 000000000..a7e7c47e9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectChalkLine.java @@ -0,0 +1,238 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ArchitectChalkLine extends SimpleAdaptation { + private final Map anchors; + + public ArchitectChalkLine() { + super("architect-chalk-line"); + registerConfiguration(ArchitectChalkLine.Config.class); + setDescription(Localizer.dLocalize("architect.chalk_line.description")); + setDisplayName(Localizer.dLocalize("architect.chalk_line.name")); + setIcon(Material.STRING); + setInterval(500); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + anchors = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.STRING) + .key("challenge_architect_chalk_line_50") + .title(Localizer.dLocalize("advancement.challenge_architect_chalk_line_50.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_chalk_line_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.STRING) + .key("challenge_architect_chalk_line_500") + .title(Localizer.dLocalize("advancement.challenge_architect_chalk_line_500.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_chalk_line_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_chalk_line_50", "architect.chalk-line.lines-drawn", 50, 300); + registerMilestone("challenge_architect_chalk_line_500", "architect.chalk-line.lines-drawn", 500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.chalk_line.lore1")); + v.addLore(C.GREEN + "" + (getDurationMillis(getLevelPercent(level)) / 1000) + C.GRAY + " " + Localizer.dLocalize("architect.chalk_line.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isItem(hand) || !hand.getType().isBlock()) { + return; + } + + UUID id = p.getUniqueId(); + ChalkAnchor existing = anchors.remove(id); + if (existing != null && M.ms() < existing.expiresAt()) { + e.setUseItemInHand(Event.Result.DENY); + e.setUseInteractedBlock(Event.Result.DENY); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_BREAK, 0.5f, 1.2f); + return; + } + + Block target = action == Action.RIGHT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (target == null) { + return; + } + + Adaptation.BlockActionContext context = resolveInteractContext(p, target.getLocation(), Player::isSneaking); + if (context == null) { + return; + } + + e.setUseItemInHand(Event.Result.DENY); + e.setUseInteractedBlock(Event.Result.DENY); + long expiresAt = M.ms() + getDurationMillis(getLevelPercent(context.level())); + Location anchor = target.getLocation().add(0.5, 1.1, 0.5); + anchors.put(id, new ChalkAnchor(anchor, p.getFacing(), expiresAt)); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.7f, 1.5f); + getPlayer(p).getData().addStat("architect.chalk-line.lines-drawn", 1); + xp(p, getConfig().xpPerLine); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + anchors.remove(e.getPlayer().getUniqueId()); + } + + @Override + public void onTick() { + if (anchors.isEmpty()) { + return; + } + + long now = M.ms(); + double rangeSquared = getConfig().renderRangeBlocks * getConfig().renderRangeBlocks; + for (Map.Entry entry : anchors.entrySet()) { + ChalkAnchor anchor = entry.getValue(); + if (now >= anchor.expiresAt()) { + anchors.remove(entry.getKey(), anchor); + continue; + } + + Player p = Bukkit.getPlayer(entry.getKey()); + if (p == null || !p.isOnline() || !p.getWorld().equals(anchor.anchor().getWorld())) { + continue; + } + + if (p.getLocation().distanceSquared(anchor.anchor()) > rangeSquared) { + continue; + } + + withPlayerThread(p, () -> renderLine(anchor)); + } + } + + private void renderLine(ChalkAnchor anchor) { + if (!areParticlesEnabled()) { + return; + } + + Vector direction = anchor.direction().getDirection(); + Location end = anchor.anchor().clone().add(direction.multiply(getConfig().lineLengthBlocks)); + vfxParticleLine(anchor.anchor(), end, getConfig().particlesPerLine, Particle.END_ROD); + } + + private long getDurationMillis(double factor) { + return (long) Math.max(1000, M.lerp(getConfig().minDurationSeconds, getConfig().maxDurationSeconds, factor) * 1000D); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record ChalkAnchor(Location anchor, BlockFace direction, long expiresAt) { + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click a block to snap a particle guide line along your facing axis.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Chalk Line adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Guide line lifetime in seconds at level 0 progression.", impact = "Higher values keep low-level guide lines visible longer.") + double minDurationSeconds = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Guide line lifetime in seconds at maximum level progression.", impact = "Higher values keep max-level guide lines visible longer.") + double maxDurationSeconds = 120; + @art.arcane.adapt.util.config.ConfigDoc(value = "Length of the rendered guide line in blocks.", impact = "Higher values render a longer alignment guide.") + double lineLengthBlocks = 24; + @art.arcane.adapt.util.config.ConfigDoc(value = "Number of particles rendered along the guide line each render pass.", impact = "Higher values draw a denser line at slightly more cost.") + int particlesPerLine = 24; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum distance in blocks from the anchor at which the line still renders.", impact = "Higher values render the guide line even when the player walks farther away.") + double renderRangeBlocks = 64; + @art.arcane.adapt.util.config.ConfigDoc(value = "Adaptation xp granted per chalk line placed.", impact = "Higher values speed up adaptation progression from chalk lines.") + double xpPerLine = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectDemolition.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectDemolition.java new file mode 100644 index 000000000..ac9e81f1e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectDemolition.java @@ -0,0 +1,245 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; + +public class ArchitectDemolition extends SimpleAdaptation { + private final Map placed; + private final Map> order; + + public ArchitectDemolition() { + super("architect-demolition"); + registerConfiguration(ArchitectDemolition.Config.class); + setDescription(Localizer.dLocalize("architect.demolition.description")); + setDisplayName(Localizer.dLocalize("architect.demolition.name")); + setIcon(Material.TNT); + setInterval(10880); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + placed = new ConcurrentHashMap<>(); + order = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TNT) + .key("challenge_architect_demolition_500") + .title(Localizer.dLocalize("advancement.challenge_architect_demolition_500.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_demolition_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TNT) + .key("challenge_architect_demolition_5k") + .title(Localizer.dLocalize("advancement.challenge_architect_demolition_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_demolition_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_demolition_500", "architect.demolition.blocks-demolished", 500, 300); + registerMilestone("challenge_architect_demolition_5k", "architect.demolition.blocks-demolished", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.demolition.lore1")); + v.addLore(C.GREEN + "" + (getWindowMillis(getLevelPercent(level)) / 1000) + C.GRAY + " " + Localizer.dLocalize("architect.demolition.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockPlaceEvent e) { + Player p = e.getPlayer(); + if (getActiveLevel(p) <= 0) { + return; + } + + UUID id = p.getUniqueId(); + Block block = e.getBlock(); + ConcurrentLinkedDeque deque = order.computeIfAbsent(id, unused -> new ConcurrentLinkedDeque<>()); + deque.addLast(block); + placed.put(block, new DemolitionMark(id, M.ms())); + int cap = getConfig().maxTrackedPerPlayer; + while (deque.size() > cap) { + Block oldest = deque.pollFirst(); + if (oldest != null) { + placed.remove(oldest); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockDamageEvent e) { + if (placed.isEmpty()) { + return; + } + + DemolitionMark mark = placed.get(e.getBlock()); + if (mark == null) { + return; + } + + Player p = e.getPlayer(); + if (!mark.owner().equals(p.getUniqueId())) { + return; + } + + Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + if (M.ms() - mark.at() > getWindowMillis(getLevelPercent(context.level()))) { + placed.remove(e.getBlock(), mark); + return; + } + + e.setInstaBreak(true); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(e.getBlock().getLocation(), Sound.BLOCK_AMETHYST_BLOCK_HIT, 0.5f, 0.7f); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (placed.isEmpty()) { + return; + } + + DemolitionMark mark = placed.remove(e.getBlock()); + if (mark == null) { + return; + } + + Player p = e.getPlayer(); + if (!mark.owner().equals(p.getUniqueId())) { + return; + } + + int level = getActiveBlockBreakLevel(p, e.getBlock().getLocation()); + if (level <= 0) { + return; + } + + if (M.ms() - mark.at() > getWindowMillis(getLevelPercent(level))) { + return; + } + + Material type = e.getBlock().getType(); + if (type.isAir() || !type.isItem()) { + return; + } + + e.setDropItems(false); + e.getBlock().getWorld().dropItemNaturally(e.getBlock().getLocation(), new ItemStack(type)); + if (areParticlesEnabled()) { + vfxCuboidOutline(e.getBlock(), Particle.SCRAPE); + } + + getPlayer(p).getData().addStat("architect.demolition.blocks-demolished", 1); + xp(p, getConfig().xpPerDemolish); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + ConcurrentLinkedDeque deque = order.remove(e.getPlayer().getUniqueId()); + if (deque == null) { + return; + } + + for (Block block : deque) { + placed.remove(block); + } + } + + private long getWindowMillis(double factor) { + return (long) Math.max(1000, M.lerp(getConfig().minWindowSeconds, getConfig().maxWindowSeconds, factor) * 1000D); + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record DemolitionMark(UUID owner, long at) { + } + + @NoArgsConstructor + @ConfigDescription("Blocks you recently placed break near-instantly and always drop their item.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Demolition adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "How many seconds a placement counts as recent at level 0 progression.", impact = "Higher values let low-level players instabreak older placements.") + double minWindowSeconds = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "How many seconds a placement counts as recent at maximum level progression.", impact = "Higher values let max-level players instabreak older placements.") + double maxWindowSeconds = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum number of recent placements tracked per player.", impact = "Higher values remember more placements at slightly more memory cost.") + int maxTrackedPerPlayer = 64; + @art.arcane.adapt.util.config.ConfigDoc(value = "Adaptation xp granted per demolished block.", impact = "Higher values speed up adaptation progression from demolition.") + double xpPerDemolish = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectElevator.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectElevator.java new file mode 100644 index 000000000..5fc1b2c12 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectElevator.java @@ -0,0 +1,432 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import com.jeff_media.customblockdata.CustomBlockData; +import com.jeff_media.customblockdata.events.CustomBlockDataMoveEvent; +import com.jeff_media.customblockdata.events.CustomBlockDataRemoveEvent; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.VoxelShape; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class ArchitectElevator extends SimpleAdaptation { + private static final NamespacedKey ELEVATOR_KEY = new NamespacedKey(Adapt.instance, "elevator"); + private static final NamespacedKey TARGET_DOWN = new NamespacedKey(Adapt.instance, "target_down"); + private static final NamespacedKey TARGET_UP = new NamespacedKey(Adapt.instance, "target_up"); + + private static final int PARTICLE_COUNT = 20; + private static final float SOUND_VOLUME = 1f; + private static final float SOUND_PITCH = 1f; + + private final Set players = java.util.concurrent.ConcurrentHashMap.newKeySet(); + + public ArchitectElevator() { + super("architect-elevator"); + registerConfiguration(ArchitectElevator.Config.class); + setDescription(Localizer.dLocalize("architect.elevator.description")); + setDisplayName(Localizer.dLocalize("architect.elevator.name")); + setIcon(Material.HEAVY_WEIGHTED_PRESSURE_PLATE); + setInterval(988); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + + registerRecipe(AdaptRecipe.shaped() + .key("elevator") + .shape("XXX") + .shape("XYX") + .shape("XXX") + .ingredient(new MaterialChar('X', Tag.WOOL)) + .ingredient(new MaterialChar('Y', Material.ENDER_PEARL)) + .result(getElevatorItem()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WHITE_WOOL) + .key("challenge_architect_elevator_100") + .title(Localizer.dLocalize("advancement.challenge_architect_elevator_100.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_elevator_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.WHITE_WOOL) + .key("challenge_architect_elevator_penthouse") + .title(Localizer.dLocalize("advancement.challenge_architect_elevator_penthouse.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_elevator_penthouse.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_elevator_100", "architect.elevator.trips", 100, 300); + } + + private static boolean isElevator(Block b) { + return b.getType() == Material.NOTE_BLOCK + && CustomBlockData.hasCustomBlockData(b, Adapt.instance) + && new CustomBlockData(b, Adapt.instance) + .has(ELEVATOR_KEY, PersistentDataType.INTEGER); + } + + private static boolean hasEnoughSpace(Player player, int targetY) { + BoundingBox box = player.getBoundingBox() + .shift(0, -player.getLocation().getY(), 0) + .shift(0, targetY, 0); + + double maxX = Math.ceil(box.getMaxX()); + double maxY = Math.ceil(box.getMaxY()); + double maxZ = Math.ceil(box.getMaxZ()); + World world = player.getWorld(); + for (int x = (int) box.getMinX(); x <= maxX; x++) { + for (int z = (int) box.getMinZ(); z <= maxZ; z++) { + for (int y = (int) box.getMinY(); y <= maxY; y++) { + Block block = world.getBlockAt(x, y, z); + if (block.isPassable() || block.isLiquid()) + continue; + VoxelShape shape = block.getCollisionShape(); + box.shift(-x, -y, -z); + if (shape.overlaps(box)) + return false; + box.shift(x, y, z); + } + } + } + return true; + } + + @Override + public void addStats(int level, Element v) { + + } + + public ItemStack getElevatorItem() { + ItemStack elevatorItem = CustomModel.get(Material.NOTE_BLOCK, "architect", "elevator", "item") + .toItemStack(); + ItemMeta meta = elevatorItem.getItemMeta(); + if (meta != null) { + meta.getPersistentDataContainer().set(ELEVATOR_KEY, PersistentDataType.BYTE, (byte) 0); + meta.setDisplayName(Localizer.dLocalize("items.elevator_block.name")); + meta.setLore(List.of(Localizer.dLocalize("items.elevator_block.usage1"), + Localizer.dLocalize("items.elevator_block.usage2"), + Localizer.dLocalize("items.elevator_block.usage3"))); + elevatorItem.setItemMeta(meta); + } + return elevatorItem; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerMoveEvent e) { + if (e.getTo() == null) return; + Player player = e.getPlayer(); + + if (!players.add(player.getUniqueId())) { + if (e.getFrom().getY() < e.getTo().getY() || player.isFlying()) + players.remove(player.getUniqueId()); + return; + } + + if (player.isFlying() || player.getVelocity().getY() <= 0 || e.getFrom().getY() >= e.getTo().getY()) + return; + + Block block = findElevator(player); + if (block == null) return; + handleElevatorMovement(block, player, false); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerToggleSneakEvent event) { + if (!event.isSneaking() || event.getPlayer().isInsideVehicle()) return; + Player player = event.getPlayer(); + Block block = findElevator(player); + if (block == null) return; + handleElevatorMovement(block, player, true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPlaceEvent event) { + ItemMeta meta = event.getItemInHand().getItemMeta(); + if (meta == null || !meta.getPersistentDataContainer().has(ELEVATOR_KEY, PersistentDataType.BYTE)) + return; + int maxDistance = getMaxDistance(event.getPlayer()); + if (maxDistance <= 0) { + event.setCancelled(true); + return; + } + + Block block = event.getBlock(); + World world = block.getWorld(); + CustomBlockData data = new CustomBlockData(block, Adapt.instance); + data.set(ELEVATOR_KEY, PersistentDataType.INTEGER, maxDistance); + + int lowerDist = Math.min(block.getY() - world.getMinHeight(), maxDistance); + for (int d = 1; d <= lowerDist; d++) { + org.bukkit.block.Block lower = block.getRelative(BlockFace.DOWN, d); + if (checkElevator(lower, TARGET_UP, d)) { + data.set(TARGET_DOWN, PersistentDataType.INTEGER, d); + break; + } + } + + int upperDist = Math.min(world.getMaxHeight() - block.getY(), maxDistance); + for (int d = 1; d <= upperDist; d++) { + org.bukkit.block.Block upper = block.getRelative(BlockFace.UP, d); + if (checkElevator(upper, TARGET_DOWN, d)) { + data.set(TARGET_UP, PersistentDataType.INTEGER, d); + } + } + } + + public int getMaxDistance(Player player) { + int level = getActiveLevel(player); + if (level == 0) return 0; + Config config = getConfig(); + return config.baseDistance * (level * config.multiplier); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(CustomBlockDataMoveEvent event) { + if (!event.getCustomBlockData().has(ELEVATOR_KEY)) return; + event.setCancelled(true); + + Event bukkit = event.getBukkitEvent(); + if (bukkit instanceof Cancellable cancellable) { + cancellable.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(BlockExplodeEvent event) { + event.blockList().removeIf(ArchitectElevator::isElevator); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(EntityExplodeEvent event) { + event.blockList().removeIf(ArchitectElevator::isElevator); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(CustomBlockDataRemoveEvent event) { + CustomBlockData data = event.getCustomBlockData(); + if (!data.has(ELEVATOR_KEY)) return; + Event bukkit = event.getBukkitEvent(); + if (!(bukkit instanceof BlockBreakEvent breakEvent)) { + if (bukkit instanceof Cancellable cancellable) + cancellable.setCancelled(true); + event.setCancelled(true); + return; + } + + breakEvent.setDropItems(false); + Block block = event.getBlock(); + World world = block.getWorld(); + Location location = block.getLocation(); + world.dropItemNaturally(location, getElevatorItem()); + + data.remove(ELEVATOR_KEY); + int y = block.getY(); + int lowerY = data.getOrDefault(TARGET_DOWN, PersistentDataType.INTEGER, 0); + int upperY = data.getOrDefault(TARGET_UP, PersistentDataType.INTEGER, 0); + data.remove(TARGET_DOWN); + data.remove(TARGET_UP); + + if (y - lowerY < world.getMinHeight()) + lowerY = 0; + + if (y + upperY > world.getMaxHeight()) + upperY = 0; + + if (lowerY != 0 && upperY != 0) { + Block lower = block.getRelative(BlockFace.DOWN, lowerY); + Block upper = block.getRelative(BlockFace.UP, upperY); + + boolean lowerElevator = isElevator(lower); + boolean upperElevator = isElevator(upper); + + if (lowerElevator && upperElevator) { + CustomBlockData lowerData = new CustomBlockData(lower, Adapt.instance); + CustomBlockData upperData = new CustomBlockData(upper, Adapt.instance); + + int dist = upperY + lowerY; + int lowerDist = lowerData.getOrDefault(ELEVATOR_KEY, PersistentDataType.INTEGER, 0); + int upperDist = upperData.getOrDefault(ELEVATOR_KEY, PersistentDataType.INTEGER, 0); + int maxDistance = Math.max(upperDist, lowerDist); + + if (dist <= maxDistance) { + lowerData.set(TARGET_UP, PersistentDataType.INTEGER, dist); + upperData.set(TARGET_DOWN, PersistentDataType.INTEGER, dist); + } else { + lowerData.remove(TARGET_UP); + upperData.remove(TARGET_DOWN); + } + } else if (lowerElevator) { + new CustomBlockData(lower, Adapt.instance) + .remove(TARGET_UP); + } else if (upperElevator) { + new CustomBlockData(upper, Adapt.instance) + .remove(TARGET_DOWN); + } + } else if (lowerY != 0) { + Block lower = block.getRelative(BlockFace.DOWN, lowerY); + + if (isElevator(lower)) { + new CustomBlockData(lower, Adapt.instance) + .remove(TARGET_UP); + } + } else if (upperY != 0) { + Block upper = block.getRelative(BlockFace.UP, upperY); + + if (isElevator(upper)) { + new CustomBlockData(upper, Adapt.instance) + .remove(TARGET_DOWN); + } + } + } + + @Nullable + private Block findElevator(Player player) { + Block base = player.getLocation().getBlock(); + for (int d = 1; d <= 2; d++) { + Block rel = base.getRelative(BlockFace.DOWN, d); + if (isElevator(rel)) + return rel; + } + return null; + } + + private boolean checkElevator(Block block, NamespacedKey key, int source) { + if (!isElevator(block)) + return false; + + new CustomBlockData(block, Adapt.instance) + .set(key, PersistentDataType.INTEGER, source); + return true; + } + + private void handleElevatorMovement(Block block, Player player, boolean down) { + if (!isElevator(block) || player.isInsideVehicle()) + return; + + CustomBlockData data = new CustomBlockData(block, Adapt.instance); + int distance = data.getOrDefault(down ? TARGET_DOWN : TARGET_UP, PersistentDataType.INTEGER, 0); + if (distance == 0) + return; + int targetY = block.getY() + (down ? -distance : distance); + if (targetY < block.getWorld().getMinHeight() || targetY > block.getWorld().getMaxHeight()) + return; + + Block target = block.getRelative(down ? BlockFace.DOWN : BlockFace.UP, distance); + if (!isElevator(target)) + return; + + org.bukkit.Location loc = player.getLocation(); + loc.setY(target.getY() + 1); + + if (!hasEnoughSpace(player, loc.getBlockY())) + return; + + teleportPlayer(player, loc); + getPlayer(player).getData().addStat("architect.elevator.trips", 1); + if (distance >= 50 && AdaptConfig.get().isAdvancements() && !getPlayer(player).getData().isGranted("challenge_architect_elevator_penthouse")) { + getPlayer(player).getAdvancementHandler().grant("challenge_architect_elevator_penthouse"); + } + } + + private void teleportPlayer(Player p, Location l) { + playTeleportEffects(p); + J.teleport(p, l); + SoundPlayer.of(p.getWorld()).play(p, Sound.ENTITY_ENDERMAN_TELEPORT, SOUND_VOLUME, SOUND_PITCH); + playTeleportEffects(p); + } + + private void playTeleportEffects(Player p) { + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.PORTAL, p.getLocation(), PARTICLE_COUNT); + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Build wool elevators to teleport vertically.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Distance for the Architect Elevator adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseDistance = 32; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Multiplier for the Architect Elevator adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int multiplier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.40; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectFoundation.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectFoundation.java new file mode 100644 index 000000000..72ffea8c2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectFoundation.java @@ -0,0 +1,364 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Painting; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPistonRetractEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ArchitectFoundation extends SimpleAdaptation { + private static final BlockData AIR = Material.AIR.createBlockData(); + private static final BlockData BLOCK = Material.TINTED_GLASS.createBlockData(); + private final Map blockPower; + private final Map cooldowns; + private final Set active; + private final Set activeBlocks; + + public ArchitectFoundation() { + super("architect-foundation"); + registerConfiguration(ArchitectFoundation.Config.class); + setDescription(Localizer.dLocalize("architect.foundation.description")); + setDisplayName(Localizer.dLocalize("architect.foundation.name")); + setIcon(Material.TINTED_GLASS); + setInterval(988); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + blockPower = new ConcurrentHashMap<>(); + cooldowns = new ConcurrentHashMap<>(); + active = ConcurrentHashMap.newKeySet(); + activeBlocks = ConcurrentHashMap.newKeySet(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SCAFFOLDING) + .key("challenge_architect_foundation_1k") + .title(Localizer.dLocalize("advancement.challenge_architect_foundation_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_foundation_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SCAFFOLDING) + .key("challenge_architect_foundation_10k") + .title(Localizer.dLocalize("advancement.challenge_architect_foundation_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_foundation_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_foundation_1k", "architect.foundation.blocks-placed", 1000, 300); + registerMilestone("challenge_architect_foundation_10k", "architect.foundation.blocks-placed", 10000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.foundation.lore1") + + (getBlockPower(getLevelPercent(level))) + C.GRAY + " " + + Localizer.dLocalize("architect.foundation.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + withPlayerThread(p, e, () -> { + UUID id = p.getUniqueId(); + if (!p.isSneaking()) { + return; + } + if (getActiveBlockPlaceLevel(p, p.getLocation()) <= 0) { + return; + } + if (e.getTo() == null || !e.getFrom().getBlock().equals(e.getTo().getBlock())) { + return; + } + if (!this.active.contains(id)) { + return; + } + int power = blockPower.getOrDefault(id, 0); + + if (power <= 0) { + return; + } + + Location l = e.getTo(); + World world = l.getWorld(); + Set locs = new HashSet<>(); + locs.add(world.getBlockAt(l.clone().add(0.3, -1, -0.3))); + locs.add(world.getBlockAt(l.clone().add(-0.3, -1, -0.3))); + locs.add(world.getBlockAt(l.clone().add(0.3, -1, 0.3))); + locs.add(world.getBlockAt(l.clone().add(-0.3, -1, +0.3))); + + for (Block b : locs) { + if (addFoundation(b)) { + power--; + getPlayer(p).getData().addStat("architect.foundation.blocks-placed", 1); + } + + if (power <= 0) { + break; + } + } + + blockPower.put(id, power); + }); + } + + // prevent piston from moving blocks // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockPistonExtendEvent e) { + e.getBlocks().forEach(b -> { + if (activeBlocks.contains(b)) { + Adapt.verbose("Cancelled Piston Extend on Adaptation Foundation Block"); + e.setCancelled(true); + } + }); + } + + // prevent piston from pulling blocks // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockPistonRetractEvent e) { + e.getBlocks().forEach(b -> { + if (activeBlocks.contains(b)) { + Adapt.verbose("Cancelled Piston Retract on Adaptation Foundation Block"); + e.setCancelled(true); + } + }); + } + + // prevent TNT from destroying blocks // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockExplodeEvent e) { + if (activeBlocks.contains(e.getBlock())) { + Adapt.verbose("Cancelled Block Explosion on Adaptation Foundation Block"); + e.setCancelled(true); + } + } + + // prevent block from being destroyed // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockBreakEvent e) { + if (activeBlocks.contains(e.getBlock())) { + e.setCancelled(true); + } + } + + // prevent Entities from destroying blocks // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityExplodeEvent e) { + e.blockList().removeIf(activeBlocks::contains); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> { + if (p.getGameMode().equals(GameMode.CREATIVE) + || p.getGameMode().equals(GameMode.SPECTATOR)) { + return; + } + UUID id = p.getUniqueId(); + + boolean ready = !hasCooldown(id); + boolean active = this.active.contains(id); + + if (e.isSneaking() && ready && !active) { + this.active.add(id); + cooldowns.put(id, Long.MAX_VALUE); + // effect start placing + } else if (!e.isSneaking() && active) { + this.active.remove(id); + cooldowns.put(id, M.ms() + getConfig().cooldown); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 1.0f, 10.0f); + sp.play(p.getLocation(), Sound.BLOCK_SCULK_CATALYST_BREAK, 1.0f, 0.81f); + } + }); + } + + public boolean addFoundation(Block block) { + if (!block.getType().isAir()) { + return false; + } + + if (!block.getWorld() + .getNearbyEntities(block.getLocation() + .add(.5, .5, .5), .5, .5, .5, entity -> + entity instanceof ItemFrame || entity instanceof Painting).isEmpty()) + return false; + + + J.runAt(block.getLocation(), () -> { + block.setBlockData(BLOCK); + activeBlocks.add(block); + }); + SoundPlayer spw = SoundPlayer.of(block.getWorld()); + spw.play(block.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 1.0f, 1.0f); + if (areParticlesEnabled()) { + + vfxCuboidOutline(block, Particle.REVERSE_PORTAL); + vfxCuboidOutline(block, Particle.ASH); + } + J.runAt(block.getLocation(), () -> removeFoundation(block), 3 * 20); + return true; + } + + public void removeFoundation(Block block) { + if (!block.getBlockData().equals(BLOCK)) { + return; + } + + J.runAt(block.getLocation(), () -> { + block.setBlockData(AIR); + activeBlocks.remove(block); + SoundPlayer spw = SoundPlayer.of(block.getWorld()); + spw.play(block.getLocation(), Sound.BLOCK_DEEPSLATE_BREAK, 1.0f, 1.0f); + }); + if (areParticlesEnabled()) { + vfxCuboidOutline(block, Particles.ENCHANTMENT_TABLE); + } + } + + public int getBlockPower(double factor) { + return (int) Math.floor(M.lerp(getConfig().minBlocks, getConfig().maxBlocks, factor)); + } + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player i = adaptPlayer.getPlayer(); + if (i == null || !i.isOnline()) { + continue; + } + withPlayerThread(i, () -> refreshPlayerPower(i)); + } + } + + private void refreshPlayerPower(Player i) { + UUID id = i.getUniqueId(); + if (!hasActiveAdaptation(i)) { + active.remove(id); + blockPower.remove(id); + cooldowns.remove(id); + return; + } + + boolean ready = !hasCooldown(id); + int availablePower = getBlockPower(getLevelPercent(i)); + blockPower.compute(id, (k, v) -> { + if (v == null || (ready && v != availablePower)) { + final org.bukkit.World world = i.getWorld(); + final org.bukkit.Location location = i.getLocation(); + + SoundPlayer spw = SoundPlayer.of(world); + spw.play(location, Sound.BLOCK_BEACON_ACTIVATE, 1.0f, 10.0f); + spw.play(location, Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 1.0f, 0.81f); + + return availablePower; + } + return v; + }); + } + + private boolean hasCooldown(UUID id) { + Long cooldown = cooldowns.get(id); + if (cooldown != null && M.ms() >= cooldown) { + cooldowns.remove(id); + cooldown = null; + } + + return cooldown != null; + } + + @EventHandler + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + blockPower.remove(id); + cooldowns.remove(id); + active.remove(id); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak to place a temporary foundation beneath you.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration for the Architect Foundation adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public long duration = 3000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Blocks for the Architect Foundation adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public int minBlocks = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Blocks for the Architect Foundation adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public int maxBlocks = 35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Architect Foundation adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public int cooldown = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Foundation adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.40; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectGlass.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectGlass.java new file mode 100644 index 000000000..87ee7652b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectGlass.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +public class ArchitectGlass extends SimpleAdaptation { + public ArchitectGlass() { + super("architect-glass"); + registerConfiguration(ArchitectGlass.Config.class); + setDescription(Localizer.dLocalize("architect.glass.description")); + setDisplayName(Localizer.dLocalize("architect.glass.name")); + setIcon(Material.GLASS); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLASS) + .key("challenge_architect_glass_200") + .title(Localizer.dLocalize("advancement.challenge_architect_glass_200.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_glass_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GLASS) + .key("challenge_architect_glass_5k") + .title(Localizer.dLocalize("advancement.challenge_architect_glass_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_glass_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_glass_200", "architect.glass.blocks-recovered", 200, 300); + registerMilestone("challenge_architect_glass_5k", "architect.glass.blocks-recovered", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.glass.lore1")); + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> { + if (p.getInventory().getItemInMainHand().getType() == Material.AIR || !isTool(p.getInventory().getItemInMainHand())) { + if (!canBlockBreak(p, e.getBlock().getLocation())) { + return; + } + if (e.getBlock().getType().toString().contains("GLASS") && !e.getBlock().getType().toString().contains("TINTED_GLASS")) { + e.getBlock().getWorld().dropItemNaturally(e.getBlock().getLocation(), new ItemStack(e.getBlock().getType(), 1)); + SoundPlayer spw = SoundPlayer.of(e.getBlock().getWorld()); + spw.play(e.getBlock().getLocation(), Sound.BLOCK_LARGE_AMETHYST_BUD_BREAK, 1.0f, 1.0f); + if (areParticlesEnabled()) { + + e.getBlock().getWorld().spawnParticle(Particle.SCRAPE, e.getBlock().getLocation(), 1); + vfxCuboidOutline(e.getBlock(), Particle.REVERSE_PORTAL); + } + e.getBlock().breakNaturally(); + getPlayer(p).getData().addStat("architect.glass.blocks-recovered", 1); + } + } + }); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Silk-touch glass blocks when breaking them with an empty hand.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Glass adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectPlacement.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectPlacement.java new file mode 100644 index 000000000..5c503fc2a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectPlacement.java @@ -0,0 +1,585 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Container; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.Display; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class ArchitectPlacement extends SimpleAdaptation { + private final Map> totalMap = new ConcurrentHashMap<>(); + private final Map> previewDisplays = new ConcurrentHashMap<>(); + + public ArchitectPlacement() { + super("architect-placement"); + registerConfiguration(ArchitectPlacement.Config.class); + setDescription(Localizer.dLocalize("architect.placement.description")); + setDisplayName(Localizer.dLocalize("architect.placement.name")); + setIcon(Material.SCAFFOLDING); + setInterval(360); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BRICKS) + .key("challenge_architect_placement_1k") + .title(Localizer.dLocalize("advancement.challenge_architect_placement_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_placement_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BRICKS) + .key("challenge_architect_placement_25k") + .title(Localizer.dLocalize("advancement.challenge_architect_placement_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_placement_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_placement_1k", "architect.placement.blocks-placed", 1000, 300); + registerMilestone("challenge_architect_placement_25k", "architect.placement.blocks-placed", 25000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.placement.lore3")); + } + + private BlockFace getBlockFace(Player player) { + List lastTwoTargetBlocks = player.getLastTwoTargetBlocks(null, 5); + if (lastTwoTargetBlocks.size() != 2 || !lastTwoTargetBlocks.get(1).getType().isOccluding()) + return null; + Block targetBlock = lastTwoTargetBlocks.get(1); + Block adjacentBlock = lastTwoTargetBlocks.get(0); + return targetBlock.getFace(adjacentBlock); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + totalMap.remove(id); + clearPreviewDisplays(id); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPlaceEvent e) { + Player p = e.getPlayer(); + withPlayerThread(p, e, () -> { + UUID id = p.getUniqueId(); + SoundPlayer sp = SoundPlayer.of(p); + if (getActiveLevel(p, Player::isSneaking) <= 0) { + return; + } + + Map blocks = totalMap.get(id); + if (blocks == null || blocks.isEmpty()) { + return; + } + + ItemStack hand = e.getItemInHand(); + Block first = null; + for (Block candidate : blocks.keySet()) { + first = candidate; + break; + } + if (!hand.getType().isBlock() || first == null || first.getType() != hand.getType()) { + return; + } + + double v = getValue(e.getBlock()); + Block ignored = null; + for (Map.Entry entry : blocks.entrySet()) { + Block source = entry.getKey(); + BlockFace face = entry.getValue(); + if (source == null || face == null) { + continue; + } + if (source.getRelative(face).equals(e.getBlock())) { + ignored = source; + break; + } + } + + if (hand.getAmount() < blocks.size()) { + Adapt.messagePlayer(p, C.RED + Localizer.dLocalize("architect.placement.lore1") + " " + C.GREEN + blocks.size() + C.RED + " " + Localizer.dLocalize("architect.placement.lore2")); + return; + } + + if (ignored != null) { + blocks.remove(ignored); + } + for (Map.Entry entry : blocks.entrySet()) { + Block b = entry.getKey(); + BlockFace face = entry.getValue(); + if (b == null || face == null) { + continue; + } + + Block relative = b.getRelative(face); + if (!relative.getType().isAir()) { + continue; + } + + if (!canBlockPlace(p, relative.getLocation())) { + Adapt.verbose("Player " + p.getName() + " doesn't have permission."); + continue; + } + + relative.setBlockData(b.getBlockData()); + getPlayer(p).getData().addStat("blocks.placed", 1); + getPlayer(p).getData().addStat("blocks.placed.value", v); + getPlayer(p).getData().addStat("architect.placement.blocks-placed", 1); + sp.play(b.getLocation(), Sound.BLOCK_AZALEA_BREAK, 0.4f, 0.25f); + xp(p, 2); + + hand.setAmount(hand.getAmount() - 1); + } + + if (ignored != null) { + e.getBlock().setBlockData(ignored.getBlockData()); + getPlayer(p).getData().addStat("blocks.placed", 1); + getPlayer(p).getData().addStat("blocks.placed.value", v); + getPlayer(p).getData().addStat("architect.placement.blocks-placed", 1); + sp.play(ignored.getLocation(), Sound.BLOCK_AZALEA_BREAK, 0.4f, 0.25f); + xp(p, 2); + + hand.setAmount(hand.getAmount() - 1); + } else { + e.setCancelled(true); + } + + totalMap.remove(id); + clearPreviewDisplays(id); + if (hand.getAmount() > 0) { + runPlayerViewport(getBlockFace(p), p.getTargetBlock(null, 5), p.getInventory().getItemInMainHand().getType(), p); + } + }); + } + + + @EventHandler + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + withPlayerThread(p, e, () -> { + UUID id = p.getUniqueId(); + int level = getActiveLevel(p); + if (level <= 0) { + totalMap.remove(id); + clearPreviewDisplays(id); + return; + } + + if (e.isSneaking()) { + totalMap.remove(id); + clearPreviewDisplays(id); + } + + if (!e.isSneaking() && p.getInventory().getItemInMainHand().getType().isBlock()) { + Block block = p.getTargetBlock(null, 5); // 5 is the range of player + if (block instanceof Container) { // return if block is a container + return; + } + Material handMaterial = p.getInventory().getItemInMainHand().getType(); + if (handMaterial.isAir()) { + return; + } + BlockFace viewPortBlock = getBlockFace(p); + runPlayerViewport(viewPortBlock, block, handMaterial, p); + } + }); + } + + + @EventHandler + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + withPlayerThread(p, e, () -> { + UUID id = p.getUniqueId(); + int level = getActiveLevel(p); + if (level <= 0) { + totalMap.remove(id); + clearPreviewDisplays(id); + return; + } + + if (!p.isSneaking()) { + totalMap.remove(id); + clearPreviewDisplays(id); + } + + if (p.isSneaking() && p.getInventory().getItemInMainHand().getType().isBlock()) { + Block block = p.getTargetBlock(null, 5); // 5 is the range of player + if (block instanceof Container) { // return if block is a container + return; + } + Material handMaterial = p.getInventory().getItemInMainHand().getType(); + if (handMaterial.isAir()) { + return; + } + BlockFace viewPortBlock = getBlockFace(p); + runPlayerViewport(viewPortBlock, block, handMaterial, p); + } + }); + } + + public void runPlayerViewport(BlockFace viewPortBlock, Block block, Material handMaterial, Player p) { + UUID id = p.getUniqueId(); + if (viewPortBlock == null || block == null || handMaterial == null || handMaterial.isAir()) { + totalMap.remove(id); + clearPreviewDisplays(id); + return; + } + + Map map = new HashMap<>(); + + if (viewPortBlock.getDirection().equals(BlockFace.NORTH.getDirection()) || viewPortBlock.getDirection().equals(BlockFace.SOUTH.getDirection())) { // North & South = X + for (int x = block.getX() - 1; x <= block.getX() + 1; x++) { // 1 is the radius of the blocks + for (int y = block.getY() - 1; y <= block.getY() + 1; y++) { + addViewportEntry(map, block.getWorld().getBlockAt(x, y, block.getZ()), viewPortBlock, handMaterial); + } + } + } else if (viewPortBlock.getDirection().equals(BlockFace.EAST.getDirection()) || viewPortBlock.getDirection().equals(BlockFace.WEST.getDirection())) { // East & West = Z + for (int z = block.getZ() - 1; z <= block.getZ() + 1; z++) { // 1 is the radius of the blocks + for (int y = block.getY() - 1; y <= block.getY() + 1; y++) { + addViewportEntry(map, block.getWorld().getBlockAt(block.getX(), y, z), viewPortBlock, handMaterial); + } + } + } else if (viewPortBlock.getDirection().equals(BlockFace.UP.getDirection()) || viewPortBlock.getDirection().equals(BlockFace.DOWN.getDirection())) { // Up & Down = Y + for (int z = block.getZ() - 1; z <= block.getZ() + 1; z++) { // 1 is the radius of the blocks + for (int x = block.getX() - 1; x <= block.getX() + 1; x++) { + addViewportEntry(map, block.getWorld().getBlockAt(x, block.getY(), z), viewPortBlock, handMaterial); + } + } + } + + if (map.isEmpty()) { + totalMap.remove(id); + clearPreviewDisplays(id); + return; + } + + totalMap.put(id, map); + } + + private void addViewportEntry(Map map, Block target, BlockFace viewPortBlock, Material handMaterial) { + if (target == null || viewPortBlock == null || handMaterial == null) { + return; + } + + int maxBlocks = Math.max(1, getConfig().maxBlocks); + if (map.size() >= maxBlocks) { + return; + } + + if (target.getType() == handMaterial) { + map.put(target, viewPortBlock); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + + @Override + public void onTick() { + if (previewDisplays.isEmpty() && totalMap.isEmpty()) { + return; + } + + for (Map.Entry> entry : previewDisplays.entrySet()) { + UUID playerId = entry.getKey(); + Map displays = entry.getValue(); + if (!totalMap.containsKey(playerId) && previewDisplays.remove(playerId, displays)) { + clearPreviewDisplays(displays); + } + } + + if (totalMap.isEmpty()) { + return; + } + + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline() || !totalMap.containsKey(p.getUniqueId())) { + continue; + } + withPlayerThread(p, () -> renderPreview(p)); + } + } + + private void renderPreview(Player p) { + UUID id = p.getUniqueId(); + Map blockRender = totalMap.get(id); + if (getActiveLevel(p, Player::isSneaking) <= 0 || blockRender == null || blockRender.isEmpty()) { + totalMap.remove(id); + clearPreviewDisplays(id); + return; + } + + Set activePreviews = new HashSet<>(); + boolean displayPreview = getConfig().useDisplayEntities; + + for (Map.Entry entry : blockRender.entrySet()) { + Block b = entry.getKey(); + BlockFace bf = entry.getValue(); + if (b == null || bf == null || b instanceof Container) { + continue; + } + + Block transposedBlock = b.getRelative(bf); + if (displayPreview) { + if (!transposedBlock.getType().isAir()) { + continue; + } + + PreviewKey key = PreviewKey.of(transposedBlock); + activePreviews.add(key); + ensurePreviewDisplay(id, key, b.getBlockData()); + } else if (areParticlesEnabled()) { + vfxCuboidOutline(transposedBlock, Particle.REVERSE_PORTAL); + } + } + + if (displayPreview) { + clearStalePreviewDisplays(id, activePreviews); + } else { + clearPreviewDisplays(id); + } + } + + private void ensurePreviewDisplay(UUID playerId, PreviewKey key, org.bukkit.block.data.BlockData sourceData) { + if (key == null || sourceData == null) { + return; + } + + Map displays = previewDisplays.computeIfAbsent(playerId, unused -> new ConcurrentHashMap<>()); + BlockDisplay existing = displays.get(key); + if (existing != null) { + if (existing.isValid()) { + if (J.isFoliaThreading()) { + J.runEntity(existing, () -> { + if (!existing.isValid()) { + displays.remove(key, existing); + return; + } + existing.setBlock(sourceData); + showPreviewToOwner(playerId, existing); + }); + } else { + existing.setBlock(sourceData); + showPreviewToOwner(playerId, existing); + } + return; + } + displays.remove(key); + } + + Runnable spawnTask = () -> { + if (!totalMap.containsKey(playerId)) { + return; + } + + org.bukkit.World world = Bukkit.getWorld(key.worldId()); + if (world == null) { + return; + } + + Block targetBlock = world.getBlockAt(key.x(), key.y(), key.z()); + BlockDisplay live = displays.get(key); + if (live != null && live.isValid()) { + live.setBlock(sourceData); + showPreviewToOwner(playerId, live); + return; + } + + BlockDisplay spawned = world.spawn(targetBlock.getLocation(), BlockDisplay.class, display -> { + display.setPersistent(false); + display.setInvulnerable(true); + display.setGravity(false); + display.setSilent(true); + display.setVisibleByDefault(false); + display.setInterpolationDuration(2); + display.setTeleportDuration(1); + display.setViewRange((float) Math.max(0.25, getConfig().displayEntityViewRange)); + display.setShadowRadius(0f); + display.setShadowStrength(0f); + display.setBrightness(new Display.Brightness(15, 15)); + display.setBlock(sourceData); + }); + displays.put(key, spawned); + showPreviewToOwner(playerId, spawned); + }; + + if (J.isFoliaThreading()) { + org.bukkit.World world = Bukkit.getWorld(key.worldId()); + if (world == null) { + return; + } + J.runAt(new org.bukkit.Location(world, key.x() + 0.5, key.y(), key.z() + 0.5), spawnTask); + return; + } + + spawnTask.run(); + } + + private void clearStalePreviewDisplays(UUID playerId, Set activePreviews) { + Map displays = previewDisplays.get(playerId); + if (displays == null || displays.isEmpty()) { + return; + } + + for (Map.Entry entry : displays.entrySet()) { + PreviewKey key = entry.getKey(); + if (key == null || activePreviews.contains(key)) { + continue; + } + + BlockDisplay removed = entry.getValue(); + if (removed != null && displays.remove(key, removed)) { + removeDisplayEntity(removed); + } + } + + if (displays.isEmpty()) { + previewDisplays.remove(playerId); + } + } + + private void clearPreviewDisplays(UUID playerId) { + Map displays = previewDisplays.remove(playerId); + clearPreviewDisplays(displays); + } + + private void clearPreviewDisplays(Map displays) { + if (displays == null || displays.isEmpty()) { + return; + } + + for (BlockDisplay display : displays.values()) { + removeDisplayEntity(display); + } + } + + private void removeDisplayEntity(Entity entity) { + if (entity == null) { + return; + } + + if (J.isFoliaThreading()) { + J.runEntity(entity, () -> { + if (entity.isValid()) { + entity.remove(); + } + }); + } else if (entity.isValid()) { + entity.remove(); + } + } + + private void showPreviewToOwner(UUID playerId, Entity entity) { + if (entity == null || !entity.isValid()) { + return; + } + + Player owner = Bukkit.getPlayer(playerId); + if (owner == null || !owner.isOnline()) { + return; + } + + if (J.isFoliaThreading()) { + J.runEntity(owner, () -> { + if (entity.isValid()) { + owner.showEntity(Adapt.instance, entity); + } + }); + return; + } + + owner.showEntity(Adapt.instance, entity); + } + + private record PreviewKey(UUID worldId, int x, int y, int z) { + private static PreviewKey of(Block block) { + return new PreviewKey(block.getWorld().getUID(), block.getX(), block.getY(), block.getZ()); + } + } + + @NoArgsConstructor + @ConfigDescription("Place multiple blocks at once while sneaking with a matching block.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Blocks for the Architect Placement adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public int maxBlocks = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Placement adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Use owner-only block display previews instead of particles for the wand guide.", impact = "True shows ghost blocks only to the wand user; false keeps particle outlines.") + boolean useDisplayEntities = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "View range used for wand preview display entities.", impact = "Lower values hide previews sooner; higher values keep them visible from farther away.") + double displayEntityViewRange = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectScaffolder.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectScaffolder.java new file mode 100644 index 000000000..0e8c023cd --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectScaffolder.java @@ -0,0 +1,230 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ArchitectScaffolder extends SimpleAdaptation { + private final Map scaffolds; + private final Map> byPlayer; + + public ArchitectScaffolder() { + super("architect-scaffolder"); + registerConfiguration(ArchitectScaffolder.Config.class); + setDescription(Localizer.dLocalize("architect.scaffolder.description")); + setDisplayName(Localizer.dLocalize("architect.scaffolder.name")); + setIcon(Material.SCAFFOLDING); + setInterval(9220); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + scaffolds = new ConcurrentHashMap<>(); + byPlayer = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SCAFFOLDING) + .key("challenge_architect_scaffolder_500") + .title(Localizer.dLocalize("advancement.challenge_architect_scaffolder_500.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_scaffolder_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SCAFFOLDING) + .key("challenge_architect_scaffolder_5k") + .title(Localizer.dLocalize("advancement.challenge_architect_scaffolder_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_scaffolder_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_scaffolder_500", "architect.scaffolder.blocks-scaffolded", 500, 300); + registerMilestone("challenge_architect_scaffolder_5k", "architect.scaffolder.blocks-scaffolded", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.scaffolder.lore1")); + v.addLore(C.GREEN + "" + getDurationSeconds(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("architect.scaffolder.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockPlaceEvent e) { + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + Block block = e.getBlock(); + Adaptation.BlockActionContext context = resolveBlockPlaceContext(p, block.getLocation(), Player::isSneaking); + if (context == null) { + return; + } + + Material type = block.getType(); + if (type.isAir() || !type.isBlock()) { + return; + } + + UUID id = p.getUniqueId(); + Set mine = byPlayer.computeIfAbsent(id, unused -> ConcurrentHashMap.newKeySet()); + if (mine.size() >= getConfig().maxScaffoldsPerPlayer) { + return; + } + + mine.add(block); + scaffolds.put(block, new ScaffoldMark(id, type)); + SoundPlayer spw = SoundPlayer.of(block.getWorld()); + spw.play(block.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 0.6f, 1.6f); + if (areParticlesEnabled()) { + vfxCuboidOutline(block, Particle.CLOUD); + } + + getPlayer(p).getData().addStat("architect.scaffolder.blocks-scaffolded", 1); + int delayTicks = getDurationSeconds(getLevelPercent(context.level())) * 20; + J.runAt(block.getLocation(), () -> removeScaffold(block), delayTicks); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (scaffolds.isEmpty()) { + return; + } + + ScaffoldMark mark = scaffolds.remove(e.getBlock()); + if (mark == null) { + return; + } + + Set mine = byPlayer.get(mark.owner()); + if (mine != null) { + mine.remove(e.getBlock()); + } + } + + @EventHandler + public void on(PlayerQuitEvent e) { + byPlayer.remove(e.getPlayer().getUniqueId()); + } + + private void removeScaffold(Block block) { + ScaffoldMark mark = scaffolds.remove(block); + if (mark == null) { + return; + } + + Set mine = byPlayer.get(mark.owner()); + if (mine != null) { + mine.remove(block); + } + + if (block.getType() != mark.material()) { + return; + } + + block.setType(Material.AIR); + SoundPlayer spw = SoundPlayer.of(block.getWorld()); + spw.play(block.getLocation(), Sound.BLOCK_DEEPSLATE_BREAK, 0.6f, 1.4f); + if (areParticlesEnabled()) { + vfxCuboidOutline(block, Particle.REVERSE_PORTAL); + } + + ItemStack refund = new ItemStack(mark.material()); + Player owner = Bukkit.getPlayer(mark.owner()); + if (owner != null && owner.isOnline()) { + J.runEntity(owner, () -> safeGiveItem(owner, refund)); + } else { + block.getWorld().dropItemNaturally(block.getLocation(), refund); + } + } + + private int getDurationSeconds(double factor) { + return (int) Math.max(1, M.lerp(getConfig().minDurationSeconds, getConfig().maxDurationSeconds, factor)); + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record ScaffoldMark(UUID owner, Material material) { + } + + @NoArgsConstructor + @ConfigDescription("Sneak-place blocks as temporary scaffolds that dissolve and refund themselves.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Scaffolder adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaffold lifetime in seconds at level 0 progression.", impact = "Higher values keep low-level scaffolds in the world longer before dissolving.") + int minDurationSeconds = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaffold lifetime in seconds at maximum level progression.", impact = "Higher values keep max-level scaffolds in the world longer before dissolving.") + int maxDurationSeconds = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum number of active scaffolds tracked per player.", impact = "Higher values let players keep more temporary scaffolds at once.") + int maxScaffoldsPerPlayer = 24; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSmartShape.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSmartShape.java new file mode 100644 index 000000000..aeef79f3c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSmartShape.java @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Axis; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import org.bukkit.block.data.Orientable; +import org.bukkit.block.data.Rotatable; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +public class ArchitectSmartShape extends SimpleAdaptation { + private static final List ROTATION_ORDER = Arrays.asList( + BlockFace.NORTH, + BlockFace.NORTH_NORTH_EAST, + BlockFace.NORTH_EAST, + BlockFace.EAST_NORTH_EAST, + BlockFace.EAST, + BlockFace.EAST_SOUTH_EAST, + BlockFace.SOUTH_EAST, + BlockFace.SOUTH_SOUTH_EAST, + BlockFace.SOUTH, + BlockFace.SOUTH_SOUTH_WEST, + BlockFace.SOUTH_WEST, + BlockFace.WEST_SOUTH_WEST, + BlockFace.WEST, + BlockFace.WEST_NORTH_WEST, + BlockFace.NORTH_WEST, + BlockFace.NORTH_NORTH_WEST + ); + + public ArchitectSmartShape() { + super("architect-smart-shape"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("architect.smart_shape.description")); + setDisplayName(Localizer.dLocalize("architect.smart_shape.name")); + setIcon(Material.BRICKS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(800); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.QUARTZ_STAIRS) + .key("challenge_architect_smart_shape_200") + .title(Localizer.dLocalize("advancement.challenge_architect_smart_shape_200.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_smart_shape_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.QUARTZ_STAIRS) + .key("challenge_architect_smart_shape_5k") + .title(Localizer.dLocalize("advancement.challenge_architect_smart_shape_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_smart_shape_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_smart_shape_200", "architect.smart-shape.rotations", 200, 300); + registerMilestone("challenge_architect_smart_shape_5k", "architect.smart-shape.rotations", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("architect.smart_shape.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("architect.smart_shape.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.LEFT_CLICK_BLOCK && action != Action.LEFT_CLICK_AIR) { + return; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> { + if (!p.isSneaking()) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (isItem(hand) && hand.getType() != Material.AIR) { + return; + } + + Block target = action == Action.LEFT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (target == null) { + return; + } + + if (!canBlockPlace(p, target.getLocation())) { + return; + } + + BlockData data = target.getBlockData().clone(); + int options = rotateData(data); + if (options <= 0) { + return; + } + + target.setBlockData(data, true); + e.setCancelled(true); + SoundPlayer.of(p.getWorld()).play(target.getLocation(), Sound.ITEM_AXE_STRIP, 0.45f, 1.8f); + xp(p, Math.max(getConfig().minXpPerRotate, options * getConfig().xpPerOrientationOption)); + getPlayer(p).getData().addStat("architect.smart-shape.rotations", 1); + }); + } + + private int rotateData(BlockData data) { + if (data instanceof Directional directional) { + BlockFace next = getNextFace(directional.getFacing(), directional.getFaces()); + if (next != null && next != directional.getFacing()) { + directional.setFacing(next); + return directional.getFaces().size(); + } + } + + if (data instanceof Rotatable rotatable) { + BlockFace next = getNextFace(rotatable.getRotation(), Set.copyOf(ROTATION_ORDER), ROTATION_ORDER); + if (next != null && next != rotatable.getRotation()) { + rotatable.setRotation(next); + return ROTATION_ORDER.size(); + } + } + + if (data instanceof Orientable orientable) { + Axis current = orientable.getAxis(); + Axis next = switch (current) { + case X -> Axis.Y; + case Y -> Axis.Z; + case Z -> Axis.X; + }; + + if (orientable.getAxes().contains(next)) { + orientable.setAxis(next); + return orientable.getAxes().size(); + } + + if (orientable.getAxes().contains(Axis.X)) { + orientable.setAxis(Axis.X); + return orientable.getAxes().size(); + } + } + + return 0; + } + + private BlockFace getNextFace(BlockFace current, Set supported) { + if (supported == null || supported.isEmpty()) { + return null; + } + + List ordered = new ArrayList<>(supported); + ordered.sort(Comparator.comparingInt(Enum::ordinal)); + return getNextFace(current, supported, ordered); + } + + private BlockFace getNextFace(BlockFace current, Set supported, List order) { + if (supported == null || supported.isEmpty()) { + return null; + } + + int idx = order.indexOf(current); + if (idx < 0) { + for (BlockFace face : order) { + if (supported.contains(face)) { + return face; + } + } + + return null; + } + + for (int i = 1; i <= order.size(); i++) { + BlockFace candidate = order.get((idx + i) % order.size()); + if (supported.contains(candidate)) { + return candidate; + } + } + + return current; + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-left-click a block with an empty hand to rotate its orientation.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Xp Per Rotate for the Architect Smart Shape adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minXpPerRotate = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Orientation Option for the Architect Smart Shape adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerOrientationOption = 0.16; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSteadyHands.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSteadyHands.java new file mode 100644 index 000000000..72313a853 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSteadyHands.java @@ -0,0 +1,208 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerVelocityEvent; +import org.bukkit.potion.PotionEffect; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ArchitectSteadyHands extends SimpleAdaptation { + private final Map lastBridge; + + public ArchitectSteadyHands() { + super("architect-steady-hands"); + registerConfiguration(ArchitectSteadyHands.Config.class); + setDescription(Localizer.dLocalize("architect.steady_hands.description")); + setDisplayName(Localizer.dLocalize("architect.steady_hands.name")); + setIcon(Material.LIGHTNING_ROD); + setInterval(10440); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + lastBridge = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LIGHTNING_ROD) + .key("challenge_architect_steady_hands_500") + .title(Localizer.dLocalize("advancement.challenge_architect_steady_hands_500.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_steady_hands_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.LIGHTNING_ROD) + .key("challenge_architect_steady_hands_5k") + .title(Localizer.dLocalize("advancement.challenge_architect_steady_hands_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_steady_hands_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_steady_hands_500", "architect.steady-hands.bridge-blocks", 500, 300); + registerMilestone("challenge_architect_steady_hands_5k", "architect.steady-hands.bridge-blocks", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.steady_hands.lore1")); + v.addLore(C.GREEN + "" + (int) getShieldedHeight(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("architect.steady_hands.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockPlaceEvent e) { + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + if (!e.getBlock().getRelative(BlockFace.DOWN).getType().isAir()) { + return; + } + + Adaptation.BlockActionContext context = resolveBlockPlaceContext(p, e.getBlock().getLocation(), Player::isSneaking); + if (context == null) { + return; + } + + lastBridge.put(p.getUniqueId(), M.ms()); + p.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, getConfig().hasteDurationTicks, getConfig().hasteAmplifier, false, false, true)); + getPlayer(p).getData().addStat("architect.steady-hands.bridge-blocks", 1); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void on(PlayerVelocityEvent e) { + Player p = e.getPlayer(); + Long last = lastBridge.get(p.getUniqueId()); + if (last == null || M.ms() - last > getConfig().bridgeGraceMillis) { + return; + } + + if (!p.isSneaking()) { + return; + } + + if (getActiveLevel(p) <= 0) { + return; + } + + e.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (e.getCause() != EntityDamageEvent.DamageCause.FALL || !(e.getEntity() instanceof Player p)) { + return; + } + + Long last = lastBridge.get(p.getUniqueId()); + if (last == null || M.ms() - last > getConfig().bridgeGraceMillis) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + double shielded = getShieldedHeight(getLevelPercent(level)); + if (e.getDamage() <= shielded) { + e.setCancelled(true); + p.setFallDistance(0); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_WOOL_BREAK, 0.5f, 0.8f); + return; + } + + e.setDamage(Math.max(0, e.getDamage() - shielded)); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + lastBridge.remove(e.getPlayer().getUniqueId()); + } + + private double getShieldedHeight(double factor) { + return M.lerp(getConfig().minShieldedBlocks, getConfig().maxShieldedBlocks, factor); + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Stay rock-steady while bridging: no knockback and reduced fall damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fall damage shielded in blocks at level 0 progression.", impact = "Higher values absorb more fall damage for low-level players while bridging.") + double minShieldedBlocks = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fall damage shielded in blocks at maximum level progression.", impact = "Higher values absorb more fall damage for max-level players while bridging.") + double maxShieldedBlocks = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "How long in milliseconds after a bridge placement the protections stay active.", impact = "Higher values keep knockback and fall protection up longer between placements.") + long bridgeGraceMillis = 4000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of the haste boost granted per bridge placement.", impact = "Higher values keep the placement-speed boost active longer.") + int hasteDurationTicks = 40; + @art.arcane.adapt.util.config.ConfigDoc(value = "Amplifier of the haste boost granted per bridge placement.", impact = "Higher values strengthen the placement-speed boost.") + int hasteAmplifier = 0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectStonecutterSavant.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectStonecutterSavant.java new file mode 100644 index 000000000..47d3499ee --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectStonecutterSavant.java @@ -0,0 +1,196 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ArchitectStonecutterSavant extends SimpleAdaptation { + private final Map cooldowns; + + public ArchitectStonecutterSavant() { + super("architect-stonecutter-savant"); + registerConfiguration(ArchitectStonecutterSavant.Config.class); + setDescription(Localizer.dLocalize("architect.stonecutter_savant.description")); + setDisplayName(Localizer.dLocalize("architect.stonecutter_savant.name")); + setIcon(Material.STONECUTTER); + setInterval(24420); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.STONECUTTER) + .key("challenge_architect_stonecutter_savant_50") + .title(Localizer.dLocalize("advancement.challenge_architect_stonecutter_savant_50.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_stonecutter_savant_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.STONECUTTER) + .key("challenge_architect_stonecutter_savant_500") + .title(Localizer.dLocalize("advancement.challenge_architect_stonecutter_savant_500.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_stonecutter_savant_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_stonecutter_savant_50", "architect.stonecutter-savant.uses", 50, 300); + registerMilestone("challenge_architect_stonecutter_savant_500", "architect.stonecutter-savant.uses", 500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.stonecutter_savant.lore1")); + v.addLore(C.GREEN + "" + (getCooldownMillis(getLevelPercent(level)) / 1000) + C.GRAY + " " + Localizer.dLocalize("architect.stonecutter_savant.lore2")); + v.addLore(C.YELLOW + Localizer.dLocalize(getConfig().requireOffhand ? "architect.stonecutter_savant.lore4" : "architect.stonecutter_savant.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) { + return; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + PlayerInventory inventory = p.getInventory(); + ItemStack hand = inventory.getItemInMainHand(); + if (isItem(hand) && hand.getType() != Material.AIR) { + return; + } + + if (!hasStonecutter(inventory)) { + return; + } + + Adaptation.BlockActionContext context = resolveInteractContext(p, p.getLocation(), Player::isSneaking); + if (context == null) { + return; + } + + UUID id = p.getUniqueId(); + long now = M.ms(); + Long until = cooldowns.get(id); + if (until != null && now < until) { + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 0.3f, 0.7f); + return; + } + + cooldowns.put(id, now + getCooldownMillis(getLevelPercent(context.level()))); + withPlayerThread(p, () -> { + p.openStonecutter(null, true); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_GRINDSTONE_USE, 0.8f, 1.4f); + getPlayer(p).getData().addStat("architect.stonecutter-savant.uses", 1); + xp(p, getConfig().xpPerUse); + }); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + private boolean hasStonecutter(PlayerInventory inventory) { + if (getConfig().requireOffhand) { + return inventory.getItemInOffHand().getType() == Material.STONECUTTER; + } + + return inventory.contains(Material.STONECUTTER); + } + + private long getCooldownMillis(double factor) { + return (long) Math.max(1000, M.lerp(getConfig().maxCooldownSeconds, getConfig().minCooldownSeconds, factor) * 1000D); + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-punch the air with an empty hand while carrying a stonecutter to open it anywhere.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Requires the stonecutter item to be in the offhand specifically.", impact = "True only accepts a stonecutter held in the offhand; false accepts a stonecutter anywhere in the inventory.") + boolean requireOffhand = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown in seconds at level 0 progression.", impact = "Higher values make low-level players wait longer between uses.") + double maxCooldownSeconds = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown in seconds at maximum level progression.", impact = "Lower values let max-level players open the stonecutter more often.") + double minCooldownSeconds = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Adaptation xp granted per stonecutter opened.", impact = "Higher values speed up adaptation progression from uses.") + double xpPerUse = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSupplyLine.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSupplyLine.java new file mode 100644 index 000000000..b23703c26 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectSupplyLine.java @@ -0,0 +1,297 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.Tag; +import org.bukkit.block.ShulkerBox; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.BundleMeta; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ArchitectSupplyLine extends SimpleAdaptation { + private final Map windows; + + public ArchitectSupplyLine() { + super("architect-supply-line"); + registerConfiguration(ArchitectSupplyLine.Config.class); + setDescription(Localizer.dLocalize("architect.supply_line.description")); + setDisplayName(Localizer.dLocalize("architect.supply_line.name")); + setIcon(Material.SHULKER_BOX); + setInterval(13780); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + windows = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHULKER_BOX) + .key("challenge_architect_supply_line_100") + .title(Localizer.dLocalize("advancement.challenge_architect_supply_line_100.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_supply_line_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SHULKER_BOX) + .key("challenge_architect_supply_line_1k") + .title(Localizer.dLocalize("advancement.challenge_architect_supply_line_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_supply_line_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_supply_line_100", "architect.supply-line.refills", 100, 300); + registerMilestone("challenge_architect_supply_line_1k", "architect.supply-line.refills", 1000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.supply_line.lore1")); + v.addLore(C.GREEN + "" + getRefillsPerMinute(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("architect.supply_line.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockPlaceEvent e) { + ItemStack hand = e.getItemInHand(); + if (hand.getAmount() > 1) { + return; + } + + Material material = hand.getType(); + if (!material.isBlock() || material.isAir()) { + return; + } + + Player p = e.getPlayer(); + Adaptation.BlockActionContext context = resolveBlockPlaceContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + if (!tryConsumeRefill(p.getUniqueId(), getRefillsPerMinute(getLevelPercent(context.level())))) { + return; + } + + EquipmentSlot slot = e.getHand(); + J.runEntity(p, () -> refill(p, slot, material), 1); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + windows.remove(e.getPlayer().getUniqueId()); + } + + private boolean tryConsumeRefill(UUID id, int allowed) { + long now = M.ms(); + RefillWindow current = windows.get(id); + if (current == null || now - current.start() >= 60000L) { + windows.put(id, new RefillWindow(now, 1)); + return true; + } + + if (current.used() >= allowed) { + return false; + } + + windows.put(id, new RefillWindow(current.start(), current.used() + 1)); + return true; + } + + private void refill(Player p, EquipmentSlot slot, Material material) { + if (!p.isOnline()) { + return; + } + + ItemStack current = slot == EquipmentSlot.OFF_HAND ? p.getInventory().getItemInOffHand() : p.getInventory().getItemInMainHand(); + if (current != null && !current.getType().isAir()) { + return; + } + + ItemStack pulled = pullMatching(p, material); + if (pulled == null) { + return; + } + + if (slot == EquipmentSlot.OFF_HAND) { + p.getInventory().setItemInOffHand(pulled); + } else { + p.getInventory().setItemInMainHand(pulled); + } + + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_COMPOSTER_FILL, 0.6f, 1.3f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.WAX_ON, p.getLocation().add(0, 1, 0), 6, 0.3, 0.3, 0.3, 0); + } + + getPlayer(p).getData().addStat("architect.supply-line.refills", 1); + xp(p, getConfig().xpPerRefill); + } + + private ItemStack pullMatching(Player p, Material material) { + ItemStack[] contents = p.getInventory().getStorageContents(); + for (int i = 0; i < contents.length; i++) { + ItemStack candidate = contents[i]; + if (candidate == null || candidate.getType().isAir()) { + continue; + } + + ItemStack pulled = pullFromShulker(candidate, material); + if (pulled == null) { + pulled = pullFromBundle(candidate, material); + } + + if (pulled != null) { + return pulled; + } + } + + return null; + } + + private ItemStack pullFromShulker(ItemStack container, Material material) { + if (!Tag.SHULKER_BOXES.isTagged(container.getType())) { + return null; + } + + if (!(container.getItemMeta() instanceof BlockStateMeta meta) || !(meta.getBlockState() instanceof ShulkerBox box)) { + return null; + } + + Inventory inv = box.getInventory(); + for (int slot = 0; slot < inv.getSize(); slot++) { + ItemStack content = inv.getItem(slot); + if (content == null || content.getType() != material) { + continue; + } + + ItemStack pulled = content.clone(); + inv.setItem(slot, null); + meta.setBlockState(box); + container.setItemMeta(meta); + return pulled; + } + + return null; + } + + private ItemStack pullFromBundle(ItemStack container, Material material) { + if (!(container.getItemMeta() instanceof BundleMeta meta)) { + return null; + } + + List items = meta.getItems(); + if (items.isEmpty()) { + return null; + } + + List remaining = new ArrayList<>(items.size()); + ItemStack pulled = null; + for (ItemStack item : items) { + if (pulled == null && item != null && item.getType() == material) { + pulled = item.clone(); + continue; + } + + remaining.add(item); + } + + if (pulled == null) { + return null; + } + + meta.setItems(remaining); + container.setItemMeta(meta); + return pulled; + } + + private int getRefillsPerMinute(double factor) { + return (int) Math.max(1, M.lerp(getConfig().minRefillsPerMinute, getConfig().maxRefillsPerMinute, factor)); + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record RefillWindow(long start, int used) { + } + + @NoArgsConstructor + @ConfigDescription("Automatically refill your hand from shulker boxes or bundles when a placed stack runs out.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Supply Line adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hand refills allowed per minute at level 0 progression.", impact = "Higher values let low-level players refill more often.") + int minRefillsPerMinute = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hand refills allowed per minute at maximum level progression.", impact = "Higher values let max-level players refill more often.") + int maxRefillsPerMinute = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Adaptation xp granted per successful refill.", impact = "Higher values speed up adaptation progression from refills.") + double xpPerRefill = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectWirelessRedstone.java b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectWirelessRedstone.java new file mode 100644 index 000000000..e6f59e35c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/architect/ArchitectWirelessRedstone.java @@ -0,0 +1,315 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.architect; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.content.item.BoundRedstoneTorch; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.data.AnaloguePowerable; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.event.Event.Result; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; + +import static art.arcane.adapt.api.adaptation.chunk.ChunkLoading.loadChunkAsync; + +public class ArchitectWirelessRedstone extends SimpleAdaptation { + private final Map cooldowns; + + public ArchitectWirelessRedstone() { + super("architect-wireless-redstone"); + registerConfiguration(ArchitectWirelessRedstone.Config.class); + setDescription(Localizer.dLocalize("architect.wireless_redstone.description")); + setDisplayName(Localizer.dLocalize("architect.wireless_redstone.name")); + setIcon(Material.REDSTONE_TORCH); + setInterval(100); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shapeless() + .key("remote-redstone-torch") + .ingredient(Material.REDSTONE_TORCH) + .ingredient(Material.TARGET) + .ingredient(Material.ENDER_PEARL) + .result(BoundRedstoneTorch.io.withData(new BoundRedstoneTorch.Data(null))) + .build()); + cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.REDSTONE) + .key("challenge_architect_wireless_100") + .title(Localizer.dLocalize("advancement.challenge_architect_wireless_100.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_wireless_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.REDSTONE) + .key("challenge_architect_wireless_5k") + .title(Localizer.dLocalize("advancement.challenge_architect_wireless_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_architect_wireless_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_architect_wireless_100", "architect.wireless-redstone.pulses", 100, 300); + registerMilestone("challenge_architect_wireless_5k", "architect.wireless-redstone.pulses", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("architect.wireless_redstone.lore1")); + } + + + @EventHandler + public void onPlaceBlock(BlockPlaceEvent event) { + ItemStack item = event.getItemInHand(); + if (BoundRedstoneTorch.hasItemData(item) && isRedstoneTorch(item)) { + event.setBuild(false); + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getHand() != EquipmentSlot.HAND && event.getHand() != EquipmentSlot.OFF_HAND) { + return; + } + + ItemStack itemInHand = event.getItem(); + + if (itemInHand == null) { + return; + } + + boolean specialItem = + isRedstoneTorch(itemInHand) && BoundRedstoneTorch.hasItemData(itemInHand); + if (!specialItem) { + return; + } + + Player player = event.getPlayer(); + withPlayerThread(player, event, () -> { + if (resolveInteractContext(player, player.getLocation()) == null) { + return; + } + + boolean canUseInCreative = AdaptConfig.get().allowAdaptationsInCreative; + boolean inCreative = player.getGameMode() == GameMode.CREATIVE; + if (inCreative && !canUseInCreative) { + return; + } + + switch (event.getAction()) { + case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> handleLeftClick(event, player); + case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> + handleRightClick(event, player); + } + }); + } + + + private boolean isRedstoneTorch(ItemStack item) { + return item.getType().equals(Material.REDSTONE_TORCH); + } + + + private void handleLeftClick(PlayerInteractEvent event, Player player) { + Adapt.verbose("Player " + player.getName() + " is left clicking"); + if (!player.isSneaking()) { + return; + } + + if (event.getHand() != EquipmentSlot.HAND) { + return; + } + + Block target = event.getClickedBlock(); + if (target == null) { + target = player.getTargetBlockExact(5); + } + + if (target == null) { + return; + } + + if (event.getAction() == Action.LEFT_CLICK_BLOCK) { + event.setUseItemInHand(Result.DENY); + } + + Location location = new Location(target.getWorld(), target.getX(), target.getY(), target.getZ()); + linkTorch(player, location); + } + + private void handleRightClick(PlayerInteractEvent event, Player player) { + Adapt.verbose("Player " + player.getName() + " is right clicking"); + + if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { + event.setUseItemInHand(Result.DENY); + event.setUseInteractedBlock(Result.DENY); + } + + if (hasCooldown(player)) { + SoundPlayer sp = SoundPlayer.of(player); + sp.play(player.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 0.1f, 0.9f); + } else { + cooldowns.put(player.getUniqueId(), System.currentTimeMillis() + getConfig().cooldown); + updatePlayerCooldown(player, false); + triggerPulse(player, event.getItem()); + } + } + + public void updatePlayerCooldown(Player player, boolean reset) { + player.setCooldown(Material.REDSTONE_TORCH, reset ? 0 : 5000); + } + + + private boolean hasCooldown(Player i) { + if (cooldowns.containsKey(i.getUniqueId())) { + if (M.ms() >= cooldowns.get(i.getUniqueId())) { + cooldowns.remove(i.getUniqueId()); + } + } + return cooldowns.containsKey(i.getUniqueId()); + } + + + private void linkTorch(Player p, Location l) { + if (!l.getBlock().getType().equals(Material.TARGET)) { + return; + } + if (areParticlesEnabled()) { + vfxCuboidOutline(l.getBlock(), l.getBlock(), Color.RED, 1); + } + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(l, Sound.BLOCK_CHEST_OPEN, 0.1f, 9f); + spw.play(l, Sound.ENTITY_ENDER_EYE_DEATH, 0.2f, 0.48f); + ItemStack hand = p.getInventory().getItemInMainHand(); + if (hand.getAmount() == 1) { + BoundRedstoneTorch.setData(hand, l); + } else { + hand.setAmount(hand.getAmount() - 1); + ItemStack torch = BoundRedstoneTorch.withData(l); + p.getInventory().addItem(torch).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); + } + } + + + private void triggerPulse(Player p, ItemStack item) { + Location l = BoundRedstoneTorch.getLocation(item); + if (isBound(item) && l != null) { + loadChunkAsync(l, chunk -> J.runAt(l, () -> { + Block b = l.getBlock(); + BlockData data = b.getBlockData(); + if (data instanceof AnaloguePowerable redBlock && b.getType().equals(Material.TARGET)) { + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(l, Sound.BLOCK_CHEST_OPEN, 0.1f, 9f); + redBlock.setPower(15); + vfxCuboidOutline(l.getBlock(), l.getBlock(), Color.RED, 1); + b.setBlockData(redBlock); + getPlayer(p).getData().addStat("architect.wireless-redstone.pulses", 1); + J.runAt(l, () -> { + redBlock.setPower(0); + b.setBlockData(redBlock); + }, 2); + } else { + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 0.1f, 0.9f); + } + })); + } + } + + private boolean isBound(ItemStack stack) { + return (stack.getType().equals(Material.REDSTONE_TORCH) && BoundRedstoneTorch.getLocation(stack) != null); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + ItemStack hand = p.getInventory().getItemInMainHand(); + ItemStack offhand = p.getInventory().getItemInOffHand(); + if ((isRedstoneTorch(hand) && BoundRedstoneTorch.hasItemData(hand)) || ( + isRedstoneTorch(offhand) && BoundRedstoneTorch.hasItemData(offhand))) { + withPlayerThread(p, () -> updatePlayerCooldown(p, false)); + } else { + withPlayerThread(p, () -> updatePlayerCooldown(p, true)); + } + } + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Use a crafted redstone remote to toggle redstone at a distance.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Architect Wireless Redstone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public int cooldown = 125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Wireless Redstone adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeChop.java b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeChop.java new file mode 100644 index 000000000..4098d6c2e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeChop.java @@ -0,0 +1,228 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.axe; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +public class AxeChop extends SimpleAdaptation { + + public AxeChop() { + super("axe-chop"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("axe.chop.description")); + setDisplayName(Localizer.dLocalize("axe.chop.name")); + setIcon(Material.IRON_AXE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(6911); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_AXE) + .key("challenge_axe_chop_100") + .title(Localizer.dLocalize("advancement.challenge_axe_chop_100.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_chop_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_AXE) + .key("challenge_axe_chop_2500") + .title(Localizer.dLocalize("advancement.challenge_axe_chop_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_chop_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.NETHERITE_AXE) + .key("challenge_axe_chop_one_swing") + .title(Localizer.dLocalize("advancement.challenge_axe_chop_one_swing.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_chop_one_swing.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_axe_chop_100", "axe.chop.trees-felled", 100, 400); + registerMilestone("challenge_axe_chop_2500", "axe.chop.trees-felled", 2500, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + level + C.GRAY + " " + Localizer.dLocalize("axe.chop.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTime(getLevelPercent(level)) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("axe.chop.lore2")); + v.addLore(C.RED + "- " + getDamagePerBlock(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("axe.chop.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (p.getCooldown(p.getInventory().getItemInMainHand().getType()) > 0) { + return; + } + + if (!isAxe(p.getInventory().getItemInMainHand()) || !hasActiveAdaptation(p)) { + return; + } + + Block target = action == Action.RIGHT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (target == null) { + return; + } + + if (!canBlockBreak(p, target.getLocation())) { + return; + } + + BlockData b = target.getBlockData(); + if (isLog(new ItemStack(b.getMaterial()))) { + e.setCancelled(true); + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ITEM_AXE_STRIP, 1.25f, 0.6f); + int logsChopped = 0; + for (int i = 0; i < getLevel(p); i++) { + if (breakStuff(target, getRange(getLevel(p)), p)) { + logsChopped++; + p.setCooldown(p.getInventory().getItemInMainHand().getType(), getCooldownTime(getLevelPercent(p))); + damageHand(p, getDamagePerBlock(getLevelPercent(p))); + } + } + if (logsChopped > 0) { + getPlayer(p).getData().addStat("axe.chop.trees-felled", 1); + if (logsChopped >= 30 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_axe_chop_one_swing")) { + getPlayer(p).getAdvancementHandler().grant("challenge_axe_chop_one_swing"); + } + } + } + } + + private int getRange(int level) { + return level * getConfig().rangeLevelMultiplier; + } + + private int getCooldownTime(double levelPercent) { + return (int) (getConfig().cooldownTicksBase + (getConfig().cooldownTicksInverseLevelMultiplier * ((1D - levelPercent)))); + } + + private int getDamagePerBlock(double levelPercent) { + return (int) (getConfig().damagePerBlockBase + (getConfig().damagePerBlockInverseLevelMultiplier * ((1D - levelPercent)))); + } + + private boolean breakStuff(Block b, int power, Player player) { + Block last = b; + for (int i = b.getY(); i < power + b.getY(); i++) { + Block bb = b.getWorld().getBlockAt(b.getX(), i, b.getZ()); + if (isLog(new ItemStack(bb.getType()))) { + last = bb; + } else { + break; + } + } + + if (!canBlockBreak(player, last.getLocation())) { + Adapt.verbose("Player " + player.getName() + " doesn't have permission."); + return false; + } + + if (!isLog(new ItemStack(last.getType()))) { + return false; + } + + Block ll = last; + + SoundPlayer spw = SoundPlayer.of(b.getWorld()); + spw.play(ll.getLocation(), Sound.ITEM_AXE_STRIP, 0.75f, 1.3f); + + player.breakBlock(ll); + return true; + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Chop down trees by right-clicking the base log.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Range Level Multiplier for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int rangeLevelMultiplier = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Inverse Level Multiplier for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksInverseLevelMultiplier = 16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Per Block Base for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damagePerBlockBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Per Block Inverse Level Multiplier for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damagePerBlockInverseLevelMultiplier = 4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeCraftLogSwap.java b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeCraftLogSwap.java new file mode 100644 index 000000000..4661069af --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeCraftLogSwap.java @@ -0,0 +1,1072 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.axe; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Materials; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +public class AxeCraftLogSwap extends SimpleAdaptation { + + public AxeCraftLogSwap() { + super("axe-logswap"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("axe.log_swap.description")); + setDisplayName(Localizer.dLocalize("axe.log_swap.name")); + setIcon(Material.MUDDY_MANGROVE_ROOTS); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(17773); + + //Birch -> Types + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapbirchoak") + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.OAK_SAPLING) + .result(new ItemStack(Material.OAK_PLANKS, 1)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapbirchacacia") + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.ACACIA_SAPLING) + .result(new ItemStack(Material.ACACIA_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapbirchdarkoak") + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.DARK_OAK_SAPLING) + .result(new ItemStack(Material.DARK_OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapbirchjungle") + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.JUNGLE_SAPLING) + .result(new ItemStack(Material.JUNGLE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapbirchspruce") + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.SPRUCE_SAPLING) + .result(new ItemStack(Material.SPRUCE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapbirchmangrove") + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.MANGROVE_PROPAGULE) + .result(new ItemStack(Material.MANGROVE_LOG, 8)) + .build()); + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapbirchcherry") + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Materials.CHERRY_SAPLING) + .result(new ItemStack(Materials.CHERRY_LOG, 8)) + .build()); + } + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapbirchpaleoak") + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Material.BIRCH_LOG) + .ingredient(Materials.PALE_OAK_SAPLING) + .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) + .build()); + } + + //Oak -> Types + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapoakbirch") + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.BIRCH_SAPLING) + .result(new ItemStack(Material.BIRCH_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapoakacacia") + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.ACACIA_SAPLING) + .result(new ItemStack(Material.ACACIA_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapoakdarkoak") + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.DARK_OAK_SAPLING) + .result(new ItemStack(Material.DARK_OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapoakjungle") + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.JUNGLE_SAPLING) + .result(new ItemStack(Material.JUNGLE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapoakspruce") + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.SPRUCE_SAPLING) + .result(new ItemStack(Material.SPRUCE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapoakmangrove") + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.MANGROVE_PROPAGULE) + .result(new ItemStack(Material.MANGROVE_LOG, 8)) + .build()); + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapoakcherry") + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Materials.CHERRY_SAPLING) + .result(new ItemStack(Materials.CHERRY_LOG, 8)) + .build()); + } + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapoakpaleoak") + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Material.OAK_LOG) + .ingredient(Materials.PALE_OAK_SAPLING) + .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) + .build()); + } + + //Acacia -> Types + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapacaciabirch") + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.BIRCH_SAPLING) + .result(new ItemStack(Material.BIRCH_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapacaciaoak") + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.OAK_SAPLING) + .result(new ItemStack(Material.OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapacaciadarkoak") + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.DARK_OAK_SAPLING) + .result(new ItemStack(Material.DARK_OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapacaciajungle") + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.JUNGLE_SAPLING) + .result(new ItemStack(Material.JUNGLE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapacaciaspruce") + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.SPRUCE_SAPLING) + .result(new ItemStack(Material.SPRUCE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapacaciamangrove") + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.MANGROVE_PROPAGULE) + .result(new ItemStack(Material.MANGROVE_LOG, 8)) + .build()); + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapacaciacherry") + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Materials.CHERRY_SAPLING) + .result(new ItemStack(Materials.CHERRY_LOG, 8)) + .build()); + } + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapacaciapaleoak") + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Material.ACACIA_LOG) + .ingredient(Materials.PALE_OAK_SAPLING) + .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) + .build()); + } + + //Dark Oak -> Types + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapdarkoakbirch") + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.BIRCH_SAPLING) + .result(new ItemStack(Material.BIRCH_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapdarkoakoak") + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.OAK_SAPLING) + .result(new ItemStack(Material.OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapdarkoakacacia") + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.ACACIA_SAPLING) + .result(new ItemStack(Material.ACACIA_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapdarkoakjungle") + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.JUNGLE_SAPLING) + .result(new ItemStack(Material.JUNGLE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapdarkoakspruce") + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.SPRUCE_SAPLING) + .result(new ItemStack(Material.SPRUCE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapdarkoakmangrove") + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.MANGROVE_PROPAGULE) + .result(new ItemStack(Material.MANGROVE_LOG, 8)) + .build()); + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapdarkoakcherry") + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Materials.CHERRY_SAPLING) + .result(new ItemStack(Materials.CHERRY_LOG, 8)) + .build()); + } + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapdarkoakpaleoak") + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Material.DARK_OAK_LOG) + .ingredient(Materials.PALE_OAK_SAPLING) + .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) + .build()); + } + + //Jungle -> Types + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapjunglebirch") + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.BIRCH_SAPLING) + .result(new ItemStack(Material.BIRCH_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapjungleoak") + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.OAK_SAPLING) + .result(new ItemStack(Material.OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapjungleacacia") + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.ACACIA_SAPLING) + .result(new ItemStack(Material.ACACIA_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapjungledarkoak") + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.DARK_OAK_SAPLING) + .result(new ItemStack(Material.DARK_OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapjunglespruce") + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.SPRUCE_SAPLING) + .result(new ItemStack(Material.SPRUCE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapjunglemangrove") + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.MANGROVE_PROPAGULE) + .result(new ItemStack(Material.MANGROVE_LOG, 8)) + .build()); + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapjunglecherry") + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Materials.CHERRY_SAPLING) + .result(new ItemStack(Materials.CHERRY_LOG, 8)) + .build()); + } + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapjunglepaleoak") + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Material.JUNGLE_LOG) + .ingredient(Materials.PALE_OAK_SAPLING) + .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) + .build()); + } + + //Spruce -> Types + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapsprucebirch") + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.BIRCH_SAPLING) + .result(new ItemStack(Material.BIRCH_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapspruceoak") + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.OAK_SAPLING) + .result(new ItemStack(Material.OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapspruceacacia") + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.ACACIA_SAPLING) + .result(new ItemStack(Material.ACACIA_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapsprucedarkoak") + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.DARK_OAK_SAPLING) + .result(new ItemStack(Material.DARK_OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapsprucejungle") + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.JUNGLE_SAPLING) + .result(new ItemStack(Material.JUNGLE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapsprucemangrove") + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.MANGROVE_PROPAGULE) + .result(new ItemStack(Material.MANGROVE_LOG, 8)) + .build()); + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapsprucecherry") + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Materials.CHERRY_SAPLING) + .result(new ItemStack(Materials.CHERRY_LOG, 8)) + .build()); + } + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapsprucepaleoak") + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Material.SPRUCE_LOG) + .ingredient(Materials.PALE_OAK_SAPLING) + .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) + .build()); + } + + //Mangrove -> Types + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapmangrovebirch") + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.BIRCH_SAPLING) + .result(new ItemStack(Material.BIRCH_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapmangroveoak") + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.OAK_SAPLING) + .result(new ItemStack(Material.OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapmangroveacacia") + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.ACACIA_SAPLING) + .result(new ItemStack(Material.ACACIA_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapmangrovedarkoak") + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.DARK_OAK_SAPLING) + .result(new ItemStack(Material.DARK_OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapmangrovejungle") + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.JUNGLE_SAPLING) + .result(new ItemStack(Material.JUNGLE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapmangrovespruce") + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.SPRUCE_SAPLING) + .result(new ItemStack(Material.SPRUCE_LOG, 8)) + .build()); + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapmangrovecherry") + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Materials.CHERRY_SAPLING) + .result(new ItemStack(Materials.CHERRY_LOG, 8)) + .build()); + } + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapmangrovepaleoak") + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Material.MANGROVE_LOG) + .ingredient(Materials.PALE_OAK_SAPLING) + .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) + .build()); + } + + //Cherry -> Types + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapcherrybirch") + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Material.BIRCH_SAPLING) + .result(new ItemStack(Material.BIRCH_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapcherryoak") + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Material.OAK_SAPLING) + .result(new ItemStack(Material.OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapcherryacacia") + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Material.ACACIA_SAPLING) + .result(new ItemStack(Material.ACACIA_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapcherrydarkoak") + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Material.DARK_OAK_SAPLING) + .result(new ItemStack(Material.DARK_OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapcherryjungle") + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Material.JUNGLE_SAPLING) + .result(new ItemStack(Material.JUNGLE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapcherryspruce") + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Material.SPRUCE_SAPLING) + .result(new ItemStack(Material.SPRUCE_LOG, 8)) + .build()); + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swapcherrypaleoak") + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.CHERRY_LOG) + .ingredient(Materials.PALE_OAK_SAPLING) + .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) + .build()); + } + } + + //Pale Oak -> Types + if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swappaleoakbirch") + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Material.BIRCH_SAPLING) + .result(new ItemStack(Material.BIRCH_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swappaleoakoak") + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Material.OAK_SAPLING) + .result(new ItemStack(Material.OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swappaleoakacacia") + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Material.ACACIA_SAPLING) + .result(new ItemStack(Material.ACACIA_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swappaleoakdarkoak") + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Material.DARK_OAK_SAPLING) + .result(new ItemStack(Material.DARK_OAK_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swappaleoakjungle") + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Material.JUNGLE_SAPLING) + .result(new ItemStack(Material.JUNGLE_LOG, 8)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swappaleoakspruce") + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Material.SPRUCE_SAPLING) + .result(new ItemStack(Material.SPRUCE_LOG, 8)) + .build()); + if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { + registerRecipe(AdaptRecipe.shapeless() + .key("axe-swappaleoakcherry") + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.PALE_OAK_LOG) + .ingredient(Materials.CHERRY_SAPLING) + .result(new ItemStack(Materials.CHERRY_LOG, 8)) + .build()); + } + } + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.OAK_SAPLING) + .key("challenge_axe_log_swap_500") + .title(Localizer.dLocalize("advancement.challenge_axe_log_swap_500.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_log_swap_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_axe_log_swap_500", "axe.log-swap.conversions", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("axe.log_swap.lore1")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(CraftItemEvent e) { + if (!(e.getWhoClicked() instanceof Player p) || !hasActiveAdaptation(p)) { + return; + } + if (e.getRecipe() instanceof org.bukkit.inventory.ShapelessRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && recipe.getKey().getKey().startsWith("axe-swap")) { + getPlayer(p).getData().addStat("axe.log-swap.conversions", 1); + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Convert log types using a sapling in a crafting table.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeDropToInventory.java b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeDropToInventory.java new file mode 100644 index 000000000..e30bc51cf --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeDropToInventory.java @@ -0,0 +1,127 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.axe; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; + +import java.util.List; + +public class AxeDropToInventory extends SimpleAdaptation { + public AxeDropToInventory() { + super("axe-drop-to-inventory"); + registerConfiguration(AxeDropToInventory.Config.class); + setDescription(Localizer.dLocalize("pickaxe.drop_to_inventory.description")); + setDisplayName(Localizer.dLocalize("axe.drop_to_inventory.name")); + setIcon(Material.BARREL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(8800); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_axe_dti_5k") + .title(Localizer.dLocalize("advancement.challenge_axe_dti_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_dti_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_axe_dti_5k", "axe.drop-to-inv.items-caught", 5000, 500); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("pickaxe.drop_to_inventory.lore1")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDropItemEvent e) { + + Player p = e.getPlayer(); + if (resolveBlockBreakContext(p, e.getBlock().getLocation(), null, true) == null) { + return; + } + + SoundPlayer sp = SoundPlayer.of(p); + if (ItemListings.toolAxes.contains(p.getInventory().getItemInMainHand().getType())) { + List items = new KList<>(e.getItems()); + e.getItems().clear(); + int caught = 0; + for (Item i : items) { + sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); + if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { + p.getWorld().dropItem(p.getLocation(), i.getItemStack()); + } + caught++; + } + if (caught > 0) { + getPlayer(p).getData().addStat("axe.drop-to-inv.items-caught", caught); + } + } + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + + @Override + public void onTick() { + } + + @NoArgsConstructor + @ConfigDescription("Chopped wood drops directly into your inventory.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeGroundSmash.java b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeGroundSmash.java new file mode 100644 index 000000000..07fdb790a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeGroundSmash.java @@ -0,0 +1,189 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.axe; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.Impulse; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public class AxeGroundSmash extends SimpleAdaptation { + public AxeGroundSmash() { + super("axe-ground-smash"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("axe.ground_smash.description")); + setDisplayName(Localizer.dLocalize("axe.ground_smash.name")); + setIcon(Material.NETHERITE_AXE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(4333); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_AXE) + .key("challenge_axe_ground_smash_500") + .title(Localizer.dLocalize("advancement.challenge_axe_ground_smash_500.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_ground_smash_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.NETHERITE_AXE) + .key("challenge_axe_ground_smash_5") + .title(Localizer.dLocalize("advancement.challenge_axe_ground_smash_5.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_ground_smash_5.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_axe_ground_smash_500", "axe.ground-smash.mobs-hit", 500, 500); + } + + @Override + public void addStats(int level, Element v) { + double f = getLevelPercent(level); + v.addLore(C.RED + "+ " + Form.f(getFalloffDamage(f), 1) + " - " + Form.f(getDamage(f), 1) + C.GRAY + " " + Localizer.dLocalize("axe.ground_smash.lore1")); + v.addLore(C.RED + "+ " + Form.f(getRadius(f), 1) + C.GRAY + " " + Localizer.dLocalize("axe.ground_smash.lore2")); + v.addLore(C.RED + "+ " + Form.pc(getForce(f), 0) + C.GRAY + " " + Localizer.dLocalize("axe.ground_smash.lore3")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTime(getLevelPercent(level)) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("axe.ground_smash.lore4")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.MeleeContext combat = resolveMeleeContext(e, this::isAxe); + if (combat == null) { + return; + } + + Player p = combat.attacker(); + if (!p.isSneaking()) { + return; + } + + double f = getLevelPercent(combat.level()); + + p.setCooldown(combat.mainHand().getType(), getCooldownTime(f)); + double radius = getRadius(f); + new Impulse(radius) + .damage(getDamage(f), getFalloffDamage(f)) + .force(getForce(f)) + .filter(nearby -> nearby != p && canDamageTarget(p, nearby)) + .punch(e.getEntity().getLocation()); + int mobsHit = 0; + for (Entity nearby : e.getEntity().getWorld().getNearbyEntities(e.getEntity().getLocation(), radius, radius, radius)) { + if (nearby instanceof LivingEntity && nearby != p && canDamageTarget(p, nearby)) { + mobsHit++; + } + } + if (mobsHit > 0) { + getPlayer(p).getData().addStat("axe.ground-smash.mobs-hit", mobsHit); + if (mobsHit >= 5 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_axe_ground_smash_5")) { + getPlayer(p).getAdvancementHandler().grant("challenge_axe_ground_smash_5"); + } + } + SoundPlayer spw = SoundPlayer.of(e.getEntity().getWorld()); + spw.play(e.getEntity().getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, SoundCategory.HOSTILE, 0.6f, 0.4f); + spw.play(e.getEntity().getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, SoundCategory.HOSTILE, 0.5f, 0.1f); + spw.play(e.getEntity().getLocation(), Sound.ENTITY_TURTLE_EGG_CRACK, SoundCategory.HOSTILE, 1f, 0.4f); + } + + + public int getCooldownTime(double factor) { + return (int) (((1D - factor) * getConfig().cooldownTicksInverseLevelMultiplier) + getConfig().cooldownTicksBase); + } + + public double getRadius(double factor) { + return getConfig().radiusLevelFactorMultiplier * factor; + } + + public double getDamage(double factor) { + return getConfig().damageLevelFactorMultiplier * factor; + } + + public double getForce(double factor) { + return (getConfig().forceFactorMultiplier * factor) + getConfig().forceBase; + } + + public double getFalloffDamage(double factor) { + return getConfig().falloffFactor * factor; + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Jump then crouch to smash all nearby enemies with your axe.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Falloff Factor for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double falloffFactor = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Level Factor Multiplier for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusLevelFactorMultiplier = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Level Factor Multiplier for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageLevelFactorMultiplier = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Force Factor Multiplier for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double forceFactorMultiplier = 1.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Force Base for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double forceBase = 0.27; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Inverse Level Multiplier for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksInverseLevelMultiplier = 225; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeLeafVeinminer.java b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeLeafVeinminer.java new file mode 100644 index 000000000..c0c4a8d7d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeLeafVeinminer.java @@ -0,0 +1,226 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.axe; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +import static art.arcane.adapt.util.data.Metadata.VEIN_MINED; + +public class AxeLeafVeinminer extends SimpleAdaptation { + public AxeLeafVeinminer() { + super("axe-leaf-veinminer"); + registerConfiguration(AxeLeafVeinminer.Config.class); + setDescription(Localizer.dLocalize("axe.leaf_miner.description")); + setDisplayName(Localizer.dLocalize("axe.leaf_miner.name")); + setIcon(Material.BIRCH_LEAVES); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(5849); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.OAK_LEAVES) + .key("challenge_axe_leaf_5k") + .title(Localizer.dLocalize("advancement.challenge_axe_leaf_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_leaf_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_axe_leaf_5k", "axe.leaf-veinminer.leaves-broken", 5000, 400); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("axe.leaf_miner.lore1")); + v.addLore(C.GREEN + "" + (level + getConfig().baseRange) + C.GRAY + " " + Localizer.dLocalize("axe.leaf_miner.lore2")); + v.addLore(C.ITALIC + Localizer.dLocalize("axe.leaf_miner.lore3")); + } + + private int getRadius(int lvl) { + return lvl + getConfig().baseRange; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + + if (VEIN_MINED.get(e.getBlock())) { + return; + } + + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + if (!hasActiveAdaptation(p)) { + return; + } + + if (!p.isSneaking()) { + return; + } + + if (!isAxe(p.getInventory().getItemInMainHand())) { + return; + } + + Material blockType = e.getBlock().getType(); + if (!blockType.isItem() || !isLeaves(new ItemStack(blockType))) { + return; + } + + if (!canBlockBreak(p, e.getBlock().getLocation())) { + return; + } + + VEIN_MINED.add(e.getBlock()); + + Block block = e.getBlock(); + Map blockMap = new HashMap<>(); + Deque stack = new ArrayDeque<>(); + Set queued = new HashSet<>(); + stack.push(block); + queued.add(block.getLocation()); + int radius = getRadius(getLevel(p)); + int radiusSquared = radius * radius; + while (!stack.isEmpty() && blockMap.size() < radius) { + Block currentBlock = stack.pop(); + Location currentLocation = currentBlock.getLocation(); + if (blockMap.containsKey(currentLocation)) { + continue; + } + blockMap.put(currentLocation, currentBlock); + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + if (x == 0 && y == 0 && z == 0) { + continue; + } + Block b = currentBlock.getRelative(x, y, z); + Location nextLocation = b.getLocation(); + if (b.getType() != block.getType() + || blockMap.containsKey(nextLocation) + || !queued.add(nextLocation)) { + continue; + } + if (currentBlock.getLocation().distanceSquared(b.getLocation()) <= radiusSquared && canBlockBreak(p, b.getLocation())) { + stack.push(b); + } else { + queued.remove(nextLocation); + } + } + } + } + } + + int leavesBroken = blockMap.size(); + J.runEntity(p, () -> { + for (Location l : blockMap.keySet()) { + Block b = block.getWorld().getBlockAt(l); + PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("axes"); + PlayerAdaptation adaptation = line != null ? line.getAdaptation("axe-drop-to-inventory") : null; + + VEIN_MINED.add(b); + if (adaptation != null && adaptation.getLevel() > 0) { + Collection items = b.getDrops(p.getInventory().getItemInMainHand(), p); + for (ItemStack i : items) { + sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.01f, 0.01f); + HashMap extra = p.getInventory().addItem(i); + if (!extra.isEmpty()) { + p.getWorld().dropItem(p.getLocation(), extra.get(0)); + } + } + b.setType(Material.AIR); + } else { + b.breakNaturally(p.getItemInUse()); + SoundPlayer spw = SoundPlayer.of(block.getWorld()); + spw.play(b.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 0.01f, 0.25f); + if (areParticlesEnabled()) { + b.getWorld().spawnParticle(Particle.ASH, b.getLocation().add(0.5, 0.5, 0.5), 25, 0.5, 0.5, 0.5, 0.1); + } + } + if (areParticlesEnabled()) { + this.vfxCuboidOutline(b, Particles.ENCHANTMENT_TABLE); + } + VEIN_MINED.remove(b); + } + VEIN_MINED.remove(block); + }); + if (leavesBroken > 0) { + getPlayer(p).getData().addStat("axe.leaf-veinminer.leaves-broken", leavesBroken); + } + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Break bulk leaves at once while sneaking.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Axe Leaf Veinminer adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.325; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Range for the Axe Leaf Veinminer adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseRange = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeTimberMark.java b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeTimberMark.java new file mode 100644 index 000000000..722824f8d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeTimberMark.java @@ -0,0 +1,317 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.axe; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; + +import java.util.ArrayDeque; +import java.util.Set; +import java.util.UUID; + +public class AxeTimberMark extends SimpleAdaptation { + public AxeTimberMark() { + super("axe-timber-mark"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("axe.timber_mark.description")); + setDisplayName(Localizer.dLocalize("axe.timber_mark.name")); + setIcon(Material.OAK_LOG); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.OAK_LOG) + .key("challenge_axe_timber_200") + .title(Localizer.dLocalize("advancement.challenge_axe_timber_200.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_timber_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_AXE) + .key("challenge_axe_timber_40") + .title(Localizer.dLocalize("advancement.challenge_axe_timber_40.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_timber_40.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_axe_timber_200", "axe.timber-mark.marks-felled", 200, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getMaxBlocks(level) + C.GRAY + " " + Localizer.dLocalize("axe.timber_mark.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getMarkDurationMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("axe.timber_mark.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + setStorage(e.getPlayer(), "timberMarkBlock", null); + setStorage(e.getPlayer(), "timberMarkUntil", 0L); + setStorage(e.getPlayer(), "timberMarkOwner", id.toString()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0 || !isAxe(p.getInventory().getItemInMainHand())) { + return; + } + + Block clicked = action == Action.RIGHT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (clicked == null || !isLog(new org.bukkit.inventory.ItemStack(clicked.getType()))) { + return; + } + + setStorage(p, "timberMarkBlock", clicked.getLocation().toString()); + setStorage(p, "timberMarkUntil", System.currentTimeMillis() + getMarkDurationMillis(level)); + e.setUseInteractedBlock(Event.Result.DENY); + e.setUseItemInHand(Event.Result.DENY); + e.setCancelled(true); + SoundPlayer.of(p.getWorld()).play(clicked.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 0.6f, 1.8f); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockBreakEvent e) { + + Player p = e.getPlayer(); + int level = getActiveLevel(p); + if (level <= 0 || !isAxe(p.getInventory().getItemInMainHand())) { + return; + } + + Long until = getStorageLong(p, "timberMarkUntil", 0L); + String marked = getStorageString(p, "timberMarkBlock", ""); + if (until == null || until < System.currentTimeMillis() || marked == null || marked.isEmpty()) { + return; + } + + if (!e.getBlock().getLocation().toString().equals(marked)) { + return; + } + + Material type = e.getBlock().getType(); + int maxBlocks = getMaxBlocks(level); + Set connected = floodLogs(e.getBlock(), type, maxBlocks); + int logsFelled = 0; + for (Block b : connected) { + if (b.equals(e.getBlock())) { + continue; + } + + if (!canBlockBreak(p, b.getLocation())) { + continue; + } + + b.breakNaturally(p.getInventory().getItemInMainHand()); + xp(p, getConfig().xpPerExtraLog); + logsFelled++; + } + + Set leaves = floodLeaves(connected, getMaxLeaves(level)); + for (Block leaf : leaves) { + if (!canBlockBreak(p, leaf.getLocation())) { + continue; + } + + leaf.breakNaturally(p.getInventory().getItemInMainHand()); + xp(p, getConfig().xpPerLeafCleared); + } + + SoundPlayer.of(p.getWorld()).play(e.getBlock().getLocation(), Sound.BLOCK_WOOD_BREAK, 0.6f, 0.8f); + setStorage(p, "timberMarkUntil", 0L); + setStorage(p, "timberMarkBlock", ""); + getPlayer(p).getData().addStat("axe.timber-mark.marks-felled", 1); + if (logsFelled >= 40 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_axe_timber_40")) { + getPlayer(p).getAdvancementHandler().grant("challenge_axe_timber_40"); + } + } + + private Set floodLogs(Block start, Material type, int maxBlocks) { + Set visited = java.util.concurrent.ConcurrentHashMap.newKeySet(); + ArrayDeque queue = new ArrayDeque<>(); + queue.add(start); + visited.add(start); + while (!queue.isEmpty() && visited.size() < maxBlocks) { + Block b = queue.poll(); + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + Block n = b.getRelative(x, y, z); + if (n.getType() != type || visited.contains(n)) { + continue; + } + + visited.add(n); + queue.add(n); + if (visited.size() >= maxBlocks) { + return visited; + } + } + } + } + } + return visited; + } + + private Set floodLeaves(Set logs, int maxLeaves) { + Set visited = java.util.concurrent.ConcurrentHashMap.newKeySet(); + ArrayDeque queue = new ArrayDeque<>(); + + for (Block log : logs) { + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + Block n = log.getRelative(x, y, z); + if (!isLeafMaterial(n.getType()) || visited.contains(n)) { + continue; + } + + visited.add(n); + queue.add(n); + if (visited.size() >= maxLeaves) { + return visited; + } + } + } + } + } + + while (!queue.isEmpty() && visited.size() < maxLeaves) { + Block b = queue.poll(); + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + Block n = b.getRelative(x, y, z); + if (!isLeafMaterial(n.getType()) || visited.contains(n)) { + continue; + } + + visited.add(n); + queue.add(n); + if (visited.size() >= maxLeaves) { + return visited; + } + } + } + } + } + + return visited; + } + + private boolean isLeafMaterial(Material type) { + return type.name().endsWith("_LEAVES"); + } + + private int getMaxBlocks(int level) { + return Math.max(8, (int) Math.round(getConfig().maxBlocksBase + (getLevelPercent(level) * getConfig().maxBlocksFactor))); + } + + private long getMarkDurationMillis(int level) { + return (long) Math.max(1000, Math.round(getConfig().markDurationMillisBase + (getLevelPercent(level) * getConfig().markDurationMillisFactor))); + } + + private int getMaxLeaves(int level) { + return Math.max(0, (int) Math.round(getConfig().maxLeavesBase + (getLevelPercent(level) * getConfig().maxLeavesFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Mark a trunk, then break the marked log to fell connected wood.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Blocks Base for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxBlocksBase = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Blocks Factor for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxBlocksFactor = 56; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mark Duration Millis Base for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double markDurationMillisBase = 6000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mark Duration Millis Factor for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double markDurationMillisFactor = 9000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Extra Log for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerExtraLog = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Leaves Base for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxLeavesBase = 24; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Leaves Factor for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxLeavesFactor = 180; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Leaf Cleared for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerLeafCleared = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeWoodVeinminer.java b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeWoodVeinminer.java new file mode 100644 index 000000000..ceec3649a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/axe/AxeWoodVeinminer.java @@ -0,0 +1,222 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.axe; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import static art.arcane.adapt.util.data.Metadata.VEIN_MINED; + +public class AxeWoodVeinminer extends SimpleAdaptation { + public AxeWoodVeinminer() { + super("axe-wood-veinminer"); + registerConfiguration(AxeWoodVeinminer.Config.class); + setDescription(Localizer.dLocalize("axe.wood_miner.description")); + setDisplayName(Localizer.dLocalize("axe.wood_miner.name")); + setIcon(Material.DIAMOND_AXE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(5849); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.OAK_LOG) + .key("challenge_axe_wood_vein_2500") + .title(Localizer.dLocalize("advancement.challenge_axe_wood_vein_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_wood_vein_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_AXE) + .key("challenge_axe_wood_vein_cascade") + .title(Localizer.dLocalize("advancement.challenge_axe_wood_vein_cascade.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_wood_vein_cascade.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_axe_wood_vein_2500", "axe.wood-veinminer.logs-veinmined", 2500, 500); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("axe.wood_miner.lore1")); + v.addLore(C.GREEN + "" + (level + getConfig().baseRange) + C.GRAY + " " + Localizer.dLocalize("axe.wood_miner.lore2")); + v.addLore(C.ITALIC + Localizer.dLocalize("axe.wood_miner.lore3")); + } + + private int getRadius(int lvl) { + return lvl + getConfig().baseRange; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (VEIN_MINED.get(e.getBlock())) { + return; + } + + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + if (!p.isSneaking()) { + return; + } + + if (!isAxe(p.getInventory().getItemInMainHand())) { + return; + } + + if (!isLog(new ItemStack(e.getBlock().getType()))) { + return; + } + + VEIN_MINED.add(e.getBlock()); + Block block = e.getBlock(); + Set blockMap = new HashSet<>(); + int blockCount = 0; + int radius = getRadius(getLevel(p)); + int radiusSquared = radius * radius; + for (int i = 0; i < radius; i++) { + for (int x = -i; x <= i; x++) { + for (int y = -i; y <= i; y++) { + for (int z = -i; z <= i; z++) { + Block b = block.getRelative(x, y, z); + if (b.getType() == block.getType()) { + blockCount++; + if (blockCount > getConfig().maxBlocks) { + Adapt.verbose("Block: " + blockCount + " > " + getConfig().maxBlocks); + continue; + } + if (block.getLocation().distanceSquared(b.getLocation()) > radiusSquared) { + Adapt.verbose("Block: " + b.getLocation() + " is too far away from " + block.getLocation() + " (" + radius + ")"); + continue; + } + if (!canBlockBreak(p, b.getLocation())) { + Adapt.verbose("Player " + p.getName() + " doesn't have permission."); + continue; + } + blockMap.add(b); + } + } + } + } + } + + int logsVeinmined = blockMap.size(); + J.runEntity(p, () -> { + for (Block blocks : blockMap) { + PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("axes"); + PlayerAdaptation adaptation = line != null ? line.getAdaptation("axe-drop-to-inventory") : null; + VEIN_MINED.add(blocks); + if (adaptation != null && adaptation.getLevel() > 0) { + Collection items = blocks.getDrops(); + for (ItemStack item : items) { + safeGiveItem(p, item); + Adapt.verbose("Giving item: " + item); + } + blocks.setType(Material.AIR); + } else { + blocks.breakNaturally(p.getItemInUse()); + SoundPlayer spw = SoundPlayer.of(blocks.getWorld()); + spw.play(e.getBlock().getLocation(), Sound.BLOCK_FUNGUS_BREAK, 0.01f, 0.25f); + if (areParticlesEnabled()) { + blocks.getWorld().spawnParticle(Particle.ASH, blocks.getLocation().add(0.5, 0.5, 0.5), 25, 0.5, 0.5, 0.5, 0.1); + } + } + if (areParticlesEnabled()) { + this.vfxCuboidOutline(blocks, Particles.ENCHANTMENT_TABLE); + } + VEIN_MINED.remove(blocks); + } + VEIN_MINED.remove(block); + }); + if (logsVeinmined > 0) { + getPlayer(p).getData().addStat("axe.wood-veinminer.logs-veinmined", logsVeinmined); + if (logsVeinmined >= 15 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_axe_wood_vein_cascade")) { + getPlayer(p).getAdvancementHandler().grant("challenge_axe_wood_vein_cascade"); + } + } + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Break bulk wood at once while sneaking.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Axe Wood Veinminer adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.95; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Blocks for the Axe Wood Veinminer adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxBlocks = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Range for the Axe Wood Veinminer adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseRange = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingBastionStance.java b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingBastionStance.java new file mode 100644 index 000000000..274d472f4 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingBastionStance.java @@ -0,0 +1,218 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.blocking; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerVelocityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockingBastionStance extends SimpleAdaptation { + public BlockingBastionStance() { + super("blocking-bastion-stance"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("blocking.bastion_stance.description")); + setDisplayName(Localizer.dLocalize("blocking.bastion_stance.name")); + setIcon(Material.SHIELD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_blocking_bastion_500") + .title(Localizer.dLocalize("advancement.challenge_blocking_bastion_500.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_bastion_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_blocking_bastion_500", "blocking.bastion-stance.projectiles-softened", 500, 500); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_blocking_bastion_10") + .title(Localizer.dLocalize("advancement.challenge_blocking_bastion_10.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_bastion_10.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getKnockbackReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.bastion_stance.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getProjectileReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.bastion_stance.lore2")); + v.addLore(C.GREEN + "+ " + Form.pc(getProjectileNegateChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.bastion_stance.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Player defender) || !isBastionStance(defender)) { + return; + } + + if (!(e.getDamager() instanceof Projectile)) { + return; + } + + int level = getActiveLevel(defender); + if (level <= 0) { + return; + } + + // Track session counter for special achievement + int sessionCount = getStorageInt(defender, "bastionSessionCount", 0) + 1; + setStorage(defender, "bastionSessionCount", sessionCount); + if (sessionCount >= 10 && AdaptConfig.get().isAdvancements() && !getPlayer(defender).getData().isGranted("challenge_blocking_bastion_10")) { + getPlayer(defender).getAdvancementHandler().grant("challenge_blocking_bastion_10"); + } + + if (ThreadLocalRandom.current().nextDouble() <= getProjectileNegateChance(level)) { + e.setCancelled(true); + SoundPlayer.of(defender.getWorld()).play(defender.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1f, 0.9f); + xp(defender, getConfig().xpOnNegate); + getPlayer(defender).getData().addStat("blocking.bastion-stance.projectiles-softened", 1); + return; + } + + e.setDamage(Math.max(0, e.getDamage() * (1D - getProjectileReduction(level)))); + SoundPlayer.of(defender.getWorld()).play(defender.getLocation(), Sound.ITEM_SHIELD_BLOCK, 0.75f, 0.75f); + xp(defender, e.getDamage() * getConfig().xpPerMitigatedDamage); + getPlayer(defender).getData().addStat("blocking.bastion-stance.projectiles-softened", 1); + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(PlayerVelocityEvent e) { + Player p = e.getPlayer(); + int level = getActiveLevel(p); + if (!isBastionStance(p, level)) { + return; + } + + double factor = 1D - getKnockbackReduction(level); + Vector v = e.getVelocity(); + e.setVelocity(new Vector(v.getX() * factor, v.getY(), v.getZ() * factor)); + } + + private boolean isBastionStance(Player p) { + return isBastionStance(p, getActiveLevel(p)); + } + + private boolean isBastionStance(Player p, int level) { + return level > 0 && p.isBlocking() && p.isSneaking() && hasShield(p); + } + + private boolean hasShield(Player p) { + ItemStack main = p.getInventory().getItemInMainHand(); + ItemStack off = p.getInventory().getItemInOffHand(); + return (isItem(main) && main.getType() == Material.SHIELD) || (isItem(off) && off.getType() == Material.SHIELD); + } + + private double getKnockbackReduction(int level) { + return Math.min(getConfig().maxKnockbackReduction, getConfig().knockbackReductionBase + (getLevelPercent(level) * getConfig().knockbackReductionFactor)); + } + + private double getProjectileReduction(int level) { + return Math.min(getConfig().maxProjectileReduction, getConfig().projectileReductionBase + (getLevelPercent(level) * getConfig().projectileReductionFactor)); + } + + private double getProjectileNegateChance(int level) { + return Math.min(getConfig().maxProjectileNegateChance, getConfig().projectileNegateChanceBase + (getLevelPercent(level) * getConfig().projectileNegateChanceFactor)); + } + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + int level = getActiveLevel(p); + if (level > 0 && !isBastionStance(p, level)) { + setStorage(p, "bastionSessionCount", 0); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-block with a shield to brace against knockback and soften projectiles.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.68; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Knockback Reduction Base for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double knockbackReductionBase = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Knockback Reduction Factor for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double knockbackReductionFactor = 0.52; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Knockback Reduction for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxKnockbackReduction = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Projectile Reduction Base for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double projectileReductionBase = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Projectile Reduction Factor for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double projectileReductionFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Projectile Reduction for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxProjectileReduction = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Projectile Negate Chance Base for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double projectileNegateChanceBase = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Projectile Negate Chance Factor for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double projectileNegateChanceFactor = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Projectile Negate Chance for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxProjectileNegateChance = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mitigated Damage for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerMitigatedDamage = 2.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Negate for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnNegate = 8.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingBulwarkBash.java b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingBulwarkBash.java new file mode 100644 index 000000000..d1d60a8df --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingBulwarkBash.java @@ -0,0 +1,286 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.blocking; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSprintEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class BlockingBulwarkBash extends SimpleAdaptation { + private final Map lastSprintMillis = new java.util.concurrent.ConcurrentHashMap<>(); + + public BlockingBulwarkBash() { + super("blocking-bulwark-bash"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("blocking.bulwark_bash.description")); + setDisplayName(Localizer.dLocalize("blocking.bulwark_bash.name")); + setIcon(Material.BELL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_blocking_bulwark_500") + .title(Localizer.dLocalize("advancement.challenge_blocking_bulwark_500.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_bulwark_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_blocking_bulwark_500", "blocking.bulwark-bash.mobs-bashed", 500, 500); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_blocking_bulwark_4") + .title(Localizer.dLocalize("advancement.challenge_blocking_bulwark_4.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_bulwark_4.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRange(level)) + C.GRAY + " " + Localizer.dLocalize("blocking.bulwark_bash.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getDamageBonus(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.bulwark_bash.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("blocking.bulwark_bash.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerToggleSprintEvent e) { + if (e.isSprinting()) { + lastSprintMillis.put(e.getPlayer().getUniqueId(), System.currentTimeMillis()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + lastSprintMillis.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.MeleeContext combat = resolveMeleeContext(e); + if (combat == null) { + return; + } + + Player p = combat.attacker(); + LivingEntity target = combat.target(); + if (p.getInventory().getItemInOffHand().getType() != Material.SHIELD || p.hasCooldown(Material.SHIELD)) { + return; + } + + if (!isJumpCrit(p) || !wasRecentlySprinting(p)) { + return; + } + + int level = combat.level(); + int affected = 0; + double radius = getRange(level); + for (org.bukkit.entity.Entity nearby : target.getWorld().getNearbyEntities(target.getLocation(), radius, radius, radius)) { + if (!(nearby instanceof LivingEntity hit) || hit == p) { + continue; + } + + if (!canDamageTarget(p, hit)) { + continue; + } + + applyImpact(p, hit, level); + affected++; + } + + if (affected <= 0) { + return; + } + + e.setDamage(e.getDamage() + getBaseDamage(level)); + p.setCooldown(Material.SHIELD, getCooldownTicks(level)); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.SWEEP_ATTACK, p.getLocation().add(0, 1, 0), 1, 0, 0, 0, 0); + } + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.CLOUD, p.getLocation().add(0, 0.3, 0), 18, 0.35, 0.1, 0.35, 0.06); + } + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1f, 0.85f); + sp.play(p.getLocation(), Sound.ENTITY_PLAYER_ATTACK_SWEEP, 0.9f, 0.7f); + xp(p, getConfig().xpPerTargetHit * affected); + getPlayer(p).getData().addStat("blocking.bulwark-bash.mobs-bashed", affected); + + // Special achievement: hit 4+ enemies in single bash + if (affected >= 4 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_blocking_bulwark_4")) { + getPlayer(p).getAdvancementHandler().grant("challenge_blocking_bulwark_4"); + } + } + + private void applyImpact(Player p, LivingEntity target, int level) { + Vector kb = target.getLocation().toVector().subtract(p.getLocation().toVector()).setY(0); + if (kb.lengthSquared() <= 0.0001) { + kb = p.getLocation().getDirection().setY(0); + } + if (kb.lengthSquared() <= 0.0001) { + return; + } + + kb.normalize(); + target.setVelocity(target.getVelocity().multiply(0.25).add(kb.multiply(getKnockback(level)).setY(getUpwardKnockback(level)))); + target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getStunTicks(level), getStunAmplifier(level), false, false, true), true); + } + + private boolean wasRecentlySprinting(Player p) { + long last = lastSprintMillis.getOrDefault(p.getUniqueId(), 0L); + return p.isSprinting() || (System.currentTimeMillis() - last) <= getConfig().recentSprintWindowMillis; + } + + private boolean isJumpCrit(Player p) { + return p.getFallDistance() > getConfig().minFallDistanceForCrit + && !p.isOnGround() + && !p.isInWater() + && !p.isInsideVehicle() + && !p.isClimbing(); + } + + private double getRange(int level) { + return getConfig().rangeBase + (getLevelPercent(level) * getConfig().rangeFactor); + } + + private double getDamageBonus(int level) { + return getConfig().damageBonusBase + (getLevelPercent(level) * getConfig().damageBonusFactor); + } + + private double getBaseDamage(int level) { + return getConfig().baseDamage + getDamageBonus(level); + } + + private double getKnockback(int level) { + return getConfig().knockbackBase + (getLevelPercent(level) * getConfig().knockbackFactor); + } + + private double getUpwardKnockback(int level) { + return getConfig().upwardKnockbackBase + (getLevelPercent(level) * getConfig().upwardKnockbackFactor); + } + + private int getStunTicks(int level) { + return Math.max(10, (int) Math.round(getConfig().stunTicksBase + (getLevelPercent(level) * getConfig().stunTicksFactor))); + } + + private int getStunAmplifier(int level) { + return Math.max(0, (int) Math.round(getConfig().stunAmplifierBase + (getLevelPercent(level) * getConfig().stunAmplifierFactor))); + } + + private int getCooldownTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + @Override + public void onTick() { + long cutoff = System.currentTimeMillis() - Math.max(1000L, getConfig().recentSprintWindowMillis * 3L); + lastSprintMillis.entrySet().removeIf(i -> i.getValue() < cutoff); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sprint, jump, and land a shielded crit to unleash a bash shockwave.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Damage for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseDamage = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageBonusBase = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageBonusFactor = 2.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Range Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rangeBase = 2.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Range Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rangeFactor = 1.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Knockback Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double knockbackBase = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Knockback Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double knockbackFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Upward Knockback Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double upwardKnockbackBase = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Upward Knockback Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double upwardKnockbackFactor = 0.14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stun Ticks Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double stunTicksBase = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stun Ticks Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double stunTicksFactor = 24; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stun Amplifier Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double stunAmplifierBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stun Amplifier Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double stunAmplifierFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 220; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 120; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Fall Distance For Crit for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + float minFallDistanceForCrit = 0.08f; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Recent Sprint Window Millis for the Blocking Bulwark Bash adaptation.", impact = "Allows crit bash to trigger shortly after sprint momentum, even if sprint toggles off mid-air.") + long recentSprintWindowMillis = 900L; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Target Hit for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerTargetHit = 8; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingChainArmorer.java b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingChainArmorer.java new file mode 100644 index 000000000..2b70aa66b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingChainArmorer.java @@ -0,0 +1,141 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.blocking; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class BlockingChainArmorer extends SimpleAdaptation { + + public BlockingChainArmorer() { + super("blocking-chainarmorer"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("blocking.chain_armorer.description")); + setDisplayName(Localizer.dLocalize("blocking.chain_armorer.name")); + setIcon(Material.CHAINMAIL_CHESTPLATE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(17774); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-chainarmorer-boots") + .ingredient(new MaterialChar('I', Material.IRON_NUGGET)) + .shapes(List.of( + "I I", + "I I")) + .result(new ItemStack(Material.CHAINMAIL_BOOTS, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-chainarmorer-leggings") + .ingredient(new MaterialChar('I', Material.IRON_NUGGET)) + .shapes(List.of( + "III", + "I I", + "I I")) + .result(new ItemStack(Material.CHAINMAIL_LEGGINGS, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-chainarmorer-chestplate") + .ingredient(new MaterialChar('I', Material.IRON_NUGGET)) + .shapes(List.of( + "I I", + "III", + "III")) + .result(new ItemStack(Material.CHAINMAIL_CHESTPLATE, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-chainarmorer-helmet") + .ingredient(new MaterialChar('I', Material.IRON_NUGGET)) + .shapes(List.of( + "III", + "I I")) + .result(new ItemStack(Material.CHAINMAIL_HELMET, 1)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CHAINMAIL_CHESTPLATE) + .key("challenge_blocking_chain_25") + .title(Localizer.dLocalize("advancement.challenge_blocking_chain_25.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_chain_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_blocking_chain_25", "blocking.chain-armorer.pieces-crafted", 25, 400); + } + + @EventHandler + public void on(CraftItemEvent e) { + if (e.getWhoClicked() instanceof Player p && hasActiveAdaptation(p) && isAdaptationRecipe(e.getRecipe())) { + getPlayer(p).getData().addStat("blocking.chain-armorer.pieces-crafted", 1); + } + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("blocking.chain_armorer.lore1")); + } + + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Chainmail Armor using iron nuggets.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingCounterGuard.java b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingCounterGuard.java new file mode 100644 index 000000000..3aad296a5 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingCounterGuard.java @@ -0,0 +1,197 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.blocking; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +public class BlockingCounterGuard extends SimpleAdaptation { + public BlockingCounterGuard() { + super("blocking-counter-guard"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("blocking.counter_guard.description")); + setDisplayName(Localizer.dLocalize("blocking.counter_guard.name")); + setIcon(Material.IRON_BARS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_blocking_counter_500") + .title(Localizer.dLocalize("advancement.challenge_blocking_counter_500.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_counter_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_blocking_counter_500", "blocking.counter-guard.damage-reflected", 500, 500); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_blocking_counter_max") + .title(Localizer.dLocalize("advancement.challenge_blocking_counter_max.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_counter_max.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getMaxStacks(level) + C.GRAY + " " + Localizer.dLocalize("blocking.counter_guard.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getReflectChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.counter_guard.lore2")); + v.addLore(C.GREEN + "+ " + Form.f(getReflectDamage(level)) + C.GRAY + " " + Localizer.dLocalize("blocking.counter_guard.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Player defender)) { + return; + } + + if (!hasActiveAdaptation(defender) || !hasShield(defender)) { + return; + } + + int level = getActiveLevel(defender); + int stacks = getStorageInt(defender, "counterStacks", 0); + + if (defender.isBlocking()) { + stacks = Math.min(getMaxStacks(level), stacks + 1); + setStorage(defender, "counterStacks", stacks); + } + + if (stacks <= 0 || !M.r(getReflectChance(level))) { + return; + } + + Entity source = e.getDamager(); + if (source instanceof Projectile projectile && projectile.getShooter() instanceof Entity shooter) { + source = shooter; + } + + if (!(source instanceof LivingEntity attacker)) { + return; + } + + if (!canDamageTarget(defender, attacker)) { + return; + } + + // Special achievement: reach max stacks and release + if (stacks >= getMaxStacks(level) && AdaptConfig.get().isAdvancements() && !getPlayer(defender).getData().isGranted("challenge_blocking_counter_max")) { + getPlayer(defender).getAdvancementHandler().grant("challenge_blocking_counter_max"); + } + + double reflected = getReflectDamage(level) + (stacks * getConfig().damagePerStack); + attacker.damage(reflected, defender); + setStorage(defender, "counterStacks", Math.max(0, stacks - getConfig().stackCostOnReflect)); + xp(defender, reflected * getConfig().xpPerReflectedDamage); + getPlayer(defender).getData().addStat("blocking.counter-guard.damage-reflected", reflected); + } + + private boolean hasShield(Player p) { + ItemStack main = p.getInventory().getItemInMainHand(); + ItemStack off = p.getInventory().getItemInOffHand(); + return (isItem(main) && main.getType() == Material.SHIELD) || (isItem(off) && off.getType() == Material.SHIELD); + } + + private double getReflectChance(int level) { + return Math.min(getConfig().maxReflectChance, getConfig().reflectChanceBase + (getLevelPercent(level) * getConfig().reflectChanceFactor)); + } + + private int getMaxStacks(int level) { + return Math.max(1, (int) Math.round(getConfig().baseStacks + (getLevelPercent(level) * getConfig().stackFactor))); + } + + private double getReflectDamage(int level) { + return getConfig().baseReflectDamage + (getLevelPercent(level) * getConfig().reflectDamageFactor); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Blocking builds retaliation stacks that reflect damage back to attackers.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Stacks for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseStacks = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Factor for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double stackFactor = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflect Chance Base for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectChanceBase = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflect Chance Factor for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectChanceFactor = 0.27; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Reflect Chance for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxReflectChance = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Reflect Damage for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseReflectDamage = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflect Damage Factor for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectDamageFactor = 3.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Per Stack for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damagePerStack = 0.28; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Cost On Reflect for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int stackCostOnReflect = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Reflected Damage for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerReflectedDamage = 5.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingHorseArmorer.java b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingHorseArmorer.java new file mode 100644 index 000000000..4cf6f64f1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingHorseArmorer.java @@ -0,0 +1,151 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.blocking; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class BlockingHorseArmorer extends SimpleAdaptation { + + public BlockingHorseArmorer() { + super("blocking-horsearmorer"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("blocking.horse_armorer.description")); + setDisplayName(Localizer.dLocalize("blocking.horse_armorer.name")); + setIcon(Material.GOLDEN_HORSE_ARMOR); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(17774); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-horsearmorerleather") + .ingredient(new MaterialChar('I', Material.LEATHER)) + .ingredient(new MaterialChar('U', Material.SADDLE)) + .shapes(List.of( + "III", + "IUI", + "III")) + .result(new ItemStack(Material.LEATHER_HORSE_ARMOR, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-horsearmoreriron") + .ingredient(new MaterialChar('I', Material.IRON_INGOT)) + .ingredient(new MaterialChar('U', Material.SADDLE)) + .shapes(List.of( + "III", + "IUI", + "III")) + .result(new ItemStack(Material.IRON_HORSE_ARMOR, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-horsearmorergold") + .ingredient(new MaterialChar('I', Material.GOLD_INGOT)) + .ingredient(new MaterialChar('U', Material.SADDLE)) + .shapes(List.of( + "III", + "IUI", + "III")) + .result(new ItemStack(Material.GOLDEN_HORSE_ARMOR, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-horsearmorerdiamond") + .ingredient(new MaterialChar('I', Material.DIAMOND)) + .ingredient(new MaterialChar('U', Material.SADDLE)) + .shapes(List.of( + "III", + "IUI", + "III")) + .result(new ItemStack(Material.DIAMOND_HORSE_ARMOR, 1)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_HORSE_ARMOR) + .key("challenge_blocking_horse_armor_10") + .title(Localizer.dLocalize("advancement.challenge_blocking_horse_armor_10.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_horse_armor_10.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_blocking_horse_armor_10", "blocking.horse-armorer.armor-crafted", 10, 400); + } + + @EventHandler + public void on(CraftItemEvent e) { + if (e.getWhoClicked() instanceof Player p && hasActiveAdaptation(p) && isAdaptationRecipe(e.getRecipe())) { + getPlayer(p).getData().addStat("blocking.horse-armorer.armor-crafted", 1); + } + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("blocking.horse_armorer.lore1")); + v.addLore("XXX"); + v.addLore("XSX"); + v.addLore("XXX"); + + } + + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Horse Armor by surrounding a saddle with material.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingMirrorBlock.java b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingMirrorBlock.java new file mode 100644 index 000000000..9de5de2ef --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingMirrorBlock.java @@ -0,0 +1,266 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.blocking; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.util.Vector; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockingMirrorBlock extends SimpleAdaptation { + private static final String REFLECTED_META = "adapt-mirror-reflected"; + private static final String DAMAGE_FACTOR_META = "adapt-mirror-damage-factor"; + + public BlockingMirrorBlock() { + super("blocking-mirror-block"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("blocking.mirror_block.description")); + setDisplayName(Localizer.dLocalize("blocking.mirror_block.name")); + setIcon(Material.LIGHT_WEIGHTED_PRESSURE_PLATE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1200); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_blocking_mirror_100") + .title(Localizer.dLocalize("advancement.challenge_blocking_mirror_100.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_mirror_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_blocking_mirror_100", "blocking.mirror-block.projectiles-reflected", 100, 500); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_blocking_mirror_3in5") + .title(Localizer.dLocalize("advancement.challenge_blocking_mirror_3in5.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_mirror_3in5.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getReflectChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.mirror_block.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getReflectedDamageFactor(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.mirror_block.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getReflectCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("blocking.mirror_block.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Projectile projectile)) { + return; + } + + applyReflectedDamageModifier(e, projectile); + + if (!(e.getEntity() instanceof Player defender) || !isMirrorReady(defender) || projectile.hasMetadata(REFLECTED_META)) { + return; + } + + int level = getActiveLevel(defender); + long now = System.currentTimeMillis(); + long next = getStorageLong(defender, "mirrorBlockNext", 0L); + if (next > now) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getReflectChance(level)) { + return; + } + + e.setCancelled(true); + reflectProjectile(defender, projectile, level); + setStorage(defender, "mirrorBlockNext", now + getReflectCooldownMillis(level)); + + SoundPlayer sp = SoundPlayer.of(defender.getWorld()); + sp.play(defender.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1f, 1.35f); + sp.play(defender.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_HIT, 0.8f, 0.8f); + if (areParticlesEnabled()) { + defender.spawnParticle(Particle.CRIT, defender.getLocation().add(0, 1, 0), 20, 0.35, 0.3, 0.35, 0.08); + } + xp(defender, getConfig().xpOnReflect); + getPlayer(defender).getData().addStat("blocking.mirror-block.projectiles-reflected", 1); + + // Special achievement: reflect 3 projectiles within 5 seconds + long windowStart = getStorageLong(defender, "mirrorWindowStart", 0L); + int windowCount = getStorageInt(defender, "mirrorWindowCount", 0); + if (now - windowStart > 5000L) { + windowStart = now; + windowCount = 1; + } else { + windowCount++; + } + setStorage(defender, "mirrorWindowStart", windowStart); + setStorage(defender, "mirrorWindowCount", windowCount); + if (windowCount >= 3 && AdaptConfig.get().isAdvancements() && !getPlayer(defender).getData().isGranted("challenge_blocking_mirror_3in5")) { + getPlayer(defender).getAdvancementHandler().grant("challenge_blocking_mirror_3in5"); + } + } + + private void applyReflectedDamageModifier(EntityDamageByEntityEvent e, Projectile projectile) { + if (!(projectile.getShooter() instanceof Player shooter) || !projectile.hasMetadata(DAMAGE_FACTOR_META)) { + return; + } + + if (!canDamageTarget(shooter, e.getEntity())) { + return; + } + + double factor = getMetadataDouble(projectile, DAMAGE_FACTOR_META, 1D); + e.setDamage(e.getDamage() * factor); + } + + private void reflectProjectile(Player defender, Projectile projectile, int level) { + Vector incoming = projectile.getVelocity().clone(); + Vector reflected = incoming.multiply(-Math.max(0.01, getReflectVelocityFactor(level))); + if (reflected.lengthSquared() < getConfig().minReflectedVelocitySquared) { + reflected = defender.getEyeLocation().getDirection().normalize().multiply(getConfig().fallbackReflectedSpeed); + } + + J.teleport(projectile, defender.getEyeLocation().add(defender.getEyeLocation().getDirection().multiply(0.55))); + projectile.setShooter(defender); + projectile.setVelocity(reflected); + projectile.setMetadata(REFLECTED_META, new FixedMetadataValue(Adapt.instance, true)); + projectile.setMetadata(DAMAGE_FACTOR_META, new FixedMetadataValue(Adapt.instance, getReflectedDamageFactor(level))); + } + + private boolean isMirrorReady(Player p) { + return hasActiveAdaptation(p) && p.isBlocking() && hasShield(p); + } + + private boolean hasShield(Player p) { + ItemStack main = p.getInventory().getItemInMainHand(); + ItemStack off = p.getInventory().getItemInOffHand(); + return (isItem(main) && main.getType() == Material.SHIELD) || (isItem(off) && off.getType() == Material.SHIELD); + } + + private double getMetadataDouble(Projectile projectile, String key, double fallback) { + for (MetadataValue value : projectile.getMetadata(key)) { + if (value.getOwningPlugin() == Adapt.instance) { + return value.asDouble(); + } + } + + return fallback; + } + + private double getReflectChance(int level) { + return Math.min(getConfig().maxReflectChance, getConfig().reflectChanceBase + (getLevelPercent(level) * getConfig().reflectChanceFactor)); + } + + private double getReflectedDamageFactor(int level) { + return Math.min(getConfig().maxReflectedDamageFactor, + getConfig().reflectedDamageFactorBase + (getLevelPercent(level) * getConfig().reflectedDamageFactorIncrease)); + } + + private double getReflectVelocityFactor(int level) { + return Math.min(getConfig().maxReflectVelocityFactor, getConfig().reflectVelocityFactorBase + (getLevelPercent(level) * getConfig().reflectVelocityFactor)); + } + + private long getReflectCooldownMillis(int level) { + return Math.max(100L, Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Blocking with a shield can reflect incoming projectiles at reduced force and damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflect Chance Base for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectChanceBase = 0.1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflect Chance Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectChanceFactor = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Reflect Chance for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxReflectChance = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflected Damage Factor Base for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectedDamageFactorBase = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflected Damage Factor Increase for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectedDamageFactorIncrease = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Reflected Damage Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxReflectedDamageFactor = 0.95; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflect Velocity Factor Base for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectVelocityFactorBase = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reflect Velocity Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reflectVelocityFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Reflect Velocity Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxReflectVelocityFactor = 1.1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 1200; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Reflected Velocity Squared for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minReflectedVelocitySquared = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fallback Reflected Speed for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fallbackReflectedSpeed = 0.95; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Reflect for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnReflect = 8; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingMultiArmor.java b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingMultiArmor.java new file mode 100644 index 000000000..2410df8f3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingMultiArmor.java @@ -0,0 +1,270 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.blocking; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.content.item.multiItems.MultiArmor; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class BlockingMultiArmor extends SimpleAdaptation { + private static final MultiArmor multiarmor = new MultiArmor(); + private final Map cooldowns; + + + public BlockingMultiArmor() { + super("blocking-multiarmor"); + registerConfiguration(BlockingMultiArmor.Config.class); + setDisplayName(Localizer.dLocalize("blocking.multi_armor.name")); + setDescription(Localizer.dLocalize("blocking.multi_armor.description")); + setIcon(Material.ELYTRA); + setInterval(20202); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ELYTRA) + .key("challenge_blocking_multi_200") + .title(Localizer.dLocalize("advancement.challenge_blocking_multi_200.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_multi_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_CHESTPLATE) + .key("challenge_blocking_multi_5k") + .title(Localizer.dLocalize("advancement.challenge_blocking_multi_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_multi_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_blocking_multi_200", "blocking.multi-armor.swaps", 200, 400); + registerMilestone("challenge_blocking_multi_5k", "blocking.multi-armor.swaps", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("blocking.multi_armor.lore1")); + v.addLore(C.GRAY + "" + C.GRAY + Localizer.dLocalize("blocking.multi_armor.lore2")); + v.addLore(C.GREEN + Localizer.dLocalize("blocking.multi_armor.lore3")); + v.addLore(C.RED + Localizer.dLocalize("blocking.multi_armor.lore4")); + v.addLore(C.GRAY + Localizer.dLocalize("blocking.multi_armor.lore5")); + v.addLore(C.UNDERLINE + Localizer.dLocalize("blocking.multi_armor.lore6")); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + ItemStack chest = p.getInventory().getChestplate(); + if (chest != null && hasActiveAdaptation(p) && validateArmor(chest)) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null) { + if (cooldown + 3000 > System.currentTimeMillis()) + return; + else cooldowns.remove(p.getUniqueId()); + } + + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + if (p.isOnGround() && !p.isFlying()) { + if (isChestplate(chest)) { + return; + } + J.runEntity(p, () -> p.getInventory().setChestplate(multiarmor.nextChestplate(chest))); + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + spw.play(p.getLocation(), Sound.BLOCK_BEEHIVE_SHEAR, 0.5f, 0.77f); + getPlayer(p).getData().addStat("blocking.multi-armor.swaps", 1); + + } else if (p.getFallDistance() > 4) { + if (isElytra(chest)) { + return; + } + J.runEntity(p, () -> p.getInventory().setChestplate(multiarmor.nextElytra(chest))); + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + spw.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.5f, 0.77f); + getPlayer(p).getData().addStat("blocking.multi-armor.swaps", 1); + } + } + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerDropItemEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + if (!hasActiveAdaptation(p)) { + return; + } + if (p.isSneaking()) { + if (validateArmor(e.getItemDrop().getItemStack())) { + List drops = multiarmor.explode(e.getItemDrop().getItemStack()); + for (ItemStack i : drops) { + Damageable iDmgable = (Damageable) i.getItemMeta(); + if (i.hasItemMeta()) { + ItemMeta im = i.getItemMeta().clone(); + ItemMeta im2 = im; + if (im.hasDisplayName()) { + im2.setDisplayName(im.getDisplayName()); + } + if (im.hasEnchants()) { + Map enchants = im.getEnchants(); + for (Enchantment enchant : enchants.keySet()) { + im2.addEnchant(enchant, enchants.get(enchant), true); + } + } + if (iDmgable != null && iDmgable.hasDamage()) { + ((Damageable) im2).setDamage(iDmgable.getDamage()); + } + im2.setLore(null); + i.setItemMeta(im2); + } + drops.set(drops.indexOf(i), i); + } + + J.runEntity(p, () -> { + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_DEATH, 0.25f, 0.77f); + for (ItemStack i : drops) { + p.getWorld().dropItem(p.getLocation(), i); + } + }); + e.getItemDrop().setItemStack(new ItemStack(Material.AIR)); + } + } + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(InventoryClickEvent e) { + Player player = (Player) e.getWhoClicked(); + int level = getActiveLevel(player); + if (level <= 0) { + return; + } + if (e.getClickedInventory() != null + && e.getClick().equals(ClickType.SHIFT_LEFT) + && e.getClickedInventory().getItem(e.getSlot()) != null + && e.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { + ItemStack cursor = e.getWhoClicked().getItemOnCursor().clone(); + ItemStack clicked = e.getClickedInventory().getItem(e.getSlot()).clone(); + + if (cursor.getType().equals(Material.ELYTRA) || clicked.getType().equals(Material.ELYTRA)) { // One must be an ELYTRA + + if (multiarmor.explode(cursor).size() > 1 || multiarmor.explode(clicked).size() > 1) { + + if (multiarmor.explode(cursor).size() >= getSlots(level) || multiarmor.explode(clicked).size() >= getSlots(level)) { + e.setCancelled(true); + SoundPlayer sp = SoundPlayer.of(player); + sp.play(e.getWhoClicked().getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 1f, 0.77f); + return; + } + } + if (ItemListings.getMultiArmorable().contains(cursor.getType()) && ItemListings.getMultiArmorable().contains(clicked.getType())) { // Chest/Elytra Only + + if (!cursor.getType().isAir() && !clicked.getType().isAir() && multiarmor.supportsItem(cursor) && multiarmor.supportsItem(clicked)) { + e.setCancelled(true); + e.getWhoClicked().setItemOnCursor(new ItemStack(Material.AIR)); + e.getClickedInventory().setItem(e.getSlot(), multiarmor.build(cursor, clicked)); + SoundPlayer spw = SoundPlayer.of(e.getWhoClicked().getWorld()); + spw.play(e.getWhoClicked().getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + } + } + } + } + } + + + private boolean validateArmor(ItemStack item) { + if (item.getItemMeta() != null && item.getItemMeta().getLore() != null) { + for (String lore : item.getItemMeta().getLore()) { + if (lore != null && lore.contains("MultiArmor")) { + return true; + } + } + } + return false; + } + + + private double getSlots(double level) { + return getConfig().startingSlots + level; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Bind Elytras to armor for dynamic merge and swap.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Starting Slots for the Blocking Multi Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int startingSlots = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingSaddlecrafter.java b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingSaddlecrafter.java new file mode 100644 index 000000000..a4dfefda8 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/blocking/BlockingSaddlecrafter.java @@ -0,0 +1,117 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.blocking; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class BlockingSaddlecrafter extends SimpleAdaptation { + + public BlockingSaddlecrafter() { + super("blocking-saddlecrafter"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("blocking.saddle_crafter.description")); + setDisplayName(Localizer.dLocalize("blocking.saddle_crafter.name")); + setIcon(Material.LEATHER_HORSE_ARMOR); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(17774); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shaped() + .key("blocking-saddlecrafter") + .ingredient(new MaterialChar('I', Material.LEATHER)) + .shapes(List.of( + "I I", + "III")) + .result(new ItemStack(Material.SADDLE, 1)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SADDLE) + .key("challenge_blocking_saddle_25") + .title(Localizer.dLocalize("advancement.challenge_blocking_saddle_25.title")) + .description(Localizer.dLocalize("advancement.challenge_blocking_saddle_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_blocking_saddle_25", "blocking.saddlecrafter.saddles-crafted", 25, 400); + } + + @EventHandler + public void on(CraftItemEvent e) { + if (e.getWhoClicked() instanceof Player p && hasActiveAdaptation(p) && isAdaptationRecipe(e.getRecipe())) { + getPlayer(p).getData().addStat("blocking.saddlecrafter.saddles-crafted", 1); + } + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("blocking.saddle_crafter.lore1")); + v.addLore("X-X"); + v.addLore("XXX"); + } + + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft a Saddle using leather.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingAbsorption.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingAbsorption.java new file mode 100644 index 000000000..eaab453d3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingAbsorption.java @@ -0,0 +1,136 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionEffectType; + + +public class BrewingAbsorption extends SimpleAdaptation { + public BrewingAbsorption() { + super("brewing-absorption"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.absorption.description")); + setDisplayName(Localizer.dLocalize("brewing.absorption.name")); + setIcon(Material.QUARTZ); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1333); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-absorption-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.QUARTZ) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.INSTANT_HEAL)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Absorption") + .setColor(Color.GRAY) + .addEffect(PotionEffectType.ABSORPTION, 1200, 1, true, true, true) + .build()) + .build() + ); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-absorption-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.QUARTZ_BLOCK) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.INSTANT_HEAL)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Haste 2") + .setColor(Color.GRAY) + .addEffect(PotionEffectType.ABSORPTION, 600, 2, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_APPLE) + .key("challenge_brewing_absorption_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_absorption_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_absorption_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_absorption_25", "brewing.absorption.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.absorption.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.absorption.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.absorption.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Absorption from Instant Heal and Quartz.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingBlindness.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingBlindness.java new file mode 100644 index 000000000..3dc987832 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingBlindness.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + + +public class BrewingBlindness extends SimpleAdaptation { + public BrewingBlindness() { + super("brewing-blindness"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.blindness.description")); + setDisplayName(Localizer.dLocalize("brewing.blindness.name")); + setIcon(Material.INK_SAC); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1333); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-blindness-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.INK_SAC) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Blindness") + .setColor(Color.OLIVE) + .addEffect(PotionEffectType.BLINDNESS, 600, 1, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-blindness-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.GLOW_INK_SAC) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Blindness 2") + .setColor(Color.OLIVE) + .addEffect(PotionEffectType.BLINDNESS, 300, 3, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.INK_SAC) + .key("challenge_brewing_blindness_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_blindness_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_blindness_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_blindness_25", "brewing.blindness.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.blindness.lore1")); +// v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.blindness.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.blindness.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Blindness from Awkward Potion and Ink Sack.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingDarkness.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingDarkness.java new file mode 100644 index 000000000..69b23b856 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingDarkness.java @@ -0,0 +1,123 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + + +public class BrewingDarkness extends SimpleAdaptation { + public BrewingDarkness() { + super("brewing-darkness"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.darkness.description")); + setDisplayName(Localizer.dLocalize("brewing.darkness.name")); + setIcon(Material.BLACK_CONCRETE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1335); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-darkness") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.BLACK_CONCRETE) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.NIGHT_VISION)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Darkness") + .setColor(Color.BLACK) + .addEffect(PotionEffectType.DARKNESS, 600, 100, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BREWING_STAND) + .key("challenge_brewing_darkness_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_darkness_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_darkness_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_darkness_25", "brewing.darkness.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.darkness.lore1")); + v.addLore(C.GRAY + "- " + Localizer.dLocalize("brewing.darkness.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.darkness.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Darkness from NightVision Potion and Black Concrete.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingDecay.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingDecay.java new file mode 100644 index 000000000..a4c7de080 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingDecay.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + + +public class BrewingDecay extends SimpleAdaptation { + public BrewingDecay() { + super("brewing-decay"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.decay.description")); + setDisplayName(Localizer.dLocalize("brewing.decay.name")); + setIcon(Material.WITHER_ROSE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1334); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-decay-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.POISONOUS_POTATO) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Decay") + .setColor(Color.MAROON) + .addEffect(PotionEffectType.WITHER, 300, 1, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-decay-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.CRIMSON_ROOTS) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Decay 2") + .setColor(Color.MAROON) + .addEffect(PotionEffectType.WITHER, 150, 2, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_ROSE) + .key("challenge_brewing_decay_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_decay_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_decay_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_decay_25", "brewing.decay.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.decay.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.decay.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.decay.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Wither from Weakness Potion and Poisonous Potato.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingFatigue.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingFatigue.java new file mode 100644 index 000000000..7870714c3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingFatigue.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionType; + + +public class BrewingFatigue extends SimpleAdaptation { + public BrewingFatigue() { + super("brewing-fatigue"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.fatigue.description")); + setDisplayName(Localizer.dLocalize("brewing.fatigue.name")); + setIcon(Material.SLIME_BALL); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1332); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-fatigue-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.SLIME_BALL) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Fatigue") + .setColor(Color.fromRGB(0, 66, 0)) + .addEffect(PotionEffectTypes.SLOW_DIGGING, 1200, 1, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-fatigue-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.SLIME_BLOCK) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Fatigue 2") + .setColor(Color.fromRGB(0, 66, 0)) + .addEffect(PotionEffectTypes.SLOW_DIGGING, 600, 2, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BREWING_STAND) + .key("challenge_brewing_fatigue_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_fatigue_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_fatigue_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_fatigue_25", "brewing.fatigue.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.fatigue.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.fatigue.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.fatigue.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Fatigue from Weakness Potion and Slime.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHaste.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHaste.java new file mode 100644 index 000000000..580edadcb --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHaste.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.adapt.util.reflect.registries.PotionTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; + + +public class BrewingHaste extends SimpleAdaptation { + public BrewingHaste() { + super("brewing-haste"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.haste.description")); + setDisplayName(Localizer.dLocalize("brewing.haste.name")); + setIcon(Material.AMETHYST_SHARD); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1334); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-haste-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.AMETHYST_SHARD) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.SPEED)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Haste") + .setColor(Color.YELLOW) + .addEffect(PotionEffectTypes.FAST_DIGGING, 1200, 1, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-haste-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.AMETHYST_BLOCK) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.SPEED)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Haste 2") + .setColor(Color.YELLOW) + .addEffect(PotionEffectTypes.FAST_DIGGING, 600, 2, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BREWING_STAND) + .key("challenge_brewing_haste_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_haste_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_haste_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_haste_25", "brewing.haste.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.haste.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.haste.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.haste.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Haste from Speed Potion and Amethyst.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHealthBoost.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHealthBoost.java new file mode 100644 index 000000000..43c079a6c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHealthBoost.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionEffectType; + + +public class BrewingHealthBoost extends SimpleAdaptation { + public BrewingHealthBoost() { + super("brewing-healthboost"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.health_boost.description")); + setDisplayName(Localizer.dLocalize("brewing.health_boost.name")); + setIcon(Material.ENCHANTED_GOLDEN_APPLE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1330); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-healthboost") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.GOLDEN_APPLE) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.INSTANT_HEAL)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Life") + .setColor(Color.RED) + .addEffect(PotionEffectType.HEALTH_BOOST, 1200, 1, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-healthboost") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.ENCHANTED_GOLDEN_APPLE) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.INSTANT_HEAL)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Life") + .setColor(Color.RED) + .addEffect(PotionEffectType.HEALTH_BOOST, 1200, 2, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLISTERING_MELON_SLICE) + .key("challenge_brewing_health_boost_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_health_boost_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_health_boost_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_health_boost_25", "brewing.health-boost.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.health_boost.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.health_boost.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.health-boost.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Health Boost from Instant Heal and Golden Apple.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHunger.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHunger.java new file mode 100644 index 000000000..83ecdb3d1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingHunger.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + + +public class BrewingHunger extends SimpleAdaptation { + public BrewingHunger() { + super("brewing-hunger"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.hunger.description")); + setDisplayName(Localizer.dLocalize("brewing.hunger.name")); + setIcon(Material.ROTTEN_FLESH); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1331); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-hunger-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.ROTTEN_FLESH) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Hunger") + .setColor(Color.GREEN) + .addEffect(PotionEffectType.HUNGER, 1200, 1, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-hunger-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.ROTTEN_FLESH) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Hunger 2") + .setColor(Color.GREEN) + .addEffect(PotionEffectType.HUNGER, 600, 3, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ROTTEN_FLESH) + .key("challenge_brewing_hunger_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_hunger_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_hunger_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_hunger_25", "brewing.hunger.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.hunger.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.hunger.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.hunger.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Hunger from Awkward Potion and Rotten Flesh.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingLingering.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingLingering.java new file mode 100644 index 000000000..b1e200939 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingLingering.java @@ -0,0 +1,372 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.ItemFlags; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.function.Function3; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; + +public class BrewingLingering extends SimpleAdaptation { + private static final Function getColor; + private static final Function> getEffectAttributes; + private static final Function3 getAttributeModifierAmount; + private static final DecimalFormat ATTRIBUTE_MODIFIER_FORMAT = new DecimalFormat("#.##"); + + static { + java.lang.invoke.MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle getCategory; + try { + java.lang.reflect.Method method = PotionEffectType.class.getDeclaredMethod("getCategory"); + getCategory = lookup.unreflect(method); + } catch (Throwable ignored) { + getCategory = null; + } + + MethodHandle modifiersHandle; + MethodHandle amountHandle; + try { + modifiersHandle = lookup.findVirtual(PotionEffectType.class, "getEffectAttributes", MethodType.methodType(Map.class)); + amountHandle = lookup.findVirtual(PotionEffectType.class, "getAttributeModifierAmount", MethodType.methodType(double.class, Attribute.class, int.class)); + } catch (Throwable ignored) { + Adapt.verbose("Failed to find attributes for potion effect type"); + modifiersHandle = null; + amountHandle = null; + } + + if (getCategory != null) { + MethodHandle handle = getCategory; + getColor = type -> { + try { + return ((Enum) handle.invoke(type)).ordinal() == 1 ? NamedTextColor.RED : NamedTextColor.BLUE; + } catch (Throwable err) { + throw new RuntimeException(err); + } + }; + } else getColor = $ -> NamedTextColor.BLUE; + + if (modifiersHandle != null) { + MethodHandle handle = modifiersHandle; + getEffectAttributes = type -> { + try { + return (Map) handle.invoke(type); + } catch (Throwable err) { + throw new RuntimeException(err); + } + }; + } else getEffectAttributes = $ -> Map.of(); + + if (amountHandle != null) { + MethodHandle handle = amountHandle; + getAttributeModifierAmount = (type, attribute, level) -> { + try { + return (double) handle.invoke(type, attribute, level); + } catch (Throwable err) { + throw new RuntimeException(err); + } + }; + } else getAttributeModifierAmount = ($, $$, $$$) -> 0d; + + ATTRIBUTE_MODIFIER_FORMAT.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); + } + + public BrewingLingering() { + super("brewing-lingering"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.lingering.description")); + setDisplayName(Localizer.dLocalize("brewing.lingering.name")); + setIcon(Material.DRAGON_BREATH); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(4788); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LINGERING_POTION) + .key("challenge_brewing_lingering_200") + .title(Localizer.dLocalize("advancement.challenge_brewing_lingering_200.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_lingering_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DRAGON_BREATH) + .key("challenge_brewing_lingering_5k") + .title(Localizer.dLocalize("advancement.challenge_brewing_lingering_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_lingering_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_brewing_lingering_200", "brewing.lingering.potions-extended", 200, 300); + registerMilestone("challenge_brewing_lingering_5k", "brewing.lingering.potions-extended", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.duration((long) getDurationBoost(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("brewing.lingering.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getPercentBoost(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("brewing.lingering.lore2")); + } + + public double getDurationBoost(double factor) { + return (getConfig().durationBoostFactorTicks * factor) + getConfig().baseDurationBoostTicks; + } + + public double getPercentBoost(double factor) { + return 1 + ((factor * factor * getConfig().durationMultiplierFactor) + getConfig().baseDurationMultiplier); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BrewEvent e) { + if (!e.getBlock().getType().equals(Material.BREWING_STAND)) { + return; + } + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + + if (owner == null) { + Adapt.verbose("No Owner"); + return; + } + + PlayerData data = null; + java.util.List results = e.getResults(); + boolean ef = false; + for (int i = 0; i < results.size(); i++) { + ItemStack is = results.get(i); + + if (is == null || is.getItemMeta() == null || !(is.getItemMeta() instanceof PotionMeta p)) + continue; + + data = data == null ? getServer().peekData(owner.getOwner()) : data; + + if (data.getSkillLines().containsKey(getSkill().getName()) && data.getSkillLine(getSkill().getName()).getAdaptations().containsKey(getName())) { + PlayerAdaptation a = data.getSkillLine(getSkill().getName()).getAdaptations().get(getName()); + + if (a.getLevel() > 0) { + double factor = getLevelPercent(a.getLevel()); + boolean enhanced = enhance(factor, is, p); + if (enhanced) { + data.addStat("brewing.lingering.potions-extended", 1); + } + ef = enhanced || ef; + results.set(i, is); + } + } + } + + if (ef) { + SoundPlayer spw = SoundPlayer.of(e.getBlock().getWorld()); + spw.play(e.getBlock().getLocation(), Sound.BLOCK_BREWING_STAND_BREW, 1f, 0.75f); + spw.play(e.getBlock().getLocation(), Sound.BLOCK_BREWING_STAND_BREW, 1f, 1.75f); + } + } + + private boolean enhance(double factor, ItemStack is, PotionMeta p) { + java.util.List effects = p.getBasePotionType().getPotionEffects(); + if (effects.stream() + .map(PotionEffect::getType) + .allMatch(PotionEffectType::isInstant)) + return false; + + p.clearCustomEffects(); + for (final PotionEffect effect : effects) { + if (effect.getType().isInstant()) { + p.addCustomEffect(effect, true); + continue; + } + + p.addCustomEffect(new PotionEffect( + effect.getType(), + (int) (getDurationBoost(factor) + (effect.getDuration() * getPercentBoost(factor))), + effect.getAmplifier() + ), true); + } + + p.addItemFlags(ItemFlags.HIDE_POTION_EFFECTS); + is.setItemMeta(p); + + if (getConfig().useCustomLore) { + KList lore = new KList<>(); + KList modifiers = new KList<>(); + for (org.bukkit.potion.PotionEffect effect : p.getCustomEffects()) { + org.bukkit.potion.PotionEffectType type = effect.getType(); + org.bukkit.NamespacedKey key = type.getKey(); + net.kyori.adventure.text.TranslatableComponent name = Component.translatable("effect." + key.getNamespace() + "." + key.getKey()); + if (effect.getAmplifier() > 0) { + name = Component.translatable("potion.withAmplifier", name, + Component.translatable("potion.potency." + effect.getAmplifier())); + } + + if (effect.getDuration() > 20) { + name = Component.translatable("potion.withDuration", name, formatDuration(effect)); + } + + lore.add(name.color(getColor.apply(type))); + getEffectAttributes.apply(type) + .entrySet() + .stream() + .map(Modifier::new) + .map(m -> m.adjust(type, effect.getAmplifier())) + .filter(m -> m.amount != 0) + .forEach(modifiers::add); + } + + if (!modifiers.isEmpty()) { + lore.add(Component.empty()); + lore.add(Component.translatable("potion.whenDrank").color(NamedTextColor.DARK_PURPLE)); + + for (Modifier modifier : modifiers) { + double amount = modifier.amount; + net.kyori.adventure.text.TextComponent formatted = Component.text(ATTRIBUTE_MODIFIER_FORMAT.format(modifier.operation == AttributeModifier.Operation.ADD_NUMBER ? amount : amount * 100d)); + net.kyori.adventure.text.TranslatableComponent name = Component.translatable("attribute.name." + modifier.attribute.getKey().getKey()); + + if (amount > 0) { + lore.add(Component.translatable("attribute.modifier.plus." + modifier.operation.ordinal(), formatted, name) + .color(NamedTextColor.BLUE)); + } else { + lore.add(Component.translatable("attribute.modifier.take." + modifier.operation.ordinal(), formatted, name) + .color(NamedTextColor.RED)); + } + } + } + lore.replaceAll(c -> c.decoration(TextDecoration.ITALIC, false)); + + Adapt.platform.editItem(is) + .lore(lore) + .build(); + } + + return true; + } + + private Component formatDuration(PotionEffect effect) { + if (effect.isInfinite()) { + return Component.translatable("effect.duration.infinite"); + } else { + int seconds = effect.getDuration() / 20; + int minutes = seconds / 60; + seconds %= 60; + int hours = minutes / 60; + minutes %= 60; + return Component.text(hours > 0 ? + "%02d:%02d:%02d".formatted(hours, minutes, seconds) : + "%02d:%02d".formatted(minutes, seconds)); + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brewed potions last longer.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Duration Boost Ticks for the Brewing Lingering adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseDurationBoostTicks = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Boost Factor Ticks for the Brewing Lingering adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durationBoostFactorTicks = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Multiplier Factor for the Brewing Lingering adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durationMultiplierFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Duration Multiplier for the Brewing Lingering adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseDurationMultiplier = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Use Custom Lore for the Brewing Lingering adaptation.", impact = "True enables this behavior and false disables it.") + boolean useCustomLore = true; + } + + private record Modifier(Attribute attribute, + AttributeModifier.Operation operation, + double amount) { + private Modifier(Map.Entry entry) { + this(entry.getKey(), entry.getValue()); + } + + private Modifier(Attribute attribute, AttributeModifier modifier) { + this(attribute, modifier.getOperation(), modifier.getAmount()); + } + + private Modifier adjust(PotionEffectType type, int amplifier) { + return new Modifier( + attribute, + operation, + getAttributeModifierAmount.apply(type, attribute, amplifier) + ); + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingNausea.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingNausea.java new file mode 100644 index 000000000..b3bc449f8 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingNausea.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionType; + + +public class BrewingNausea extends SimpleAdaptation { + public BrewingNausea() { + super("brewing-nausea"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.nausea.description")); + setDisplayName(Localizer.dLocalize("brewing.nausea.name")); + setIcon(Material.CRIMSON_FUNGUS); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1333); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-nausea-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.BROWN_MUSHROOM) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Nausea") + .setColor(Color.LIME) + .addEffect(PotionEffectTypes.CONFUSION, 600, 1, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-nausea-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.CRIMSON_FUNGUS) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Nausea 2") + .setColor(Color.LIME) + .addEffect(PotionEffectTypes.CONFUSION, 300, 2, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.POISONOUS_POTATO) + .key("challenge_brewing_nausea_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_nausea_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_nausea_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_nausea_25", "brewing.nausea.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.nausea.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.nausea.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.nausea.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Nausea from Awkward Potion and Mushroom.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingResistance.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingResistance.java new file mode 100644 index 000000000..96dab1c8d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingResistance.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + + +public class BrewingResistance extends SimpleAdaptation { + public BrewingResistance() { + super("brewing-resistance"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.resistance.description")); + setDisplayName(Localizer.dLocalize("brewing.resistance.name")); + setIcon(Material.IRON_BLOCK); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1333); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-resistance-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.IRON_INGOT) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Resistance") + .setColor(Color.WHITE) + .addEffect(PotionEffectType.RESISTANCE, 1200, 1, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-resistance-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.IRON_BLOCK) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Resistance 2") + .setColor(Color.WHITE) + .addEffect(PotionEffectType.RESISTANCE, 600, 2, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_CHESTPLATE) + .key("challenge_brewing_resistance_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_resistance_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_resistance_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_resistance_25", "brewing.resistance.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.resistance.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.resistance.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.resistance.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Resistance from Awkward Potion and Iron.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingSaturation.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingSaturation.java new file mode 100644 index 000000000..ad1fefaff --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingSaturation.java @@ -0,0 +1,135 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.potion.BrewingRecipe; +import art.arcane.adapt.api.potion.PotionBuilder; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.potion.PotionEffectType; + + +public class BrewingSaturation extends SimpleAdaptation { + public BrewingSaturation() { + super("brewing-saturation"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.saturation.description")); + setDisplayName(Localizer.dLocalize("brewing.saturation.name")); + setIcon(Material.BAKED_POTATO); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1334); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-saturation-1") + .brewingTime(320) + .fuelCost(16) + .ingredient(Material.BAKED_POTATO) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.REGEN)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Saturation") + .setColor(Color.ORANGE) + .addEffect(PotionEffectType.SATURATION, 1, 4, true, true, true) + .build()) + .build()); + registerBrewingRecipe(BrewingRecipe.builder() + .id("brewing-saturation-2") + .brewingTime(320) + .fuelCost(32) + .ingredient(Material.HAY_BLOCK) + .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.REGEN)) + .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) + .setName("Bottled Saturation 2") + .setColor(Color.ORANGE) + .addEffect(PotionEffectType.SATURATION, 1, 8, true, true, true) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_CARROT) + .key("challenge_brewing_saturation_25") + .title(Localizer.dLocalize("advancement.challenge_brewing_saturation_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_saturation_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_brewing_saturation_25", "brewing.saturation.potions-brewed", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.saturation.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.saturation.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BrewEvent e) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.saturation.potions-brewed", 1); + } + } + + @Override + public void onTick() { + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brew a Potion of Saturation from Regen Potion and Baked Potato.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingSuperHeated.java b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingSuperHeated.java new file mode 100644 index 000000000..3fa602be9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/brewing/BrewingSuperHeated.java @@ -0,0 +1,268 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.brewing; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import art.arcane.volmlib.util.math.RNG; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.BrewingStand; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.BrewEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.inventory.InventoryType; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class BrewingSuperHeated extends SimpleAdaptation { + + private static final int MAX_CHECKS_BEFORE_REMOVE = 20; + private final Map activeStands = new ConcurrentHashMap<>(); + + public BrewingSuperHeated() { + super("brewing-super-heated"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("brewing.super_heated.description")); + setDisplayName(Localizer.dLocalize("brewing.super_heated.name")); + setIcon(Material.LAVA_BUCKET); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(253); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BLAZE_POWDER) + .key("challenge_brewing_super_heated_100") + .title(Localizer.dLocalize("advancement.challenge_brewing_super_heated_100.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_super_heated_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.MAGMA_CREAM) + .key("challenge_brewing_super_heated_2500") + .title(Localizer.dLocalize("advancement.challenge_brewing_super_heated_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_brewing_super_heated_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_brewing_super_heated_100", "brewing.super-heated.brews-accelerated", 100, 300); + registerMilestone("challenge_brewing_super_heated_2500", "brewing.super-heated.brews-accelerated", 2500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getFireBoost(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("brewing.super_heated.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getLavaBoost(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("brewing.super_heated.lore2")); + } + + public double getLavaBoost(double factor) { + return (getConfig().lavaMultiplier) * (getConfig().multiplierFactor * factor); + } + + public double getFireBoost(double factor) { + return (getConfig().fireMultiplier) * (getConfig().multiplierFactor * factor); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(InventoryMoveItemEvent e) { + if (!e.getDestination().getType().equals(InventoryType.BREWING) || e.getDestination().getLocation() == null) { + return; + } + + activeStands.put(e.getDestination().getLocation().getBlock(), MAX_CHECKS_BEFORE_REMOVE); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BrewEvent e) { + if (activeStands.containsKey(e.getBlock())) { + BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); + if (owner != null) { + getServer().peekData(owner.getOwner()).addStat("brewing.super-heated.brews-accelerated", 1); + } + } + if (((BrewingStand) e.getBlock().getState()).getBrewingTime() > 0) { + activeStands.put(e.getBlock(), MAX_CHECKS_BEFORE_REMOVE); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(InventoryClickEvent e) { + if (e.getClickedInventory() == null) { + return; + } + if (e.getView().getTopInventory().getType().equals(InventoryType.BREWING)) { + activeStands.put(e.getView().getTopInventory().getLocation().getBlock(), MAX_CHECKS_BEFORE_REMOVE); + } + } + + + @Override + public void onTick() { + if (activeStands.isEmpty()) { + return; + } + + for (Block block : activeStands.keySet()) { + if (block == null) { + continue; + } + + J.runAt(block.getLocation(), () -> tickStand(block)); + } + } + + private void tickStand(Block block) { + BlockState state = block.getState(); + if (!(state instanceof BrewingStand brewingStand)) { + activeStands.remove(block); + return; + } + + if (brewingStand.getBrewingTime() <= 0) { + BrewingStand current = (BrewingStand) block.getState(); + if (current.getBrewingTime() <= 0) { + Integer remainingChecks = activeStands.get(block); + if (remainingChecks == null) { + return; + } + + if (remainingChecks <= 0) { + activeStands.remove(block); + } else { + activeStands.put(block, remainingChecks - 1); + } + } + return; + } + + BrewingStandOwner owner = WorldData.of(brewingStand.getWorld()).get(block, BrewingStandOwner.class); + if (owner == null) { + activeStands.remove(block); + return; + } + + PlayerData playerData = getServer().peekData(owner.getOwner()); + if (playerData == null) { + activeStands.remove(block); + return; + } + + PlayerSkillLine line = playerData.getSkillLineNullable(getSkill().getName()); + PlayerAdaptation adaptation = line != null ? line.getAdaptation(getName()) : null; + if (adaptation == null || adaptation.getLevel() <= 0) { + activeStands.remove(block); + return; + } + + updateHeat(brewingStand, getLevelPercent(adaptation.getLevel())); + } + + private void updateHeat(BrewingStand b, double factor) { + double l = 0; + double f = 0; + + switch (b.getBlock().getRelative(BlockFace.DOWN).getType()) { + case LAVA -> l = l + 1; + case FIRE -> f = f + 1; + } + switch (b.getBlock().getRelative(BlockFace.NORTH).getType()) { + case LAVA -> l = l + 1; + case FIRE -> f = f + 1; + } + switch (b.getBlock().getRelative(BlockFace.SOUTH).getType()) { + case LAVA -> l = l + 1; + case FIRE -> f = f + 1; + } + switch (b.getBlock().getRelative(BlockFace.EAST).getType()) { + case LAVA -> l = l + 1; + case FIRE -> f = f + 1; + } + switch (b.getBlock().getRelative(BlockFace.WEST).getType()) { + case LAVA -> l = l + 1; + case FIRE -> f = f + 1; + } + + double pct = (getFireBoost(factor) * f) + (getLavaBoost(factor) * l) + 1; + int warp = (int) ((getInterval() / 50D) * pct); + b.setBrewingTime(Math.max(1, b.getBrewingTime() - warp)); + b.update(); + + if (M.r(1D / (333D / getInterval()))) { + SoundPlayer spw = SoundPlayer.of(b.getBlock().getWorld()); + spw.play(b.getBlock().getLocation(), Sound.BLOCK_FIRE_AMBIENT, 1f, 1f + RNG.r.f(0.3f, 0.6f)); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Brewing stands work faster when surrounded by fire or lava.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Multiplier Factor for the Brewing Super Heated adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double multiplierFactor = 1.33; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fire Multiplier for the Brewing Super Heated adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fireMultiplier = 0.14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Lava Multiplier for the Brewing Super Heated adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lavaMultiplier = 0.69; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosAberrantTouch.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosAberrantTouch.java new file mode 100644 index 000000000..f8d561c83 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosAberrantTouch.java @@ -0,0 +1,226 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class ChronosAberrantTouch extends SimpleAdaptation { + private final Map cooldowns; + private final Map targetStacks; + + public ChronosAberrantTouch() { + super("chronos-aberrant-touch"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.aberrant_touch.description")); + setDisplayName(Localizer.dLocalize("chronos.aberrant_touch.name")); + setIcon(Material.SPIDER_EYE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + targetStacks = new java.util.concurrent.ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CLOCK) + .key("challenge_chronos_aberrant_500") + .title(Localizer.dLocalize("advancement.challenge_chronos_aberrant_500.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_aberrant_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CLOCK) + .key("challenge_chronos_aberrant_frozen") + .title(Localizer.dLocalize("advancement.challenge_chronos_aberrant_frozen.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_aberrant_frozen.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_aberrant_500", "chronos.aberrant-touch.slowness-stacks-applied", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("chronos.aberrant_touch.lore1")); + v.addLore(C.YELLOW + "+ " + getPvEDurationCapTicks(level) / 20D + "s " + Localizer.dLocalize("chronos.aberrant_touch.lore2")); + v.addLore(C.RED + "* " + getConfig().playerAmplifierCap + " " + Localizer.dLocalize("chronos.aberrant_touch.lore3")); + v.addLore(C.AQUA + "* " + getConfig().rootAtStacks + " stacks roots for " + (getConfig().rootDurationTicks / 20D) + "s"); + } + + private int getPvEAmplifierCap(int level) { + return Math.max(0, Math.min(getConfig().entityAmplifierCap, level)); + } + + private int getPvEDurationCapTicks(int level) { + return getConfig().entityDurationCapTicks + (level * getConfig().entityDurationCapPerLevelTicks); + } + + private int getDurationAddedTicks(int level) { + return getConfig().durationAddTicks + (level * getConfig().durationPerLevelTicks); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player attacker)) { + return; + } + + long now = System.currentTimeMillis(); + long cooldownUntil = cooldowns.getOrDefault(attacker.getUniqueId(), 0L); + if (cooldownUntil > now) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.MeleeContext combat = resolveMeleeContext(e); + if (combat == null) { + return; + } + + attacker = combat.attacker(); + LivingEntity target = combat.target(); + if (!getPlayer(attacker).consumeFood(getConfig().hungerCost, getConfig().minimumFoodLevel)) { + return; + } + + int level = combat.level(); + int amplifierCap = target instanceof Player ? getConfig().playerAmplifierCap : getPvEAmplifierCap(level); + int durationCap = target instanceof Player ? getConfig().playerDurationCapTicks : getPvEDurationCapTicks(level); + + PotionEffect existing = target.getPotionEffect(PotionEffectType.SLOWNESS); + int currentAmplifier = existing == null ? -1 : existing.getAmplifier(); + int currentDuration = existing == null ? 0 : existing.getDuration(); + + int newAmplifier = Math.min(amplifierCap, currentAmplifier + 1); + int newDuration = Math.min(durationCap, currentDuration + getDurationAddedTicks(level)); + + target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, Math.max(20, newDuration), Math.max(0, newAmplifier), true, true, true), true); + getPlayer(attacker).getData().addStat("chronos.aberrant-touch.slowness-stacks-applied", 1); + + StackState state = targetStacks.getOrDefault(target.getUniqueId(), new StackState(0, 0L)); + int stacks = (now - state.lastHitMillis() > getConfig().stackResetMillis) ? 1 : state.stacks() + 1; + if (stacks >= getConfig().rootAtStacks) { + target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getConfig().rootDurationTicks, getConfig().rootAmplifier, true, true, true), true); + target.setVelocity(new Vector()); + stacks = 0; + if (AdaptConfig.get().isAdvancements() && !getPlayer(attacker).getData().isGranted("challenge_chronos_aberrant_frozen")) { + getPlayer(attacker).getAdvancementHandler().grant("challenge_chronos_aberrant_frozen"); + } + } + targetStacks.put(target.getUniqueId(), new StackState(stacks, now)); + + if (getConfig().playClockSounds) { + ChronosSoundFX.playTouchProc(attacker, target.getLocation()); + } + xp(attacker, attacker.getLocation(), getConfig().xpPerProc + (getConfig().xpPerLevel * level)); + cooldowns.put(attacker.getUniqueId(), now + getConfig().cooldownMillis); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + targetStacks.entrySet().removeIf(entry -> now - entry.getValue().lastHitMillis() > getConfig().stackResetMillis); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Melee attacks apply stacking slowness at the cost of hunger, with PvP caps.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Aberrant Touch adaptation.", impact = "True enables this behavior and false disables it.") + boolean playClockSounds = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.38; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Add Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int durationAddTicks = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Per Level Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int durationPerLevelTicks = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Player Duration Cap Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int playerDurationCapTicks = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Player Amplifier Cap for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int playerAmplifierCap = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Entity Duration Cap Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int entityDurationCapTicks = 120; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Entity Duration Cap Per Level Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int entityDurationCapPerLevelTicks = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Entity Amplifier Cap for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int entityAmplifierCap = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hungerCost = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Food Level for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int minimumFoodLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Root At Stacks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int rootAtStacks = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Root Duration Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int rootDurationTicks = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Root Amplifier for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int rootAmplifier = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Reset Millis for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long stackResetMillis = 2500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownMillis = 250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Proc for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerProc = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Level for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerLevel = 1.25; + } + + private record StackState(int stacks, long lastHitMillis) { + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosAccelerate.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosAccelerate.java new file mode 100644 index 000000000..b975e146e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosAccelerate.java @@ -0,0 +1,270 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BrewingStand; +import org.bukkit.block.Furnace; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.concurrent.ThreadLocalRandom; + +public class ChronosAccelerate extends SimpleAdaptation { + public ChronosAccelerate() { + super("chronos-accelerate"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.accelerate.description")); + setDisplayName(Localizer.dLocalize("chronos.accelerate.name")); + setIcon(Material.SUGAR); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(getConfig().pulseIntervalMillis); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SUGAR) + .key("challenge_chronos_accelerate_1k") + .title(Localizer.dLocalize("advancement.challenge_chronos_accelerate_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_accelerate_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_accelerate_1k", "chronos.accelerate.blocks-accelerated", 1000, 600); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + " " + Localizer.dLocalize("chronos.accelerate.lore1")); + v.addLore(C.YELLOW + "+ " + Math.round(getGrowChance(level) * 100D) + "% " + Localizer.dLocalize("chronos.accelerate.lore2")); + v.addLore(C.GRAY + "* " + Math.round(getCookBoostFraction(level) * 100D) + "% " + Localizer.dLocalize("chronos.accelerate.lore3")); + } + + private double getRadius(int level) { + return getConfig().baseRadius + (Math.max(1, level) * getConfig().radiusPerLevel); + } + + private double getGrowChance(int level) { + return Math.max(0D, Math.min(1D, getConfig().baseGrowChance + (Math.max(1, level) * getConfig().growChancePerLevel))); + } + + private int getSamples(int level) { + return Math.min(getConfig().maxSamplesPerPulse, getConfig().baseSamplesPerPulse + level); + } + + private double getCookBoostFraction(int level) { + return Math.min(getConfig().maxCookBoostFraction, + getConfig().baseCookBoostFraction + (Math.max(1, level) * getConfig().cookBoostFractionPerLevel)); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + + int level = getActiveLevel(p); + if (level <= 0) { + continue; + } + + Location center = p.getLocation().clone(); + Runnable task = () -> accelerateAround(p, center, level); + if (J.isFoliaThreading()) { + J.runAt(center, task); + } else { + task.run(); + } + } + } + + private void accelerateAround(Player p, Location center, int level) { + World world = center.getWorld(); + if (world == null) { + return; + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + int radius = Math.max(1, (int) Math.round(getRadius(level))); + int samples = getSamples(level); + double growChance = getGrowChance(level); + int accelerated = 0; + + for (int i = 0; i < samples; i++) { + int x = center.getBlockX() + random.nextInt(-radius, radius + 1); + int y = center.getBlockY() + random.nextInt(-2, 3); + int z = center.getBlockZ() + random.nextInt(-radius, radius + 1); + Block block = world.getBlockAt(x, y, z); + Material type = block.getType(); + if (type.isAir()) { + continue; + } + + if (type == Material.FURNACE || type == Material.BLAST_FURNACE || type == Material.SMOKER) { + if (canInteract(p, block.getLocation()) && block.getState() instanceof Furnace furnace && accelerateFurnace(furnace, level)) { + accelerated++; + spawnAccelerationParticle(world, x, y, z); + } + continue; + } + + if (type == Material.BREWING_STAND) { + if (canInteract(p, block.getLocation()) && block.getState() instanceof BrewingStand stand && accelerateBrewingStand(stand, level)) { + accelerated++; + spawnAccelerationParticle(world, x, y, z); + } + continue; + } + + BlockData data = block.getBlockData(); + if (data instanceof Ageable ageable + && ageable.getAge() < ageable.getMaximumAge() + && random.nextDouble() < growChance + && canInteract(p, block.getLocation())) { + ageable.setAge(ageable.getAge() + 1); + block.setBlockData(data, true); + accelerated++; + spawnAccelerationParticle(world, x, y, z); + } + } + + if (accelerated > 0) { + getPlayer(p).getData().addStat("chronos.accelerate.blocks-accelerated", accelerated); + xpSilent(p, accelerated * getConfig().xpPerAcceleratedBlock, "chronos:accelerate"); + } + } + + private boolean accelerateFurnace(Furnace furnace, int level) { + if (furnace.getBurnTime() <= 0) { + return false; + } + + ItemStack smelting = furnace.getInventory().getSmelting(); + if (smelting == null || smelting.getType().isAir()) { + return false; + } + + int total = furnace.getCookTimeTotal(); + int current = furnace.getCookTime(); + if (total <= 0 || current >= total - 1) { + return false; + } + + int bonus = (int) Math.round(total * getCookBoostFraction(level)); + if (bonus <= 0) { + return false; + } + + furnace.setCookTime((short) Math.min(total - 1, current + bonus)); + furnace.update(true, true); + return true; + } + + private boolean accelerateBrewingStand(BrewingStand stand, int level) { + int brewingTime = stand.getBrewingTime(); + if (brewingTime <= 1) { + return false; + } + + int bonus = (int) Math.round(400D * getCookBoostFraction(level)); + if (bonus <= 0) { + return false; + } + + stand.setBrewingTime(Math.max(1, brewingTime - bonus)); + stand.update(true, true); + return true; + } + + private void spawnAccelerationParticle(World world, int x, int y, int z) { + if (areParticlesEnabled()) { + world.spawnParticle(Particle.HAPPY_VILLAGER, x + 0.5, y + 0.5, z + 0.5, 2, 0.2, 0.2, 0.2, 0); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Passively accelerate time around you, occasionally growing nearby crops and speeding furnaces and brewing stands.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds between acceleration pulses.", impact = "Lower values pulse more often at more server cost.") + long pulseIntervalMillis = 3000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base aura radius in blocks.", impact = "Higher values sample blocks from a wider area.") + double baseRadius = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra aura radius per adaptation level.", impact = "Higher values make leveling widen the aura faster.") + double radiusPerLevel = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base number of random blocks sampled per pulse.", impact = "Higher values accelerate more blocks at more server cost.") + int baseSamplesPerPulse = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on blocks sampled per pulse.", impact = "Higher values raise the per pulse work ceiling.") + int maxSamplesPerPulse = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base chance for a sampled crop to advance one growth stage.", impact = "Higher values grow crops faster.") + double baseGrowChance = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra crop growth chance per adaptation level.", impact = "Higher values make leveling grow crops faster.") + double growChancePerLevel = 0.06; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base fraction of total cook or brew time fast-forwarded per pulse hit.", impact = "Higher values complete cooks and brews in fewer pulses.") + double baseCookBoostFraction = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra cook fast-forward fraction per adaptation level.", impact = "Higher values make leveling speed cooking faster.") + double cookBoostFractionPerLevel = 0.07; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on the cook fast-forward fraction per pulse hit.", impact = "Higher values allow nearly instant cooks at high levels.") + double maxCookBoostFraction = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per accelerated block.", impact = "Higher values grant more skill XP from the aura.") + double xpPerAcceleratedBlock = 1.2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosBorrowedTime.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosBorrowedTime.java new file mode 100644 index 000000000..500842d0d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosBorrowedTime.java @@ -0,0 +1,263 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Deque; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; + +public class ChronosBorrowedTime extends SimpleAdaptation { + private final Map> deferred; + private final Set applyingDeferred; + + public ChronosBorrowedTime() { + super("chronos-borrowed-time"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.borrowed_time.description")); + setDisplayName(Localizer.dLocalize("chronos.borrowed_time.name")); + setIcon(Material.SOUL_SAND); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + deferred = new ConcurrentHashMap<>(); + applyingDeferred = ConcurrentHashMap.newKeySet(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SOUL_SAND) + .key("challenge_chronos_borrowed_2500") + .title(Localizer.dLocalize("advancement.challenge_chronos_borrowed_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_borrowed_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_borrowed_2500", "chronos.borrowed-time.damage-deferred", 2500, 900); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Math.round(getDeferFraction(level) * 100D) + "% " + Localizer.dLocalize("chronos.borrowed_time.lore1")); + v.addLore(C.YELLOW + "+ " + getConfig().paybackPulses + "s " + Localizer.dLocalize("chronos.borrowed_time.lore2")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.borrowed_time.lore3")); + } + + private double getDeferFraction(int level) { + return Math.min(getConfig().maxDeferFraction, + getConfig().baseDeferFraction + (Math.max(1, level) * getConfig().deferFractionPerLevel)); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + deferred.remove(id); + applyingDeferred.remove(id); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerDeathEvent e) { + UUID id = e.getEntity().getUniqueId(); + deferred.remove(id); + applyingDeferred.remove(id); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + UUID id = p.getUniqueId(); + if (applyingDeferred.contains(id)) { + return; + } + + double finalDamage = e.getFinalDamage(); + if (finalDamage < getConfig().minimumDeferDamage) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + double fraction = getDeferFraction(level); + if (fraction <= 0D) { + return; + } + + double deferredAmount = finalDamage * fraction; + e.setDamage(Math.max(0D, e.getDamage() * (1D - fraction))); + + int pulses = Math.max(1, getConfig().paybackPulses); + deferred.computeIfAbsent(id, k -> new ConcurrentLinkedDeque<>()) + .add(new DeferredDamage(deferredAmount / pulses, pulses)); + + getPlayer(p).getData().addStat("chronos.borrowed-time.damage-deferred", deferredAmount); + } + + @Override + public void onTick() { + if (deferred.isEmpty()) { + return; + } + + for (Map.Entry> entry : deferred.entrySet()) { + UUID id = entry.getKey(); + Player p = Bukkit.getPlayer(id); + if (p == null || !p.isOnline() || p.isDead()) { + deferred.remove(id); + continue; + } + + Deque queue = entry.getValue(); + double amount = 0D; + Iterator iterator = queue.iterator(); + while (iterator.hasNext()) { + DeferredDamage chunk = iterator.next(); + amount += chunk.perPulse(); + if (chunk.consumePulse() <= 0) { + iterator.remove(); + } + } + + if (queue.isEmpty()) { + deferred.remove(id); + } + + if (amount <= 0D) { + continue; + } + + double damage = amount; + Runnable apply = () -> { + if (!p.isOnline() || p.isDead()) { + return; + } + + double health = p.getHealth(); + if (health <= 0D) { + return; + } + + double remaining = health - damage; + if (remaining <= 0D) { + applyingDeferred.add(id); + try { + p.damage(damage); + } finally { + applyingDeferred.remove(id); + } + } else { + p.setHealth(remaining); + } + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.ASH, p.getLocation().add(0, 1, 0), 3, 0.2, 0.3, 0.2, 0); + } + }; + + if (J.isFoliaThreading()) { + J.runEntity(p, apply); + } else { + apply.run(); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Defer a portion of incoming damage, paying it back in small ticks over the following seconds.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base fraction of incoming damage that is deferred.", impact = "Higher values move more damage into the payback window.") + double baseDeferFraction = 0.1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra deferred fraction per adaptation level.", impact = "Higher values make leveling defer more damage.") + double deferFractionPerLevel = 0.06; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on the deferred damage fraction.", impact = "Higher values allow more of each hit to be deferred.") + double maxDeferFraction = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum final damage required before any deferral happens.", impact = "Higher values skip deferring small hits entirely.") + double minimumDeferDamage = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Number of one second pulses the deferred damage is paid back over.", impact = "Higher values spread payback thinner over a longer window.") + int paybackPulses = 10; + } + + private static final class DeferredDamage { + private final double perPulse; + private int pulsesRemaining; + + private DeferredDamage(double perPulse, int pulsesRemaining) { + this.perPulse = perPulse; + this.pulsesRemaining = pulsesRemaining; + } + + private double perPulse() { + return perPulse; + } + + private int consumePulse() { + pulsesRemaining--; + return pulsesRemaining; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosDejaVu.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosDejaVu.java new file mode 100644 index 000000000..49f32819d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosDejaVu.java @@ -0,0 +1,170 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ChronosDejaVu extends SimpleAdaptation { + private final Map memory; + + public ChronosDejaVu() { + super("chronos-deja-vu"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.deja_vu.description")); + setDisplayName(Localizer.dLocalize("chronos.deja_vu.name")); + setIcon(Material.ITEM_FRAME); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(60000); + memory = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ITEM_FRAME) + .key("challenge_chronos_deja_vu_500") + .title(Localizer.dLocalize("advancement.challenge_chronos_deja_vu_500.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_deja_vu_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_deja_vu_500", "chronos.deja-vu.damage-absorbed", 500, 700); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Math.round(getReductionFraction(level) * 100D) + "% " + Localizer.dLocalize("chronos.deja_vu.lore1")); + v.addLore(C.YELLOW + "+ " + Form.duration(getConfig().memoryWindowMillis, 1) + " " + Localizer.dLocalize("chronos.deja_vu.lore2")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.deja_vu.lore3")); + } + + private double getReductionFraction(int level) { + return Math.min(getConfig().maxReductionFraction, + getConfig().baseReductionFraction + (Math.max(1, level) * getConfig().reductionFractionPerLevel)); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + memory.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + EntityDamageEvent.DamageCause cause = e.getCause(); + long now = System.currentTimeMillis(); + DamageMemory previous = memory.put(p.getUniqueId(), new DamageMemory(cause, now)); + if (previous == null || previous.cause() != cause || now - previous.timestamp() > getConfig().memoryWindowMillis) { + return; + } + + double fraction = getReductionFraction(level); + if (fraction <= 0D) { + return; + } + + double absorbed = e.getFinalDamage() * fraction; + if (absorbed <= 0D) { + return; + } + + e.setDamage(Math.max(0D, e.getDamage() * (1D - fraction))); + + getPlayer(p).getData().addStat("chronos.deja-vu.damage-absorbed", absorbed); + xpSilent(p, absorbed * getConfig().xpPerAbsorbedDamage, "chronos:deja-vu"); + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.REVERSE_PORTAL, p.getLocation().add(0, 1, 0), 6, 0.2, 0.3, 0.2, 0.02); + } + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + long window = getConfig().memoryWindowMillis; + memory.entrySet().removeIf(entry -> now - entry.getValue().timestamp() > window); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Remember the last source of pain; taking the same kind of damage again shortly after hurts less.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Window in milliseconds during which a repeated damage cause counts as familiar.", impact = "Higher values keep the damage memory alive longer between hits.") + long memoryWindowMillis = 8000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base fraction of repeated damage that is absorbed.", impact = "Higher values reduce familiar damage more before level scaling.") + double baseReductionFraction = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra absorbed fraction per adaptation level.", impact = "Higher values make leveling reduce familiar damage faster.") + double reductionFractionPerLevel = 0.09; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on the absorbed damage fraction.", impact = "Higher values allow more of a familiar hit to be absorbed.") + double maxReductionFraction = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per point of absorbed damage.", impact = "Higher values grant more skill XP from familiar hits.") + double xpPerAbsorbedDamage = 0.8; + } + + private record DamageMemory(EntityDamageEvent.DamageCause cause, long timestamp) { + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosHourglassGuard.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosHourglassGuard.java new file mode 100644 index 000000000..252e7b2d0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosHourglassGuard.java @@ -0,0 +1,222 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ChronosHourglassGuard extends SimpleAdaptation { + private final Map cooldowns; + private final Map invulnerableUntil; + + public ChronosHourglassGuard() { + super("chronos-hourglass-guard"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.hourglass_guard.description")); + setDisplayName(Localizer.dLocalize("chronos.hourglass_guard.name")); + setIcon(Material.TOTEM_OF_UNDYING); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + cooldowns = new ConcurrentHashMap<>(); + invulnerableUntil = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TOTEM_OF_UNDYING) + .key("challenge_chronos_hourglass_10") + .title(Localizer.dLocalize("advancement.challenge_chronos_hourglass_10.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_hourglass_10.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_hourglass_10", "chronos.hourglass-guard.saves", 10, 800); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + (getConfig().invulnerabilityMillis / 1000D) + "s " + Localizer.dLocalize("chronos.hourglass_guard.lore1")); + v.addLore(C.RED + "* " + Form.duration(getCooldownMillis(level), 1) + " " + Localizer.dLocalize("chronos.hourglass_guard.lore2")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.hourglass_guard.lore3")); + } + + private long getCooldownMillis(int level) { + return Math.max(getConfig().minimumCooldownMillis, + getConfig().baseCooldownMillis - (Math.max(1, level) * getConfig().cooldownReductionPerLevelMillis)); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + cooldowns.remove(id); + invulnerableUntil.remove(id); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + UUID id = p.getUniqueId(); + long now = M.ms(); + if (invulnerableUntil.getOrDefault(id, 0L) > now) { + e.setCancelled(true); + return; + } + + if (p.getHealth() - e.getFinalDamage() > 0D) { + return; + } + + if (cooldowns.getOrDefault(id, 0L) > now) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + e.setCancelled(true); + p.setHealth(Math.max(0.5D, getConfig().survivalHealth)); + invulnerableUntil.put(id, now + getConfig().invulnerabilityMillis); + cooldowns.put(id, now + getCooldownMillis(level)); + p.setNoDamageTicks(Math.max(p.getNoDamageTicks(), (int) (getConfig().invulnerabilityMillis / 50L))); + + slowNearbyEnemies(p); + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.TOTEM_OF_UNDYING, p.getLocation().add(0, 1, 0), 30, 0.35, 0.5, 0.35, 0.12); + p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 1, 0), 10, 0.25, 0.4, 0.25, 0.03); + } + + if (getConfig().playClockSounds) { + ChronosSoundFX.playRewindFinish(p); + } + + getPlayer(p).getData().addStat("chronos.hourglass-guard.saves", 1); + xp(p, p.getLocation(), getConfig().xpOnSave + (level * getConfig().xpPerLevel)); + } + + private void slowNearbyEnemies(Player p) { + double radius = getConfig().enemySlowRadius; + double radiusSq = radius * radius; + for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), radius, radius, radius)) { + if (!(entity instanceof LivingEntity living) || entity.getUniqueId().equals(p.getUniqueId())) { + continue; + } + + if (entity.getLocation().distanceSquared(p.getLocation()) > radiusSq) { + continue; + } + + if (isProtectedFriendly(p, living)) { + continue; + } + + if (living instanceof Player target && !canDamageTarget(p, target)) { + continue; + } + + living.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, + getConfig().enemySlowTicks, getConfig().enemySlowAmplifier, true, false, false), true); + } + } + + @Override + public void onTick() { + long now = M.ms(); + invulnerableUntil.entrySet().removeIf(entry -> entry.getValue() <= now); + cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("A killing blow instead leaves you at half a heart, granting brief invulnerability and slowing nearby enemies, on a long cooldown.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Hourglass Guard adaptation.", impact = "True enables this behavior and false disables it.") + boolean playClockSounds = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Health the player is left with after a save.", impact = "Higher values leave the player healthier after cheating death.") + double survivalHealth = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Invulnerability window in milliseconds after a save.", impact = "Higher values protect the player longer after a save.") + long invulnerabilityMillis = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base cooldown in milliseconds between saves.", impact = "Higher values force longer waits between saves.") + long baseCooldownMillis = 480000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown reduction in milliseconds per adaptation level.", impact = "Higher values make leveling shorten the cooldown faster.") + long cooldownReductionPerLevelMillis = 60000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Lowest possible cooldown in milliseconds regardless of level.", impact = "Higher values keep a floor under cooldown reduction.") + long minimumCooldownMillis = 180000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Radius in blocks for the slow applied to nearby enemies on save.", impact = "Higher values slow enemies from further away.") + double enemySlowRadius = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of the slow applied to nearby enemies.", impact = "Higher values keep enemies slowed longer.") + int enemySlowTicks = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Slowness amplifier applied to nearby enemies on save.", impact = "Higher values slow enemies more severely.") + int enemySlowAmplifier = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted when a save triggers.", impact = "Higher values grant more skill XP per save.") + double xpOnSave = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra XP granted per adaptation level on save.", impact = "Higher values scale save XP with level faster.") + double xpPerLevel = 10; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecall.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecall.java new file mode 100644 index 000000000..e0d834fbd --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecall.java @@ -0,0 +1,1193 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ChronoTimeBombItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import org.bukkit.*; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.*; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class ChronosInstantRecall extends SimpleAdaptation { + private static final EnumSet RECALL_ACTIONS = EnumSet.of( + Action.RIGHT_CLICK_AIR, + Action.RIGHT_CLICK_BLOCK, + Action.LEFT_CLICK_AIR, + Action.LEFT_CLICK_BLOCK + ); + private static final Map TELEPORT_XP_SUPPRESS_UNTIL = new ConcurrentHashMap<>(); + + private final Map> snapshots = new ConcurrentHashMap<>(); + private final Map lastSnapshot = new ConcurrentHashMap<>(); + private final Map cooldowns = new ConcurrentHashMap<>(); + private final Set cooldownReadyNotify = ConcurrentHashMap.newKeySet(); + private final Map rewindProtection = new ConcurrentHashMap<>(); + private final Set rewinding = ConcurrentHashMap.newKeySet(); + private final Map recallXpStamps = new ConcurrentHashMap<>(); + private final Map jumpArmUntil = new ConcurrentHashMap<>(); + private final Map lastOnGround = new ConcurrentHashMap<>(); + + public ChronosInstantRecall() { + super("chronos-instant-recall"); + registerConfiguration(ChronosInstantRecallConfig.class); + setDescription(Localizer.dLocalize("chronos.instant_recall.description")); + setDisplayName(Localizer.dLocalize("chronos.instant_recall.name")); + setIcon(Material.RECOVERY_COMPASS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(50); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CLOCK) + .key("challenge_chronos_recall_50") + .title(Localizer.dLocalize("advancement.challenge_chronos_recall_50.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_recall_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.RECOVERY_COMPASS) + .key("challenge_chronos_recall_1k") + .title(Localizer.dLocalize("advancement.challenge_chronos_recall_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_recall_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.RECOVERY_COMPASS) + .key("challenge_chronos_recall_cheat_death") + .title(Localizer.dLocalize("advancement.challenge_chronos_recall_cheat_death.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_recall_cheat_death.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_recall_50", "chronos.instant-recall.recalls", 50, 300); + registerMilestone("challenge_chronos_recall_1k", "chronos.instant-recall.recalls", 1000, 1500); + } + + private static void markRecallTeleportSuppressed(UUID id, long suppressUntilMillis) { + long current = TELEPORT_XP_SUPPRESS_UNTIL.getOrDefault(id, 0L); + if (suppressUntilMillis > current) { + TELEPORT_XP_SUPPRESS_UNTIL.put(id, suppressUntilMillis); + } + } + + public static boolean isRecallTeleportSuppressed(Player p) { + if (p == null) { + return false; + } + + UUID id = p.getUniqueId(); + long until = TELEPORT_XP_SUPPRESS_UNTIL.getOrDefault(id, 0L); + if (until <= M.ms()) { + TELEPORT_XP_SUPPRESS_UNTIL.remove(id); + return false; + } + + return true; + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.duration(getRewindDurationMillis(level), 1) + " " + Localizer.dLocalize("chronos.instant_recall.lore1")); + v.addLore(C.RED + "* " + Form.duration(getCooldownMillis(level), 1) + " " + Localizer.dLocalize("chronos.instant_recall.lore2")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.instant_recall.lore3")); + if (getConfig().consumeClock) { + v.addLore(C.RED + "* " + Localizer.dLocalize("chronos.instant_recall.lore_cost_clock")); + } + if (getConfig().healthCostFraction > 0) { + v.addLore(C.RED + "* " + Form.pc(getConfig().healthCostFraction, 0) + " " + Localizer.dLocalize("chronos.instant_recall.lore_cost_health")); + } + List combos = getTriggerCombos(); + if (combos.isEmpty()) { + v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + "none"); + return; + } + + for (String combo : combos) { + v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + combo); + } + } + + @Override + public String getDescription() { + return "Rewind to a recent snapshot with health and hunger restored. " + summarizeTriggerDescription(); + } + + private String summarizeTriggerDescription() { + List combos = getTriggerCombos(); + if (combos.isEmpty()) { + return "No active triggers are currently enabled."; + } + + if (combos.size() == 1) { + return "Trigger: " + combos.get(0) + "."; + } + + if (combos.size() == 2) { + return "Triggers: " + combos.get(0) + " or " + combos.get(1) + "."; + } + + return "Triggers: " + combos.get(0) + ", " + combos.get(1) + ", +" + (combos.size() - 2) + " more."; + } + + private long getRewindDurationMillis(int level) { + return (long) (getRewindDurationSeconds(level) * 1000D); + } + + private double getRewindDurationSeconds(int level) { + double raw = getConfig().baseRewindSeconds + (level * getConfig().rewindSecondsPerLevel); + return Math.max(0.25D, Math.min(getConfig().maxRewindSeconds, raw)); + } + + private long getCooldownMillis(int level) { + return getRewindDurationMillis(level) + (getConfig().cooldownPaddingSeconds * 1000L); + } + + private long getMaximumHistoryMillis() { + return (long) ((getRewindDurationSeconds(getConfig().maxLevel) + getConfig().historyPaddingSeconds) * 1000D); + } + + private int getRewindAnimationTicks() { + int durationMillis = Math.max(100, getConfig().rewindAnimationDurationMillis); + int ticks = (int) Math.ceil(durationMillis / 50D); + if (ticks <= 1) { + ticks = Math.max(2, getConfig().rewindAnimationTicks); + } + + return Math.max(2, ticks); + } + + private boolean isSingleWorldPath(List path) { + if (path.isEmpty()) { + return false; + } + + String world = path.get(0).worldName(); + for (Snapshot snapshot : path) { + if (!Objects.equals(world, snapshot.worldName())) { + return false; + } + } + + return true; + } + + private ArmorStand spawnRecallCameraAnchor(Player p) { + Location location = p.getLocation().clone(); + if (location.getWorld() == null) { + return null; + } + + return location.getWorld().spawn(location, ArmorStand.class, stand -> { + stand.setInvisible(true); + stand.setMarker(false); + stand.setGravity(false); + stand.setSilent(true); + stand.setInvulnerable(true); + stand.setCollidable(false); + stand.setBasePlate(false); + stand.setSmall(true); + stand.setPersistent(false); + }); + } + + private List getTriggerCombos() { + List triggers = new ArrayList<>(); + String clickSurface = getClickSurfaceLabel(); + if (getConfig().enableClockClickTrigger) { + appendClickCombos(triggers, "Clock", getConfig().clockClickLeftClick, getConfig().clockClickRightClick, clickSurface); + } + + if (getConfig().enableSprintClickTrigger) { + appendClickCombos(triggers, "Sprint + Clock", getConfig().sprintClickLeftClick, getConfig().sprintClickRightClick, clickSurface); + } + + if (getConfig().enableSingleSneakTrigger) { + String combo = getConfig().singleSneakRequiresSprint ? "Sprint + Sneak" : "Sneak"; + if (getConfig().singleSneakRequiresClockInHand) { + combo += " + Clock"; + } + triggers.add(combo); + } + + if (getConfig().enableDoubleJumpTrigger) { + String combo = "Double Jump"; + if (getConfig().doubleJumpRequiresSprint) { + combo += " + Sprint"; + } + if (getConfig().doubleJumpRequiresClockInHand) { + combo += " + Clock"; + } + triggers.add(combo); + } + + return triggers; + } + + private void appendClickCombos(List triggers, String prefix, boolean allowLeft, boolean allowRight, String clickSurface) { + if (clickSurface.isBlank()) { + return; + } + + if (allowLeft) { + triggers.add(prefix + " + Left Click" + clickSurface); + } + + if (allowRight) { + triggers.add(prefix + " + Right Click" + clickSurface); + } + } + + private String getClickSurfaceLabel() { + if (getConfig().allowAirClicks && getConfig().allowBlockClicks) { + return " (air/block)"; + } + + if (getConfig().allowAirClicks) { + return " (air)"; + } + + if (getConfig().allowBlockClicks) { + return " (block)"; + } + + return ""; + } + + private RecallXPContext buildRecallXPContext(Snapshot from, Snapshot to) { + double distance; + if (Objects.equals(from.worldName(), to.worldName())) { + double dx = from.x() - to.x(); + double dy = from.y() - to.y(); + double dz = from.z() - to.z(); + distance = Math.sqrt((dx * dx) + (dy * dy) + (dz * dz)); + } else { + distance = getConfig().xpCrossWorldDistanceCredit; + } + + double healthRecovered = Math.max(0D, to.health() - from.health()); + double hungerRecovered = Math.max(0D, to.foodLevel() - from.foodLevel()); + double saturationRecovered = Math.max(0D, to.saturation() - from.saturation()); + + return new RecallXPContext( + from.worldName(), + from.x(), + from.y(), + from.z(), + to.worldName(), + to.x(), + to.y(), + to.z(), + distance, + healthRecovered, + hungerRecovered, + saturationRecovered); + } + + private boolean pointsAreSimilar(String worldA, double ax, double ay, double az, String worldB, double bx, double by, double bz, double radius) { + if (!Objects.equals(worldA, worldB)) { + return false; + } + + double dx = ax - bx; + double dy = ay - by; + double dz = az - bz; + return (dx * dx) + (dy * dy) + (dz * dz) <= (radius * radius); + } + + private boolean isRepeatRecall(RecallXPFarmStamp stamp, RecallXPContext context) { + return pointsAreSimilar(stamp.fromWorld(), stamp.fromX(), stamp.fromY(), stamp.fromZ(), + context.fromWorld(), context.fromX(), context.fromY(), context.fromZ(), + getConfig().xpRepeatSourceRadius) + && pointsAreSimilar(stamp.toWorld(), stamp.toX(), stamp.toY(), stamp.toZ(), + context.toWorld(), context.toX(), context.toY(), context.toZ(), + getConfig().xpRepeatTargetRadius); + } + + private double computeRecallXPGain(UUID playerId, int level, RecallXPContext context, long now) { + double raw = (context.distance() * getConfig().xpPerDistanceBlock) + + (context.healthRecovered() * getConfig().xpPerHealthPoint) + + (context.hungerRecovered() * getConfig().xpPerHungerPoint) + + (context.saturationRecovered() * getConfig().xpPerSaturationPoint); + + if (raw < getConfig().xpMinRawReward) { + return 0D; + } + + double leveled = raw * (1D + ((Math.max(1, level) - 1) * getConfig().xpLevelMultiplierPerLevel)); + double multiplier = 1D; + + RecallXPFarmStamp previous = recallXpStamps.get(playerId); + if (previous != null) { + long elapsed = now - previous.awardedAt(); + if (elapsed < getConfig().xpDiminishWindowMillis) { + double t = Math.max(0D, Math.min(1D, elapsed / (double) Math.max(1L, getConfig().xpDiminishWindowMillis))); + multiplier *= getConfig().xpDiminishMinMultiplier + ((1D - getConfig().xpDiminishMinMultiplier) * t); + } + + if (elapsed < getConfig().xpRepeatWindowMillis && isRepeatRecall(previous, context)) { + multiplier *= getConfig().xpRepeatPenaltyMultiplier; + } + } + + double reward = Math.min(getConfig().xpMaxAward, leveled * multiplier); + if (reward < getConfig().xpMinAward) { + return 0D; + } + + return reward; + } + + private Snapshot snapshotFromPlayer(Player p, long now) { + return new Snapshot(now, + p.getWorld().getName(), + p.getLocation().getX(), + p.getLocation().getY(), + p.getLocation().getZ(), + p.getLocation().getYaw(), + p.getLocation().getPitch(), + p.getHealth(), + p.getFoodLevel(), + p.getSaturation(), + p.getExhaustion(), + p.getFireTicks()); + } + + private Snapshot snapshotFromLocation(Player p, Location location, long now) { + World world = location.getWorld(); + if (world == null) { + world = p.getWorld(); + } + + return new Snapshot(now, + world.getName(), + location.getX(), + location.getY(), + location.getZ(), + location.getYaw(), + location.getPitch(), + p.getHealth(), + p.getFoodLevel(), + p.getSaturation(), + p.getExhaustion(), + p.getFireTicks()); + } + + private void resetSnapshotHistory(Player p, Location location) { + if (location == null) { + return; + } + + long now = M.ms(); + UUID id = p.getUniqueId(); + Deque queue = snapshots.computeIfAbsent(id, unused -> new ArrayDeque<>()); + queue.clear(); + queue.addLast(snapshotFromLocation(p, location, now)); + lastSnapshot.put(id, now); + } + + private void captureSnapshot(Player p) { + long now = M.ms(); + UUID id = p.getUniqueId(); + long last = lastSnapshot.getOrDefault(id, 0L); + if (now - last < getConfig().snapshotIntervalMillis) { + return; + } + + lastSnapshot.put(id, now); + Deque queue = snapshots.computeIfAbsent(id, k -> new ArrayDeque<>()); + queue.addLast(snapshotFromPlayer(p, now)); + + long maxAge = getMaximumHistoryMillis(); + while (!queue.isEmpty() && now - queue.getFirst().timestamp() > maxAge) { + queue.removeFirst(); + } + } + + private Snapshot findSnapshot(Player p, long rewindMillis) { + Deque queue = snapshots.get(p.getUniqueId()); + if (queue == null || queue.isEmpty()) { + return null; + } + + long target = M.ms() - rewindMillis; + Snapshot fallback = queue.getFirst(); + + for (Snapshot s : queue) { + if (s.timestamp() <= target) { + fallback = s; + } else { + break; + } + } + + return fallback; + } + + private List buildRewindPath(Player p, long rewindMillis, Snapshot anchor) { + List path = new ArrayList<>(); + long now = M.ms(); + path.add(snapshotFromPlayer(p, now)); + + Deque queue = snapshots.get(p.getUniqueId()); + if (queue == null || queue.isEmpty()) { + path.add(anchor); + return path; + } + + Iterator reverse = queue.descendingIterator(); + while (reverse.hasNext()) { + Snapshot snap = reverse.next(); + if (snap.timestamp() < anchor.timestamp()) { + break; + } + if (snap.timestamp() <= now) { + path.add(snap); + } + } + + Snapshot last = path.get(path.size() - 1); + if (last.timestamp() != anchor.timestamp()) { + path.add(anchor); + } + + if (path.size() < 2) { + path.add(anchor); + } + + return path; + } + + private List buildAnimationPath(List rawPath, int animationTicks) { + List animationPath = new ArrayList<>(); + if (rawPath.isEmpty()) { + return animationPath; + } + + if (animationTicks <= 1 || rawPath.size() == 1) { + animationPath.add(rawPath.get(rawPath.size() - 1)); + return animationPath; + } + + for (int step = 0; step < animationTicks; step++) { + double progress = step / (double) (animationTicks - 1); + double scaled = progress * (rawPath.size() - 1); + int lower = (int) Math.floor(scaled); + int upper = Math.min(rawPath.size() - 1, lower + 1); + double alpha = scaled - lower; + Snapshot a = rawPath.get(lower); + Snapshot b = rawPath.get(upper); + animationPath.add(interpolateSnapshot(a, b, alpha)); + } + + Snapshot anchor = rawPath.get(rawPath.size() - 1); + animationPath.set(animationPath.size() - 1, anchor); + return animationPath; + } + + private Snapshot interpolateSnapshot(Snapshot a, Snapshot b, double alpha) { + if (alpha <= 0D) { + return a; + } + if (alpha >= 1D) { + return b; + } + + long timestamp = (long) Math.round(lerp(a.timestamp(), b.timestamp(), alpha)); + String worldName = alpha < 0.5D ? a.worldName() : b.worldName(); + double x = lerp(a.x(), b.x(), alpha); + double y = lerp(a.y(), b.y(), alpha); + double z = lerp(a.z(), b.z(), alpha); + float yaw = lerpAngle(a.yaw(), b.yaw(), alpha); + float pitch = (float) lerp(a.pitch(), b.pitch(), alpha); + double health = lerp(a.health(), b.health(), alpha); + int foodLevel = (int) Math.round(lerp(a.foodLevel(), b.foodLevel(), alpha)); + float saturation = (float) lerp(a.saturation(), b.saturation(), alpha); + float exhaustion = (float) lerp(a.exhaustion(), b.exhaustion(), alpha); + int fireTicks = (int) Math.round(lerp(a.fireTicks(), b.fireTicks(), alpha)); + + return new Snapshot(timestamp, worldName, x, y, z, yaw, pitch, health, foodLevel, saturation, exhaustion, fireTicks); + } + + private double lerp(double a, double b, double alpha) { + return a + ((b - a) * alpha); + } + + private float lerpAngle(float from, float to, double alpha) { + float delta = to - from; + while (delta > 180F) { + delta -= 360F; + } + while (delta < -180F) { + delta += 360F; + } + + return from + (float) (delta * alpha); + } + + private Location toLocation(Snapshot snapshot, World fallback) { + World world = Bukkit.getWorld(snapshot.worldName()); + if (world == null) { + world = fallback; + } + return new Location(world, snapshot.x(), snapshot.y(), snapshot.z(), snapshot.yaw(), snapshot.pitch()); + } + + private void applySnapshotState(Player p, Snapshot snapshot) { + double maxHealth = p.getAttribute(Attribute.MAX_HEALTH) == null ? 20D : p.getAttribute(Attribute.MAX_HEALTH).getValue(); + p.setHealth(Math.max(1, Math.min(maxHealth, snapshot.health()))); + p.setFoodLevel(Math.max(0, Math.min(20, snapshot.foodLevel()))); + p.setSaturation(Math.max(0, snapshot.saturation())); + p.setExhaustion(Math.max(0, snapshot.exhaustion())); + p.setFireTicks(Math.max(0, snapshot.fireTicks())); + p.setFallDistance(0); + p.setVelocity(new Vector()); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + clearPlayerState(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerTeleportEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + if (!isRecallEligible(p) || rewinding.contains(id)) { + return; + } + + Location destination = e.getTo(); + if (destination == null) { + return; + } + + resetSnapshotHistory(p, destination); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerChangedWorldEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + if (!isRecallEligible(p) || rewinding.contains(id)) { + return; + } + + resetSnapshotHistory(p, p.getLocation()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + if (!isRecallEligible(p)) { + return; + } + + if (shouldTriggerSprintClockClick(e)) { + e.setCancelled(true); + attemptRecall(p); + return; + } + + if (shouldTriggerClockClick(e)) { + e.setCancelled(true); + attemptRecall(p); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerToggleFlightEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + if (!isRecallEligible(p) || !getConfig().enableDoubleJumpTrigger) { + return; + } + + Long armUntil = jumpArmUntil.get(id); + if (armUntil == null) { + return; + } + + e.setCancelled(true); + p.setFlying(false); + clearDoubleJumpArm(p, id); + if (armUntil > M.ms()) { + attemptRecall(p); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + if (!e.isSneaking() || !isRecallEligible(p) || !getConfig().enableSingleSneakTrigger) { + return; + } + + if (getConfig().singleSneakRequiresSprint && !p.isSprinting()) { + return; + } + + if (getConfig().singleSneakRequiresClockInHand && !hasRecallClockInEitherHand(p)) { + return; + } + + attemptRecall(p); + } + + private EquipmentSlot resolveRecallHand(Player p, EquipmentSlot eventHand) { + ItemStack main = p.getInventory().getItemInMainHand(); + ItemStack off = p.getInventory().getItemInOffHand(); + + if (eventHand == null) { + if (isRecallClock(main)) { + return EquipmentSlot.HAND; + } + + if (isRecallClock(off)) { + return EquipmentSlot.OFF_HAND; + } + + return null; + } + + if (eventHand == EquipmentSlot.HAND) { + return isRecallClock(main) ? EquipmentSlot.HAND : null; + } + + if (eventHand == EquipmentSlot.OFF_HAND) { + if (isRecallClock(main)) { + return null; + } + + return isRecallClock(off) ? EquipmentSlot.OFF_HAND : null; + } + + return null; + } + + private boolean isRecallEligible(Player p) { + return hasActiveAdaptation(p) && p.getGameMode() == GameMode.SURVIVAL; + } + + private boolean isLeftClick(Action action) { + return action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK; + } + + private boolean isRightClick(Action action) { + return action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK; + } + + private boolean isBlockClick(Action action) { + return action == Action.LEFT_CLICK_BLOCK || action == Action.RIGHT_CLICK_BLOCK; + } + + private boolean isActionAllowed(Action action) { + if (!RECALL_ACTIONS.contains(action)) { + return false; + } + + if (!getConfig().allowAirClicks && !isBlockClick(action)) { + return false; + } + + if (!getConfig().allowBlockClicks && isBlockClick(action)) { + return false; + } + + return true; + } + + private boolean shouldTriggerClockClick(PlayerInteractEvent e) { + if (!getConfig().enableClockClickTrigger) { + return false; + } + + Action action = e.getAction(); + if (!isActionAllowed(action)) { + return false; + } + + if (isLeftClick(action) && !getConfig().clockClickLeftClick) { + return false; + } + + if (isRightClick(action) && !getConfig().clockClickRightClick) { + return false; + } + + return resolveRecallHand(e.getPlayer(), e.getHand()) != null; + } + + private boolean shouldTriggerSprintClockClick(PlayerInteractEvent e) { + if (!getConfig().enableSprintClickTrigger || !e.getPlayer().isSprinting()) { + return false; + } + + Action action = e.getAction(); + if (!isActionAllowed(action)) { + return false; + } + + if (isLeftClick(action) && !getConfig().sprintClickLeftClick) { + return false; + } + + if (isRightClick(action) && !getConfig().sprintClickRightClick) { + return false; + } + + return resolveRecallHand(e.getPlayer(), e.getHand()) != null; + } + + private boolean hasRecallClockInEitherHand(Player p) { + return isRecallClock(p.getInventory().getItemInMainHand()) + || isRecallClock(p.getInventory().getItemInOffHand()); + } + + private boolean canArmDoubleJump(Player p) { + if (rewinding.contains(p.getUniqueId())) { + return false; + } + + if (getConfig().doubleJumpRequiresSprint && !p.isSprinting()) { + return false; + } + + return !getConfig().doubleJumpRequiresClockInHand || hasRecallClockInEitherHand(p); + } + + private void clearDoubleJumpArm(Player p, UUID id) { + if (jumpArmUntil.remove(id) == null) { + return; + } + + if (p.getGameMode() == GameMode.SURVIVAL) { + p.setAllowFlight(false); + p.setFlying(false); + } + } + + private void armDoubleJump(Player p, UUID id) { + int triggerWindowMillis = Math.max(150, getConfig().doubleJumpWindowMillis); + jumpArmUntil.put(id, M.ms() + triggerWindowMillis); + p.setAllowFlight(true); + J.runEntity(p, () -> { + if (!p.isOnline()) { + return; + } + + Long armUntil = jumpArmUntil.get(id); + if (armUntil != null && armUntil <= M.ms()) { + clearDoubleJumpArm(p, id); + } + }, Math.max(1, (int) Math.ceil(triggerWindowMillis / 50D))); + } + + private boolean isDoubleJumpStart(boolean wasOnGround, boolean onGround, Player p) { + return wasOnGround + && !onGround + && p.getVelocity().getY() >= getConfig().doubleJumpMinVerticalVelocity; + } + + private void clearPlayerState(UUID id) { + snapshots.remove(id); + lastSnapshot.remove(id); + cooldowns.remove(id); + cooldownReadyNotify.remove(id); + rewindProtection.remove(id); + rewinding.remove(id); + recallXpStamps.remove(id); + jumpArmUntil.remove(id); + lastOnGround.remove(id); + TELEPORT_XP_SUPPRESS_UNTIL.remove(id); + } + + private boolean isRecallClock(ItemStack stack) { + return stack != null + && stack.getType() == Material.CLOCK + && !ChronoTimeBombItem.isBindableItem(stack); + } + + private void consumeRecallClock(Player p) { + if (!getConfig().consumeClock) { + return; + } + + ItemStack main = p.getInventory().getItemInMainHand(); + if (isRecallClock(main)) { + main.setAmount(main.getAmount() - 1); + return; + } + + ItemStack off = p.getInventory().getItemInOffHand(); + if (isRecallClock(off)) { + off.setAmount(off.getAmount() - 1); + } + } + + private void applyRecallHealthCost(Player p) { + double fraction = Math.max(0D, Math.min(1D, getConfig().healthCostFraction)); + if (fraction <= 0D || p.isDead()) { + return; + } + + p.setHealth(Math.max(1.0D, p.getHealth() * (1D - fraction))); + } + + private void attemptRecall(Player p) { + UUID id = p.getUniqueId(); + if (!isRecallEligible(p)) { + return; + } + + clearDoubleJumpArm(p, id); + if (rewinding.contains(id)) { + return; + } + + long now = M.ms(); + long cooldown = cooldowns.getOrDefault(id, 0L); + if (cooldown > now) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + return; + } + + int level = getActiveLevel(p); + long rewindMillis = getRewindDurationMillis(level); + Snapshot anchor = findSnapshot(p, rewindMillis); + if (anchor == null) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + return; + } + + int animationTicks = getRewindAnimationTicks(); + List path = buildRewindPath(p, rewindMillis, anchor); + List animationPath = buildAnimationPath(path, animationTicks); + if (animationPath.isEmpty()) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + return; + } + + consumeRecallClock(p); + Snapshot finalSnapshot = animationPath.get(animationPath.size() - 1); + RecallXPContext xpContext = buildRecallXPContext(animationPath.get(0), finalSnapshot); + double healthBeforeRecall = p.getHealth(); + double healthAfterRecall = finalSnapshot.health(); + long castAt = M.ms(); + GameMode originalGameMode = p.getGameMode(); + boolean temporarySpectator = getConfig().rewindUseTemporarySpectator && originalGameMode == GameMode.SURVIVAL; + boolean allowClientCamera = temporarySpectator + && getConfig().rewindUseClientCamera + && isSingleWorldPath(animationPath); + ArmorStand cameraAnchor = null; + if (temporarySpectator) { + p.setGameMode(GameMode.SPECTATOR); + p.setFlying(true); + if (allowClientCamera) { + cameraAnchor = spawnRecallCameraAnchor(p); + if (cameraAnchor != null && cameraAnchor.isValid()) { + p.setSpectatorTarget(cameraAnchor); + } else { + allowClientCamera = false; + } + } + } + + cooldowns.put(id, castAt + getCooldownMillis(level)); + cooldownReadyNotify.add(id); + rewinding.add(id); + long protectionUntil = castAt + ((long) (animationTicks + getConfig().rewindProtectionTicks) * 50L); + rewindProtection.put(id, protectionUntil); + markRecallTeleportSuppressed(id, protectionUntil + ((long) Math.max(0, getConfig().rewindTeleportXpSuppressExtraTicks) * 50L)); + p.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, animationTicks + getConfig().rewindProtectionTicks, 0, true, false, false), true); + + if (getConfig().playClockSounds) { + ChronosSoundFX.playRewindStart(p); + ChronosSoundFX.playRewindFinish(p); + } + + final boolean initialClientCamera = allowClientCamera; + final ArmorStand initialCameraAnchor = cameraAnchor; + int[] step = {0}; + Location[] lastLoc = {p.getLocation().clone()}; + boolean[] clientCameraActive = {initialClientCamera}; + ArmorStand[] cameraRef = {initialCameraAnchor}; + + Consumer cleanupVisualState = restoreGameMode -> { + if (cameraRef[0] != null) { + Entity anchorEntity = cameraRef[0]; + cameraRef[0] = null; + if (anchorEntity.isValid()) { + anchorEntity.remove(); + } + } + + if (temporarySpectator) { + p.setSpectatorTarget(null); + if (restoreGameMode && p.getGameMode() == GameMode.SPECTATOR) { + p.setGameMode(originalGameMode); + p.setFlying(false); + } + } + }; + + Runnable[] rewindTask = new Runnable[1]; + rewindTask[0] = () -> { + if (!p.isOnline() || p.isDead()) { + rewinding.remove(id); + cleanupVisualState.accept(true); + return; + } + + float progress = animationTicks <= 1 ? 1f : (float) step[0] / (float) (animationTicks - 1); + int index = Math.min(animationPath.size() - 1, step[0]); + Snapshot snapshot = animationPath.get(index); + Location destination = toLocation(snapshot, p.getWorld()); + + if (getConfig().showRewindTraceParticles && lastLoc[0].getWorld() != null && lastLoc[0].getWorld().equals(destination.getWorld())) { + Predicate traceFilter = J.isFoliaThreading() ? null : l -> l.getBlock().isPassable(); + vfxParticleLine(lastLoc[0].clone().add(0, 1, 0), destination.clone().add(0, 1, 0), Particle.REVERSE_PORTAL, + Math.max(4, getConfig().rewindTracePoints), 1, 0.08D, 0.08D, 0.08D, 0D, null, true, + traceFilter); + } + + boolean movedClient = false; + if (clientCameraActive[0] && cameraRef[0] != null && cameraRef[0].isValid()) { + Entity target = p.getSpectatorTarget(); + if (target == null || !target.getUniqueId().equals(cameraRef[0].getUniqueId())) { + p.setSpectatorTarget(cameraRef[0]); + target = p.getSpectatorTarget(); + } + + if (target != null + && target.getUniqueId().equals(cameraRef[0].getUniqueId()) + && destination.getWorld() != null + && destination.getWorld().equals(cameraRef[0].getWorld())) { + J.teleport(cameraRef[0], destination, PlayerTeleportEvent.TeleportCause.PLUGIN); + movedClient = true; + } else { + clientCameraActive[0] = false; + } + } + + if (!movedClient) { + J.teleport(p, destination, PlayerTeleportEvent.TeleportCause.PLUGIN); + } + + if (!temporarySpectator || step[0] >= animationPath.size() - 1) { + applySnapshotState(p, snapshot); + } + + if (getConfig().playClockSounds) { + ChronosSoundFX.playRewindStep(p, progress); + } + + lastLoc[0] = destination; + step[0]++; + + if (step[0] >= animationTicks) { + cleanupVisualState.accept(true); + Location finalDestination = toLocation(finalSnapshot, p.getWorld()); + if (finalDestination.getWorld() != null) { + J.teleport(p, finalDestination, PlayerTeleportEvent.TeleportCause.PLUGIN); + } + applySnapshotState(p, finalSnapshot); + applyRecallHealthCost(p); + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.TOTEM_OF_UNDYING, p.getLocation().add(0, 1, 0), 26, 0.25, 0.35, 0.25, 0.01); + } + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.ITEM, p.getLocation().add(0, 1, 0), 18, 0.30, 0.30, 0.30, 0.01, new ItemStack(Material.CLOCK)); + } + if (getConfig().playClockSounds) { + ChronosSoundFX.playRewindFinish(p); + } + rewinding.remove(id); + getPlayer(p).getData().addStat("chronos.instant-recall.recalls", 1); + if (healthBeforeRecall <= 4 && healthAfterRecall >= 16 + && AdaptConfig.get().isAdvancements() + && !getPlayer(p).getData().isGranted("challenge_chronos_recall_cheat_death")) { + getPlayer(p).getAdvancementHandler().grant("challenge_chronos_recall_cheat_death"); + } + long awardAt = M.ms(); + double xpGain = computeRecallXPGain(id, level, xpContext, awardAt); + if (xpGain > 0D) { + xp(p, p.getLocation(), xpGain); + recallXpStamps.put(id, new RecallXPFarmStamp( + awardAt, + xpContext.fromWorld(), + xpContext.fromX(), + xpContext.fromY(), + xpContext.fromZ(), + xpContext.toWorld(), + xpContext.toX(), + xpContext.toY(), + xpContext.toZ())); + } + return; + } + + if (J.isFoliaThreading()) { + J.runEntity(p, rewindTask[0], 1); + } else { + J.s(rewindTask[0], 1); + } + }; + if (J.isFoliaThreading()) { + J.runEntity(p, rewindTask[0]); + } else { + J.s(rewindTask[0]); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + UUID id = p.getUniqueId(); + long protectedUntil = rewindProtection.getOrDefault(id, 0L); + if (rewinding.contains(id) || protectedUntil > M.ms()) { + e.setCancelled(true); + p.setNoDamageTicks(Math.max(p.getNoDamageTicks(), getConfig().rewindProtectionTicks)); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + if (!isRecallEligible(p)) { + return; + } + + captureSnapshot(p); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onDoubleJumpMove(PlayerMoveEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + boolean wasOnGround = lastOnGround.getOrDefault(id, true); + boolean onGround = p.isOnGround(); + lastOnGround.put(id, onGround); + + if (!isRecallEligible(p) || !getConfig().enableDoubleJumpTrigger) { + clearDoubleJumpArm(p, id); + return; + } + + if (!wasOnGround && onGround) { + clearDoubleJumpArm(p, id); + return; + } + + if (!canArmDoubleJump(p)) { + clearDoubleJumpArm(p, id); + return; + } + + if (cooldowns.getOrDefault(id, 0L) > M.ms()) { + clearDoubleJumpArm(p, id); + return; + } + + if (isDoubleJumpStart(wasOnGround, onGround, p)) { + armDoubleJump(p, id); + return; + } + + Long armUntil = jumpArmUntil.get(id); + if (armUntil != null && armUntil <= M.ms()) { + clearDoubleJumpArm(p, id); + } + } + + @Override + public void onTick() { + long now = M.ms(); + Set cooldownReadySet = cooldownReadyNotify; + for (Iterator iterator = cooldownReadySet.iterator(); iterator.hasNext(); ) { + UUID id = iterator.next(); + Player p = Bukkit.getPlayer(id); + if (p == null) { + iterator.remove(); + continue; + } + + long cooldown = cooldowns.getOrDefault(id, 0L); + if (cooldown <= now) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playCooldownReady(p); + } + iterator.remove(); + } + } + + cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + rewindProtection.entrySet().removeIf(entry -> entry.getValue() <= now); + TELEPORT_XP_SUPPRESS_UNTIL.entrySet().removeIf(entry -> entry.getValue() <= now); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecallConfig.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecallConfig.java new file mode 100644 index 000000000..f1824c3c3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecallConfig.java @@ -0,0 +1,115 @@ +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.util.config.ConfigDescription; + +@ConfigDescription("Click with a clock to rewind to a recent snapshot with health and hunger restored.") +public class ChronosInstantRecallConfig { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Instant Recall adaptation.", impact = "True enables this behavior and false disables it.") + boolean playClockSounds = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Consumes one clock from the casting hand when a recall successfully starts.", impact = "True makes each recall cost a clock; false keeps recalls item-free.") + boolean consumeClock = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fraction of current health lost when a recall completes; never lethal, health is floored at 1.0.", impact = "Higher values make recalls cost more health; 0 disables the health cost.") + double healthCostFraction = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Rewind Trace Particles for the Chronos Instant Recall adaptation.", impact = "True enables this behavior and false disables it.") + boolean showRewindTraceParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Rewind Trace Points for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int rewindTracePoints = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Target rewind animation duration in milliseconds.", impact = "Higher values make recall rewind visuals slower/smoother; lower values make them faster.") + int rewindAnimationDurationMillis = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Legacy fallback rewind animation ticks used when duration is invalid.", impact = "Retained for backward compatibility with older configs.") + int rewindAnimationTicks = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Temporarily switches to spectator during rewind animation for smoother camera movement through obstacles.", impact = "True improves rewind smoothness; false keeps player in survival during rewind.") + boolean rewindUseTemporarySpectator = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Uses a client-side spectator camera anchor during rewind so server position only updates at the end.", impact = "True reduces jitter and rubber-banding by avoiding per-tick player teleports.") + boolean rewindUseClientCamera = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra ticks to suppress teleport XP/stat tracking after recall rewinds.", impact = "Higher values make it safer against teleport-XP side effects from other skills.") + int rewindTeleportXpSuppressExtraTicks = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables direct click-with-clock activation for instant recall.", impact = "True allows recall by clicking with a valid recall clock; false disables direct clock-click activation.") + boolean enableClockClickTrigger = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows left-click to activate recall when clock-click trigger is enabled.", impact = "True allows left-click activation; false blocks left-click activation.") + boolean clockClickLeftClick = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows right-click to activate recall when clock-click trigger is enabled.", impact = "True allows right-click activation; false blocks right-click activation.") + boolean clockClickRightClick = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables sprint + click activation for instant recall with a valid recall clock.", impact = "True allows sprint-click activation; false disables sprint-click activation.") + boolean enableSprintClickTrigger = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows left-click for sprint-click recall trigger.", impact = "True enables left-click sprint activation; false disables it.") + boolean sprintClickLeftClick = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows right-click for sprint-click recall trigger.", impact = "True enables right-click sprint activation; false disables it.") + boolean sprintClickRightClick = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows click-in-air interactions for recall click triggers.", impact = "True lets air-clicks activate enabled click modes; false blocks air-click activations.") + boolean allowAirClicks = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows click-on-block interactions for recall click triggers.", impact = "True lets block-clicks activate enabled click modes; false blocks block-click activations.") + boolean allowBlockClicks = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables single-sneak activation for instant recall.", impact = "True allows pressing sneak once to trigger recall.") + boolean enableSingleSneakTrigger = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Require sprinting for single-sneak instant recall trigger.", impact = "True requires sprint state when using single-sneak activation.") + boolean singleSneakRequiresSprint = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Require holding a valid recall clock in either hand for single-sneak trigger.", impact = "True keeps single-sneak recall tied to clock usage.") + boolean singleSneakRequiresClockInHand = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables double-tap jump activation for instant recall.", impact = "True allows jump-based recall trigger; false disables jump trigger.") + boolean enableDoubleJumpTrigger = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Require sprinting while double-jumping to trigger recall.", impact = "True requires sprint state for double-jump trigger; false allows it without sprint.") + boolean doubleJumpRequiresSprint = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Require holding a valid recall clock in either hand for double-jump trigger.", impact = "True requires clock-in-hand for jump trigger; false allows jump trigger without holding a clock.") + boolean doubleJumpRequiresClockInHand = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum milliseconds allowed between jump taps for double-jump recall.", impact = "Higher values make double-tap detection easier; lower values make it stricter.") + int doubleJumpWindowMillis = 450; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum upward velocity required to arm double-jump recall.", impact = "Higher values reduce accidental arm events; lower values increase sensitivity.") + double doubleJumpMinVerticalVelocity = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Rewind Seconds for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseRewindSeconds = 3.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Rewind Seconds Per Level for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rewindSecondsPerLevel = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap for recall rewind duration in seconds.", impact = "Prevents high levels/config values from exceeding this rewind window.") + double maxRewindSeconds = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Padding Seconds for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int cooldownPaddingSeconds = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Snapshot Interval Millis for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int snapshotIntervalMillis = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls History Padding Seconds for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int historyPaddingSeconds = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Rewind Protection Ticks for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int rewindProtectionTicks = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Distance Block for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerDistanceBlock = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Health Point for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerHealthPoint = 0.85; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Hunger Point for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerHungerPoint = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Saturation Point for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerSaturationPoint = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Level Multiplier Per Level for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpLevelMultiplierPerLevel = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Min Raw Reward for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpMinRawReward = 1.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Min Award for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpMinAward = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Max Award for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpMaxAward = 36; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Cross World Distance Credit for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpCrossWorldDistanceCredit = 16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Diminish Window Millis for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long xpDiminishWindowMillis = 45000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Diminish Min Multiplier for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpDiminishMinMultiplier = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Repeat Window Millis for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long xpRepeatWindowMillis = 180000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Repeat Source Radius for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpRepeatSourceRadius = 3.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Repeat Target Radius for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpRepeatTargetRadius = 3.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Repeat Penalty Multiplier for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpRepeatPenaltyMultiplier = 0.2; +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecallTypes.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecallTypes.java new file mode 100644 index 000000000..90f4ed010 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosInstantRecallTypes.java @@ -0,0 +1,40 @@ +package art.arcane.adapt.content.adaptation.chronos; + +record Snapshot(long timestamp, + String worldName, + double x, + double y, + double z, + float yaw, + float pitch, + double health, + int foodLevel, + float saturation, + float exhaustion, + int fireTicks) { +} + +record RecallXPContext(String fromWorld, + double fromX, + double fromY, + double fromZ, + String toWorld, + double toX, + double toY, + double toZ, + double distance, + double healthRecovered, + double hungerRecovered, + double saturationRecovered) { +} + +record RecallXPFarmStamp(long awardedAt, + String fromWorld, + double fromX, + double fromY, + double fromZ, + String toWorld, + double toX, + double toY, + double toZ) { +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosOvertime.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosOvertime.java new file mode 100644 index 000000000..fce924a17 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosOvertime.java @@ -0,0 +1,241 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityPotionEffectEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ChronosOvertime extends SimpleAdaptation { + private static final Set BENEFICIAL = buildBeneficialSet(); + + private final Set extending; + + public ChronosOvertime() { + super("chronos-overtime"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.overtime.description")); + setDisplayName(Localizer.dLocalize("chronos.overtime.name")); + setIcon(Material.GLISTERING_MELON_SLICE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(60000); + extending = ConcurrentHashMap.newKeySet(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLISTERING_MELON_SLICE) + .key("challenge_chronos_overtime_1k") + .title(Localizer.dLocalize("advancement.challenge_chronos_overtime_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_overtime_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_overtime_1k", "chronos.overtime.seconds-extended", 1000, 750); + } + + private static Set buildBeneficialSet() { + Set set = new HashSet<>(); + addIfPresent(set, PotionEffectType.SPEED); + addIfPresent(set, PotionEffectType.JUMP_BOOST); + addIfPresent(set, PotionEffectType.REGENERATION); + addIfPresent(set, PotionEffectType.RESISTANCE); + addIfPresent(set, PotionEffectType.FIRE_RESISTANCE); + addIfPresent(set, PotionEffectType.WATER_BREATHING); + addIfPresent(set, PotionEffectType.INVISIBILITY); + addIfPresent(set, PotionEffectType.NIGHT_VISION); + addIfPresent(set, PotionEffectType.HEALTH_BOOST); + addIfPresent(set, PotionEffectType.ABSORPTION); + addIfPresent(set, PotionEffectType.SATURATION); + addIfPresent(set, PotionEffectType.LUCK); + addIfPresent(set, PotionEffectType.SLOW_FALLING); + addIfPresent(set, PotionEffectType.DOLPHINS_GRACE); + addIfPresent(set, PotionEffectType.HERO_OF_THE_VILLAGE); + addIfPresent(set, PotionEffectTypes.FAST_DIGGING); + addIfPresent(set, PotionEffectTypes.INCREASE_DAMAGE); + return set; + } + + private static void addIfPresent(Set set, PotionEffectType type) { + if (type != null) { + set.add(type); + } + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Math.round(getExtensionPercent(level) * 100D) + "% " + Localizer.dLocalize("chronos.overtime.lore1")); + v.addLore(C.YELLOW + "+ " + (getConfig().maxBonusTicks / 20) + "s " + Localizer.dLocalize("chronos.overtime.lore2")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.overtime.lore3")); + } + + private double getExtensionPercent(int level) { + return Math.min(getConfig().maxExtensionPercent, + getConfig().baseExtensionPercent + (Math.max(1, level) * getConfig().extensionPercentPerLevel)); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + extending.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityPotionEffectEvent e) { + EntityPotionEffectEvent.Action action = e.getAction(); + if (action != EntityPotionEffectEvent.Action.ADDED && action != EntityPotionEffectEvent.Action.CHANGED) { + return; + } + + if (!(e.getEntity() instanceof Player p)) { + return; + } + + UUID id = p.getUniqueId(); + if (extending.contains(id)) { + return; + } + + PotionEffect newEffect = e.getNewEffect(); + if (newEffect == null) { + return; + } + + int duration = newEffect.getDuration(); + if (duration < getConfig().minimumDurationTicks || duration > getConfig().maximumBaseDurationTicks) { + return; + } + + PotionEffectType type = newEffect.getType(); + if (!BENEFICIAL.contains(type)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + int bonus = (int) Math.min(getConfig().maxBonusTicks, Math.round(duration * getExtensionPercent(level))); + if (bonus <= 0) { + return; + } + + int amplifier = newEffect.getAmplifier(); + int targetDuration = duration + bonus; + + Runnable extend = () -> { + if (!p.isOnline() || p.isDead()) { + return; + } + + PotionEffect current = p.getPotionEffect(type); + if (current == null + || current.getAmplifier() != amplifier + || current.getDuration() <= 0 + || current.getDuration() >= targetDuration) { + return; + } + + extending.add(id); + try { + p.addPotionEffect(new PotionEffect(type, targetDuration, amplifier, + current.isAmbient(), current.hasParticles(), current.hasIcon()), true); + } finally { + extending.remove(id); + } + + getPlayer(p).getData().addStat("chronos.overtime.seconds-extended", bonus / 20D); + xpSilent(p, Math.min(getConfig().maxXpPerExtension, (bonus / 20D) * getConfig().xpPerBonusSecond), "chronos:overtime"); + }; + + if (J.isFoliaThreading()) { + J.runEntity(p, extend, 1); + } else { + J.s(extend, 1); + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Beneficial potion effects applied to you last longer, scaled by adaptation level.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.38; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base duration extension as a fraction of the original duration.", impact = "Higher values extend beneficial effects more.") + double baseExtensionPercent = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra extension fraction per adaptation level.", impact = "Higher values make leveling extend effects faster.") + double extensionPercentPerLevel = 0.07; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on the duration extension fraction.", impact = "Higher values allow larger total extensions.") + double maxExtensionPercent = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum bonus ticks added to any single effect application.", impact = "Higher values let long potions gain bigger absolute extensions.") + int maxBonusTicks = 2400; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum original duration in ticks for an effect to be extended.", impact = "Higher values ignore short pulsing effects entirely.") + int minimumDurationTicks = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum original duration in ticks eligible for extension.", impact = "Lower values stop near permanent effects from being extended.") + int maximumBaseDurationTicks = 72000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per bonus second of effect duration.", impact = "Higher values grant more skill XP per extension.") + double xpPerBonusSecond = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum XP granted by a single extension.", impact = "Higher values allow bigger XP payouts per potion.") + double maxXpPerExtension = 8; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosPocketWatch.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosPocketWatch.java new file mode 100644 index 000000000..0b623ae6b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosPocketWatch.java @@ -0,0 +1,189 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ChronosPocketWatch extends SimpleAdaptation { + private static final long PULSE_MILLIS = 250L; + + private final Map airBudgetMillis; + + public ChronosPocketWatch() { + super("chronos-pocket-watch"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.pocket_watch.description")); + setDisplayName(Localizer.dLocalize("chronos.pocket_watch.name")); + setIcon(Material.FEATHER); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(PULSE_MILLIS); + airBudgetMillis = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FEATHER) + .key("challenge_chronos_pocket_watch_500") + .title(Localizer.dLocalize("advancement.challenge_chronos_pocket_watch_500.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_pocket_watch_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_pocket_watch_500", "chronos.pocket-watch.slow-fall-seconds", 500, 650); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + (getBudgetMillis(level) / 1000D) + "s " + Localizer.dLocalize("chronos.pocket_watch.lore1")); + v.addLore(C.YELLOW + "* " + Localizer.dLocalize("chronos.pocket_watch.lore2")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.pocket_watch.lore3")); + } + + private long getBudgetMillis(int level) { + return (long) ((getConfig().baseBudgetSeconds + (Math.max(1, level) * getConfig().budgetSecondsPerLevel)) * 1000D); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + airBudgetMillis.remove(e.getPlayer().getUniqueId()); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + + UUID id = p.getUniqueId(); + if (p.isOnGround()) { + airBudgetMillis.remove(id); + continue; + } + + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0) { + continue; + } + + if (p.isFlying() || p.isGliding() || p.isInsideVehicle() || p.isSwimming()) { + continue; + } + + if (!airBudgetMillis.containsKey(id) && p.getFallDistance() < getConfig().minFallDistance) { + continue; + } + + if (getConfig().requireClock && !p.getInventory().contains(Material.CLOCK)) { + continue; + } + + long budget = airBudgetMillis.computeIfAbsent(id, k -> getBudgetMillis(level)); + if (budget < PULSE_MILLIS) { + continue; + } + + airBudgetMillis.put(id, budget - PULSE_MILLIS); + + Runnable apply = () -> { + if (!p.isOnline() || p.isDead()) { + return; + } + + p.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, + getConfig().pulseDurationTicks, 0, true, false, false), true); + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.CLOUD, p.getLocation(), 1, 0.1, 0, 0.1, 0); + } + }; + + if (J.isFoliaThreading()) { + J.runEntity(p, apply); + } else { + apply.run(); + } + + getPlayer(p).getData().addStat("chronos.pocket-watch.slow-fall-seconds", PULSE_MILLIS / 1000D); + xpSilent(p, getConfig().xpPerPulse, "chronos:pocket-watch"); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak while airborne to fall in slow motion, with a level scaled time budget per airtime.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base seconds of slow falling sustainable per airtime.", impact = "Higher values let the player drift longer each fall.") + double baseBudgetSeconds = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra sustain seconds per adaptation level.", impact = "Higher values make leveling extend the drift budget faster.") + double budgetSecondsPerLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of each refreshing slow falling pulse.", impact = "Higher values leave slow falling lingering longer after sneaking stops.") + int pulseDurationTicks = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum fall distance in blocks before the slow fall kicks in.", impact = "Lower values start the slow fall earlier in the drop.") + double minFallDistance = 1.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Requires a clock anywhere in the inventory for the slow fall to apply.", impact = "False removes the item requirement entirely.") + boolean requireClock = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per slow fall pulse.", impact = "Higher values grant more skill XP while drifting.") + double xpPerPulse = 0.3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosRewind.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosRewind.java new file mode 100644 index 000000000..15e34c804 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosRewind.java @@ -0,0 +1,309 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ChronosRewind extends SimpleAdaptation { + private final Map snapshots; + private final Map cooldowns; + private final Set cooldownReadyNotify; + + public ChronosRewind() { + super("chronos-rewind"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.rewind.description")); + setDisplayName(Localizer.dLocalize("chronos.rewind.name")); + setIcon(Material.ENDER_EYE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + snapshots = new ConcurrentHashMap<>(); + cooldowns = new ConcurrentHashMap<>(); + cooldownReadyNotify = ConcurrentHashMap.newKeySet(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_chronos_rewind_50") + .title(Localizer.dLocalize("advancement.challenge_chronos_rewind_50.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_rewind_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.RECOVERY_COMPASS) + .key("challenge_chronos_rewind_500") + .title(Localizer.dLocalize("advancement.challenge_chronos_rewind_500.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_rewind_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_chronos_rewind_50", "chronos.rewind.rewinds", 50, 350); + registerMilestone("challenge_chronos_rewind_500", "chronos.rewind.rewinds", 500, 1400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.duration(getConfig().snapshotWindowMillis, 1) + " " + Localizer.dLocalize("chronos.rewind.lore1")); + v.addLore(C.RED + "* " + Form.duration(getCooldownMillis(level), 1) + " " + Localizer.dLocalize("chronos.rewind.lore2")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.rewind.lore3")); + if (getConfig().hungerCost > 0) { + v.addLore(C.RED + "* " + getConfig().hungerCost + " " + Localizer.dLocalize("chronos.rewind.lore_cost_hunger")); + } + } + + private long getCooldownMillis(int level) { + return Math.max(getConfig().minimumCooldownMillis, + getConfig().baseCooldownMillis - (Math.max(1, level) * getConfig().cooldownReductionPerLevelMillis)); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + snapshots.remove(id); + cooldowns.remove(id); + cooldownReadyNotify.remove(id); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerSwapHandItemsEvent e) { + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + Adaptation.BlockActionContext context = resolveInteractContext(p, p.getLocation()); + if (context == null) { + return; + } + + e.setCancelled(true); + UUID id = p.getUniqueId(); + long now = M.ms(); + + RewindSnapshot snapshot = snapshots.get(id); + if (snapshot != null && snapshot.expiresAt() > now) { + performRewind(p, context.level(), snapshot, now); + return; + } + + long cooldownUntil = cooldowns.getOrDefault(id, 0L); + if (cooldownUntil > now) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + return; + } + + snapshots.put(id, new RewindSnapshot(p.getLocation().clone(), p.getHealth(), p.getFoodLevel(), + now + Math.max(1000L, getConfig().snapshotWindowMillis))); + + if (getConfig().playClockSounds) { + ChronosSoundFX.playRewindStart(p); + } + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.REVERSE_PORTAL, p.getLocation().add(0, 1, 0), 14, 0.3, 0.5, 0.3, 0.02); + } + } + + private void performRewind(Player p, int level, RewindSnapshot snapshot, long now) { + UUID id = p.getUniqueId(); + int hungerCost = Math.max(0, getConfig().hungerCost); + if (hungerCost > 0 && p.getFoodLevel() < hungerCost) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + return; + } + + snapshots.remove(id); + + Location destination = snapshot.location(); + if (destination.getWorld() == null) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + return; + } + + cooldowns.put(id, now + getCooldownMillis(level)); + cooldownReadyNotify.add(id); + + Location departure = p.getLocation().clone(); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.REVERSE_PORTAL, departure.clone().add(0, 1, 0), 18, 0.3, 0.5, 0.3, 0.04); + p.getWorld().spawnParticle(Particle.PORTAL, departure.clone().add(0, 1, 0), 28, 0.4, 0.6, 0.4, 0.6); + } + + SoundPlayer.of(p.getWorld()).play(departure, Sound.ENTITY_ENDERMAN_TELEPORT, 0.7f, 0.65f); + + J.teleport(p, destination, PlayerTeleportEvent.TeleportCause.PLUGIN); + + Runnable restore = () -> { + if (!p.isOnline() || p.isDead()) { + return; + } + + if (p.getHealth() < snapshot.health()) { + AttributeInstance maxHealthAttribute = p.getAttribute(Attribute.MAX_HEALTH); + double maxHealth = maxHealthAttribute == null ? 20D : maxHealthAttribute.getValue(); + p.setHealth(Math.min(maxHealth, snapshot.health())); + } + + if (p.getFoodLevel() < snapshot.foodLevel()) { + p.setFoodLevel(Math.min(20, snapshot.foodLevel())); + } + + if (hungerCost > 0) { + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + } + + p.setFallDistance(0); + p.addPotionEffect(new PotionEffect(PotionEffectType.DARKNESS, 35, 0, true, false, false)); + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation().add(0, 1, 0), 10, 0.25, 0.4, 0.25, 0.02); + p.getWorld().spawnParticle(Particle.REVERSE_PORTAL, p.getLocation().add(0, 1, 0), 22, 0.35, 0.55, 0.35, 0.05); + p.getWorld().spawnParticle(Particle.PORTAL, p.getLocation().add(0, 1, 0), 28, 0.4, 0.6, 0.4, 0.6); + } + + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.7f, 0.85f); + }; + + if (J.isFoliaThreading()) { + J.runEntity(p, restore, 1); + } else { + J.s(restore, 1); + } + + if (getConfig().playClockSounds) { + ChronosSoundFX.playRewindFinish(p); + } + + getPlayer(p).getData().addStat("chronos.rewind.rewinds", 1); + xp(p, destination, getConfig().xpOnRewind + (level * getConfig().xpPerLevel)); + } + + @Override + public void onTick() { + long now = M.ms(); + snapshots.entrySet().removeIf(entry -> entry.getValue().expiresAt() <= now); + + for (Iterator iterator = cooldownReadyNotify.iterator(); iterator.hasNext(); ) { + UUID id = iterator.next(); + Player p = Bukkit.getPlayer(id); + if (p == null) { + iterator.remove(); + continue; + } + + if (cooldowns.getOrDefault(id, 0L) <= now) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playCooldownReady(p); + } + iterator.remove(); + } + } + + cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak and swap hands to mark a moment in time, then do it again within the window to snap back with health and hunger restored.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Rewind adaptation.", impact = "True enables this behavior and false disables it.") + boolean playClockSounds = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Food points consumed when a rewind completes.", impact = "Higher values make each rewind cost more hunger; 0 disables the cost.") + int hungerCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Window in milliseconds after marking a snapshot during which the rewind can be completed.", impact = "Higher values give more time to trigger the snap back.") + long snapshotWindowMillis = 10000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base cooldown in milliseconds applied after a completed rewind.", impact = "Higher values force longer waits between rewinds.") + long baseCooldownMillis = 45000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown reduction in milliseconds per adaptation level.", impact = "Higher values make leveling shorten the cooldown faster.") + long cooldownReductionPerLevelMillis = 4000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Lowest possible cooldown in milliseconds regardless of level.", impact = "Higher values keep a floor under cooldown reduction.") + long minimumCooldownMillis = 15000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted when a rewind completes.", impact = "Higher values grant more skill XP per rewind.") + double xpOnRewind = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra XP granted per adaptation level on rewind.", impact = "Higher values scale rewind XP with level faster.") + double xpPerLevel = 3; + } + + private record RewindSnapshot(Location location, double health, + int foodLevel, long expiresAt) { + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosSoundFX.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosSoundFX.java new file mode 100644 index 000000000..0ef659d5c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosSoundFX.java @@ -0,0 +1,214 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.util.common.scheduling.J; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.entity.Player; + +public final class ChronosSoundFX { + private ChronosSoundFX() { + } + + private static void play(Location location, Sound sound, float volume, float pitch) { + if (location == null || location.getWorld() == null) { + return; + } + if (!areSoundsEnabled()) { + return; + } + + Location at = location.clone(); + Runnable playTask = () -> { + World world = at.getWorld(); + if (world != null) { + world.playSound(at, sound, volume, pitch); + } + }; + + if (J.isPrimaryThread()) { + playTask.run(); + } else { + J.runAt(at, playTask); + } + } + + private static void playLater(Location location, Sound sound, float volume, float pitch, int delayTicks) { + if (location == null || location.getWorld() == null) { + return; + } + if (!areSoundsEnabled()) { + return; + } + + Location at = location.clone(); + Runnable playTask = () -> { + World world = at.getWorld(); + if (world != null) { + world.playSound(at, sound, volume, pitch); + } + }; + + if (delayTicks <= 0 && J.isPrimaryThread()) { + playTask.run(); + return; + } + + J.runAt(at, playTask, Math.max(0, delayTicks)); + } + + private static void playOnPlayer(Player player, Sound sound, float volume, float pitch) { + if (player == null || !player.isOnline()) { + return; + } + if (!areSoundsEnabled()) { + return; + } + + Runnable playTask = () -> { + if (!player.isOnline()) { + return; + } + Location at = player.getLocation(); + World world = at.getWorld(); + if (world != null) { + world.playSound(at, sound, volume, pitch); + } + }; + + if (J.isPrimaryThread()) { + playTask.run(); + } else { + J.runEntity(player, playTask); + } + } + + private static void playOnPlayerLater(Player player, Sound sound, float volume, float pitch, int delayTicks) { + if (player == null || !player.isOnline()) { + return; + } + if (!areSoundsEnabled()) { + return; + } + + Runnable playTask = () -> { + if (!player.isOnline()) { + return; + } + Location at = player.getLocation(); + World world = at.getWorld(); + if (world != null) { + world.playSound(at, sound, volume, pitch); + } + }; + + if (delayTicks <= 0 && J.isPrimaryThread()) { + playTask.run(); + return; + } + + J.runEntity(player, playTask, Math.max(0, delayTicks)); + } + + public static void playClockReject(Player p) { + Location l = p.getLocation(); + play(l, Sound.BLOCK_NOTE_BLOCK_BASS, 0.42f, 0.58f); + play(l, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.32f, 0.62f); + } + + public static void playCooldownReady(Player p) { + Location l = p.getLocation(); + play(l, Sound.BLOCK_LEVER_CLICK, 0.35f, 1.75f); + playLater(l, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.28f, 1.93f, 1); + } + + public static void playBottleUse(Player p, Location at, int advanceTicks) { + float pitch = Math.min(1.95f, 0.8f + (advanceTicks / 175f)); + play(at, Sound.BLOCK_LEVER_CLICK, 0.55f, pitch); + play(at, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.35f, Math.min(2f, pitch + 0.24f)); + playLater(at, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.62f, Math.min(2f, pitch + 0.12f), 2); + } + + public static void playRewindStart(Player p) { + Location l = p.getLocation(); + play(l, Sound.BLOCK_NOTE_BLOCK_BASS, 0.45f, 0.82f); + play(l, Sound.BLOCK_LEVER_CLICK, 0.5f, 0.75f); + } + + public static void playRewindStep(Player p, float progress) { + Location l = p.getLocation(); + float clamped = Math.max(0f, Math.min(1f, progress)); + float pitch = 0.74f + (clamped * 0.95f); + play(l, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.2f, Math.min(2f, pitch + 0.1f)); + play(l, Sound.BLOCK_LEVER_CLICK, 0.24f, pitch); + } + + public static void playRewindFinish(Player p) { + // Reverse wind-up + 3 tines with varied tone/volume, all following the caster. + playOnPlayer(p, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.68f, 0.9f); + playOnPlayerLater(p, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.76f, 1.16f, 1); + playOnPlayerLater(p, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.84f, 1.42f, 2); + + // Low tine + playOnPlayerLater(p, Sound.BLOCK_BELL_RESONATE, 1.70f, 0.78f, 3); + playOnPlayerLater(p, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.88f, 1.28f, 4); + + // Mid tine + playOnPlayerLater(p, Sound.BLOCK_BELL_RESONATE, 1.44f, 0.98f, 7); + playOnPlayerLater(p, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.80f, 1.5f, 8); + + // High tine + trailing echo + playOnPlayerLater(p, Sound.BLOCK_BELL_RESONATE, 1.16f, 1.2f, 11); + playOnPlayerLater(p, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.72f, 1.74f, 12); + playOnPlayerLater(p, Sound.BLOCK_BELL_RESONATE, 0.56f, 1.34f, 15); + } + + public static void playTouchProc(Player p, Location target) { + Location l = target == null ? p.getLocation() : target; + play(l, Sound.BLOCK_LEVER_CLICK, 0.34f, 1.7f); + play(l, Sound.BLOCK_NOTE_BLOCK_BASS, 0.26f, 1.18f); + } + + public static void playTimeBombArm(Player p) { + Location l = p.getLocation(); + play(l, Sound.BLOCK_LEVER_CLICK, 0.46f, 1.35f); + play(l, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.35f, 1.1f); + } + + public static void playTimeBombDetonate(Location center) { + play(center, Sound.BLOCK_NOTE_BLOCK_BASS, 0.8f, 0.6f); + play(center, Sound.BLOCK_LEVER_CLICK, 0.8f, 0.68f); + playLater(center, Sound.BLOCK_BELL_RESONATE, 0.55f, 1.05f, 2); + } + + public static void playTimeFieldTick(Location center, float pitch) { + float clamped = Math.max(0.35f, Math.min(2f, pitch)); + play(center, Sound.BLOCK_NOTE_BLOCK_BASS, 0.14f, Math.max(0.3f, Math.min(2f, clamped * 0.78f))); + play(center, Sound.BLOCK_LEVER_CLICK, 0.22f, clamped); + play(center, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.15f, Math.min(2f, clamped + 0.18f)); + } + + private static boolean areSoundsEnabled() { + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + return effects == null || effects.isSoundsEnabled(); + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosStasisField.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosStasisField.java new file mode 100644 index 000000000..cea114588 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosStasisField.java @@ -0,0 +1,429 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ThreadLocalRandom; + +public class ChronosStasisField extends SimpleAdaptation { + private final Map cooldowns; + private final List bubbles; + private final Map frozenProjectiles; + + public ChronosStasisField() { + super("chronos-stasis-field"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.stasis_field.description")); + setDisplayName(Localizer.dLocalize("chronos.stasis_field.name")); + setIcon(Material.AMETHYST_SHARD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(250); + cooldowns = new ConcurrentHashMap<>(); + bubbles = new CopyOnWriteArrayList<>(); + frozenProjectiles = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.AMETHYST_SHARD) + .key("challenge_chronos_stasis_50") + .title(Localizer.dLocalize("advancement.challenge_chronos_stasis_50.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_stasis_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CLOCK) + .key("challenge_chronos_stasis_500") + .title(Localizer.dLocalize("advancement.challenge_chronos_stasis_500.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_stasis_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_chronos_stasis_50", "chronos.stasis-field.casts", 50, 400); + registerMilestone("challenge_chronos_stasis_500", "chronos.stasis-field.casts", 500, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + " " + Localizer.dLocalize("chronos.stasis_field.lore1")); + v.addLore(C.YELLOW + "+ " + Form.duration(getDurationMillis(level), 1) + " " + Localizer.dLocalize("chronos.stasis_field.lore2")); + v.addLore(C.RED + "* " + Form.duration(getCooldownMillis(), 1) + " " + Localizer.dLocalize("chronos.stasis_field.lore3")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.stasis_field.lore4")); + if (getConfig().consumeShard) { + v.addLore(C.RED + "* " + Localizer.dLocalize("chronos.stasis_field.lore_cost_shard")); + } + } + + private double getRadius(int level) { + return getConfig().baseRadius + ((Math.max(1, level) - 1) * getConfig().radiusPerLevel); + } + + private long getDurationMillis(int level) { + return getConfig().baseDurationMillis + ((Math.max(1, level) - 1L) * getConfig().durationPerLevelMillis); + } + + private long getCooldownMillis() { + return Math.max(0L, getConfig().cooldownMillis); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + EquipmentSlot hand = e.getHand(); + if (hand == null) { + return; + } + + ItemStack held = hand == EquipmentSlot.OFF_HAND + ? p.getInventory().getItemInOffHand() + : p.getInventory().getItemInMainHand(); + if (held.getType() != Material.AMETHYST_SHARD) { + return; + } + + Adaptation.BlockActionContext context = resolveInteractContext(p, p.getLocation()); + if (context == null) { + return; + } + + e.setCancelled(true); + long now = M.ms(); + long until = cooldowns.getOrDefault(p.getUniqueId(), 0L); + if (until > now) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + return; + } + + if (getConfig().consumeShard) { + held.setAmount(held.getAmount() - 1); + } + + cooldowns.put(p.getUniqueId(), now + getCooldownMillis()); + deployBubble(p, context.level(), now); + } + + private void deployBubble(Player p, int level, long now) { + Location center = p.getLocation().clone().add(0, getConfig().centerYOffset, 0); + bubbles.add(new StasisBubble(p.getUniqueId(), center, getRadius(level), now + getDurationMillis(level), 0L)); + + if (getConfig().playClockSounds) { + ChronosSoundFX.playTimeBombDetonate(center); + } + + getPlayer(p).getData().addStat("chronos.stasis-field.casts", 1); + xp(p, center, getConfig().xpOnCast + (level * getConfig().xpPerLevel)); + } + + @Override + public void onTick() { + long now = M.ms(); + cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + if (bubbles.isEmpty() && frozenProjectiles.isEmpty()) { + return; + } + + for (StasisBubble bubble : bubbles) { + if (bubble.expiresAt() <= now) { + bubbles.remove(bubble); + releaseBubble(bubble); + continue; + } + + if (J.isFoliaThreading()) { + J.runAt(bubble.center(), () -> pulse(bubble, now)); + } else { + pulse(bubble, now); + } + } + + if (bubbles.isEmpty() && !frozenProjectiles.isEmpty()) { + releaseOrphans(); + } + } + + private void releaseBubble(StasisBubble bubble) { + for (Map.Entry entry : frozenProjectiles.entrySet()) { + if (!entry.getValue().bubbleId().equals(bubble.id())) { + continue; + } + + frozenProjectiles.remove(entry.getKey()); + restoreProjectile(entry.getKey(), entry.getValue()); + } + } + + private void releaseOrphans() { + for (Map.Entry entry : frozenProjectiles.entrySet()) { + frozenProjectiles.remove(entry.getKey()); + restoreProjectile(entry.getKey(), entry.getValue()); + } + } + + private void restoreProjectile(UUID entityId, FrozenProjectileState state) { + Entity entity = Bukkit.getEntity(entityId); + if (entity == null || entity.isDead() || !entity.isValid()) { + return; + } + + Runnable restore = () -> { + if (entity.isDead() || !entity.isValid()) { + return; + } + + if (getConfig().removeProjectilesOnExpire) { + entity.remove(); + return; + } + + entity.setGravity(state.gravity()); + entity.setVelocity(state.velocity()); + }; + + if (J.isFoliaThreading()) { + J.runEntity(entity, restore); + } else { + restore.run(); + } + } + + private void pulse(StasisBubble bubble, long now) { + World world = bubble.center().getWorld(); + if (world == null) { + return; + } + + double radius = bubble.radius(); + double radiusSq = radius * radius; + Player owner = Bukkit.getPlayer(bubble.owner()); + + for (Entity entity : world.getNearbyEntities(bubble.center(), radius, radius, radius)) { + if (entity.getLocation().distanceSquared(bubble.center()) > radiusSq) { + continue; + } + + if (entity instanceof Projectile projectile) { + if (!frozenProjectiles.containsKey(projectile.getUniqueId())) { + frozenProjectiles.put(projectile.getUniqueId(), + new FrozenProjectileState(bubble.id(), projectile.getVelocity().clone(), projectile.hasGravity())); + if (owner != null) { + getPlayer(owner).getData().addStat("chronos.stasis-field.projectiles-frozen", 1); + } + } + + projectile.setGravity(false); + projectile.setVelocity(new Vector()); + continue; + } + + if (entity instanceof LivingEntity living && !(entity instanceof Player) && !entity.getUniqueId().equals(bubble.owner())) { + if (isProtectedFriendly(owner, living)) { + continue; + } + + living.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getConfig().effectRefreshTicks, getConfig().slownessAmplifier, true, false, false), true); + if (PotionEffectTypes.JUMP != null) { + living.addPotionEffect(new PotionEffect(PotionEffectTypes.JUMP, getConfig().effectRefreshTicks, getConfig().jumpLockAmplifier, true, false, false), true); + } + } + } + + if (areParticlesEnabled() && now >= bubble.nextVisualAt()) { + spawnOutline(world, bubble); + bubble.setNextVisualAt(now + Math.max(100L, getConfig().outlineRefreshMillis)); + } + } + + private void spawnOutline(World world, StasisBubble bubble) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + int points = Math.max(4, getConfig().outlineParticleCount); + double radius = bubble.radius(); + for (int i = 0; i < points; i++) { + double theta = random.nextDouble() * Math.PI * 2D; + double phi = Math.acos((random.nextDouble() * 2D) - 1D); + double x = bubble.center().getX() + (radius * Math.sin(phi) * Math.cos(theta)); + double y = bubble.center().getY() + (radius * Math.cos(phi)); + double z = bubble.center().getZ() + (radius * Math.sin(phi) * Math.sin(theta)); + world.spawnParticle(Particle.END_ROD, x, y, z, 1, 0, 0, 0, 0); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak and right click with an amethyst shard to deploy a stasis bubble that freezes projectiles and locks down mobs inside.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Stasis Field adaptation.", impact = "True enables this behavior and false disables it.") + boolean playClockSounds = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Consumes the amethyst shard used to deploy a stasis bubble.", impact = "True makes each cast cost one shard; false keeps casts item-free.") + boolean consumeShard = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base radius of the stasis bubble in blocks.", impact = "Higher values freeze projectiles and slow mobs in a larger area.") + double baseRadius = 3.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra bubble radius granted per adaptation level.", impact = "Higher values make leveling expand the bubble faster.") + double radiusPerLevel = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base bubble lifetime in milliseconds.", impact = "Higher values keep the stasis bubble active longer.") + long baseDurationMillis = 3000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra bubble lifetime in milliseconds per adaptation level.", impact = "Higher values make leveling extend the bubble duration faster.") + long durationPerLevelMillis = 750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown between bubble deployments in milliseconds.", impact = "Higher values force longer waits between casts.") + long cooldownMillis = 20000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Vertical offset of the bubble center above the caster.", impact = "Higher values raise the bubble center off the ground.") + double centerYOffset = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Slowness amplifier applied to mobs inside the bubble.", impact = "Higher values slow trapped mobs more severely.") + int slownessAmplifier = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Jump boost amplifier applied to mobs inside the bubble; negative values prevent jumping.", impact = "Lower negative values suppress jumping harder.") + int jumpLockAmplifier = -6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of each refreshed potion pulse on trapped mobs.", impact = "Higher values keep effects on mobs longer between pulses.") + int effectRefreshTicks = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Removes frozen projectiles when the bubble expires instead of restoring their motion.", impact = "True deletes trapped projectiles; false releases them with their original velocity.") + boolean removeProjectilesOnExpire = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Number of outline particles spawned per visual refresh.", impact = "Higher values make the bubble edge denser at more visual cost.") + int outlineParticleCount = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds between bubble outline particle refreshes.", impact = "Lower values redraw the outline more often at more visual cost.") + long outlineRefreshMillis = 400; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted when a bubble is deployed.", impact = "Higher values grant more skill XP per cast.") + double xpOnCast = 22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra XP granted per adaptation level on cast.", impact = "Higher values scale cast XP with level faster.") + double xpPerLevel = 3; + } + + private static final class StasisBubble { + private final UUID id; + private final UUID owner; + private final Location center; + private final double radius; + private final long expiresAt; + private long nextVisualAt; + + private StasisBubble(UUID owner, Location center, double radius, long expiresAt, long nextVisualAt) { + this.id = UUID.randomUUID(); + this.owner = owner; + this.center = center; + this.radius = radius; + this.expiresAt = expiresAt; + this.nextVisualAt = nextVisualAt; + } + + private UUID id() { + return id; + } + + private UUID owner() { + return owner; + } + + private Location center() { + return center; + } + + private double radius() { + return radius; + } + + private long expiresAt() { + return expiresAt; + } + + private long nextVisualAt() { + return nextVisualAt; + } + + private void setNextVisualAt(long nextVisualAt) { + this.nextVisualAt = nextVisualAt; + } + } + + private record FrozenProjectileState(UUID bubbleId, Vector velocity, + boolean gravity) { + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTemporalEcho.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTemporalEcho.java new file mode 100644 index 000000000..a805e6b1d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTemporalEcho.java @@ -0,0 +1,239 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Egg; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Snowball; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class ChronosTemporalEcho extends SimpleAdaptation { + private static final String ECHO_META = "adapt-chronos-temporal-echo"; + private final Map cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + + public ChronosTemporalEcho() { + super("chronos-temporal-echo"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.temporal_echo.description")); + setDisplayName(Localizer.dLocalize("chronos.temporal_echo.name")); + setIcon(Material.AMETHYST_CLUSTER); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1600); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_chronos_echo_200") + .title(Localizer.dLocalize("advancement.challenge_chronos_echo_200.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_echo_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_echo_200", "chronos.temporal-echo.echo-hits", 200, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.duration(getEchoDelayTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("chronos.temporal_echo.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getEchoVelocityFactor(level), 0) + C.GRAY + " " + Localizer.dLocalize("chronos.temporal_echo.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("chronos.temporal_echo.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(ProjectileLaunchEvent e) { + if (!(e.getEntity().getShooter() instanceof Player p) || !hasActiveAdaptation(p) || e.getEntity().hasMetadata(ECHO_META)) { + return; + } + + EchoType echoType = getEchoType(e.getEntity()); + if (echoType == null) { + return; + } + + int level = getActiveLevel(p); + long now = System.currentTimeMillis(); + if (now < cooldowns.getOrDefault(p.getUniqueId(), 0L)) { + return; + } + + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + Projectile original = e.getEntity(); + Vector originalVelocity = original.getVelocity().clone(); + int delay = getEchoDelayTicks(level); + J.runEntity(p, () -> spawnEcho(p, echoType, originalVelocity, level), delay); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(ProjectileHitEvent e) { + if (e.getHitEntity() == null || !e.getEntity().hasMetadata(ECHO_META)) { + return; + } + + if (!(e.getHitEntity() instanceof LivingEntity target)) { + return; + } + + Player shooter = e.getEntity().getShooter() instanceof Player p ? p : null; + if (isProtectedFriendly(shooter, target)) { + return; + } + + target.setNoDamageTicks(0); + target.setLastDamage(0.0D); + } + + private void spawnEcho(Player p, EchoType type, Vector velocity, int level) { + if (!p.isOnline() || p.isDead()) { + return; + } + + Projectile echo = switch (type) { + case ARROW -> + p.getWorld().spawnArrow(p.getEyeLocation().add(p.getLocation().getDirection().normalize().multiply(0.35)), + p.getLocation().getDirection(), 0.6f, 0f); + case SNOWBALL -> + p.getWorld().spawn(p.getEyeLocation().add(p.getLocation().getDirection().normalize().multiply(0.35)), Snowball.class); + case EGG -> + p.getWorld().spawn(p.getEyeLocation().add(p.getLocation().getDirection().normalize().multiply(0.35)), Egg.class); + case ENDER_PEARL -> + p.getWorld().spawn(p.getEyeLocation().add(p.getLocation().getDirection().normalize().multiply(0.35)), EnderPearl.class); + }; + echo.setShooter(p); + echo.setVelocity(velocity.multiply(getEchoVelocityFactor(level))); + echo.setMetadata(ECHO_META, new FixedMetadataValue(Adapt.instance, true)); + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.75f, 1.35f); + getPlayer(p).getData().addStat("chronos.temporal-echo.echo-hits", 1); + xp(p, getConfig().xpPerEcho); + } + + private EchoType getEchoType(Projectile projectile) { + if (projectile instanceof Arrow) { + return EchoType.ARROW; + } + + if (projectile instanceof Snowball) { + return EchoType.SNOWBALL; + } + + if (projectile instanceof Egg) { + return EchoType.EGG; + } + + if (projectile instanceof EnderPearl) { + return EchoType.ENDER_PEARL; + } + + return null; + } + + private int getEchoDelayTicks(int level) { + return Math.max(1, (int) Math.round(getConfig().echoDelayTicksBase - (getLevelPercent(level) * getConfig().echoDelayTicksFactor))); + } + + private double getEchoVelocityFactor(int level) { + return Math.min(getConfig().maxEchoVelocityFactor, getConfig().echoVelocityFactorBase + (getLevelPercent(level) * getConfig().echoVelocityFactorFactor)); + } + + private long getCooldownMillis(int level) { + return Math.max(500L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private enum EchoType { + ARROW, + SNOWBALL, + EGG, + ENDER_PEARL + } + + @NoArgsConstructor + @ConfigDescription("Replays your last projectile action shortly after firing at reduced power.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Echo Delay Ticks Base for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double echoDelayTicksBase = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Echo Delay Ticks Factor for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double echoDelayTicksFactor = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Echo Velocity Factor Base for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double echoVelocityFactorBase = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Echo Velocity Factor Factor for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double echoVelocityFactorFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Echo Velocity Factor for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxEchoVelocityFactor = 0.92; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 2600; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Echo for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerEcho = 12; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTimeBomb.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTimeBomb.java new file mode 100644 index 000000000..0ac394a1b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTimeBomb.java @@ -0,0 +1,812 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.content.item.ChronoTimeBombItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.LingeringPotionSplashEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ChronosTimeBomb extends SimpleAdaptation { + private static final EnumSet SUPPORTED_ACTIONS = EnumSet.of( + Action.RIGHT_CLICK_AIR, + Action.RIGHT_CLICK_BLOCK + ); + private static final long PROJECTILE_TRACK_TTL_MS = 15000L; + + private final Map cooldowns; + private final Set cooldownReadyNotify; + private final List fields; + private final Map activeBombProjectiles; + private final Map frozenEntities; + private final Map frozenPlayers; + private final AtomicBoolean syncTickQueued; + + public ChronosTimeBomb() { + super("chronos-time-bomb"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.time_bomb.description")); + setDisplayName(Localizer.dLocalize("chronos.time_bomb.name")); + setIcon(Material.TNT); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(50); + + registerRecipe(AdaptRecipe.shapeless() + .key("chronos-time-bomb") + .ingredient(Material.SNOWBALL) + .ingredient(Material.CLOCK) + .ingredient(Material.DIAMOND) + .ingredient(Material.SAND) + .result(ChronoTimeBombItem.withData()) + .build()); + + cooldowns = new ConcurrentHashMap<>(); + cooldownReadyNotify = ConcurrentHashMap.newKeySet(); + fields = new CopyOnWriteArrayList<>(); + activeBombProjectiles = new ConcurrentHashMap<>(); + frozenEntities = new ConcurrentHashMap<>(); + frozenPlayers = new ConcurrentHashMap<>(); + syncTickQueued = new AtomicBoolean(false); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ICE) + .key("challenge_chronos_bomb_freeze_50") + .title(Localizer.dLocalize("advancement.challenge_chronos_bomb_freeze_50.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_bomb_freeze_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ICE) + .key("challenge_chronos_bomb_crowd_8") + .title(Localizer.dLocalize("advancement.challenge_chronos_bomb_crowd_8.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_bomb_crowd_8.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_chronos_bomb_freeze_50", "chronos.time-bomb.projectiles-frozen", 50, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getRadius(level) + " " + Localizer.dLocalize("chronos.time_bomb.lore1")); + v.addLore(C.YELLOW + "+ " + (getDurationTicks(level) / 20D) + "s " + Localizer.dLocalize("chronos.time_bomb.lore2")); + v.addLore(C.RED + "* " + (getCooldownMillis() / 1000D) + "s " + Localizer.dLocalize("chronos.time_bomb.lore3")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.time_bomb.lore4")); + } + + private double getRadius(int level) { + return getConfig().baseRadius + ((Math.max(1, level) - 1) * getConfig().radiusPerLevel); + } + + private int getDurationTicks(int level) { + return getConfig().baseDurationTicks + ((Math.max(1, level) - 1) * getConfig().durationPerLevelTicks); + } + + private long getCooldownMillis() { + return Math.max(0L, getConfig().cooldownMillis); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + cooldowns.remove(id); + cooldownReadyNotify.remove(id); + frozenPlayers.remove(id); + activeBombProjectiles.entrySet().removeIf(entry -> entry.getValue().owner().equals(id)); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (!SUPPORTED_ACTIONS.contains(action)) { + return; + } + + EquipmentSlot handSlot = e.getHand(); + if (handSlot == null) { + return; + } + + Player p = e.getPlayer(); + if (handSlot == EquipmentSlot.OFF_HAND && ChronoTimeBombItem.isBindableItem(p.getInventory().getItemInMainHand())) { + return; + } + + ItemStack hand = getItemInHand(p, handSlot); + if (!ChronoTimeBombItem.isBindableItem(hand)) { + return; + } + + if (!hasActiveAdaptation(p)) { + e.setCancelled(true); + return; + } + + long now = M.ms(); + long cooldown = cooldowns.getOrDefault(p.getUniqueId(), 0L); + if (cooldown > now) { + e.setCancelled(true); + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + } + } + + private ItemStack getItemInHand(Player p, EquipmentSlot handSlot) { + return handSlot == EquipmentSlot.OFF_HAND + ? p.getInventory().getItemInOffHand() + : p.getInventory().getItemInMainHand(); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(ProjectileLaunchEvent e) { + if (!(e.getEntity() instanceof ThrownPotion potion)) { + return; + } + + if (!(potion.getShooter() instanceof Player p)) { + return; + } + + ItemStack item = potion.getItem(); + if (!ChronoTimeBombItem.isBindableItem(item)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + e.setCancelled(true); + return; + } + + long now = M.ms(); + long cooldown = cooldowns.getOrDefault(p.getUniqueId(), 0L); + if (cooldown > now) { + e.setCancelled(true); + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + return; + } + + cooldowns.put(p.getUniqueId(), now + getCooldownMillis()); + cooldownReadyNotify.add(p.getUniqueId()); + activeBombProjectiles.put(potion.getUniqueId(), new ArmedBombProjectile(p.getUniqueId(), level, now)); + + if (getConfig().playClockSounds) { + ChronosSoundFX.playTimeBombArm(p); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(LingeringPotionSplashEvent e) { + ThrownPotion potion = e.getEntity(); + UUID projectileId = potion.getUniqueId(); + ArmedBombProjectile armed = activeBombProjectiles.remove(projectileId); + boolean bindable = ChronoTimeBombItem.isBindableItem(potion.getItem()); + + if (!bindable && armed == null) { + return; + } + + if (e.getAreaEffectCloud() != null) { + e.getAreaEffectCloud().remove(); + } + + if (armed == null) { + return; + } + + Location deployCenter = potion.getLocation().clone().add(0, getConfig().fieldCenterYOffset, 0); + Player owner = Bukkit.getPlayer(armed.owner()); + if (owner != null && !canInteract(owner, deployCenter)) { + return; + } + + deployField(armed.owner(), armed.level(), deployCenter); + } + + private void deployField(UUID ownerId, int level, Location center) { + TemporalField field = new TemporalField( + ownerId, + center, + getRadius(level), + M.ms() + (getDurationTicks(level) * 50L), + level, + M.ms(), + M.ms()); + fields.add(field); + + if (getConfig().playClockSounds) { + ChronosSoundFX.playTimeBombDetonate(center); + } + + if (areParticlesEnabled() && center.getWorld() != null) { + center.getWorld().spawnParticle(Particle.ENCHANT, center, 45, field.radius() * 0.4, 0.35, field.radius() * 0.4, 0.15); + center.getWorld().spawnParticle(Particle.END_ROD, center, 20, field.radius() * 0.2, 0.2, field.radius() * 0.2, 0.02); + } + + Player owner = Bukkit.getPlayer(ownerId); + if (owner != null) { + xp(owner, center, getConfig().xpOnCast + (level * getConfig().xpPerLevel)); + } + } + + private void freezeEntity(Entity entity) { + if (entity instanceof Player) { + return; + } + + UUID id = entity.getUniqueId(); + FrozenEntityState state = frozenEntities.get(id); + if (state == null) { + Boolean hadAi = null; + if (entity instanceof LivingEntity living) { + hadAi = living.hasAI(); + } + + state = new FrozenEntityState(entity.getVelocity().clone(), entity.hasGravity(), hadAi); + frozenEntities.put(id, state); + } else if (getConfig().accumulateFrozenImpulse) { + state.captureImpulse(entity.getVelocity(), getConfig().frozenImpulseMinMagnitude, getConfig().frozenImpulseSampleCap); + } + + entity.setGravity(false); + entity.setVelocity(new Vector()); + entity.setFallDistance(0); + + if (entity instanceof LivingEntity living) { + living.setAI(false); + } + } + + private void freezePlayer(Player player) { + UUID id = player.getUniqueId(); + if (!frozenPlayers.containsKey(id)) { + frozenPlayers.put(id, new FrozenPlayerState(player.getAllowFlight(), player.isFlying(), player.hasGravity())); + } + + if (!getConfig().freezePlayersInAir || player.isOnGround()) { + return; + } + + player.setFallDistance(0); + player.setAllowFlight(true); + player.setFlying(true); + player.setGravity(false); + player.setVelocity(new Vector()); + } + + private void unfreezePlayer(UUID playerId, FrozenPlayerState state) { + Player player = Bukkit.getPlayer(playerId); + if (player == null || !player.isOnline()) { + return; + } + + player.setGravity(state.gravity()); + if (state.allowFlight()) { + player.setAllowFlight(true); + player.setFlying(state.flying()); + } else { + player.setFlying(false); + player.setAllowFlight(false); + } + } + + private boolean shouldFreezePlayer(Player player) { + if (!getConfig().freezePlayersInAir) { + return false; + } + + UUID playerId = player.getUniqueId(); + for (TemporalField field : fields) { + if (field.center().getWorld() == null || !field.center().getWorld().equals(player.getWorld())) { + continue; + } + + if (field.center().distanceSquared(player.getLocation()) > field.radius() * field.radius()) { + continue; + } + + if (playerId.equals(field.owner())) { + continue; + } + + Player owner = Bukkit.getPlayer(field.owner()); + if (owner != null && !canDamageTarget(owner, player)) { + continue; + } + + return true; + } + + return false; + } + + private void unfreezeEntity(UUID entityId, FrozenEntityState state) { + Entity entity = Bukkit.getEntity(entityId); + if (entity == null || entity.isDead() || !entity.isValid()) { + return; + } + + entity.setGravity(state.gravity()); + entity.setVelocity(state.buildReleaseVelocity(getConfig().frozenImpulseReleaseCap)); + + if (entity instanceof LivingEntity living && state.hadAi() != null) { + living.setAI(state.hadAi()); + } + } + + private boolean isInsideAnyField(Location location) { + if (location.getWorld() == null) { + return false; + } + + for (TemporalField field : fields) { + if (field.center().getWorld() == null || !field.center().getWorld().equals(location.getWorld())) { + continue; + } + + if (field.center().distanceSquared(location) <= field.radius() * field.radius()) { + return true; + } + } + + return false; + } + + private void applyField(TemporalField field, long now) { + if (field.center().getWorld() == null) { + return; + } + + Player owner = Bukkit.getPlayer(field.owner()); + + Collection nearby = field.center().getWorld().getNearbyEntities(field.center(), field.radius(), field.radius(), field.radius()); + int entitiesSlowed = 0; + for (Entity entity : nearby) { + if (entity.getLocation().distanceSquared(field.center()) > field.radius() * field.radius()) { + continue; + } + + if (!(entity instanceof Player)) { + if (isProtectedFriendly(owner, entity)) { + continue; + } + + boolean wasNew = !frozenEntities.containsKey(entity.getUniqueId()); + freezeEntity(entity); + if (wasNew && owner != null) { + entitiesSlowed++; + if (entity instanceof Projectile) { + getPlayer(owner).getData().addStat("chronos.time-bomb.projectiles-frozen", 1); + } + getPlayer(owner).getData().addStat("chronos.time-bomb.entities-slowed", 1); + } + continue; + } + + LivingEntity living = (LivingEntity) entity; + boolean caster = entity.getUniqueId().equals(field.owner()); + if (caster) { + living.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getConfig().effectRefreshTicks, getConfig().casterSlownessAmplifier, true, false, false), true); + living.addPotionEffect(new PotionEffect(PotionEffectType.MINING_FATIGUE, getConfig().effectRefreshTicks, getConfig().fatigueAmplifier, true, false, false), true); + continue; + } + + if (owner != null) { + if (!canDamageTarget(owner, living)) { + continue; + } + } + + living.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getConfig().effectRefreshTicks, getConfig().slownessAmplifier, true, false, false), true); + living.addPotionEffect(new PotionEffect(PotionEffectType.MINING_FATIGUE, getConfig().effectRefreshTicks, getConfig().fatigueAmplifier, true, false, false), true); + freezePlayer((Player) living); + living.setVelocity(new Vector()); + entitiesSlowed++; + } + + if (entitiesSlowed >= 8 && owner != null + && AdaptConfig.get().isAdvancements() + && !getPlayer(owner).getData().isGranted("challenge_chronos_bomb_crowd_8")) { + getPlayer(owner).getAdvancementHandler().grant("challenge_chronos_bomb_crowd_8"); + } + + if (areParticlesEnabled()) { + spawnFieldSphere(field, now); + } + + if (getConfig().playClockSounds && now >= field.nextTickSoundAt()) { + long totalDurationMillis = Math.max(50L, getDurationTicks(field.level()) * 50L); + long remainingMillis = Math.max(0L, field.expiresAt() - now); + double progress = 1D - (remainingMillis / (double) totalDurationMillis); + progress = Math.max(0D, Math.min(1D, progress)); + + double pitchCurve = Math.pow(progress, Math.max(0.1D, getConfig().fieldTickPitchCurveExponent)); + float pitch = (float) (getConfig().fieldTickPitchStart + + ((getConfig().fieldTickPitchEnd - getConfig().fieldTickPitchStart) * pitchCurve)); + ChronosSoundFX.playTimeFieldTick(field.center(), pitch); + + double acceleration = Math.max(0D, Math.min(0.95D, getConfig().fieldTickAccelerationFactor)); + long interval = (long) Math.max(getConfig().fieldTickMinIntervalMillis, + getConfig().fieldTickSoundIntervalMillis * (1D - (progress * acceleration))); + field.setNextTickSoundAt(now + interval); + } + } + + private void spawnFieldSphere(TemporalField field, long now) { + if (!getConfig().showFieldSphere || now < field.nextVisualAt() || field.center().getWorld() == null) { + return; + } + + int particles = Math.max(24, getConfig().fieldSphereParticleCount); + double radius = Math.max(0.1, field.radius()); + vfxFastSphere(field.center(), radius, Color.BLACK, particles); + vfxFastSphere(field.center(), Math.max(0.2, radius * 0.75), Color.fromRGB(210, 210, 210), Math.max(12, particles / 2)); + + field.setNextVisualAt(now + Math.max(1, getConfig().fieldSphereRefreshMillis)); + } + + @Override + public void onTick() { + if (J.isPrimaryThread()) { + onTickSync(); + return; + } + + if (!syncTickQueued.compareAndSet(false, true)) { + return; + } + + J.s(() -> { + try { + onTickSync(); + } finally { + syncTickQueued.set(false); + } + }); + } + + private void onTickSync() { + long now = M.ms(); + cleanupBombProjectiles(now); + + for (UUID id : new HashSet<>(cooldownReadyNotify)) { + Player p = Bukkit.getPlayer(id); + if (p == null) { + cooldownReadyNotify.remove(id); + continue; + } + + long cooldown = cooldowns.getOrDefault(id, 0L); + if (cooldown <= now) { + if (getConfig().playClockSounds) { + ChronosSoundFX.playCooldownReady(p); + } + cooldownReadyNotify.remove(id); + } + } + + fields.removeIf(field -> field.expiresAt() <= now); + cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + + for (TemporalField field : fields) { + applyField(field, now); + } + + for (Map.Entry entry : new ArrayList<>(frozenPlayers.entrySet())) { + UUID playerId = entry.getKey(); + Player player = Bukkit.getPlayer(playerId); + if (player == null || !player.isOnline() || player.isDead()) { + frozenPlayers.remove(playerId); + continue; + } + + if (shouldFreezePlayer(player)) { + freezePlayer(player); + continue; + } + + unfreezePlayer(playerId, entry.getValue()); + frozenPlayers.remove(playerId); + } + + for (Map.Entry entry : new ArrayList<>(frozenEntities.entrySet())) { + UUID entityId = entry.getKey(); + Entity entity = Bukkit.getEntity(entityId); + if (entity == null || entity.isDead() || !entity.isValid()) { + frozenEntities.remove(entityId); + continue; + } + + if (isInsideAnyField(entity.getLocation())) { + freezeEntity(entity); + continue; + } + + unfreezeEntity(entityId, entry.getValue()); + frozenEntities.remove(entityId); + } + } + + private void cleanupBombProjectiles(long now) { + for (Map.Entry entry : new ArrayList<>(activeBombProjectiles.entrySet())) { + UUID projectileId = entry.getKey(); + Entity entity = Bukkit.getEntity(projectileId); + if (entity == null || !entity.isValid() || entity.isDead()) { + activeBombProjectiles.remove(projectileId); + continue; + } + + if (now - entry.getValue().launchedAt() > PROJECTILE_TRACK_TTL_MS) { + activeBombProjectiles.remove(projectileId); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Throw a crafted chrono bomb that creates a temporal field, slowing entities and freezing projectiles.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") + boolean playClockSounds = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Radius for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseRadius = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Per Level for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusPerLevel = 1.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Duration Ticks for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseDurationTicks = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Per Level Ticks for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int durationPerLevelTicks = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownMillis = 15000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Target Deploy Range for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double targetDeployRange = 64; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Center YOffset for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fieldCenterYOffset = 1.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Slowness Amplifier for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int slownessAmplifier = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Caster Slowness Amplifier for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int casterSlownessAmplifier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fatigue Amplifier for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int fatigueAmplifier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Freeze Players In Air for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") + boolean freezePlayersInAir = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Accumulate Frozen Impulse for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") + boolean accumulateFrozenImpulse = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Frozen Impulse Min Magnitude for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double frozenImpulseMinMagnitude = 0.03; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Frozen Impulse Sample Cap for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double frozenImpulseSampleCap = 2.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Frozen Impulse Release Cap for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double frozenImpulseReleaseCap = 7.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effect Refresh Ticks for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int effectRefreshTicks = 24; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Field Sphere for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") + boolean showFieldSphere = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Sphere Particle Count for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int fieldSphereParticleCount = 280; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Sphere Refresh Millis for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long fieldSphereRefreshMillis = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Tick Sound Interval Millis for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int fieldTickSoundIntervalMillis = 325; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Tick Min Interval Millis for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int fieldTickMinIntervalMillis = 70; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Tick Pitch Start for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fieldTickPitchStart = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Tick Pitch End for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fieldTickPitchEnd = 1.96; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Tick Pitch Curve Exponent for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fieldTickPitchCurveExponent = 3.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Field Tick Acceleration Factor for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fieldTickAccelerationFactor = 0.82; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Cast for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnCast = 28; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Level for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerLevel = 3; + } + + private static final class TemporalField { + private final UUID owner; + private final Location center; + private final double radius; + private final long expiresAt; + private final int level; + private long nextTickSoundAt; + private long nextVisualAt; + + private TemporalField(UUID owner, Location center, double radius, long expiresAt, int level, long nextTickSoundAt, long nextVisualAt) { + this.owner = owner; + this.center = center; + this.radius = radius; + this.expiresAt = expiresAt; + this.level = level; + this.nextTickSoundAt = nextTickSoundAt; + this.nextVisualAt = nextVisualAt; + } + + private UUID owner() { + return owner; + } + + private Location center() { + return center; + } + + private double radius() { + return radius; + } + + private long expiresAt() { + return expiresAt; + } + + private int level() { + return level; + } + + private long nextTickSoundAt() { + return nextTickSoundAt; + } + + private void setNextTickSoundAt(long nextTickSoundAt) { + this.nextTickSoundAt = nextTickSoundAt; + } + + private long nextVisualAt() { + return nextVisualAt; + } + + private void setNextVisualAt(long nextVisualAt) { + this.nextVisualAt = nextVisualAt; + } + } + + private static final class FrozenEntityState { + private final Vector originalVelocity; + private final boolean gravity; + private final Boolean hadAi; + private final Vector impulseDirectionAccumulator; + private double impulseMagnitudeAccumulator; + private int impulseSamples; + + private FrozenEntityState(Vector originalVelocity, boolean gravity, Boolean hadAi) { + this.originalVelocity = originalVelocity.clone(); + this.gravity = gravity; + this.hadAi = hadAi; + this.impulseDirectionAccumulator = new Vector(); + this.impulseMagnitudeAccumulator = 0D; + this.impulseSamples = 0; + } + + private boolean gravity() { + return gravity; + } + + private Boolean hadAi() { + return hadAi; + } + + private void captureImpulse(Vector currentVelocity, double minMagnitude, double sampleCap) { + if (currentVelocity == null) { + return; + } + + Vector sample = currentVelocity.clone(); + double magnitude = sample.length(); + if (magnitude < Math.max(0D, minMagnitude)) { + return; + } + + if (sampleCap > 0D && magnitude > sampleCap) { + sample = sample.normalize().multiply(sampleCap); + magnitude = sampleCap; + } + + if (magnitude <= 0D) { + return; + } + + impulseDirectionAccumulator.add(sample); + impulseMagnitudeAccumulator += magnitude; + impulseSamples++; + } + + private Vector buildReleaseVelocity(double releaseCap) { + Vector release = originalVelocity.clone(); + if (impulseSamples <= 0 + || impulseMagnitudeAccumulator <= 0D + || impulseDirectionAccumulator.lengthSquared() <= 1.0E-6D) { + return release; + } + + double magnitude = impulseMagnitudeAccumulator; + if (releaseCap > 0D) { + magnitude = Math.min(magnitude, releaseCap); + } + + Vector direction = impulseDirectionAccumulator.clone().normalize(); + release.add(direction.multiply(magnitude)); + return release; + } + } + + private record FrozenPlayerState(boolean allowFlight, boolean flying, + boolean gravity) { + } + + private record ArmedBombProjectile(UUID owner, int level, long launchedAt) { + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTimeInABottle.java b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTimeInABottle.java new file mode 100644 index 000000000..ee92af36d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/chronos/ChronosTimeInABottle.java @@ -0,0 +1,821 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.chronos; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.content.item.ChronoTimeBottle; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Keyed; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.TreeType; +import org.bukkit.block.Block; +import org.bukkit.block.BrewingStand; +import org.bukkit.block.Campfire; +import org.bukkit.block.Furnace; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Sapling; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionType; + +import java.util.concurrent.ThreadLocalRandom; + +public class ChronosTimeInABottle extends SimpleAdaptation { + private static final String RECIPE_KEY = "chronos-time-in-a-bottle"; + + public ChronosTimeInABottle() { + super("chronos-time-bottle"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("chronos.time_in_a_bottle.description")); + setDisplayName(Localizer.dLocalize("chronos.time_in_a_bottle.name")); + setIcon(Material.CLOCK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + + registerRecipe(AdaptRecipe.shapeless() + .key(RECIPE_KEY) + .ingredient(Material.CLOCK) + .ingredient(Material.POTION) + .ingredient(Material.GLASS_BOTTLE) + .result(ChronoTimeBottle.withStoredSeconds(0)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CLOCK) + .key("challenge_chronos_bottle_1k") + .title(Localizer.dLocalize("advancement.challenge_chronos_bottle_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_bottle_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.RECOVERY_COMPASS) + .key("challenge_chronos_bottle_25k") + .title(Localizer.dLocalize("advancement.challenge_chronos_bottle_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_bottle_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_chronos_bottle_1k", "chronos.time-bottle.charges-spent", 1000, 500); + registerMilestone("challenge_chronos_bottle_25k", "chronos.time-bottle.charges-spent", 25000, 2000); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(CraftItemEvent e) { + if (!(e.getRecipe() instanceof Keyed keyed) || !keyed.getKey().getKey().equals(RECIPE_KEY)) { + return; + } + + boolean hasSwiftnessPotion = false; + for (ItemStack item : e.getInventory().getMatrix()) { + if (item == null || item.getType() != Material.POTION) { + continue; + } + if (item.getItemMeta() instanceof PotionMeta meta && meta.getBasePotionType() == PotionType.SWIFTNESS) { + hasSwiftnessPotion = true; + break; + } + } + + if (!hasSwiftnessPotion) { + e.setCancelled(true); + if (e.getWhoClicked() instanceof Player p && getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(p); + } + } + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + (getConfig().chargePerSecond + (level * getConfig().chargePerSecondPerLevel)) + " " + Localizer.dLocalize("chronos.time_in_a_bottle.lore1")); + v.addLore(C.YELLOW + "+ " + Math.round(getCookTicksPerStoredSecond(level)) + " " + Localizer.dLocalize("chronos.time_in_a_bottle.lore2")); + v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.time_in_a_bottle.lore3")); + } + + private double getCookTicksPerStoredSecond(int level) { + return getConfig().baseCookTicksPerStoredSecond + (level * getConfig().cookTicksPerSecondPerLevel); + } + + private int getMaxCookTicksPerUse(int level) { + return getConfig().maxCookTicksPerUse + (level * getConfig().maxCookTicksPerUsePerLevel); + } + + private double getBrewingTicksPerStoredSecond(int level) { + return getConfig().baseBrewingTicksPerStoredSecond + (level * getConfig().brewingTicksPerSecondPerLevel); + } + + private int getMaxBrewingTicksPerUse(int level) { + return getConfig().maxBrewingTicksPerUse + (level * getConfig().maxBrewingTicksPerUsePerLevel); + } + + private double getCampfireTicksPerStoredSecond(int level) { + return getConfig().baseCampfireTicksPerStoredSecond + (level * getConfig().campfireTicksPerSecondPerLevel); + } + + private int getMaxCampfireTicksPerUse(int level) { + return getConfig().maxCampfireTicksPerUse + (level * getConfig().maxCampfireTicksPerUsePerLevel); + } + + private double getFurnaceSpendMultiplier() { + return Math.max(0.01, getConfig().furnaceSpendMultiplier); + } + + private double getBrewingSpendMultiplier() { + return Math.max(0.01, getConfig().brewingSpendMultiplier); + } + + private double getCampfireSpendMultiplier() { + return Math.max(0.01, getConfig().campfireSpendMultiplier); + } + + private double getEntityAgeTicksPerStoredSecond(int level) { + return getConfig().baseEntityAgeTicksPerStoredSecond + (level * getConfig().entityAgeTicksPerSecondPerLevel); + } + + private int getMaxEntityAgeTicksPerUse(int level) { + return getConfig().maxEntityAgeTicksPerUse + (level * getConfig().maxEntityAgeTicksPerUsePerLevel); + } + + private double getEntitySpendMultiplier() { + return Math.max(0.01, getConfig().entitySpendMultiplier); + } + + private int getMaxGrowthStepsPerUse(int level) { + return getConfig().maxGrowthStepsPerUse + (level * getConfig().maxGrowthStepsPerUsePerLevel); + } + + private double getGrowthLevelScale(int level) { + return Math.max(getConfig().minGrowthCostLevelScale, 1D - (level * getConfig().growthCostReductionPerLevel)); + } + + private double getSaplingGrowChance(int level) { + return Math.max(0, Math.min(1, getConfig().saplingGrowChanceBase + (level * getConfig().saplingGrowChancePerLevel))); + } + + private TreeType getTreeType(Material type) { + return switch (type) { + case OAK_SAPLING -> + ThreadLocalRandom.current().nextBoolean() ? TreeType.TREE : TreeType.BIG_TREE; + case SPRUCE_SAPLING -> + ThreadLocalRandom.current().nextBoolean() ? TreeType.REDWOOD : TreeType.TALL_REDWOOD; + case BIRCH_SAPLING -> TreeType.BIRCH; + case JUNGLE_SAPLING -> + ThreadLocalRandom.current().nextBoolean() ? TreeType.SMALL_JUNGLE : TreeType.JUNGLE; + case ACACIA_SAPLING -> TreeType.ACACIA; + case DARK_OAK_SAPLING -> TreeType.DARK_OAK; + case CHERRY_SAPLING -> TreeType.CHERRY; + case MANGROVE_PROPAGULE -> + ThreadLocalRandom.current().nextBoolean() ? TreeType.MANGROVE : TreeType.TALL_MANGROVE; + case AZALEA, FLOWERING_AZALEA -> TreeType.AZALEA; + default -> null; + }; + } + + private double getGrowthStepCostSeconds(Block target, int level) { + GrowthProfile profile = detectGrowthProfile(target.getType()); + double naturalSeconds = getNaturalGrowthSeconds(profile); + int steps = getEstimatedGrowthSteps(target, profile); + double baseStepSeconds = naturalSeconds / Math.max(1, steps); + double profileMultiplier = getGrowthProfileCostMultiplier(profile); + double scaled = baseStepSeconds * profileMultiplier * getConfig().growthCostMultiplier * getGrowthLevelScale(level); + return Math.max(getConfig().minGrowthStepSeconds, scaled); + } + + private int getEstimatedGrowthSteps(Block target, GrowthProfile profile) { + BlockData data = target.getBlockData(); + if (data instanceof Ageable ageable) { + return Math.max(1, ageable.getMaximumAge()); + } + + if (data instanceof Sapling) { + return Math.max(1, getConfig().saplingGrowthSteps); + } + + return switch (profile) { + case SAPLING -> Math.max(1, getConfig().saplingGrowthSteps); + case STEM -> Math.max(1, getConfig().stemGrowthSteps); + case BERRY_BUSH -> Math.max(1, getConfig().berryGrowthSteps); + case VINE -> Math.max(1, getConfig().vineGrowthSteps); + case CAVE_VINE -> Math.max(1, getConfig().caveVineGrowthSteps); + case KELP -> Math.max(1, getConfig().kelpGrowthSteps); + default -> Math.max(1, getConfig().defaultGrowthSteps); + }; + } + + private double getNaturalGrowthSeconds(GrowthProfile profile) { + return switch (profile) { + case CROP -> getConfig().cropNaturalSeconds; + case NETHER_WART -> getConfig().netherWartNaturalSeconds; + case SAPLING -> getConfig().saplingNaturalSeconds; + case STEM -> getConfig().stemNaturalSeconds; + case BERRY_BUSH -> getConfig().berryBushNaturalSeconds; + case VINE -> getConfig().vineNaturalSeconds; + case CAVE_VINE -> getConfig().caveVineNaturalSeconds; + case KELP -> getConfig().kelpNaturalSeconds; + default -> getConfig().defaultGrowableNaturalSeconds; + }; + } + + private double getGrowthProfileCostMultiplier(GrowthProfile profile) { + return switch (profile) { + case CROP -> getConfig().cropCostMultiplier; + case NETHER_WART -> getConfig().netherWartCostMultiplier; + case SAPLING -> getConfig().saplingCostMultiplier; + case STEM -> getConfig().stemCostMultiplier; + case BERRY_BUSH -> getConfig().berryBushCostMultiplier; + case VINE -> getConfig().vineCostMultiplier; + case CAVE_VINE -> getConfig().caveVineCostMultiplier; + case KELP -> getConfig().kelpCostMultiplier; + default -> getConfig().defaultGrowableCostMultiplier; + }; + } + + private GrowthProfile detectGrowthProfile(Material type) { + String name = type.name(); + + if (type == Material.NETHER_WART) { + return GrowthProfile.NETHER_WART; + } + + if (type == Material.SWEET_BERRY_BUSH) { + return GrowthProfile.BERRY_BUSH; + } + + if (type == Material.KELP || type == Material.KELP_PLANT) { + return GrowthProfile.KELP; + } + + if (type == Material.CAVE_VINES || type == Material.CAVE_VINES_PLANT) { + return GrowthProfile.CAVE_VINE; + } + + if (type == Material.VINE || type == Material.WEEPING_VINES || type == Material.WEEPING_VINES_PLANT + || type == Material.TWISTING_VINES || type == Material.TWISTING_VINES_PLANT) { + return GrowthProfile.VINE; + } + + if (name.endsWith("_SAPLING") || type == Material.MANGROVE_PROPAGULE || type == Material.AZALEA + || type == Material.FLOWERING_AZALEA) { + return GrowthProfile.SAPLING; + } + + if (type == Material.PUMPKIN_STEM || type == Material.MELON_STEM || type == Material.ATTACHED_MELON_STEM + || type == Material.ATTACHED_PUMPKIN_STEM) { + return GrowthProfile.STEM; + } + + if (type == Material.WHEAT || type == Material.CARROTS || type == Material.POTATOES || type == Material.BEETROOTS + || type == Material.COCOA || type == Material.TORCHFLOWER_CROP || type == Material.PITCHER_CROP) { + return GrowthProfile.CROP; + } + + return GrowthProfile.DEFAULT; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerItemConsumeEvent e) { + if (ChronoTimeBottle.isBindableItem(e.getItem())) { + e.setCancelled(true); + if (getConfig().playClockSounds) { + ChronosSoundFX.playClockReject(e.getPlayer()); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + Player p = e.getPlayer(); + EquipmentSlot handSlot = e.getHand(); + if (handSlot == null) { + return; + } + + ItemStack hand = handSlot == EquipmentSlot.OFF_HAND + ? p.getInventory().getItemInOffHand() + : p.getInventory().getItemInMainHand(); + if (!ChronoTimeBottle.isBindableItem(hand)) { + return; + } + + // Chrono bottles are never drinkable; always deny vanilla potion use. + e.setUseItemInHand(Event.Result.DENY); + + Block clicked = action == Action.RIGHT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (clicked == null) { + return; + } + int level = getActiveInteractLevel(p, clicked.getLocation()); + if (level <= 0) { + return; + } + double storedSeconds = ChronoTimeBottle.getStoredSeconds(hand); + if (storedSeconds <= 0) { + return; + } + + TimeSpendResult result = accelerateTarget(clicked, storedSeconds, level); + if (!result.applied()) { + return; + } + + e.setCancelled(true); + ChronoTimeBottle.setStoredSeconds(hand, Math.max(0, storedSeconds - result.spentSeconds())); + getPlayer(p).getData().addStat("chronos.time-bottle.charges-spent", 1); + + if (getConfig().playClockSounds) { + ChronosSoundFX.playBottleUse(p, clicked.getLocation().add(0.5, 1.0, 0.5), result.effectTicks()); + } + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.ENCHANT, clicked.getLocation().add(0.5, 1.0, 0.5), 32, 0.35, 0.3, 0.35, 0.08); + p.getWorld().spawnParticle(Particle.END_ROD, clicked.getLocation().add(0.5, 1.0, 0.5), 8, 0.1, 0.2, 0.1, 0.01); + } + + xp(p, clicked.getLocation().add(0.5, 1.0, 0.5), Math.min(getConfig().maxXPPerUse, result.xpGain())); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEntityEvent e) { + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!ChronoTimeBottle.isBindableItem(hand)) { + return; + } + + if (!(e.getRightClicked() instanceof org.bukkit.entity.Ageable ageable)) { + return; + } + + int level = getActiveInteractLevel(p, e.getRightClicked().getLocation()); + if (level <= 0) { + return; + } + + int currentAge = ageable.getAge(); + if (currentAge == 0) { + return; + } + double storedSeconds = ChronoTimeBottle.getStoredSeconds(hand); + if (storedSeconds <= 0) { + return; + } + + TimeSpendResult result = accelerateAgeableEntity(ageable, storedSeconds, level); + if (!result.applied()) { + return; + } + + e.setCancelled(true); + ChronoTimeBottle.setStoredSeconds(hand, Math.max(0, storedSeconds - result.spentSeconds())); + getPlayer(p).getData().addStat("chronos.time-bottle.charges-spent", 1); + + if (getConfig().playClockSounds) { + ChronosSoundFX.playBottleUse(p, ageable.getLocation().add(0, 1.0, 0), result.effectTicks()); + } + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.ENCHANT, ageable.getLocation().add(0, 1.0, 0), 24, 0.3, 0.4, 0.3, 0.05); + p.getWorld().spawnParticle(Particle.END_ROD, ageable.getLocation().add(0, 1.0, 0), 7, 0.1, 0.25, 0.1, 0.01); + } + + xp(p, ageable.getLocation().add(0, 1.0, 0), Math.min(getConfig().maxXPPerUse, result.xpGain())); + } + + private TimeSpendResult accelerateTarget(Block clicked, double storedSeconds, int level) { + if (clicked.getState() instanceof Furnace furnace) { + return accelerateFurnace(furnace, storedSeconds, level); + } + + if (clicked.getState() instanceof BrewingStand brewingStand) { + return accelerateBrewingStand(brewingStand, storedSeconds, level); + } + + if (clicked.getState() instanceof Campfire campfire) { + return accelerateCampfire(campfire, storedSeconds, level); + } + + return accelerateGrowables(clicked, storedSeconds, level); + } + + private TimeSpendResult accelerateFurnace(Furnace furnace, double storedSeconds, int level) { + if (furnace.getInventory().getSmelting() == null || furnace.getInventory().getSmelting().getType().isAir()) { + return TimeSpendResult.none(); + } + + int totalCookTime = furnace.getCookTimeTotal(); + int currentCookTime = furnace.getCookTime(); + int remainingCookTime = Math.max(0, totalCookTime - currentCookTime); + if (remainingCookTime <= 0) { + return TimeSpendResult.none(); + } + + double cookTicksPerStoredSecond = getCookTicksPerStoredSecond(level); + double spendMultiplier = getFurnaceSpendMultiplier(); + int affordableTicks = (int) Math.floor((storedSeconds / spendMultiplier) * cookTicksPerStoredSecond); + int advanceTicks = Math.min(remainingCookTime, Math.min(affordableTicks, getMaxCookTicksPerUse(level))); + if (advanceTicks <= 0) { + return TimeSpendResult.none(); + } + + furnace.setCookTime((short) Math.min(totalCookTime, currentCookTime + advanceTicks)); + furnace.update(true, true); + + double spentSeconds = (advanceTicks / cookTicksPerStoredSecond) * spendMultiplier; + return new TimeSpendResult(spentSeconds, advanceTicks, advanceTicks * getConfig().xpPerCookTick); + } + + private TimeSpendResult accelerateBrewingStand(BrewingStand stand, double storedSeconds, int level) { + int brewingTime = stand.getBrewingTime(); + if (brewingTime <= 0) { + return TimeSpendResult.none(); + } + + double brewTicksPerStoredSecond = getBrewingTicksPerStoredSecond(level); + double spendMultiplier = getBrewingSpendMultiplier(); + int affordableTicks = (int) Math.floor((storedSeconds / spendMultiplier) * brewTicksPerStoredSecond); + int advanceTicks = Math.min(brewingTime, Math.min(affordableTicks, getMaxBrewingTicksPerUse(level))); + if (advanceTicks <= 0) { + return TimeSpendResult.none(); + } + + stand.setBrewingTime(Math.max(0, brewingTime - advanceTicks)); + stand.update(true, true); + + double spentSeconds = (advanceTicks / brewTicksPerStoredSecond) * spendMultiplier; + return new TimeSpendResult(spentSeconds, advanceTicks, advanceTicks * getConfig().xpPerBrewTick); + } + + private TimeSpendResult accelerateCampfire(Campfire campfire, double storedSeconds, int level) { + double campfireTicksPerStoredSecond = getCampfireTicksPerStoredSecond(level); + double spendMultiplier = getCampfireSpendMultiplier(); + int affordableTicks = (int) Math.floor((storedSeconds / spendMultiplier) * campfireTicksPerStoredSecond); + int budgetTicks = Math.min(affordableTicks, getMaxCampfireTicksPerUse(level)); + if (budgetTicks <= 0) { + return TimeSpendResult.none(); + } + + int usedTicks = 0; + for (int i = 0; i < campfire.getSize() && usedTicks < budgetTicks; i++) { + ItemStack item = campfire.getItem(i); + if (item == null || item.getType().isAir()) { + continue; + } + + int total = campfire.getCookTimeTotal(i); + int current = campfire.getCookTime(i); + int remaining = Math.max(0, total - current); + if (remaining <= 0) { + continue; + } + + int step = Math.min(remaining, budgetTicks - usedTicks); + campfire.setCookTime(i, current + step); + usedTicks += step; + } + + if (usedTicks <= 0) { + return TimeSpendResult.none(); + } + + campfire.update(true, true); + double spentSeconds = (usedTicks / campfireTicksPerStoredSecond) * spendMultiplier; + return new TimeSpendResult(spentSeconds, usedTicks, usedTicks * getConfig().xpPerCampfireTick); + } + + private TimeSpendResult accelerateAgeableEntity(org.bukkit.entity.Ageable entity, double storedSeconds, int level) { + int currentAge = entity.getAge(); + if (currentAge == 0) { + return TimeSpendResult.none(); + } + + double ageTicksPerStoredSecond = getEntityAgeTicksPerStoredSecond(level); + double spendMultiplier = getEntitySpendMultiplier(); + int affordableTicks = (int) Math.floor((storedSeconds / spendMultiplier) * ageTicksPerStoredSecond); + int advanceTicks = Math.min(Math.abs(currentAge), Math.min(affordableTicks, getMaxEntityAgeTicksPerUse(level))); + if (advanceTicks <= 0) { + return TimeSpendResult.none(); + } + + if (currentAge < 0) { + entity.setAge(Math.min(0, currentAge + advanceTicks)); + } else { + entity.setAge(Math.max(0, currentAge - advanceTicks)); + } + + double spentSeconds = (advanceTicks / ageTicksPerStoredSecond) * spendMultiplier; + return new TimeSpendResult(spentSeconds, advanceTicks, advanceTicks * getConfig().xpPerEntityAgeTick); + } + + private TimeSpendResult accelerateGrowables(Block clicked, double storedSeconds, int level) { + int attempts = getMaxGrowthStepsPerUse(level); + if (attempts <= 0 || storedSeconds <= 0) { + return TimeSpendResult.none(); + } + + double remainingSeconds = storedSeconds; + double spentSeconds = 0; + int successes = 0; + for (int i = 0; i < attempts; i++) { + Block target = clicked.getWorld().getBlockAt(clicked.getLocation()); + double stepCost = getGrowthStepCostSeconds(target, level); + if (remainingSeconds + 1.0E-6 < stepCost) { + break; + } + + if (!applyDirectGrowthStep(target, level)) { + break; + } + + remainingSeconds -= stepCost; + spentSeconds += stepCost; + successes++; + } + + if (successes <= 0 || spentSeconds <= 0) { + return TimeSpendResult.none(); + } + + int effectTicks = Math.max(8, successes * 20); + return new TimeSpendResult(spentSeconds, effectTicks, successes * getConfig().xpPerGrowthStep); + } + + private boolean applyDirectGrowthStep(Block block, int level) { + BlockData data = block.getBlockData(); + if (data instanceof Sapling sapling) { + if (sapling.getStage() < sapling.getMaximumStage()) { + sapling.setStage(Math.min(sapling.getMaximumStage(), sapling.getStage() + 1)); + block.setBlockData(data, true); + return true; + } + + if (!getConfig().allowSaplingTreeGeneration) { + return false; + } + + if (ThreadLocalRandom.current().nextDouble() > getSaplingGrowChance(level)) { + return true; + } + + TreeType treeType = getTreeType(block.getType()); + return treeType != null && block.getWorld().generateTree(block.getLocation(), treeType); + } + + if (data instanceof Ageable ageable && ageable.getAge() < ageable.getMaximumAge()) { + ageable.setAge(Math.min(ageable.getMaximumAge(), ageable.getAge() + 1)); + block.setBlockData(data, true); + return true; + } + + return false; + } + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + int level = getActiveLevel(p); + if (level <= 0) { + continue; + } + + double chargePerSecond = getConfig().chargePerSecond + (level * getConfig().chargePerSecondPerLevel); + + for (ItemStack stack : p.getInventory().getContents()) { + if (!ChronoTimeBottle.isBindableItem(stack)) { + continue; + } + + double stored = ChronoTimeBottle.getStoredSeconds(stack); + double capped = Math.min(getConfig().maxStoredSeconds, stored + chargePerSecond); + if (capped > stored) { + ChronoTimeBottle.setStoredSeconds(stack, capped); + } + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private enum GrowthProfile { + CROP, + NETHER_WART, + SAPLING, + STEM, + BERRY_BUSH, + VINE, + CAVE_VINE, + KELP, + DEFAULT + } + + private record TimeSpendResult(double spentSeconds, int effectTicks, + double xpGain) { + private static TimeSpendResult none() { + return new TimeSpendResult(0, 0, 0); + } + + private boolean applied() { + return spentSeconds > 0; + } + } + + @NoArgsConstructor + @ConfigDescription("Carry a temporal bottle that stores time to accelerate timed blocks and baby animals.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Chronos Time In ABottle adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Time In ABottle adaptation.", impact = "True enables this behavior and false disables it.") + boolean playClockSounds = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Stored Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxStoredSeconds = 900; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Charge Per Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double chargePerSecond = 0.1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Charge Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double chargePerSecondPerLevel = 0.02; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Cook Ticks Per Stored Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseCookTicksPerStoredSecond = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cook Ticks Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cookTicksPerSecondPerLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Cook Ticks Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxCookTicksPerUse = 140; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Cook Ticks Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxCookTicksPerUsePerLevel = 35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Furnace Spend Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double furnaceSpendMultiplier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Brewing Ticks Per Stored Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseBrewingTicksPerStoredSecond = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Brewing Ticks Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double brewingTicksPerSecondPerLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Brewing Ticks Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxBrewingTicksPerUse = 140; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Brewing Ticks Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxBrewingTicksPerUsePerLevel = 35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Brewing Spend Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double brewingSpendMultiplier = 1.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Campfire Ticks Per Stored Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseCampfireTicksPerStoredSecond = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Campfire Ticks Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double campfireTicksPerSecondPerLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Campfire Ticks Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxCampfireTicksPerUse = 160; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Campfire Ticks Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxCampfireTicksPerUsePerLevel = 40; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Campfire Spend Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double campfireSpendMultiplier = 0.9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Entity Age Ticks Per Stored Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseEntityAgeTicksPerStoredSecond = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Entity Age Ticks Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double entityAgeTicksPerSecondPerLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Entity Age Ticks Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxEntityAgeTicksPerUse = 180; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Entity Age Ticks Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxEntityAgeTicksPerUsePerLevel = 55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Entity Spend Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double entitySpendMultiplier = 1.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Growth Steps Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxGrowthStepsPerUse = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Growth Steps Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxGrowthStepsPerUsePerLevel = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Allow Sapling Tree Generation for the Chronos Time In ABottle adaptation.", impact = "True enables this behavior and false disables it.") + boolean allowSaplingTreeGeneration = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sapling Grow Chance Base for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double saplingGrowChanceBase = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sapling Grow Chance Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double saplingGrowChancePerLevel = 0.04; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Growth Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double growthCostMultiplier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Growth Cost Reduction Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double growthCostReductionPerLevel = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Growth Cost Level Scale for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minGrowthCostLevelScale = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Growth Step Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minGrowthStepSeconds = 0.06; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sapling Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int saplingGrowthSteps = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stem Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int stemGrowthSteps = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Berry Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int berryGrowthSteps = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Vine Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int vineGrowthSteps = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cave Vine Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int caveVineGrowthSteps = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Kelp Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int kelpGrowthSteps = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Default Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int defaultGrowthSteps = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Crop Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cropNaturalSeconds = 300; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Nether Wart Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double netherWartNaturalSeconds = 420; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sapling Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double saplingNaturalSeconds = 900; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stem Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double stemNaturalSeconds = 660; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Berry Bush Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double berryBushNaturalSeconds = 260; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Vine Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double vineNaturalSeconds = 300; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cave Vine Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double caveVineNaturalSeconds = 280; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Kelp Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double kelpNaturalSeconds = 240; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Default Growable Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double defaultGrowableNaturalSeconds = 420; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Crop Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cropCostMultiplier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Nether Wart Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double netherWartCostMultiplier = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sapling Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double saplingCostMultiplier = 2.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stem Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double stemCostMultiplier = 1.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Berry Bush Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double berryBushCostMultiplier = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Vine Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double vineCostMultiplier = 0.85; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cave Vine Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double caveVineCostMultiplier = 0.9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Kelp Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double kelpCostMultiplier = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Default Growable Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double defaultGrowableCostMultiplier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Cook Tick for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerCookTick = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Brew Tick for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBrewTick = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Campfire Tick for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerCampfireTick = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Entity Age Tick for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerEntityAgeTick = 0.06; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Growth Step for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerGrowthStep = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max XPPer Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxXPPerUse = 55; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingBackpacks.java b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingBackpacks.java new file mode 100644 index 000000000..12474a402 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingBackpacks.java @@ -0,0 +1,121 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.crafting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdvancementSpec; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class CraftingBackpacks extends SimpleAdaptation { + + public CraftingBackpacks() { + super("crafting-backpacks"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("crafting.backpacks.description")); + setDisplayName(Localizer.dLocalize("crafting.backpacks.name")); + setIcon(Material.BUNDLE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(17779); + registerRecipe(AdaptRecipe.shaped() + .key("crafting-backpacks") + .ingredient(new MaterialChar('I', Material.LEATHER)) + .ingredient(new MaterialChar('L', Material.LEAD)) + .ingredient(new MaterialChar('C', Material.CHEST)) + .ingredient(new MaterialChar('X', Material.BARREL)) + .shapes(List.of( + "ILI", + "IXI", + "ICI")) + .result(new ItemStack(Material.BUNDLE, 1)) + .build()); + AdvancementSpec backpacksCrafted = AdvancementSpec.challenge( + "challenge_crafting_backpack_25", + Material.BUNDLE, + Localizer.dLocalize("advancement.challenge_crafting_backpack_25.title"), + Localizer.dLocalize("advancement.challenge_crafting_backpack_25.description") + ); + registerMilestone(backpacksCrafted, "crafting.backpacks.bundles-crafted", 25, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("crafting.backpacks.lore1")); + v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.backpacks.lore2")); + v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.backpacks.lore3")); + v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.backpacks.lore4")); + + } + + + @EventHandler + public void on(CraftItemEvent e) { + Player p = (Player) e.getWhoClicked(); + if (!hasActiveAdaptation(p)) return; + if (e.getRecipe() != null && e.getRecipe().getResult().getType() == Material.BUNDLE) { + getPlayer(p).getData().addStat("crafting.backpacks.bundles-crafted", 1); + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Bundles for portable item storage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingDeconstruction.java b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingDeconstruction.java new file mode 100644 index 000000000..678ad4259 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingDeconstruction.java @@ -0,0 +1,232 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.crafting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdvancementSpec; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.util.RayTraceResult; + +import java.util.HashMap; +import java.util.Map; + +public class CraftingDeconstruction extends SimpleAdaptation { + public CraftingDeconstruction() { + super("crafting-deconstruction"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("crafting.deconstruction.description")); + setDisplayName(Localizer.dLocalize("crafting.deconstruction.name")); + setIcon(Material.SHEARS); + setBaseCost(getConfig().baseCost); + setMaxLevel(1); + setInterval(5590); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + AdvancementSpec deconstruction5k = AdvancementSpec.challenge( + "challenge_crafting_decon_5k", + Material.IRON_INGOT, + Localizer.dLocalize("advancement.challenge_crafting_decon_5k.title"), + Localizer.dLocalize("advancement.challenge_crafting_decon_5k.description") + ); + AdvancementSpec deconstruction200 = AdvancementSpec.challenge( + "challenge_crafting_decon_200", + Material.SHEARS, + Localizer.dLocalize("advancement.challenge_crafting_decon_200.title"), + Localizer.dLocalize("advancement.challenge_crafting_decon_200.description") + ).withChild(deconstruction5k); + registerAdvancementSpec(deconstruction200); + registerStatTracker(deconstruction200.statTracker("crafting.deconstruction.items-deconstructed", 200, 300)); + registerStatTracker(deconstruction5k.statTracker("crafting.deconstruction.items-deconstructed", 5000, 1000)); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("crafting.deconstruction.lore1")); + v.addLore(C.GREEN + Localizer.dLocalize("crafting.deconstruction.lore2")); + } + + public ItemStack getDeconstructionOffering(ItemStack forStuff) { + if (forStuff == null) return null; + + int maxPow = 0; + Recipe selectedRecipe = null; + + for (Recipe recipe : Bukkit.getRecipesFor(forStuff)) { + int currentPower = 0; + if (recipe instanceof ShapelessRecipe r) { + for (ItemStack ingredient : r.getIngredientList()) { + if (ingredient == null) { + continue; + } + currentPower += ingredient.getAmount(); + } + } else if (recipe instanceof ShapedRecipe r) { + for (ItemStack ingredient : r.getIngredientMap().values()) { + if (ingredient == null) { + continue; + } + currentPower += ingredient.getAmount(); + } + } + if (currentPower > maxPow) { + selectedRecipe = recipe; + maxPow = currentPower; + } + } + + if (selectedRecipe == null) return null; + + int v = 0; + int outa = 1; + ItemStack sel = null; + + if (selectedRecipe instanceof ShapelessRecipe r) { + for (ItemStack i : r.getIngredientList()) { + int amount = i.getAmount() * forStuff.getAmount(); + if (amount > v) { + v = amount; + sel = i; + outa = r.getResult().getAmount(); + } + } + } else { + ShapedRecipe r = (ShapedRecipe) selectedRecipe; + Map ings = new HashMap<>(); + for (ItemStack ingredient : r.getIngredientMap().values()) { + if (ingredient == null) { + continue; + } + ings.merge(ingredient.getType(), ingredient.getAmount(), Integer::sum); + } + + for (Map.Entry entry : ings.entrySet()) { + int amount = entry.getValue() * forStuff.getAmount(); + if (amount > v) { + v = amount; + sel = new ItemStack(entry.getKey(), entry.getValue()); + outa = r.getResult().getAmount(); + } + } + } + + if (sel != null && sel.getAmount() * forStuff.getAmount() > 1) { + int a = ((sel.getAmount() * forStuff.getAmount()) / outa) / 2; + if (a <= sel.getMaxStackSize() && getValue(sel) < getValue(forStuff)) { + sel.setAmount(a); + return sel.clone(); + } + } + + return null; + } + + + @EventHandler + public void on(PlayerInteractEvent e) { + Player player = e.getPlayer(); + ItemStack mainHandItem = player.getInventory().getItemInMainHand(); + if (!hasActiveAdaptation(player)) { + return; + } + + if (!player.isSneaking() || mainHandItem.getType() != Material.SHEARS) { + return; + } + + // Perform a ray trace for 6 blocks looking for an item + RayTraceResult rayTrace = player.getWorld().rayTraceEntities(player.getEyeLocation(), player.getLocation().getDirection(), 6, entity -> entity instanceof Item); + if (rayTrace != null && rayTrace.getHitEntity() instanceof Item itemEntity) { + processItemInteraction(player, mainHandItem, itemEntity); + } + } + + private void processItemInteraction(Player player, ItemStack mainHandItem, Item itemEntity) { + ItemStack forStuff = itemEntity.getItemStack(); + ItemStack offering = getDeconstructionOffering(forStuff); + + SoundPlayer spw = SoundPlayer.of(player.getWorld()); + if (offering != null) { + itemEntity.setItemStack(offering); + spw.play(itemEntity.getLocation(), Sound.BLOCK_BASALT_BREAK, 1F, 0.2f); + spw.play(itemEntity.getLocation(), Sound.BLOCK_BEEHIVE_SHEAR, 1F, 0.7f); + xp(player, getValue(offering), "deconstruct"); + getPlayer(player).getData().addStat("crafting.deconstruction.items-deconstructed", 1); + + // Damage the shears + Damageable damageable = (Damageable) mainHandItem.getItemMeta(); + int newDamage = damageable.getDamage() + 8 * forStuff.getAmount(); + if (newDamage >= mainHandItem.getType().getMaxDurability()) { + player.getInventory().setItemInMainHand(null); // Break the shears + } else { + damageable.setDamage(newDamage); + mainHandItem.setItemMeta(damageable); + } + } else { + spw.play(itemEntity.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 1F, 1f); // Burnt torch sound + } + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Deconstruct blocks and items into salvageable base components using shears.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingLeather.java b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingLeather.java new file mode 100644 index 000000000..32545afb6 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingLeather.java @@ -0,0 +1,110 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.crafting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdvancementSpec; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; + +public class CraftingLeather extends SimpleAdaptation { + + public CraftingLeather() { + super("crafting-leather"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("crafting.leather.description")); + setDisplayName(Localizer.dLocalize("crafting.leather.name")); + setIcon(Material.LEATHER); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(17776); + registerRecipe(AdaptRecipe.campfire() + .key("crafting-leather") + .ingredient(Material.ROTTEN_FLESH) + .cookTime(100) + .experience(1) + .result(new ItemStack(Material.LEATHER, 1)) + .build()); + AdvancementSpec leatherCrafted = AdvancementSpec.challenge( + "challenge_crafting_leather_100", + Material.LEATHER, + Localizer.dLocalize("advancement.challenge_crafting_leather_100.title"), + Localizer.dLocalize("advancement.challenge_crafting_leather_100.description") + ); + registerMilestone(leatherCrafted, "crafting.leather.leather-crafted", 100, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("crafting.leather.lore1")); + } + + @EventHandler + public void on(PlayerInteractEvent e) { + if (e.getItem() != null && e.getItem().getType() == Material.ROTTEN_FLESH && e.getClickedBlock() != null && e.getClickedBlock().getType() == Material.CAMPFIRE) { + if (getActiveLevel(e.getPlayer()) <= 0) { + e.setCancelled(true); + } else { + getPlayer(e.getPlayer()).getData().addStat("crafting.leather.leather-crafted", 1); + } + } + } + + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Leather from Rotten Flesh on a campfire.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingReconstruction.java b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingReconstruction.java new file mode 100644 index 000000000..45dfc32fc --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingReconstruction.java @@ -0,0 +1,366 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.crafting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; + + +public class CraftingReconstruction extends SimpleAdaptation { + public CraftingReconstruction() { + super("crafting-reconstruction"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("crafting.reconstruction.description")); + setDisplayName(Localizer.dLocalize("crafting.reconstruction.name")); + setIcon(Material.COAL_ORE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(80248); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-iron-ore") + .ingredient(Material.STONE) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .result(new ItemStack(Material.IRON_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-gold-ore") + .ingredient(Material.STONE) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .result(new ItemStack(Material.GOLD_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-copper-ore") + .ingredient(Material.STONE) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .result(new ItemStack(Material.COPPER_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-lapis-ore") + .ingredient(Material.STONE) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .result(new ItemStack(Material.LAPIS_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-redstone-ore") + .ingredient(Material.STONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .result(new ItemStack(Material.REDSTONE_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-emerald-ore") + .ingredient(Material.STONE) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .result(new ItemStack(Material.EMERALD_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-diamond-ore") + .ingredient(Material.STONE) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .result(new ItemStack(Material.DIAMOND_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-coal-ore") + .ingredient(Material.STONE) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .result(new ItemStack(Material.COAL_ORE)) + .build()); + + // Use Deepslate + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-deepslate-iron-ore") + .ingredient(Material.DEEPSLATE) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .ingredient(Material.IRON_INGOT) + .result(new ItemStack(Material.DEEPSLATE_IRON_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-deepslate-gold-ore") + .ingredient(Material.DEEPSLATE) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .result(new ItemStack(Material.DEEPSLATE_GOLD_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-deepslate-copper-ore") + .ingredient(Material.DEEPSLATE) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .ingredient(Material.COPPER_INGOT) + .result(new ItemStack(Material.DEEPSLATE_COPPER_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-deepslate-lapis-ore") + .ingredient(Material.DEEPSLATE) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .ingredient(Material.LAPIS_LAZULI) + .result(new ItemStack(Material.DEEPSLATE_LAPIS_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-deepslate-redstone-ore") + .ingredient(Material.DEEPSLATE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .ingredient(Material.REDSTONE) + .result(new ItemStack(Material.DEEPSLATE_REDSTONE_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-deepslate-emerald-ore") + .ingredient(Material.DEEPSLATE) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .ingredient(Material.EMERALD) + .result(new ItemStack(Material.DEEPSLATE_EMERALD_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-deepslate-diamond-ore") + .ingredient(Material.DEEPSLATE) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .ingredient(Material.DIAMOND) + .result(new ItemStack(Material.DEEPSLATE_DIAMOND_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-deepslate-coal-ore") + .ingredient(Material.DEEPSLATE) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .ingredient(Material.COAL) + .result(new ItemStack(Material.DEEPSLATE_COAL_ORE)) + .build()); + +// Use Nether Bricks + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-nether-gold-ore") + .ingredient(Material.NETHER_BRICKS) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .ingredient(Material.GOLD_INGOT) + .result(new ItemStack(Material.NETHER_GOLD_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-nether-quartz-ore") + .ingredient(Material.NETHER_BRICKS) + .ingredient(Material.QUARTZ) + .ingredient(Material.QUARTZ) + .ingredient(Material.QUARTZ) + .ingredient(Material.QUARTZ) + .ingredient(Material.QUARTZ) + .ingredient(Material.QUARTZ) + .ingredient(Material.QUARTZ) + .ingredient(Material.QUARTZ) + .result(new ItemStack(Material.NETHER_QUARTZ_ORE)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("reconstruction-ancient-debris") + .ingredient(Material.NETHER_BRICKS) + .ingredient(Material.NETHERITE_SCRAP) + .ingredient(Material.NETHERITE_SCRAP) + .ingredient(Material.NETHERITE_SCRAP) + .ingredient(Material.NETHERITE_SCRAP) + .ingredient(Material.NETHERITE_SCRAP) + .ingredient(Material.NETHERITE_SCRAP) + .ingredient(Material.NETHERITE_SCRAP) + .ingredient(Material.NETHERITE_SCRAP) + .result(new ItemStack(Material.ANCIENT_DEBRIS)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.RAW_IRON) + .key("challenge_crafting_recon_100") + .title(Localizer.dLocalize("advancement.challenge_crafting_recon_100.title")) + .description(Localizer.dLocalize("advancement.challenge_crafting_recon_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_crafting_recon_100", "crafting.reconstruction.ores-reconstructed", 100, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("crafting.reconstruction.lore1")); + v.addLore(C.UNDERLINE + Localizer.dLocalize("crafting.reconstruction.lore2")); + v.addLore(C.YELLOW + Localizer.dLocalize("crafting.reconstruction.lore3")); + v.addLore(C.YELLOW + Localizer.dLocalize("crafting.reconstruction.lore4")); + } + + @EventHandler + public void on(PlayerInteractEvent e) { + + } + + @EventHandler + public void on(CraftItemEvent e) { + Player p = (Player) e.getWhoClicked(); + if (!hasActiveAdaptation(p)) return; + if (e.getRecipe() != null && (e.getRecipe().getResult().getType().name().contains("ORE") || e.getRecipe().getResult().getType() == Material.ANCIENT_DEBRIS)) { + getPlayer(p).getData().addStat("crafting.reconstruction.ores-reconstructed", 1); + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Recraft ores from their base smelted components.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingSkulls.java b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingSkulls.java new file mode 100644 index 000000000..4a771a059 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingSkulls.java @@ -0,0 +1,173 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.crafting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdvancementSpec; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class CraftingSkulls extends SimpleAdaptation { + + public CraftingSkulls() { + super("crafting-skulls"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("crafting.skulls.description")); + setDisplayName(Localizer.dLocalize("crafting.skulls.name")); + setIcon(Material.SKELETON_SKULL); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(17776); + registerRecipe(AdaptRecipe.shaped() + .key("crafting-skeletonskull") + .ingredient(new MaterialChar('I', Material.BONE)) + .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) + .shapes(List.of( + "III", + "IXI", + "III")) + .result(new ItemStack(Material.SKELETON_SKULL, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("crafting-witherskeletonskull") + .ingredient(new MaterialChar('I', Material.NETHER_BRICK)) + .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) + .shapes(List.of( + "III", + "IXI", + "III")) + .result(new ItemStack(Material.WITHER_SKELETON_SKULL, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("crafting-zombieskull") + .ingredient(new MaterialChar('I', Material.ROTTEN_FLESH)) + .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) + .shapes(List.of( + "III", + "IXI", + "III")) + .result(new ItemStack(Material.ZOMBIE_HEAD, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("crafting-creeperhead") + .ingredient(new MaterialChar('I', Material.GUNPOWDER)) + .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) + .shapes(List.of( + "III", + "IXI", + "III")) + .result(new ItemStack(Material.CREEPER_HEAD, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("crafting-dragonhead") + .ingredient(new MaterialChar('I', Material.DRAGON_BREATH)) + .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) + .shapes(List.of( + "III", + "IXI", + "III")) + .result(new ItemStack(Material.DRAGON_HEAD, 1)) + .build()); + AdvancementSpec skulls100 = AdvancementSpec.challenge( + "challenge_crafting_skulls_100", + Material.WITHER_SKELETON_SKULL, + Localizer.dLocalize("advancement.challenge_crafting_skulls_100.title"), + Localizer.dLocalize("advancement.challenge_crafting_skulls_100.description") + ); + AdvancementSpec skulls10 = AdvancementSpec.challenge( + "challenge_crafting_skulls_10", + Material.SKELETON_SKULL, + Localizer.dLocalize("advancement.challenge_crafting_skulls_10.title"), + Localizer.dLocalize("advancement.challenge_crafting_skulls_10.description") + ).withChild(skulls100); + registerAdvancementSpec(skulls10); + registerStatTracker(skulls10.statTracker("crafting.skulls.skulls-crafted", 10, 300)); + registerStatTracker(skulls100.statTracker("crafting.skulls.skulls-crafted", 100, 1000)); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore1")); + v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore2")); + v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore3")); + v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore4")); + v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore5")); + v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore6")); + } + + + @EventHandler + public void on(CraftItemEvent e) { + Player p = (Player) e.getWhoClicked(); + if (!hasActiveAdaptation(p)) return; + if (e.getRecipe() != null) { + Material result = e.getRecipe().getResult().getType(); + if (result == Material.SKELETON_SKULL || result == Material.WITHER_SKELETON_SKULL + || result == Material.ZOMBIE_HEAD || result == Material.CREEPER_HEAD + || result == Material.DRAGON_HEAD) { + getPlayer(p).getData().addStat("crafting.skulls.skulls-crafted", 1); + } + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Mob Skulls using materials surrounding a Bone Block.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingStations.java b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingStations.java new file mode 100644 index 000000000..5fca9e1c7 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingStations.java @@ -0,0 +1,174 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.crafting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdvancementSpec; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + + +public class CraftingStations extends SimpleAdaptation { + public CraftingStations() { + super("crafting-stations"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("crafting.stations.description")); + setDisplayName(Localizer.dLocalize("crafting.stations.name")); + setIcon(Material.CRAFTING_TABLE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(9248); + AdvancementSpec stations5k = AdvancementSpec.challenge( + "challenge_crafting_stations_5k", + Material.SMITHING_TABLE, + Localizer.dLocalize("advancement.challenge_crafting_stations_5k.title"), + Localizer.dLocalize("advancement.challenge_crafting_stations_5k.description") + ); + AdvancementSpec stations200 = AdvancementSpec.challenge( + "challenge_crafting_stations_200", + Material.CRAFTING_TABLE, + Localizer.dLocalize("advancement.challenge_crafting_stations_200.title"), + Localizer.dLocalize("advancement.challenge_crafting_stations_200.description") + ).withChild(stations5k); + registerAdvancementSpec(stations200); + registerStatTracker(stations200.statTracker("crafting.stations.portable-opens", 200, 300)); + registerStatTracker(stations5k.statTracker("crafting.stations.portable-opens", 5000, 1000)); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.RED + Localizer.dLocalize("crafting.stations.lore2")); + v.addLore(C.GRAY + Localizer.dLocalize("crafting.stations.lore3")); + if (getConfig().hungerCost > 0) { + v.addLore(C.YELLOW + "* " + getConfig().hungerCost + C.GRAY + " " + Localizer.dLocalize("crafting.stations.lore4")); + } + } + + @EventHandler + public void on(PlayerInteractEvent e) { + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + + if (p.hasCooldown(hand.getType())) { + e.setCancelled(true); + return; + } + + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) { + return; + } + + InventoryType station = switch (hand.getType()) { + case CRAFTING_TABLE -> InventoryType.WORKBENCH; + case GRINDSTONE -> InventoryType.GRINDSTONE; + case ANVIL -> InventoryType.ANVIL; + case STONECUTTER -> InventoryType.STONECUTTER; + case CARTOGRAPHY_TABLE -> InventoryType.CARTOGRAPHY; + case LOOM -> InventoryType.LOOM; + default -> null; + }; + + if (station == null) { + return; + } + + int hungerCost = getConfig().hungerCost; + if (hungerCost > 0 && p.getFoodLevel() < hungerCost) { + return; + } + + if (hungerCost > 0) { + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + } + + p.setCooldown(hand.getType(), 1000); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); + sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); + if (station == InventoryType.WORKBENCH) { + p.openWorkbench(null, true); + } else { + Inventory inv = Bukkit.createInventory(p, station); + p.openInventory(inv); + } + getPlayer(p).getData().addStat("crafting.stations.portable-opens", 1); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Use crafting tables, anvils, and other stations in the palm of your hand.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Crafting Stations adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public int cooldown = 125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hunger points consumed each time a portable station is opened.", impact = "Higher values make portable station access cost more food; 0 disables the cost.") + int hungerCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingXP.java b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingXP.java new file mode 100644 index 000000000..d75bf5af2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/crafting/CraftingXP.java @@ -0,0 +1,134 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.crafting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdvancementSpec; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; + + +public class CraftingXP extends SimpleAdaptation { + private final Map cooldown = new java.util.concurrent.ConcurrentHashMap<>(); + + + public CraftingXP() { + super("crafting-xp"); + registerConfiguration(CraftingXP.Config.class); + setDisplayName(Localizer.dLocalize("crafting.xp.name")); + setDescription(Localizer.dLocalize("crafting.xp.description")); + setIcon(Material.ENCHANTED_BOOK); + setInterval(5580); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + AdvancementSpec xp25k = AdvancementSpec.challenge( + "challenge_crafting_xp_25k", + Material.EXPERIENCE_BOTTLE, + Localizer.dLocalize("advancement.challenge_crafting_xp_25k.title"), + Localizer.dLocalize("advancement.challenge_crafting_xp_25k.description") + ); + AdvancementSpec xp1k = AdvancementSpec.challenge( + "challenge_crafting_xp_1k", + Material.CRAFTING_TABLE, + Localizer.dLocalize("advancement.challenge_crafting_xp_1k.title"), + Localizer.dLocalize("advancement.challenge_crafting_xp_1k.description") + ).withChild(xp25k); + registerAdvancementSpec(xp1k); + registerStatTracker(xp1k.statTracker("crafting.xp.items-crafted", 1000, 300)); + registerStatTracker(xp25k.statTracker("crafting.xp.items-crafted", 25000, 1500)); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("crafting.xp.lore1")); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + Player p = e.getPlayer(); + cooldown.remove(p.getUniqueId()); + } + + + @EventHandler(priority = EventPriority.LOW) + public void on(CraftItemEvent e) { + Player p = (Player) e.getWhoClicked(); + if (e.getInventory().getResult() != null && hasActiveAdaptation(p) && e.getInventory().getResult().getAmount() > 0) { + if (e.getInventory().getResult() != null && e.getCursor() != null && e.getCursor().getAmount() < 64) { + if (p.getInventory().addItem(e.getCurrentItem()).isEmpty()) { + p.getInventory().removeItem(e.getCurrentItem()); + if (cooldown.containsKey(p.getUniqueId()) && cooldown.get(p.getUniqueId()) + 20000 < System.currentTimeMillis()) { + cooldown.remove(p.getUniqueId()); + } else if (cooldown.containsKey(p.getUniqueId()) && cooldown.get(p.getUniqueId()) + 20000 > System.currentTimeMillis()) { + return; + } + cooldown.put(p.getUniqueId(), System.currentTimeMillis()); + p.getWorld().spawn(p.getLocation(), org.bukkit.entity.ExperienceOrb.class).setExperience(getLevel(p) * 2); + getPlayer(p).getData().addStat("crafting.xp.items-crafted", 1); + } + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain passive XP when crafting items.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryArchaeologist.java b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryArchaeologist.java new file mode 100644 index 000000000..deab0ebc2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryArchaeologist.java @@ -0,0 +1,429 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.discovery; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.EventExecutor; + +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicBoolean; + +public class DiscoveryArchaeologist extends SimpleAdaptation { + private static final String BLOCK_BRUSH_EVENT_CLASS = "org.bukkit.event.block.BlockBrushEvent"; + private static final long BRUSH_FALLBACK_WINDOW_MILLIS = 25000L; + private final Map cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map pendingBrushes = new java.util.concurrent.ConcurrentHashMap<>(); + private final AtomicBoolean brushEventFailureWarned = new AtomicBoolean(false); + private final BrushEventBridge brushEventBridge; + + public DiscoveryArchaeologist() { + super("discovery-archaeologist"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("discovery.archaeologist.description")); + setDisplayName(Localizer.dLocalize("discovery.archaeologist.name")); + setIcon(Material.BRUSH); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2300); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BRUSH) + .key("challenge_discovery_archaeologist_50") + .title(Localizer.dLocalize("advancement.challenge_discovery_archaeologist_50.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_archaeologist_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DECORATED_POT) + .key("challenge_discovery_archaeologist_500") + .title(Localizer.dLocalize("advancement.challenge_discovery_archaeologist_500.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_archaeologist_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discovery_archaeologist_50", "discovery.archaeologist.bonus-finds", 50, 300); + registerMilestone("challenge_discovery_archaeologist_500", "discovery.archaeologist.bonus-finds", 500, 1000); + brushEventBridge = BrushEventBridge.create(); + registerBrushEventBridge(brushEventBridge); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getBonusRollChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("discovery.archaeologist.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getRareRewardChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("discovery.archaeologist.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("discovery.archaeologist.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + cooldowns.remove(id); + pendingBrushes.remove(id); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + if (e.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + ItemStack hand = e.getItem(); + if (hand == null || hand.getType() != Material.BRUSH) { + return; + } + + Block block = e.getClickedBlock(); + if (block == null || !isSuspiciousBlock(block.getType())) { + return; + } + + Player p = e.getPlayer(); + if (getActiveBlockBreakLevel(p, block.getLocation()) <= 0) { + return; + } + + pendingBrushes.put(p.getUniqueId(), PendingBrush.from(block, System.currentTimeMillis() + BRUSH_FALLBACK_WINDOW_MILLIS)); + } + + private void registerBrushEventBridge(BrushEventBridge bridge) { + if (bridge == null) { + Adapt.verbose("BlockBrushEvent not available; discovery-archaeologist will use interaction fallback tracking."); + return; + } + + EventExecutor executor = (listener, event) -> onBrush(event, bridge); + Bukkit.getPluginManager().registerEvent(bridge.eventClass, this, EventPriority.HIGHEST, executor, Adapt.instance, true); + } + + private void onBrush(Event event, BrushEventBridge bridge) { + try { + Player p = bridge.player(event); + Block block = bridge.block(event); + Material originalType = block == null ? null : block.getType(); + if (p != null) { + PendingBrush pending = pendingBrushes.get(p.getUniqueId()); + if (pending != null && isSuspiciousBlock(pending.originalType)) { + originalType = pending.originalType; + } + } + + Material newStateType = bridge.newStateType(event); + handleBrush(p, block, originalType, newStateType); + if (p != null && newStateType != null && !isSuspiciousBlock(newStateType)) { + pendingBrushes.remove(p.getUniqueId()); + } + } catch (Throwable t) { + if (brushEventFailureWarned.compareAndSet(false, true)) { + Adapt.warn("DiscoveryArchaeologist brush event bridge failed once (" + t.getClass().getSimpleName() + ": " + t.getMessage() + ")."); + } + } + } + + private void handleBrush(Player p, Block block, Material originalType, Material newStateType) { + if (p == null || block == null) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (!isSuspiciousBlock(originalType)) { + return; + } + + // Only award when brushing actually completes and the suspicious block resolves. + if (newStateType == null || isSuspiciousBlock(newStateType)) { + return; + } + + if (!canBlockBreak(p, block.getLocation())) { + return; + } + + long now = System.currentTimeMillis(); + long nextReady = cooldowns.getOrDefault(p.getUniqueId(), 0L); + if (now < nextReady) { + return; + } + + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + if (ThreadLocalRandom.current().nextDouble() > getBonusRollChance(level)) { + return; + } + + ItemStack reward = rollReward(level); + Map overflow = p.getInventory().addItem(reward); + overflow.values().forEach(item -> p.getWorld().dropItemNaturally(p.getLocation(), item)); + + if (areParticlesEnabled()) { + block.getWorld().spawnParticle(Particle.ENCHANT, block.getLocation().add(0.5, 0.65, 0.5), 18, 0.25, 0.2, 0.25, 0.2); + } + if (areParticlesEnabled()) { + block.getWorld().spawnParticle(Particle.CRIT, block.getLocation().add(0.5, 0.65, 0.5), 12, 0.22, 0.22, 0.22, 0.02); + } + SoundPlayer sp = SoundPlayer.of(block.getWorld()); + sp.play(block.getLocation().add(0.5, 0.5, 0.5), Sound.ITEM_BRUSH_BRUSHING_SAND_COMPLETE, 1f, 1.15f); + sp.play(block.getLocation().add(0.5, 0.5, 0.5), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.7f, 1.55f); + xp(p, getConfig().xpPerReward + (getValue(reward.getType()) * getConfig().rewardValueXpMultiplier)); + getPlayer(p).getData().addStat("discovery.archaeologist.bonus-finds", 1); + } + + private ItemStack rollReward(int level) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + if (random.nextDouble() <= getRareRewardChance(level)) { + return switch (random.nextInt(4)) { + case 0 -> new ItemStack(Material.DIAMOND, 1); + case 1 -> new ItemStack(Material.EMERALD, 1); + case 2 -> new ItemStack(Material.GOLD_INGOT, 1 + random.nextInt(2)); + default -> + new ItemStack(Material.AMETHYST_SHARD, 2 + random.nextInt(3)); + }; + } + + return switch (random.nextInt(6)) { + case 0 -> new ItemStack(Material.BRICK, 1 + random.nextInt(2)); + case 1 -> new ItemStack(Material.CLAY_BALL, 2 + random.nextInt(3)); + case 2 -> new ItemStack(Material.BONE, 1 + random.nextInt(2)); + case 3 -> new ItemStack(Material.FLINT, 1 + random.nextInt(2)); + case 4 -> new ItemStack(Material.STRING, 1 + random.nextInt(2)); + default -> new ItemStack(Material.COAL, 1 + random.nextInt(2)); + }; + } + + private boolean isSuspiciousBlock(Material type) { + return type == Material.SUSPICIOUS_SAND || type == Material.SUSPICIOUS_GRAVEL; + } + + private double getBonusRollChance(int level) { + return Math.min(getConfig().maxBonusRollChance, + getConfig().bonusRollChanceBase + (getLevelPercent(level) * getConfig().bonusRollChanceFactor)); + } + + private double getRareRewardChance(int level) { + return Math.min(getConfig().maxRareRewardChance, + getConfig().rareRewardChanceBase + (getLevelPercent(level) * getConfig().rareRewardChanceFactor)); + } + + private long getCooldownMillis(int level) { + return Math.max(250L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + if (pendingBrushes.isEmpty()) { + return; + } + + long now = System.currentTimeMillis(); + Iterator> iterator = pendingBrushes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + PendingBrush pending = entry.getValue(); + if (pending.expiresAt <= now) { + iterator.remove(); + continue; + } + + Player p = Bukkit.getPlayer(entry.getKey()); + if (p == null || !p.isOnline()) { + iterator.remove(); + continue; + } + + if (!hasActiveAdaptation(p)) { + iterator.remove(); + continue; + } + + Block current = pending.resolveBlock(); + if (current == null) { + iterator.remove(); + continue; + } + + Material currentType = current.getType(); + if (isSuspiciousBlock(currentType)) { + continue; + } + + handleBrush(p, current, pending.originalType, currentType); + iterator.remove(); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private static final class BrushEventBridge { + private final Class eventClass; + private final Method getPlayer; + private final Method getBlock; + private final Method getNewState; + private final Method getBlockStateType; + + private BrushEventBridge(Class eventClass, Method getPlayer, Method getBlock, Method getNewState, Method getBlockStateType) { + this.eventClass = eventClass; + this.getPlayer = getPlayer; + this.getBlock = getBlock; + this.getNewState = getNewState; + this.getBlockStateType = getBlockStateType; + } + + private static BrushEventBridge create() { + try { + Class eventType = Class.forName(BLOCK_BRUSH_EVENT_CLASS); + if (!Event.class.isAssignableFrom(eventType)) { + return null; + } + + Method getPlayer = eventType.getMethod("getPlayer"); + Method getBlock = eventType.getMethod("getBlock"); + Method getNewState = eventType.getMethod("getNewState"); + Class blockStateType = Class.forName("org.bukkit.block.BlockState"); + Method getBlockStateType = blockStateType.getMethod("getType"); + + @SuppressWarnings("unchecked") + Class typedEvent = (Class) eventType; + return new BrushEventBridge(typedEvent, getPlayer, getBlock, getNewState, getBlockStateType); + } catch (Throwable ignored) { + return null; + } + } + + private Player player(Event event) throws ReflectiveOperationException { + Object value = getPlayer.invoke(event); + return value instanceof Player p ? p : null; + } + + private Block block(Event event) throws ReflectiveOperationException { + Object value = getBlock.invoke(event); + return value instanceof Block b ? b : null; + } + + private Material newStateType(Event event) throws ReflectiveOperationException { + Object newState = getNewState.invoke(event); + if (newState == null) { + return null; + } + + Object material = getBlockStateType.invoke(newState); + return material instanceof Material m ? m : null; + } + } + + private static final class PendingBrush { + private final UUID worldId; + private final int x; + private final int y; + private final int z; + private final Material originalType; + private final long expiresAt; + + private PendingBrush(UUID worldId, int x, int y, int z, Material originalType, long expiresAt) { + this.worldId = worldId; + this.x = x; + this.y = y; + this.z = z; + this.originalType = originalType; + this.expiresAt = expiresAt; + } + + private static PendingBrush from(Block block, long expiresAt) { + return new PendingBrush(block.getWorld().getUID(), block.getX(), block.getY(), block.getZ(), block.getType(), expiresAt); + } + + private Block resolveBlock() { + World world = Bukkit.getWorld(worldId); + return world == null ? null : world.getBlockAt(x, y, z); + } + } + + @NoArgsConstructor + @ConfigDescription("Brushing suspicious blocks has a chance to grant bonus archaeology loot.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Roll Chance Base for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusRollChanceBase = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Roll Chance Factor for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusRollChanceFactor = 0.43; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Bonus Roll Chance for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxBonusRollChance = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Rare Reward Chance Base for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rareRewardChanceBase = 0.04; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Rare Reward Chance Factor for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rareRewardChanceFactor = 0.24; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Rare Reward Chance for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxRareRewardChance = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 1600; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Reward for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerReward = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reward Value Xp Multiplier for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rewardValueXpMultiplier = 0.45; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryArmor.java b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryArmor.java new file mode 100644 index 000000000..964cc1f4e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryArmor.java @@ -0,0 +1,216 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.discovery; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.IAttribute; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.math.Sphere; +import art.arcane.adapt.util.common.math.VectorMath; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.util.Vector; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class DiscoveryArmor extends SimpleAdaptation { + private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-discovery-armor".getBytes()); + private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString("adapt:discovery-armor"); + private static final long UPDATE_COOLDOWN = TimeUnit.SECONDS.toMillis(3); + private static final Sphere SPHERE = new Sphere(5); + + private final KMap playerData = new KMap<>(); + + public DiscoveryArmor() { + super("discovery-world-armor"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("discovery.armor.description")); + setDisplayName(Localizer.dLocalize("discovery.armor.name")); + setIcon(Material.TURTLE_HELMET); + setInterval(305); + setBaseCost(getConfig().baseCost); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_CHESTPLATE) + .key("challenge_discovery_armor_1hr") + .title(Localizer.dLocalize("advancement.challenge_discovery_armor_1hr.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_armor_1hr.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_CHESTPLATE) + .key("challenge_discovery_armor_24hr") + .title(Localizer.dLocalize("advancement.challenge_discovery_armor_24hr.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_armor_24hr.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discovery_armor_1hr", "discovery.armor.ticks-with-bonus", 72000, 400); + registerMilestone("challenge_discovery_armor_24hr", "discovery.armor.ticks-with-bonus", 1728000, 2000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("discovery.armor.lore1") + C.GRAY + ", " + Localizer.dLocalize("discovery.armor.lore2")); + v.addLore(C.YELLOW + "~ " + Localizer.dLocalize("discovery.armor.lore3") + C.BLUE + " +" + level * 0.25); + } + + public double getArmorPoints(Material m) { + return Math.log(Math.min(2000, m.getBlastResistance() * m.getBlastResistance())) + Math.log((m.getHardness() < 0 ? 50 : Math.min(50, m.getHardness() + 25)) * 0.33); + } + + public double getArmor(Location l, int level) { + Block center = l.getBlock(); + double armorValue = 0.0; + double count = 0; + + art.arcane.adapt.util.common.math.Sphere sphere = SPHERE.clone(); + + while (sphere.hasNext()) { + art.arcane.volmlib.util.math.BlockPosition r = sphere.next(); + Block b = center.getRelative(r.getX(), r.getY(), r.getZ()); + if (b.isEmpty() || b.isLiquid()) + continue; + + count++; + double a = getArmorPoints(b.getType()); + if (Double.isNaN(a) || a < 0) { + a = 0; + } + armorValue += a; + + if (a > 2 && M.r(0.005 * a)) { + Vector v = VectorMath.directionNoNormal(l, b.getLocation().add(0.5, 0.5, 0.5)); + if (areParticlesEnabled()) { + l.getWorld().spawnParticle(Particles.ENCHANTMENT_TABLE, l.clone().add(0, 1, 0), 0, v.getX(), v.getY(), v.getZ()); + } + } + } + + return Math.min((armorValue / count) * (level / 2D) * 0.65, 10); + } + + + private double getRadius(double factor) { + return factor * getConfig().radiusFactor; + } + + private double getStrength(double factor) { + return Math.pow(factor, getConfig().strengthExponent); + } + + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) continue; + + long now = M.ms(); + Long nextUpdate = playerData.getOrDefault(p.getUniqueId(), now); + if (nextUpdate > now) continue; + playerData.put(p.getUniqueId(), now + UPDATE_COOLDOWN); + + IAttribute attribute = Version.get().getAttribute(p, Attributes.GENERIC_ARMOR); + if (attribute == null) continue; + + if (!hasActiveAdaptation(p)) { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + } else { + double oldArmor = 0; + for (IAttribute.Modifier modifier : attribute.getModifier(MODIFIER, MODIFIER_KEY)) { + double amount = modifier.getAmount(); + if (!Double.isNaN(amount) && amount > oldArmor) { + oldArmor = amount; + } + } + + double armor = getArmor(p.getLocation(), getLevel(p)); + armor = Double.isNaN(armor) ? 0 : armor; + + double lArmor = M.lerp(oldArmor, armor, 0.3); + lArmor = Double.isNaN(lArmor) ? 0 : lArmor; + attribute.setModifier(MODIFIER, MODIFIER_KEY, lArmor, AttributeModifier.Operation.ADD_NUMBER); + if (lArmor > 0) { + adaptPlayer.getData().addStat("discovery.armor.ticks-with-bonus", 1); + } + } + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + playerData.remove(event.getPlayer().getUniqueId()); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain passive armor based on nearby block hardness.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Discovery Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public int radiusFactor = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Strength Exponent for the Discovery Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double strengthExponent = 1.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Discovery Armor adaptation.", impact = "True enables this behavior and false disables it.") + public boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryBetterMending.java b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryBetterMending.java new file mode 100644 index 000000000..a0205d875 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryBetterMending.java @@ -0,0 +1,217 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.discovery; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; + +public class DiscoveryBetterMending extends SimpleAdaptation { + public DiscoveryBetterMending() { + super("discovery-better-mending"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("discovery.better_mending.description")); + setDisplayName(Localizer.dLocalize("discovery.better_mending.name")); + setIcon(Material.PHANTOM_MEMBRANE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ANVIL) + .key("challenge_discovery_mending_10k") + .title(Localizer.dLocalize("advancement.challenge_discovery_mending_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_mending_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENCHANTED_GOLDEN_APPLE) + .key("challenge_discovery_mending_100k") + .title(Localizer.dLocalize("advancement.challenge_discovery_mending_100k.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_mending_100k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discovery_mending_10k", "discovery.better-mending.durability-restored", 10000, 400); + registerMilestone("challenge_discovery_mending_100k", "discovery.better-mending.durability-restored", 100000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRepairPerXp(level)) + C.GRAY + " " + Localizer.dLocalize("discovery.better_mending.lore1")); + v.addLore(C.GREEN + "+ " + getMaxXpSpend(level) + C.GRAY + " " + Localizer.dLocalize("discovery.better_mending.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("discovery.better_mending.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Action action = e.getAction(); + if (action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!canMend(hand) || p.hasCooldown(hand.getType())) { + return; + } + + Damageable damageable = (Damageable) hand.getItemMeta(); + if (damageable == null || damageable.getDamage() <= 0) { + return; + } + + int availableXp = getTotalExp(p); + if (availableXp <= 0) { + return; + } + + double repairPerXp = getRepairPerXp(level); + int maxXpSpend = Math.min(getMaxXpSpend(level), availableXp); + int currentDamage = damageable.getDamage(); + int xpNeeded = (int) Math.ceil(currentDamage / repairPerXp); + int xpSpent = Math.min(maxXpSpend, xpNeeded); + if (xpSpent <= 0) { + return; + } + + int repaired = Math.max(1, (int) Math.round(xpSpent * repairPerXp)); + int newDamage = Math.max(0, currentDamage - repaired); + + takeExp(p, xpSpent, true); + damageable.setDamage(newDamage); + hand.setItemMeta(damageable); + p.getInventory().setItemInMainHand(hand); + p.setCooldown(hand.getType(), getCooldownTicks(level)); + e.setCancelled(true); + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8f, 1.45f); + if (newDamage <= 0) { + sp.play(p.getLocation(), Sound.BLOCK_ANVIL_USE, 0.5f, 1.65f); + } + + xp(p, Math.max(1D, (currentDamage - newDamage) * getConfig().skillXpPerDurability)); + getPlayer(p).getData().addStat("discovery.better-mending.durability-restored", repaired); + } + + private boolean canMend(ItemStack hand) { + if (!isItem(hand) || hand.getType().getMaxDurability() <= 0) { + return false; + } + + if (!hand.containsEnchantment(Enchantment.MENDING)) { + return false; + } + + if (!(hand.getItemMeta() instanceof Damageable damageable)) { + return false; + } + + return damageable.getDamage() > 0; + } + + private double getRepairPerXp(int level) { + return Math.max(0.1, getConfig().repairPerXpBase + (getLevelPercent(level) * getConfig().repairPerXpFactor)); + } + + private int getMaxXpSpend(int level) { + return Math.max(1, (int) Math.round(getConfig().maxXpSpendBase + (getLevelPercent(level) * getConfig().maxXpSpendFactor))); + } + + private int getCooldownTicks(int level) { + return Math.max(6, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksReduction))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-left-click to spend XP and directly mend the Mending item in your hand.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Repair Per Xp Base for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double repairPerXpBase = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Repair Per Xp Factor for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double repairPerXpFactor = 4.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Xp Spend Base for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxXpSpendBase = 14.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Xp Spend Factor for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxXpSpendFactor = 130.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 38.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Reduction for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksReduction = 26.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Skill Xp Per Durability for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double skillXpPerDurability = 0.35; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryCartographerPulse.java b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryCartographerPulse.java new file mode 100644 index 000000000..c28c7b805 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryCartographerPulse.java @@ -0,0 +1,292 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.discovery; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; + +public class DiscoveryCartographerPulse extends SimpleAdaptation { + private final Map cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + + public DiscoveryCartographerPulse() { + super("discovery-cartographer-pulse"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("discovery.cartographer_pulse.description")); + setDisplayName(Localizer.dLocalize("discovery.cartographer_pulse.name")); + setIcon(Material.COMPASS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COMPASS) + .key("challenge_discovery_cartographer_100") + .title(Localizer.dLocalize("advancement.challenge_discovery_cartographer_100.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_cartographer_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.FILLED_MAP) + .key("challenge_discovery_cartographer_1k") + .title(Localizer.dLocalize("advancement.challenge_discovery_cartographer_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_cartographer_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discovery_cartographer_100", "discovery.cartographer-pulse.pulses", 100, 300); + registerMilestone("challenge_discovery_cartographer_1k", "discovery.cartographer-pulse.pulses", 1000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getSearchRange(level)) + C.GRAY + " " + Localizer.dLocalize("discovery.cartographer_pulse.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("discovery.cartographer_pulse.lore2")); + if (getConfig().hungerCost > 0) { + v.addLore(C.RED + "* " + getConfig().hungerCost + C.GRAY + " " + Localizer.dLocalize("discovery.cartographer_pulse.lore_cost_hunger")); + } + } + + @EventHandler + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0 || p.getInventory().getItemInMainHand().getType() != Material.COMPASS) { + return; + } + + long now = System.currentTimeMillis(); + if (now < cooldowns.getOrDefault(p.getUniqueId(), 0L)) { + return; + } + + int hungerCost = Math.max(0, getConfig().hungerCost); + if (hungerCost > 0 && p.getFoodLevel() < hungerCost) { + return; + } + + Location target = locateNearestStructureFallback(p.getWorld(), p.getLocation(), getSearchRange(level)); + if (target == null) { + target = p.getWorld().getSpawnLocation(); + } + + p.setCompassTarget(target); + p.sendMessage(C.AQUA + "Compass pulse: " + C.WHITE + Form.f(target.getBlockX()) + ", " + Form.f(target.getBlockZ())); + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + if (hungerCost > 0) { + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ITEM_LODESTONE_COMPASS_LOCK, 0.8f, 1.3f); + xp(p, getConfig().xpPerPulse); + getPlayer(p).getData().addStat("discovery.cartographer-pulse.pulses", 1); + } + + private Location locateNearestStructureFallback(World world, Location from, int range) { + try { + for (Method m : world.getClass().getMethods()) { + if (!m.getName().equals("locateNearestStructure")) { + continue; + } + + Class[] p = m.getParameterTypes(); + if (p.length < 4 || p[0] != Location.class || p[2] != int.class || p[3] != boolean.class) { + continue; + } + + Object structure = resolvePreferredStructureType(p[1]); + if (structure == null) { + continue; + } + + Object out; + if (p.length == 4) { + out = m.invoke(world, from, structure, range, false); + } else if (p.length == 5 && p[4] == boolean.class) { + out = m.invoke(world, from, structure, range, false, false); + } else { + continue; + } + + Location location = extractLocation(out); + if (location != null) { + return location; + } + } + } catch (Throwable ignored) { + } + + return null; + } + + private Object resolvePreferredStructureType(Class structureTypeClass) { + String[] preferred = {"VILLAGE", "STRONGHOLD", "RUINED_PORTAL", "MINESHAFT", "SHIPWRECK", "TRAIL_RUINS"}; + + if (structureTypeClass.isEnum()) { + Object[] constants = structureTypeClass.getEnumConstants(); + if (constants == null || constants.length == 0) { + return null; + } + + for (String name : preferred) { + Object c = Arrays.stream(constants).filter(i -> ((Enum) i).name().equals(name)).findFirst().orElse(null); + if (c != null) { + return c; + } + } + + return constants[0]; + } + + for (String name : preferred) { + try { + Field f = structureTypeClass.getField(name); + Object value = f.get(null); + if (value != null) { + return value; + } + } catch (Throwable ignored) { + } + } + + try { + Method values = structureTypeClass.getMethod("values"); + Object out = values.invoke(null); + if (out instanceof Object[] a && a.length > 0) { + return a[0]; + } + } catch (Throwable ignored) { + } + + return null; + } + + private Location extractLocation(Object out) { + if (out instanceof Location location) { + return location; + } + + if (out == null) { + return null; + } + + try { + Method getter = out.getClass().getMethod("getLocation"); + Object loc = getter.invoke(out); + if (loc instanceof Location location) { + return location; + } + } catch (Throwable ignored) { + } + + return null; + } + + private int getSearchRange(int level) { + return Math.max(128, (int) Math.round(getConfig().searchRangeBase + (getLevelPercent(level) * getConfig().searchRangeFactor))); + } + + private long getCooldownMillis(int level) { + return Math.max(1500L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click with a compass to pulse toward a nearby structure target.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Search Range Base for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double searchRangeBase = 640; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Search Range Factor for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double searchRangeFactor = 768; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 26000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 14000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Pulse for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerPulse = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Food points consumed per compass pulse.", impact = "Higher values make each pulse cost more hunger; 0 disables the cost.") + int hungerCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryUnity.java b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryUnity.java new file mode 100644 index 000000000..59dd45d42 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryUnity.java @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.discovery; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerExpChangeEvent; + +import java.util.List; + +import static xyz.xenondevs.particle.utils.MathUtils.RANDOM; + +public class DiscoveryUnity extends SimpleAdaptation { + public DiscoveryUnity() { + super("discovery-unity"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("discovery.unity.description")); + setDisplayName(Localizer.dLocalize("discovery.unity.name")); + setIcon(Material.END_CRYSTAL); + setBaseCost(getConfig().baseCost); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInterval(666); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EXPERIENCE_BOTTLE) + .key("challenge_discovery_unity_5k") + .title(Localizer.dLocalize("advancement.challenge_discovery_unity_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_unity_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENCHANTING_TABLE) + .key("challenge_discovery_unity_50k") + .title(Localizer.dLocalize("advancement.challenge_discovery_unity_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_unity_50k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discovery_unity_5k", "discovery.unity.orbs-distributed", 5000, 400); + registerMilestone("challenge_discovery_unity_50k", "discovery.unity.orbs-distributed", 50000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getXPGained(getLevelPercent(level), 1), 0) + " " + Localizer.dLocalize("discovery.unity.lore1") + C.GRAY + " " + Localizer.dLocalize("discovery.unity.lore2")); + } + + //Give random XP to the player when they gain XP! + @EventHandler(priority = EventPriority.LOW) + public void on(PlayerExpChangeEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + AdaptPlayer ap = getPlayer(p); + if (hasActiveAdaptation(p) && e.getAmount() > 0) { + xp(p, 5); + sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.9f); + //get a random skill that they have unlocked already + List skills = ap.getData().getSkillLines().sortV(); + if (skills.size() > 0) { + PlayerSkillLine skill = skills.get(RANDOM.nextInt(skills.size())); + //give them a random amount of XP in that skill + skill.giveXPFresh(Adapt.instance.getAdaptServer().getPlayer(p).getNot(), getXPGained(getLevelPercent(getLevel(p)), RANDOM.nextInt(3) + 1)); + getPlayer(p).getData().addStat("discovery.unity.orbs-distributed", 1); + } + + } + } + + private double getXPGained(double factor, int amount) { + return amount * getConfig().xpGainedMultiplier * factor; + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Collecting Experience Orbs adds XP to random skills.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Gained Multiplier for the Discovery Unity adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpGainedMultiplier = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Boost Multiplier for the Discovery Unity adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpBoostMultiplier = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Boost Duration for the Discovery Unity adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int xpBoostDuration = 15000; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryVillagerAtt.java b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryVillagerAtt.java new file mode 100644 index 000000000..c151d70b0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryVillagerAtt.java @@ -0,0 +1,210 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.discovery; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.ReceiveCancelledEvents; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import de.slikey.effectlib.effect.BleedEffect; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class DiscoveryVillagerAtt extends SimpleAdaptation { + private final KMap active = new KMap<>(); + + public DiscoveryVillagerAtt() { + super("discovery-villager-att"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("discovery.villager.description")); + setDisplayName(Localizer.dLocalize("discovery.villager.name")); + setIcon(Material.GLASS_BOTTLE); + setInterval(2432); + setBaseCost(getConfig().baseCost); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EMERALD) + .key("challenge_discovery_villager_100") + .title(Localizer.dLocalize("advancement.challenge_discovery_villager_100.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_villager_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.EMERALD_BLOCK) + .key("challenge_discovery_villager_2500") + .title(Localizer.dLocalize("advancement.challenge_discovery_villager_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_villager_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discovery_villager_100", "discovery.villager-att.improved-trades", 100, 300); + registerMilestone("challenge_discovery_villager_2500", "discovery.villager-att.improved-trades", 2500, 1000); + } + + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("discovery.villager.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getEffectiveness(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("discovery.villager.lore2")); + v.addLore(C.GREEN + "+ " + getXpTaken(level) + " " + C.GRAY + Localizer.dLocalize("discovery.villager.lore3")); + } + + private double getEffectiveness(double multiplier) { + return Math.min(getConfig().maxEffectiveness, multiplier * multiplier + getConfig().effectivenessBase); + } + + private int getXpTaken(double level) { + double d = (getConfig().levelCostAdd * getConfig().amplifier) - (level * getConfig().levelDrain); + return (int) d; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerInteractEntityEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + int level = getActiveLevel(p); + if (e.getRightClicked() instanceof Villager v && level > 0) { + if (ThreadLocalRandom.current().nextDouble() <= getEffectiveness(getLevelPercent(level))) { + if (p.getLevel() - getXpTaken(level) > 0) { + BleedEffect blood = new BleedEffect(Adapt.instance.adaptEffectManager); // Enemy gets blood + blood.material = Material.EMERALD; + blood.setEntity(v); + p.setLevel((p.getLevel() - getXpTaken(level))); + sp.play(p.getLocation(), Sound.ENTITY_VILLAGER_CELEBRATE, 1f, 1f); + sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f); + active.put(p.getUniqueId(), level); + p.addPotionEffect(new PotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE, 60, level, true, true)); + getPlayer(p).getData().addStat("discovery.villager-att.improved-trades", 1); + } else { + BleedEffect blood = new BleedEffect(Adapt.instance.adaptEffectManager); // Enemy gets blood + blood.material = Material.STONE; + v.shakeHead(); + blood.setEntity(v); + sp.play(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f); + } + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + @ReceiveCancelledEvents + public void on(InventoryOpenEvent event) { + if (!(event.getPlayer() instanceof Player p)) { + return; + } + int level = active.getOrDefault(p.getUniqueId(), 0); + if (level == 0) return; + + if (event.isCancelled()) { + active.remove(p.getUniqueId()); + p.removePotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE); + } else { + p.addPotionEffect(new PotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE, 60, level, true, true)); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(InventoryCloseEvent event) { + if (!(event.getPlayer() instanceof Player p) || !active.containsKey(p.getUniqueId())) { + return; + } + + active.remove(p.getUniqueId()); + p.removePotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE); + } + + @EventHandler + public void on(PlayerQuitEvent event) { + active.remove(event.getPlayer().getUniqueId()); + } + + @Override + public void onTick() { + active.forEach((p, lvl) -> { + org.bukkit.entity.Player player = Bukkit.getPlayer(p); + if (player == null) return; + J.runEntity(player, () -> player.addPotionEffect(new PotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE, 60, lvl, true, true))); + }); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Get better villager trades at the cost of XP per interaction.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effectiveness Base for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double effectivenessBase = 0.005; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Effectiveness for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxEffectiveness = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Level Drain for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int levelDrain = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Level Cost Add for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int levelCostAdd = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Amplifier for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double amplifier = 1.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryXpResist.java b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryXpResist.java new file mode 100644 index 000000000..b4b74e35d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/discovery/DiscoveryXpResist.java @@ -0,0 +1,204 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.discovery; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; + +import java.util.Map; +import java.util.UUID; + +public class DiscoveryXpResist extends SimpleAdaptation { + private static final long COOLDOWN_MILLIS = 15000L; + private final Map cooldowns; + + public DiscoveryXpResist() { + super("discovery-xp-resist"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("discovery.resist.description")); + setDisplayName(Localizer.dLocalize("discovery.resist.name")); + setIcon(Material.TOTEM_OF_UNDYING); + setInterval(5215); + setBaseCost(getConfig().baseCost); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TOTEM_OF_UNDYING) + .key("challenge_discovery_xp_resist_25") + .title(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_25.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENCHANTED_GOLDEN_APPLE) + .key("challenge_discovery_xp_resist_250") + .title(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_250.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_250.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TOTEM_OF_UNDYING) + .key("challenge_discovery_xp_resist_clutch") + .title(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_clutch.title")) + .description(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_clutch.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_discovery_xp_resist_25", "discovery.xp-resist.saves", 25, 500); + registerMilestone("challenge_discovery_xp_resist_250", "discovery.xp-resist.saves", 250, 2000); + } + + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("discovery.resist.lore0")); + v.addLore(C.GREEN + "+ " + Form.pc(getEffectiveness(getLevelPercent(level)), 0) + C.GRAY + Localizer.dLocalize("discovery.resist.lore1")); + v.addLore(C.GREEN + "+ " + getXpTaken(level) + " " + C.GRAY + Localizer.dLocalize("discovery.resist.lore2")); + } + + private double getEffectiveness(double factor) { + return Math.min(getConfig().maxEffectiveness, factor * factor + getConfig().effectivenessBase); + } + + private int getXpTaken(double level) { + double d = (getConfig().levelCostAdd * getConfig().amplifier) - (level * getConfig().levelDrain); + return Math.max(1, (int) Math.round(d)); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (!isCriticalHealthDamage(p, e)) { + return; + } + + SoundPlayer sp = SoundPlayer.of(p); + int xpCost = getXpTaken(level); + if (p.getLevel() < xpCost) { + vfxFastRing(p.getLocation().add(0, 0.05, 0), 1, Color.RED); + sp.play(p.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 15, 0.01f); + return; + } + UUID id = p.getUniqueId(); + Long cooldown = cooldowns.get(id); + if (cooldown == null || M.ms() - cooldown > COOLDOWN_MILLIS) { + double effectiveness = getEffectiveness(getLevelPercent(level)); + double originalDamage = e.getDamage(); + e.setDamage(Math.max(0D, e.getDamage() * (1D - effectiveness))); + xp(p, 5); + cooldowns.put(id, M.ms()); + p.setLevel(p.getLevel() - xpCost); + vfxFastRing(p.getLocation().add(0, 0.05, 0), 1, Color.LIME); + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_REPAIR, 3, 0.01f); + sp.play(p.getLocation(), Sound.BLOCK_SHROOMLIGHT_HIT, 15, 0.01f); + getPlayer(p).getData().addStat("discovery.xp-resist.saves", 1); + if (originalDamage >= 30.0 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_discovery_xp_resist_clutch")) { + getPlayer(p).getAdvancementHandler().grant("challenge_discovery_xp_resist_clutch"); + } + } else { + vfxFastRing(p.getLocation().add(0, 0.05, 0), 1, Color.RED); + sp.play(p.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 15, 0.01f); + } + } + + private boolean isCriticalHealthDamage(Player p, EntityDamageEvent e) { + double threshold = Math.max(0D, getConfig().triggerHealthThreshold); + double absorption = Math.max(0D, p.getAbsorptionAmount()); + double rawDamage = Math.max(0D, e.getDamage()); + double finalDamage = Math.max(0D, e.getFinalDamage()); + double healthAfterRaw = p.getHealth() - Math.max(0D, rawDamage - absorption); + double healthAfterFinal = p.getHealth() - Math.max(0D, finalDamage - absorption); + double predictedHealth = Math.min(healthAfterRaw, healthAfterFinal); + return predictedHealth <= 0D || predictedHealth <= threshold || p.getHealth() <= threshold; + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Consume experience to mitigate damage when a hit would drop you below 5 hearts.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effectiveness Base for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double effectivenessBase = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Effectiveness for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxEffectiveness = 0.95; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Level Drain for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int levelDrain = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Level Cost Add for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int levelCostAdd = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Amplifier for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double amplifier = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Trigger Health Threshold for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double triggerHealthThreshold = 10.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingAnvilSavant.java b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingAnvilSavant.java new file mode 100644 index 000000000..aeb8aa73e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingAnvilSavant.java @@ -0,0 +1,167 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.enchanting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.inventory.AnvilInventory; + +public class EnchantingAnvilSavant extends SimpleAdaptation { + public EnchantingAnvilSavant() { + super("enchanting-anvil-savant"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("enchanting.anvil_savant.description")); + setDisplayName(Localizer.dLocalize("enchanting.anvil_savant.name")); + setIcon(Material.ANVIL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2200); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ANVIL) + .key("challenge_enchanting_anvil_200") + .title(Localizer.dLocalize("advancement.challenge_enchanting_anvil_200.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_anvil_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ANVIL) + .key("challenge_enchanting_anvil_5k") + .title(Localizer.dLocalize("advancement.challenge_enchanting_anvil_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_anvil_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchanting_anvil_200", "enchanting.anvil-savant.levels-saved", 200, 400); + registerMilestone("challenge_enchanting_anvil_5k", "enchanting.anvil-savant.levels-saved", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getCostReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("enchanting.anvil_savant.lore1")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PrepareAnvilEvent e) { + if (!(e.getView().getPlayer() instanceof Player p)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (!(e.getInventory() instanceof AnvilInventory inventory)) { + return; + } + + Integer current = readRepairCost(inventory); + if (current == null || current <= 0) { + return; + } + + int reduced = Math.max(getConfig().minimumCost, (int) Math.ceil(current * (1D - getCostReduction(level)))); + writeRepairCost(inventory, reduced); + int saved = current - reduced; + if (saved > 0) { + getPlayer(p).getData().addStat("enchanting.anvil-savant.levels-saved", saved); + } + } + + private Integer readRepairCost(AnvilInventory inventory) { + try { + Object value = inventory.getClass().getMethod("getRepairCost").invoke(inventory); + if (value instanceof Number number) { + return number.intValue(); + } + } catch (Throwable ignored) { + + } + + return null; + } + + private void writeRepairCost(AnvilInventory inventory, int cost) { + try { + inventory.getClass().getMethod("setRepairCost", int.class).invoke(inventory, cost); + } catch (Throwable ignored) { + + } + } + + private double getCostReduction(int level) { + return Math.min(getConfig().maximumReduction, getConfig().reductionBase + (getLevelPercent(level) * getConfig().reductionFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Reduce anvil XP cost when combining, repairing, and renaming.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reduction Base for the Enchanting Anvil Savant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reductionBase = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reduction Factor for the Enchanting Anvil Savant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reductionFactor = 0.37; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Maximum Reduction for the Enchanting Anvil Savant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maximumReduction = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Cost for the Enchanting Anvil Savant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int minimumCost = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingBookshelfAttunement.java b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingBookshelfAttunement.java new file mode 100644 index 000000000..e2b71a064 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingBookshelfAttunement.java @@ -0,0 +1,134 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.enchanting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.enchantments.EnchantmentOffer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.enchantment.PrepareItemEnchantEvent; + +public class EnchantingBookshelfAttunement extends SimpleAdaptation { + public EnchantingBookshelfAttunement() { + super("enchanting-bookshelf-attunement"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("enchanting.bookshelf_attunement.description")); + setDisplayName(Localizer.dLocalize("enchanting.bookshelf_attunement.name")); + setIcon(Material.BOOKSHELF); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BOOKSHELF) + .key("challenge_enchanting_bookshelf_100") + .title(Localizer.dLocalize("advancement.challenge_enchanting_bookshelf_100.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_bookshelf_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_enchanting_bookshelf_100", "enchanting.bookshelf-attunement.enchants-boosted", 100, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getVirtualPower(level) + C.GRAY + " " + Localizer.dLocalize("enchanting.bookshelf_attunement.lore1")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PrepareItemEnchantEvent e) { + Player p = e.getEnchanter(); + if (!hasActiveAdaptation(p)) { + return; + } + + int power = getVirtualPower(getLevel(p)); + EnchantmentOffer[] offers = e.getOffers(); + if (offers == null) { + return; + } + + boolean boosted = false; + for (EnchantmentOffer offer : offers) { + if (offer == null) { + continue; + } + + int newCost = Math.min(30, offer.getCost() + power); + int newLevel = Math.min(offer.getEnchantment().getMaxLevel(), offer.getEnchantmentLevel() + Math.max(0, power / 3)); + offer.setCost(newCost); + offer.setEnchantmentLevel(Math.max(1, newLevel)); + boosted = true; + } + if (boosted) { + getPlayer(p).getData().addStat("enchanting.bookshelf-attunement.enchants-boosted", 1); + } + } + + private int getVirtualPower(int level) { + return Math.max(1, (int) Math.round(getConfig().powerBase + (getLevelPercent(level) * getConfig().powerFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain virtual bookshelf power to improve enchanting table offer quality.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Power Base for the Enchanting Bookshelf Attunement adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double powerBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Power Factor for the Enchanting Bookshelf Attunement adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double powerFactor = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingGrindstoneRecovery.java b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingGrindstoneRecovery.java new file mode 100644 index 000000000..6448579e1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingGrindstoneRecovery.java @@ -0,0 +1,225 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.enchanting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public class EnchantingGrindstoneRecovery extends SimpleAdaptation { + public EnchantingGrindstoneRecovery() { + super("enchanting-grindstone-recovery"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("enchanting.grindstone_recovery.description")); + setDisplayName(Localizer.dLocalize("enchanting.grindstone_recovery.name")); + setIcon(Material.GRINDSTONE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1700); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GRINDSTONE) + .key("challenge_enchanting_grindstone_50") + .title(Localizer.dLocalize("advancement.challenge_enchanting_grindstone_50.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_grindstone_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GRINDSTONE) + .key("challenge_enchanting_grindstone_500") + .title(Localizer.dLocalize("advancement.challenge_enchanting_grindstone_500.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_grindstone_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchanting_grindstone_50", "enchanting.grindstone-recovery.enchants-recovered", 50, 300); + registerMilestone("challenge_enchanting_grindstone_500", "enchanting.grindstone-recovery.enchants-recovered", 500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getRecoverChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("enchanting.grindstone_recovery.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getBonusXp(level), 1) + C.GRAY + " " + Localizer.dLocalize("enchanting.grindstone_recovery.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("enchanting.grindstone_recovery.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(InventoryClickEvent e) { + if (!(e.getWhoClicked() instanceof Player p)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (e.getView().getTopInventory().getType() != InventoryType.GRINDSTONE || e.getRawSlot() != 2 || p.hasCooldown(Material.GRINDSTONE)) { + return; + } + + ItemStack source = getEnchantedSource(e.getView().getTopInventory().getItem(0), e.getView().getTopInventory().getItem(1)); + if (source == null) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getRecoverChance(level)) { + return; + } + + ItemStack recovered = makeBook(source.getEnchantments()); + if (recovered == null) { + return; + } + + Map overflow = p.getInventory().addItem(recovered); + overflow.values().forEach(item -> p.getWorld().dropItemNaturally(p.getLocation(), item)); + int xp = Math.max(0, (int) Math.round(getBonusXp(level))); + if (xp > 0) { + p.giveExp(xp); + } + + p.setCooldown(Material.GRINDSTONE, getCooldownTicks(level)); + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.BLOCK_GRINDSTONE_USE, 0.95f, 1.15f); + sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8f, 1.45f); + xp(p, getConfig().skillXpOnRecovery); + getPlayer(p).getData().addStat("enchanting.grindstone-recovery.enchants-recovered", 1); + } + + private ItemStack getEnchantedSource(ItemStack a, ItemStack b) { + if (isEnchanted(a)) { + return a; + } + + if (isEnchanted(b)) { + return b; + } + + return null; + } + + private boolean isEnchanted(ItemStack item) { + return isItem(item) && !item.getEnchantments().isEmpty(); + } + + private ItemStack makeBook(Map source) { + if (source.isEmpty()) { + return null; + } + + List> entries = new ArrayList<>(source.entrySet()); + Map.Entry picked = entries.get(ThreadLocalRandom.current().nextInt(entries.size())); + ItemStack book = new ItemStack(Material.ENCHANTED_BOOK); + EnchantmentStorageMeta meta = (EnchantmentStorageMeta) book.getItemMeta(); + if (meta == null) { + return null; + } + + int safeLevel = Math.max(1, Math.min(picked.getValue(), picked.getKey().getMaxLevel())); + meta.addStoredEnchant(picked.getKey(), safeLevel, true); + book.setItemMeta(meta); + return book; + } + + private double getRecoverChance(int level) { + return Math.min(getConfig().maxRecoverChance, getConfig().recoverChanceBase + (getLevelPercent(level) * getConfig().recoverChanceFactor)); + } + + private double getBonusXp(int level) { + return getConfig().bonusXpBase + (getLevelPercent(level) * getConfig().bonusXpFactor); + } + + private int getCooldownTicks(int level) { + return Math.max(10, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Using a grindstone can recover one removed enchant on a book with bonus XP.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.74; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Recover Chance Base for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double recoverChanceBase = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Recover Chance Factor for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double recoverChanceFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Recover Chance for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxRecoverChance = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Xp Base for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusXpBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Xp Factor for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusXpFactor = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 120; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 70; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Skill Xp On Recovery for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double skillXpOnRecovery = 13; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingLapisReturn.java b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingLapisReturn.java new file mode 100644 index 000000000..bfe47ecbc --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingLapisReturn.java @@ -0,0 +1,142 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.enchanting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class EnchantingLapisReturn extends SimpleAdaptation { + private final Map cooldown = new java.util.concurrent.ConcurrentHashMap<>(); + + public EnchantingLapisReturn() { + super("enchanting-lapis-return"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("enchanting.lapis_return.description")); + setDisplayName(Localizer.dLocalize("enchanting.lapis_return.name")); + setIcon(Material.LAPIS_LAZULI); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(20999); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LAPIS_LAZULI) + .key("challenge_enchanting_lapis_100") + .title(Localizer.dLocalize("advancement.challenge_enchanting_lapis_100.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_lapis_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.LAPIS_BLOCK) + .key("challenge_enchanting_lapis_2500") + .title(Localizer.dLocalize("advancement.challenge_enchanting_lapis_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_lapis_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchanting_lapis_100", "enchanting.lapis-return.lapis-saved", 100, 300); + registerMilestone("challenge_enchanting_lapis_2500", "enchanting.lapis-return.lapis-saved", 2500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("enchanting.lapis_return.lore1")); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + cooldown.remove(e.getPlayer().getUniqueId()); + } + + + @EventHandler(priority = EventPriority.HIGH) + public void on(EnchantItemEvent e) { + + Player p = e.getEnchanter(); + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + + if (ThreadLocalRandom.current().nextDouble(100D) > 80D) { + long now = System.currentTimeMillis(); + UUID playerId = p.getUniqueId(); + Long nextAllowedAt = cooldown.get(playerId); + if (nextAllowedAt != null && nextAllowedAt > now) { + return; + } + + cooldown.put(playerId, now + 20000L); + p.getWorld().dropItemNaturally(p.getLocation(), new ItemStack(Material.LAPIS_LAZULI, level)); + getPlayer(p).getData().addStat("enchanting.lapis-return.lapis-saved", level); + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Chance to return free lapis when enchanting at the cost of 1 extra level.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.9; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingOfferReroll.java b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingOfferReroll.java new file mode 100644 index 000000000..83c2e6d6a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingOfferReroll.java @@ -0,0 +1,221 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.enchanting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.concurrent.ThreadLocalRandom; + +public class EnchantingOfferReroll extends SimpleAdaptation { + public EnchantingOfferReroll() { + super("enchanting-offer-reroll"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("enchanting.offer_reroll.description")); + setDisplayName(Localizer.dLocalize("enchanting.offer_reroll.name")); + setIcon(Material.ENCHANTING_TABLE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1800); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENCHANTING_TABLE) + .key("challenge_enchanting_reroll_100") + .title(Localizer.dLocalize("advancement.challenge_enchanting_reroll_100.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_reroll_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENCHANTING_TABLE) + .key("challenge_enchanting_reroll_1k") + .title(Localizer.dLocalize("advancement.challenge_enchanting_reroll_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_reroll_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchanting_reroll_100", "enchanting.offer-reroll.rerolls", 100, 300); + registerMilestone("challenge_enchanting_reroll_1k", "enchanting.offer-reroll.rerolls", 1000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("enchanting.offer_reroll.lore1")); + v.addLore(C.YELLOW + "* " + getLapisCost(level) + C.GRAY + " " + Localizer.dLocalize("enchanting.offer_reroll.lore2")); + if (getConfig().xpLevelCost > 0) { + v.addLore(C.YELLOW + "* " + getConfig().xpLevelCost + C.GRAY + " " + Localizer.dLocalize("enchanting.offer_reroll.lore_cost_xp")); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0 || p.hasCooldown(Material.ENCHANTING_TABLE)) { + return; + } + + Block table = action == Action.RIGHT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (table == null || table.getType() != Material.ENCHANTING_TABLE) { + return; + } + + if (p.getLevel() < getConfig().xpLevelCost) { + return; + } + + int lapisCost = getLapisCost(level); + if (!hasLapis(p, lapisCost)) { + return; + } + + if (!setSeed(p, ThreadLocalRandom.current().nextInt())) { + return; + } + + consumeLapis(p, lapisCost); + p.setLevel(Math.max(0, p.getLevel() - getConfig().xpLevelCost)); + p.setCooldown(Material.ENCHANTING_TABLE, getCooldownTicks(level)); + e.setCancelled(true); + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.2f); + sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.5f, 0.85f); + xp(p, getConfig().xpGainOnReroll); + getPlayer(p).getData().addStat("enchanting.offer-reroll.rerolls", 1); + } + + private boolean hasLapis(Player p, int amount) { + int found = 0; + for (ItemStack stack : p.getInventory().getContents()) { + if (stack == null || stack.getType() != Material.LAPIS_LAZULI) { + continue; + } + + found += stack.getAmount(); + if (found >= amount) { + return true; + } + } + return false; + } + + private void consumeLapis(Player p, int amount) { + int need = amount; + for (ItemStack stack : p.getInventory().getContents()) { + if (stack == null || stack.getType() != Material.LAPIS_LAZULI || need <= 0) { + continue; + } + + int used = Math.min(stack.getAmount(), need); + stack.setAmount(stack.getAmount() - used); + need -= used; + } + } + + private boolean setSeed(Player p, int seed) { + try { + p.getClass().getMethod("setEnchantmentSeed", int.class).invoke(p, seed); + return true; + } catch (Throwable ignored) { + return false; + } + } + + private int getLapisCost(int level) { + return Math.max(1, (int) Math.round(getConfig().lapisCostBase - (getLevelPercent(level) * getConfig().lapisCostFactor))); + } + + private int getCooldownTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click an enchanting table to reroll offers for lapis and XP.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Lapis Cost Base for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lapisCostBase = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Lapis Cost Factor for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lapisCostFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 320; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 220; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Level Cost for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int xpLevelCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Gain On Reroll for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpGainOnReroll = 15; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingQuickEnchant.java b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingQuickEnchant.java new file mode 100644 index 000000000..2eb5caf8a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingQuickEnchant.java @@ -0,0 +1,219 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.enchanting; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; + +public class EnchantingQuickEnchant extends SimpleAdaptation { + public EnchantingQuickEnchant() { + super("enchanting-quick-enchant"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("enchanting.quick_enchant.description")); + setDisplayName(Localizer.dLocalize("enchanting.quick_enchant.name")); + setIcon(Material.WRITABLE_BOOK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(15100); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENCHANTED_BOOK) + .key("challenge_enchanting_quick_100") + .title(Localizer.dLocalize("advancement.challenge_enchanting_quick_100.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_quick_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BOOKSHELF) + .key("challenge_enchanting_quick_1k") + .title(Localizer.dLocalize("advancement.challenge_enchanting_quick_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_quick_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchanting_quick_100", "enchanting.quick-enchant.books-applied", 100, 300); + registerMilestone("challenge_enchanting_quick_1k", "enchanting.quick-enchant.books-applied", 1000, 1000); + } + + private int getTotalLevelCount(int level) { + return level + (level > getConfig().maxPowerBonusLimit ? level / getConfig().maxPowerBonus1PerLevels : 0); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getTotalLevelCount(level) + C.GRAY + " " + Localizer.dLocalize("enchanting.quick_enchant.lore1")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(InventoryClickEvent e) { + if (e.getClickedInventory() == null) { + return; + } + + if (!(e.getWhoClicked() instanceof Player p)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (e.getAction().equals(InventoryAction.SWAP_WITH_CURSOR) + && e.getClick().equals(ClickType.LEFT) + && (e.getSlotType().equals(InventoryType.SlotType.CONTAINER) + || e.getSlotType().equals(InventoryType.SlotType.ARMOR) + || e.getSlotType().equals(InventoryType.SlotType.QUICKBAR)) + && e.getCursor() != null + && e.getCurrentItem() != null + && e.getCursor().getType().equals(Material.ENCHANTED_BOOK) + && !e.getCurrentItem().getType().equals(Material.BOOK) + && !e.getCurrentItem().getType().equals(Material.ENCHANTED_BOOK) + && e.getCursor().getItemMeta() != null + && e.getCursor().getItemMeta() instanceof EnchantmentStorageMeta eb + && e.getCurrentItem().getItemMeta() != null + && e.getCurrentItem().getAmount() == 1 + && e.getCursor().getAmount() == 1) { + ItemStack item = e.getCurrentItem(); + ItemStack book = e.getCursor(); + KMap itemEnchants = new KMap<>(item.getType().equals(Material.ENCHANTED_BOOK) + ? ((EnchantmentStorageMeta) item.getItemMeta()).getStoredEnchants() + : item.getEnchantments()); + KMap bookEnchants = new KMap<>(eb.getStoredEnchants()); + KMap newEnchants = itemEnchants.copy(); + KMap addEnchants = new KMap<>(); + int power = itemEnchants.values().stream().mapToInt(i -> i).sum(); + + if (bookEnchants.isEmpty()) { + return; + } + + for (Enchantment i : bookEnchants.k()) { + if (itemEnchants.containsKey(i)) { + continue; + } + + power += bookEnchants.get(i); + newEnchants.put(i, bookEnchants.get(i)); + addEnchants.put(i, bookEnchants.get(i)); + bookEnchants.remove(i); + } + + SoundPlayer sp = SoundPlayer.of(p); + if (power > getTotalLevelCount(level)) { + Adapt.actionbar(p, C.RED + Localizer.dLocalize("enchanting.quick_enchant.lore2") + getTotalLevelCount(level) + " " + Localizer.dLocalize("enchanting.quick_enchant.lore3")); + sp.play(p.getLocation(), Sound.BLOCK_CONDUIT_DEACTIVATE, 0.5f, 1.7f); + return; + } + + if (!itemEnchants.equals(newEnchants)) { + ItemMeta im = item.getItemMeta(); + + if (im instanceof EnchantmentStorageMeta sm) { + sm.getStoredEnchants().keySet().forEach(sm::removeStoredEnchant); + newEnchants.forEach((ec, l) -> sm.addStoredEnchant(ec, l, true)); + Adapt.messagePlayer(p, "---"); + sm.getStoredEnchants().forEach((k, v) -> Adapt.messagePlayer(p, k.getKey().getKey() + " " + v)); + } else { + im.getEnchants().keySet().forEach(im::removeEnchant); + newEnchants.forEach((ec, l) -> im.addEnchant(ec, l, true)); + } + + xp(p, 50); + item.setItemMeta(im); + e.setCurrentItem(item); + e.setCancelled(true); + getPlayer(p).getData().addStat("enchanting.quick-enchant.books-applied", 1); + sp.play(p.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.7f); + sp.play(p.getLocation(), Sound.BLOCK_DEEPSLATE_TILES_BREAK, 0.5f, 0.7f); + xp(p, 320 * addEnchants.values().stream().mapToInt((i) -> i).sum(), "quick-apply"); + + if (bookEnchants.isEmpty()) { + e.setCursor(null); + } else if (!eb.getStoredEnchants().equals(bookEnchants)) { + eb.getStoredEnchants().keySet().forEach(eb::removeStoredEnchant); + bookEnchants.forEach((ec, l) -> eb.addStoredEnchant(ec, l, true)); + book.setItemMeta(eb); + e.setCursor(book); + } + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Enchant items by clicking enchant books directly on them.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Power Bonus Limit for the Enchanting Quick Enchant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxPowerBonusLimit = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Power Bonus1Per Levels for the Enchanting Quick Enchant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxPowerBonus1PerLevels = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingXPReturn.java b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingXPReturn.java new file mode 100644 index 000000000..992a762a1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/enchanting/EnchantingXPReturn.java @@ -0,0 +1,130 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.enchanting; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; + +public class EnchantingXPReturn extends SimpleAdaptation { + private final Map cooldown = new java.util.concurrent.ConcurrentHashMap<>(); + + public EnchantingXPReturn() { + super("enchanting-xp-return"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("enchanting.return.description")); + setDisplayName(Localizer.dLocalize("enchanting.return.name")); + setIcon(Material.EXPERIENCE_BOTTLE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(13001); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EXPERIENCE_BOTTLE) + .key("challenge_enchanting_xp_100") + .title(Localizer.dLocalize("advancement.challenge_enchanting_xp_100.title")) + .description(Localizer.dLocalize("advancement.challenge_enchanting_xp_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_enchanting_xp_100", "enchanting.xp-return.levels-saved", 100, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("enchanting.return.lore1")); + v.addLore(C.GREEN + "" + getConfig().xpReturn * (level * level) + Localizer.dLocalize("enchanting.return.lore2")); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + Player p = e.getPlayer(); + cooldown.remove(p.getUniqueId()); + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EnchantItemEvent e) { + Player p = e.getEnchanter(); + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (cooldown.containsKey(p.getUniqueId()) && cooldown.get(p.getUniqueId()) + 20000 < System.currentTimeMillis()) { + cooldown.remove(p.getUniqueId()); + } else if (cooldown.containsKey(p.getUniqueId()) && cooldown.get(p.getUniqueId()) + 20000 > System.currentTimeMillis()) { + return; + } + cooldown.put(p.getUniqueId(), System.currentTimeMillis()); + int xpAmount = getConfig().xpReturn * (level * level); + p.getWorld().spawn(p.getLocation(), org.bukkit.entity.ExperienceOrb.class).setExperience(xpAmount); + getPlayer(p).getData().addStat("enchanting.xp-return.levels-saved", xpAmount); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Enchanting XP is partially refunded when you enchant an item.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Return for the Enchanting XPReturn adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public int xpReturn = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.9; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationBurrow.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationBurrow.java new file mode 100644 index 000000000..87ad3f836 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationBurrow.java @@ -0,0 +1,316 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ExcavationBurrow extends SimpleAdaptation { + private final Map cooldowns = new ConcurrentHashMap<>(); + + public ExcavationBurrow() { + super("excavation-burrow"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.burrow.description")); + setDisplayName(Localizer.dLocalize("excavation.burrow.name")); + setIcon(Material.COARSE_DIRT); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4130); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COARSE_DIRT) + .key("challenge_excavation_burrow_100") + .title(Localizer.dLocalize("advancement.challenge_excavation_burrow_100.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_burrow_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_burrow_100", "excavation.burrow.burrows-dug", 100, 450); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getMaxDepth(level) + C.GRAY + " " + Localizer.dLocalize("excavation.burrow.lore1")); + v.addLore(C.GREEN + "+ " + getConfig().durabilityCostPerBlock + C.GRAY + " " + Localizer.dLocalize("excavation.burrow.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.burrow.lore3")); + v.addLore(C.RED + "- " + getConfig().hungerCost + C.GRAY + " " + Localizer.dLocalize("excavation.burrow.lore4")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isShovel(hand)) { + return; + } + + Block clicked = e.getClickedBlock(); + if (action == Action.RIGHT_CLICK_AIR) { + clicked = p.getTargetBlockExact(5); + if (clicked == null) { + clicked = p.getLocation().getBlock().getRelative(BlockFace.DOWN); + } + } + + if (clicked == null || !isShovelable(clicked.getType())) { + return; + } + + long now = System.currentTimeMillis(); + long nextReady = cooldowns.getOrDefault(p.getUniqueId(), 0L); + if (now < nextReady) { + return; + } + + int hungerCost = getConfig().hungerCost; + if (p.getFoodLevel() < hungerCost) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveBlockBreakContext(p, clicked.getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + + List plan = planDig(p, clicked, getMaxDepth(level)); + if (plan.isEmpty()) { + return; + } + + if (!applyDurability(p, hand, plan.size() * getConfig().durabilityCostPerBlock)) { + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_ANVIL_PLACE, 0.5f, 0.65f); + return; + } + + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + e.setCancelled(true); + scheduleDig(plan, hand.clone()); + getPlayer(p).getData().addStat("excavation.burrow.burrows-dug", 1); + getPlayer(p).getData().addStat("excavation.burrow.blocks-burrowed", plan.size()); + xp(p, plan.size() * getConfig().xpPerBlock); + } + + private List planDig(Player p, Block start, int maxDepth) { + World world = start.getWorld(); + int stopY = world.getMinHeight() + getConfig().safeFloorMargin; + List plan = new ArrayList<>(maxDepth); + Block current = start; + for (int i = 0; i < maxDepth; i++) { + if (current.getY() <= stopY) { + break; + } + + if (!isShovelable(current.getType())) { + break; + } + + if (!canBlockBreak(p, current.getLocation())) { + break; + } + + Block below = current.getRelative(BlockFace.DOWN); + Material belowType = below.getType(); + if (belowType == Material.LAVA) { + break; + } + + if (belowType.isAir() && below.getRelative(BlockFace.DOWN).getType().isAir()) { + break; + } + + plan.add(current); + current = below; + } + + return plan; + } + + private void scheduleDig(List plan, ItemStack tool) { + int interval = Math.max(1, getConfig().ticksPerBlock); + for (int i = 0; i < plan.size(); i++) { + Block block = plan.get(i); + int delay = i * interval; + if (delay <= 0) { + digBlock(block, tool); + continue; + } + + J.runAt(block.getLocation(), () -> digBlock(block, tool), delay); + } + } + + private void digBlock(Block block, ItemStack tool) { + if (!isShovelable(block.getType())) { + return; + } + + if (block.getRelative(BlockFace.DOWN).getType() == Material.LAVA) { + return; + } + + block.breakNaturally(tool); + if (areParticlesEnabled()) { + block.getWorld().spawnParticle(Particle.CLOUD, block.getLocation().add(0.5, 0.5, 0.5), 3, 0.2, 0.2, 0.2, 0.01); + } + + SoundPlayer.of(block.getWorld()).play(block.getLocation(), Sound.ITEM_SHOVEL_FLATTEN, 0.45f, 1.3f); + } + + private boolean applyDurability(Player p, ItemStack hand, int cost) { + if (cost <= 0) { + return true; + } + + if (!(hand.getItemMeta() instanceof Damageable damageable)) { + return false; + } + + int maxDurability = hand.getType().getMaxDurability(); + int currentDamage = damageable.getDamage(); + if (currentDamage + cost >= maxDurability) { + return false; + } + + damageable.setDamage(currentDamage + cost); + hand.setItemMeta(damageable); + p.getInventory().setItemInMainHand(hand); + return true; + } + + private boolean isShovelable(Material type) { + return switch (type) { + case CLAY, DIRT, COARSE_DIRT, ROOTED_DIRT, FARMLAND, GRASS_BLOCK, + DIRT_PATH, GRAVEL, MYCELIUM, PODZOL, SAND, RED_SAND, SOUL_SAND, + SOUL_SOIL, SNOW, SNOW_BLOCK, MUD, MUDDY_MANGROVE_ROOTS -> true; + default -> false; + }; + } + + private int getMaxDepth(int level) { + return Math.max(2, (int) Math.round(getConfig().depthBase + (getLevelPercent(level) * getConfig().depthFactor))); + } + + private long getCooldownMillis(int level) { + return Math.max(2000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click soft ground with a shovel to rapidly dig straight down, stopping before hazards.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.78; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Depth Base for the Excavation Burrow adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double depthBase = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Depth Factor for the Excavation Burrow adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double depthFactor = 13; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ticks Per Block for the Excavation Burrow adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int ticksPerBlock = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Safe Floor Margin for the Excavation Burrow adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int safeFloorMargin = 16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Per Block for the Excavation Burrow adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int durabilityCostPerBlock = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hunger points consumed per burrow activation.", impact = "Higher values drain more food per dig; activation fails when food is below the cost. Set to 0 to disable the hunger cost.") + int hungerCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Excavation Burrow adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 14000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Excavation Burrow adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 7000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Block for the Excavation Burrow adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBlock = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationDowsing.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationDowsing.java new file mode 100644 index 000000000..ca2656355 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationDowsing.java @@ -0,0 +1,285 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ExcavationDowsing extends SimpleAdaptation { + private static final int MAX_SCAN_RANGE = 24; + private static final int[][] SCAN_OFFSETS = buildScanOffsets(); + private final Map cooldowns = new ConcurrentHashMap<>(); + + public ExcavationDowsing() { + super("excavation-dowsing"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.dowsing.description")); + setDisplayName(Localizer.dLocalize("excavation.dowsing.name")); + setIcon(Material.COMPASS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(3970); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COMPASS) + .key("challenge_excavation_dowsing_200") + .title(Localizer.dLocalize("advancement.challenge_excavation_dowsing_200.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_dowsing_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_dowsing_200", "excavation.dowsing.pockets-found", 200, 400); + } + + private static int[][] buildScanOffsets() { + List offsets = new ArrayList<>(); + for (int x = -MAX_SCAN_RANGE; x <= MAX_SCAN_RANGE; x += 2) { + for (int y = -MAX_SCAN_RANGE; y <= MAX_SCAN_RANGE; y += 2) { + for (int z = -MAX_SCAN_RANGE; z <= MAX_SCAN_RANGE; z += 2) { + int d2 = (x * x) + (y * y) + (z * z); + if (d2 < 25 || d2 > MAX_SCAN_RANGE * MAX_SCAN_RANGE) { + continue; + } + + offsets.add(new int[]{x, y, z, d2}); + } + } + } + + offsets.sort(Comparator.comparingInt(o -> o[3])); + return offsets.toArray(new int[0][]); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getScanRange(level) + C.GRAY + " " + Localizer.dLocalize("excavation.dowsing.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.dowsing.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerToggleSneakEvent e) { + if (!e.isSneaking()) { + return; + } + + Player p = e.getPlayer(); + if (!isShovel(p.getInventory().getItemInMainHand())) { + return; + } + + long now = System.currentTimeMillis(); + long nextReady = cooldowns.getOrDefault(p.getUniqueId(), 0L); + if (now < nextReady) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveInteractContext(p, p.getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + + Block pocket = findNearestPocket(p.getLocation(), getScanRange(level), getConfig().maxSamples); + if (pocket == null) { + return; + } + + Location origin = p.getEyeLocation(); + Location target = pocket.getLocation().add(0.5, 0.5, 0.5); + Vector direction = target.toVector().subtract(origin.toVector()); + if (direction.lengthSquared() <= 0.0000001) { + return; + } + + Material type = pocket.getType(); + renderTrail(p, origin, direction.normalize(), type); + playTone(p, type, origin.distance(target), getScanRange(level)); + getPlayer(p).getData().addStat("excavation.dowsing.pockets-found", 1); + xp(p, getConfig().xpPerPing); + } + + private Block findNearestPocket(Location origin, int range, int maxSamples) { + World world = origin.getWorld(); + if (world == null) { + return null; + } + + int ox = origin.getBlockX(); + int oy = origin.getBlockY(); + int oz = origin.getBlockZ(); + int minY = world.getMinHeight(); + int maxY = world.getMaxHeight() - 1; + int rangeSq = range * range; + int samples = 0; + + for (int[] offset : SCAN_OFFSETS) { + if (offset[3] > rangeSq || samples >= maxSamples) { + return null; + } + + samples++; + int by = oy + offset[1]; + if (by < minY || by > maxY) { + continue; + } + + int bx = ox + offset[0]; + int bz = oz + offset[2]; + if (!world.isChunkLoaded(bx >> 4, bz >> 4)) { + continue; + } + + Block block = world.getBlockAt(bx, by, bz); + Material type = block.getType(); + if ((type == Material.WATER || type == Material.LAVA || type.isAir()) && block.getLightFromSky() == 0) { + return block; + } + } + + return null; + } + + private void renderTrail(Player p, Location origin, Vector direction, Material type) { + if (!areParticlesEnabled()) { + return; + } + + Color color = switch (type) { + case WATER -> Color.fromRGB(70, 140, 255); + case LAVA -> Color.fromRGB(255, 120, 30); + default -> Color.fromRGB(205, 205, 215); + }; + Particle.DustOptions dust = new Particle.DustOptions(color, (float) getConfig().particleSize); + Location at = origin.clone(); + for (int i = 0; i < getConfig().trailSegments; i++) { + at = at.add(direction.clone().multiply(getConfig().segmentSpacing)); + p.spawnParticle(Particle.DUST, at, 2, 0.04, 0.04, 0.04, 0, dust); + } + + p.spawnParticle(Particle.END_ROD, at, 6, 0.1, 0.1, 0.1, 0.02); + } + + private void playTone(Player p, Material type, double distance, int range) { + double normalized = Math.min(1.0, distance / Math.max(1.0, range)); + float pitch = (float) Math.max(0.5, Math.min(2.0, 1.95 - (normalized * 1.2))); + Sound tone = switch (type) { + case WATER -> Sound.BLOCK_NOTE_BLOCK_CHIME; + case LAVA -> Sound.BLOCK_NOTE_BLOCK_BASS; + default -> Sound.BLOCK_NOTE_BLOCK_BIT; + }; + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), tone, 0.85f, pitch); + sp.play(p.getLocation(), Sound.ITEM_SHOVEL_FLATTEN, 0.3f, 1.6f); + } + + private int getScanRange(int level) { + return Math.min(MAX_SCAN_RANGE, Math.max(8, (int) Math.round(getConfig().scanRangeBase + (getLevelPercent(level) * getConfig().scanRangeFactor)))); + } + + private long getCooldownMillis(int level) { + return Math.max(2000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneaking with a shovel pings the nearest hidden cave, water, or lava pocket with a directional trail.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Scan Range Base for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double scanRangeBase = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Scan Range Factor for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double scanRangeFactor = 16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Samples for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxSamples = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 9000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 4500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Trail Segments for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int trailSegments = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Segment Spacing for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double segmentSpacing = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Particle Size for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double particleSize = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Ping for the Excavation Dowsing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerPing = 6; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationDropToInventory.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationDropToInventory.java new file mode 100644 index 000000000..5f023e8ac --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationDropToInventory.java @@ -0,0 +1,123 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; + +import java.util.List; + +public class ExcavationDropToInventory extends SimpleAdaptation { + public ExcavationDropToInventory() { + super("excavation-drop-to-inventory"); + registerConfiguration(ExcavationDropToInventory.Config.class); + setDescription(Localizer.dLocalize("pickaxe.drop_to_inventory.description")); + setDisplayName(Localizer.dLocalize("excavation.drop_to_inventory.name")); + setIcon(Material.CHEST); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(11777); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_excavation_dti_10k") + .title(Localizer.dLocalize("advancement.challenge_excavation_dti_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_dti_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_dti_10k", "excavation.drop-to-inv.items-caught", 10000, 500); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("pickaxe.drop_to_inventory.lore1")); + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDropItemEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + if (resolveInteractBreakContext(p, e.getBlock().getLocation(), null, true) == null) { + return; + } + if (ItemListings.toolShovels.contains(p.getInventory().getItemInMainHand().getType())) { + List items = new KList<>(e.getItems()); + e.getItems().clear(); + for (Item i : items) { + sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); + xp(p, 2); + getPlayer(p).getData().addStat("excavation.drop-to-inv.items-caught", 1); + if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { + p.getWorld().dropItem(p.getLocation(), i.getItemStack()); + } + } + } + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Excavated blocks drop directly into your inventory.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationEarthMover.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationEarthMover.java new file mode 100644 index 000000000..57f3a7f0e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationEarthMover.java @@ -0,0 +1,273 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.RegistryUtil; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ExcavationEarthMover extends SimpleAdaptation { + private static final PotionEffectType SLOWNESS = RegistryUtil.find(PotionEffectType.class, "slowness", "slow"); + private final Map cooldowns = new ConcurrentHashMap<>(); + + public ExcavationEarthMover() { + super("excavation-earth-mover"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.earth_mover.description")); + setDisplayName(Localizer.dLocalize("excavation.earth_mover.name")); + setIcon(Material.DIRT); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(3730); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIRT) + .key("challenge_excavation_earthmover_250") + .title(Localizer.dLocalize("advancement.challenge_excavation_earthmover_250.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_earthmover_250.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_earthmover_250", "excavation.earth-mover.waves-unleashed", 250, 450); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.earth_mover.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getForce(level), 0) + C.GRAY + " " + Localizer.dLocalize("excavation.earth_mover.lore2")); + v.addLore(C.GREEN + "+ " + Form.duration(getSlowTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("excavation.earth_mover.lore3")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.earth_mover.lore4")); + v.addLore(C.RED + "- " + getConfig().hungerCost + C.GRAY + " " + Localizer.dLocalize("excavation.earth_mover.lore5")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { + return; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + if (!isShovel(p.getInventory().getItemInMainHand())) { + return; + } + + long now = System.currentTimeMillis(); + long nextReady = cooldowns.getOrDefault(p.getUniqueId(), 0L); + if (now < nextReady) { + return; + } + + int hungerCost = getConfig().hungerCost; + if (p.getFoodLevel() < hungerCost) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveInteractContext(p, p.getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + + double radius = getRadius(level); + double force = getForce(level); + int slowTicks = getSlowTicks(level); + int slowAmplifier = getSlowAmplifier(level); + int hit = 0; + for (Entity nearby : p.getNearbyEntities(radius, getConfig().verticalRange, radius)) { + if (!(nearby instanceof Monster monster)) { + continue; + } + + if (!canDamageTarget(p, monster)) { + continue; + } + + Vector direction = monster.getLocation().toVector().subtract(p.getLocation().toVector()); + direction.setY(0); + if (direction.lengthSquared() < 0.01) { + direction = new Vector(1, 0, 0); + } + + direction.normalize().multiply(force).setY(getConfig().liftVelocity); + monster.setVelocity(direction); + monster.addPotionEffect(new PotionEffect(SLOWNESS, slowTicks, slowAmplifier, false, true, true)); + hit++; + } + + renderWave(p, radius); + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.BLOCK_ROOTED_DIRT_BREAK, 1.0f, 0.6f); + sp.play(p.getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, 0.4f, 0.5f); + getPlayer(p).getData().addStat("excavation.earth-mover.waves-unleashed", 1); + if (hit > 0) { + getPlayer(p).getData().addStat("excavation.earth-mover.mobs-launched", hit); + xp(p, hit * getConfig().xpPerMobHit); + } + } + + private void renderWave(Player p, double radius) { + if (!areParticlesEnabled()) { + return; + } + + World world = p.getWorld(); + Location base = p.getLocation().add(0, 0.2, 0); + ItemStack dirt = new ItemStack(Material.DIRT); + for (int pulse = 0; pulse < 3; pulse++) { + double r = radius * ((pulse + 1) / 3.0); + int points = 10 + (pulse * 6); + J.runEntity(p, () -> { + for (int i = 0; i < points; i++) { + double angle = (Math.PI * 2 * i) / points; + Location at = base.clone().add(Math.cos(angle) * r, 0, Math.sin(angle) * r); + world.spawnParticle(Particle.ITEM, at, 3, 0.12, 0.1, 0.12, 0.04, dirt); + world.spawnParticle(Particle.CLOUD, at, 1, 0.05, 0.05, 0.05, 0.01); + } + }, pulse * 2); + } + } + + private double getRadius(int level) { + return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); + } + + private double getForce(int level) { + return getConfig().forceBase + (getLevelPercent(level) * getConfig().forceFactor); + } + + private int getSlowTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().slowTicksBase + (getLevelPercent(level) * getConfig().slowTicksFactor))); + } + + private int getSlowAmplifier(int level) { + return Math.max(0, (int) Math.round(getLevelPercent(level) * getConfig().slowAmplifierMax)); + } + + private long getCooldownMillis(int level) { + return Math.max(1000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click the air with a shovel to fling a wave of earth that knocks back and slows hostile mobs.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Vertical Range for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double verticalRange = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Force Base for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double forceBase = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Force Factor for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double forceFactor = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Lift Velocity for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double liftVelocity = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Slow Ticks Base for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double slowTicksBase = 40; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Slow Ticks Factor for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double slowTicksFactor = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Slow Amplifier Max for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double slowAmplifierMax = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 16000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 8000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hunger points consumed per earth wave.", impact = "Higher values drain more food per wave; activation fails when food is below the cost. Set to 0 to disable the hunger cost.") + int hungerCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mob Hit for the Excavation Earth Mover adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerMobHit = 6; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationGraveDigger.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationGraveDigger.java new file mode 100644 index 000000000..62761d45e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationGraveDigger.java @@ -0,0 +1,310 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.entity.Skeleton; +import org.bukkit.entity.Zombie; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class ExcavationGraveDigger extends SimpleAdaptation { + private static final NamespacedKey GRAVE_MOB_KEY = NamespacedKey.fromString("adapt:excavation_grave_mob"); + private final Map graveCooldowns = new ConcurrentHashMap<>(); + private volatile TableCache tableCache; + + public static boolean isGraveMob(Entity entity) { + return entity.getPersistentDataContainer().has(GRAVE_MOB_KEY, PersistentDataType.BYTE); + } + + public ExcavationGraveDigger() { + super("excavation-grave-digger"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.grave_digger.description")); + setDisplayName(Localizer.dLocalize("excavation.grave_digger.name")); + setIcon(Material.BONE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4310); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_excavation_gravedigger_300") + .title(Localizer.dLocalize("advancement.challenge_excavation_gravedigger_300.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_gravedigger_300.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_gravedigger_300", "excavation.grave-digger.bones-unearthed", 300, 450); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getLootChance(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.grave_digger.lore1")); + v.addLore(C.RED + "+ " + Form.pc(getGraveChance(level), 2) + C.GRAY + " " + Localizer.dLocalize("excavation.grave_digger.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + graveCooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (!isGraveSoil(e.getBlock().getType())) { + return; + } + + Player p = e.getPlayer(); + if (!isShovel(p.getInventory().getItemInMainHand())) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + Location center = e.getBlock().getLocation().add(0.5, 0.5, 0.5); + + if (random.nextDouble() < getLootChance(level)) { + dropLoot(p, center, random); + } + + if (random.nextDouble() < getGraveChance(level)) { + long now = System.currentTimeMillis(); + long nextReady = graveCooldowns.getOrDefault(p.getUniqueId(), 0L); + if (now >= nextReady) { + graveCooldowns.put(p.getUniqueId(), now + (long) getConfig().graveCooldownMillis); + disturbGrave(p, e.getBlock().getLocation(), random); + } + } + } + + private void dropLoot(Player p, Location center, ThreadLocalRandom random) { + TableCache table = getLootTable(); + if (table.entries().isEmpty()) { + return; + } + + LootEntry entry = pickWeighted(table, random.nextDouble() * table.totalWeight()); + int amount = entry.min() >= entry.max() ? entry.min() : random.nextInt(entry.min(), entry.max() + 1); + center.getWorld().dropItemNaturally(center, new ItemStack(entry.material(), amount)); + if (areParticlesEnabled()) { + p.spawnParticle(Particle.ASH, center, 8, 0.25, 0.25, 0.25, 0.01); + } + + SoundPlayer.of(p.getWorld()).play(center, Sound.BLOCK_ROOTED_DIRT_BREAK, 0.7f, 0.8f); + getPlayer(p).getData().addStat("excavation.grave-digger.bones-unearthed", 1); + xp(p, getConfig().xpPerLoot); + } + + private void disturbGrave(Player p, Location blockLocation, ThreadLocalRandom random) { + Location spawnAt = blockLocation.add(0.5, 0, 0.5); + Monster grave = random.nextBoolean() + ? spawnAt.getWorld().spawn(spawnAt, Zombie.class, z -> { + z.getPersistentDataContainer().set(GRAVE_MOB_KEY, PersistentDataType.BYTE, (byte) 1); + z.setTarget(p); + }) + : spawnAt.getWorld().spawn(spawnAt, Skeleton.class, s -> { + s.getPersistentDataContainer().set(GRAVE_MOB_KEY, PersistentDataType.BYTE, (byte) 1); + s.setTarget(p); + }); + + if (areParticlesEnabled()) { + p.spawnParticle(Particle.SOUL, grave.getLocation().add(0, 1, 0), 14, 0.3, 0.5, 0.3, 0.02); + } + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(spawnAt, Sound.ENTITY_SKELETON_HURT, 0.8f, 0.6f); + sp.play(spawnAt, Sound.BLOCK_ROOTED_DIRT_BREAK, 1.0f, 0.5f); + getPlayer(p).getData().addStat("excavation.grave-digger.graves-disturbed", 1); + xp(p, getConfig().xpPerGrave); + } + + private LootEntry pickWeighted(TableCache table, double roll) { + List entries = table.entries(); + double remaining = roll; + for (LootEntry entry : entries) { + remaining -= entry.weight(); + if (remaining <= 0) { + return entry; + } + } + + return entries.get(entries.size() - 1); + } + + private TableCache getLootTable() { + List source = getConfig().lootTable; + TableCache local = tableCache; + if (local != null && local.source() == source) { + return local; + } + + List parsed = new ArrayList<>(source.size()); + double totalWeight = 0; + for (String raw : source) { + String[] parts = raw.split(":"); + if (parts.length < 2) { + continue; + } + + Material material = Material.matchMaterial(parts[0].trim()); + if (material == null) { + continue; + } + + double weight = parseDouble(parts[1]); + if (weight <= 0) { + continue; + } + + int min = parts.length > 2 ? Math.max(1, (int) parseDouble(parts[2])) : 1; + int max = parts.length > 3 ? Math.max(min, (int) parseDouble(parts[3])) : min; + parsed.add(new LootEntry(material, weight, min, max)); + totalWeight += weight; + } + + TableCache built = new TableCache(source, parsed, totalWeight); + tableCache = built; + return built; + } + + private double parseDouble(String raw) { + try { + return Double.parseDouble(raw.trim()); + } catch (NumberFormatException ignored) { + return 0; + } + } + + private boolean isGraveSoil(Material type) { + return switch (type) { + case DIRT, GRASS_BLOCK, COARSE_DIRT, ROOTED_DIRT, PODZOL, MYCELIUM, + DIRT_PATH -> true; + default -> false; + }; + } + + private double getLootChance(int level) { + return Math.min(getConfig().maxLootChance, getConfig().lootChanceBase + (getLevelPercent(level) * getConfig().lootChanceFactor)); + } + + private double getGraveChance(int level) { + return Math.min(getConfig().maxGraveChance, getConfig().graveChanceBase + (getLevelPercent(level) * getConfig().graveChanceFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record LootEntry(Material material, double weight, int min, int max) { + } + + private record TableCache(List source, List entries, + double totalWeight) { + } + + @NoArgsConstructor + @ConfigDescription("Digging earthen ground can unearth bone loot, and rarely disturbs a hostile grave.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.68; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Loot Chance Base for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lootChanceBase = 0.008; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Loot Chance Factor for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lootChanceFactor = 0.035; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Loot Chance for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxLootChance = 0.045; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Grave Chance Base for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double graveChanceBase = 0.001; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Grave Chance Factor for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double graveChanceFactor = 0.004; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Grave Chance for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxGraveChance = 0.005; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Grave Cooldown Millis for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double graveCooldownMillis = 45000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Weighted loot table entries formatted as MATERIAL:weight:min:max.", impact = "Adding entries or raising weights changes which bone loot drops and how often.") + List lootTable = new ArrayList<>(List.of( + "BONE:40:1:2", + "BONE_MEAL:25:2:4", + "ROTTEN_FLESH:20:1:2", + "BONE_BLOCK:6:1:1", + "SKELETON_SKULL:2:1:1" + )); + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Loot for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerLoot = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Grave for the Excavation Grave Digger adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerGrave = 35; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationHaste.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationHaste.java new file mode 100644 index 000000000..1bf2597e8 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationHaste.java @@ -0,0 +1,119 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.potion.PotionEffect; + +public class ExcavationHaste extends SimpleAdaptation { + public ExcavationHaste() { + super("excavation-haste"); + registerConfiguration(ExcavationHaste.Config.class); + setDisplayName(Localizer.dLocalize("excavation.haste.name")); + setDescription(Localizer.dLocalize("excavation.haste.description")); + setIcon(Material.GOLDEN_PICKAXE); + setInterval(4388); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SHOVEL) + .key("challenge_excavation_haste_5k") + .title(Localizer.dLocalize("advancement.challenge_excavation_haste_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_haste_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SHOVEL) + .key("challenge_excavation_haste_50k") + .title(Localizer.dLocalize("advancement.challenge_excavation_haste_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_haste_50k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_excavation_haste_5k", "excavation.haste.blocks-while-hasted", 5000, 400); + registerMilestone("challenge_excavation_haste_50k", "excavation.haste.blocks-while-hasted", 50000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("excavation.haste.lore1")); + v.addLore(C.GREEN + "" + (level) + C.GRAY + Localizer.dLocalize("excavation.haste.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDamageEvent e) { + Player p = e.getPlayer(); + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveInteractContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + p.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, 15, context.level(), false, false, true)); + getPlayer(p).getData().addStat("excavation.haste.blocks-while-hasted", 1); + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain Haste while excavating blocks.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationMudlark.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationMudlark.java new file mode 100644 index 000000000..27a666c99 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationMudlark.java @@ -0,0 +1,208 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + +import java.util.concurrent.ThreadLocalRandom; + +public class ExcavationMudlark extends SimpleAdaptation { + public ExcavationMudlark() { + super("excavation-mudlark"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.mudlark.description")); + setDisplayName(Localizer.dLocalize("excavation.mudlark.name")); + setIcon(Material.MUD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4530); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.MUD) + .key("challenge_excavation_mudlark_1k") + .title(Localizer.dLocalize("advancement.challenge_excavation_mudlark_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_mudlark_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_mudlark_1k", "excavation.mudlark.bonus-drops", 1000, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getBonusChance(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.mudlark.lore1")); + v.addLore(C.GREEN + "+ " + (getHasteAmplifier(level) + 1) + C.GRAY + " " + Localizer.dLocalize("excavation.mudlark.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDamageEvent e) { + Player p = e.getPlayer(); + if (!isShovel(p.getInventory().getItemInMainHand())) { + return; + } + + if (!isWet(p)) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveInteractContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + p.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, getConfig().hasteDurationTicks, getHasteAmplifier(context.level()), false, false, true)); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + Material type = e.getBlock().getType(); + if (!isMudlarkBlock(type)) { + return; + } + + Player p = e.getPlayer(); + if (!isShovel(p.getInventory().getItemInMainHand())) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + if (ThreadLocalRandom.current().nextDouble() > getBonusChance(level)) { + return; + } + + Material bonus = getBonusDrop(type); + Location drop = e.getBlock().getLocation().add(0.5, 0.5, 0.5); + e.getBlock().getWorld().dropItemNaturally(drop, new ItemStack(bonus, 1)); + if (areParticlesEnabled()) { + p.spawnParticle(Particle.SPLASH, drop, 8, 0.25, 0.2, 0.25, 0.01); + } + + SoundPlayer.of(p.getWorld()).play(drop, Sound.BLOCK_ROOTED_DIRT_BREAK, 0.6f, 1.2f); + getPlayer(p).getData().addStat("excavation.mudlark.bonus-drops", 1); + xp(p, getConfig().xpPerBonusDrop); + } + + private boolean isWet(Player p) { + if (p.isInWater()) { + return true; + } + + if (!p.getWorld().hasStorm()) { + return false; + } + + return p.getLocation().getBlock().getLightFromSky() >= 14; + } + + private boolean isMudlarkBlock(Material type) { + return switch (type) { + case CLAY, MUD, MUDDY_MANGROVE_ROOTS, SOUL_SAND, SOUL_SOIL -> true; + default -> false; + }; + } + + private Material getBonusDrop(Material type) { + return switch (type) { + case CLAY -> Material.CLAY_BALL; + case MUD, MUDDY_MANGROVE_ROOTS -> Material.MUD; + case SOUL_SAND -> Material.SOUL_SAND; + default -> Material.SOUL_SOIL; + }; + } + + private double getBonusChance(int level) { + return Math.min(getConfig().maxBonusChance, getConfig().bonusChanceBase + (getLevelPercent(level) * getConfig().bonusChanceFactor)); + } + + private int getHasteAmplifier(int level) { + return Math.max(0, (int) Math.round(getLevelPercent(level) * (getConfig().maxHasteLevel - 1))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Bonus drops from muddy blocks, plus haste while digging in water or rain.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Chance Base for the Excavation Mudlark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusChanceBase = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Chance Factor for the Excavation Mudlark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusChanceFactor = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Bonus Chance for the Excavation Mudlark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxBonusChance = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Haste Level for the Excavation Mudlark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxHasteLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Haste Duration Ticks for the Excavation Mudlark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int hasteDurationTicks = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Bonus Drop for the Excavation Mudlark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBonusDrop = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationOmniTool.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationOmniTool.java new file mode 100644 index 000000000..c54ab109b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationOmniTool.java @@ -0,0 +1,390 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.content.item.multiItems.OmniTool; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; +import java.util.Map; + +public class ExcavationOmniTool extends SimpleAdaptation { + private static final OmniTool omniTool = new OmniTool(); + + public ExcavationOmniTool() { + super("excavation-omnitool"); + registerConfiguration(ExcavationOmniTool.Config.class); + setDisplayName(Localizer.dLocalize("excavation.omni_tool.name")); + setDescription(Localizer.dLocalize("excavation.omni_tool.description")); + setIcon(Material.DISC_FRAGMENT_5); + setInterval(20202); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE) + .key("challenge_excavation_omni_1k") + .title(Localizer.dLocalize("advancement.challenge_excavation_omni_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_omni_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_PICKAXE) + .key("challenge_excavation_omni_25k") + .title(Localizer.dLocalize("advancement.challenge_excavation_omni_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_omni_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_excavation_omni_1k", "excavation.omni-tool.auto-swaps", 1000, 400); + registerMilestone("challenge_excavation_omni_25k", "excavation.omni-tool.auto-swaps", 25000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("excavation.omni_tool.lore1")); + v.addLore(C.GRAY + Localizer.dLocalize("excavation.omni_tool.lore2")); + v.addLore(C.GREEN + Localizer.dLocalize("excavation.omni_tool.lore3")); + v.addLore(C.RED + Localizer.dLocalize("excavation.omni_tool.lore4")); + v.addLore(C.GRAY + Localizer.dLocalize("excavation.omni_tool.lore5")); + v.addLore(C.GREEN + "" + (level + getConfig().startingSlots) + C.GRAY + " " + Localizer.dLocalize("excavation.omni_tool.lore6")); + v.addLore(C.UNDERLINE + Localizer.dLocalize("excavation.omni_tool.lore7")); + + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p && validateTool(p.getInventory().getItemInMainHand())) { + //deny if the tool durability is about to break + if (p.getInventory().getItemInMainHand().getType().getMaxDurability() - p.getInventory().getItemInMainHand().getDurability() <= 2) { + e.setCancelled(true); + return; + } + if (!hasActiveAdaptation(p) && validateTool(p.getInventory().getItemInMainHand())) { + e.setCancelled(true); + return; + } + ItemStack hand = p.getInventory().getItemInMainHand(); + Damageable inHand = (Damageable) hand.getItemMeta(); + + if (!validateTool(hand)) { + return; + } + J.runEntity(p, () -> p.getInventory().setItemInMainHand(omniTool.nextSword(hand))); + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + if (inHand != null && inHand.hasDamage()) { + if ((hand.getType().getMaxDurability() - inHand.getDamage() - 2) <= 2) { + e.setCancelled(true); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.77f); + } + } + + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + if (validateTool(p.getInventory().getItemInMainHand())) { + //deny if the tool durability is about to break + if (p.getInventory().getItemInMainHand().getType().getMaxDurability() - p.getInventory().getItemInMainHand().getDurability() <= 2) { + e.setCancelled(true); + return; + } + + + //deny if they dont have the adaptation + if (!hasActiveAdaptation(p)) { + e.setCancelled(true); + return; + } + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + if (validateTool(p.getInventory().getItemInMainHand())) { + //deny if the tool durability is about to break + if (p.getInventory().getItemInMainHand().getType().getMaxDurability() - p.getInventory().getItemInMainHand().getDurability() <= 2) { + e.setCancelled(true); + return; + } + + if (!hasActiveAdaptation(p)) { + return; + } + Action action = e.getAction(); + if (action == Action.RIGHT_CLICK_BLOCK || action == Action.RIGHT_CLICK_AIR) { + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + ItemStack hand = p.getInventory().getItemInMainHand(); + Damageable imHand = (Damageable) hand.getItemMeta(); + Block block = action == Action.RIGHT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (block != null) { + SoundPlayer sp = SoundPlayer.of(p); + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + if (ItemListings.farmable.contains(block.getType())) { + if (isShovel(hand)) { + J.runEntity(p, () -> p.getInventory().setItemInMainHand(omniTool.nextHoe(hand))); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + } else { + J.runEntity(p, () -> p.getInventory().setItemInMainHand(omniTool.nextShovel(hand))); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + } + if (imHand != null && imHand.hasDamage()) { + if ((hand.getType().getMaxDurability() - imHand.getDamage() - 2) <= 2) { + e.setCancelled(true); + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.77f); + } + } + } else if (ItemListings.burnable.contains(block.getType())) { + J.runEntity(p, () -> p.getInventory().setItemInMainHand(omniTool.nextFnS(hand))); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + if (imHand != null && imHand.hasDamage()) { + if ((hand.getType().getMaxDurability() - imHand.getDamage() - 2) <= 2) { + e.setCancelled(true); + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.77f); + } + } + } + } + } + } + + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerDropItemEvent e) { + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + if (p.isSneaking()) { + if (validateTool(e.getItemDrop().getItemStack())) { + List drops = omniTool.explode(e.getItemDrop().getItemStack()); + for (ItemStack i : drops) { + Damageable iDmgable = (Damageable) i.getItemMeta(); + if (i.hasItemMeta()) { + ItemMeta im = i.getItemMeta().clone(); + ItemMeta im2 = im; + if (im.hasDisplayName()) { + im2.setDisplayName(im.getDisplayName()); + } + if (im.hasEnchants()) { + Map enchants = im.getEnchants(); + for (Enchantment enchant : enchants.keySet()) { + im2.addEnchant(enchant, enchants.get(enchant), true); + } + } + if (iDmgable != null && iDmgable.hasDamage()) { + ((Damageable) im2).setDamage(iDmgable.getDamage()); + } + im2.setLore(null); + i.setItemMeta(im2); + } + drops.set(drops.indexOf(i), i); + } + + J.runEntity(p, () -> { + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_DEATH, 0.25f, 0.77f); + for (ItemStack i : drops) { + p.getWorld().dropItem(p.getLocation(), i); + } + }); + e.getItemDrop().setItemStack(new ItemStack(Material.AIR)); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockDamageEvent e) { + Player p = e.getPlayer(); + org.bukkit.block.Block b = e.getBlock(); // nms block for pref tool + ItemStack hand = p.getInventory().getItemInMainHand(); + + if (validateTool(hand)) { + if (!hasActiveAdaptation(p)) { + return; + } + + Damageable imHand = (Damageable) hand.getItemMeta(); + if (ItemListings.getAxePreference().contains(b.getType())) { + if (!isAxe(hand)) { + Adapt.verbose("Omnitool for " + p.getName() + " changed to axe"); + J.runEntity(p, () -> p.getInventory().setItemInMainHand(omniTool.nextAxe(hand))); + itemDelegate(e, hand, imHand); + } else { + Adapt.verbose("Omnitool for " + p.getName() + " is already axe"); + } + } else if (ItemListings.getShovelPreference().contains(b.getType())) { + if (!isShovel(hand)) { + Adapt.verbose("Omnitool for " + p.getName() + " changed to shovel"); + J.runEntity(p, () -> p.getInventory().setItemInMainHand(omniTool.nextShovel(hand))); + itemDelegate(e, hand, imHand); + } else { + Adapt.verbose("Omnitool for " + p.getName() + " is already shovel"); + } + } else if (ItemListings.getSwordPreference().contains(b.getType())) { + if (!isSword(hand)) { + Adapt.verbose("Omnitool for " + p.getName() + " changed to sword"); + J.runEntity(p, () -> p.getInventory().setItemInMainHand(omniTool.nextSword(hand))); + itemDelegate(e, hand, imHand); + } else { + Adapt.verbose("Omnitool for " + p.getName() + " is already sword"); + } + } else { // Default to pickaxe + if (!isPickaxe(hand)) { + Adapt.verbose("Omnitool for " + p.getName() + " changed to pickaxe"); + J.runEntity(p, () -> p.getInventory().setItemInMainHand(omniTool.nextPickaxe(hand))); + itemDelegate(e, hand, imHand); + } + } + } + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(InventoryClickEvent e) { + Player player = (Player) e.getWhoClicked(); + int level = getActiveLevel(player); + if (level <= 0) { + return; + } + if (e.getClickedInventory() != null && e.getClick().equals(ClickType.SHIFT_LEFT) && e.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { + ItemStack cursor = e.getWhoClicked().getItemOnCursor().clone(); + ItemStack clicked = e.getClickedInventory().getItem(e.getSlot()).clone(); + + if (omniTool.explode(cursor).size() > 1 || omniTool.explode(clicked).size() > 1) { + if (omniTool.explode(cursor).size() >= getSlots(level) || omniTool.explode(clicked).size() >= getSlots(level)) { + e.setCancelled(true); + SoundPlayer sp = SoundPlayer.of(player); + sp.play(e.getWhoClicked().getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 1f, 0.77f); + return; + } + } + if (ItemListings.tool.contains(cursor.getType()) && ItemListings.tool.contains(clicked.getType())) { // TOOLS ONLY + if (!cursor.getType().isAir() && !clicked.getType().isAir() && omniTool.supportsItem(cursor) && omniTool.supportsItem(clicked)) { + e.setCancelled(true); + e.getWhoClicked().setItemOnCursor(new ItemStack(Material.AIR)); + e.getClickedInventory().setItem(e.getSlot(), omniTool.build(cursor, clicked)); + SoundPlayer spw = SoundPlayer.of(e.getWhoClicked().getWorld()); + spw.play(e.getWhoClicked().getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + } + } + } + + } + + private void itemDelegate(BlockDamageEvent e, ItemStack hand, Damageable imHand) { + Player p = e.getPlayer(); + getPlayer(p).getData().addStat("excavation.omni-tool.auto-swaps", 1); + SoundPlayer sp = SoundPlayer.of(p); + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); + if (imHand != null && imHand.hasDamage()) { + if ((hand.getType().getMaxDurability() - imHand.getDamage() - 2) <= 2) { + e.setCancelled(true); + sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.77f); + } + } + } + + private boolean validateTool(ItemStack item) { + return (item.getItemMeta() != null + && item.getItemMeta().getLore() != null + && item.getItemMeta().getLore().toString().contains("Leatherman")); + } + + private double getSlots(double level) { + return getConfig().startingSlots + level; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Dynamically merge and swap tools on the fly based on what you are mining.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Starting Slots for the Excavation Omni Tool adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int startingSlots = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSeismicPing.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSeismicPing.java new file mode 100644 index 000000000..b0cc1f217 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSeismicPing.java @@ -0,0 +1,304 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.integration.hiddenore.HiddenOreLink; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class ExcavationSeismicPing extends SimpleAdaptation { + private final Map cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + + public ExcavationSeismicPing() { + super("excavation-seismic-ping"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.seismic_ping.description")); + setDisplayName(Localizer.dLocalize("excavation.seismic_ping.name")); + setIcon(Material.GOAT_HORN); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2200); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BELL) + .key("challenge_excavation_seismic_200") + .title(Localizer.dLocalize("advancement.challenge_excavation_seismic_200.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_seismic_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_seismic_200", "excavation.seismic-ping.pings-triggered", 200, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getScanRange(level) + C.GRAY + " " + Localizer.dLocalize("excavation.seismic_ping.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getPingChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("excavation.seismic_ping.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.seismic_ping.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + if (!isExcavationTool(p.getInventory().getItemInMainHand())) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + long now = System.currentTimeMillis(); + long nextReady = cooldowns.getOrDefault(p.getUniqueId(), 0L); + if (now < nextReady) { + return; + } + + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + if (ThreadLocalRandom.current().nextDouble() > getPingChance(level)) { + return; + } + + Location blockLocation = e.getBlock().getLocation(); + Block target = findNearestOre(blockLocation, getScanRange(level)); + HiddenOreLink.VeinTarget hidden = HiddenOreLink.nearestVein(blockLocation, getScanRange(level)); + + Location targetCenter = null; + Material targetValueType = null; + if (target != null) { + targetCenter = target.getLocation().add(0.5, 0.5, 0.5); + targetValueType = target.getType(); + } + if (hidden != null) { + Location hiddenCenter = hidden.location().clone().add(0.5, 0.5, 0.5); + if (targetCenter == null || hiddenCenter.distanceSquared(blockLocation) < targetCenter.distanceSquared(blockLocation)) { + targetCenter = hiddenCenter; + targetValueType = hidden.display(); + } + } + if (targetCenter == null) { + return; + } + + Location origin = p.getEyeLocation(); + Vector direction = targetCenter.toVector().subtract(origin.toVector()); + if (direction.lengthSquared() <= 0.0000001) { + return; + } + + renderDirectionHint(p, origin, direction.normalize(), getHintSegments(level)); + playPingSound(p, origin.distance(targetCenter), getScanRange(level)); + getPlayer(p).getData().addStat("excavation.seismic-ping.pings-triggered", 1); + xp(p, getConfig().xpPerPing + (getValue(targetValueType) * getConfig().targetValueXpMultiplier)); + } + + private void renderDirectionHint(Player p, Location origin, Vector direction, int segments) { + Particle.DustOptions dust = new Particle.DustOptions(Color.fromRGB(110, 230, 255), (float) getConfig().particleSize); + Location at = origin.clone(); + for (int i = 0; i < segments; i++) { + at = at.add(direction.clone().multiply(getConfig().segmentSpacing)); + if (areParticlesEnabled()) { + p.spawnParticle(Particle.DUST, at, Math.max(1, getConfig().segmentParticleCount), 0.05, 0.05, 0.05, 0, dust); + } + } + + if (areParticlesEnabled()) { + + p.spawnParticle(Particle.ELECTRIC_SPARK, at, Math.max(1, getConfig().tipParticleCount), 0.1, 0.1, 0.1, 0.04); + + } + } + + private void playPingSound(Player p, double distance, int range) { + double normalized = Math.min(1.0, distance / Math.max(1.0, range)); + float pitch = (float) Math.max(0.45, Math.min(1.95, 1.9 - (normalized * 1.1))); + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.9f, pitch); + sp.play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BIT, 0.65f, (float) Math.min(2.0, pitch + 0.2)); + } + + private Block findNearestOre(Location origin, int range) { + World world = origin.getWorld(); + if (world == null) { + return null; + } + + int ox = origin.getBlockX(); + int oy = origin.getBlockY(); + int oz = origin.getBlockZ(); + int minY = world.getMinHeight(); + int maxY = world.getMaxHeight() - 1; + int rangeSq = range * range; + + Block best = null; + double bestDistanceSq = Double.MAX_VALUE; + for (int x = -range; x <= range; x++) { + int bx = ox + x; + for (int z = -range; z <= range; z++) { + int bz = oz + z; + if (!world.isChunkLoaded(bx >> 4, bz >> 4)) { + continue; + } + + for (int y = -range; y <= range; y++) { + int by = oy + y; + if (by < minY || by > maxY) { + continue; + } + + int d2 = (x * x) + (y * y) + (z * z); + if (d2 > rangeSq || d2 >= bestDistanceSq) { + continue; + } + + Block block = world.getBlockAt(bx, by, bz); + if (!isOre(block.getType())) { + continue; + } + + best = block; + bestDistanceSq = d2; + } + } + } + + return best; + } + + private boolean isOre(Material type) { + return type == Material.ANCIENT_DEBRIS || type.name().endsWith("_ORE"); + } + + private boolean isExcavationTool(ItemStack item) { + if (!isItem(item)) { + return false; + } + + String name = item.getType().name(); + return name.endsWith("_SHOVEL") || name.endsWith("_PICKAXE"); + } + + private int getScanRange(int level) { + return Math.max(6, (int) Math.round(getConfig().scanRangeBase + (getLevelPercent(level) * getConfig().scanRangeFactor))); + } + + private double getPingChance(int level) { + return Math.min(getConfig().maxPingChance, getConfig().pingChanceBase + (getLevelPercent(level) * getConfig().pingChanceFactor)); + } + + private long getCooldownMillis(int level) { + return Math.max(350L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + private int getHintSegments(int level) { + return Math.max(4, (int) Math.round(getConfig().hintSegmentsBase + (getLevelPercent(level) * getConfig().hintSegmentsFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Mining can emit seismic pings that hint toward nearby ore direction.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.78; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Scan Range Base for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double scanRangeBase = 11; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Scan Range Factor for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double scanRangeFactor = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ping Chance Base for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pingChanceBase = 0.14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ping Chance Factor for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pingChanceFactor = 0.37; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Ping Chance for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxPingChance = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 2600; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 1850; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hint Segments Base for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hintSegmentsBase = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hint Segments Factor for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hintSegmentsFactor = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Segment Spacing for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double segmentSpacing = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Particle Size for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double particleSize = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Segment Particle Count for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int segmentParticleCount = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Tip Particle Count for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int tipParticleCount = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Ping for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerPing = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Target Value Xp Multiplier for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double targetValueXpMultiplier = 0.5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSoftFall.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSoftFall.java new file mode 100644 index 000000000..9da78da3d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSoftFall.java @@ -0,0 +1,162 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; + +public class ExcavationSoftFall extends SimpleAdaptation { + public ExcavationSoftFall() { + super("excavation-soft-fall"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.soft_fall.description")); + setDisplayName(Localizer.dLocalize("excavation.soft_fall.name")); + setIcon(Material.SAND); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(3530); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SAND) + .key("challenge_excavation_softfall_1k") + .title(Localizer.dLocalize("advancement.challenge_excavation_softfall_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_softfall_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_softfall_1k", "excavation.soft-fall.damage-prevented", 1000, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("excavation.soft_fall.lore1")); + v.addLore(C.GRAY + Localizer.dLocalize("excavation.soft_fall.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageEvent e) { + if (e.getCause() != EntityDamageEvent.DamageCause.FALL || !(e.getEntity() instanceof Player p)) { + return; + } + + Block feet = p.getLocation().getBlock(); + if (!isSoftGround(feet.getType()) && !isSoftGround(feet.getRelative(BlockFace.DOWN).getType())) { + return; + } + + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + double reduction = getReduction(level); + double prevented = e.getDamage() * reduction; + if (prevented <= 0) { + return; + } + + e.setDamage(Math.max(0, e.getDamage() - prevented)); + if (e.getDamage() <= 0.01) { + e.setCancelled(true); + } + + if (areParticlesEnabled()) { + p.spawnParticle(Particle.CLOUD, p.getLocation(), 8, 0.3, 0.1, 0.3, 0.02); + } + + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_ROOTED_DIRT_BREAK, 0.6f, 0.7f); + getPlayer(p).getData().addStat("excavation.soft-fall.damage-prevented", prevented); + xp(p, prevented * getConfig().xpPerDamagePrevented); + }); + } + + private boolean isSoftGround(Material type) { + return switch (type) { + case DIRT, GRASS_BLOCK, COARSE_DIRT, ROOTED_DIRT, PODZOL, MYCELIUM, + DIRT_PATH, FARMLAND, SAND, RED_SAND, GRAVEL, CLAY, MUD, + MUDDY_MANGROVE_ROOTS, SOUL_SAND, SOUL_SOIL, SNOW, SNOW_BLOCK -> + true; + default -> false; + }; + } + + private double getReduction(int level) { + return Math.min(getConfig().maxReduction, getConfig().reductionBase + (getLevelPercent(level) * getConfig().reductionFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Landing on soft diggable ground reduces fall damage, up to full negation.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reduction Base for the Excavation Soft Fall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reductionBase = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reduction Factor for the Excavation Soft Fall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reductionFactor = 0.85; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Reduction for the Excavation Soft Fall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxReduction = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Damage Prevented for the Excavation Soft Fall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerDamagePrevented = 3.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSpelunker.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSpelunker.java new file mode 100644 index 000000000..77671cdbb --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationSpelunker.java @@ -0,0 +1,246 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.inventorygui.Element; +import fr.skytasul.glowingentities.GlowingEntities; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.entity.Slime; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class ExcavationSpelunker extends SimpleAdaptation { + private final Map cooldowns; + + public ExcavationSpelunker() { + super("excavation-spelunker"); + registerConfiguration(ExcavationSpelunker.Config.class); + setDisplayName(Localizer.dLocalize("excavation.spelunker.name")); + setDescription(Localizer.dLocalize("excavation.spelunker.description")); + setIcon(Material.GOLDEN_HELMET); + setInterval(20388); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPYGLASS) + .key("challenge_excavation_spelunker_1k") + .title(Localizer.dLocalize("advancement.challenge_excavation_spelunker_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_spelunker_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_ORE) + .key("challenge_excavation_spelunker_25k") + .title(Localizer.dLocalize("advancement.challenge_excavation_spelunker_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_spelunker_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_excavation_spelunker_1k", "excavation.spelunker.ores-revealed", 1000, 400); + registerMilestone("challenge_excavation_spelunker_25k", "excavation.spelunker.ores-revealed", 25000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("excavation.spelunker.lore1")); + v.addLore(C.YELLOW + Localizer.dLocalize("excavation.spelunker.lore2") + getConfig().rangeMultiplier * level); + v.addLore(C.YELLOW + Localizer.dLocalize("excavation.spelunker.lore3")); + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + int level = getActiveLevel(p, Player::isSneaking); + // Check if player is sneaking, has Glowberries in main hand, and an ore in offhand + if (level > 0 && hasGlowberries(p) && hasOreInOffhand(p)) { + // Check if player is on cooldown + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown > System.currentTimeMillis()) { + sp.play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1); + return; + } + int radius = getConfig().rangeMultiplier * level; + consumeGlowberry(p); + searchForOres(p, radius); + cooldowns.put(p.getUniqueId(), (long) (System.currentTimeMillis() + (1000 * getConfig().cooldown))); + } + } + + private boolean hasGlowberries(Player player) { + return player.getInventory().getItemInMainHand().getType() == Material.GLOW_BERRIES; + } + + private void consumeGlowberry(Player player) { + ItemStack berries = player.getInventory().getItemInMainHand(); + berries.setAmount(berries.getAmount() - 1); + player.getInventory().setItemInMainHand(berries); + } + + private boolean hasOreInOffhand(Player player) { + Material offhandType = player.getInventory().getItemInOffHand().getType(); + return ItemListings.ores.contains(offhandType); + } + + private void searchForOres(Player p, int radius) { + Location playerLocation = p.getLocation(); + World world = p.getWorld(); + Material targetOre = p.getInventory().getItemInOffHand().getType(); + ChatColor c = ItemListings.oreColorsChatColor.get(targetOre); + Particle.DustOptions dustOptions = new Particle.DustOptions(Color.WHITE, 1); + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + for (int z = -radius; z <= radius; z++) { + if (x * x + y * y + z * z <= radius * radius) { + Location blockLocation = playerLocation.clone().add(x, y, z); + Block block = world.getBlockAt(blockLocation); + GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); + + if (block.getType() == targetOre) { + getPlayer(p).getData().addStat("excavation.spelunker.ores-revealed", 1); + // Raytrace particles from player to the found ore + Vector vector = blockLocation.clone().subtract(playerLocation).toVector().normalize().multiply(0.5); + Location particleLocation = playerLocation.clone(); + + while (particleLocation.distance(blockLocation) > 0.5) { + particleLocation.add(vector); + if (areParticlesEnabled()) { + p.spawnParticle(Particles.REDSTONE, particleLocation, 1, dustOptions); + } + } + + SoundPlayer spw = SoundPlayer.of(world); + spw.play(block.getLocation().add(0.5, 0, 0.5), Sound.BLOCK_BEACON_ACTIVATE, 1, 1); + if (glowingEntities == null) { + continue; + } + Slime slime = block.getWorld().spawn(block.getLocation().add(0.5, 0, 0.5), Slime.class, (s) -> { + s.setRotation(0, 0); + s.setInvulnerable(true); + s.setCollidable(false); + s.setGravity(false); + s.setSilent(true); + s.setAI(false); + s.setSize(2); + s.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0, false, false)); + s.setMetadata("preventSuffocation", new FixedMetadataValue(Adapt.instance, true)); + }); + + try { + glowingEntities.setGlowing(slime, p, c); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + + J.runEntity(slime, () -> { + try { + glowingEntities.unsetGlowing(slime, p); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + + slime.remove(); + }, 5 * 20); + } + } + } + } + } + } + + + @EventHandler + public void onEntityDamage(EntityDamageEvent e) { + if (e.getEntity() instanceof Slime && e.getCause() == EntityDamageEvent.DamageCause.SUFFOCATION) { + Slime slime = (Slime) e.getEntity(); + if (slime.hasMetadata("preventSuffocation")) { + e.setCancelled(true); + } else { + e.setCancelled(true); + slime.remove(); + } + } + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("See ores through the ground using Glowberries in your main hand.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Excavation Spelunker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldown = 6.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Range Multiplier for the Excavation Spelunker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int rangeMultiplier = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationTreasureHunter.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationTreasureHunter.java new file mode 100644 index 000000000..064d06a52 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationTreasureHunter.java @@ -0,0 +1,244 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class ExcavationTreasureHunter extends SimpleAdaptation { + private volatile TableCache tableCache; + + public ExcavationTreasureHunter() { + super("excavation-treasure-hunter"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.treasure_hunter.description")); + setDisplayName(Localizer.dLocalize("excavation.treasure_hunter.name")); + setIcon(Material.EMERALD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(3370); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EMERALD) + .key("challenge_excavation_treasure_500") + .title(Localizer.dLocalize("advancement.challenge_excavation_treasure_500.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_treasure_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_treasure_500", "excavation.treasure-hunter.treasures-found", 500, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getTreasureChance(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.treasure_hunter.lore1")); + v.addLore(C.GRAY + Localizer.dLocalize("excavation.treasure_hunter.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (!isTreasureBlock(e.getBlock().getType())) { + return; + } + + Player p = e.getPlayer(); + if (!isShovel(p.getInventory().getItemInMainHand())) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + ThreadLocalRandom random = ThreadLocalRandom.current(); + if (random.nextDouble() > getTreasureChance(level)) { + return; + } + + TableCache table = getLootTable(); + if (table.entries().isEmpty()) { + return; + } + + LootEntry entry = pickWeighted(table, random.nextDouble() * table.totalWeight()); + int amount = entry.min() >= entry.max() ? entry.min() : random.nextInt(entry.min(), entry.max() + 1); + Location drop = e.getBlock().getLocation().add(0.5, 0.5, 0.5); + e.getBlock().getWorld().dropItemNaturally(drop, new ItemStack(entry.material(), amount)); + + if (areParticlesEnabled()) { + p.spawnParticle(Particle.WAX_ON, drop, 10, 0.25, 0.25, 0.25, 0.02); + } + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(drop, Sound.ITEM_BRUSH_BRUSHING_SAND_COMPLETE, 0.8f, 1.2f); + sp.play(drop, Sound.BLOCK_NOTE_BLOCK_PLING, 0.6f, 1.7f); + getPlayer(p).getData().addStat("excavation.treasure-hunter.treasures-found", 1); + xp(p, getConfig().xpPerTreasure); + } + + private LootEntry pickWeighted(TableCache table, double roll) { + List entries = table.entries(); + double remaining = roll; + for (LootEntry entry : entries) { + remaining -= entry.weight(); + if (remaining <= 0) { + return entry; + } + } + + return entries.get(entries.size() - 1); + } + + private TableCache getLootTable() { + List source = getConfig().lootTable; + TableCache local = tableCache; + if (local != null && local.source() == source) { + return local; + } + + List parsed = new ArrayList<>(source.size()); + double totalWeight = 0; + for (String raw : source) { + String[] parts = raw.split(":"); + if (parts.length < 2) { + continue; + } + + Material material = Material.matchMaterial(parts[0].trim()); + if (material == null) { + continue; + } + + double weight = parseDouble(parts[1]); + if (weight <= 0) { + continue; + } + + int min = parts.length > 2 ? Math.max(1, (int) parseDouble(parts[2])) : 1; + int max = parts.length > 3 ? Math.max(min, (int) parseDouble(parts[3])) : min; + parsed.add(new LootEntry(material, weight, min, max)); + totalWeight += weight; + } + + TableCache built = new TableCache(source, parsed, totalWeight); + tableCache = built; + return built; + } + + private double parseDouble(String raw) { + try { + return Double.parseDouble(raw.trim()); + } catch (NumberFormatException ignored) { + return 0; + } + } + + private boolean isTreasureBlock(Material type) { + return switch (type) { + case SAND, RED_SAND, GRAVEL, MUD, CLAY -> true; + default -> false; + }; + } + + private double getTreasureChance(int level) { + return Math.min(getConfig().maxTreasureChance, getConfig().treasureChanceBase + (getLevelPercent(level) * getConfig().treasureChanceFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record LootEntry(Material material, double weight, int min, int max) { + } + + private record TableCache(List source, List entries, + double totalWeight) { + } + + @NoArgsConstructor + @ConfigDescription("Digging sand, gravel, mud, or clay with a shovel can unearth archaeology treasure.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Treasure Chance Base for the Excavation Treasure Hunter adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double treasureChanceBase = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Treasure Chance Factor for the Excavation Treasure Hunter adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double treasureChanceFactor = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Treasure Chance for the Excavation Treasure Hunter adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxTreasureChance = 0.06; + @art.arcane.adapt.util.config.ConfigDoc(value = "Weighted loot table entries formatted as MATERIAL:weight:min:max.", impact = "Adding entries or raising weights changes which treasures drop and how often.") + List lootTable = new ArrayList<>(List.of( + "BONE:30:1:2", + "FLINT:30:1:2", + "CLAY_BALL:15:1:3", + "ANGLER_POTTERY_SHERD:6:1:1", + "ARMS_UP_POTTERY_SHERD:6:1:1", + "SKULL_POTTERY_SHERD:4:1:1", + "EMERALD:3:1:1" + )); + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Treasure for the Excavation Treasure Hunter adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerTreasure = 12; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationTunneler.java b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationTunneler.java new file mode 100644 index 000000000..f37a97e3c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/excavation/ExcavationTunneler.java @@ -0,0 +1,228 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.excavation; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; + +public class ExcavationTunneler extends SimpleAdaptation { + private static final int[][] PLANE_OFFSETS = { + {0, 1}, {0, -1}, {1, 0}, {-1, 0}, {1, 1}, {-1, 1}, {1, -1}, {-1, -1} + }; + + public ExcavationTunneler() { + super("excavation-tunneler"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("excavation.tunneler.description")); + setDisplayName(Localizer.dLocalize("excavation.tunneler.name")); + setIcon(Material.IRON_SHOVEL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(3170); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SHOVEL) + .key("challenge_excavation_tunneler_10k") + .title(Localizer.dLocalize("advancement.challenge_excavation_tunneler_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavation_tunneler_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_excavation_tunneler_10k", "excavation.tunneler.blocks-tunneled", 10000, 600); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getBonusBlocks(level) + C.GRAY + " " + Localizer.dLocalize("excavation.tunneler.lore1")); + v.addLore(C.YELLOW + "* " + getConfig().durabilityCostPerBonusBlock + C.GRAY + " " + Localizer.dLocalize("excavation.tunneler.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isShovel(hand)) { + return; + } + + if (!isShovelable(e.getBlock().getType())) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + int bonus = getBonusBlocks(level); + float pitch = p.getLocation().getPitch(); + float yaw = p.getLocation().getYaw(); + boolean horizontal = pitch >= 50 || pitch <= -50; + boolean facingX = !horizontal && isFacingX(yaw); + + Block origin = e.getBlock(); + int broken = 0; + for (int[] offset : PLANE_OFFSETS) { + if (broken >= bonus) { + break; + } + + int dx; + int dy; + int dz; + if (horizontal) { + dx = offset[0]; + dy = 0; + dz = offset[1]; + } else if (facingX) { + dx = 0; + dy = offset[1]; + dz = offset[0]; + } else { + dx = offset[0]; + dy = offset[1]; + dz = 0; + } + + Block target = origin.getRelative(dx, dy, dz); + if (!isShovelable(target.getType())) { + continue; + } + + if (!canBlockBreak(p, target.getLocation())) { + continue; + } + + if (!applyDurability(p, hand, getConfig().durabilityCostPerBonusBlock)) { + break; + } + + target.breakNaturally(hand); + broken++; + } + + if (broken <= 0) { + return; + } + + SoundPlayer.of(p.getWorld()).play(origin.getLocation(), Sound.ITEM_SHOVEL_FLATTEN, 0.7f, 0.8f); + getPlayer(p).getData().addStat("excavation.tunneler.blocks-tunneled", broken); + xp(p, broken * getConfig().xpPerBonusBlock); + } + + private boolean isFacingX(float yaw) { + float normalized = ((yaw % 360) + 360) % 360; + return (normalized >= 45 && normalized < 135) || (normalized >= 225 && normalized < 315); + } + + private boolean applyDurability(Player p, ItemStack hand, int cost) { + if (cost <= 0) { + return true; + } + + if (!(hand.getItemMeta() instanceof Damageable damageable)) { + return false; + } + + int maxDurability = hand.getType().getMaxDurability(); + int currentDamage = damageable.getDamage(); + if (currentDamage + cost >= maxDurability) { + return false; + } + + damageable.setDamage(currentDamage + cost); + hand.setItemMeta(damageable); + p.getInventory().setItemInMainHand(hand); + return true; + } + + private boolean isShovelable(Material type) { + return switch (type) { + case CLAY, DIRT, COARSE_DIRT, ROOTED_DIRT, FARMLAND, GRASS_BLOCK, + DIRT_PATH, GRAVEL, MYCELIUM, PODZOL, SAND, RED_SAND, SOUL_SAND, + SOUL_SOIL, SNOW, SNOW_BLOCK, MUD, MUDDY_MANGROVE_ROOTS -> true; + default -> false; + }; + } + + private int getBonusBlocks(int level) { + return Math.max(1, Math.min(8, (int) Math.floor(getLevelPercent(level) * getConfig().bonusBlocksMax))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak while digging soft blocks to carve a facing-oriented plane of blocks at once.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Blocks Max for the Excavation Tunneler adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusBlocksMax = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Per Bonus Block for the Excavation Tunneler adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int durabilityCostPerBonusBlock = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Bonus Block for the Excavation Tunneler adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBonusBlock = 1.5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismBeeShepherd.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismBeeShepherd.java new file mode 100644 index 000000000..75a88551a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismBeeShepherd.java @@ -0,0 +1,272 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; +import org.bukkit.entity.Bee; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class HerbalismBeeShepherd extends SimpleAdaptation { + private final Map lastPulse = new java.util.concurrent.ConcurrentHashMap<>(); + + public HerbalismBeeShepherd() { + super("herbalism-bee-shepherd"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.bee_shepherd.description")); + setDisplayName(Localizer.dLocalize("herbalism.bee_shepherd.name")); + setIcon(Material.BEE_NEST); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(10); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.HONEYCOMB) + .key("challenge_herbalism_bee_100") + .title(Localizer.dLocalize("advancement.challenge_herbalism_bee_100.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_bee_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_bee_100", "herbalism.bee-shepherd.bees-attracted", 100, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("herbalism.bee_shepherd.lore1")); + v.addLore(C.GREEN + "+ " + getGrowthAttempts(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.bee_shepherd.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getPulseMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("herbalism.bee_shepherd.lore3")); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + if (!hasActiveAdaptation(p) || !isHoldingFlower(p)) { + continue; + } + + int level = getActiveLevel(p); + if (now - lastPulse.getOrDefault(p.getUniqueId(), 0L) < getPulseMillis(level)) { + continue; + } + + int foodCost = getFoodCost(level); + if (p.getFoodLevel() < foodCost) { + continue; + } + + int grown = pulseGrowth(p, level); + int attracted = pullNearbyBees(p, level); + lastPulse.put(p.getUniqueId(), now); + if (attracted > 0) { + getPlayer(p).getData().addStat("herbalism.bee-shepherd.bees-attracted", attracted); + } + if (grown <= 0) { + continue; + } + + p.setFoodLevel(Math.max(0, p.getFoodLevel() - foodCost)); + if (areParticlesEnabled()) { + p.spawnParticle(Particle.HAPPY_VILLAGER, p.getLocation().add(0, 1, 0), 12, 0.5, 0.4, 0.5, 0.1); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_BEE_POLLINATE, 0.85f, 1.25f); + xp(p, grown * getConfig().xpPerGrowth); + } + } + + private int pulseGrowth(Player p, int level) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + int radius = Math.max(1, (int) Math.round(getRadius(level))); + int grown = 0; + int attempts = getGrowthAttempts(level); + for (int i = 0; i < attempts; i++) { + int dx = random.nextInt(-radius, radius + 1); + int dz = random.nextInt(-radius, radius + 1); + int dy = random.nextInt(-1, 2); + Block block = p.getLocation().getBlock().getRelative(dx, dy, dz); + if (!(block.getBlockData() instanceof Ageable ageable) || ageable.getAge() >= ageable.getMaximumAge()) { + continue; + } + + int increase = Math.max(1, getGrowthStep(level)); + ageable.setAge(Math.min(ageable.getMaximumAge(), ageable.getAge() + increase)); + block.setBlockData(ageable, true); + grown++; + if (getConfig().showGrowthParticles) { + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.COMPOSTER, block.getLocation().add(0.5, 0.5, 0.5), 3, 0.1, 0.1, 0.1, 0.01); + } + } + } + + return grown; + } + + private int pullNearbyBees(Player p, int level) { + double radius = getRadius(level); + int count = 0; + for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), radius, radius, radius)) { + if (!(entity instanceof Bee bee)) { + continue; + } + + Vector toward = p.getLocation().add(0, 0.75, 0).toVector().subtract(bee.getLocation().toVector()); + if (toward.lengthSquared() <= 0.001) { + continue; + } + + toward.normalize().multiply(getBeePullStrength(level)); + bee.setVelocity(bee.getVelocity().multiply(0.6).add(toward)); + bee.setTarget(null); + count++; + } + return count; + } + + private boolean isHoldingFlower(Player p) { + return isFlower(p.getInventory().getItemInMainHand()) || isFlower(p.getInventory().getItemInOffHand()); + } + + private boolean isFlower(ItemStack item) { + if (!isItem(item)) { + return false; + } + + Material type = item.getType(); + return type.name().endsWith("_TULIP") + || type == Material.DANDELION + || type == Material.POPPY + || type == Material.BLUE_ORCHID + || type == Material.ALLIUM + || type == Material.AZURE_BLUET + || type == Material.OXEYE_DAISY + || type == Material.CORNFLOWER + || type == Material.LILY_OF_THE_VALLEY + || type == Material.WITHER_ROSE + || type == Material.SUNFLOWER + || type == Material.LILAC + || type == Material.ROSE_BUSH + || type == Material.PEONY + || type == Material.TORCHFLOWER + || type == Material.PINK_PETALS; + } + + private double getRadius(int level) { + return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); + } + + private int getGrowthAttempts(int level) { + return Math.max(1, (int) Math.round(getConfig().growthAttemptsBase + (getLevelPercent(level) * getConfig().growthAttemptsFactor))); + } + + private int getGrowthStep(int level) { + return Math.max(1, (int) Math.round(getConfig().growthStepBase + (getLevelPercent(level) * getConfig().growthStepFactor))); + } + + private int getFoodCost(int level) { + return Math.max(1, (int) Math.round(getConfig().foodCostBase - (getLevelPercent(level) * getConfig().foodCostFactor))); + } + + private long getPulseMillis(int level) { + return Math.max(250L, (long) Math.round(getConfig().pulseMillisBase - (getLevelPercent(level) * getConfig().pulseMillisFactor))); + } + + private double getBeePullStrength(int level) { + return Math.max(0.01, getConfig().beePullStrengthBase + (getLevelPercent(level) * getConfig().beePullStrengthFactor)); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Holding flowers near crops emits growth pulses and draws nearby bees toward you.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Growth Particles for the Herbalism Bee Shepherd adaptation.", impact = "True enables this behavior and false disables it.") + boolean showGrowthParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.64; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Growth Attempts Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double growthAttemptsBase = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Growth Attempts Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double growthAttemptsFactor = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Growth Step Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double growthStepBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Growth Step Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double growthStepFactor = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Food Cost Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double foodCostBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Food Cost Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double foodCostFactor = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Pulse Millis Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pulseMillisBase = 900; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Pulse Millis Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pulseMillisFactor = 650; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bee Pull Strength Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double beePullStrengthBase = 0.07; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bee Pull Strength Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double beePullStrengthFactor = 0.14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Growth for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerGrowth = 0.9; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCompostCascade.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCompostCascade.java new file mode 100644 index 000000000..0e145aa33 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCompostCascade.java @@ -0,0 +1,561 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Levelled; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.Locale; +import java.util.concurrent.ThreadLocalRandom; + +public class HerbalismCompostCascade extends SimpleAdaptation { + public HerbalismCompostCascade() { + super("herbalism-compost-cascade"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.compost_cascade.description")); + setDisplayName(Localizer.dLocalize("herbalism.compost_cascade.name")); + setIcon(Material.COMPOSTER); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(600); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COMPOSTER) + .key("challenge_herbalism_compost_1k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_compost_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_compost_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BONE_MEAL) + .key("challenge_herbalism_compost_25k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_compost_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_compost_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_herbalism_compost_1k", "herbalism.compost-cascade.items-composted", 1000, 300); + registerMilestone("challenge_herbalism_compost_25k", "herbalism.compost-cascade.items-composted", 25000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("herbalism.compost_cascade.lore1")); + v.addLore(C.GREEN + "+ " + getMaxItems(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.compost_cascade.lore2")); + v.addLore(C.GREEN + "+ " + Form.pc(getFillChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.compost_cascade.lore3")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("herbalism.compost_cascade.lore4")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if ((action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) || e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p) || !p.isSneaking() || p.hasCooldown(Material.COMPOSTER)) { + return; + } + + Block composter = e.getClickedBlock(); + if (composter == null) { + composter = p.getTargetBlockExact(5); + } + + if (composter == null || composter.getType() != Material.COMPOSTER) { + return; + } + + if (!(composter.getBlockData() instanceof Levelled levelled)) { + return; + } + + int oldLevel = levelled.getLevel(); + if (oldLevel >= 8) { + return; + } + + int level = getActiveLevel(p); + double fillChance = getFillChance(level); + int maxItems = getMaxItems(level); + double radius = getRadius(level); + Location center = composter.getLocation().clone().add(0.5, 0.5, 0.5); + World world = center.getWorld(); + if (world == null) { + return; + } + + CompostState state = new CompostState(oldLevel); + + processDroppedItems(p, world, center, radius, state, maxItems, fillChance); + processMatureCrops(p, world, center, radius, state, maxItems, fillChance); + processLeafBlocks(p, world, center, radius, level, state, maxItems, fillChance); + processInventoryItems(p, state, maxItems, fillChance); + + if (state.consumed <= 0) { + return; + } + + Levelled updated = (Levelled) composter.getBlockData(); + updated.setLevel(Math.min(8, Math.max(oldLevel, state.compostLevel))); + composter.setBlockData(updated); + + p.setCooldown(Material.COMPOSTER, getCooldownTicks(level)); + e.setCancelled(true); + + getPlayer(p).getData().addStat("harvest.composted", state.consumed); + getPlayer(p).getData().addStat("herbalism.compost-cascade.items-composted", state.consumed); + xp(p, center, (state.consumed * getConfig().xpPerItemConsumed) + (state.levelGains * getConfig().xpPerLevelGain)); + + SoundPlayer sp = SoundPlayer.of(world); + sp.play(center, Sound.BLOCK_COMPOSTER_FILL, 0.8f, 1.25f); + if (updated.getLevel() >= 8) { + sp.play(center, Sound.BLOCK_COMPOSTER_READY, 1.0f, 1.12f); + } + + dropRewards(world, center, level, oldLevel, updated.getLevel(), state.consumed); + } + + private void processDroppedItems(Player p, World world, Location center, double radius, CompostState state, int maxItems, double fillChance) { + if (isComposterDone(state, maxItems)) { + return; + } + + for (Entity entity : world.getNearbyEntities(center, radius, radius, radius)) { + if (!(entity instanceof Item item) || isComposterDone(state, maxItems)) { + continue; + } + + if (!canSnatchItem(p, item)) { + continue; + } + + ItemStack stack = item.getItemStack(); + if (!isItem(stack) || !isCompostable(stack.getType())) { + continue; + } + + compostStack(stack, state, maxItems, fillChance); + if (stack.getAmount() <= 0) { + item.remove(); + } else { + item.setItemStack(stack); + } + } + } + + private void processMatureCrops(Player p, World world, Location center, double radius, CompostState state, int maxItems, double fillChance) { + if (isComposterDone(state, maxItems)) { + return; + } + + int r = Math.max(1, (int) Math.ceil(radius)); + double rs = radius * radius; + for (int x = -r; x <= r; x++) { + for (int y = -r; y <= r; y++) { + for (int z = -r; z <= r; z++) { + if (isComposterDone(state, maxItems)) { + return; + } + + if ((x * x) + (y * y) + (z * z) > rs) { + continue; + } + + Block b = world.getBlockAt(center.getBlockX() + x, center.getBlockY() + y, center.getBlockZ() + z); + if (!isMatureCrop(b)) { + continue; + } + + if (!canBlockBreak(p, b.getLocation()) || !canBlockPlace(p, b.getLocation())) { + continue; + } + + ItemStack[] drops = b.getDrops().toArray(ItemStack[]::new); + if (!replantCrop(b)) { + continue; + } + + for (ItemStack drop : drops) { + if (!isItem(drop)) { + continue; + } + + if (isCompostable(drop.getType()) && !isComposterDone(state, maxItems)) { + compostStack(drop, state, maxItems, fillChance); + } + + if (drop.getAmount() > 0) { + world.dropItemNaturally(b.getLocation().add(0.5, 0.5, 0.5), drop); + } + } + } + } + } + } + + private void processLeafBlocks(Player p, World world, Location center, double radius, int level, CompostState state, int maxItems, double fillChance) { + if (isComposterDone(state, maxItems)) { + return; + } + + int r = Math.max(1, (int) Math.ceil(radius)); + double rs = radius * radius; + int bursts = getLeafCompostBursts(level); + for (int x = -r; x <= r; x++) { + for (int y = -r; y <= r; y++) { + for (int z = -r; z <= r; z++) { + if (isComposterDone(state, maxItems)) { + return; + } + + if ((x * x) + (y * y) + (z * z) > rs) { + continue; + } + + Block b = world.getBlockAt(center.getBlockX() + x, center.getBlockY() + y, center.getBlockZ() + z); + if (!isLeafBlock(b.getType()) || !canBlockBreak(p, b.getLocation())) { + continue; + } + + b.setType(Material.AIR, false); + ItemStack leafMass = new ItemStack(Material.OAK_LEAVES, bursts); + compostStack(leafMass, state, maxItems, getLeafFillChance(level, fillChance)); + } + } + } + } + + private void processInventoryItems(Player p, CompostState state, int maxItems, double fillChance) { + if (isComposterDone(state, maxItems)) { + return; + } + + ItemStack[] storage = p.getInventory().getStorageContents(); + boolean changed = false; + for (int i = 0; i < storage.length; i++) { + if (isComposterDone(state, maxItems)) { + break; + } + + ItemStack stack = storage[i]; + if (!isItem(stack) || !isCompostable(stack.getType())) { + continue; + } + + compostStack(stack, state, maxItems, fillChance); + changed = true; + if (stack.getAmount() <= 0) { + storage[i] = null; + } + } + + if (changed) { + p.getInventory().setStorageContents(storage); + } + } + + private void compostStack(ItemStack stack, CompostState state, int maxItems, double fillChance) { + while (stack.getAmount() > 0 && !isComposterDone(state, maxItems)) { + stack.setAmount(stack.getAmount() - 1); + state.processed++; + state.consumed++; + + if (ThreadLocalRandom.current().nextDouble() <= fillChance) { + state.compostLevel++; + state.levelGains++; + } + } + } + + private void dropRewards(World world, Location center, int level, int oldLevel, int newLevel, int consumed) { + int boneMeal = getBaseBoneMeal(level) + Math.max(0, consumed / getItemsPerBoneMeal(level)); + if (newLevel >= 8 && oldLevel < 8) { + boneMeal += getReadyBonusBoneMeal(level); + } + + if (boneMeal > 0) { + world.dropItemNaturally(center, new ItemStack(Material.BONE_MEAL, Math.min(64, boneMeal))); + } + + if (newLevel < 8) { + return; + } + + int rolls = getValuableRolls(level); + for (int i = 0; i < rolls; i++) { + if (ThreadLocalRandom.current().nextDouble() <= getValuableChance(level)) { + world.dropItemNaturally(center, rollValuableReward(level)); + } + } + } + + private ItemStack rollValuableReward(int level) { + double lp = getLevelPercent(level); + double r = ThreadLocalRandom.current().nextDouble(); + + if (r < 0.45) { + return new ItemStack(Material.MOSS_BLOCK, 1 + ThreadLocalRandom.current().nextInt(1 + Math.max(1, (int) Math.round(lp * 3)))); + } + + if (r < 0.7) { + return new ItemStack(Material.GLOW_BERRIES, 2 + ThreadLocalRandom.current().nextInt(2 + Math.max(1, (int) Math.round(lp * 4)))); + } + + if (r < 0.88) { + return new ItemStack(Material.AMETHYST_SHARD, 1 + ThreadLocalRandom.current().nextInt(1 + Math.max(1, (int) Math.round(lp * 4)))); + } + + if (r < 0.97) { + return new ItemStack(Material.EMERALD, 1); + } + + return new ItemStack(Material.DIAMOND, 1); + } + + private boolean isMatureCrop(Block b) { + BlockData data = b.getBlockData(); + if (!(data instanceof Ageable ageable)) { + return false; + } + + Material type = b.getType(); + if (type == Material.CHORUS_PLANT || type == Material.SUGAR_CANE || type == Material.BAMBOO) { + return false; + } + + return ageable.getAge() >= ageable.getMaximumAge(); + } + + private boolean replantCrop(Block b) { + BlockData data = b.getBlockData(); + if (!(data instanceof Ageable ageable)) { + return false; + } + + ageable.setAge(0); + b.setBlockData(ageable, true); + return true; + } + + private boolean isLeafBlock(Material type) { + return type.name().endsWith("_LEAVES"); + } + + private boolean isComposterDone(CompostState state, int maxItems) { + return state.compostLevel >= 8 || state.processed >= maxItems; + } + + private boolean isCompostable(Material type) { + String n = type.name().toUpperCase(Locale.ROOT); + return n.contains("SEEDS") + || n.contains("SAPLING") + || n.contains("LEAVES") + || n.contains("FLOWER") + || n.contains("MUSHROOM") + || n.contains("ROOTS") + || n.contains("VINE") + || n.contains("KELP") + || n.contains("DRIPLEAF") + || n.contains("MOSS") + || type == Material.WHEAT + || type == Material.BEETROOT + || type == Material.CARROT + || type == Material.POTATO + || type == Material.POISONOUS_POTATO + || type == Material.NETHER_WART + || type == Material.CACTUS + || type == Material.SUGAR_CANE + || type == Material.BAMBOO + || type == Material.SHORT_GRASS + || type == Material.TALL_GRASS + || type == Material.SEA_PICKLE; + } + + private int getMaxItems(int level) { + return Math.max(1, (int) Math.round(getConfig().maxItemsBase + (getLevelPercent(level) * getConfig().maxItemsFactor))); + } + + private double getRadius(int level) { + return Math.max(1, getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor)); + } + + private double getFillChance(int level) { + return Math.min(getConfig().maxFillChance, getConfig().fillChanceBase + (getLevelPercent(level) * getConfig().fillChanceFactor)); + } + + private double getLeafFillChance(int level, double baseFillChance) { + return Math.min(1.0, baseFillChance * (getConfig().leafFillChanceMultiplierBase + (getLevelPercent(level) * getConfig().leafFillChanceMultiplierFactor))); + } + + private int getLeafCompostBursts(int level) { + return Math.max(1, (int) Math.round(getConfig().leafCompostBurstsBase + (getLevelPercent(level) * getConfig().leafCompostBurstsFactor))); + } + + private int getCooldownTicks(int level) { + return Math.max(4, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksReduction))); + } + + private int getBaseBoneMeal(int level) { + return Math.max(0, (int) Math.round(getConfig().boneMealBase + (getLevelPercent(level) * getConfig().boneMealFactor))); + } + + private int getReadyBonusBoneMeal(int level) { + return Math.max(0, (int) Math.round(getConfig().readyBonusBoneMealBase + (getLevelPercent(level) * getConfig().readyBonusBoneMealFactor))); + } + + private int getItemsPerBoneMeal(int level) { + return Math.max(1, (int) Math.round(getConfig().itemsPerBoneMealBase - (getLevelPercent(level) * getConfig().itemsPerBoneMealReduction))); + } + + private double getValuableChance(int level) { + return Math.min(getConfig().maxValuableChance, getConfig().valuableChanceBase + (getLevelPercent(level) * getConfig().valuableChanceFactor)); + } + + private int getValuableRolls(int level) { + return Math.max(0, (int) Math.round(getConfig().valuableRollsBase + (getLevelPercent(level) * getConfig().valuableRollsFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click a composter to process nearby drops, crops, leaves, and your own compostables.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 5.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 12.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Items Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxItemsBase = 80.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Items Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxItemsFactor = 240.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fill Chance Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fillChanceBase = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fill Chance Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fillChanceFactor = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Fill Chance for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxFillChance = 0.98; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Leaf Compost Bursts Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double leafCompostBurstsBase = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Leaf Compost Bursts Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double leafCompostBurstsFactor = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Leaf Fill Chance Multiplier Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double leafFillChanceMultiplierBase = 1.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Leaf Fill Chance Multiplier Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double leafFillChanceMultiplierFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 36.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Reduction for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksReduction = 28.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bone Meal Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double boneMealBase = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bone Meal Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double boneMealFactor = 6.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ready Bonus Bone Meal Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double readyBonusBoneMealBase = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ready Bonus Bone Meal Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double readyBonusBoneMealFactor = 8.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Items Per Bone Meal Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double itemsPerBoneMealBase = 20.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Items Per Bone Meal Reduction for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double itemsPerBoneMealReduction = 14.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Valuable Chance Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double valuableChanceBase = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Valuable Chance Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double valuableChanceFactor = 0.09; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Valuable Chance for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxValuableChance = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Valuable Rolls Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double valuableRollsBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Valuable Rolls Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double valuableRollsFactor = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Item Consumed for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerItemConsumed = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Level Gain for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerLevelGain = 2.8; + } + + private static class CompostState { + private int compostLevel; + private int processed; + private int consumed; + private int levelGains; + + private CompostState(int compostLevel) { + this.compostLevel = compostLevel; + this.processed = 0; + this.consumed = 0; + this.levelGains = 0; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCraftableCobweb.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCraftableCobweb.java new file mode 100644 index 000000000..1241349cb --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCraftableCobweb.java @@ -0,0 +1,120 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class HerbalismCraftableCobweb extends SimpleAdaptation { + + public HerbalismCraftableCobweb() { + super("herbalism-cobweb"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.cobweb.description")); + setDisplayName(Localizer.dLocalize("herbalism.cobweb.name")); + setIcon(Material.STRING); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(17771); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shaped() + .key("herbalism-cobwebBlock") + .ingredient(new MaterialChar('I', Material.STRING)) + .shapes(List.of( + "III", + "III", + "III")) + .result(new ItemStack(Material.COBWEB, 1)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COBWEB) + .key("challenge_herbalism_cobweb_100") + .title(Localizer.dLocalize("advancement.challenge_herbalism_cobweb_100.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_cobweb_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_cobweb_100", "herbalism.cobweb.cobwebs-crafted", 100, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.cobweb.lore1")); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(CraftItemEvent e) { + if (!(e.getWhoClicked() instanceof Player p) || !hasActiveAdaptation(p)) { + return; + } + if (e.getRecipe() instanceof org.bukkit.inventory.ShapedRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && recipe.getKey().getKey().equals("herbalism-cobwebBlock")) { + getPlayer(p).getData().addStat("herbalism.cobweb.cobwebs-crafted", 1); + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Cobwebs from String in a Crafting Table.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCraftableMushroomBlocks.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCraftableMushroomBlocks.java new file mode 100644 index 000000000..ca7ddb480 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismCraftableMushroomBlocks.java @@ -0,0 +1,137 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class HerbalismCraftableMushroomBlocks extends SimpleAdaptation { + + public HerbalismCraftableMushroomBlocks() { + super("herbalism-mushroom-blocks"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.mushroom_blocks.description")); + setDisplayName(Localizer.dLocalize("herbalism.mushroom_blocks.name")); + setIcon(Material.BROWN_MUSHROOM_BLOCK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(17772); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shaped() + .key("herbalism-redmushblock") + .ingredient(new MaterialChar('I', Material.RED_MUSHROOM)) + .shapes(List.of( + "II", + "II")) + .result(new ItemStack(Material.RED_MUSHROOM_BLOCK, 1)) + .build()); + registerRecipe(AdaptRecipe.shaped() + .key("herbalism-brownmushblock") + .ingredient(new MaterialChar('I', Material.BROWN_MUSHROOM)) + .shapes(List.of( + "II", + "II")) + .result(new ItemStack(Material.BROWN_MUSHROOM_BLOCK, 1)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("herbalism-mushstemred") + .ingredient(Material.RED_MUSHROOM_BLOCK) + .result(new ItemStack(Material.MUSHROOM_STEM, 1)) + .build()); + registerRecipe(AdaptRecipe.shapeless() + .key("herbalism-mushstembrown") + .ingredient(Material.BROWN_MUSHROOM_BLOCK) + .result(new ItemStack(Material.MUSHROOM_STEM, 1)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.RED_MUSHROOM_BLOCK) + .key("challenge_herbalism_mushroom_100") + .title(Localizer.dLocalize("advancement.challenge_herbalism_mushroom_100.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_mushroom_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_mushroom_100", "herbalism.mushroom-blocks.crafted", 100, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.mushroom_blocks.lore1")); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(CraftItemEvent e) { + if (!(e.getWhoClicked() instanceof Player p) || !hasActiveAdaptation(p)) { + return; + } + if (e.getRecipe() instanceof org.bukkit.inventory.ShapedRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && (recipe.getKey().getKey().equals("herbalism-redmushblock") || recipe.getKey().getKey().equals("herbalism-brownmushblock"))) { + getPlayer(p).getData().addStat("herbalism.mushroom-blocks.crafted", 1); + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Mushroom Blocks from Mushrooms in a Crafting Table.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismDropToInventory.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismDropToInventory.java new file mode 100644 index 000000000..cf015ba29 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismDropToInventory.java @@ -0,0 +1,126 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; + +import java.util.List; + +public class HerbalismDropToInventory extends SimpleAdaptation { + public HerbalismDropToInventory() { + super("herbalism-drop-to-inventory"); + registerConfiguration(HerbalismDropToInventory.Config.class); + setDescription(Localizer.dLocalize("pickaxe.drop_to_inventory.description")); + setDisplayName(Localizer.dLocalize("herbalism.drop_to_inventory.name")); + setIcon(Material.HOPPER); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(7999); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_herbalism_dti_10k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_dti_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_dti_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_dti_10k", "herbalism.drop-to-inv.items-caught", 10000, 500); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("pickaxe.drop_to_inventory.lore1")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDropItemEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + if (!hasActiveAdaptation(p)) { + return; + } + if (p.getGameMode() != GameMode.SURVIVAL) { + return; + } + if (ItemListings.toolHoes.contains(p.getInventory().getItemInMainHand().getType())) { + List items = new KList<>(e.getItems()); + e.getItems().clear(); + for (Item i : items) { + sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); + xp(p, 2); + getPlayer(p).getData().addStat("herbalism.drop-to-inv.items-caught", 1); + if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { + p.getWorld().dropItem(p.getLocation(), i.getItemStack()); + } + } + } + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Harvested crops drop directly into your inventory.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismGrowthAura.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismGrowthAura.java new file mode 100644 index 000000000..373a9d31a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismGrowthAura.java @@ -0,0 +1,200 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import art.arcane.volmlib.util.math.RNG; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.concurrent.ThreadLocalRandom; + +public class HerbalismGrowthAura extends SimpleAdaptation { + public HerbalismGrowthAura() { + super("herbalism-growth-aura"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.growth_aura.description")); + setDisplayName(Localizer.dLocalize("herbalism.growth_aura.name")); + setIcon(Material.BONE_MEAL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(850); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WHEAT) + .key("challenge_herbalism_growth_1k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_growth_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_growth_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.HAY_BLOCK) + .key("challenge_herbalism_growth_25k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_growth_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_growth_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_herbalism_growth_1k", "herbalism.growth-aura.blocks-grown", 1000, 300); + registerMilestone("challenge_herbalism_growth_25k", "herbalism.growth-aura.blocks-grown", 25000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.growth_aura.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getStrength(level), 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.growth_aura.lore2")); + v.addLore(C.YELLOW + "+ " + Form.f(getFoodCost(getLevelPercent(level)), 2) + C.GRAY + " " + Localizer.dLocalize("herbalism.growth_aura.lore3")); + } + + private double getRadius(double factor) { + return factor * getConfig().radiusFactor; + } + + private double getStrength(int level) { + return level * getConfig().strengthFactor; + } + + private double getFoodCost(double factor) { + return M.lerp(1D - factor, getConfig().maxFoodCost, getConfig().minFoodCost); + } + + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + try { + if (hasActiveAdaptation(p)) { + double rad = getRadius(getLevelPercent(p)); + double strength = getStrength(getLevel(p)); + ThreadLocalRandom random = ThreadLocalRandom.current(); + double angle = Math.toRadians(random.nextDouble(360D)); + double foodCost = getFoodCost(getLevelPercent(p)); + + + for (int i = 0; i < Math.min(Math.min(rad * rad, 256), 3); i++) { + Location m = p.getLocation().clone().add(new Vector(Math.sin(angle), RNG.r.i(-1, 1), Math.cos(angle)).multiply(random.nextDouble(rad))); + Block a = m.getBlock(); + if (getConfig().surfaceOnly) { + int max = a.getWorld().getHighestBlockYAt(m); + + if (max + 1 != a.getY()) + continue; + } + + SoundPlayer spw = SoundPlayer.of(a.getWorld()); + if (a.getBlockData() instanceof Ageable) { + Ageable ab = (Ageable) a.getBlockData(); + int toGrowLeft = ab.getMaximumAge() - ab.getAge(); + + if (toGrowLeft > 0) { + int add = (int) Math.max(1, Math.min(strength, toGrowLeft)); + AdaptPlayer player = getPlayer(p); + if (ab.getMaximumAge() > ab.getAge() && player.canConsumeFood(foodCost, 10)) { + while (add-- > 0) { + J.runEntity(p, () -> { + if (!p.isOnline() + || !player.consumeFood(foodCost, 10) + || !(a.getBlockData() instanceof Ageable aab) + || aab.getAge() == aab.getMaximumAge()) + return; + + aab.setAge(aab.getAge() + 1); + a.setBlockData(aab, true); + getPlayer(p).getData().addStat("herbalism.growth-aura.blocks-grown", 1); + spw.play(a.getLocation(), Sound.BLOCK_CHORUS_FLOWER_DEATH, 0.25f, RNG.r.f(0.3f, 0.7f)); + if (areParticlesEnabled()) { + p.spawnParticle(Particles.VILLAGER_HAPPY, a.getLocation().clone().add(0.5, 0.5, 0.5), 3, 0.3, 0.3, 0.3, 0.9); + } +// xp(p, 1); // JESUS THIS IS FUCKING BUSTED + }, RNG.r.i(30, 60)); + } + } + } + + + } + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Grow nature around you in an aura at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Herbalism Growth Aura adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Surface Only for the Herbalism Growth Aura adaptation.", impact = "True enables this behavior and false disables it.") + boolean surfaceOnly = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.325; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Food Cost for the Herbalism Growth Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minFoodCost = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Food Cost for the Herbalism Growth Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxFoodCost = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Herbalism Growth Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Strength Factor for the Herbalism Growth Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double strengthFactor = 0.75; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismHungryHippo.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismHungryHippo.java new file mode 100644 index 000000000..461b37804 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismHungryHippo.java @@ -0,0 +1,117 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerItemConsumeEvent; + +public class HerbalismHungryHippo extends SimpleAdaptation { + + public HerbalismHungryHippo() { + super("herbalism-hippo"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.hippo.description")); + setDisplayName(Localizer.dLocalize("herbalism.hippo.name")); + setIcon(Material.POTATO); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(8111); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_APPLE) + .key("challenge_herbalism_hippo_500") + .title(Localizer.dLocalize("advancement.challenge_herbalism_hippo_500.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_hippo_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_hippo_500", "herbalism.hungry-hippo.bonus-saturation", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ (" + (2 + level) + C.GRAY + " + " + Localizer.dLocalize("herbalism.hippo.lore1")); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void on(PlayerItemConsumeEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + if (!hasActiveAdaptation(p)) { + return; + } + if (ItemListings.getFood().contains(e.getItem().getType())) { + p.setFoodLevel(p.getFoodLevel() + 2 + getLevel(p)); + sp.play(p.getLocation(), Sound.BLOCK_POINTED_DRIPSTONE_LAND, 1, 0.25f); + vfxFastRing(p.getLocation().add(0, 0.25, 0), 2, Color.GREEN); + getPlayer(p).getData().addStat("herbalism.hungry-hippo.bonus-saturation", 2 + getLevel(p)); + xp(p, 5); + } + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Consuming food gives you more saturation.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismHungryShield.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismHungryShield.java new file mode 100644 index 000000000..edb40533a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismHungryShield.java @@ -0,0 +1,132 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; + +public class HerbalismHungryShield extends SimpleAdaptation { + + public HerbalismHungryShield() { + super("herbalism-hungry-shield"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.hungry_shield.description")); + setDisplayName(Localizer.dLocalize("herbalism.hungry_shield.name")); + setIcon(Material.APPLE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(875); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BREAD) + .key("challenge_herbalism_shield_500") + .title(Localizer.dLocalize("advancement.challenge_herbalism_shield_500.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_shield_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GOLDEN_APPLE) + .key("challenge_herbalism_shield_5k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_shield_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_shield_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_herbalism_shield_500", "herbalism.hungry-shield.damage-absorbed", 500, 400); + registerMilestone("challenge_herbalism_shield_5k", "herbalism.hungry-shield.damage-absorbed", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getEffectiveness(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.hungry_shield.lore1")); + } + + + @Override + public void onTick() { + + } + + private double getEffectiveness(double factor) { + return Math.min(getConfig().maxEffectiveness, factor * factor + getConfig().effectivenessBase); + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof Player p && hasActiveAdaptation(p)) { + double f = getEffectiveness(getLevelPercent(p)); + double h = e.getDamage() * f; + double d = e.getDamage() - h; + + if (getPlayer(p).consumeFood(h, 6)) { + d += h; + e.setDamage(d); + getPlayer(p).getData().addStat("herbalism.hungry-shield.damage-absorbed", (int) Math.ceil(h)); + xp(p, d); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Take damage to your hunger before your health.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.78; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effectiveness Base for the Herbalism Hungry Shield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double effectivenessBase = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Effectiveness for the Herbalism Hungry Shield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxEffectiveness = 0.95; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismLuck.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismLuck.java new file mode 100644 index 000000000..800e046ed --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismLuck.java @@ -0,0 +1,154 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Materials; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.concurrent.ThreadLocalRandom; + +public class HerbalismLuck extends SimpleAdaptation { + + public HerbalismLuck() { + super("herbalism-luck"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.luck.description")); + setDisplayName(Localizer.dLocalize("herbalism.luck.name")); + setIcon(Material.EMERALD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(8121); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.RABBIT_FOOT) + .key("challenge_herbalism_luck_100") + .title(Localizer.dLocalize("advancement.challenge_herbalism_luck_100.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_luck_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.EMERALD) + .key("challenge_herbalism_luck_2500") + .title(Localizer.dLocalize("advancement.challenge_herbalism_luck_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_luck_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_herbalism_luck_100", "herbalism.luck.lucky-drops", 100, 300); + registerMilestone("challenge_herbalism_luck_2500", "herbalism.luck.lucky-drops", 2500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.luck.lore0")); + v.addLore(C.GREEN + "+ (" + (getEffectiveness(level)) + C.GRAY + "%) + " + Localizer.dLocalize("herbalism.luck.lore1")); + v.addLore(C.GREEN + "+ (" + (getEffectiveness(level)) + C.GRAY + "%) + " + Localizer.dLocalize("herbalism.luck.lore2")); + } + + private double getEffectiveness(double factor) { + return Math.min(getConfig().highChance, factor * factor + getConfig().lowChance); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void on(BlockDropItemEvent e) { + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + Block broken = e.getBlock(); + if (broken.getType() == Materials.GRASS || broken.getType() == Material.TALL_GRASS) { + double d = ThreadLocalRandom.current().nextDouble(100D); + Material m = ItemListings.getHerbalLuckSeeds().getRandom(); + if (d < getEffectiveness(getLevel(p))) { + xp(p, 100); + getPlayer(p).getData().addStat("herbalism.luck.lucky-drops", 1); + ItemStack luckDrop = new ItemStack(m, 1); + e.getBlock().getWorld().dropItem(e.getBlock().getLocation(), luckDrop); + } + } + + if (ItemListings.getFlowers().contains(broken.getType())) { + double d = ThreadLocalRandom.current().nextDouble(100D); + Material m = ItemListings.getHerbalLuckFood().getRandom(); + if (d < getEffectiveness(getLevel(p))) { + xp(p, 100); + getPlayer(p).getData().addStat("herbalism.luck.lucky-drops", 1); + ItemStack luckDrop = new ItemStack(m, 1); + e.getBlock().getWorld().dropItem(e.getBlock().getLocation(), luckDrop); + } + } + + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Breaking Grass or Flowers has a chance to drop random items.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Low Chance for the Herbalism Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lowChance = 0.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls High Chance for the Herbalism Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double highChance = 90; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismMyconid.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismMyconid.java new file mode 100644 index 000000000..b73dfefa4 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismMyconid.java @@ -0,0 +1,115 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +public class HerbalismMyconid extends SimpleAdaptation { + + public HerbalismMyconid() { + super("herbalism-myconid"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.myconid.description")); + setDisplayName(Localizer.dLocalize("herbalism.myconid.name")); + setIcon(Material.MYCELIUM); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(17771); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shapeless() + .key("herbalism-dirt-myconid") + .ingredient(Material.DIRT) + .ingredient(Material.RED_MUSHROOM) + .ingredient(Material.BROWN_MUSHROOM) + .result(new ItemStack(Material.MYCELIUM, 1)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.MYCELIUM) + .key("challenge_herbalism_myconid_100") + .title(Localizer.dLocalize("advancement.challenge_herbalism_myconid_100.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_myconid_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_myconid_100", "herbalism.myconid.mycelium-crafted", 100, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.myconid.lore1")); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(CraftItemEvent e) { + if (!(e.getWhoClicked() instanceof Player p) || !hasActiveAdaptation(p)) { + return; + } + if (e.getRecipe() instanceof org.bukkit.inventory.ShapelessRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && recipe.getKey().getKey().equals("herbalism-dirt-myconid")) { + getPlayer(p).getData().addStat("herbalism.myconid.mycelium-crafted", 1); + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Mycelium from Dirt and Mushrooms.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismReplant.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismReplant.java new file mode 100644 index 000000000..c81dcf4db --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismReplant.java @@ -0,0 +1,250 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.content.skill.SkillHerbalism; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.data.Cuboid; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.Collection; + +public class HerbalismReplant extends SimpleAdaptation { + + public HerbalismReplant() { + super("herbalism-replant"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.replant.description")); + setDisplayName(Localizer.dLocalize("herbalism.replant.name")); + setIcon(Material.PUMPKIN_SEEDS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(6090); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WHEAT_SEEDS) + .key("challenge_herbalism_replant_500") + .title(Localizer.dLocalize("advancement.challenge_herbalism_replant_500.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_replant_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.COMPOSTER) + .key("challenge_herbalism_replant_25k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_replant_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_replant_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_herbalism_replant_500", "herbalism.replant.crops-replanted", 500, 300); + registerMilestone("challenge_herbalism_replant_25k", "herbalism.replant.crops-replanted", 25000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getRadius(level) + C.GRAY + Localizer.dLocalize("herbalism.replant.lore1")); + } + + + private int getCooldown(double factor, int level) { + if (level == 1) { + return (int) getConfig().cooldownLvl1; + } + + return (int) ((getConfig().baseCooldown - (getConfig().cooldownFactor * factor)) + getConfig().bonusCooldown); + } + + private float getRadius(int lvl) { + return lvl - getConfig().radiusSub; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if ((action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) || e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + int lvl = getLevel(p); + if (lvl <= 0) { + return; + } + + Block target = e.getClickedBlock(); + if (target == null) { + target = p.getTargetBlockExact(5); + } + + if (target == null || !(target.getBlockData() instanceof Ageable)) { + return; + } + + ItemStack right = p.getInventory().getItemInMainHand(); + ItemStack left = p.getInventory().getItemInOffHand(); + + if (isTool(left) && isHoe(left) && !p.hasCooldown(left.getType())) { + damageOffHand(p, 1 + ((lvl - 1) * 7)); + p.setCooldown(left.getType(), getCooldown(getLevelPercent(p), getLevel(p))); + } else if (isTool(right) && isHoe(right) && !p.hasCooldown(right.getType())) { + damageHand(p, 1 + ((lvl - 1) * 7)); + p.setCooldown(right.getType(), getCooldown(getLevelPercent(p), getLevel(p))); + } else { + return; + } + + if (lvl > 1) { + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + Cuboid c = new Cuboid(target.getLocation().clone().add(0.5, 0.5, 0.5)); + c = c.expand(Cuboid.CuboidDirection.Up, (int) Math.floor(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.Down, (int) Math.floor(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.North, Math.round(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.South, Math.round(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.East, Math.round(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.West, Math.round(getRadius(lvl))); + + for (Block i : c) { + J.runEntity(p, () -> hit(p, i), M.irand(1, 6)); + } + spw.play(p.getLocation(), Sound.ITEM_SHOVEL_FLATTEN, 1f, 0.66f); + spw.play(p.getLocation(), Sound.BLOCK_BAMBOO_SAPLING_BREAK, 1f, 0.66f); + if (areParticlesEnabled()) { + p.spawnParticle(Particles.VILLAGER_HAPPY, p.getLocation().clone().add(0.5, 0.5, 0.5), getLevel(p) * 3, 0.3 * getLevel(p), 0.3 * getLevel(p), 0.3 * getLevel(p), 0.9); + } + } else { + hit(p, target); + } + } + + private void hit(Player p, Block b) { + if (b != null && b.getBlockData() instanceof Ageable aa && hasActiveAdaptation(p)) { + if (aa.getAge() != aa.getMaximumAge()) { + return; + } + + if (!canBlockBreak(p, b.getLocation()) || !canBlockPlace(p, b.getLocation())) { + return; + } + + xp(p, b.getLocation().clone().add(0.5, 0.5, 0.5), ((SkillHerbalism.Config) getSkill().getConfig()).harvestPerAgeXP * aa.getAge()); + xp(p, b.getLocation().clone().add(0.5, 0.5, 0.5), ((SkillHerbalism.Config) getSkill().getConfig()).plantCropSeedsXP); + PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("herbalism"); + PlayerAdaptation adaptation = line != null ? line.getAdaptation("herbalism-drop-to-inventory") : null; + if (adaptation != null && adaptation.getLevel() > 0) { + Collection items = b.getDrops(); + SoundPlayer sp = SoundPlayer.of(p); + for (ItemStack i : items) { + sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); + i.setAmount(1); + if (!p.getInventory().addItem(i).isEmpty()) { + p.getWorld().dropItem(p.getLocation(), i); + } + } + aa.setAge(0); + J.runAt(b.getLocation(), () -> b.setBlockData(aa, true)); + + } else { + p.breakBlock(b); + } + + aa.setAge(0); + J.runAt(b.getLocation(), () -> b.setBlockData(aa, true)); + + getPlayer(p).getData().addStat("harvest.blocks", 1); + getPlayer(p).getData().addStat("harvest.planted", 1); + getPlayer(p).getData().addStat("herbalism.replant.crops-replanted", 1); + + if (M.r(1D / (double) getLevel(p))) { + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(b.getLocation(), Sound.ITEM_CROP_PLANT, 1f, 0.7f); + } + } + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Right-click a crop with a hoe to harvest and replant it.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Herbalism Replant adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.95; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Lvl1 for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownLvl1 = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Cooldown for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseCooldown = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Factor for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownFactor = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Cooldown for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusCooldown = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Sub for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int radiusSub = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismRootedFooting.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismRootedFooting.java new file mode 100644 index 000000000..de579d890 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismRootedFooting.java @@ -0,0 +1,172 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +public class HerbalismRootedFooting extends SimpleAdaptation { + public HerbalismRootedFooting() { + super("herbalism-rooted-footing"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.rooted_footing.description")); + setDisplayName(Localizer.dLocalize("herbalism.rooted_footing.name")); + setIcon(Material.FARMLAND); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2050); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FARMLAND) + .key("challenge_herbalism_rooted_500") + .title(Localizer.dLocalize("advancement.challenge_herbalism_rooted_500.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_rooted_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_rooted_500", "herbalism.rooted-footing.farmland-saved", 500, 300); + } + + @Override + public void addStats(int level, Element v) { + double absorb = getFallAbsorb(level); + v.addLore(C.GREEN + "+ " + Form.pc(absorb, 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.rooted_footing.lore1")); + v.addLore(C.YELLOW + "* " + getConfig().foodPerDamage + C.GRAY + " " + Localizer.dLocalize("herbalism.rooted_footing.lore2")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("herbalism.rooted_footing.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + + if (e.getAction() != Action.PHYSICAL || e.getClickedBlock() == null || !(e.getPlayer() instanceof Player p)) { + return; + } + + if (!hasActiveAdaptation(p)) { + return; + } + + if (e.getClickedBlock().getType() == Material.FARMLAND) { + e.setCancelled(true); + getPlayer(p).getData().addStat("herbalism.rooted-footing.farmland-saved", 1); + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p) || e.getCause() != EntityDamageEvent.DamageCause.FALL) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0 || !isNatureGround(p)) { + return; + } + + double absorbCap = e.getDamage() * getFallAbsorb(level); + int foodRequired = (int) Math.ceil(absorbCap * getConfig().foodPerDamage); + if (foodRequired <= 0 || p.getFoodLevel() <= 0) { + return; + } + + int usableFood = Math.min(p.getFoodLevel(), foodRequired); + double absorbed = usableFood / getConfig().foodPerDamage; + if (absorbed <= 0) { + return; + } + + p.setFoodLevel(Math.max(0, p.getFoodLevel() - usableFood)); + e.setDamage(Math.max(0, e.getDamage() - absorbed)); + if (e.getDamage() <= 0.01) { + e.setCancelled(true); + } + } + + private boolean isNatureGround(Player p) { + Block under = p.getLocation().clone().add(0, -1, 0).getBlock(); + Material type = under.getType(); + return type == Material.FARMLAND + || type == Material.GRASS_BLOCK + || type == Material.MOSS_BLOCK + || type == Material.MYCELIUM + || type == Material.DIRT + || type == Material.ROOTED_DIRT; + } + + private double getFallAbsorb(int level) { + return Math.min(getConfig().maxAbsorbPercent, getConfig().absorbBase + (getLevelPercent(level) * getConfig().absorbFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Protect farmland and convert fall damage into hunger on natural ground.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Absorb Base for the Herbalism Rooted Footing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double absorbBase = 0.28; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Absorb Factor for the Herbalism Rooted Footing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double absorbFactor = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Absorb Percent for the Herbalism Rooted Footing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxAbsorbPercent = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Food Per Damage for the Herbalism Rooted Footing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double foodPerDamage = 1.8; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismSeedSower.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismSeedSower.java new file mode 100644 index 000000000..e13e4aefd --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismSeedSower.java @@ -0,0 +1,248 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +public class HerbalismSeedSower extends SimpleAdaptation { + public HerbalismSeedSower() { + super("herbalism-seed-sower"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.seed_sower.description")); + setDisplayName(Localizer.dLocalize("herbalism.seed_sower.name")); + setIcon(Material.WHEAT_SEEDS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(6920); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WHEAT_SEEDS) + .key("challenge_herbalism_seed_1k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_seed_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_seed_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.FARMLAND) + .key("challenge_herbalism_seed_25k") + .title(Localizer.dLocalize("advancement.challenge_herbalism_seed_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_seed_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_herbalism_seed_1k", "herbalism.seed-sower.seeds-planted", 1000, 300); + registerMilestone("challenge_herbalism_seed_25k", "herbalism.seed-sower.seeds-planted", 25000, 1000); + } + + @Override + public void addStats(int level, Element v) { + double factor = getLevelPercent(level); + v.addLore(C.GREEN + "+ " + getRadius(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.seed_sower.lore1")); + v.addLore(C.GREEN + "+ " + getMaxCrops(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.seed_sower.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(factor) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("herbalism.seed_sower.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if ((action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) || e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!p.isSneaking() || !hasActiveAdaptation(p)) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isItem(hand)) { + return; + } + + Material seedType = hand.getType(); + Material cropType = getCropType(seedType); + if (cropType == null || p.hasCooldown(seedType)) { + return; + } + + Block origin = e.getClickedBlock(); + if (origin == null) { + origin = p.getTargetBlockExact(5); + } + + if (origin == null) { + return; + } + + int planted = plantNearby(p, origin, hand, seedType, cropType, getRadius(getLevel(p)), getMaxCrops(getLevel(p))); + if (planted <= 0) { + return; + } + + e.setCancelled(true); + p.setCooldown(seedType, getCooldownTicks(getLevelPercent(p))); + getPlayer(p).getData().addStat("harvest.planted", planted); + getPlayer(p).getData().addStat("herbalism.seed-sower.seeds-planted", planted); + xp(p, planted * getConfig().xpPerCrop); + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.ITEM_CROP_PLANT, 0.6f, 1.25f); + if (planted > 4) { + sp.play(p.getLocation(), Sound.BLOCK_ROOTED_DIRT_PLACE, 0.5f, 1.35f); + } + } + + private int plantNearby(Player p, Block origin, ItemStack seeds, Material seedType, Material cropType, int radius, int maxCrops) { + int planted = 0; + int available = p.getGameMode() == GameMode.CREATIVE ? Integer.MAX_VALUE : seeds.getAmount(); + int y = origin.getY(); + + for (int x = -radius; x <= radius && planted < maxCrops; x++) { + for (int z = -radius; z <= radius && planted < maxCrops; z++) { + if (available <= 0) { + break; + } + + Block base = origin.getWorld().getBlockAt(origin.getX() + x, y, origin.getZ() + z); + if (!isValidBase(seedType, base.getType())) { + continue; + } + + Block crop = base.getRelative(0, 1, 0); + if (!crop.isEmpty() || !canBlockPlace(p, crop.getLocation())) { + continue; + } + + crop.setType(cropType); + planted++; + available--; + } + } + + if (p.getGameMode() != GameMode.CREATIVE && planted > 0) { + seeds.setAmount(Math.max(0, seeds.getAmount() - planted)); + p.getInventory().setItemInMainHand(seeds.getAmount() <= 0 ? new ItemStack(Material.AIR) : seeds); + } + + return planted; + } + + private boolean isValidBase(Material seedType, Material base) { + if (seedType == Material.NETHER_WART) { + return base == Material.SOUL_SAND; + } + + return base == Material.FARMLAND; + } + + private Material getCropType(Material seedType) { + return switch (seedType) { + case WHEAT_SEEDS -> Material.WHEAT; + case CARROT -> Material.CARROTS; + case POTATO -> Material.POTATOES; + case BEETROOT_SEEDS -> Material.BEETROOTS; + case MELON_SEEDS -> Material.MELON_STEM; + case PUMPKIN_SEEDS -> Material.PUMPKIN_STEM; + case TORCHFLOWER_SEEDS -> Material.TORCHFLOWER_CROP; + case NETHER_WART -> Material.NETHER_WART; + default -> null; + }; + } + + private int getRadius(int level) { + return Math.max(1, (int) Math.round(getConfig().baseRadius + (getLevelPercent(level) * getConfig().radiusFactor))); + } + + private int getMaxCrops(int level) { + return Math.max(1, (int) Math.round(getConfig().baseCropCount + (getLevelPercent(level) * getConfig().cropCountFactor))); + } + + private int getCooldownTicks(double factor) { + return Math.max(2, (int) Math.round(getConfig().cooldownTicksBase - (factor * getConfig().cooldownTicksReduction))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click with seeds to plant nearby farmland and soul-sand plots.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.675; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Radius for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseRadius = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Crop Count for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseCropCount = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Crop Count Factor for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cropCountFactor = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Reduction for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksReduction = 42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Crop for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerCrop = 1.45; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismSporeBloom.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismSporeBloom.java new file mode 100644 index 000000000..6edb2552e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismSporeBloom.java @@ -0,0 +1,588 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class HerbalismSporeBloom extends SimpleAdaptation { + private final Map cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + + public HerbalismSporeBloom() { + super("herbalism-spore-bloom"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.spore_bloom.description")); + setDisplayName(Localizer.dLocalize("herbalism.spore_bloom.name")); + setIcon(Material.RED_MUSHROOM_BLOCK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2100); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BROWN_MUSHROOM) + .key("challenge_herbalism_spore_500") + .title(Localizer.dLocalize("advancement.challenge_herbalism_spore_500.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_spore_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_spore_500", "herbalism.spore-bloom.blocks-spread", 500, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getBloomAttempts(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.spore_bloom.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getBloomRadius(level)) + C.GRAY + " " + Localizer.dLocalize("herbalism.spore_bloom.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("herbalism.spore_bloom.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPlaceEvent e) { + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + if (getActiveLevel(e.getPlayer()) <= 0 || !e.getPlayer().isSneaking()) { + return; + } + + if (!isSporeItem(e.getItemInHand())) { + return; + } + + Block floor = e.getBlockPlaced().getRelative(0, -1, 0); + if (!isBloomFloor(floor.getType())) { + return; + } + + // Place-trigger activation: sneak-place mushroom on valid floor to bloom. + e.setCancelled(true); + attemptBloom(e.getPlayer(), floor, e.getItemInHand().getType()); + } + + private void startBloom(org.bukkit.entity.Player player, Block center, Material catalyst, Material spreadSurface, int level) { + List path = buildSpiderPath(center, getBloomRadius(level), getSpokes(level), getBloomAttempts(level), getGuaranteedReach(level)); + if (path.isEmpty()) { + return; + } + + int intervalTicks = Math.max(1, getSpreadIntervalTicks(level)); + int[] cursor = {0}; + int[] totalChanged = {0}; + Runnable[] bloomTask = new Runnable[1]; + bloomTask[0] = () -> { + if (!player.isOnline() || center.getWorld() == null) { + return; + } + + int pulseChanged = 0; + int batch = getBlocksPerPulse(level); + for (int i = 0; i < batch && cursor[0] < path.size(); i++) { + pulseChanged += spreadAt(player, path.get(cursor[0]++), catalyst, spreadSurface); + } + + if (pulseChanged > 0) { + totalChanged[0] += pulseChanged; + if (areParticlesEnabled()) { + center.getWorld().spawnParticle(Particle.SPORE_BLOSSOM_AIR, center.getLocation().add(0.5, 1.0, 0.5), 8, 0.55, 0.2, 0.55, 0.02); + } + if (areParticlesEnabled()) { + center.getWorld().spawnParticle(Particle.CRIMSON_SPORE, center.getLocation().add(0.5, 1.0, 0.5), 8, 0.55, 0.2, 0.55, 0.01); + } + SoundPlayer sp = SoundPlayer.of(center.getWorld()); + sp.play(center.getLocation().add(0.5, 0.5, 0.5), Sound.BLOCK_FUNGUS_PLACE, 0.45f, 0.75f); + sp.play(center.getLocation().add(0.5, 0.5, 0.5), Sound.ENTITY_ENDERMAN_AMBIENT, 0.22f, 0.45f + ThreadLocalRandom.current().nextFloat() * 0.45f); + } + + if (cursor[0] >= path.size()) { + if (totalChanged[0] > 0) { + getPlayer(player).getData().addStat("herbalism.spore-bloom.blocks-spread", totalChanged[0]); + xp(player, totalChanged[0] * getConfig().xpPerMushroomPlaced); + } + return; + } + + J.runAt(center.getLocation(), bloomTask[0], intervalTicks); + }; + J.runAt(center.getLocation(), bloomTask[0]); + } + + private List buildSpiderPath(Block center, double radius, int spokes, int max, int guaranteedReach) { + int r = Math.max(1, (int) Math.ceil(radius)); + int sectors = Math.max(8, Math.min(48, Math.max(1, spokes) * 3)); + double maxDistance = radius + 0.35D; + double maxDistanceSq = maxDistance * maxDistance; + List out = new ArrayList<>(Math.max(8, max)); + Set seen = new HashSet<>(); + out.add(center); + seen.add(key(center)); + + if (out.size() >= max) { + return out; + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + double offset = random.nextDouble(Math.PI * 2D); + + int forcedReach = Math.max(0, Math.min(r, guaranteedReach)); + for (int step = 1; step <= forcedReach; step++) { + addRingSamples(center, out, seen, step, sectors, offset, maxDistanceSq, max); + if (out.size() >= max) { + return out; + } + } + + for (int step = 1; step <= r; step++) { + addRingSamples(center, out, seen, step, sectors, offset, maxDistanceSq, max); + if (out.size() >= max) { + return out; + } + + // Offset pass fills sector rounding gaps so rings look uniform. + addRingSamples(center, out, seen, step, sectors, offset + (Math.PI / sectors), maxDistanceSq, max); + if (out.size() >= max) { + return out; + } + } + + fillRemainingFromCircle(center, out, seen, r, offset, maxDistanceSq, max); + return out; + } + + private void addRingSamples(Block center, List out, Set seen, int step, int sectors, double offset, double maxDistanceSq, int max) { + for (int i = 0; i < sectors && out.size() < max; i++) { + double angle = offset + ((Math.PI * 2D) * i / sectors); + int dx = (int) Math.round(Math.cos(angle) * step); + int dz = (int) Math.round(Math.sin(angle) * step); + if (dx == 0 && dz == 0) { + continue; + } + + if ((dx * dx) + (dz * dz) > maxDistanceSq) { + continue; + } + + Block block = center.getRelative(dx, 0, dz); + if (seen.add(key(block))) { + out.add(block); + } + } + } + + private void fillRemainingFromCircle(Block center, List out, Set seen, int r, double offset, double maxDistanceSq, int max) { + List candidates = new ArrayList<>(); + for (int dx = -r; dx <= r; dx++) { + for (int dz = -r; dz <= r; dz++) { + if (dx == 0 && dz == 0) { + continue; + } + + if ((dx * dx) + (dz * dz) > maxDistanceSq) { + continue; + } + + candidates.add(new int[]{dx, dz}); + } + } + + candidates.sort(Comparator.comparingInt(v -> (v[0] * v[0]) + (v[1] * v[1])) + .thenComparingDouble(v -> normalizeAngle(Math.atan2(v[1], v[0]) - offset))); + + for (int[] c : candidates) { + if (out.size() >= max) { + return; + } + + Block block = center.getRelative(c[0], 0, c[1]); + if (seen.add(key(block))) { + out.add(block); + } + } + } + + private double normalizeAngle(double angle) { + double out = angle % (Math.PI * 2D); + return out < 0 ? out + (Math.PI * 2D) : out; + } + + private int spreadAt(org.bukkit.entity.Player player, Block floor, Material catalyst, Material spreadSurface) { + int changed = 0; + Block ground = resolveTopSurfaceSoil(floor); + if (ground == null) { + return 0; + } + Block above = ground.getRelative(0, 1, 0); + + if (spreadSurface != null && isConvertibleSoil(ground.getType()) && ground.getType() != spreadSurface + && canBlockBreak(player, ground.getLocation()) && canBlockPlace(player, ground.getLocation())) { + ground.setType(spreadSurface, false); + changed++; + } + + if (getConfig().swapFlowersToMushrooms && isFlower(above.getType())) { + Material replacement = getFlowerReplacement(above.getType(), catalyst); + if (replacement != null && above.getType() != replacement + && canBlockBreak(player, above.getLocation()) && canBlockPlace(player, above.getLocation())) { + above.setType(replacement, false); + changed++; + } + } + + return changed; + } + + private Block resolveTopSurfaceSoil(Block sample) { + int x = sample.getX(); + int z = sample.getZ(); + int highestY = sample.getWorld().getHighestBlockYAt(x, z); + int minY = sample.getWorld().getMinHeight(); + + for (int y = highestY; y >= minY; y--) { + Block block = sample.getWorld().getBlockAt(x, y, z); + if (!isConvertibleSoil(block.getType())) { + continue; + } + + Block above = block.getRelative(0, 1, 0); + if (above.getType().isAir() || isReplaceablePlant(above.getType()) || isFlower(above.getType())) { + return block; + } + } + + return null; + } + + private boolean isBloomFloor(Material type) { + return type == Material.MYCELIUM || type == Material.PODZOL; + } + + private boolean isSporeItem(ItemStack hand) { + return isItem(hand) && (hand.getType() == Material.RED_MUSHROOM || hand.getType() == Material.BROWN_MUSHROOM); + } + + private boolean consumeOne(ItemStack hand) { + if (hand.getAmount() <= 0) { + return false; + } + + hand.setAmount(hand.getAmount() - 1); + return true; + } + + private boolean attemptBloom(org.bukkit.entity.Player player, Block center, Material catalyst) { + Material spreadSurface = resolveSpreadSurface(center.getType()); + if (spreadSurface == null) { + return false; + } + + int level = getActiveLevel(player); + long now = System.currentTimeMillis(); + long ready = cooldowns.getOrDefault(player.getUniqueId(), 0L); + if (now < ready) { + SoundPlayer.of(center.getWorld()).play(center.getLocation().add(0.5, 0.5, 0.5), Sound.BLOCK_NOTE_BLOCK_BASS, 0.5f, 0.75f); + return false; + } + + if (player.getFoodLevel() < getFoodCost(level)) { + SoundPlayer.of(center.getWorld()).play(center.getLocation().add(0.5, 0.5, 0.5), Sound.BLOCK_NOTE_BLOCK_BASS, 0.5f, 0.75f); + return false; + } + + if (!consumeCatalystFromMainHandIfPresent(player, catalyst)) { + return false; + } + + player.setFoodLevel(Math.max(0, player.getFoodLevel() - getFoodCost(level))); + cooldowns.put(player.getUniqueId(), now + getCooldownMillis(level)); + + if (areParticlesEnabled()) { + center.getWorld().spawnParticle(Particle.SPORE_BLOSSOM_AIR, center.getLocation().add(0.5, 1.0, 0.5), 30, 0.35, 0.15, 0.35, 0.01); + } + SoundPlayer.of(center.getWorld()).play(center.getLocation().add(0.5, 0.5, 0.5), Sound.ENTITY_ENDERMAN_AMBIENT, 0.45f, 0.55f); + startBloom(player, center, catalyst, spreadSurface, level); + return true; + } + + private boolean consumeCatalystFromMainHandIfPresent(org.bukkit.entity.Player player, Material catalyst) { + ItemStack held = player.getInventory().getItemInMainHand(); + if (!isItem(held)) { + // Some server flows decrement the placed stack before cancelled placement is finalized. + // In that case, allow activation without double-consuming. + return true; + } + + if (held.getType() != catalyst) { + return true; + } + + return consumeOne(held); + } + + private Material resolveSpreadSurface(Material floorType) { + if (floorType == Material.MYCELIUM) { + return Material.MYCELIUM; + } + + if (floorType == Material.PODZOL) { + return Material.PODZOL; + } + + return null; + } + + private int getGuaranteedReach(int level) { + return level >= 5 ? 6 : 0; + } + + private boolean isConvertibleSoil(Material type) { + return type == Material.DIRT + || type == Material.GRASS_BLOCK + || type == Material.COARSE_DIRT + || type == Material.ROOTED_DIRT + || type == Material.MYCELIUM + || type == Material.PODZOL; + } + + private boolean isFlower(Material type) { + String n = type.name(); + return n.endsWith("_FLOWER") + || n.endsWith("_TULIP") + || type == Material.DANDELION + || type == Material.POPPY + || type == Material.BLUE_ORCHID + || type == Material.ALLIUM + || type == Material.AZURE_BLUET + || type == Material.OXEYE_DAISY + || type == Material.CORNFLOWER + || type == Material.LILY_OF_THE_VALLEY + || type == Material.WITHER_ROSE + || type == Material.SUNFLOWER + || type == Material.LILAC + || type == Material.ROSE_BUSH + || type == Material.PEONY + || type == Material.TORCHFLOWER + || type == Material.PINK_PETALS + || type == Material.SPORE_BLOSSOM; + } + + private Material getFlowerReplacement(Material flower, Material catalyst) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + if (isWarmFlower(flower)) { + // Warm flowers lean red. + return random.nextDouble() <= 0.7 ? Material.RED_MUSHROOM : Material.BROWN_MUSHROOM; + } + + if (isCoolFlower(flower)) { + // Cool flowers lean brown. + return random.nextDouble() <= 0.7 ? Material.BROWN_MUSHROOM : Material.RED_MUSHROOM; + } + + // Fallback uses catalyst flavor. + return catalyst == Material.BROWN_MUSHROOM ? Material.BROWN_MUSHROOM : Material.RED_MUSHROOM; + } + + private boolean isWarmFlower(Material flower) { + return flower == Material.DANDELION + || flower == Material.POPPY + || flower == Material.RED_TULIP + || flower == Material.ORANGE_TULIP + || flower == Material.PINK_TULIP + || flower == Material.SUNFLOWER + || flower == Material.ROSE_BUSH + || flower == Material.PEONY + || flower == Material.WITHER_ROSE + || flower == Material.TORCHFLOWER + || flower == Material.PINK_PETALS; + } + + private boolean isCoolFlower(Material flower) { + return flower == Material.BLUE_ORCHID + || flower == Material.ALLIUM + || flower == Material.AZURE_BLUET + || flower == Material.WHITE_TULIP + || flower == Material.OXEYE_DAISY + || flower == Material.CORNFLOWER + || flower == Material.LILY_OF_THE_VALLEY + || flower == Material.LILAC + || flower == Material.SPORE_BLOSSOM; + } + + private boolean isWoodLike(Material type) { + String n = type.name(); + return n.endsWith("_LOG") || n.endsWith("_WOOD") || n.endsWith("_STEM") || n.endsWith("_HYPHAE") || n.endsWith("_PLANKS"); + } + + private boolean isReplaceablePlant(Material type) { + if (type == Material.RED_MUSHROOM || type == Material.BROWN_MUSHROOM || type == Material.CRIMSON_FUNGUS || type == Material.WARPED_FUNGUS) { + return true; + } + + String n = type.name(); + return n.endsWith("_FLOWER") + || n.endsWith("_TULIP") + || n.endsWith("_SAPLING") + || n.endsWith("_SEEDS") + || n.endsWith("_ROOTS") + || n.endsWith("_CROP") + || n.equals("TALL_GRASS") + || n.equals("GRASS") + || n.equals("FERN") + || n.equals("LARGE_FERN") + || n.equals("WHEAT") + || n.equals("CARROTS") + || n.equals("POTATOES") + || n.equals("BEETROOTS") + || n.equals("NETHER_WART") + || n.equals("TORCHFLOWER_CROP") + || n.equals("PITCHER_CROP"); + } + + private String key(Block block) { + return block.getX() + ":" + block.getY() + ":" + block.getZ(); + } + + private int getBloomAttempts(int level) { + double scaled = getConfig().bloomAttemptsBase + (getLevelPercent(level) * getConfig().bloomAttemptsFactor); + double perLevel = Math.max(0, level - 1) * getConfig().bloomAttemptsPerLevel; + return Math.max(1, (int) Math.round(scaled + perLevel)); + } + + private double getBloomRadius(int level) { + double radius = getConfig().bloomRadiusBase + (getLevelPercent(level) * getConfig().bloomRadiusFactor); + if (level >= 5) { + radius = Math.max(6D, radius); + } + return radius; + } + + private int getSpokes(int level) { + return Math.max(4, (int) Math.round(getConfig().spokesBase + (getLevelPercent(level) * getConfig().spokesFactor))); + } + + private int getBlocksPerPulse(int level) { + return Math.max(1, (int) Math.round(getConfig().blocksPerPulseBase + (getLevelPercent(level) * getConfig().blocksPerPulseFactor))); + } + + private int getSpreadIntervalTicks(int level) { + return Math.max(1, (int) Math.round(getConfig().spreadIntervalTicksBase - (getLevelPercent(level) * getConfig().spreadIntervalTicksFactor))); + } + + private int getFoodCost(int level) { + return Math.max(1, (int) Math.round(getConfig().foodCostBase - (getLevelPercent(level) * getConfig().foodCostFactor))); + } + + private long getCooldownMillis(int level) { + return Math.max(250L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click mycelium with mushrooms to spread an outward spore-web that mutates nearby growth.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Convert Wood To Hyphae for the Herbalism Spore Bloom adaptation.", impact = "True enables this behavior and false disables it.") + boolean convertWoodToHyphae = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows flowers hit by the bloom to be replaced with mushrooms.", impact = "Disable this to keep flowers untouched while still converting soil into mushroom blocks.") + boolean swapFlowersToMushrooms = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Branch Chance for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double branchChance = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mushroom Choices for the Herbalism Spore Bloom adaptation.", impact = "Changing this alters the identifier or text used by the feature.") + String[] mushroomChoices = {"RED_MUSHROOM", "BROWN_MUSHROOM", "CRIMSON_FUNGUS", "WARPED_FUNGUS"}; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bloom Attempts Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bloomAttemptsBase = 26; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bloom Attempts Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bloomAttemptsFactor = 58; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional bloom attempts granted each adaptation level.", impact = "Higher values make each level spread across more total blocks.") + double bloomAttemptsPerLevel = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bloom Radius Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bloomRadiusBase = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bloom Radius Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bloomRadiusFactor = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spokes Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double spokesBase = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spokes Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double spokesFactor = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Blocks Per Pulse Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double blocksPerPulseBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Blocks Per Pulse Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double blocksPerPulseFactor = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spread Interval Ticks Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double spreadIntervalTicksBase = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spread Interval Ticks Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double spreadIntervalTicksFactor = 1.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Food Cost Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double foodCostBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Food Cost Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double foodCostFactor = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 1700; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 1100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mushroom Placed for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerMushroomPlaced = 1.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismTerralid.java b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismTerralid.java new file mode 100644 index 000000000..80f5166de --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/herbalism/HerbalismTerralid.java @@ -0,0 +1,120 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.herbalism; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class HerbalismTerralid extends SimpleAdaptation { + + public HerbalismTerralid() { + super("herbalism-terralid"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("herbalism.terralid.description")); + setDisplayName(Localizer.dLocalize("herbalism.terralid.name")); + setIcon(Material.GRASS_BLOCK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(17771); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shaped() + .key("herbalism-dirt-terralid") + .ingredient(new MaterialChar('S', Material.WHEAT_SEEDS)) + .ingredient(new MaterialChar('D', Material.DIRT)) + .shapes(List.of( + "SSS", + "DDD")) + .result(new ItemStack(Material.GRASS_BLOCK, 3)) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GRASS_BLOCK) + .key("challenge_herbalism_terralid_200") + .title(Localizer.dLocalize("advancement.challenge_herbalism_terralid_200.title")) + .description(Localizer.dLocalize("advancement.challenge_herbalism_terralid_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_herbalism_terralid_200", "herbalism.terralid.grass-crafted", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.terralid.lore1")); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(CraftItemEvent e) { + if (!(e.getWhoClicked() instanceof Player p) || !hasActiveAdaptation(p)) { + return; + } + if (e.getRecipe() instanceof org.bukkit.inventory.ShapedRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && recipe.getKey().getKey().equals("herbalism-dirt-terralid")) { + getPlayer(p).getData().addStat("herbalism.terralid.grass-crafted", 1); + } + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft Grass Blocks from Seeds and Dirt.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterAdrenaline.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterAdrenaline.java new file mode 100644 index 000000000..43be2baa2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterAdrenaline.java @@ -0,0 +1,132 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public class HunterAdrenaline extends SimpleAdaptation { + public HunterAdrenaline() { + super("hunter-adrenaline"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.adrenaline.description")); + setDisplayName(Localizer.dLocalize("hunter.adrenaline.name")); + setIcon(Material.LEATHER_HELMET); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1911); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_hunter_adrenaline_100") + .title(Localizer.dLocalize("advancement.challenge_hunter_adrenaline_100.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_adrenaline_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_hunter_adrenaline_2500") + .title(Localizer.dLocalize("advancement.challenge_hunter_adrenaline_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_adrenaline_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_hunter_adrenaline_100", "hunter.adrenaline.low-health-kills", 100, 400); + registerMilestone("challenge_hunter_adrenaline_2500", "hunter.adrenaline.low-health-kills", 2500, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getDamage(level), 0) + C.GRAY + " " + Localizer.dLocalize("hunter.adrenaline.lore1")); + } + + private double getDamage(int level) { + return ((getLevelPercent(level) * getConfig().damageFactor) + getConfig().damageBase); + } + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + double damageMax = getDamage(attack.level()); + double hpp = p.getHealth() / p.getMaxHealth(); + + if (hpp >= 1) { + return; + } + + damageMax *= (1D - hpp); + e.setDamage(e.getDamage() * (damageMax + 1D)); + getPlayer(p).getData().addStat("hunter.adrenaline.low-health-kills", 1); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Deal more melee damage the lower your health is.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Base for the Hunter Adrenaline adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageBase = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Hunter Adrenaline adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageFactor = 0.21; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterDropToInventory.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterDropToInventory.java new file mode 100644 index 000000000..302757191 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterDropToInventory.java @@ -0,0 +1,151 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.List; + +public class HunterDropToInventory extends SimpleAdaptation { + public HunterDropToInventory() { + super("hunter-drop-to-inventory"); + registerConfiguration(HunterDropToInventory.Config.class); + setDescription(Localizer.dLocalize("hunter.drop_to_inventory.description")); + setDisplayName(Localizer.dLocalize("hunter.drop_to_inventory.name")); + setIcon(Material.TRAPPED_CHEST); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(18440); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_hunter_dti_10k") + .title(Localizer.dLocalize("advancement.challenge_hunter_dti_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_dti_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_hunter_dti_10k", "hunter.drop-to-inv.items-caught", 10000, 500); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("hunter.drop_to_inventory.lore1")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDropItemEvent e) { + + Player p = e.getPlayer(); + if (resolveInteractContext(p, e.getBlock().getLocation(), null, true) == null + || !canPVP(p, e.getBlock().getLocation())) { + return; + } + + SoundPlayer sp = SoundPlayer.of(p); + if (ItemListings.toolSwords.contains(p.getInventory().getItemInMainHand().getType())) { + List items = new KList<>(e.getItems()); + e.getItems().clear(); + sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); + for (Item i : items) { + if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { + p.getWorld().dropItem(p.getLocation(), i.getItemStack()); + } + } + getPlayer(p).getData().addStat("hunter.drop-to-inv.items-caught", items.size()); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDeathEvent e) { + LivingEntity k = e.getEntity(); + if (k.getKiller() == null || k.getKiller().getType() != EntityType.PLAYER) { + return; + } + Player p = k.getKiller(); + if (e.getEntity() instanceof Player || getActiveDamageLevel(p, e.getEntity()) <= 0) { + return; + } + if (e.getEntity().getKiller() != null && e.getEntity().getKiller().getClass().getSimpleName().equals("CraftPlayer")) { + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); + int itemCount = e.getDrops().size(); + e.getDrops().forEach(i -> { + if (!p.getInventory().addItem(i).isEmpty()) { + p.getWorld().dropItem(p.getLocation(), i); + } + }); + e.getDrops().clear(); + getPlayer(p).getData().addStat("hunter.drop-to-inv.items-caught", itemCount); + } + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Mob and block drops teleport directly into your inventory.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterInvis.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterInvis.java new file mode 100644 index 000000000..4bb8f3565 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterInvis.java @@ -0,0 +1,157 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; + +public class HunterInvis extends SimpleAdaptation { + public HunterInvis() { + super("hunter-invis"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.invisibility.description")); + setDisplayName(Localizer.dLocalize("hunter.invisibility.name")); + setIcon(Material.TROPICAL_FISH_BUCKET); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(9444); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLASS) + .key("challenge_hunter_invis_200") + .title(Localizer.dLocalize("advancement.challenge_hunter_invis_200.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_invis_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_hunter_invis_200", "hunter.invis.activations", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("hunter.invisibility.lore1")); + v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.invisibility.lore2")); + v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.invisibility.lore3")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.invisibility.lore4")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.invisibility.lore5")); + v.addLore(C.RED + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.penalty.lore1")); + v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); + + } + + + @EventHandler + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasActiveAdaptation(p)) { + if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { + return; + } + if (!getConfig().useConsumable) { + if (p.getFoodLevel() == 0) { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + } else { + addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); + addPotionStacks(p, PotionEffectType.INVISIBILITY, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.invis.activations", 1); + } + } else { + if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { + Material mat = Material.getMaterial(getConfig().consumable); + if (mat != null && p.getInventory().contains(mat)) { + p.getInventory().removeItem(new ItemStack(mat, 1)); + addPotionStacks(p, PotionEffectType.INVISIBILITY, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.invis.activations", 1); + } else { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + } + } + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain invisibility when struck, at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") + boolean useConsumable = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") + boolean poisonPenalty = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackHungerPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackPoisonPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackBuff = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Invis adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseEffectbyLevel = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Invis adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerFromLevel = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Invis adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerDuration = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Invis adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int basePoisonFromLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Invis adaptation.", impact = "Changing this alters the identifier or text used by the feature.") + String consumable = "ROTTEN_FLESH"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterJumpBoost.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterJumpBoost.java new file mode 100644 index 000000000..2f80ef87f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterJumpBoost.java @@ -0,0 +1,159 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; + +public class HunterJumpBoost extends SimpleAdaptation { + public HunterJumpBoost() { + super("hunter-jumpboost"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.jump_boost.description")); + setDisplayName(Localizer.dLocalize("hunter.jump_boost.name")); + setIcon(Material.PUFFERFISH_BUCKET); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(9544); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.RABBIT_FOOT) + .key("challenge_hunter_jump_200") + .title(Localizer.dLocalize("advancement.challenge_hunter_jump_200.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_jump_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_hunter_jump_200", "hunter.jump-boost.activations", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("hunter.jump_boost.lore1")); + v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.jump_boost.lore2")); + v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.jump_boost.lore3")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.jump_boost.lore4")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.jump_boost.lore5")); + v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); + + } + + + @EventHandler + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasActiveAdaptation(p)) { + if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { + return; + } + + if (!getConfig().useConsumable) { + if (p.getFoodLevel() == 0) { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + + } else { + addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); + addPotionStacks(p, PotionEffectTypes.JUMP, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.jump-boost.activations", 1); + } + } else { + if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { + Material mat = Material.getMaterial(getConfig().consumable); + if (mat != null && p.getInventory().contains(mat)) { + p.getInventory().removeItem(new ItemStack(mat, 1)); + addPotionStacks(p, PotionEffectTypes.JUMP, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.jump-boost.activations", 1); + } else { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + } + } + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain jump boost when struck, at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") + boolean useConsumable = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") + boolean poisonPenalty = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackHungerPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackPoisonPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackBuff = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Jump Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseEffectbyLevel = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Jump Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerFromLevel = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Jump Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerDuration = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Jump Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int basePoisonFromLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Jump Boost adaptation.", impact = "Changing this alters the identifier or text used by the feature.") + String consumable = "ROTTEN_FLESH"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterLuck.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterLuck.java new file mode 100644 index 000000000..bf966c720 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterLuck.java @@ -0,0 +1,160 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; + +public class HunterLuck extends SimpleAdaptation { + public HunterLuck() { + super("hunter-luck"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.luck.description")); + setDisplayName(Localizer.dLocalize("hunter.luck.name")); + setIcon(Material.TADPOLE_BUCKET); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(9644); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EMERALD) + .key("challenge_hunter_luck_200") + .title(Localizer.dLocalize("advancement.challenge_hunter_luck_200.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_luck_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_hunter_luck_200", "hunter.luck.activations", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("hunter.luck.lore1")); + v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.luck.lore2")); + v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.luck.lore3")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.luck.lore4")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.luck.lore5")); + v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); + + } + + + @EventHandler + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasActiveAdaptation(p)) { + if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { + return; + } + + if (!getConfig().useConsumable) { + if (p.getFoodLevel() == 0) { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + addPotionStacks(p, PotionEffectType.UNLUCK, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + + } else { + addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); + addPotionStacks(p, PotionEffectType.LUCK, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.luck.activations", 1); + } + } else { + if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { + Material mat = Material.getMaterial(getConfig().consumable); + if (mat != null && p.getInventory().contains(mat)) { + p.getInventory().removeItem(new ItemStack(mat, 1)); + addPotionStacks(p, PotionEffectType.LUCK, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.luck.activations", 1); + } else { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + addPotionStacks(p, PotionEffectType.UNLUCK, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + } + } + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain luck when struck, at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") + boolean useConsumable = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") + boolean poisonPenalty = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackHungerPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackPoisonPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackBuff = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseEffectbyLevel = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerFromLevel = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerDuration = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int basePoisonFromLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Luck adaptation.", impact = "Changing this alters the identifier or text used by the feature.") + String consumable = "ROTTEN_FLESH"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterRegen.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterRegen.java new file mode 100644 index 000000000..c8fd0b4ef --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterRegen.java @@ -0,0 +1,158 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; + +public class HunterRegen extends SimpleAdaptation { + public HunterRegen() { + super("hunter-regen"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.regen.description")); + setDisplayName(Localizer.dLocalize("hunter.regen.name")); + setIcon(Material.AXOLOTL_BUCKET); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(9744); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_APPLE) + .key("challenge_hunter_regen_500") + .title(Localizer.dLocalize("advancement.challenge_hunter_regen_500.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_regen_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_hunter_regen_500", "hunter.regen.health-regened", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("hunter.regen.lore1")); + v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.regen.lore2")); + v.addLore(C.RED + "- " + (getConfig().basePoisonFromLevel - level) + C.GRAY + Localizer.dLocalize("hunter.regen.lore3")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.regen.lore4")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.regen.lore5")); + v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); + + } + + + @EventHandler + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasActiveAdaptation(p)) { + if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { + return; + } + + if (!getConfig().useConsumable) { + if (p.getFoodLevel() == 0) { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + + } else { + addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); + addPotionStacks(p, PotionEffectType.REGENERATION, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.regen.health-regened", 1); + } + } else { + if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { + Material mat = Material.getMaterial(getConfig().consumable); + if (mat != null && p.getInventory().contains(mat)) { + p.getInventory().removeItem(new ItemStack(mat, 1)); + addPotionStacks(p, PotionEffectType.REGENERATION, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.regen.health-regened", 1); + } else { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + } + } + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain regeneration when struck, at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") + boolean useConsumable = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") + boolean poisonPenalty = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackHungerPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackPoisonPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackBuff = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Regen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseEffectbyLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Regen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerFromLevel = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Regen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerDuration = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Regen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int basePoisonFromLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Regen adaptation.", impact = "Changing this alters the identifier or text used by the feature.") + String consumable = "ROTTEN_FLESH"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterResistance.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterResistance.java new file mode 100644 index 000000000..247817d20 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterResistance.java @@ -0,0 +1,159 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; + +public class HunterResistance extends SimpleAdaptation { + public HunterResistance() { + super("hunter-resistance"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.resistance.description")); + setDisplayName(Localizer.dLocalize("hunter.resistance.name")); + setIcon(Material.POWDER_SNOW_BUCKET); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(9844); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_CHESTPLATE) + .key("challenge_hunter_resistance_500") + .title(Localizer.dLocalize("advancement.challenge_hunter_resistance_500.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_resistance_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_hunter_resistance_500", "hunter.resistance.activations", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("hunter.resistance.lore1")); + v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.resistance.lore2")); + v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.resistance.lore3")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.resistance.lore4")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.resistance.lore5")); + v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); + + } + + + @EventHandler + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasActiveAdaptation(p)) { + if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { + return; + } + + if (!getConfig().useConsumable) { + if (p.getFoodLevel() == 0) { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + + } else { + addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); + addPotionStacks(p, PotionEffectTypes.DAMAGE_RESISTANCE, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.resistance.activations", 1); + } + } else { + if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { + Material mat = Material.getMaterial(getConfig().consumable); + if (mat != null && p.getInventory().contains(mat)) { + p.getInventory().removeItem(new ItemStack(mat, 1)); + addPotionStacks(p, PotionEffectTypes.DAMAGE_RESISTANCE, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.resistance.activations", 1); + } else { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + } + } + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain resistance when struck, at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") + boolean useConsumable = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") + boolean poisonPenalty = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackHungerPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackPoisonPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackBuff = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Resistance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseEffectbyLevel = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Resistance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerFromLevel = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Resistance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerDuration = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Resistance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int basePoisonFromLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Resistance adaptation.", impact = "Changing this alters the identifier or text used by the feature.") + String consumable = "ROTTEN_FLESH"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterSpeed.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterSpeed.java new file mode 100644 index 000000000..22a95d012 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterSpeed.java @@ -0,0 +1,322 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.math.VelocitySpeed; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class HunterSpeed extends SimpleAdaptation { + private final Map speedBursts = new java.util.concurrent.ConcurrentHashMap<>(); + + public HunterSpeed() { + super("hunter-speed"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.speed.description")); + setDisplayName(Localizer.dLocalize("hunter.speed.name")); + setIcon(Material.SUGAR); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(getConfig().setInterval); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SUGAR) + .key("challenge_hunter_speed_200") + .title(Localizer.dLocalize("advancement.challenge_hunter_speed_200.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_speed_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_hunter_speed_200", "hunter.speed.activations", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("hunter.speed.lore1")); + v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.speed.lore2")); + v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.speed.lore3")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.speed.lore4")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.speed.lore5")); + v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); + + } + + + @EventHandler + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasActiveAdaptation(p)) { + if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { + return; + } + + if (!getConfig().useConsumable) { + if (p.getFoodLevel() == 0) { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + + } else { + addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); + grantSpeedBurst(p, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.speed.activations", 1); + } + } else { + if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { + Material mat = Material.getMaterial(getConfig().consumable); + if (mat != null && p.getInventory().contains(mat)) { + p.getInventory().removeItem(new ItemStack(mat, 1)); + grantSpeedBurst(p, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.speed.activations", 1); + } else { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + } + } + } + } + } + + @EventHandler + public void on(PlayerQuitEvent e) { + speedBursts.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler + public void on(PlayerDeathEvent e) { + speedBursts.remove(e.getEntity().getUniqueId()); + } + + private void grantSpeedBurst(org.bukkit.entity.Player p, int amplifier, int durationTicks, boolean overlap) { + if (durationTicks <= 0) { + return; + } + + UUID id = p.getUniqueId(); + long now = System.currentTimeMillis(); + long durationMs = Math.max(50L, durationTicks * 50L); + SpeedBurst current = speedBursts.get(id); + if (current != null && current.expiresAt > now) { + if (!overlap) { + return; + } + + current.expiresAt += durationMs; + current.amplifier = Math.max(current.amplifier, amplifier); + return; + } + + speedBursts.put(id, new SpeedBurst(now + durationMs, amplifier)); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + org.bukkit.entity.Player p = adaptPlayer.getPlayer(); + SpeedBurst burst = speedBursts.get(p.getUniqueId()); + if (burst == null) { + continue; + } + + if (burst.expiresAt <= now) { + invalidateBurst(p, burst, false); + speedBursts.remove(p.getUniqueId()); + continue; + } + + if (!isVelocityEligible(p)) { + invalidateBurst(p, burst, true); + continue; + } + + VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); + if (!input.hasHorizontal()) { + brakeBurst(p, burst); + continue; + } + + applyBurst(p, burst, input); + } + } + + private void applyBurst(org.bukkit.entity.Player p, SpeedBurst burst, VelocitySpeed.InputSnapshot input) { + Vector desiredDirection = VelocitySpeed.resolveHorizontalDirection(p, input); + if (desiredDirection.lengthSquared() <= VelocitySpeed.EPSILON) { + brakeBurst(p, burst); + return; + } + + double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, + Math.max(0, getConfig().baseHorizontalSpeed * VelocitySpeed.speedAmplifierScalar(burst.amplifier))); + Vector velocity = p.getVelocity(); + Vector currentHorizontal = VelocitySpeed.horizontalOnly(velocity); + Vector targetHorizontal = desiredDirection.multiply(targetSpeed); + Vector nextHorizontal = VelocitySpeed.moveTowards(currentHorizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); + nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); + VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); + burst.boosting = true; + } + + private void invalidateBurst(org.bukkit.entity.Player p, SpeedBurst burst, boolean invalidState) { + if (!burst.boosting) { + return; + } + + if (invalidState && getConfig().hardStopOnInvalidState) { + VelocitySpeed.hardStopHorizontal(p); + } + + burst.boosting = false; + } + + private void brakeBurst(org.bukkit.entity.Player p, SpeedBurst burst) { + if (!burst.boosting) { + return; + } + + Vector velocity = p.getVelocity(); + Vector currentHorizontal = VelocitySpeed.horizontalOnly(velocity); + double stopThreshold = Math.max(0, getConfig().stopThreshold); + if (currentHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { + VelocitySpeed.hardStopHorizontal(p); + burst.boosting = false; + return; + } + + Vector nextHorizontal = VelocitySpeed.moveTowards(currentHorizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); + if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { + VelocitySpeed.hardStopHorizontal(p); + burst.boosting = false; + return; + } + + VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); + } + + private boolean isVelocityEligible(org.bukkit.entity.Player p) { + GameMode mode = p.getGameMode(); + if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { + return false; + } + + return !p.isDead() && !p.isFlying() && !p.isGliding() && !p.isSwimming() && p.getVehicle() == null; + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private static class SpeedBurst { + private long expiresAt; + private int amplifier; + private boolean boosting; + + private SpeedBurst(long expiresAt, int amplifier) { + this.expiresAt = expiresAt; + this.amplifier = amplifier; + } + + } + + @NoArgsConstructor + @ConfigDescription("Gain speed when struck, at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Tick interval (ms) used to update velocity speed bursts.", impact = "Lower values feel more responsive but run updates more frequently.") + long setInterval = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") + boolean useConsumable = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") + boolean poisonPenalty = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackHungerPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackPoisonPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackBuff = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Speed adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseEffectbyLevel = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Speed adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerDuration = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Speed adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerFromLevel = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Speed adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int basePoisonFromLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for hunter bursts before amplifier scaling.", impact = "Higher values increase movement speed while a burst is active.") + double baseHorizontalSpeed = 0.13; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent runaway momentum.") + double maxHorizontalSpeed = 0.32; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the burst target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") + double accelPerTick = 0.045; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast velocity decays when movement input is released.", impact = "Higher values reduce carry momentum more aggressively.") + double brakePerTick = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny momentum longer.") + double stopThreshold = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "If true, burst velocity is force-cleared when entering invalid states.", impact = "Prevents retained speed when state changes skip expected flow.") + boolean hardStopOnInvalidState = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") + double fallbackInputVelocityThreshold = 0.0008; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Speed adaptation.", impact = "Changing this alters the identifier or text used by the feature.") + String consumable = "ROTTEN_FLESH"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + + double fallbackInputVelocityThresholdSquared() { + double threshold = Math.max(0, fallbackInputVelocityThreshold); + return threshold * threshold; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterStrength.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterStrength.java new file mode 100644 index 000000000..0a3f0cae9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterStrength.java @@ -0,0 +1,159 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; + +public class HunterStrength extends SimpleAdaptation { + public HunterStrength() { + super("hunter-strength"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.strength.description")); + setDisplayName(Localizer.dLocalize("hunter.strength.name")); + setIcon(Material.COD_BUCKET); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(9044); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BLAZE_POWDER) + .key("challenge_hunter_strength_200") + .title(Localizer.dLocalize("advancement.challenge_hunter_strength_200.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_strength_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_hunter_strength_200", "hunter.strength.activations", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("hunter.strength.lore1")); + v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.strength.lore2")); + v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.strength.lore3")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.strength.lore4")); + v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.strength.lore5")); + v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); + + } + + + @EventHandler + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasActiveAdaptation(p)) { + if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { + return; + } + + if (!getConfig().useConsumable) { + if (p.getFoodLevel() == 0) { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + + } else { + addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); + addPotionStacks(p, PotionEffectTypes.INCREASE_DAMAGE, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.strength.activations", 1); + } + } else { + if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { + Material mat = Material.getMaterial(getConfig().consumable); + if (mat != null && p.getInventory().contains(mat)) { + p.getInventory().removeItem(new ItemStack(mat, 1)); + addPotionStacks(p, PotionEffectTypes.INCREASE_DAMAGE, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); + getPlayer(p).getData().addStat("hunter.strength.activations", 1); + } else { + if (getConfig().poisonPenalty) { + addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); + } + } + } + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain strength when struck, at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") + boolean useConsumable = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") + boolean poisonPenalty = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackHungerPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackPoisonPenalty = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") + boolean stackBuff = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Strength adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseEffectbyLevel = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Strength adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerFromLevel = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Strength adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int basePoisonFromLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Strength adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseHungerDuration = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Strength adaptation.", impact = "Changing this alters the identifier or text used by the feature.") + String consumable = "ROTTEN_FLESH"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterTrophySkinner.java b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterTrophySkinner.java new file mode 100644 index 000000000..0c817d429 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/hunter/HunterTrophySkinner.java @@ -0,0 +1,239 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.hunter; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.concurrent.ThreadLocalRandom; + +public class HunterTrophySkinner extends SimpleAdaptation { + public HunterTrophySkinner() { + super("hunter-trophy-skinner"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("hunter.trophy_skinner.description")); + setDisplayName(Localizer.dLocalize("hunter.trophy_skinner.name")); + setIcon(Material.ZOMBIE_HEAD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SKELETON_SKULL) + .key("challenge_hunter_trophy_50") + .title(Localizer.dLocalize("advancement.challenge_hunter_trophy_50.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_trophy_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ZOMBIE_HEAD) + .key("challenge_hunter_trophy_heads_100") + .title(Localizer.dLocalize("advancement.challenge_hunter_trophy_heads_100.title")) + .description(Localizer.dLocalize("advancement.challenge_hunter_trophy_heads_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_hunter_trophy_50", "hunter.trophy-skinner.trophies-collected", 50, 400); + registerMilestone("challenge_hunter_trophy_heads_100", "hunter.trophy-skinner.heads-collected", 100, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getDropChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("hunter.trophy_skinner.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getHeadChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("hunter.trophy_skinner.lore2")); + v.addLore(C.YELLOW + "* " + Form.f(getMinimumRange(level), 1) + C.GRAY + " " + Localizer.dLocalize("hunter.trophy_skinner.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + Player killer = e.getEntity().getKiller(); + if (killer == null || !hasActiveAdaptation(killer) || e.getEntity() instanceof Player || !canDamageTarget(killer, e.getEntity())) { + return; + } + + int level = getActiveLevel(killer); + PrecisionContext precision = readPrecisionContext(e, killer, level); + if (!precision.precise()) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getDropChance(level)) { + return; + } + + ItemStack trophy = buildTrophyDrop(e.getEntityType(), level, precision.projectileKill()); + if (trophy != null) { + e.getDrops().add(trophy); + getPlayer(killer).getData().addStat("hunter.trophy-skinner.trophies-collected", 1); + } + + if (ThreadLocalRandom.current().nextDouble() <= getHeadChance(level)) { + ItemStack head = buildHeadDrop(e.getEntityType()); + if (head != null) { + e.getDrops().add(head); + getPlayer(killer).getData().addStat("hunter.trophy-skinner.heads-collected", 1); + } + } + + SoundPlayer.of(killer).play(killer.getLocation(), Sound.ENTITY_WOLF_SHAKE, 0.55f, 1.35f); + xp(killer, getConfig().xpPerTrophy); + } + + private PrecisionContext readPrecisionContext(EntityDeathEvent e, Player killer, int level) { + boolean projectileKill = false; + double range = 0; + if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent damageByEntity) { + if (damageByEntity.getDamager() instanceof Projectile projectile && projectile.getShooter() == killer) { + projectileKill = true; + } + } + + if (killer.getWorld() == e.getEntity().getWorld()) { + range = killer.getLocation().distance(e.getEntity().getLocation()); + } + + boolean rangedPrecision = projectileKill && range >= getMinimumRange(level); + boolean stealthPrecision = killer.isSneaking(); + return new PrecisionContext(projectileKill, rangedPrecision || stealthPrecision); + } + + private ItemStack buildTrophyDrop(EntityType type, int level, boolean projectileKill) { + Material material = switch (type) { + case CREEPER -> Material.GUNPOWDER; + case SKELETON, BOGGED, WITHER_SKELETON, STRAY -> Material.BONE; + case ZOMBIE, ZOMBIFIED_PIGLIN, HUSK, DROWNED -> Material.ROTTEN_FLESH; + case SPIDER, CAVE_SPIDER -> Material.STRING; + case BLAZE -> Material.BLAZE_POWDER; + case ENDERMAN -> Material.ENDER_PEARL; + case WITCH -> Material.REDSTONE; + case PIGLIN, PIGLIN_BRUTE, HOGLIN, ZOGLIN -> Material.PORKCHOP; + default -> Material.LEATHER; + }; + + int amount = Math.max(1, (int) Math.round(getConfig().trophyAmountBase + (getLevelPercent(level) * getConfig().trophyAmountFactor))); + if (projectileKill) { + amount += 1; + } + + return new ItemStack(material, Math.min(8, amount)); + } + + private ItemStack buildHeadDrop(EntityType type) { + Material material = switch (type) { + case CREEPER -> Material.CREEPER_HEAD; + case SKELETON, STRAY, BOGGED -> Material.SKELETON_SKULL; + case WITHER_SKELETON -> Material.WITHER_SKELETON_SKULL; + case ZOMBIE, HUSK, DROWNED, ZOMBIFIED_PIGLIN -> Material.ZOMBIE_HEAD; + case PIGLIN, PIGLIN_BRUTE -> Material.PIGLIN_HEAD; + default -> null; + }; + + return material == null ? null : new ItemStack(material); + } + + private double getDropChance(int level) { + return Math.min(getConfig().maxDropChance, getConfig().dropChanceBase + (getLevelPercent(level) * getConfig().dropChanceFactor)); + } + + private double getHeadChance(int level) { + return Math.min(getConfig().maxHeadChance, getConfig().headChanceBase + (getLevelPercent(level) * getConfig().headChanceFactor)); + } + + private double getMinimumRange(int level) { + return Math.max(4, getConfig().minimumRangeBase - (getLevelPercent(level) * getConfig().minimumRangeFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Precision kills can grant bonus trophy drops and occasional heads from elite targets.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Drop Chance Base for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dropChanceBase = 0.14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Drop Chance Factor for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dropChanceFactor = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Maximum Drop Chance for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxDropChance = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Head Chance Base for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double headChanceBase = 0.015; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Head Chance Factor for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double headChanceFactor = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Maximum Head Chance for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxHeadChance = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Trophy Amount Base for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double trophyAmountBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Trophy Amount Factor for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double trophyAmountFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Range Base for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minimumRangeBase = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Range Factor for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minimumRangeFactor = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP Per Trophy for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerTrophy = 16; + } + + private record PrecisionContext(boolean projectileKill, boolean precise) { + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherBlazeLeech.java b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherBlazeLeech.java new file mode 100644 index 000000000..e575a6cc4 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherBlazeLeech.java @@ -0,0 +1,240 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.nether; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.concurrent.ThreadLocalRandom; + +public class NetherBlazeLeech extends SimpleAdaptation { + public NetherBlazeLeech() { + super("nether-blaze-leech"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("nether.blaze_leech.description")); + setDisplayName(Localizer.dLocalize("nether.blaze_leech.name")); + setIcon(Material.BLAZE_POWDER); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(900); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BLAZE_ROD) + .key("challenge_nether_blaze_200") + .title(Localizer.dLocalize("advancement.challenge_nether_blaze_200.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_blaze_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BLAZE_POWDER) + .key("challenge_nether_blaze_2500") + .title(Localizer.dLocalize("advancement.challenge_nether_blaze_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_blaze_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_nether_blaze_200", "nether.blaze-leech.health-from-fire", 200, 300); + registerMilestone("challenge_nether_blaze_2500", "nether.blaze-leech.health-from-fire", 2500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getTriggerChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.blaze_leech.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getRegenTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("nether.blaze_leech.lore2")); + v.addLore(C.GREEN + "+ " + Form.f(getFoodRestore(level)) + C.GRAY + " " + Localizer.dLocalize("nether.blaze_leech.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (!isFireCause(e.getCause()) || !isReady(p, level)) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getTriggerChance(level)) { + return; + } + + applyLeech(p, level, true); + }); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player p) || !(e.getEntity() instanceof LivingEntity target)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + if (!canDamageTarget(p, target)) { + return; + } + + int level = getActiveLevel(p); + if (target.getFireTicks() <= 0 || !isReady(p, level)) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getTriggerChance(level)) { + return; + } + + applyLeech(p, level, false); + }); + } + + private boolean isFireCause(EntityDamageEvent.DamageCause cause) { + return cause == EntityDamageEvent.DamageCause.FIRE + || cause == EntityDamageEvent.DamageCause.FIRE_TICK + || cause == EntityDamageEvent.DamageCause.LAVA + || cause == EntityDamageEvent.DamageCause.HOT_FLOOR; + } + + private boolean isReady(Player p, int level) { + long now = System.currentTimeMillis(); + long next = getStorageLong(p, "blazeLeechNext", 0L); + if (next > now) { + return false; + } + + setStorage(p, "blazeLeechNext", now + getCooldownMillis(level)); + return true; + } + + private void applyLeech(Player p, int level, boolean defensive) { + int newFood = Math.min(20, p.getFoodLevel() + (int) Math.round(getFoodRestore(level))); + p.setFoodLevel(newFood); + float sat = Math.min(20f, p.getSaturation() + (float) getConfig().saturationRestore); + p.setSaturation(sat); + + int amp = Math.max(0, (int) Math.floor(getRegenAmplifier(level))); + p.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, getRegenTicks(level), amp, true, false, true), true); + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.ENTITY_BLAZE_AMBIENT, 0.45f, defensive ? 1.4f : 1.7f); + xp(p, defensive ? getConfig().xpOnDefensiveProc : getConfig().xpOnOffensiveProc); + getPlayer(p).getData().addStat("nether.blaze-leech.health-from-fire", 1); + } + + private double getTriggerChance(int level) { + return Math.min(getConfig().maxTriggerChance, getConfig().triggerChanceBase + (getLevelPercent(level) * getConfig().triggerChanceFactor)); + } + + private int getRegenTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().regenTicksBase + (getLevelPercent(level) * getConfig().regenTicksFactor))); + } + + private double getRegenAmplifier(int level) { + return getConfig().regenAmplifierBase + (getLevelPercent(level) * getConfig().regenAmplifierFactor); + } + + private double getFoodRestore(int level) { + return getConfig().foodRestoreBase + (getLevelPercent(level) * getConfig().foodRestoreFactor); + } + + private long getCooldownMillis(int level) { + return Math.max(100L, Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Fire interactions can grant hunger and regeneration in short bursts.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.62; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Trigger Chance Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double triggerChanceBase = 0.16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Trigger Chance Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double triggerChanceFactor = 0.34; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Trigger Chance for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxTriggerChance = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Regen Ticks Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double regenTicksBase = 28; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Regen Ticks Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double regenTicksFactor = 42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Regen Amplifier Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double regenAmplifierBase = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Regen Amplifier Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double regenAmplifierFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Food Restore Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double foodRestoreBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Food Restore Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double foodRestoreFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Saturation Restore for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double saturationRestore = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 1400; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 900; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Defensive Proc for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnDefensiveProc = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Offensive Proc for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnOffensiveProc = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherFireResist.java b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherFireResist.java new file mode 100644 index 000000000..c0876ff8f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherFireResist.java @@ -0,0 +1,136 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ +package art.arcane.adapt.content.adaptation.nether; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; + +import java.util.concurrent.ThreadLocalRandom; + +public class NetherFireResist extends SimpleAdaptation { + public NetherFireResist() { + super("nether-fire-resist"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("nether.fire_resist.description")); + setDisplayName(Localizer.dLocalize("nether.fire_resist.name")); + setIcon(Material.FIRE_CHARGE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(4333); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FIRE_CHARGE) + .key("challenge_nether_fire_200") + .title(Localizer.dLocalize("advancement.challenge_nether_fire_200.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_fire_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.MAGMA_CREAM) + .key("challenge_nether_fire_5k") + .title(Localizer.dLocalize("advancement.challenge_nether_fire_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_fire_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_nether_fire_200", "nether.fire-resist.negated", 200, 300); + registerMilestone("challenge_nether_fire_5k", "nether.fire-resist.negated", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.RED + "+ " + Form.pc(getFireResist(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.fire_resist.lore1")); + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(EntityDamageEvent e) { + + if (!(e.getEntity() instanceof Player p)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + if (e.getCause() != EntityDamageEvent.DamageCause.FIRE && e.getCause() != EntityDamageEvent.DamageCause.FIRE_TICK) { + return; + } + + + if (ThreadLocalRandom.current().nextDouble() < getFireResist(getLevel(p))) { + e.setCancelled(true); + getPlayer(p).getData().addStat("nether.fire-resist.negated", 1); + } + }); + } + + public double getFireResist(double level) { + return getConfig().fireResistBase + (getConfig().fireResistFactor * level); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @Data + @NoArgsConstructor + @ConfigDescription("Chance to negate the burning effect.") + public static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fire Resist Base for the Nether Fire Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fireResistBase = 0.10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fire Resist Factor for the Nether Fire Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fireResistFactor = 0.25; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherGhastWard.java b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherGhastWard.java new file mode 100644 index 000000000..daa48dee8 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherGhastWard.java @@ -0,0 +1,204 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.nether; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; + +public class NetherGhastWard extends SimpleAdaptation { + public NetherGhastWard() { + super("nether-ghast-ward"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("nether.ghast_ward.description")); + setDisplayName(Localizer.dLocalize("nether.ghast_ward.name")); + setIcon(Material.GHAST_TEAR); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GHAST_TEAR) + .key("challenge_nether_ghast_500") + .title(Localizer.dLocalize("advancement.challenge_nether_ghast_500.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_ghast_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_nether_ghast_500", "nether.ghast-ward.damage-reduced", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getGhastProjectileReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.ghast_ward.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getExplosionReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.ghast_ward.lore2")); + v.addLore(C.GREEN + "+ " + Form.pc(getWitherSkeletonReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.ghast_ward.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + if (!isNether(p)) { + return; + } + + int level = getActiveLevel(p); + if (e.getDamager() instanceof Fireball fireball && fireball.getShooter() instanceof Ghast) { + double before = e.getDamage(); + e.setDamage(Math.max(0, e.getDamage() * (1D - getGhastProjectileReduction(level)))); + p.setFireTicks(Math.min(p.getFireTicks(), getMaxFireTicks(level))); + xp(p, e.getDamage() * getConfig().xpPerMitigatedDamage); + int reduced = (int) Math.round(before - e.getDamage()); + if (reduced > 0) { + getPlayer(p).getData().addStat("nether.ghast-ward.damage-reduced", reduced); + } + return; + } + + if (e.getDamager() instanceof AbstractArrow arrow && arrow.getShooter() instanceof WitherSkeleton) { + double before = e.getDamage(); + e.setDamage(Math.max(0, e.getDamage() * (1D - getWitherSkeletonReduction(level)))); + xp(p, e.getDamage() * getConfig().xpPerMitigatedDamage); + int reduced = (int) Math.round(before - e.getDamage()); + if (reduced > 0) { + getPlayer(p).getData().addStat("nether.ghast-ward.damage-reduced", reduced); + } + } + }); + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(EntityDamageEvent e) { + if (e instanceof EntityDamageByEntityEvent || !(e.getEntity() instanceof Player p)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + if (!isNether(p)) { + return; + } + + if (e.getCause() != EntityDamageEvent.DamageCause.ENTITY_EXPLOSION && e.getCause() != EntityDamageEvent.DamageCause.BLOCK_EXPLOSION) { + return; + } + + double before = e.getDamage(); + e.setDamage(Math.max(0, e.getDamage() * (1D - getExplosionReduction(getLevel(p))))); + xp(p, e.getDamage() * getConfig().xpPerMitigatedDamage); + int reduced = (int) Math.round(before - e.getDamage()); + if (reduced > 0) { + getPlayer(p).getData().addStat("nether.ghast-ward.damage-reduced", reduced); + } + }); + } + + private boolean isNether(Player p) { + return p.getWorld().getEnvironment().name().contains("NETHER"); + } + + private double getGhastProjectileReduction(int level) { + return Math.min(getConfig().maxGhastProjectileReduction, getConfig().ghastProjectileReductionBase + (getLevelPercent(level) * getConfig().ghastProjectileReductionFactor)); + } + + private double getExplosionReduction(int level) { + return Math.min(getConfig().maxExplosionReduction, getConfig().explosionReductionBase + (getLevelPercent(level) * getConfig().explosionReductionFactor)); + } + + private double getWitherSkeletonReduction(int level) { + return Math.min(getConfig().maxWitherSkeletonReduction, getConfig().witherSkeletonReductionBase + (getLevelPercent(level) * getConfig().witherSkeletonReductionFactor)); + } + + private int getMaxFireTicks(int level) { + return Math.max(0, (int) Math.round(getConfig().maxFireTicksBase - (getLevelPercent(level) * getConfig().maxFireTicksFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Harden against ghast blasts and wither-skeleton pressure in the Nether.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.73; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ghast Projectile Reduction Base for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double ghastProjectileReductionBase = 0.14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ghast Projectile Reduction Factor for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double ghastProjectileReductionFactor = 0.54; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Ghast Projectile Reduction for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxGhastProjectileReduction = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Explosion Reduction Base for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double explosionReductionBase = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Explosion Reduction Factor for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double explosionReductionFactor = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Explosion Reduction for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxExplosionReduction = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Wither Skeleton Reduction Base for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double witherSkeletonReductionBase = 0.1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Wither Skeleton Reduction Factor for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double witherSkeletonReductionFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Wither Skeleton Reduction for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxWitherSkeletonReduction = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Fire Ticks Base for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxFireTicksBase = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Fire Ticks Factor for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxFireTicksFactor = 70; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mitigated Damage for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerMitigatedDamage = 4.2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherLavaWalker.java b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherLavaWalker.java new file mode 100644 index 000000000..e11eeb889 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherLavaWalker.java @@ -0,0 +1,180 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.nether; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +public class NetherLavaWalker extends SimpleAdaptation { + public NetherLavaWalker() { + super("nether-lava-walker"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("nether.lava_walker.description")); + setDisplayName(Localizer.dLocalize("nether.lava_walker.name")); + setIcon(Material.MAGMA_BLOCK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.MAGMA_BLOCK) + .key("challenge_nether_lava_1k") + .title(Localizer.dLocalize("advancement.challenge_nether_lava_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_lava_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_INGOT) + .key("challenge_nether_lava_25k") + .title(Localizer.dLocalize("advancement.challenge_nether_lava_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_lava_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_nether_lava_1k", "nether.lava-walker.blocks-walked", 1000, 300); + registerMilestone("challenge_nether_lava_25k", "nether.lava-walker.blocks-walked", 25000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getStride(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.lava_walker.lore1")); + v.addLore(C.YELLOW + "* " + getHungerCost(level) + C.GRAY + " " + Localizer.dLocalize("nether.lava_walker.lore2")); + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> { + if (!p.getWorld().getEnvironment().name().contains("NETHER")) { + return; + } + + if (p.isFlying() || p.isGliding() || p.isInsideVehicle() || p.getFoodLevel() <= 0) { + return; + } + + Block feet = p.getLocation().getBlock(); + Block below = p.getLocation().clone().add(0, -1, 0).getBlock(); + if (!(isLava(feet) || isLava(below))) { + return; + } + + int level = getActiveLevel(p); + if (getStorageLong(p, "lavaWalkerCooldown", 0L) > System.currentTimeMillis()) { + return; + } + + Vector velocity = p.getVelocity(); + Vector dir = p.getLocation().getDirection().setY(0).normalize().multiply(getStride(level)); + p.setVelocity(new Vector(dir.getX(), Math.max(0.16, velocity.getY()), dir.getZ())); + p.setFallDistance(0); + p.setFireTicks(0); + p.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, getConfig().fireResistTicks, 0, false, false)); + + int hungerCost = getHungerCost(level); + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + setStorage(p, "lavaWalkerCooldown", System.currentTimeMillis() + getCooldownMillis(level)); + xp(p, getConfig().xpPerStride); + getPlayer(p).getData().addStat("nether.lava-walker.blocks-walked", 1); + }); + } + + private boolean isLava(Block b) { + return b.getType() == Material.LAVA; + } + + private double getStride(int level) { + return getConfig().strideBase + (getLevelPercent(level) * getConfig().strideFactor); + } + + private int getHungerCost(int level) { + return Math.max(1, (int) Math.round(getConfig().hungerCostBase - (getLevelPercent(level) * getConfig().hungerCostFactor))); + } + + private long getCooldownMillis(int level) { + return Math.max(100, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Stride over lava in the Nether at the cost of hunger.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stride Base for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double strideBase = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Stride Factor for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double strideFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost Base for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hungerCostBase = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost Factor for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hungerCostFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisBase = 900; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownMillisFactor = 700; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fire Resist Ticks for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int fireResistTicks = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Stride for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerStride = 3.5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherPiglinBroker.java b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherPiglinBroker.java new file mode 100644 index 000000000..ba516a4d1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherPiglinBroker.java @@ -0,0 +1,224 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.nether; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Piglin; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.PiglinBarterEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class NetherPiglinBroker extends SimpleAdaptation { + public NetherPiglinBroker() { + super("nether-piglin-broker"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("nether.piglin_broker.description")); + setDisplayName(Localizer.dLocalize("nether.piglin_broker.name")); + setIcon(Material.GOLD_INGOT); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2300); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLD_INGOT) + .key("challenge_nether_piglin_100") + .title(Localizer.dLocalize("advancement.challenge_nether_piglin_100.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_piglin_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GOLD_BLOCK) + .key("challenge_nether_piglin_2500") + .title(Localizer.dLocalize("advancement.challenge_nether_piglin_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_piglin_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_nether_piglin_100", "nether.piglin-broker.improved-barters", 100, 300); + registerMilestone("challenge_nether_piglin_2500", "nether.piglin-broker.improved-barters", 2500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getExtraRollChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.piglin_broker.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getRareBonusChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.piglin_broker.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PiglinBarterEvent e) { + Piglin piglin = e.getEntity(); + Player broker = findBroker(piglin, getConfig().brokerRange); + if (broker == null) { + return; + } + + int level = getActiveLevel(broker); + if (level <= 0) { + return; + } + + List outcome = e.getOutcome(); + if (outcome.isEmpty()) { + return; + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + boolean changed = false; + if (random.nextDouble() <= getExtraRollChance(level)) { + ItemStack bonus = outcome.get(random.nextInt(outcome.size())).clone(); + int amount = Math.max(1, (int) Math.round(bonus.getAmount() * getAmountMultiplier(level))); + bonus.setAmount(Math.min(bonus.getMaxStackSize(), amount)); + outcome.add(bonus); + changed = true; + } + + if (random.nextDouble() <= getRareBonusChance(level)) { + outcome.add(getRareBonusRoll()); + changed = true; + } + + if (!changed) { + return; + } + + SoundPlayer.of(broker.getWorld()).play(broker.getLocation(), Sound.ENTITY_PIGLIN_ADMIRING_ITEM, 0.9f, 1.25f); + xp(broker, getConfig().xpOnBoostedBarter); + getPlayer(broker).getData().addStat("nether.piglin-broker.improved-barters", 1); + } + + private Player findBroker(Piglin piglin, double range) { + Player best = null; + double bestDist = Double.MAX_VALUE; + double rangeSquared = range * range; + org.bukkit.Location piglinLocation = piglin.getLocation(); + for (Entity nearby : piglin.getNearbyEntities(range, range, range)) { + if (!(nearby instanceof Player p)) { + continue; + } + if (!hasActiveAdaptation(p)) { + continue; + } + + double d = p.getLocation().distanceSquared(piglinLocation); + if (d > rangeSquared) { + continue; + } + if (d < bestDist) { + best = p; + bestDist = d; + } + } + + return best; + } + + private ItemStack getRareBonusRoll() { + return switch (ThreadLocalRandom.current().nextInt(5)) { + case 0 -> new ItemStack(Material.ENDER_PEARL, 1); + case 1 -> new ItemStack(Material.OBSIDIAN, 2); + case 2 -> new ItemStack(Material.STRING, 4); + case 3 -> new ItemStack(Material.IRON_NUGGET, 6); + default -> new ItemStack(Material.SPECTRAL_ARROW, 2); + }; + } + + private double getExtraRollChance(int level) { + return Math.min(getConfig().maxExtraRollChance, getConfig().extraRollChanceBase + (getLevelPercent(level) * getConfig().extraRollChanceFactor)); + } + + private double getRareBonusChance(int level) { + return Math.min(getConfig().maxRareBonusChance, getConfig().rareBonusChanceBase + (getLevelPercent(level) * getConfig().rareBonusChanceFactor)); + } + + private double getAmountMultiplier(int level) { + return Math.max(1.0, getConfig().amountMultiplierBase + (getLevelPercent(level) * getConfig().amountMultiplierFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Nearby piglin bartering can yield extra or improved rolls.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Broker Range for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double brokerRange = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Extra Roll Chance Base for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double extraRollChanceBase = 0.1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Extra Roll Chance Factor for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double extraRollChanceFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Extra Roll Chance for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxExtraRollChance = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Rare Bonus Chance Base for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rareBonusChanceBase = 0.03; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Rare Bonus Chance Factor for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rareBonusChanceFactor = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Rare Bonus Chance for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxRareBonusChance = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Amount Multiplier Base for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double amountMultiplierBase = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Amount Multiplier Factor for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double amountMultiplierFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Boosted Barter for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnBoostedBarter = 12; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherSkullYeet.java b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherSkullYeet.java new file mode 100644 index 000000000..153920835 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherSkullYeet.java @@ -0,0 +1,224 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.nether; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.WitherSkull; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class NetherSkullYeet extends SimpleAdaptation { + + private final Map lastJump = new ConcurrentHashMap<>(); + + public NetherSkullYeet() { + super("nether-skull-toss"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("nether.skull_toss.description1") + C.ITALIC + " " + Localizer.dLocalize("nether.skull_toss.description2") + " " + C.GRAY + Localizer.dLocalize("nether.skull_toss.description3")); + setDisplayName(Localizer.dLocalize("nether.skull_toss.name")); + setIcon(Material.WITHER_SKELETON_SKULL); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(2314); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_nether_skull_100") + .title(Localizer.dLocalize("advancement.challenge_nether_skull_100.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_skull_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_nether_skull_kills_50") + .title(Localizer.dLocalize("advancement.challenge_nether_skull_kills_50.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_skull_kills_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_nether_skull_long_bomb") + .title(Localizer.dLocalize("advancement.challenge_nether_skull_long_bomb.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_skull_long_bomb.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.HIDDEN) + .build()); + registerMilestone("challenge_nether_skull_100", "nether.skull-yeet.skulls-thrown", 100, 300); + registerMilestone("challenge_nether_skull_kills_50", "nether.skull-yeet.skull-kills", 50, 500); + } + + @Override + public void addStats(int level, Element v) { + int chance = getConfig().getBaseCooldown() - getConfig().getLevelCooldown() * level; + v.addLore(C.GREEN + String.valueOf(chance) + C.GRAY + " " + Localizer.dLocalize("nether.skull_toss.lore1")); + v.addLore(C.GRAY + Localizer.dLocalize("nether.skull_toss.lore2") + C.DARK_GRAY + Localizer.dLocalize("nether.skull_toss.lore3") + C.GRAY + ", " + Localizer.dLocalize("nether.skull_toss.lore4")); + } + + private int getCooldownDuration(Player p) { + return (getConfig().getBaseCooldown() - getConfig().getLevelCooldown() * getLevel(p)) * 20; + } + + @EventHandler + public void on(PlayerQuitEvent e) { + Player p = e.getPlayer(); + lastJump.remove(p.getUniqueId()); + } + + @EventHandler + public void onRightClick(PlayerInteractEvent e) { + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> { + if (e.useItemInHand() == Event.Result.DENY) { + return; + } + + if (e.getAction() != Action.LEFT_CLICK_AIR && e.getAction() != Action.LEFT_CLICK_BLOCK) { + return; + } + if (e.getHand() != EquipmentSlot.HAND || e.getItem() == null || e.getMaterial() != Material.WITHER_SKELETON_SKULL) { + return; + } + + SoundPlayer sp = SoundPlayer.of(p); + + if (lastJump.get(p.getUniqueId()) != null && M.ms() - lastJump.get(p.getUniqueId()) <= getCooldownDuration(p)) { + sp.play(p, Sound.BLOCK_CONDUIT_DEACTIVATE, 1F, 1F); + return; + } + + if (lastJump.get(p.getUniqueId()) != null && M.ms() - lastJump.get(p.getUniqueId()) <= getCooldownDuration(p)) { + return; + } + + if (p.hasCooldown(p.getInventory().getItemInMainHand().getType())) { + e.setCancelled(true); + sp.play(p, Sound.BLOCK_CONDUIT_DEACTIVATE, 1F, 1F); + return; + } else { + p.setCooldown(Material.WITHER_SKELETON_SKULL, getCooldownDuration(p)); + } + + + if (p.getGameMode() != GameMode.CREATIVE) { + e.getItem().setAmount(e.getItem().getAmount() - 1); + lastJump.put(p.getUniqueId(), M.ms()); + } + + Vector dir = p.getEyeLocation().getDirection(); + Location spawn = p.getEyeLocation().add(new Vector(.5, -.5, .5)).add(dir); + p.getWorld().spawn(spawn, WitherSkull.class, entity -> { + sp.play(entity, Sound.ENTITY_WITHER_SHOOT, 1, 1); + entity.setRotation(p.getEyeLocation().getYaw(), p.getEyeLocation().getPitch()); + entity.setCharged(false); + entity.setBounce(false); + entity.setDirection(dir); + entity.setShooter(p); + xp(p, 100); + }); + getPlayer(p).getData().addStat("nether.skull-yeet.skulls-thrown", 1); + }); + } + + @EventHandler + public void onEntityDeath(EntityDeathEvent e) { + LivingEntity dead = e.getEntity(); + if (dead.getLastDamageCause() instanceof EntityDamageByEntityEvent dbe + && dbe.getDamager() instanceof WitherSkull skull + && skull.getShooter() instanceof Player p) { + withAdaptedPlayer(p, () -> { + getPlayer(p).getData().addStat("nether.skull-yeet.skull-kills", 1); + + double distance = p.getLocation().distance(dead.getLocation()); + if (distance >= 40 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_nether_skull_long_bomb")) { + getPlayer(p).getAdvancementHandler().grant("challenge_nether_skull_long_bomb"); + } + }); + } + } + + @Override + public boolean isEnabled() { + return getConfig().isEnabled(); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + + @Data + @NoArgsConstructor + @ConfigDescription("Throw Wither Skulls that explode on impact.") + public static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + public boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + private boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Cooldown for the Nether Skull Yeet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private int baseCooldown = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Level Cooldown for the Nether Skull Yeet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private int levelCooldown = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + private int baseCost = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + private double costFactor = 0.92; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + private int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + private int initialCost = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherWitherResist.java b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherWitherResist.java new file mode 100644 index 000000000..bc57e960e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/nether/NetherWitherResist.java @@ -0,0 +1,141 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.nether; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.concurrent.ThreadLocalRandom; + +public class NetherWitherResist extends SimpleAdaptation { + + public NetherWitherResist() { + super("nether-wither-resist"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("nether.wither_resist.description")); + setDisplayName(Localizer.dLocalize("nether.wither_resist.name")); + setIcon(Material.NETHERITE_CHESTPLATE); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(9283); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_ROSE) + .key("challenge_nether_wither_100") + .title(Localizer.dLocalize("advancement.challenge_nether_wither_100.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_wither_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHER_STAR) + .key("challenge_nether_wither_1k") + .title(Localizer.dLocalize("advancement.challenge_nether_wither_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_wither_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_nether_wither_100", "nether.wither-resist.negated", 100, 300); + registerMilestone("challenge_nether_wither_1k", "nether.wither-resist.negated", 1000, 1000); + } + + @Override + public void addStats(int level, Element v) { + int chance = (int) (getConfig().basePieceChance + getConfig().getChanceAddition() * level); + v.addLore(C.GREEN + "+ " + chance + "%" + C.GRAY + Localizer.dLocalize("nether.wither_resist.lore1")); + v.addLore(C.GRAY + " " + Localizer.dLocalize("nether.wither_resist.lore1") + C.DARK_GRAY + Localizer.dLocalize("nether.wither_resist.lore2")); + } + + @EventHandler + public void onEntityDamage(EntityDamageEvent e) { + if (e.getCause() == EntityDamageEvent.DamageCause.WITHER && e.getEntity() instanceof Player p) { + withAdaptedPlayer(p, e, () -> { + double chance = getTotalChange(p); + if (ThreadLocalRandom.current().nextInt(101) <= chance) { + e.setCancelled(true); + getPlayer(p).getData().addStat("nether.wither-resist.negated", 1); + } + }); + } + } + + @Override + public boolean isEnabled() { + return getConfig().isEnabled(); + } + + @Override + public void onTick() { + } + + private double getTotalChange(Player p) { + return getChance(p, EquipmentSlot.HEAD) + getChance(p, EquipmentSlot.CHEST) + getChance(p, EquipmentSlot.LEGS) + getChance(p, EquipmentSlot.FEET); + } + + private double getChance(Player p, EquipmentSlot slot) { + if (p.getEquipment() == null) + return 0.0; + ItemStack item = p.getEquipment().getItem(slot); + if (item.getType() == Material.NETHERITE_HELMET || item.getType() == Material.NETHERITE_CHESTPLATE || item.getType() == Material.NETHERITE_LEGGINGS || item.getType() == Material.NETHERITE_BOOTS) + return getConfig().basePieceChance + getConfig().chanceAddition * getLevel(p); + return 0.0D; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @Data + @NoArgsConstructor + @ConfigDescription("Wearing Netherite Armor has a chance to negate the wither effect.") + public static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + public boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + private boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + private int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + private double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + private int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + private int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Piece Chance for the Nether Wither Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double basePieceChance = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Chance Addition for the Nether Wither Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double chanceAddition = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeAutosmelt.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeAutosmelt.java new file mode 100644 index 000000000..cf3077f5c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeAutosmelt.java @@ -0,0 +1,264 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Enchantments; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class PickaxeAutosmelt extends SimpleAdaptation { + public PickaxeAutosmelt() { + super("pickaxe-autosmelt"); + registerConfiguration(PickaxeAutosmelt.Config.class); + setDescription(Localizer.dLocalize("pickaxe.auto_smelt.description")); + setDisplayName(Localizer.dLocalize("pickaxe.auto_smelt.name")); + setIcon(Material.RAW_GOLD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(7444); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FURNACE) + .key("challenge_pickaxe_autosmelt_1k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_autosmelt_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_autosmelt_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BLAST_FURNACE) + .key("challenge_pickaxe_autosmelt_25k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_autosmelt_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_autosmelt_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_pickaxe_autosmelt_1k", "pickaxe.autosmelt.ores-smelted", 1000, 400); + registerMilestone("challenge_pickaxe_autosmelt_25k", "pickaxe.autosmelt.ores-smelted", 25000, 1500); + } + + static void autosmeltBlockDTI(Block b, Player p) { + int fortune = getFortuneOreMultiplier(p.getInventory().getItemInMainHand() + .getEnchantments().get(Enchantments.LOOT_BONUS_BLOCKS)); + SoundPlayer spw = SoundPlayer.of(b.getWorld()); + switch (b.getType()) { + case IRON_ORE, DEEPSLATE_IRON_ORE -> { + if (b.getLocation().getWorld() == null) { + return; + } + + b.setType(Material.AIR); + HashMap excessItems = p.getInventory().addItem(new ItemStack(Material.IRON_INGOT, fortune)); + excessItems.values().forEach(itemStack -> b.getLocation().getWorld().dropItemNaturally(b.getLocation(), itemStack)); + if (soundsEnabled()) { + spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); + } + if (particlesEnabled()) { + b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); + } + } + case GOLD_ORE, DEEPSLATE_GOLD_ORE -> { + if (b.getLocation().getWorld() == null) { + return; + } + + b.setType(Material.AIR); + HashMap excessItems = p.getInventory().addItem(new ItemStack(Material.GOLD_INGOT, fortune)); + excessItems.values().forEach(itemStack -> b.getLocation().getWorld().dropItemNaturally(b.getLocation(), itemStack)); + if (soundsEnabled()) { + spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); + } + if (particlesEnabled()) { + b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); + } + } + case COPPER_ORE, DEEPSLATE_COPPER_ORE -> { + if (b.getLocation().getWorld() == null) { + return; + } + b.setType(Material.AIR); + HashMap excessItems = p.getInventory().addItem(new ItemStack(Material.COPPER_INGOT, fortune)); + excessItems.values().forEach(itemStack -> b.getLocation().getWorld().dropItemNaturally(b.getLocation(), itemStack)); + if (soundsEnabled()) { + spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); + } + if (particlesEnabled()) { + b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); + } + } + + } + } + + static void autosmeltBlock(Block b, Player p) { + int fortune = getFortuneOreMultiplier(p.getInventory().getItemInMainHand() + .getEnchantments().get(Enchantments.LOOT_BONUS_BLOCKS)); + SoundPlayer spw = SoundPlayer.of(b.getWorld()); + switch (b.getType()) { + case IRON_ORE, DEEPSLATE_IRON_ORE -> { + + if (b.getLocation().getWorld() == null) { + return; + } + + b.setType(Material.AIR); + b.getLocation().getWorld().dropItemNaturally(b.getLocation(), new ItemStack(Material.IRON_INGOT, fortune)); + if (soundsEnabled()) { + spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); + } + if (particlesEnabled()) { + b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); + } + } + case GOLD_ORE, DEEPSLATE_GOLD_ORE -> { + if (b.getLocation().getWorld() == null) { + return; + } + + b.setType(Material.AIR); + b.getLocation().getWorld().dropItemNaturally(b.getLocation(), new ItemStack(Material.GOLD_INGOT, fortune)); + if (soundsEnabled()) { + spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); + } + if (particlesEnabled()) { + b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); + } + } + case COPPER_ORE, DEEPSLATE_COPPER_ORE -> { + if (b.getLocation().getWorld() == null) { + return; + } + b.setType(Material.AIR); + b.getLocation().getWorld().dropItemNaturally(b.getLocation(), new ItemStack(Material.COPPER_INGOT, fortune)); + if (soundsEnabled()) { + spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); + } + if (particlesEnabled()) { + b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); + } + } + + } + } + + // https://minecraft.fandom.com/wiki/Fortune?oldid=2359015#Ore + private static int getFortuneOreMultiplier(Integer fortuneLevel) { + if (fortuneLevel == null || fortuneLevel < 1) return 1; + + double averageBonusMultiplier = (1.0 / (fortuneLevel + 2) + (fortuneLevel + 1) / 2.0) - 1; + int sumOfBonusMultipliers = (fortuneLevel * (fortuneLevel + 1)) / 2; + double chancePerMultiplier = averageBonusMultiplier / sumOfBonusMultipliers; + + int bonusMultiplier = ((int) (ThreadLocalRandom.current().nextDouble() / chancePerMultiplier)) + 1; + + return bonusMultiplier <= fortuneLevel ? bonusMultiplier + 1 : 1; + } + + private static boolean particlesEnabled() { + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + return effects == null || effects.isParticlesEnabled(); + } + + private static boolean soundsEnabled() { + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + return effects == null || effects.isSoundsEnabled(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.auto_smelt.lore1")); + v.addLore(C.GREEN + "" + (level * 1.25) + C.GRAY + Localizer.dLocalize("pickaxe.auto_smelt.lore2")); + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + if (!e.getBlock().getBlockData().getMaterial().name().endsWith("_ORE") && !ItemListings.getSmeltOre().contains(e.getBlock().getType())) { + return; + } + if (resolveBlockBreakContext(p, e.getBlock().getLocation()) == null) { + return; + } + + PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("pickaxe"); + PlayerAdaptation adaptation = line != null ? line.getAdaptation("pickaxe-drop-to-inventory") : null; + if (adaptation != null && adaptation.getLevel() > 0) { + PickaxeAutosmelt.autosmeltBlockDTI(e.getBlock(), p); + } else { + PickaxeAutosmelt.autosmeltBlock(e.getBlock(), p); + } + getPlayer(p).getData().addStat("pickaxe.autosmelt.ores-smelted", 1); + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Automatically smelt mined ores with a chance for extra drops.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.95; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeChisel.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeChisel.java new file mode 100644 index 000000000..e56b81d8a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeChisel.java @@ -0,0 +1,227 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.RayTraceResult; + +public class PickaxeChisel extends SimpleAdaptation { + public PickaxeChisel() { + super("pickaxe-chisel"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("pickaxe.chisel.description")); + setDisplayName(Localizer.dLocalize("pickaxe.chisel.name")); + setIcon(Material.IRON_NUGGET); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(7433); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE) + .key("challenge_pickaxe_chisel_500") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_chisel_500.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_chisel_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_chisel_500", "pickaxe.chisel.extra-ores", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getDropChance(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("pickaxe.chisel.lore1")); + v.addLore(C.RED + "- " + getDamagePerBlock(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("pickaxe.chisel.lore2")); + } + + private int getCooldownTime(double levelPercent) { + return getConfig().cooldownTime; + } + + private double getDropChance(double levelPercent) { + return ((levelPercent) * getConfig().dropChanceFactor) + getConfig().dropChanceBase; + } + + private double getBreakChance(double levelPercent) { + return getConfig().breakChance; + } + + private int getDamagePerBlock(double levelPercent) { + return (int) (getConfig().damagePerBlockBase + (getConfig().damageFactorInverseMultiplier * ((1D - levelPercent)))); + } + + + @EventHandler + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!isPickaxe(p.getInventory().getItemInMainHand()) || !hasActiveAdaptation(p)) { + return; + } + + if (p.getInventory().getItemInMainHand().getEnchantments().containsKey(Enchantment.SILK_TOUCH) || p.getInventory().getItemInMainHand().getEnchantments().containsKey(Enchantment.MENDING)) { + return; + } + if (p.getCooldown(p.getInventory().getItemInMainHand().getType()) > 0) { + return; + } + + Block target = action == Action.RIGHT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (target == null) { + return; + } + + if (!canBlockBreak(p, target.getLocation())) { + return; + } + BlockData b = target.getBlockData(); + if (isOre(b)) { + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 1.25f, 1.4f); + spw.play(p.getLocation(), Sound.BLOCK_METAL_HIT, 1.25f, 1.7f); + + p.setCooldown(p.getInventory().getItemInMainHand().getType(), getCooldownTime(getLevelPercent(p))); + damageHand(p, getDamagePerBlock(getLevelPercent(p))); + + RayTraceResult ray = p.rayTraceBlocks(8); + Location c = ray != null ? ray.getHitPosition().toLocation(p.getWorld()) : target.getLocation().add(0.5, 0.5, 0.5); + + ItemStack is = getDropFor(b); + if (M.r(getDropChance(getLevelPercent(p)))) { + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particles.ITEM_CRACK, c, 14, 0.10, 0.01, 0.01, 0.1, is); + } + spw.play(p.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 1.25f, 0.787f); + spw.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_PLACE, 0.55f, 1.89f); + target.getWorld().dropItemNaturally(c.clone().subtract(p.getLocation().getDirection().clone().multiply(0.1)), is); + getPlayer(p).getData().addStat("pickaxe.chisel.extra-ores", 1); + } else { + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particles.ITEM_CRACK, c, 3, 0.01, 0.01, 0.01, 0.1, is); + target.getWorld().spawnParticle(Particles.BLOCK_CRACK, c, 9, 0.1, 0.1, 0.1, target.getBlockData()); + } + } + + if (M.r(getBreakChance(getLevelPercent(p)))) { + spw.play(p.getLocation(), Sound.BLOCK_BASALT_BREAK, 1.25f, 0.4f); + spw.play(p.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 1.25f, 0.887f); + target.breakNaturally(p.getInventory().getItemInMainHand()); + } + } + } + + private ItemStack getDropFor(BlockData b) { + return switch (b.getMaterial()) { + case COAL_ORE, DEEPSLATE_COAL_ORE -> new ItemStack(Material.COAL); + case COPPER_ORE, DEEPSLATE_COPPER_ORE -> + new ItemStack(Material.RAW_COPPER); + case GOLD_ORE, DEEPSLATE_GOLD_ORE, NETHER_GOLD_ORE -> + new ItemStack(Material.RAW_GOLD); + case IRON_ORE, DEEPSLATE_IRON_ORE -> new ItemStack(Material.RAW_IRON); + case DIAMOND_ORE, DEEPSLATE_DIAMOND_ORE -> + new ItemStack(Material.DIAMOND); + case LAPIS_ORE, DEEPSLATE_LAPIS_ORE -> + new ItemStack(Material.LAPIS_LAZULI); + case EMERALD_ORE, DEEPSLATE_EMERALD_ORE -> + new ItemStack(Material.EMERALD); + case NETHER_QUARTZ_ORE -> new ItemStack(Material.QUARTZ); + case REDSTONE_ORE -> new ItemStack(Material.REDSTONE); + + default -> new ItemStack(Material.AIR); + }; + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Right-click ores to chisel extra ore at a severe durability cost.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Pickaxe Chisel adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Time for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int cooldownTime = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Drop Chance Base for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dropChanceBase = 0.07; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Drop Chance Factor for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dropChanceFactor = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Break Chance for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double breakChance = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Per Block Base for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damagePerBlockBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Factor Inverse Multiplier for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageFactorInverseMultiplier = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeDeepCore.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeDeepCore.java new file mode 100644 index 000000000..0c1711275 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeDeepCore.java @@ -0,0 +1,153 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.potion.PotionEffect; + +import java.util.EnumSet; +import java.util.Set; + +public class PickaxeDeepCore extends SimpleAdaptation { + private static final Set DEEPSLATE_BLOCKS = EnumSet.of( + Material.DEEPSLATE, Material.COBBLED_DEEPSLATE, Material.POLISHED_DEEPSLATE, + Material.DEEPSLATE_BRICKS, Material.DEEPSLATE_TILES, + Material.DEEPSLATE_COAL_ORE, Material.DEEPSLATE_COPPER_ORE, + Material.DEEPSLATE_IRON_ORE, Material.DEEPSLATE_GOLD_ORE, + Material.DEEPSLATE_REDSTONE_ORE, Material.DEEPSLATE_LAPIS_ORE, + Material.DEEPSLATE_DIAMOND_ORE, Material.DEEPSLATE_EMERALD_ORE); + + public PickaxeDeepCore() { + super("pickaxe-deep-core"); + registerConfiguration(PickaxeDeepCore.Config.class); + setDescription(Localizer.dLocalize("pickaxe.deep_core.description")); + setDisplayName(Localizer.dLocalize("pickaxe.deep_core.name")); + setIcon(Material.DEEPSLATE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(5825); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DEEPSLATE) + .key("challenge_pickaxe_deepcore_5k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_deepcore_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_deepcore_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_deepcore_5k", "pickaxe.deep-core.deepslate-mined", 5000, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.deep_core.lore1")); + v.addLore(C.GREEN + "" + (getAmplifier(level) + 1) + C.GRAY + " " + Localizer.dLocalize("pickaxe.deep_core.lore2")); + } + + private int getAmplifier(int level) { + return Math.min(getConfig().maxAmplifier, getConfig().amplifierBase + level - 1); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDamageEvent e) { + if (!DEEPSLATE_BLOCKS.contains(e.getBlock().getType())) { + return; + } + + Player p = e.getPlayer(); + if (!isPickaxe(p.getInventory().getItemInMainHand())) { + return; + } + + Adaptation.BlockActionContext context = resolveInteractContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + p.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, getConfig().durationTicks, getAmplifier(context.level()), false, false, true)); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (!DEEPSLATE_BLOCKS.contains(e.getBlock().getType())) { + return; + } + + Player p = e.getPlayer(); + if (!isPickaxe(p.getInventory().getItemInMainHand()) || getActiveLevel(p) <= 0) { + return; + } + + getPlayer(p).getData().addStat("pickaxe.deep-core.deepslate-mined", 1); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain Haste while mining deepslate so it digs like normal stone.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Haste amplifier granted at level 1 while mining deepslate.", impact = "Higher values make deepslate mine faster at every level.") + int amplifierBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum Haste amplifier this adaptation can grant.", impact = "Higher values allow stronger Haste at high levels.") + int maxAmplifier = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of the Haste effect applied when damaging deepslate.", impact = "Higher values keep the effect active longer between swings.") + int durationTicks = 60; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeDropToInventory.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeDropToInventory.java new file mode 100644 index 000000000..9af349de1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeDropToInventory.java @@ -0,0 +1,125 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; + +import java.util.List; + +public class PickaxeDropToInventory extends SimpleAdaptation { + public PickaxeDropToInventory() { + super("pickaxe-drop-to-inventory"); + registerConfiguration(PickaxeDropToInventory.Config.class); + setDescription(Localizer.dLocalize("pickaxe.drop_to_inventory.description")); + setDisplayName(Localizer.dLocalize("pickaxe.drop_to_inventory.name")); + setIcon(Material.MINECART); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(7944); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_pickaxe_dti_25k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_dti_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_dti_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_dti_25k", "pickaxe.drop-to-inv.items-caught", 25000, 500); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("pickaxe.drop_to_inventory.lore1")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDropItemEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + if (resolveBlockBreakContext(p, e.getBlock().getLocation(), null, true) == null) { + return; + } + if (ItemListings.toolPickaxes.contains(p.getInventory().getItemInMainHand().getType())) { + List items = new KList<>(e.getItems()); + e.getItems().clear(); + int caught = 0; + for (Item i : items) { + sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); + if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { + p.getWorld().dropItem(p.getLocation(), i.getItemStack()); + } + caught++; + } + if (caught > 0) { + getPlayer(p).getData().addStat("pickaxe.drop-to-inv.items-caught", caught); + } + } + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Mined blocks drop directly into your inventory.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeGemPolish.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeGemPolish.java new file mode 100644 index 000000000..90d16bb1e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeGemPolish.java @@ -0,0 +1,170 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +public class PickaxeGemPolish extends SimpleAdaptation { + public PickaxeGemPolish() { + super("pickaxe-gem-polish"); + registerConfiguration(PickaxeGemPolish.Config.class); + setDescription(Localizer.dLocalize("pickaxe.gem_polish.description")); + setDisplayName(Localizer.dLocalize("pickaxe.gem_polish.name")); + setIcon(Material.DIAMOND); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(6844); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EMERALD) + .key("challenge_pickaxe_gempolish_500") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_gempolish_500.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_gempolish_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_gempolish_500", "pickaxe.gem-polish.gems-polished", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.gem_polish.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getGemChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("pickaxe.gem_polish.lore2")); + v.addLore(C.GREEN + "+ " + getBonusXp(level) + C.GRAY + " " + Localizer.dLocalize("pickaxe.gem_polish.lore3")); + } + + public double getGemChance(int level) { + return Math.min(getConfig().maxGemChance, getConfig().gemChanceBase + (level * getConfig().gemChancePerLevel)); + } + + public int getBonusXp(int level) { + return getConfig().bonusXpBase + (level * getConfig().bonusXpPerLevel); + } + + private Material getGemFor(Material type) { + return switch (type) { + case DIAMOND_ORE, DEEPSLATE_DIAMOND_ORE -> Material.DIAMOND; + case EMERALD_ORE, DEEPSLATE_EMERALD_ORE -> Material.EMERALD; + case LAPIS_ORE, DEEPSLATE_LAPIS_ORE -> Material.LAPIS_LAZULI; + case AMETHYST_CLUSTER -> Material.AMETHYST_SHARD; + default -> null; + }; + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + Material gem = getGemFor(e.getBlock().getType()); + if (gem == null) { + return; + } + + Player p = e.getPlayer(); + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isPickaxe(hand)) { + return; + } + + if (getConfig().preventSilkTouchDoubleDip && hand.getEnchantments().containsKey(Enchantment.SILK_TOUCH)) { + return; + } + + Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + Location drop = e.getBlock().getLocation().add(0.5, 0.5, 0.5); + int bonusXp = getBonusXp(context.level()); + if (bonusXp > 0) { + e.getBlock().getWorld().spawn(drop, org.bukkit.entity.ExperienceOrb.class).setExperience(bonusXp); + } + + if (M.r(getGemChance(context.level()))) { + e.getBlock().getWorld().dropItemNaturally(drop, new ItemStack(gem)); + getPlayer(p).getData().addStat("pickaxe.gem-polish.gems-polished", 1); + if (areParticlesEnabled()) { + e.getBlock().getWorld().spawnParticle(Particle.HAPPY_VILLAGER, drop, 6, 0.25, 0.25, 0.25); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Mining gem ores grants bonus XP orbs and a chance for an extra matching gem.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Skips all bonuses when the pickaxe has Silk Touch.", impact = "True prevents double-dipping by silk-touching ores and mining them again.") + boolean preventSilkTouchDoubleDip = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base chance for an extra gem drop when mining a gem ore.", impact = "Higher values drop extra gems more often at every level.") + double gemChanceBase = 0.04; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional extra-gem chance gained per adaptation level.", impact = "Higher values drop extra gems more often at higher levels.") + double gemChancePerLevel = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum total extra-gem chance.", impact = "Higher values allow more frequent extra gems at max level.") + double maxGemChance = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base bonus XP orb value granted per mined gem ore.", impact = "Higher values grant more XP at every level.") + int bonusXpBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional bonus XP orb value gained per adaptation level.", impact = "Higher values grant more XP at higher levels.") + int bonusXpPerLevel = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeObsidianRush.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeObsidianRush.java new file mode 100644 index 000000000..6ecc3f920 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeObsidianRush.java @@ -0,0 +1,159 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + +public class PickaxeObsidianRush extends SimpleAdaptation { + public PickaxeObsidianRush() { + super("pickaxe-obsidian-rush"); + registerConfiguration(PickaxeObsidianRush.Config.class); + setDescription(Localizer.dLocalize("pickaxe.obsidian_rush.description")); + setDisplayName(Localizer.dLocalize("pickaxe.obsidian_rush.name")); + setIcon(Material.CRYING_OBSIDIAN); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(6233); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.OBSIDIAN) + .key("challenge_pickaxe_obsidianrush_1k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_obsidianrush_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_obsidianrush_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_obsidianrush_1k", "pickaxe.obsidian-rush.obsidian-mined", 1000, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.obsidian_rush.lore1")); + v.addLore(C.GREEN + "" + (getAmplifier(level) + 1) + C.GRAY + " " + Localizer.dLocalize("pickaxe.obsidian_rush.lore2")); + v.addLore(C.ITALIC + Localizer.dLocalize("pickaxe.obsidian_rush.lore3")); + } + + private int getAmplifier(int level) { + return Math.min(getConfig().maxAmplifier, getConfig().amplifierBase + level); + } + + private boolean isRushTarget(Material type) { + return type == Material.OBSIDIAN || type == Material.CRYING_OBSIDIAN; + } + + private boolean isRushPickaxe(ItemStack is) { + if (!isItem(is)) { + return false; + } + + return switch (is.getType()) { + case DIAMOND_PICKAXE, NETHERITE_PICKAXE -> true; + default -> false; + }; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDamageEvent e) { + if (!isRushTarget(e.getBlock().getType())) { + return; + } + + Player p = e.getPlayer(); + if (!isRushPickaxe(p.getInventory().getItemInMainHand())) { + return; + } + + Adaptation.BlockActionContext context = resolveInteractContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + p.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, getConfig().durationTicks, getAmplifier(context.level()), false, false, true)); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (!isRushTarget(e.getBlock().getType())) { + return; + } + + Player p = e.getPlayer(); + if (!isRushPickaxe(p.getInventory().getItemInMainHand()) || getActiveLevel(p) <= 0) { + return; + } + + getPlayer(p).getData().addStat("pickaxe.obsidian-rush.obsidian-mined", 1); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain a strong Haste burst while mining obsidian with a diamond or netherite pickaxe.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Haste amplifier added on top of the adaptation level while mining obsidian.", impact = "Higher values make obsidian mine faster at every level.") + int amplifierBase = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum Haste amplifier this adaptation can grant.", impact = "Higher values allow stronger Haste at high levels.") + int maxAmplifier = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of the Haste burst applied when damaging obsidian.", impact = "Higher values keep the burst active longer between swings.") + int durationTicks = 120; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeQuarrySense.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeQuarrySense.java new file mode 100644 index 000000000..02b96b92f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeQuarrySense.java @@ -0,0 +1,394 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.integration.hiddenore.HiddenOreLink; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import fr.skytasul.glowingentities.GlowingEntities; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.entity.Slime; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class PickaxeQuarrySense extends SimpleAdaptation { + private static final String MARKER_META = "adapt-quarry-sense-marker"; + + public PickaxeQuarrySense() { + super("pickaxe-quarry-sense"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("pickaxe.quarry_sense.description")); + setDisplayName(Localizer.dLocalize("pickaxe.quarry_sense.name")); + setIcon(Material.MAP); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1200); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPYGLASS) + .key("challenge_pickaxe_quarry_200") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_quarry_200.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_quarry_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_quarry_200", "pickaxe.quarry-sense.scans", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getScanRadius(level)) + C.GRAY + " " + Localizer.dLocalize("pickaxe.quarry_sense.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getDurabilityCostPercent(level), 2) + C.GRAY + " " + Localizer.dLocalize("pickaxe.quarry_sense.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("pickaxe.quarry_sense.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isEligiblePickaxe(hand) || p.hasCooldown(hand.getType())) { + return; + } + + if (areParticlesEnabled()) { + p.spawnParticle(Particle.ENCHANT, p.getEyeLocation(), 14, 0.2, 0.25, 0.2, 0.15); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.8f, 1.35f); + + int durabilityCost = getDurabilityCost(hand, level); + if (!applyPickaxeCost(p, hand, durabilityCost)) { + if (areParticlesEnabled()) { + p.spawnParticle(Particle.SMOKE, p.getEyeLocation(), 8, 0.2, 0.2, 0.2, 0.03); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_ANVIL_PLACE, 0.5f, 0.65f); + return; + } + + List ores = findNearbyOres(p.getLocation(), getScanRadius(level), getMaxHighlights(level)); + if (ores.isEmpty()) { + if (areParticlesEnabled()) { + p.spawnParticle(Particle.SMOKE, p.getEyeLocation(), 12, 0.22, 0.22, 0.22, 0.02); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.6f, 0.75f); + p.setCooldown(hand.getType(), getCooldownTicks(level)); + e.setCancelled(true); + return; + } + + for (Block ore : ores) { + showOreMarker(p, ore, getHighlightTicks(level)); + } + + p.setCooldown(hand.getType(), getCooldownTicks(level)); + if (areParticlesEnabled()) { + p.spawnParticle(Particle.GLOW, p.getEyeLocation(), 8, 0.15, 0.15, 0.15, 0.01); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.9f, 1.6f); + xp(p, ores.size() * getConfig().xpPerFoundOre); + getPlayer(p).getData().addStat("pickaxe.quarry-sense.scans", 1); + e.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof Slime slime && slime.hasMetadata(MARKER_META)) { + e.setCancelled(true); + } + } + + private List findNearbyOres(Location origin, int radius, int maxResults) { + List ores = new ArrayList<>(); + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + for (int z = -radius; z <= radius; z++) { + Block b = origin.getWorld().getBlockAt(origin.getBlockX() + x, origin.getBlockY() + y, origin.getBlockZ() + z); + if (!isOre(b.getBlockData())) { + continue; + } + + ores.add(b); + } + } + } + + for (HiddenOreLink.VeinTarget vein : HiddenOreLink.veins(origin, radius)) { + Location at = vein.location(); + ores.add(origin.getWorld().getBlockAt(at.getBlockX(), at.getBlockY(), at.getBlockZ())); + } + + ores.sort(Comparator.comparingDouble(b -> b.getLocation().distanceSquared(origin))); + if (ores.size() > maxResults) { + return new ArrayList<>(ores.subList(0, maxResults)); + } + + return ores; + } + + private void showOreMarker(Player p, Block ore, int durationTicks) { + GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); + if (glowingEntities == null) { + showFallbackMarker(p, ore, durationTicks); + return; + } + + Slime slime = ore.getWorld().spawn(ore.getLocation().add(0.5, 0.5, 0.5), Slime.class, s -> { + s.setInvulnerable(true); + s.setCollidable(false); + s.setGravity(false); + s.setSilent(true); + s.setAI(false); + s.setSize(2); + s.setRotation(0, 0); + s.setMetadata(MARKER_META, new FixedMetadataValue(Adapt.instance, true)); + s.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0, false, false)); + }); + + try { + glowingEntities.setGlowing(slime, p, ChatColor.AQUA); + } catch (ReflectiveOperationException ex) { + Adapt.verbose("Failed to enable glowing marker for QuarrySense: " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + slime.remove(); + showFallbackMarker(p, ore, durationTicks); + return; + } + + if (areParticlesEnabled()) { + + p.spawnParticle(Particle.GLOW, ore.getLocation().add(0.5, 0.5, 0.5), 20, 0.25, 0.25, 0.25, 0.001); + + } + if (areParticlesEnabled()) { + p.spawnParticle(Particle.END_ROD, ore.getLocation().add(0.5, 0.5, 0.5), 8, 0.15, 0.15, 0.15, 0.003); + + } + J.runEntity(slime, () -> { + try { + glowingEntities.unsetGlowing(slime, p); + } catch (ReflectiveOperationException ex) { + Adapt.verbose("Failed to clear glowing marker for QuarrySense: " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + slime.remove(); + }, durationTicks); + } + + private void showFallbackMarker(Player p, Block ore, int durationTicks) { + Location loc = ore.getLocation().add(0.5, 0.5, 0.5); + for (int t = 0; t <= durationTicks; t += 8) { + J.runEntity(p, () -> { + if (!p.isOnline()) { + return; + } + + if (areParticlesEnabled()) { + + p.spawnParticle(Particle.GLOW, loc, 14, 0.22, 0.22, 0.22, 0.001); + + } + if (areParticlesEnabled()) { + p.spawnParticle(Particle.END_ROD, loc, 4, 0.12, 0.12, 0.12, 0.001); + } + }, t); + } + } + + private boolean isEligiblePickaxe(ItemStack hand) { + if (!isItem(hand)) { + return false; + } + + return switch (hand.getType()) { + case IRON_PICKAXE, DIAMOND_PICKAXE, NETHERITE_PICKAXE -> true; + default -> false; + }; + } + + private boolean applyPickaxeCost(Player p, ItemStack hand, int durabilityCost) { + if (getConfig().costsReduceMaxDurability) { + return tryReduceMaxDurability(p, hand, durabilityCost); + } + + return tryDamagePickaxe(p, hand, durabilityCost); + } + + private boolean tryDamagePickaxe(Player p, ItemStack hand, int durabilityCost) { + if (!(hand.getItemMeta() instanceof Damageable damageable)) { + return false; + } + + int maxDurability = hand.getType().getMaxDurability(); + int currentDamage = damageable.getDamage(); + if (currentDamage + durabilityCost >= maxDurability) { + return false; + } + + damageable.setDamage(currentDamage + durabilityCost); + hand.setItemMeta(damageable); + p.getInventory().setItemInMainHand(hand); + return true; + } + + private boolean tryReduceMaxDurability(Player p, ItemStack hand, int durabilityCost) { + if (!(hand.getItemMeta() instanceof Damageable damageable)) { + return false; + } + + int fallbackMax = Math.max(1, hand.getType().getMaxDurability()); + int currentMax = damageable.hasMaxDamage() ? Math.max(1, damageable.getMaxDamage()) : fallbackMax; + int currentDamage = Math.max(0, damageable.getDamage()); + int newMax = currentMax - durabilityCost; + if (newMax <= currentDamage + 1) { + return false; + } + + damageable.setMaxDamage(Math.max(1, newMax)); + hand.setItemMeta(damageable); + p.getInventory().setItemInMainHand(hand); + return true; + } + + private int getScanRadius(int level) { + return Math.max(4, (int) Math.round(getConfig().scanRadiusBase + (getLevelPercent(level) * getConfig().scanRadiusFactor))); + } + + private int getMaxHighlights(int level) { + return Math.max(1, (int) Math.round(getConfig().maxHighlightsBase + (getLevelPercent(level) * getConfig().maxHighlightsFactor))); + } + + private int getHighlightTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().highlightTicksBase + (getLevelPercent(level) * getConfig().highlightTicksFactor))); + } + + private int getCooldownTicks(int level) { + return Math.max(10, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + private int getDurabilityCost(ItemStack hand, int level) { + int maxDurability = Math.max(1, hand.getType().getMaxDurability()); + return Math.max(1, (int) Math.round(maxDurability * getDurabilityCostPercent(level))); + } + + private double getDurabilityCostPercent(int level) { + return Math.max(getConfig().minDurabilityCostPercent, + getConfig().durabilityCostPercentBase - (getLevelPercent(level) * getConfig().durabilityCostPercentFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click a block with an iron+ pickaxe to highlight nearby ores.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Costs Reduce Max Durability for the Pickaxe Quarry Sense adaptation.", impact = "True reduces max durability instead of adding normal damage.") + boolean costsReduceMaxDurability = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Scan Radius Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double scanRadiusBase = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Scan Radius Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double scanRadiusFactor = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Highlights Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxHighlightsBase = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Highlights Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxHighlightsFactor = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Highlight Ticks Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double highlightTicksBase = 90; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Highlight Ticks Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double highlightTicksFactor = 90; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 40; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Percent Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durabilityCostPercentBase = 0.006; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Percent Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durabilityCostPercentFactor = 0.0045; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Durability Cost Percent for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minDurabilityCostPercent = 0.001; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Found Ore for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerFoundOre = 6; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeRepairRhythm.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeRepairRhythm.java new file mode 100644 index 000000000..039026ab3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeRepairRhythm.java @@ -0,0 +1,150 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; + +import java.util.concurrent.ThreadLocalRandom; + +public class PickaxeRepairRhythm extends SimpleAdaptation { + public PickaxeRepairRhythm() { + super("pickaxe-repair-rhythm"); + registerConfiguration(PickaxeRepairRhythm.Config.class); + setDescription(Localizer.dLocalize("pickaxe.repair_rhythm.description")); + setDisplayName(Localizer.dLocalize("pickaxe.repair_rhythm.name")); + setIcon(Material.EXPERIENCE_BOTTLE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(7561); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EXPERIENCE_BOTTLE) + .key("challenge_pickaxe_rhythm_5k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_rhythm_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_rhythm_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_rhythm_5k", "pickaxe.repair-rhythm.durability-restored", 5000, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.repair_rhythm.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getRepairChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("pickaxe.repair_rhythm.lore2")); + } + + private double getRepairChance(int level) { + return Math.min(getConfig().maxChance, getConfig().chanceBase + (level * getConfig().chancePerLevel)); + } + + @EventHandler(ignoreCancelled = true) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isPickaxe(hand)) { + return; + } + + Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + if (!M.r(getRepairChance(context.level()))) { + return; + } + + if (!(hand.getItemMeta() instanceof Damageable damageable)) { + return; + } + + int damage = damageable.getDamage(); + if (damage <= 0) { + return; + } + + int min = Math.max(1, getConfig().restoreMin); + int max = Math.max(min, getConfig().restoreMax); + int restore = Math.min(damage, min == max ? min : min + ThreadLocalRandom.current().nextInt((max - min) + 1)); + damageable.setDamage(damage - restore); + hand.setItemMeta(damageable); + p.getInventory().setItemInMainHand(hand); + getPlayer(p).getData().addStat("pickaxe.repair-rhythm.durability-restored", restore); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sustained mining has a chance to restore pickaxe durability per broken block.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base chance per broken block to restore durability.", impact = "Higher values trigger repairs more often at every level.") + double chanceBase = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional repair chance gained per adaptation level.", impact = "Higher values trigger repairs more often at higher levels.") + double chancePerLevel = 0.06; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum total repair chance per broken block.", impact = "Higher values allow more frequent repairs at max level.") + double maxChance = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum durability restored per repair proc.", impact = "Higher values restore more durability per proc.") + int restoreMin = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum durability restored per repair proc.", impact = "Higher values restore more durability per proc.") + int restoreMax = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeSilkSpawner.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeSilkSpawner.java new file mode 100644 index 000000000..19bb614b9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeSilkSpawner.java @@ -0,0 +1,139 @@ +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.RNG; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Item; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; + +public class PickaxeSilkSpawner extends SimpleAdaptation { + private final RNG rng = new RNG(); + + public PickaxeSilkSpawner() { + super("pickaxe-silk-spawner"); + registerConfiguration(PickaxeSilkSpawner.Config.class); + setDescription(Localizer.dLocalize("pickaxe.silk_spawner.description")); + setDisplayName(Localizer.dLocalize("pickaxe.silk_spawner.name")); + setIcon(Material.SPAWNER); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(8444); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPAWNER) + .key("challenge_pickaxe_spawner_10") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_spawner_10.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_spawner_10.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SPAWNER) + .key("challenge_pickaxe_spawner_50") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_spawner_50.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_spawner_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_pickaxe_spawner_10", "pickaxe.silk-spawner.spawners-collected", 10, 500); + registerMilestone("challenge_pickaxe_spawner_50", "pickaxe.silk-spawner.spawners-collected", 50, 2000); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + org.bukkit.entity.Player player = event.getPlayer(); + org.bukkit.block.Block block = event.getBlock(); + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveBlockBreakContext(player, block.getLocation()); + if (!event.isDropItems() || block.getType() != Material.SPAWNER || context == null) + return; + int level = context.level(); + if (level == 1 && !player.getInventory().getItemInMainHand().getEnchantments().containsKey(Enchantment.SILK_TOUCH)) { + return; + } else if (level > 1 && !player.isSneaking()) { + return; + } + + event.setDropItems(false); + org.bukkit.inventory.ItemStack spawner = new ItemStack(Material.SPAWNER); + org.bukkit.block.BlockState state = block.getState(); + if (spawner.getItemMeta() instanceof BlockStateMeta meta) { + meta.setBlockState(state); + spawner.setItemMeta(meta); + } + + org.bukkit.Location loc = block.getLocation().add( + rng.d(-0.25D, 0.25D), + rng.d(-0.25D, 0.25D) - 0.125D, + rng.d(-0.25D, 0.25D) + ); + org.bukkit.entity.Item item = block.getWorld().createEntity(loc, Item.class); + item.setItemStack(spawner); + item.setOwner(player.getUniqueId()); + + org.bukkit.event.block.BlockDropItemEvent dropEvent = new BlockDropItemEvent(block, state, player, new KList().qadd(item)); + Bukkit.getPluginManager().callEvent(dropEvent); + if (dropEvent.isCancelled()) { + for (Item i : dropEvent.getItems()) { + if (i.isValid()) i.remove(); + } + } else { + for (Item i : dropEvent.getItems()) { + if (!i.isValid()) block.getWorld().addEntity(i); + } + getPlayer(player).getData().addStat("pickaxe.silk-spawner.spawners-collected", 1); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.silk_spawner.lore" + (level < 2 ? 1 : 2))); + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Spawners drop when broken with silk touch or while sneaking.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.95; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeStoneSkin.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeStoneSkin.java new file mode 100644 index 000000000..9833a022a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeStoneSkin.java @@ -0,0 +1,167 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; + +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class PickaxeStoneSkin extends SimpleAdaptation { + private static final Set STONE_BLOCKS = EnumSet.of( + Material.STONE, Material.COBBLESTONE, Material.MOSSY_COBBLESTONE, + Material.DEEPSLATE, Material.COBBLED_DEEPSLATE, Material.TUFF, + Material.CALCITE, Material.ANDESITE, Material.DIORITE, Material.GRANITE); + private final Map stacks = new ConcurrentHashMap<>(); + + public PickaxeStoneSkin() { + super("pickaxe-stone-skin"); + registerConfiguration(PickaxeStoneSkin.Config.class); + setDescription(Localizer.dLocalize("pickaxe.stone_skin.description")); + setDisplayName(Localizer.dLocalize("pickaxe.stone_skin.name")); + setIcon(Material.STONE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(5377); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.STONE) + .key("challenge_pickaxe_stoneskin_10k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_stoneskin_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_stoneskin_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_stoneskin_10k", "pickaxe.stone-skin.stacks-gained", 10000, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.stone_skin.lore1")); + v.addLore(C.GREEN + "" + getTierCap(level) + C.GRAY + " " + Localizer.dLocalize("pickaxe.stone_skin.lore2")); + } + + private int getTierCap(int level) { + return Math.min(level, getConfig().maxAmplifier + 1); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (!STONE_BLOCKS.contains(e.getBlock().getType())) { + return; + } + + Player p = e.getPlayer(); + if (!isPickaxe(p.getInventory().getItemInMainHand())) { + return; + } + + Adaptation.BlockActionContext context = resolveBlockBreakContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + long now = System.currentTimeMillis(); + UUID id = p.getUniqueId(); + StackState state = stacks.get(id); + int current = state == null || now > state.expiresAt() ? 0 : state.stacks(); + int blocksPerStack = Math.max(1, getConfig().blocksPerStack); + int tierCap = getTierCap(context.level()); + int next = Math.min(current + 1, tierCap * blocksPerStack); + stacks.put(id, new StackState(next, now + getConfig().stackDurationMs)); + if (next > current) { + getPlayer(p).getData().addStat("pickaxe.stone-skin.stacks-gained", 1); + } + + int tier = Math.min(next / blocksPerStack, tierCap); + if (tier <= 0) { + return; + } + + p.addPotionEffect(new PotionEffect(PotionEffectTypes.DAMAGE_RESISTANCE, getConfig().effectDurationTicks, tier - 1, false, false, true)); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + stacks.remove(e.getPlayer().getUniqueId()); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record StackState(int stacks, long expiresAt) { + } + + @NoArgsConstructor + @ConfigDescription("Breaking stone-type blocks builds short-lived stacking damage resistance.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Stone blocks that must be broken to gain one resistance tier.", impact = "Higher values make resistance tiers slower to build.") + int blocksPerStack = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds before built stacks expire without mining.", impact = "Higher values keep stacks alive longer between breaks.") + long stackDurationMs = 6000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of the applied resistance effect.", impact = "Higher values keep the resistance active longer after each break.") + int effectDurationTicks = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum resistance amplifier this adaptation can reach.", impact = "Higher values allow stronger damage reduction at max stacks.") + int maxAmplifier = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeTunnelBore.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeTunnelBore.java new file mode 100644 index 000000000..aa2f32060 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeTunnelBore.java @@ -0,0 +1,198 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +public class PickaxeTunnelBore extends SimpleAdaptation { + private static final Set BORE_BLOCKS = EnumSet.of( + Material.STONE, Material.COBBLESTONE, Material.MOSSY_COBBLESTONE, + Material.DEEPSLATE, Material.COBBLED_DEEPSLATE, Material.TUFF, + Material.CALCITE, Material.ANDESITE, Material.DIORITE, Material.GRANITE); + private static final int[] SINGLE = {0}; + private static final int[] PAIR = {0, 1}; + private static final int[] TRIPLE = {-1, 0, 1}; + + public PickaxeTunnelBore() { + super("pickaxe-tunnel-bore"); + registerConfiguration(PickaxeTunnelBore.Config.class); + setDescription(Localizer.dLocalize("pickaxe.tunnel_bore.description")); + setDisplayName(Localizer.dLocalize("pickaxe.tunnel_bore.name")); + setIcon(Material.COBBLESTONE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(8123); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE) + .key("challenge_pickaxe_tunnelbore_10k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_tunnelbore_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_tunnelbore_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_tunnelbore_10k", "pickaxe.tunnel-bore.blocks-bored", 10000, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.tunnel_bore.lore1")); + v.addLore(C.GREEN + "" + getBoreWidth(level) + "x" + getBoreHeight(level) + C.GRAY + " " + Localizer.dLocalize("pickaxe.tunnel_bore.lore2")); + v.addLore(C.RED + "- " + getConfig().durabilityPerBonusBlock + C.GRAY + " " + Localizer.dLocalize("pickaxe.tunnel_bore.lore3")); + } + + private int getBoreWidth(int level) { + return level >= 2 ? 3 : 1; + } + + private int getBoreHeight(int level) { + return level >= 3 ? 3 : 2; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + Block block = e.getBlock(); + if (!BORE_BLOCKS.contains(block.getType())) { + return; + } + + Player p = e.getPlayer(); + if (!p.isSneaking() || !isPickaxe(p.getInventory().getItemInMainHand())) { + return; + } + + Adaptation.BlockActionContext context = resolveBlockBreakContext(p, block.getLocation()); + if (context == null) { + return; + } + + List targets = collectPlane(p, block, getBoreWidth(context.level()), getBoreHeight(context.level())); + if (targets.isEmpty()) { + return; + } + + ItemStack tool = p.getInventory().getItemInMainHand().clone(); + damageHand(p, targets.size() * getConfig().durabilityPerBonusBlock); + getPlayer(p).getData().addStat("pickaxe.tunnel-bore.blocks-bored", targets.size()); + J.runEntity(p, () -> { + for (Block b : targets) { + if (!BORE_BLOCKS.contains(b.getType())) { + continue; + } + + b.breakNaturally(tool); + } + }); + } + + private List collectPlane(Player p, Block origin, int width, int height) { + List targets = new ArrayList<>(8); + int[] wOffsets = width == 1 ? SINGLE : TRIPLE; + int[] hOffsets = height == 2 ? PAIR : TRIPLE; + BlockFace facing = p.getFacing(); + float pitch = p.getLocation().getPitch(); + boolean steep = pitch > 60F || pitch < -60F; + int fx = facing.getModX(); + int fz = facing.getModZ(); + boolean widthOnX = fz != 0; + for (int h : hOffsets) { + for (int w : wOffsets) { + if (h == 0 && w == 0) { + continue; + } + + Block b; + if (steep) { + b = origin.getRelative((h * fx) - (w * fz), 0, (h * fz) + (w * fx)); + } else { + b = origin.getRelative(widthOnX ? w : 0, h, widthOnX ? 0 : w); + } + + if (!BORE_BLOCKS.contains(b.getType())) { + continue; + } + + if (!canBlockBreak(p, b.getLocation())) { + continue; + } + + targets.add(b); + } + } + + return targets; + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-mine stone-type blocks to bore a tunnel plane oriented by your facing.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Extra pickaxe durability damage taken per bonus block bored.", impact = "Higher values wear the pickaxe out faster while tunnel boring.") + int durabilityPerBonusBlock = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeUnbreakablePact.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeUnbreakablePact.java new file mode 100644 index 000000000..9e1d2635f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeUnbreakablePact.java @@ -0,0 +1,144 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; + +public class PickaxeUnbreakablePact extends SimpleAdaptation { + public PickaxeUnbreakablePact() { + super("pickaxe-unbreakable-pact"); + registerConfiguration(PickaxeUnbreakablePact.Config.class); + setDescription(Localizer.dLocalize("pickaxe.unbreakable_pact.description")); + setDisplayName(Localizer.dLocalize("pickaxe.unbreakable_pact.name")); + setIcon(Material.ANVIL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(9122); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.NETHERITE_PICKAXE) + .key("challenge_pickaxe_pact_100") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_pact_100.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_pact_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_pact_100", "pickaxe.unbreakable-pact.saves", 100, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.unbreakable_pact.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getIgnoreChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("pickaxe.unbreakable_pact.lore2")); + } + + private double getIgnoreChance(int level) { + return Math.min(getConfig().maxIgnoreChance, level * getConfig().ignoreChancePerLevel); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerItemDamageEvent e) { + ItemStack item = e.getItem(); + if (!isPickaxe(item)) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (M.r(getIgnoreChance(level))) { + e.setCancelled(true); + getPlayer(p).getData().addStat("pickaxe.unbreakable-pact.damage-ignored", e.getDamage()); + return; + } + + if (!(item.getItemMeta() instanceof Damageable damageable)) { + return; + } + + int maxDurability = item.getType().getMaxDurability(); + if (damageable.getDamage() + e.getDamage() < maxDurability) { + return; + } + + e.setCancelled(true); + damageable.setDamage(maxDurability - 1); + item.setItemMeta(damageable); + getPlayer(p).getData().addStat("pickaxe.unbreakable-pact.saves", 1); + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_ANVIL_PLACE, 0.4f, 1.8f); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Your pickaxe refuses to break, surviving at 1 durability.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Chance per level to ignore pickaxe durability loss entirely.", impact = "Higher values make the pickaxe lose durability less often at higher levels.") + double ignoreChancePerLevel = 0.04; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum total chance to ignore durability loss.", impact = "Higher values allow more durability loss to be ignored at max level.") + double maxIgnoreChance = 0.25; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeVeinminer.java b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeVeinminer.java new file mode 100644 index 000000000..b08a91353 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/pickaxe/PickaxeVeinminer.java @@ -0,0 +1,279 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.pickaxe; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.content.integration.hiddenore.HiddenOreLink; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +import static art.arcane.adapt.util.data.Metadata.VEIN_MINED; + +public class PickaxeVeinminer extends SimpleAdaptation { + public PickaxeVeinminer() { + super("pickaxe-veinminer"); + registerConfiguration(PickaxeVeinminer.Config.class); + setDescription(Localizer.dLocalize("pickaxe.vein_miner.description")); + setDisplayName(Localizer.dLocalize("pickaxe.vein_miner.name")); + setIcon(Material.IRON_PICKAXE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(8484); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_PICKAXE) + .key("challenge_pickaxe_veinminer_2500") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_veinminer_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_veinminer_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_PICKAXE) + .key("challenge_pickaxe_veinminer_20") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_veinminer_20.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_veinminer_20.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_pickaxe_veinminer_2500", "pickaxe.veinminer.ores-veinmined", 2500, 500); + } + + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.vein_miner.lore1")); + v.addLore(C.GREEN + "" + (level + getConfig().baseRange) + C.GRAY + " " + Localizer.dLocalize("pickaxe.vein_miner.lore2")); + v.addLore(C.ITALIC + Localizer.dLocalize("pickaxe.vein_miner.lore3")); + } + + private int getRadius(int lvl) { + return lvl + getConfig().baseRange; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent e) { + if (VEIN_MINED.get(e.getBlock())) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0) { + return; + } + + if (!e.getBlock().getBlockData().getMaterial().name().endsWith("_ORE")) { + if (!e.getBlock().getType().equals(Material.OBSIDIAN)) { + chainHiddenVein(e.getBlock(), p, level); + return; + } + } + VEIN_MINED.add(e.getBlock()); + + Block block = e.getBlock(); + Material targetType = block.getType(); + Location origin = block.getLocation(); + Map blockMap = new HashMap<>(); + Set queued = new HashSet<>(); + Deque queue = new ArrayDeque<>(); + queue.add(block); + queued.add(origin); + int radius = getRadius(level); + int radiusSquared = radius * radius; + int maxBlocks = Math.max(1, ((radius * 2) + 1) * ((radius * 2) + 1) * ((radius * 2) + 1)); + while (!queue.isEmpty() && blockMap.size() < maxBlocks) { + Block current = queue.poll(); + if (current == null) { + continue; + } + + Location currentLocation = current.getLocation(); + if (current.getType() != targetType || blockMap.containsKey(currentLocation)) { + continue; + } + + if (currentLocation.distanceSquared(origin) > radiusSquared || !canBlockBreak(p, currentLocation)) { + continue; + } + + blockMap.put(currentLocation, current); + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + if (x == 0 && y == 0 && z == 0) { + continue; + } + + Block next = current.getRelative(x, y, z); + if (next.getType() != targetType) { + continue; + } + + Location nextLocation = next.getLocation(); + if (nextLocation.distanceSquared(origin) > radiusSquared) { + continue; + } + + if (queued.add(nextLocation)) { + queue.add(next); + } + } + } + } + } + + int veinSize = blockMap.size(); + getPlayer(p).getData().addStat("pickaxe.veinminer.ores-veinmined", veinSize); + if (veinSize >= 20 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_pickaxe_veinminer_20")) { + getPlayer(p).getAdvancementHandler().grant("challenge_pickaxe_veinminer_20"); + } + + J.runEntity(p, () -> { + for (Location l : blockMap.keySet()) { + if (!canBlockBreak(p, l)) { + Adapt.verbose("Player " + p.getName() + " doesn't have permission."); + continue; + } + Block b = block.getWorld().getBlockAt(l); + PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("pickaxe"); + PlayerAdaptation autoSmelt = line != null ? line.getAdaptation("pickaxe-autosmelt") : null; + PlayerAdaptation drop2Inv = line != null ? line.getAdaptation("pickaxe-drop-to-inventory") : null; + VEIN_MINED.add(b); + if (autoSmelt != null && autoSmelt.getLevel() > 0 && ItemListings.getSmeltOre().contains(b.getType())) { + if (drop2Inv != null && drop2Inv.getLevel() > 0) { + PickaxeAutosmelt.autosmeltBlockDTI(b, p); + } else { + PickaxeAutosmelt.autosmeltBlock(b, p); + } + } else { + if (drop2Inv != null && drop2Inv.getLevel() > 0) { + b.getDrops(p.getInventory().getItemInMainHand(), p).forEach(item -> { + HashMap extra = p.getInventory().addItem(item); + extra.forEach((k, v) -> p.getWorld().dropItem(p.getLocation(), v)); + }); + b.setType(Material.AIR); + } else { + b.breakNaturally(p.getItemInUse()); + SoundPlayer spw = SoundPlayer.of(block.getWorld()); + spw.play(block.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 0.4f, 0.25f); + if (areParticlesEnabled()) { + block.getWorld().spawnParticle(Particle.ASH, b.getLocation().add(0.5, 0.5, 0.5), 25, 0.5, 0.5, 0.5, 0.1); + } + } + } + VEIN_MINED.remove(b); + } + VEIN_MINED.remove(block); + }); + } + + private void chainHiddenVein(Block block, Player p, int level) { + List siblings = HiddenOreLink.veinSiblings(block); + if (siblings.isEmpty()) { + return; + } + + int radius = getRadius(level); + int radiusSquared = radius * radius; + Location origin = block.getLocation(); + List targets = new ArrayList<>(); + for (Block sibling : siblings) { + if (sibling.getLocation().distanceSquared(origin) <= radiusSquared && canBlockBreak(p, sibling.getLocation())) { + targets.add(sibling); + } + } + + if (targets.isEmpty()) { + return; + } + + getPlayer(p).getData().addStat("pickaxe.veinminer.ores-veinmined", targets.size()); + J.runEntity(p, () -> { + for (Block target : targets) { + if (VEIN_MINED.get(target)) { + continue; + } + VEIN_MINED.add(target); + p.breakBlock(target); + VEIN_MINED.remove(target); + } + }); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Break connected ore veins at once while sneaking.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Pickaxe Veinminer adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.95; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Range for the Pickaxe Veinminer adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int baseRange = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedArrowRecovery.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedArrowRecovery.java new file mode 100644 index 000000000..0c4154fb0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedArrowRecovery.java @@ -0,0 +1,135 @@ +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Enchantments; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import static xyz.xenondevs.particle.utils.MathUtils.RANDOM; + +public class RangedArrowRecovery extends SimpleAdaptation { + private final Map shotArrows; + + public RangedArrowRecovery() { + super("ranged-recovery"); + registerConfiguration(RangedArrowRecovery.Config.class); + setDescription(Localizer.dLocalize("ranged.arrow_recovery.description")); + setDisplayName(Localizer.dLocalize("ranged.arrow_recovery.name")); + setIcon(Material.ARROW); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + shotArrows = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ARROW) + .key("challenge_ranged_arrow_500") + .title(Localizer.dLocalize("advancement.challenge_ranged_arrow_500.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_arrow_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_ranged_arrow_10k") + .title(Localizer.dLocalize("advancement.challenge_ranged_arrow_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_arrow_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_ranged_arrow_500", "ranged.arrow-recovery.arrows-recovered", 500, 300); + registerMilestone("challenge_ranged_arrow_10k", "ranged.arrow-recovery.arrows-recovered", 10000, 1000); + } + + @EventHandler + public void onEntityShootBow(EntityShootBowEvent event) { + if (event.getEntity() instanceof Player player && hasActiveAdaptation(player)) { + if (!event.getBow().containsEnchantment(Enchantments.ARROW_INFINITE)) { + if (event.getProjectile() instanceof Arrow arrow) { + shotArrows.put(arrow.getUniqueId(), player.getUniqueId()); + } + } + } + } + + @EventHandler + public void onProjectileHit(ProjectileHitEvent event) { + if (event.getEntity() instanceof Arrow arrow) { + UUID shooterId = shotArrows.get(arrow.getUniqueId()); + Player shooter = shooterId == null ? null : Bukkit.getPlayer(shooterId); + int level = shooter == null ? 0 : getActiveLevel(shooter); + if (level > 0) { + double chance = getConfig().hitChance[level - 1] / 100.0; + if (RANDOM.nextDouble() < chance) { + ItemStack arrowStack = new ItemStack(Material.ARROW, 1); + shooter.getInventory().addItem(arrowStack); + getPlayer(shooter).getData().addStat("ranged.arrow-recovery.arrows-recovered", 1); + Adapt.info("Arrow added to inventory."); + } + } + shotArrows.remove(arrow.getUniqueId()); + } + } + + private double chancePerLevel(int level) { + return (getConfig().hitChance[level - 1] / 100.0); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("ranged.arrow_recovery.lore1")); + v.addLore(C.GREEN + Localizer.dLocalize("ranged.arrow_recovery.lore2") + chancePerLevel(level)); + } + + @NoArgsConstructor + @ConfigDescription("Chance to recover arrows after hitting or killing an enemy.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.78; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hit Chance for the Ranged Arrow Recovery adaptation.", impact = "Add or remove entries to control which values are included.") + double[] hitChance = {10, 20, 30, 40, 50, 60, 70, 80}; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedFloaters.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedFloaters.java new file mode 100644 index 000000000..6372ffcdb --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedFloaters.java @@ -0,0 +1,166 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.concurrent.ThreadLocalRandom; + +public class RangedFloaters extends SimpleAdaptation { + public RangedFloaters() { + super("ranged-floaters"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("ranged.floaters.description")); + setDisplayName(Localizer.dLocalize("ranged.floaters.name")); + setIcon(Material.SHULKER_SHELL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHULKER_SHELL) + .key("challenge_ranged_floaters_200") + .title(Localizer.dLocalize("advancement.challenge_ranged_floaters_200.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_floaters_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_ranged_floaters_200", "ranged.floaters.targets-levitated", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getProcChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.floaters.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("ranged.floaters.lore2")); + v.addLore(C.GREEN + "+ " + (1 + getAmplifier(level)) + C.GRAY + " " + Localizer.dLocalize("ranged.floaters.lore3")); + } + + @EventHandler(priority = EventPriority.HIGH) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.ProjectileContext combat = resolveProjectileContext(e); + if (combat == null) { + return; + } + + Player p = combat.attacker(); + LivingEntity target = combat.target(); + int level = combat.level(); + if (ThreadLocalRandom.current().nextDouble() > getProcChance(level)) { + return; + } + + target.addPotionEffect(new PotionEffect( + PotionEffectType.LEVITATION, + getDurationTicks(level), + getAmplifier(level), + true, + true, + true + ), true); + getPlayer(p).getData().addStat("ranged.floaters.targets-levitated", 1); + + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particle.END_ROD, target.getLocation().add(0, 1, 0), 10, 0.2, 0.5, 0.2, 0.02); + } + + SoundPlayer.of(target.getWorld()).play(target.getLocation(), Sound.ENTITY_SHULKER_SHOOT, 0.6f, 1.45f); + xp(p, getConfig().skillXpOnProc); + } + + private double getProcChance(int level) { + return Math.min(getConfig().maxChance, getConfig().chanceBase + (getLevelPercent(level) * getConfig().chanceFactor)); + } + + private int getDurationTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().durationTicksBase + (getLevelPercent(level) * getConfig().durationTicksFactor))); + } + + private int getAmplifier(int level) { + return Math.max(0, (int) Math.floor(getLevelPercent(level) * getConfig().maxAmplifier)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Projectiles have a chance to apply levitation to targets.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Ranged Floaters adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.78; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Chance Base for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double chanceBase = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Chance Factor for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double chanceFactor = 0.58; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Chance for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxChance = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Ticks Base for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durationTicksBase = 26.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Ticks Factor for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durationTicksFactor = 110.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Amplifier for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxAmplifier = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Skill Xp On Proc for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double skillXpOnProc = 8.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedForce.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedForce.java new file mode 100644 index 000000000..6f6324d5f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedForce.java @@ -0,0 +1,155 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; + +public class RangedForce extends SimpleAdaptation { + + public RangedForce() { + super("ranged-force"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("ranged.force_shot.description")); + setDisplayName(Localizer.dLocalize("ranged.force_shot.name")); + setIcon(Material.TIPPED_ARROW); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(4900); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_force_30") + .title(Localizer.dLocalize("ranged.force_shot.advancementname")) + .description(Localizer.dLocalize("ranged.force_shot.advancementlore")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_ranged_force_500") + .title(Localizer.dLocalize("advancement.challenge_ranged_force_500.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_force_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_ranged_force_500", "ranged.force.long-range-hits", 500, 500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getSpeed(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.force_shot.lore1")); + } + + private double getSpeed(double factor) { + return (factor * getConfig().speedFactor); + } + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.ProjectileContext combat = resolveProjectileContext(e); + if (combat == null) { + return; + } + + Player p = combat.attacker(); + Location a = e.getEntity().getLocation().clone(); + Location b = p.getLocation().clone(); + a.setY(0); + b.setY(0); + xp(p, 5); + double distSq = a.distanceSquared(b); + + if (distSq > 10 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_force_30")) { + getPlayer(p).getAdvancementHandler().grant("challenge_force_30"); + xp(p, getConfig().challengeRewardLongShotReward, "challenge-long-shot"); + } + + if (distSq > 900) { + getPlayer(p).getData().addStat("ranged.force.long-range-hits", 1); + } + } + + @EventHandler + public void on(ProjectileLaunchEvent e) { + if (e.getEntity().getShooter() instanceof Player p) { + int level = getActiveLevel(p); + if (level > 0) { + double factor = getLevelPercent(level); + e.getEntity().setVelocity(e.getEntity().getVelocity().clone().multiply(1 + getSpeed(factor))); + SoundPlayer spw = SoundPlayer.of(e.getEntity().getWorld()); + spw.play(e.getEntity().getLocation(), Sound.ENTITY_SNOWBALL_THROW, 0.5f + ((float) factor * 0.25f), 0.7f + (float) (factor / 2f)); + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Shoot projectiles further and faster.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.225; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Reward Long Shot Reward for the Ranged Force adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeRewardLongShotReward = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Factor for the Ranged Force adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedFactor = 1.135; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedLungeShot.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedLungeShot.java new file mode 100644 index 000000000..b3362699e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedLungeShot.java @@ -0,0 +1,143 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.util.Vector; + +public class RangedLungeShot extends SimpleAdaptation { + public RangedLungeShot() { + super("ranged-lunge-shot"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("ranged.lunge_shot.description")); + setDisplayName(Localizer.dLocalize("ranged.lunge_shot.name")); + setIcon(Material.RABBIT_HIDE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(4859); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ARROW) + .key("challenge_ranged_lunge_200") + .title(Localizer.dLocalize("advancement.challenge_ranged_lunge_200.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_lunge_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.FEATHER) + .key("challenge_ranged_lunge_2500") + .title(Localizer.dLocalize("advancement.challenge_ranged_lunge_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_lunge_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_ranged_lunge_200", "ranged.lunge-shot.lunges", 200, 300); + registerMilestone("challenge_ranged_lunge_2500", "ranged.lunge-shot.lunges", 2500, 1000); + } + + private double getSpeed(double factor) { + return (factor * getConfig().factor); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getSpeed(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.lunge_shot.lore1")); + } + + @EventHandler + public void on(ProjectileLaunchEvent e) { + if (e.getEntity().getShooter() instanceof Player p) { + if (e.getEntity() instanceof AbstractArrow a) { + if (hasActiveAdaptation(p)) { + if (!p.isOnGround()) { + Vector velocity = p.getPlayer().getLocation().getDirection().normalize().multiply(getSpeed(getLevelPercent(p))); + p.setVelocity(p.getVelocity().subtract(velocity)); + getPlayer(p).getData().addStat("ranged.lunge-shot.lunges", 1); + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_TURTLE, 1f, 0.75f); + spw.play(p.getLocation(), Sound.ITEM_CROSSBOW_SHOOT, 1f, 1.95f); + + for (int i = 0; i < 9; i++) { + Vector v = velocity.clone().add(Vector.getRandom().subtract(Vector.getRandom()).multiply(0.3)).normalize(); + if (areParticlesEnabled()) { + + p.getWorld().spawnParticle(Particle.CLOUD, p.getLocation().clone().add(0, 1, 0), 0, v.getX(), v.getY(), v.getZ(), 0.2); + } + } + } + } + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("While falling, firing arrows launches you in a random direction.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Ranged Lunge Shot adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Factor for the Ranged Lunge Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double factor = 0.935; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedPiercing.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedPiercing.java new file mode 100644 index 000000000..95dc10f49 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedPiercing.java @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; + +import java.util.Map; +import java.util.UUID; + +public class RangedPiercing extends SimpleAdaptation { + private final Map arrowHitCounts = new java.util.concurrent.ConcurrentHashMap<>(); + + public RangedPiercing() { + super("ranged-piercing"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("ranged.arrow_piercing.description")); + setDisplayName(Localizer.dLocalize("ranged.arrow_piercing.name")); + setIcon(Material.FLETCHING_TABLE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(4791); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_ranged_piercing_500") + .title(Localizer.dLocalize("advancement.challenge_ranged_piercing_500.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_piercing_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_ranged_piercing_4") + .title(Localizer.dLocalize("advancement.challenge_ranged_piercing_4.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_piercing_4.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_ranged_piercing_500", "ranged.piercing.extra-hits", 500, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + level + C.GRAY + " " + Localizer.dLocalize("ranged.arrow_piercing.lore1")); + } + + @EventHandler + public void on(ProjectileLaunchEvent e) { + if (e.getEntity().getShooter() instanceof Player p) { + if (e.getEntity() instanceof AbstractArrow a) { + xp(p, 5); + int level = getActiveLevel(p); + if (level > 0) { + a.setPierceLevel(((AbstractArrow) e.getEntity()).getPierceLevel() + level); + } + } + } + } + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.ProjectileContext combat = resolveProjectileContext(e, projectile -> projectile instanceof AbstractArrow); + if (combat == null) { + return; + } + + AbstractArrow arrow = (AbstractArrow) combat.projectile(); + if (arrow.getPierceLevel() > 0) { + UUID arrowId = arrow.getUniqueId(); + int hits = arrowHitCounts.getOrDefault(arrowId, 0) + 1; + arrowHitCounts.put(arrowId, hits); + Player p = combat.attacker(); + if (hits > 1) { + getPlayer(p).getData().addStat("ranged.piercing.extra-hits", 1); + } + if (hits >= 4 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_ranged_piercing_4")) { + getPlayer(p).getAdvancementHandler().grant("challenge_ranged_piercing_4"); + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Projectiles pierce through multiple targets.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedPinningShot.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedPinningShot.java new file mode 100644 index 000000000..892b34fa7 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedPinningShot.java @@ -0,0 +1,215 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class RangedPinningShot extends SimpleAdaptation { + private final Map targetProcTimes = new java.util.concurrent.ConcurrentHashMap<>(); + + public RangedPinningShot() { + super("ranged-pinning-shot"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("ranged.pinning_shot.description")); + setDisplayName(Localizer.dLocalize("ranged.pinning_shot.name")); + setIcon(Material.TRIPWIRE_HOOK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2200); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ARROW) + .key("challenge_ranged_pinning_300") + .title(Localizer.dLocalize("advancement.challenge_ranged_pinning_300.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_pinning_300.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_ranged_pinning_300", "ranged.pinning-shot.targets-pinned", 300, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getProcChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.pinning_shot.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("ranged.pinning_shot.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getReapplyCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("ranged.pinning_shot.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Projectile projectile) || !(projectile.getShooter() instanceof Player p) || !(e.getEntity() instanceof LivingEntity target)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (!canDamageTarget(p, target)) { + return; + } + + long now = System.currentTimeMillis(); + cleanupExpired(now); + long reapply = getReapplyCooldownMillis(level); + Long last = targetProcTimes.get(target.getUniqueId()); + if (last != null && last + reapply > now) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getProcChance(level)) { + return; + } + + targetProcTimes.put(target.getUniqueId(), now); + target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getDurationTicks(level), getAmplifier(level), true, true, true), true); + getPlayer(p).getData().addStat("ranged.pinning-shot.targets-pinned", 1); + + if (getConfig().dampenVelocityOnProc) { + Vector v = target.getVelocity(); + target.setVelocity(new Vector(v.getX() * getConfig().horizontalVelocityFactor, v.getY(), v.getZ() * getConfig().horizontalVelocityFactor)); + } + + if (areParticlesEnabled()) { + + target.getWorld().spawnParticle(Particle.CRIT, target.getLocation().add(0, 0.9, 0), 18, 0.3, 0.45, 0.3, 0.08); + + } + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particle.ENCHANT, target.getLocation().add(0, 1.0, 0), 28, 0.35, 0.5, 0.35, 0.35); + + } + SoundPlayer sp = SoundPlayer.of(target.getWorld()); + sp.play(target.getLocation(), Sound.BLOCK_BELL_USE, 1.1f, 0.48f); + sp.play(target.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.7f, 0.55f); + xp(p, getConfig().xpOnProc); + } + + private void cleanupExpired(long now) { + if (targetProcTimes.size() < getConfig().cleanupThreshold) { + return; + } + + targetProcTimes.entrySet().removeIf(entry -> entry.getValue() + getConfig().entryTtlMillis < now); + } + + private double getProcChance(int level) { + return Math.min(getConfig().maxProcChance, getConfig().procChanceBase + (getLevelPercent(level) * getConfig().procChanceFactor)); + } + + private int getDurationTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().durationTicksBase + (getLevelPercent(level) * getConfig().durationTicksFactor))); + } + + private int getAmplifier(int level) { + return Math.max(0, (int) Math.floor(getConfig().amplifierBase + (getLevelPercent(level) * getConfig().amplifierFactor))); + } + + private long getReapplyCooldownMillis(int level) { + return Math.max(1000, (long) Math.round(getConfig().reapplyCooldownMillisBase - (getLevelPercent(level) * getConfig().reapplyCooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Projectiles can pin targets with heavy slowness.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Dampen Velocity On Proc for the Ranged Pinning Shot adaptation.", impact = "True enables this behavior and false disables it.") + boolean dampenVelocityOnProc = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.74; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Proc Chance Base for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double procChanceBase = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Proc Chance Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double procChanceFactor = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Proc Chance for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxProcChance = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Ticks Base for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durationTicksBase = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration Ticks Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durationTicksFactor = 90; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Amplifier Base for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double amplifierBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Amplifier Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double amplifierFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reapply Cooldown Millis Base for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reapplyCooldownMillisBase = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Reapply Cooldown Millis Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double reapplyCooldownMillisFactor = 2800; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Horizontal Velocity Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double horizontalVelocityFactor = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cleanup Threshold for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int cleanupThreshold = 128; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Entry Ttl Millis for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long entryTtlMillis = 60000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Proc for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnProc = 12; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedRicochetBolt.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedRicochetBolt.java new file mode 100644 index 000000000..6f0836630 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedRicochetBolt.java @@ -0,0 +1,436 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.util.Vector; + +public class RangedRicochetBolt extends SimpleAdaptation { + private static final String RICOCHET_COUNT_META = "adapt-ricochet-count"; + private static final String RICOCHET_MAX_META = "adapt-ricochet-max"; + private static final String BONUS_DAMAGE_META = "adapt-ricochet-bonus-damage"; + + public RangedRicochetBolt() { + super("ranged-ricochet-bolt"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("ranged.ricochet_bolt.description")); + setDisplayName(Localizer.dLocalize("ranged.ricochet_bolt.name")); + setIcon(Material.SPECTRAL_ARROW); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_ranged_ricochet_kills_50") + .title(Localizer.dLocalize("advancement.challenge_ranged_ricochet_kills_50.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_ricochet_kills_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_ranged_ricochet_kills_500") + .title(Localizer.dLocalize("advancement.challenge_ranged_ricochet_kills_500.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_ricochet_kills_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_ranged_ricochet_kills_50", "ranged.ricochet-bolt.ricochet-kills", 50, 500); + registerMilestone("challenge_ranged_ricochet_kills_500", "ranged.ricochet-bolt.ricochet-kills", 500, 2000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getMaxRicochets(level) + C.GRAY + " " + Localizer.dLocalize("ranged.ricochet_bolt.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getSpeedBonusPerRicochet(level), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.ricochet_bolt.lore2")); + v.addLore(C.GREEN + "+ " + Form.f(getDamageBonusPerRicochet(level), 2) + C.GRAY + " " + Localizer.dLocalize("ranged.ricochet_bolt.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(ProjectileHitEvent e) { + if (!(e.getEntity() instanceof Projectile projectile) || !(projectile.getShooter() instanceof Player p)) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (e.getHitBlock() == null || !supportsRicochet(projectile)) { + return; + } + + int ricochetCount = Math.max(0, getMetadataInt(projectile, RICOCHET_COUNT_META, 0)); + int maxRicochets = Math.max(1, getMetadataInt(projectile, RICOCHET_MAX_META, getMaxRicochets(level))); + if (ricochetCount >= maxRicochets) { + return; + } + + Vector incoming = resolveIncomingVector(projectile); + if (incoming.lengthSquared() < getConfig().minRicochetVelocitySquared) { + return; + } + + BlockFace hitFace = resolveHitFace(e, incoming); + if (hitFace == null) { + return; + } + + Vector reflectedDir = reflect(incoming.clone().normalize(), hitFace); + if (reflectedDir.lengthSquared() <= 0.0000001) { + return; + } + + reflectedDir.normalize(); + double nextSpeed = Math.max(getConfig().minimumPostBounceSpeed, incoming.length()) * (1D + getSpeedBonusPerRicochet(level)); + if (nextSpeed <= 0) { + return; + } + + Vector ricochetVelocity = reflectedDir.clone().multiply(nextSpeed); + int nextRicochetCount = ricochetCount + 1; + double bonusDamage = getMetadataDouble(projectile, BONUS_DAMAGE_META, 0D) + getDamageBonusPerRicochet(level); + + Location bounceLocation = projectile.getLocation().clone() + .add(hitFace.getDirection().normalize().multiply(getConfig().spawnOffsetFromSurface)) + .add(reflectedDir.clone().multiply(getConfig().spawnOffsetAlongDirection)); + Projectile ricochet = spawnRicochetProjectile(projectile, bounceLocation, ricochetVelocity, p); + if (ricochet == null) { + return; + } + + ricochet.setMetadata(RICOCHET_COUNT_META, new FixedMetadataValue(Adapt.instance, nextRicochetCount)); + ricochet.setMetadata(RICOCHET_MAX_META, new FixedMetadataValue(Adapt.instance, maxRicochets)); + ricochet.setMetadata(BONUS_DAMAGE_META, new FixedMetadataValue(Adapt.instance, bonusDamage)); + + Location fx = e.getHitBlock().getLocation().add(0.5, 0.5, 0.5); + if (areParticlesEnabled()) { + projectile.getWorld().spawnParticle(Particle.ELECTRIC_SPARK, fx, Math.max(1, getConfig().sparkParticleCount), + getConfig().sparkSpread, getConfig().sparkSpread, getConfig().sparkSpread, 0.02); + projectile.getWorld().spawnParticle(Particle.CRIT, fx, Math.max(1, getConfig().critParticleCount), + getConfig().critSpread, getConfig().critSpread, getConfig().critSpread, 0.08); + } + if (areSoundsEnabled()) { + SoundPlayer sp = SoundPlayer.of(projectile.getWorld()); + sp.play(fx, Sound.BLOCK_ANVIL_HIT, 0.85f, (float) Math.max(0.4, getConfig().bouncePitchBase - (nextRicochetCount * getConfig().bouncePitchDropPerRicochet))); + sp.play(fx, Sound.BLOCK_AMETHYST_BLOCK_HIT, 0.9f, (float) Math.min(2.0, getConfig().sparkPitchBase + (nextRicochetCount * getConfig().sparkPitchRaisePerRicochet))); + } + xp(p, getConfig().xpPerRicochet + (nextRicochetCount * getConfig().xpPerRicochetStep)); + getPlayer(p).getData().addStat("ranged.ricochet-bolt.total-ricochets", 1); + projectile.remove(); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Projectile projectile) || !(projectile.getShooter() instanceof Player p) || !projectile.hasMetadata(BONUS_DAMAGE_META)) { + return; + } + + if (!canDamageTarget(p, e.getEntity())) { + return; + } + + double bonusDamage = getMetadataDouble(projectile, BONUS_DAMAGE_META, 0D); + if (bonusDamage > 0 && e.getDamage() > 0) { + e.setDamage(e.getDamage() + bonusDamage); + } + } + + @EventHandler + public void on(EntityDeathEvent e) { + if (e.getEntity().getKiller() instanceof Player p && hasActiveAdaptation(p)) { + if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent dmg + && dmg.getDamager() instanceof Projectile projectile + && projectile.hasMetadata(RICOCHET_COUNT_META) + && projectile.getShooter() instanceof Player) { + getPlayer(p).getData().addStat("ranged.ricochet-bolt.ricochet-kills", 1); + } + } + } + + private Projectile spawnRicochetProjectile(Projectile original, Location spawnLocation, Vector velocity, Player shooter) { + Vector dir = velocity.clone().normalize(); + float speed = (float) Math.max(0.2, velocity.length()); + if (original instanceof SpectralArrow sourceSpectral && sourceSpectral instanceof AbstractArrow sourceAbstract) { + SpectralArrow spectral = original.getWorld().spawn(spawnLocation, SpectralArrow.class); + copyArrowState(sourceAbstract, spectral, shooter, velocity); + spectral.setGlowingTicks(sourceSpectral.getGlowingTicks()); + return spectral; + } + + if (original instanceof Trident sourceTrident) { + Trident trident = original.getWorld().spawn(spawnLocation, Trident.class); + copyArrowState(sourceTrident, trident, shooter, velocity); + trident.setItem(sourceTrident.getItem()); + return trident; + } + + if (original instanceof Arrow sourceArrow) { + Arrow arrow = original.getWorld().spawnArrow(spawnLocation, dir, speed, 0f); + copyArrowState(sourceArrow, arrow, shooter, velocity); + arrow.setBasePotionType(sourceArrow.getBasePotionType()); + sourceArrow.getCustomEffects().forEach(effect -> arrow.addCustomEffect(effect, true)); + return arrow; + } + + if (original instanceof Snowball) { + Snowball snowball = original.getWorld().spawn(spawnLocation, Snowball.class); + copyProjectileState(original, snowball, shooter, velocity); + return snowball; + } + + if (original instanceof Egg) { + Egg egg = original.getWorld().spawn(spawnLocation, Egg.class); + copyProjectileState(original, egg, shooter, velocity); + return egg; + } + + if (original instanceof EnderPearl) { + EnderPearl pearl = original.getWorld().spawn(spawnLocation, EnderPearl.class); + copyProjectileState(original, pearl, shooter, velocity); + return pearl; + } + + if (original instanceof ThrownPotion sourcePotion) { + ThrownPotion potion = original.getWorld().spawn(spawnLocation, ThrownPotion.class); + copyProjectileState(sourcePotion, potion, shooter, velocity); + potion.setItem(sourcePotion.getItem().clone()); + return potion; + } + + if (original instanceof ThrownExpBottle) { + ThrownExpBottle bottle = original.getWorld().spawn(spawnLocation, ThrownExpBottle.class); + copyProjectileState(original, bottle, shooter, velocity); + return bottle; + } + + return null; + } + + private void copyArrowState(AbstractArrow source, AbstractArrow target, Player shooter, Vector velocity) { + copyProjectileState(source, target, shooter, velocity); + target.setDamage(source.getDamage()); + target.setCritical(source.isCritical()); + target.setKnockbackStrength(source.getKnockbackStrength()); + target.setPierceLevel(source.getPierceLevel()); + target.setPickupStatus(AbstractArrow.PickupStatus.CREATIVE_ONLY); + } + + private void copyProjectileState(Projectile source, Projectile target, Player shooter, Vector velocity) { + target.setShooter(shooter); + target.setVelocity(velocity); + target.setBounce(source.doesBounce()); + target.setGravity(source.hasGravity()); + target.setFireTicks(source.getFireTicks()); + } + + private Vector reflect(Vector incoming, BlockFace face) { + Vector normal = face.getDirection().normalize(); + double dot = incoming.dot(normal); + return incoming.clone().subtract(normal.multiply(2D * dot)); + } + + private Vector resolveIncomingVector(Projectile projectile) { + Vector liveVelocity = projectile.getVelocity().clone(); + if (liveVelocity.lengthSquared() >= getConfig().minimumLiveVelocitySquared) { + return liveVelocity; + } + + Vector facing = projectile.getLocation().getDirection().clone(); + if (facing.lengthSquared() > 0.0000001) { + return facing.normalize().multiply(Math.max(getConfig().minimumPostBounceSpeed, liveVelocity.length())); + } + + return liveVelocity; + } + + private boolean supportsRicochet(Projectile projectile) { + if (projectile instanceof AbstractArrow) { + return true; + } + + return getConfig().applyToAllProjectiles + && (projectile instanceof Snowball + || projectile instanceof Egg + || projectile instanceof EnderPearl + || projectile instanceof ThrownPotion + || projectile instanceof ThrownExpBottle); + } + + private BlockFace resolveHitFace(ProjectileHitEvent e, Vector incoming) { + if (e.getHitBlockFace() != null) { + return e.getHitBlockFace(); + } + + double ax = Math.abs(incoming.getX()); + double ay = Math.abs(incoming.getY()); + double az = Math.abs(incoming.getZ()); + + if (ay >= ax && ay >= az) { + return incoming.getY() > 0 ? BlockFace.DOWN : BlockFace.UP; + } + + if (ax >= az) { + return incoming.getX() > 0 ? BlockFace.WEST : BlockFace.EAST; + } + + return incoming.getZ() > 0 ? BlockFace.NORTH : BlockFace.SOUTH; + } + + private int getMetadataInt(Projectile projectile, String key, int fallback) { + for (MetadataValue value : projectile.getMetadata(key)) { + if (value.getOwningPlugin() == Adapt.instance) { + return value.asInt(); + } + } + + return fallback; + } + + private double getMetadataDouble(Projectile projectile, String key, double fallback) { + for (MetadataValue value : projectile.getMetadata(key)) { + if (value.getOwningPlugin() == Adapt.instance) { + return value.asDouble(); + } + } + + return fallback; + } + + private int getMaxRicochets(int level) { + return Math.max(1, (int) Math.round(getConfig().maxRicochetsBase + (getLevelPercent(level) * getConfig().maxRicochetsFactor))); + } + + private double getSpeedBonusPerRicochet(int level) { + return Math.min(getConfig().maxSpeedBonusPerRicochet, + getConfig().speedBonusPerRicochetBase + (getLevelPercent(level) * getConfig().speedBonusPerRicochetFactor)); + } + + private double getDamageBonusPerRicochet(int level) { + return Math.min(getConfig().maxDamageBonusPerRicochet, + getConfig().damageBonusPerRicochetBase + (getLevelPercent(level) * getConfig().damageBonusPerRicochetFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Projectiles ricochet from block impacts with chained bounces, scaling speed, and bonus damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.74; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Ricochets Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxRicochetsBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Ricochets Factor for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxRicochetsFactor = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Bonus Per Ricochet Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedBonusPerRicochetBase = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Bonus Per Ricochet Factor for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedBonusPerRicochetFactor = 0.27; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Speed Bonus Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxSpeedBonusPerRicochet = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Per Ricochet Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageBonusPerRicochetBase = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Per Ricochet Factor for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageBonusPerRicochetFactor = 2.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Damage Bonus Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxDamageBonusPerRicochet = 3.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Ricochet Velocity Squared for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minRicochetVelocitySquared = 0.09; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Live Velocity Squared for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minimumLiveVelocitySquared = 0.0004; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Post Bounce Speed for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minimumPostBounceSpeed = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spawn Offset From Surface for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double spawnOffsetFromSurface = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spawn Offset Along Direction for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double spawnOffsetAlongDirection = 0.14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spark Particle Count for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int sparkParticleCount = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spark Spread for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sparkSpread = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Crit Particle Count for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int critParticleCount = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Crit Spread for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double critSpread = 0.14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bounce Pitch Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bouncePitchBase = 1.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bounce Pitch Drop Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bouncePitchDropPerRicochet = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spark Pitch Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sparkPitchBase = 1.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spark Pitch Raise Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sparkPitchRaisePerRicochet = 0.07; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerRicochet = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Ricochet Step for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerRicochetStep = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allow ricochet behavior to apply to throwables (snowballs, eggs, pearls, potions, exp bottles) so all supported player projectiles can bounce.", impact = "True enables universal ricochet across most player-thrown projectiles.") + boolean applyToAllProjectiles = true; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedTrajectorySight.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedTrajectorySight.java new file mode 100644 index 000000000..3440790b4 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedTrajectorySight.java @@ -0,0 +1,1032 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import fr.skytasul.glowingentities.GlowingEntities; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.player.*; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class RangedTrajectorySight extends SimpleAdaptation { + private static final double EPSILON = 0.0000001D; + private final Map drawStartedMillis = new ConcurrentHashMap<>(); + private final Map previewGlowTargets = new ConcurrentHashMap<>(); + private final Set previewCandidates = ConcurrentHashMap.newKeySet(); + private final Map previewState = new ConcurrentHashMap<>(); + private volatile RangedForce cachedRangedForce; + private volatile RangedRicochetBolt cachedRicochetBolt; + private volatile long lastPreviewCandidateRefreshMs; + + public RangedTrajectorySight() { + super("ranged-trajectory-sight"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("ranged.trajectory_sight.description")); + setDisplayName(Localizer.dLocalize("ranged.trajectory_sight.name")); + setIcon(Material.SPYGLASS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(20); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPYGLASS) + .key("challenge_ranged_trajectory_100") + .title(Localizer.dLocalize("advancement.challenge_ranged_trajectory_100.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_trajectory_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_ranged_trajectory_100", "ranged.trajectory-sight.kills-while-aiming", 100, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getVelocityMultiplier(level)) + C.GRAY + " " + Localizer.dLocalize("ranged.trajectory_sight.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getSegments(level)) + C.GRAY + " " + Localizer.dLocalize("ranged.trajectory_sight.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + Player p = e.getPlayer(); + clearPreviewState(p); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isItem(hand)) { + return; + } + + Material type = hand.getType(); + if (type != Material.BOW && type != Material.CROSSBOW) { + return; + } + + if (e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) { + drawStartedMillis.put(p.getUniqueId(), System.currentTimeMillis()); + markPreviewCandidate(p); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerItemHeldEvent e) { + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + markPreviewCandidate(p); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerSwapHandItemsEvent e) { + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + markPreviewCandidate(p); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + if (e.isSneaking()) { + markPreviewCandidate(p); + return; + } + + if (resolvePreviewContext(p) == null) { + previewCandidates.remove(p.getUniqueId()); + previewState.remove(p.getUniqueId()); + clearPreviewGlow(p); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + boolean tracked = previewCandidates.contains(id); + if (!tracked && !p.isSneaking() && !p.isHandRaised()) { + return; + } + + if (!hasActiveAdaptation(p)) { + return; + } + + markPreviewCandidate(p); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityShootBowEvent e) { + if (e.getEntity() instanceof Player p) { + drawStartedMillis.remove(p.getUniqueId()); + markPreviewCandidate(p); + } + } + + @EventHandler + public void on(EntityDeathEvent e) { + if (e.getEntity().getKiller() instanceof Player p && hasActiveAdaptation(p)) { + if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent dmg + && dmg.getDamager() instanceof Projectile projectile + && projectile.getShooter() instanceof Player) { + getPlayer(p).getData().addStat("ranged.trajectory-sight.kills-while-aiming", 1); + } + } + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + refreshPreviewCandidates(now); + if (previewCandidates.isEmpty()) { + return; + } + + Set candidates = new HashSet<>(previewCandidates); + for (UUID id : candidates) { + Player p = Bukkit.getPlayer(id); + if (p == null || !p.isOnline()) { + clearPreviewState(id); + continue; + } + + int level = getActiveLevel(p); + if (level <= 0) { + clearPreviewState(p); + continue; + } + + PreviewContext context = resolvePreviewContext(p); + if (context == null) { + previewState.remove(id); + previewCandidates.remove(id); + drawStartedMillis.remove(id); + clearPreviewGlow(p); + continue; + } + + if (context.trigger() != PreviewTrigger.DRAWING_BOW) { + drawStartedMillis.remove(p.getUniqueId()); + } + + ShotPreview shot = getShotPreview(p, context); + if (shot == null) { + clearPreviewGlow(p); + continue; + } + + if (!shouldRenderPreview(p, level, context, now)) { + continue; + } + + UUID predictedHit = renderTrajectory(p, getRenderSegments(level), shot); + updatePreviewGlow(p, predictedHit); + previewState.put(id, PreviewState.capture(now, level, context, p.getEyeLocation())); + } + } + + private void refreshPreviewCandidates(long now) { + long refreshEvery = Math.max(250L, getConfig().previewCandidateRefreshMillis); + if (now - lastPreviewCandidateRefreshMs < refreshEvery) { + return; + } + + lastPreviewCandidateRefreshMs = now; + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + + int level = getActiveLevel(p); + if (level <= 0) { + continue; + } + + if (resolvePreviewContext(p) != null) { + previewCandidates.add(p.getUniqueId()); + } + } + } + + private void markPreviewCandidate(Player p) { + if (p == null) { + return; + } + + previewCandidates.add(p.getUniqueId()); + } + + private boolean shouldRenderPreview(Player p, int level, PreviewContext context, long now) { + PreviewState state = previewState.get(p.getUniqueId()); + if (state == null) { + return true; + } + + long refreshEvery = Math.max(10L, getConfig().previewRenderIntervalMillis); + if (now - state.renderedAtMs() >= refreshEvery) { + return true; + } + + if (state.level() != level) { + return true; + } + + Material material = context.item().getType(); + if (state.material() != material || state.trigger() != context.trigger()) { + return true; + } + + Location eye = p.getEyeLocation(); + if (angleDelta(eye.getYaw(), state.yaw()) >= Math.max(0.01D, getConfig().previewYawDeltaDegrees)) { + return true; + } + + if (angleDelta(eye.getPitch(), state.pitch()) >= Math.max(0.01D, getConfig().previewPitchDeltaDegrees)) { + return true; + } + + double dx = eye.getX() - state.x(); + double dy = eye.getY() - state.y(); + double dz = eye.getZ() - state.z(); + double movedSq = (dx * dx) + (dy * dy) + (dz * dz); + return movedSq >= Math.max(0D, getConfig().previewPositionDeltaSquared); + } + + private double angleDelta(float from, float to) { + double delta = Math.abs((double) from - to) % 360D; + return delta > 180D ? 360D - delta : delta; + } + + private int getRenderSegments(int level) { + int minSegments = Math.max(6, getConfig().minimumRenderedSegments); + int maxSegments = Math.max(minSegments, getConfig().maxRenderedSegments); + int segments = Math.max(minSegments, Math.min(getSegments(level), maxSegments)); + if (Adapt.instance.getTicker() == null) { + return segments; + } + + double load = Adapt.instance.getTicker().getWindowLoadPercent(); + if (load < Math.max(0D, getConfig().previewHighLoadPercent)) { + return segments; + } + + double scale = Math.max(0.2D, Math.min(1D, getConfig().previewHighLoadSegmentScale)); + int scaled = (int) Math.round(segments * scale); + return Math.max(minSegments, Math.min(maxSegments, scaled)); + } + + private void clearPreviewState(Player p) { + if (p == null) { + return; + } + + clearPreviewState(p.getUniqueId()); + clearPreviewGlow(p); + } + + private void clearPreviewState(UUID id) { + if (id == null) { + return; + } + + drawStartedMillis.remove(id); + previewCandidates.remove(id); + previewState.remove(id); + } + + private PreviewContext resolvePreviewContext(Player p) { + ItemStack main = p.getInventory().getItemInMainHand(); + ItemStack off = p.getInventory().getItemInOffHand(); + + if (isDrawingBow(p, main)) { + return new PreviewContext(main, PreviewTrigger.DRAWING_BOW); + } + + if (!p.isSneaking()) { + return null; + } + + if (isSneakProjectile(main)) { + return new PreviewContext(main, PreviewTrigger.SNEAK_PROJECTILE); + } + + if (isSneakProjectile(off)) { + return new PreviewContext(off, PreviewTrigger.SNEAK_PROJECTILE); + } + + return null; + } + + private ShotPreview getShotPreview(Player p, PreviewContext context) { + Material launchType = context.item().getType(); + double launchVelocity = getLaunchVelocity(p, launchType); + if (launchVelocity <= 0.01) { + return null; + } + + BallisticsProfile profile = resolveBallisticsProfile(launchType); + Vector direction = applyLaunchDirectionTuning(launchType, p.getEyeLocation().getDirection().clone()); + if (direction.lengthSquared() <= EPSILON) { + return null; + } + + Vector velocity = direction.normalize().multiply(launchVelocity); + velocity.multiply(getRangedForceLaunchMultiplier(p)); + RicochetPreview ricochet = getRicochetPreview(p); + if (!supportsRicochet(launchType, ricochet)) { + ricochet = RicochetPreview.disabled(); + } + return new ShotPreview(velocity, ricochet, profile, context.trigger()); + } + + private double getLaunchVelocity(Player p, Material type) { + if (type == Material.SNOWBALL || type == Material.EGG || type == Material.ENDER_PEARL) { + return getConfig().thrownProjectileVelocity; + } + + if (type == Material.SPLASH_POTION || type == Material.LINGERING_POTION || type == Material.EXPERIENCE_BOTTLE) { + return getConfig().thrownPotionVelocity; + } + + if (type == Material.TRIDENT) { + return getConfig().tridentVelocity; + } + + if (type == Material.BOW) { + double force = getBowForce(p); + if (force <= 0) { + return 0; + } + + return force * 3.0; + } + + if (type == Material.CROSSBOW) { + return getConfig().crossbowVelocity; + } + + return getConfig().fallbackVelocity; + } + + private double getBowForce(Player p) { + UUID id = p.getUniqueId(); + long now = System.currentTimeMillis(); + long start = drawStartedMillis.computeIfAbsent(id, k -> now); + double chargeTicks = Math.max(0, (now - start) / 50.0); + + if (!p.isHandRaised() && p.isSneaking()) { + chargeTicks = getConfig().sneakPreviewChargeTicks; + } + + double force = chargeTicks / 20.0; + force = (force * force + force * 2.0) / 3.0; + return Math.min(1.0, force); + } + + private UUID renderTrajectory(Player p, int segments, ShotPreview shot) { + Location eye = p.getEyeLocation().clone(); + Location current = eye.clone().add(p.getEyeLocation().getDirection().normalize().multiply(getConfig().previewStartOffset)); + Vector velocity = shot.initialVelocity().clone(); + Color trailColor = shot.trigger() == PreviewTrigger.DRAWING_BOW + ? Color.fromRGB(120, 225, 255) + : Color.fromRGB(255, 190, 125); + int every = Math.max(1, getConfig().trailParticleEvery); + double minDistanceSq = Math.max(0, getConfig().minPreviewDistanceFromEye); + minDistanceSq *= minDistanceSq; + int ricochets = 0; + Location lastVisiblePoint = null; + UUID hitEntityId = null; + + for (int i = 0; i < segments; i++) { + Vector step = velocity.clone(); + double stepLength = step.length(); + if (stepLength <= EPSILON) { + return hitEntityId; + } + + Vector stepDirection = step.clone().normalize(); + Location from = current.clone(); + RayTraceResult entityHit = p.getWorld().rayTraceEntities(current, stepDirection, stepLength, entity -> isValidPreviewTarget(p, entity)); + RayTraceResult hit = p.getWorld().rayTraceBlocks(current, stepDirection, stepLength, FluidCollisionMode.NEVER, true); + if (isEntityFirstHit(current, hit, entityHit)) { + Entity target = entityHit.getHitEntity(); + if (target != null) { + hitEntityId = target.getUniqueId(); + } + + if (entityHit.getHitPosition() != null) { + current = entityHit.getHitPosition().toLocation(p.getWorld()); + } else if (target != null) { + current = target.getLocation().clone(); + } + + if (areParticlesEnabled() && eye.distanceSquared(current) >= minDistanceSq) { + float impactSize = getScaledParticleSize(eye.distance(current), 1.15D); + Particle.DustOptions impact = new Particle.DustOptions(Color.fromRGB(255, 236, 128), impactSize); + p.spawnParticle(Particle.DUST, current, Math.max(1, getConfig().impactParticleCount + 1), 0.02, 0.02, 0.02, 0.0, impact); + } + break; + } + + if (hit != null && hit.getHitBlock() != null) { + current = hit.getHitPosition().toLocation(p.getWorld()); + if (areParticlesEnabled()) { + if (eye.distanceSquared(current) >= minDistanceSq) { + float impactSize = getScaledParticleSize(eye.distance(current), 1.1D); + Particle.DustOptions impact = new Particle.DustOptions(Color.fromRGB(255, 236, 128), impactSize); + p.spawnParticle(Particle.DUST, current, Math.max(1, getConfig().impactParticleCount), 0.02, 0.02, 0.02, 0.0, impact); + } + } + + RicochetPreview ricochet = shot.ricochetPreview(); + if (canRicochet(ricochet, ricochets, velocity)) { + BlockFace hitFace = hit.getHitBlockFace() == null ? resolveHitFace(velocity) : hit.getHitBlockFace(); + if (hitFace == null) { + break; + } + + Vector reflectedDir = reflect(velocity.clone().normalize(), hitFace); + if (reflectedDir.lengthSquared() <= EPSILON) { + break; + } + + reflectedDir.normalize(); + double nextSpeed = Math.max(ricochet.minimumPostBounceSpeed(), velocity.length()) * (1D + ricochet.speedBonusPerRicochet()); + velocity = reflectedDir.clone().multiply(nextSpeed); + current.add(hitFace.getDirection().normalize().multiply(ricochet.spawnOffsetFromSurface())) + .add(reflectedDir.clone().multiply(ricochet.spawnOffsetAlongDirection())); + ricochets++; + if (areParticlesEnabled()) { + if (eye.distanceSquared(current) >= minDistanceSq) { + float bounceSize = getScaledParticleSize(eye.distance(current), 1.15D); + Particle.DustOptions bounce = new Particle.DustOptions(Color.fromRGB(170, 200, 255), bounceSize); + p.spawnParticle(Particle.DUST, current, Math.max(1, getConfig().impactParticleCount), 0.02, 0.02, 0.02, 0.0, bounce); + } + } + continue; + } + + break; + } + + current.add(step); + + if (i % every == 0) { + if (areParticlesEnabled()) { + if (eye.distanceSquared(current) >= minDistanceSq) { + Location delta = current.clone().subtract(from); + int subSteps = Math.max(1, getConfig().trailSubSteps); + for (int s = 1; s <= subSteps; s++) { + double f = (double) s / (double) subSteps; + Location point = from.clone().add(delta.clone().multiply(f)); + if (eye.distanceSquared(point) < minDistanceSq) { + continue; + } + + float trailSize = getScaledParticleSize(eye.distance(point), 1D); + Particle.DustOptions trail = new Particle.DustOptions(trailColor, trailSize); + p.spawnParticle(Particle.DUST, point, Math.max(1, getConfig().trailParticleCount), 0.0, 0.0, 0.0, 0.0, trail); + lastVisiblePoint = point; + } + } + } + } + + velocity.multiply(shot.profile().dragFactor()); + velocity.setY(velocity.getY() - shot.profile().gravityStep()); + } + + if (areParticlesEnabled() && lastVisiblePoint != null) { + float tipSize = getScaledParticleSize(eye.distance(lastVisiblePoint), 1.2D); + Particle.DustOptions impact = new Particle.DustOptions(Color.fromRGB(255, 236, 128), tipSize); + p.spawnParticle(Particle.DUST, lastVisiblePoint, 1, 0.0, 0.0, 0.0, 0.0, impact); + } + return hitEntityId; + } + + private BallisticsProfile resolveBallisticsProfile(Material type) { + if (type == Material.SNOWBALL || type == Material.EGG || type == Material.ENDER_PEARL) { + return new BallisticsProfile(getConfig().lightProjectileDragFactor, getConfig().lightProjectileGravityStep); + } + + if (type == Material.SPLASH_POTION || type == Material.LINGERING_POTION || type == Material.EXPERIENCE_BOTTLE) { + return new BallisticsProfile(getConfig().heavyProjectileDragFactor, getConfig().heavyProjectileGravityStep); + } + + return new BallisticsProfile(getConfig().dragFactor, getConfig().gravityStep); + } + + private Vector applyLaunchDirectionTuning(Material type, Vector direction) { + if (type == Material.SPLASH_POTION || type == Material.LINGERING_POTION || type == Material.EXPERIENCE_BOTTLE) { + direction.setY(direction.getY() - getConfig().heavyProjectilePitchDrop); + } + + return direction; + } + + private double getRangedForceLaunchMultiplier(Player p) { + RangedForce force = getRangedForceAdaptation(); + if (force == null || !force.isEnabled() || !force.getSkill().isEnabled()) { + return 1D; + } + + int level = getAdaptationLevel(p, force.getName()); + if (level <= 0) { + return 1D; + } + + double levelPercent = Math.min(1D, Math.max(0D, (double) level / (double) Math.max(1, force.getMaxLevel()))); + double speedBonus = levelPercent * force.getConfig().speedFactor; + return Math.max(0.1D, 1D + speedBonus); + } + + private RicochetPreview getRicochetPreview(Player p) { + RangedRicochetBolt ricochet = getRicochetAdaptation(); + if (ricochet == null || !ricochet.isEnabled() || !ricochet.getSkill().isEnabled()) { + return RicochetPreview.disabled(); + } + + int level = getAdaptationLevel(p, ricochet.getName()); + if (level <= 0) { + return RicochetPreview.disabled(); + } + + RangedRicochetBolt.Config cfg = ricochet.getConfig(); + double levelPercent = Math.min(1D, Math.max(0D, (double) level / (double) Math.max(1, ricochet.getMaxLevel()))); + int maxRicochets = Math.max(1, (int) Math.round(cfg.maxRicochetsBase + (levelPercent * cfg.maxRicochetsFactor))); + double speedBonus = Math.min(cfg.maxSpeedBonusPerRicochet, + cfg.speedBonusPerRicochetBase + (levelPercent * cfg.speedBonusPerRicochetFactor)); + return new RicochetPreview( + true, + maxRicochets, + speedBonus, + cfg.minRicochetVelocitySquared, + cfg.minimumPostBounceSpeed, + cfg.spawnOffsetFromSurface, + cfg.spawnOffsetAlongDirection, + cfg.applyToAllProjectiles + ); + } + + private int getAdaptationLevel(Player p, String adaptationId) { + PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("ranged"); + return line == null ? 0 : line.getAdaptationLevel(adaptationId); + } + + private RangedForce getRangedForceAdaptation() { + RangedForce cached = cachedRangedForce; + if (cached != null) { + return cached; + } + + RangedForce found = resolveRangedAdaptation(RangedForce.class); + if (found != null) { + cachedRangedForce = found; + } + return found; + } + + private RangedRicochetBolt getRicochetAdaptation() { + RangedRicochetBolt cached = cachedRicochetBolt; + if (cached != null) { + return cached; + } + + RangedRicochetBolt found = resolveRangedAdaptation(RangedRicochetBolt.class); + if (found != null) { + cachedRicochetBolt = found; + } + return found; + } + + private T resolveRangedAdaptation(Class type) { + Skill ranged = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill("ranged"); + if (ranged == null) { + return null; + } + + for (Adaptation adaptation : ranged.getAdaptations()) { + if (type.isInstance(adaptation)) { + return type.cast(adaptation); + } + } + + return null; + } + + private boolean canRicochet(RicochetPreview ricochet, int ricochetCount, Vector incomingVelocity) { + return ricochet.enabled() + && ricochetCount < ricochet.maxRicochets() + && incomingVelocity.lengthSquared() >= ricochet.minRicochetVelocitySquared(); + } + + private float getScaledParticleSize(double distance, double multiplier) { + double size = getConfig().particleSize + (Math.max(0D, distance) * getConfig().particleSizePerBlock); + size *= multiplier; + size = Math.max(0.05D, Math.min(getConfig().maxParticleSize, size)); + return (float) size; + } + + private boolean isEntityFirstHit(Location from, RayTraceResult blockHit, RayTraceResult entityHit) { + if (entityHit == null || entityHit.getHitEntity() == null) { + return false; + } + + if (blockHit == null || blockHit.getHitPosition() == null) { + return true; + } + + if (entityHit.getHitPosition() == null) { + return false; + } + + double blockDistSq = blockHit.getHitPosition().distanceSquared(from.toVector()); + double entityDistSq = entityHit.getHitPosition().distanceSquared(from.toVector()); + return entityDistSq <= blockDistSq; + } + + private boolean isValidPreviewTarget(Player shooter, Entity entity) { + return entity instanceof LivingEntity + && entity.isValid() + && !entity.isDead() + && entity.getUniqueId() != shooter.getUniqueId(); + } + + private void updatePreviewGlow(Player p, UUID targetId) { + if (!getConfig().glowPredictedTarget) { + clearPreviewGlow(p); + return; + } + + UUID viewerId = p.getUniqueId(); + UUID current = previewGlowTargets.get(viewerId); + if (current != null && current.equals(targetId)) { + return; + } + + GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); + if (glowingEntities == null) { + return; + } + + if (current != null) { + Entity stale = Bukkit.getEntity(current); + if (stale != null) { + try { + glowingEntities.unsetGlowing(stale, p); + } catch (ReflectiveOperationException ignored) { + // Ignore and continue; preview should never hard-fail from packet glow. + } + } + previewGlowTargets.remove(viewerId); + } + + if (targetId == null) { + return; + } + + Entity target = Bukkit.getEntity(targetId); + if (target == null || !target.isValid()) { + return; + } + + try { + glowingEntities.setGlowing(target, p, ChatColor.GOLD); + previewGlowTargets.put(viewerId, targetId); + } catch (ReflectiveOperationException ignored) { + // Ignore and continue; preview should never hard-fail from packet glow. + } + } + + private void clearPreviewGlow(Player p) { + UUID viewerId = p.getUniqueId(); + UUID targetId = previewGlowTargets.remove(viewerId); + if (targetId == null) { + return; + } + + GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); + if (glowingEntities == null) { + return; + } + + Entity entity = Bukkit.getEntity(targetId); + if (entity == null) { + return; + } + + try { + glowingEntities.unsetGlowing(entity, p); + } catch (ReflectiveOperationException ignored) { + // Ignore and continue; preview should never hard-fail from packet glow. + } + } + + private boolean supportsRicochet(Material launchType, RicochetPreview ricochet) { + if (!ricochet.enabled()) { + return false; + } + + if (launchType == Material.BOW || launchType == Material.CROSSBOW || launchType == Material.TRIDENT) { + return true; + } + + return ricochet.applyToAllProjectiles() + && (launchType == Material.SNOWBALL + || launchType == Material.EGG + || launchType == Material.ENDER_PEARL + || launchType == Material.SPLASH_POTION + || launchType == Material.LINGERING_POTION + || launchType == Material.EXPERIENCE_BOTTLE); + } + + private boolean isDrawingBow(Player p, ItemStack main) { + if (!isItem(main)) { + return false; + } + + Material type = main.getType(); + if (type != Material.BOW && type != Material.CROSSBOW) { + return false; + } + + if (!p.isHandRaised()) { + return false; + } + + ItemStack active = p.getItemInUse(); + return isItem(active) && active.getType() == type; + } + + private boolean isSneakProjectile(ItemStack item) { + if (!isItem(item)) { + return false; + } + + Material type = item.getType(); + return type == Material.BOW + || type == Material.CROSSBOW + || type == Material.TRIDENT + || type == Material.SNOWBALL + || type == Material.EGG + || type == Material.ENDER_PEARL + || type == Material.SPLASH_POTION + || type == Material.LINGERING_POTION + || type == Material.EXPERIENCE_BOTTLE; + } + + private Vector reflect(Vector incoming, BlockFace face) { + Vector normal = face.getDirection().normalize(); + double dot = incoming.dot(normal); + return incoming.clone().subtract(normal.multiply(2D * dot)); + } + + private BlockFace resolveHitFace(Vector incoming) { + double ax = Math.abs(incoming.getX()); + double ay = Math.abs(incoming.getY()); + double az = Math.abs(incoming.getZ()); + + if (ay >= ax && ay >= az) { + return incoming.getY() > 0 ? BlockFace.DOWN : BlockFace.UP; + } + + if (ax >= az) { + return incoming.getX() > 0 ? BlockFace.WEST : BlockFace.EAST; + } + + return incoming.getZ() > 0 ? BlockFace.NORTH : BlockFace.SOUTH; + } + + private int getSegments(int level) { + return Math.max(10, (int) Math.round(getConfig().segmentsBase + (getLevelPercent(level) * getConfig().segmentsFactor))); + } + + private double getVelocityMultiplier(int level) { + return Math.max(0.1, getConfig().velocityBase + (getLevelPercent(level) * getConfig().velocityFactor)); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private enum PreviewTrigger { + SNEAK_PROJECTILE, + DRAWING_BOW + } + + private record PreviewContext(ItemStack item, PreviewTrigger trigger) { + } + + private record BallisticsProfile(double dragFactor, double gravityStep) { + } + + private record ShotPreview(Vector initialVelocity, + RicochetPreview ricochetPreview, + BallisticsProfile profile, + PreviewTrigger trigger) { + } + + private record RicochetPreview(boolean enabled, int maxRicochets, + double speedBonusPerRicochet, + double minRicochetVelocitySquared, + double minimumPostBounceSpeed, + double spawnOffsetFromSurface, + double spawnOffsetAlongDirection, + boolean applyToAllProjectiles) { + private static RicochetPreview disabled() { + return new RicochetPreview(false, 0, 0D, Double.MAX_VALUE, 0D, 0D, 0D, false); + } + } + + private record PreviewState( + long renderedAtMs, + int level, + Material material, + PreviewTrigger trigger, + double x, + double y, + double z, + float yaw, + float pitch + ) { + private static PreviewState capture(long renderedAtMs, int level, PreviewContext context, Location eye) { + return new PreviewState( + renderedAtMs, + level, + context.item().getType(), + context.trigger(), + eye.getX(), + eye.getY(), + eye.getZ(), + eye.getYaw(), + eye.getPitch() + ); + } + } + + @NoArgsConstructor + @ConfigDescription("Preview ranged projectile flight while sneaking or drawing your shot.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Segments Base for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double segmentsBase = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Segments Factor for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double segmentsFactor = 26; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Velocity Base for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double velocityBase = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Velocity Factor for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double velocityFactor = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Gravity Step for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double gravityStep = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Step Scale for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double stepScale = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Drag Factor for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dragFactor = 0.99; + @art.arcane.adapt.util.config.ConfigDoc(value = "Drag factor used for lighter thrown projectiles (snowballs, eggs, pearls).", impact = "Higher values keep thrown arcs flatter and longer; lower values make them lose speed faster.") + double lightProjectileDragFactor = 0.99; + @art.arcane.adapt.util.config.ConfigDoc(value = "Drag factor used for heavier thrown projectiles (potions, experience bottles).", impact = "Higher values keep heavier throws moving faster; lower values shorten their travel.") + double heavyProjectileDragFactor = 0.99; + @art.arcane.adapt.util.config.ConfigDoc(value = "Gravity step used for lighter thrown projectiles (snowballs, eggs, pearls).", impact = "Higher values produce steeper arcs for light projectiles.") + double lightProjectileGravityStep = 0.03; + @art.arcane.adapt.util.config.ConfigDoc(value = "Gravity step used for heavier thrown projectiles (potions, experience bottles).", impact = "Higher values cause potions and bottles to drop faster.") + double heavyProjectileGravityStep = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Crossbow Velocity for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double crossbowVelocity = 3.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Launch velocity used for trident previews while sneaking.", impact = "Higher values extend trident prediction distance.") + double tridentVelocity = 2.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Launch velocity used for light thrown projectile previews.", impact = "Higher values extend snowball, egg, and pearl prediction distance.") + double thrownProjectileVelocity = 1.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Launch velocity used for potion and experience bottle previews.", impact = "Higher values extend heavy throw prediction distance.") + double thrownPotionVelocity = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional downward launch offset for heavy thrown projectile previews.", impact = "Higher values tilt potion and bottle trajectories downward more strongly.") + double heavyProjectilePitchDrop = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fallback Velocity for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fallbackVelocity = 1.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sneak Preview Charge Ticks for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sneakPreviewChargeTicks = 16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Particle Size for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double particleSize = 0.13; + @art.arcane.adapt.util.config.ConfigDoc(value = "How much particle size grows per block of distance from the viewer.", impact = "Higher values make far trajectory points easier to see.") + double particleSizePerBlock = 0.008; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum particle size used for the trajectory preview.", impact = "Caps distance scaling to prevent oversized particles.") + double maxParticleSize = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Trail Particle Count for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int trailParticleCount = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Impact Particle Count for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int impactParticleCount = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Trail Particle Every for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int trailParticleEvery = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum distance from the player's eye before preview particles are shown.", impact = "Higher values keep particles out of your sightline and reduce visual obstruction.") + double minPreviewDistanceFromEye = 1.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "How many interpolated points are drawn between each simulated physics step.", impact = "Higher values smooth the line while increasing particle density.") + int trailSubSteps = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Offset forward from the eye where trajectory simulation begins.", impact = "Higher values start the preview further from your face.") + double previewStartOffset = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Highlights the predicted hit target entity with per-player glow.", impact = "Enable to glow whichever entity the preview would hit first.") + boolean glowPredictedTarget = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum milliseconds between preview renders for a player when aim and context have not changed.", impact = "Lower values make previews smoother but increase CPU and ray-trace load.") + int previewRenderIntervalMillis = 75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum milliseconds between full-candidate refresh scans.", impact = "Lower values discover eligible preview players faster but increase baseline scan cost.") + int previewCandidateRefreshMillis = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Yaw delta in degrees required to force a preview recompute before the normal render interval.", impact = "Lower values react to small camera turns; higher values reduce recompute frequency.") + double previewYawDeltaDegrees = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Pitch delta in degrees required to force a preview recompute before the normal render interval.", impact = "Lower values react to small vertical aim changes; higher values reduce recompute frequency.") + double previewPitchDeltaDegrees = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Movement distance squared required to force a preview recompute before the normal render interval.", impact = "Lower values recompute more while strafing; higher values reduce recomputes while moving.") + double previewPositionDeltaSquared = 0.0125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Lowest number of simulation segments used when rendering a trajectory.", impact = "Higher values preserve long previews under load; lower values cut work more aggressively.") + int minimumRenderedSegments = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on simulation segments used for rendering.", impact = "Lower values reduce ray-trace workload; higher values produce longer and more precise previews.") + int maxRenderedSegments = 36; + @art.arcane.adapt.util.config.ConfigDoc(value = "Ticker load percentage at which trajectory segments are scaled down.", impact = "Lower values engage load-shedding sooner; higher values preserve preview fidelity longer.") + double previewHighLoadPercent = 42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Segment scale applied once high-load shedding is active.", impact = "Lower values reduce trajectory compute cost more aggressively during load spikes.") + double previewHighLoadSegmentScale = 0.7; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedWebBomb.java b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedWebBomb.java new file mode 100644 index 000000000..c5bac17ac --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/ranged/RangedWebBomb.java @@ -0,0 +1,283 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.ranged; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.api.recipe.MaterialChar; +import art.arcane.adapt.content.item.BoundSnowBall; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Player; +import org.bukkit.entity.Snowball; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPistonRetractEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class RangedWebBomb extends SimpleAdaptation { + private static final BlockData AIR = Material.AIR.createBlockData(); + private static final BlockData BLOCK = Material.COBWEB.createBlockData(); + private final Map activeSnowballs; + private final Set activeBlocks; + + public RangedWebBomb() { + super("ranged-webshot"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("ranged.web_shot.description")); + setDisplayName(Localizer.dLocalize("ranged.web_shot.name")); + setIcon(Material.COBWEB); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(4900); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerRecipe(AdaptRecipe.shaped() + .key("ranged-web-bomb") + .ingredient(new MaterialChar('I', Material.COBWEB)) + .ingredient(new MaterialChar('S', Material.SNOWBALL)) + .shapes(List.of( + "III", + "ISI", + "III")) + .result(BoundSnowBall.io.withData(new BoundSnowBall.Data(null))) + .build()); + activeBlocks = ConcurrentHashMap.newKeySet(); + activeSnowballs = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COBWEB) + .key("challenge_ranged_web_200") + .title(Localizer.dLocalize("advancement.challenge_ranged_web_200.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_web_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_ranged_web_200", "ranged.web-bomb.mobs-trapped", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("ranged.web_shot.lore1")); + v.addLore(C.YELLOW + "+ " + level + C.GRAY + " " + Localizer.dLocalize("ranged.web_shot.lore2")); + } + + + @EventHandler + public void on(ProjectileHitEvent e) { + if (!(e.getEntity() instanceof Snowball snowball)) { + return; + } + UUID shooterId = activeSnowballs.remove(snowball.getUniqueId()); + if (shooterId == null) { + return; + } + Player p = Bukkit.getPlayer(shooterId); + if (p == null || !p.isOnline()) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + Block block; + + if (e.getHitEntity() != null) { + block = e.getHitEntity().getLocation().add(0, 1, 0).getBlock(); + } else if (e.getHitBlock() != null) { + block = e.getHitBlock().getLocation().add(0, 1, 0).getBlock(); + } else { + block = e.getEntity().getLocation().add(0, 1, 0).getBlock(); + } + + vfxCuboidOutline(block, Particle.REVERSE_PORTAL); + Adapt.verbose("Snowball Got: " + snowball.getEntityId() + " " + snowball.getUniqueId()); + Adapt.verbose("Detected snowball hit"); + if (e.getHitEntity() != null) { + getPlayer(p).getData().addStat("ranged.web-bomb.mobs-trapped", 1); + } + snowball.remove(); + Set locs = new HashSet<>(); + locs.add(block.getLocation().add(0, 1, 0).getBlock()); + locs.add(block.getLocation().add(0, -1, 0).getBlock()); + locs.add(block.getLocation().add(0, 0, 1).getBlock()); + locs.add(block.getLocation().add(0, 0, -1).getBlock()); + locs.add(block.getLocation().add(1, 0, 0).getBlock()); + locs.add(block.getLocation().add(-1, 0, 0).getBlock()); + + for (Block i : locs) { + addWebFoundation(i, level); + } + } + + + @EventHandler + public void on(ProjectileLaunchEvent e) { + if (e.getEntity().getShooter() instanceof Player p && e.getEntity() instanceof Snowball snowball && hasActiveAdaptation(p)) { + Adapt.verbose("Snowball Launched: " + snowball.getEntityId() + " " + snowball.getUniqueId()); + if (BoundSnowBall.isBindableItem(snowball.getItem())) { + Adapt.verbose("Snowball is bound"); + activeSnowballs.put(snowball.getUniqueId(), p.getUniqueId()); + } else { + Adapt.verbose("Snowball is not bound"); + } + } + } + + public void addWebFoundation(Block block, int seconds) { + if (!block.getType().isAir()) { + return; + } + + J.runAt(block.getLocation(), () -> { + block.setBlockData(BLOCK); + activeBlocks.add(block); + }); + SoundPlayer spw = SoundPlayer.of(block.getWorld()); + spw.play(block.getLocation(), Sound.BLOCK_ROOTED_DIRT_PLACE, 1.0f, 1.0f); + if (areParticlesEnabled()) { + + vfxCuboidOutline(block, Particle.CLOUD); + vfxCuboidOutline(block, Particle.WHITE_ASH); + } + J.runAt(block.getLocation(), () -> removeFoundation(block), seconds * 16); + } + + public void removeFoundation(Block block) { + if (!block.getBlockData().equals(BLOCK)) { + return; + } + + J.runAt(block.getLocation(), () -> { + block.setBlockData(AIR); + activeBlocks.remove(block); + }); + SoundPlayer spw = SoundPlayer.of(block.getWorld()); + spw.play(block.getLocation(), Sound.BLOCK_ROOTED_DIRT_BREAK, 1.0f, 1.0f); + if (areParticlesEnabled()) { + vfxCuboidOutline(block, Particles.ENCHANTMENT_TABLE); + } + } + + + //prevent piston from moving blocks // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockPistonExtendEvent e) { + e.getBlocks().forEach(b -> { + if (activeBlocks.contains(b)) { + Adapt.verbose("Cancelled Piston Extend on Adaptation Foundation Block"); + e.setCancelled(true); + } + }); + } + + //prevent piston from pulling blocks // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockPistonRetractEvent e) { + e.getBlocks().forEach(b -> { + if (activeBlocks.contains(b)) { + Adapt.verbose("Cancelled Piston Retract on Adaptation Foundation Block"); + e.setCancelled(true); + } + }); + } + + //prevent TNT from destroying blocks // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockExplodeEvent e) { + if (activeBlocks.contains(e.getBlock())) { + Adapt.verbose("Cancelled Block Explosion on Adaptation Foundation Block"); + e.setCancelled(true); + } + } + + //prevent block from being destroyed // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockBreakEvent e) { + if (activeBlocks.contains(e.getBlock())) { + e.setCancelled(true); + } + } + + //prevent Entities from destroying blocks // Dupe fix + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityExplodeEvent e) { + e.blockList().removeIf(activeBlocks::contains); + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Throw a crafted web snare to trap targets in cobwebs.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Ranged Web Bomb adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.9; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftAccess.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftAccess.java new file mode 100644 index 000000000..c0a6b6e6d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftAccess.java @@ -0,0 +1,353 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.content.item.BoundEnderPearl; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import manifold.rt.api.util.Pair; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.*; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import static art.arcane.adapt.api.adaptation.chunk.ChunkLoading.loadChunkAsync; + +public class RiftAccess extends SimpleAdaptation { + private final Map, List> activeViewsMap = new ConcurrentHashMap<>(); + private final Map tickets = new ConcurrentHashMap<>(); + + public RiftAccess() { + super("rift-access"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.remote_access.description")); + setDisplayName(Localizer.dLocalize("rift.remote_access.name")); + setMaxLevel(1); + setIcon(Material.NETHER_STAR); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setInitialCost(getConfig().initialCost); + setInterval(1000); + registerRecipe(AdaptRecipe.shapeless() + .key("rift-remote-access") + .ingredient(Material.ENDER_PEARL) + .ingredient(Material.COMPASS) + .result(BoundEnderPearl.io.withData(new BoundEnderPearl.Data(null))) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_rift_access_100") + .title(Localizer.dLocalize("advancement.challenge_rift_access_100.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_access_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_CHEST) + .key("challenge_rift_access_2500") + .title(Localizer.dLocalize("advancement.challenge_rift_access_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_access_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_access_100", "rift.access.remote-opens", 100, 300); + registerMilestone("challenge_rift_access_2500", "rift.access.remote-opens", 2500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.ITALIC + Localizer.dLocalize("rift.remote_access.lore1")); + v.addLore(C.ITALIC + Localizer.dLocalize("rift.remote_access.lore2")); + v.addLore(C.ITALIC + Localizer.dLocalize("rift.remote_access.lore3")); + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + ItemStack mainHand = p.getInventory().getItemInMainHand(); + ItemStack offHand = p.getInventory().getItemInOffHand(); + Block block = e.getClickedBlock(); + + boolean mainHandBound = BoundEnderPearl.isBindableItem(mainHand); + boolean offHandBound = BoundEnderPearl.isBindableItem(offHand); + + // Cancel event if the enderpearl is in the offhand + if (offHandBound && e.getHand() != null && e.getHand().equals(EquipmentSlot.OFF_HAND)) { + e.setCancelled(true); + return; + } + + // If the main hand is holding a bound enderpearl + if (mainHandBound) { + e.setCancelled(true); + if (e.getHand() == EquipmentSlot.HAND && hasActiveAdaptation(p)) { + Adapt.verbose("Player using bound enderpearl."); + handleEnderPearlInteraction(e, p, block); + } + } + } + + private void handleEnderPearlInteraction(PlayerInteractEvent event, Player player, Block block) { + boolean canUseInCreative = AdaptConfig.get().allowAdaptationsInCreative; + boolean isCreative = player.getGameMode() == GameMode.CREATIVE; + boolean sneaking = player.isSneaking(); + boolean allowed = canUseInCreative || !isCreative; + + + // Check if the player is allowed to use the bound item in creative + if (!allowed) { + Adapt.info("Player " + player.getName() + " tried to use the bound item in creative mode."); + return; + } + + Action action = event.getAction(); + if (action == Action.LEFT_CLICK_BLOCK || action == Action.LEFT_CLICK_AIR) { + if (!sneaking) { + return; + } + + Block target = action == Action.LEFT_CLICK_BLOCK ? block : player.getTargetBlockExact(5); + if (target == null || !isStorage(target.getBlockData())) { + return; + } + + if (canAccessChest(player, target.getLocation())) { + linkPearl(player, target, event); + } else { + Adapt.verbose("Player " + player.getName() + " doesn't have permission."); + } + } else if (action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK) { + openPearl(player); + } + } + + private void linkPearl(Player p, Block block, PlayerInteractEvent event) { + event.setCancelled(true); + if (areParticlesEnabled()) { + vfxCuboidOutline(block, Particle.REVERSE_PORTAL); + } + ItemStack hand = p.getInventory().getItemInMainHand(); + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_CLOSE, 0.5f, 0.8f); + + if (hand.getAmount() == 1) { + BoundEnderPearl.setData(hand, block); + } else { + hand.setAmount(hand.getAmount() - 1); + ItemStack pearl = BoundEnderPearl.withData(block); + p.getInventory().addItem(pearl).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); + } + } + + private void openPearl(Player p) { + SoundPlayer sp = SoundPlayer.of(p); + Block b = BoundEnderPearl.getBlock(p.getInventory().getItemInMainHand()); + if (b == null || !canAccessChest(p, b.getLocation())) { + sp.play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f); + return; + } + loadChunkAsync(b.getLocation(), chunk -> { + if (Bukkit.getPluginManager().isPluginEnabled("AdvancedChests") && + AdvancedChestsAPI.getChestManager().getAdvancedChest(b.getLocation()) != null) { + AdvancedChestsAPI.getChestManager().getAdvancedChest(b.getLocation()).openPage(p, 1); + Adapt.verbose("Opening AdvancedChests GUI"); + } else if (b.getState() instanceof InventoryHolder holder) { + InventoryView view = p.openInventory(holder.getInventory()); + if (view == null) return; + activeViewsMap.computeIfAbsent(Pair.make(new ChunkPos(chunk).add(), b.getLocation()), k -> new ArrayList<>()).add(view); + } + sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); + sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); + getPlayer(p).getData().addStat("rift.access.remote-opens", 1); + }); + } + + @Override + public void onTick() { + checkActiveViews(); + } + + private void checkActiveViews() { + Iterator, List>> mapIterator = activeViewsMap.entrySet().iterator(); + while (mapIterator.hasNext()) { + Map.Entry, List> entry = mapIterator.next(); + removeInvalidViews(entry); + removeEntryIfViewsEmpty(mapIterator, entry); + } + } + + private void removeInvalidViews(Map.Entry, List> entry) { + List views = entry.getValue(); + for (int ii = views.size() - 1; ii >= 0; ii--) { + InventoryView i = views.get(ii); + if (shouldRemoveView(i)) { + views.remove(ii); + } + } + } + + private boolean shouldRemoveView(InventoryView i) { + Location location = i.getTopInventory().getLocation(); + return !i.getPlayer().getOpenInventory().equals(i) || (location == null || !isStorage(location.getBlock().getBlockData())); + } + + private void removeEntryIfViewsEmpty(Iterator, List>> mapIterator, Map.Entry, List> entry) { + List views = entry.getValue(); + if (views.isEmpty()) { + mapIterator.remove(); + entry.getKey().getFirst().remove(); + } + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBurnEvent event) { + invClose(event.getBlock()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockPistonRetractEvent event) { + for (Block b : event.getBlocks()) { + invClose(b); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockPistonExtendEvent event) { + for (Block b : event.getBlocks()) { + invClose(b); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockExplodeEvent event) { + for (Block b : event.blockList()) { + invClose(b); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent event) { + invClose(event.getBlock()); + } + + + private void invClose(Block block) { + List views = activeViewsMap.get(block.getLocation()); + if (views != null) { + for (InventoryView view : views) { + view.getPlayer().closeInventory(); + } + activeViewsMap.remove(block.getLocation()); + } + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft a Reliquary Portkey to access marked containers remotely.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Rift Access adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 15; + } + + @EqualsAndHashCode + private class ChunkPos { + @EqualsAndHashCode.Exclude + private final WeakReference world; + private final String name; + private final int x, z; + + private ChunkPos(Chunk chunk) { + this.world = new WeakReference<>(chunk.getWorld()); + this.name = chunk.getWorld().getName(); + this.x = chunk.getX(); + this.z = chunk.getZ(); + } + + public ChunkPos add() { + World world = this.world.get(); + if (world == null) return this; + if (tickets.computeIfAbsent(this, k -> new AtomicInteger()).getAndIncrement() == 0) + world.addPluginChunkTicket(x, z, Adapt.instance); + return this; + } + + public void remove() { + World world = this.world.get(); + if (world == null) { + tickets.remove(this); + return; + } + if (tickets.computeIfAbsent(this, k -> new AtomicInteger()).decrementAndGet() <= 0) { + world.removePluginChunkTicket(x, z, Adapt.instance); + world.unloadChunkRequest(x, z); + tickets.remove(this); + } + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftBlink.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftBlink.java new file mode 100644 index 000000000..97d722296 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftBlink.java @@ -0,0 +1,589 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.content.event.AdaptAdaptationTeleportEvent; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.*; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import static art.arcane.adapt.api.adaptation.chunk.ChunkLoading.loadChunkAsync; + + +public class RiftBlink extends SimpleAdaptation { + private final Map lastBlink = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map jumpArmUntil = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map lastOnGround = new java.util.concurrent.ConcurrentHashMap<>(); + + public RiftBlink() { + super("rift-blink"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.blink.description")); + setDisplayName(Localizer.dLocalize("rift.blink.name")); + setIcon(Material.FEATHER); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(9288); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_blink_500") + .title(Localizer.dLocalize("advancement.challenge_rift_blink_500.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_blink_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_rift_blink_5k") + .title(Localizer.dLocalize("advancement.challenge_rift_blink_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_blink_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_blink_500", "rift.blink.blinks", 500, 400); + registerMilestone("challenge_rift_blink_5k", "rift.blink.distance-blinked", 5000, 1500); + } + + private double getBlinkDistance(int level) { + return getConfig().baseDistance + (getLevelPercent(level) * getConfig().distanceFactor); + } + + private long getCooldownDuration() { + return Math.max(0L, getConfig().cooldownMillis); + } + + private boolean isBlinkEligible(Player p) { + return hasActiveAdaptation(p) && p.getGameMode() == GameMode.SURVIVAL; + } + + private boolean isOnCooldown(UUID id) { + return M.ms() - lastBlink.getOrDefault(id, 0L) <= getCooldownDuration(); + } + + private void clearDoubleJumpArm(Player p, UUID id) { + if (jumpArmUntil.remove(id) == null) { + return; + } + + if (p.getGameMode() == GameMode.SURVIVAL) { + p.setAllowFlight(false); + p.setFlying(false); + } + } + + private void armDoubleJump(Player p, UUID id) { + int triggerWindowMillis = Math.max(150, getConfig().doubleJumpWindowMillis); + long expires = M.ms() + triggerWindowMillis; + jumpArmUntil.put(id, expires); + p.setAllowFlight(true); + J.runEntity(p, () -> { + if (!p.isOnline()) { + return; + } + + Long armUntil = jumpArmUntil.get(id); + if (armUntil != null && armUntil <= M.ms()) { + clearDoubleJumpArm(p, id); + } + }, Math.max(1, (int) Math.ceil(triggerWindowMillis / 50D))); + } + + private boolean isClickAction(Action action) { + return action == Action.LEFT_CLICK_AIR + || action == Action.LEFT_CLICK_BLOCK + || action == Action.RIGHT_CLICK_AIR + || action == Action.RIGHT_CLICK_BLOCK; + } + + private boolean isLeftClick(Action action) { + return action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK; + } + + private boolean isRightClick(Action action) { + return action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK; + } + + private boolean isBlockClick(Action action) { + return action == Action.LEFT_CLICK_BLOCK || action == Action.RIGHT_CLICK_BLOCK; + } + + private boolean isActionAllowed(Action action) { + if (!getConfig().allowAirClicks && !isBlockClick(action)) { + return false; + } + + if (!getConfig().allowBlockClicks && isBlockClick(action)) { + return false; + } + + return true; + } + + private boolean shouldTriggerSprintClick(PlayerInteractEvent e) { + if (!getConfig().enableSprintClickTrigger || !e.getPlayer().isSprinting()) { + return false; + } + + if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { + return false; + } + + Action action = e.getAction(); + if (!isClickAction(action) || !isActionAllowed(action)) { + return false; + } + + if (isLeftClick(action) && !getConfig().sprintClickLeftClick) { + return false; + } + + return !isRightClick(action) || getConfig().sprintClickRightClick; + } + + private boolean shouldTriggerPearlClick(PlayerInteractEvent e) { + if (!getConfig().enableEnderPearlClickTrigger || e.getItem() == null || e.getItem().getType() != Material.ENDER_PEARL) { + return false; + } + + Action action = e.getAction(); + if (!isClickAction(action) || !isActionAllowed(action)) { + return false; + } + + if (isLeftClick(action) && !getConfig().enderPearlClickLeftClick) { + return false; + } + + return !isRightClick(action) || getConfig().enderPearlClickRightClick; + } + + private Location findBlinkGround(Player player) { + Location start = player.getLocation().clone(); + Vector direction = start.getDirection().clone().setY(0); + if (direction.lengthSquared() <= 0.0001) { + double yawRadians = Math.toRadians(start.getYaw()); + direction = new Vector(-Math.sin(yawRadians), 0, Math.cos(yawRadians)); + } + direction.normalize(); + + int maxVerticalAdjustment = Math.max(0, getConfig().maxVerticalAdjustment); + double step = Math.max(0.25, getConfig().distanceSearchStep); + double maxDistance = getBlinkDistance(getLevel(player)); + + for (double distance = maxDistance; distance >= 1; distance -= step) { + Location horizontalTarget = start.clone().add(direction.clone().multiply(distance)); + Location safe = findSafeGroundNear(horizontalTarget, maxVerticalAdjustment); + if (safe != null) { + return safe; + } + } + + return null; + } + + private Location findSafeGroundNear(Location base, int maxVerticalAdjustment) { + if (isSafe(base)) { + return base; + } + + for (int y = 1; y <= maxVerticalAdjustment; y++) { + Location down = base.clone().subtract(0, y, 0); + if (isSafe(down)) { + return down; + } + + Location up = base.clone().add(0, y, 0); + if (isSafe(up)) { + return up; + } + } + + return null; + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + (getBlinkDistance(level)) + C.GRAY + " " + Localizer.dLocalize("rift.blink.lore1")); + if (getConfig().pearlConsumeChance > 0) { + v.addLore(C.RED + "* " + Form.pc(getConfig().pearlConsumeChance, 0) + C.GRAY + " " + Localizer.dLocalize("rift.blink.lore_cost_pearl")); + } + java.util.List combos = getTriggerCombos(); + if (combos.isEmpty()) { + v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + "none"); + return; + } + + for (String combo : combos) { + v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + combo); + } + } + + @Override + public String getDescription() { + return "Short-ranged instant teleportation to safe ground. " + summarizeTriggerDescription(); + } + + private String summarizeTriggerDescription() { + java.util.List combos = getTriggerCombos(); + if (combos.isEmpty()) { + return "No active triggers are currently enabled."; + } + + if (combos.size() == 1) { + return "Trigger: " + combos.get(0) + "."; + } + + if (combos.size() == 2) { + return "Triggers: " + combos.get(0) + " or " + combos.get(1) + "."; + } + + return "Triggers: " + combos.get(0) + ", " + combos.get(1) + ", +" + (combos.size() - 2) + " more."; + } + + private java.util.List getTriggerCombos() { + java.util.List triggers = new java.util.ArrayList<>(); + String clickSurface = getClickSurfaceLabel(); + if (getConfig().enableDoubleJumpTrigger) { + triggers.add(getConfig().doubleJumpRequiresSprint ? "Double Jump + Sprint" : "Double Jump"); + } + + if (getConfig().enableSprintClickTrigger) { + appendClickCombos(triggers, "Sprint", getConfig().sprintClickLeftClick, getConfig().sprintClickRightClick, clickSurface); + } + + if (getConfig().enableSingleSneakTrigger) { + triggers.add(getConfig().singleSneakRequiresSprint ? "Sprint + Sneak" : "Sneak"); + } + + if (getConfig().enableEnderPearlClickTrigger) { + appendClickCombos(triggers, "Ender Pearl", getConfig().enderPearlClickLeftClick, getConfig().enderPearlClickRightClick, clickSurface); + } + + return triggers; + } + + private void appendClickCombos(java.util.List triggers, String prefix, boolean allowLeft, boolean allowRight, String clickSurface) { + if (clickSurface.isBlank()) { + return; + } + + if (allowLeft) { + triggers.add(prefix + " + Left Click" + clickSurface); + } + + if (allowRight) { + triggers.add(prefix + " + Right Click" + clickSurface); + } + } + + private String getClickSurfaceLabel() { + if (getConfig().allowAirClicks && getConfig().allowBlockClicks) { + return " (air/block)"; + } + + if (getConfig().allowAirClicks) { + return " (air)"; + } + + if (getConfig().allowBlockClicks) { + return " (block)"; + } + + return ""; + } + + @EventHandler + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + lastBlink.remove(id); + jumpArmUntil.remove(id); + lastOnGround.remove(id); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerToggleFlightEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + if (!isBlinkEligible(p) || !getConfig().enableDoubleJumpTrigger) { + return; + } + + Long armUntil = jumpArmUntil.get(id); + if (armUntil == null) { + return; + } + + e.setCancelled(true); + p.setFlying(false); + clearDoubleJumpArm(p, id); + if (armUntil > M.ms()) { + attemptBlink(p); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + if (!isBlinkEligible(p)) { + return; + } + + if (shouldTriggerPearlClick(e)) { + e.setCancelled(true); + attemptBlink(p); + return; + } + + if (shouldTriggerSprintClick(e)) { + attemptBlink(p); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + if (!e.isSneaking() || !isBlinkEligible(p) || !getConfig().enableSingleSneakTrigger) { + return; + } + + if (getConfig().singleSneakRequiresSprint && !p.isSprinting()) { + return; + } + + attemptBlink(p); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + boolean wasOnGround = lastOnGround.getOrDefault(id, true); + boolean onGround = p.isOnGround(); + lastOnGround.put(id, onGround); + + if (!isBlinkEligible(p) || !getConfig().enableDoubleJumpTrigger) { + clearDoubleJumpArm(p, id); + return; + } + + if (!wasOnGround && onGround) { + clearDoubleJumpArm(p, id); + return; + } + + if (isOnCooldown(id)) { + clearDoubleJumpArm(p, id); + return; + } + + if (isDoubleJumpStart(wasOnGround, onGround, p)) { + if (getConfig().doubleJumpRequiresSprint && !p.isSprinting()) { + return; + } + + armDoubleJump(p, id); + return; + } + + Long armUntil = jumpArmUntil.get(id); + if (armUntil != null && armUntil <= M.ms()) { + clearDoubleJumpArm(p, id); + } + } + + private boolean isDoubleJumpStart(boolean wasOnGround, boolean onGround, Player p) { + return wasOnGround + && !onGround + && p.getVelocity().getY() >= getConfig().doubleJumpMinVerticalVelocity; + } + + private boolean attemptBlink(Player p) { + UUID id = p.getUniqueId(); + if (isOnCooldown(id)) { + return false; + } + + Location locOG = p.getLocation().clone(); + SoundPlayer spw = SoundPlayer.of(p); + Location destinationGround = findBlinkGround(p); + if (destinationGround == null) { + spw.play(p.getLocation(), Sound.BLOCK_CONDUIT_DEACTIVATE, 1f, 1.24f); + lastBlink.put(id, M.ms()); + return false; + } + + consumeBlinkPearl(p); + PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("rift"); + PlayerAdaptation adaptation = line != null ? line.getAdaptation("rift-resist") : null; + if (adaptation != null && adaptation.getLevel() > 0) { + RiftResist.riftResistStackAdd(p, 10, 5); + } + + if (areParticlesEnabled()) { + vfxParticleLine(locOG, destinationGround, Particle.REVERSE_PORTAL, 50, 8, 0.1D, 1D, 0.1D, 0D, null, false, l -> l.getBlock().isPassable()); + } + + Vector v = p.getVelocity().clone(); + loadChunkAsync(destinationGround, chunk -> J.runEntity(p, () -> { + Location toLoc = destinationGround.clone().add(0, 1, 0); + + AdaptAdaptationTeleportEvent event = new AdaptAdaptationTeleportEvent(!Bukkit.isPrimaryThread(), getPlayer(p), this, locOG, destinationGround.clone()); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + + J.teleport(p, toLoc, PlayerTeleportEvent.TeleportCause.PLUGIN); + p.setVelocity(v.multiply(3)); + })); + + getPlayer(p).getData().addStat("rift.teleports", 1); + getPlayer(p).getData().addStat("rift.blink.blinks", 1); + getPlayer(p).getData().addStat("rift.blink.distance-blinked", (int) locOG.distance(destinationGround)); + lastBlink.put(id, M.ms()); + spw.play(p.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.50f, 1.0f); + vfxLevelUp(p); + return true; + } + + private void consumeBlinkPearl(Player p) { + double chance = Math.max(0D, Math.min(1D, getConfig().pearlConsumeChance)); + if (chance <= 0D || ThreadLocalRandom.current().nextDouble() >= chance) { + return; + } + + for (ItemStack stack : p.getInventory().getContents()) { + if (stack == null || stack.getType() != Material.ENDER_PEARL || stack.hasItemMeta()) { + continue; + } + + stack.setAmount(stack.getAmount() - 1); + return; + } + } + + private boolean isSafe(Location l) { + return l.getBlock().getType().isSolid() + && !l.getBlock().getRelative(BlockFace.UP).getType().isSolid() + && !l.getBlock().getRelative(BlockFace.UP).getRelative(BlockFace.UP).getType().isSolid(); + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Short-ranged instant teleportation by double-tapping jump while sprinting.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Rift Blink adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown between successful Rift Blink triggers in milliseconds.", impact = "Higher values reduce blink frequency; lower values allow faster reuse.") + int cooldownMillis = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Chance per successful blink to consume one plain ender pearl from the inventory.", impact = "Higher values make blinking drain pearls faster; 0 disables the pearl cost.") + double pearlConsumeChance = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables double-tap jump detection for Rift Blink.", impact = "True allows jump-based activation; false disables jump activation.") + boolean enableDoubleJumpTrigger = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Require sprinting for the double-tap jump trigger.", impact = "True requires sprinting while double-tapping jump; false allows it without sprint.") + boolean doubleJumpRequiresSprint = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum time window between jump taps in milliseconds.", impact = "Higher values make double-tap detection easier; lower values make it stricter.") + int doubleJumpWindowMillis = 450; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum upward velocity required to arm double-jump blink.", impact = "Higher values reduce accidental arming; lower values make detection more sensitive.") + double doubleJumpMinVerticalVelocity = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables sprint + click activation for Rift Blink.", impact = "True allows clicking while sprinting to blink; false disables this trigger.") + boolean enableSprintClickTrigger = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables single-sneak activation for Rift Blink.", impact = "True allows pressing sneak once to trigger blink.") + boolean enableSingleSneakTrigger = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Require sprinting for single-sneak trigger.", impact = "True requires sprint state when using single-sneak activation.") + boolean singleSneakRequiresSprint = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows left-click as a sprint-click trigger.", impact = "True allows left-click activation while sprinting; false disables left-click activation.") + boolean sprintClickLeftClick = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows right-click as a sprint-click trigger.", impact = "True allows right-click activation while sprinting; false disables right-click activation.") + boolean sprintClickRightClick = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables click-with-ender-pearl activation for Rift Blink.", impact = "True allows pearl-click activation; false disables pearl-click activation.") + boolean enableEnderPearlClickTrigger = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows left-click with an ender pearl to trigger Rift Blink.", impact = "True enables left-click pearl activation; false disables it.") + boolean enderPearlClickLeftClick = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows right-click with an ender pearl to trigger Rift Blink.", impact = "True enables right-click pearl activation; false disables it.") + boolean enderPearlClickRightClick = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows air-click interactions to trigger Rift Blink.", impact = "True lets air clicks trigger enabled click modes; false blocks air-click triggers.") + boolean allowAirClicks = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows block-click interactions to trigger Rift Blink.", impact = "True lets block clicks trigger enabled click modes; false blocks block-click triggers.") + boolean allowBlockClicks = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Distance for the Rift Blink adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseDistance = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Distance Factor for the Rift Blink adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double distanceFactor = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Vertical Adjustment for the Rift Blink adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxVerticalAdjustment = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Distance Search Step for the Rift Blink adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double distanceSearchStep = 0.5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftDescent.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftDescent.java new file mode 100644 index 000000000..88e02a47e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftDescent.java @@ -0,0 +1,150 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class RiftDescent extends SimpleAdaptation { + private final Set cooldown = ConcurrentHashMap.newKeySet(); + + public RiftDescent() { + super("rift-descent"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.descent.description")); + setDisplayName(Localizer.dLocalize("rift.descent.name")); + setMaxLevel(1); + setIcon(Material.SHULKER_BOX); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setInitialCost(getConfig().initialCost); + setInterval(9544); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_descent_100") + .title(Localizer.dLocalize("advancement.challenge_rift_descent_100.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_descent_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SHULKER_SHELL) + .key("challenge_rift_descent_1k") + .title(Localizer.dLocalize("advancement.challenge_rift_descent_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_descent_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_descent_100", "rift.descent.levitation-cancelled", 100, 300); + registerMilestone("challenge_rift_descent_1k", "rift.descent.levitation-cancelled", 1000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.YELLOW + Localizer.dLocalize("rift.descent.lore1")); + v.addLore(C.GREEN + Localizer.dLocalize("rift.descent.lore2") + " " + C.WHITE + getConfig().cooldown + "s"); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + SoundPlayer sp = SoundPlayer.of(p); + if (p.getPotionEffect(PotionEffectType.LEVITATION) == null) { + return; + } + if (!hasActiveAdaptation(p)) { + return; + } + UUID playerId = p.getUniqueId(); + if (cooldown.contains(playerId)) { + return; + } + + PotionEffect levi = p.getPotionEffect(PotionEffectType.LEVITATION); + + if (!e.isSneaking() && (levi != null)) { + p.removePotionEffect(PotionEffectType.LEVITATION); + getPlayer(p).getData().addStat("rift.descent.levitation-cancelled", 1); + cooldown.add(playerId); + J.runEntity(p, () -> { + sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f); + cooldown.remove(playerId); + }, Math.max(1, (int) Math.round(getConfig().cooldown * 20D))); + + J.runEntity(p, () -> { + p.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, (int) (20 * getConfig().cooldown), 0)); + sp.play(p.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, 1f, 1f); + }); + } + } + + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak to descend and negate levitation effects.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Rift Descent adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldown = 5.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.95; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + } + +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftEnderTaglock.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftEnderTaglock.java new file mode 100644 index 000000000..415ecc565 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftEnderTaglock.java @@ -0,0 +1,462 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.BoundEnderPearl; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class RiftEnderTaglock extends SimpleAdaptation { + private static final String PROJECTILE_TARGET_META = "adapt-rift-taglock-target"; + private final NamespacedKey targetKey; + private final Map suppressPearlTeleportUntil = new ConcurrentHashMap<>(); + + public RiftEnderTaglock() { + super("rift-ender-taglock"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.ender_taglock.description")); + setDisplayName(Localizer.dLocalize("rift.ender_taglock.name")); + setIcon(Material.ENDER_PEARL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1200); + targetKey = new NamespacedKey(Adapt.instance, "rift_taglock_target_uuid"); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_taglock_100") + .title(Localizer.dLocalize("advancement.challenge_rift_taglock_100.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_taglock_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_rift_taglock_500") + .title(Localizer.dLocalize("advancement.challenge_rift_taglock_500.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_taglock_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_taglock_100", "rift.ender-taglock.entities-tagged", 100, 400); + registerMilestone("challenge_rift_taglock_500", "rift.ender-taglock.taglocked-teleports", 500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.ender_taglock.lore1")); + if (level >= 2) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.ender_taglock.lore2")); + } + if (level >= 3) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.ender_taglock.lore3")); + } + v.addLore(C.YELLOW + "* " + Form.duration(getThrowCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("rift.ender_taglock.lore4")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + suppressPearlTeleportUntil.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player p) || !(e.getEntity() instanceof LivingEntity target)) { + return; + } + + int level = getActiveDamageLevel(p, target); + if (level <= 0 || !p.isSneaking()) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!isItem(hand) || hand.getType() != Material.ENDER_PEARL || BoundEnderPearl.isBindableItem(hand)) { + return; + } + + if (!isTaggable(target, level)) { + return; + } + + e.setCancelled(true); + tagIntoPearl(p, hand, target); + SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.55f, 1.4f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.PORTAL, target.getLocation().add(0, 1, 0), 14, 0.25, 0.4, 0.25, 0.04); + } + getPlayer(p).getData().addStat("rift.ender-taglock.entities-tagged", 1); + xp(p, getConfig().xpOnTag); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + EquipmentSlot slot = e.getHand(); + if (slot == null) { + return; + } + + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + ItemStack hand = slot == EquipmentSlot.HAND + ? p.getInventory().getItemInMainHand() + : p.getInventory().getItemInOffHand(); + + UUID target = getTaggedTarget(hand); + if (target == null) { + return; + } + + e.setCancelled(true); + if (p.hasCooldown(Material.ENDER_PEARL)) { + SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_DIDGERIDOO, 0.55f, 0.6f); + return; + } + + decrementTaggedPearl(p, slot, hand); + + EnderPearl pearl = p.launchProjectile(EnderPearl.class); + pearl.setMetadata(PROJECTILE_TARGET_META, new FixedMetadataValue(Adapt.instance, target.toString())); + suppressPearlTeleportUntil.put(p.getUniqueId(), System.currentTimeMillis() + getSuppressPearlTeleportWindowMillis()); + p.setCooldown(Material.ENDER_PEARL, getThrowCooldownTicks(level)); + SoundPlayer.of(p).play(p.getLocation(), Sound.ENTITY_ENDER_EYE_LAUNCH, 0.65f, 1.25f); + xp(p, getConfig().xpOnThrow); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerTeleportEvent e) { + if (e.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { + return; + } + + UUID id = e.getPlayer().getUniqueId(); + long until = suppressPearlTeleportUntil.getOrDefault(id, 0L); + if (until <= System.currentTimeMillis()) { + suppressPearlTeleportUntil.remove(id); + return; + } + + e.setCancelled(true); + e.getPlayer().setFallDistance(0f); + suppressPearlTeleportUntil.remove(id); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(ProjectileHitEvent e) { + if (!(e.getEntity() instanceof EnderPearl pearl) || !(pearl.getShooter() instanceof Player p)) { + return; + } + + if (!hasActiveAdaptation(p)) { + return; + } + + String raw = getMetadataString(pearl, PROJECTILE_TARGET_META); + if (raw == null) { + return; + } + + UUID targetId; + try { + targetId = UUID.fromString(raw); + } catch (IllegalArgumentException ex) { + return; + } + + Entity entity = Bukkit.getEntity(targetId); + if (!(entity instanceof LivingEntity target) || !target.isValid() || target.isDead()) { + return; + } + + Location destination = resolveDestination(e); + if (!canDamageTarget(p, target)) { + return; + } + + if (target instanceof Player) { + if (!canPVP(p, destination)) { + return; + } + } else if (!canPVE(p, destination)) { + return; + } + + destination.getChunk().load(); + J.teleport(target, destination); + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particle.REVERSE_PORTAL, destination.clone().add(0, 0.75, 0), 18, 0.3, 0.35, 0.3, 0.05); + } + SoundPlayer.of(target.getWorld()).play(destination, Sound.ENTITY_ENDERMAN_TELEPORT, 0.75f, 1.35f); + SoundPlayer.of(target.getWorld()).play(destination, Sound.ENTITY_ELDER_GUARDIAN_CURSE, 0.5f, 1.9f); + if (target instanceof Player victim && areSoundsEnabled()) { + victim.playSound(victim.getLocation(), Sound.ENTITY_ELDER_GUARDIAN_CURSE, 0.75f, 1.9f); + } + getPlayer(p).getData().addStat("rift.ender-taglock.taglocked-teleports", 1); + xp(p, getConfig().xpOnTeleport); + J.runEntity(p, () -> suppressPearlTeleportUntil.remove(p.getUniqueId()), 2); + } + + private Location resolveDestination(ProjectileHitEvent e) { + Location base = e.getEntity().getLocation().clone(); + if (e.getHitBlock() != null && e.getHitBlockFace() != null) { + Vector n = e.getHitBlockFace().getDirection().clone().normalize(); + base = e.getHitBlock().getLocation().add(0.5, 0.5, 0.5).add(n.multiply(0.6)); + } + + return base.add(0, 0.05, 0); + } + + private void decrementTaggedPearl(Player p, EquipmentSlot slot, ItemStack stack) { + if (!isItem(stack)) { + return; + } + + if (stack.getAmount() <= 1) { + if (slot == EquipmentSlot.HAND) { + p.getInventory().setItemInMainHand(new ItemStack(Material.AIR)); + } else { + p.getInventory().setItemInOffHand(new ItemStack(Material.AIR)); + } + return; + } + + stack.setAmount(stack.getAmount() - 1); + if (slot == EquipmentSlot.HAND) { + p.getInventory().setItemInMainHand(stack); + } else { + p.getInventory().setItemInOffHand(stack); + } + } + + private void tagIntoPearl(Player p, ItemStack hand, LivingEntity target) { + ItemStack tagged = makeTaggedPearl(target); + + if (hand.getAmount() <= 1) { + p.getInventory().setItemInMainHand(tagged); + return; + } + + hand.setAmount(hand.getAmount() - 1); + p.getInventory().addItem(tagged).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); + } + + private ItemStack makeTaggedPearl(LivingEntity target) { + ItemStack item = new ItemStack(Material.ENDER_PEARL, 1); + ItemMeta meta = item.getItemMeta(); + if (meta == null) { + return item; + } + + PersistentDataContainer pdc = meta.getPersistentDataContainer(); + pdc.set(targetKey, PersistentDataType.STRING, target.getUniqueId().toString()); + + meta.setDisplayName(C.LIGHT_PURPLE + "Taglocked Ender Pearl"); + List lore = new ArrayList<>(); + lore.add(C.GRAY + "Target: " + C.WHITE + getTargetName(target)); + lore.add(C.DARK_GRAY + "Right click to throw"); + meta.setLore(lore); + item.setItemMeta(meta); + return item; + } + + private UUID getTaggedTarget(ItemStack item) { + if (!isItem(item) || item.getType() != Material.ENDER_PEARL || item.getItemMeta() == null) { + return null; + } + + String raw = item.getItemMeta().getPersistentDataContainer().get(targetKey, PersistentDataType.STRING); + if (raw == null || raw.isEmpty()) { + return null; + } + + try { + return UUID.fromString(raw); + } catch (IllegalArgumentException e) { + return null; + } + } + + private String getTargetName(LivingEntity target) { + if (target instanceof Player player) { + return player.getName(); + } + + if (target.getCustomName() != null && !target.getCustomName().isEmpty()) { + return target.getCustomName(); + } + + return Form.capitalizeWords(target.getType().name().toLowerCase().replaceAll("\\Q_\\E", " ")); + } + + private boolean isTaggable(LivingEntity target, int level) { + if (target instanceof Player) { + return level >= 3; + } + + if (level >= 3) { + return true; + } + + if (level == 2) { + return isLevel1Taggable(target) || target instanceof Villager || isLargeTarget(target); + } + + return isLevel1Taggable(target); + } + + private boolean isLevel1Taggable(LivingEntity target) { + if (!(target instanceof Mob)) { + return false; + } + + if (target instanceof Villager) { + return false; + } + + if (isLargeTarget(target)) { + return false; + } + + return target instanceof Animals + || target instanceof Monster + || target instanceof WaterMob + || target instanceof Ambient + || target instanceof Slime; + } + + private boolean isLargeTarget(LivingEntity target) { + BoundingBox box = target.getBoundingBox(); + double width = Math.max(box.getWidthX(), box.getWidthZ()); + double height = box.getHeight(); + return width >= getConfig().largeWidthThreshold || height >= getConfig().largeHeightThreshold; + } + + private String getMetadataString(Entity entity, String key) { + for (MetadataValue value : entity.getMetadata(key)) { + if (value.getOwningPlugin() == Adapt.instance) { + return value.asString(); + } + } + + return null; + } + + private int getThrowCooldownTicks(int level) { + return Math.max(4, (int) Math.round(getConfig().throwCooldownTicksBase - (getLevelPercent(level) * getConfig().throwCooldownTicksFactor))); + } + + private long getSuppressPearlTeleportWindowMillis() { + return Math.max(1000L, getConfig().suppressPearlTeleportWindowMillis); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + suppressPearlTeleportUntil.entrySet().removeIf(i -> i.getValue() <= now); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Tag entities into ender pearls and throw those pearls to reposition the tagged target.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.95; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Throw Cooldown Ticks Base for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double throwCooldownTicksBase = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Throw Cooldown Ticks Factor for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double throwCooldownTicksFactor = 14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Suppress Pearl Teleport Window Millis for the Rift Ender Taglock adaptation.", impact = "This should be long enough to catch the teleport event from a taglocked throw.") + long suppressPearlTeleportWindowMillis = 180000L; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Large Width Threshold for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double largeWidthThreshold = 1.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Large Height Threshold for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double largeHeightThreshold = 2.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP On Tag for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnTag = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP On Throw for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnThrow = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP On Teleport for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnTeleport = 14; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftEnderchest.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftEnderchest.java new file mode 100644 index 000000000..fa1f46082 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftEnderchest.java @@ -0,0 +1,130 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + + +public class RiftEnderchest extends SimpleAdaptation { + public RiftEnderchest() { + super("rift-enderchest"); + setDescription(Localizer.dLocalize("rift.chest.description")); + setDisplayName(Localizer.dLocalize("rift.chest.name")); + setIcon(Material.ENDER_CHEST); + setBaseCost(0); + setCostFactor(0); + setMaxLevel(1); + setInitialCost(10); + setInterval(9248); + registerConfiguration(Config.class); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_CHEST) + .key("challenge_rift_enderchest_200") + .title(Localizer.dLocalize("advancement.challenge_rift_enderchest_200.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_enderchest_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_rift_enderchest_200", "rift.enderchest.opens", 200, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.ITALIC + Localizer.dLocalize("rift.chest.lore1")); + } + + @EventHandler + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) { + return; + } + + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + ItemStack hand = p.getInventory().getItemInMainHand(); + if (hand.getType() != Material.ENDER_CHEST || !hasActiveAdaptation(p)) { + return; + } + + if (p.hasCooldown(hand.getType())) { + e.setCancelled(true); + return; + } + + p.setCooldown(Material.ENDER_CHEST, 100); + PlayerSkillLine line = getPlayer(p).getData().getSkillLine("rift"); + PlayerAdaptation adaptation = line != null ? line.getAdaptation("rift-resist") : null; + if (adaptation != null && adaptation.getLevel() > 0) { + RiftResist.riftResistStackAdd(p, 10, 2); + } + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); + sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); + p.openInventory(p.getEnderChest()); + getPlayer(p).getData().addStat("rift.enderchest.opens", 1); + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Open an enderchest by left-clicking it in your hand.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftGate.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftGate.java new file mode 100644 index 000000000..0e6d3f013 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftGate.java @@ -0,0 +1,279 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.recipe.AdaptRecipe; +import art.arcane.adapt.content.event.AdaptAdaptationTeleportEvent; +import art.arcane.adapt.content.item.BoundEyeOfEnder; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class RiftGate extends SimpleAdaptation { + public RiftGate() { + super("rift-gate"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.gate.description")); + setDisplayName(Localizer.dLocalize("rift.gate.name")); + setIcon(Material.RESPAWN_ANCHOR); + setBaseCost(0); + setCostFactor(0); + setMaxLevel(1); + setInitialCost(30); + setInterval(1322); + registerRecipe(AdaptRecipe.shapeless() + .key("rift-recall-gate") + .ingredient(Material.ENDER_PEARL) + .ingredient(Material.AMETHYST_SHARD) + .ingredient(Material.EMERALD) + .result(BoundEyeOfEnder.io.withData(new BoundEyeOfEnder.Data(null))) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_gate_100") + .title(Localizer.dLocalize("advancement.challenge_rift_gate_100.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_gate_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_rift_gate_50k_dist") + .title(Localizer.dLocalize("advancement.challenge_rift_gate_50k_dist.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_gate_50k_dist.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_gate_100", "rift.gate.teleports", 100, 400); + registerMilestone("challenge_rift_gate_50k_dist", "rift.gate.total-distance", 50000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.YELLOW + Localizer.dLocalize("rift.gate.lore1")); + v.addLore(C.RED + Localizer.dLocalize("rift.gate.lore2")); + v.addLore(C.ITALIC + Localizer.dLocalize("rift.gate.lore3") + C.UNDERLINE + C.RED + Localizer.dLocalize("rift.gate.lore4")); + } + + + @EventHandler + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + ItemStack hand = p.getInventory().getItemInMainHand(); + ItemStack offHand = p.getInventory().getItemInOffHand(); + Location location = e.getClickedBlock() == null ? p.getLocation() : e.getClickedBlock().getLocation(); + + // Deny usage if the offhand contains a bindable item + if (BoundEyeOfEnder.isBindableItem(offHand) && e.getHand() != null && e.getHand().equals(EquipmentSlot.OFF_HAND)) { + e.setCancelled(true); + return; + } + + if (p.getInventory().getItemInMainHand().getType().equals(Material.ENDER_EYE) + && !p.hasCooldown(Material.ENDER_EYE) + && hasActiveAdaptation(p) + && BoundEyeOfEnder.isBindableItem(hand)) { + + e.setCancelled(true); + Adapt.verbose(" - Player Main hand: " + hand.getType()); + Action action = e.getAction(); + if (action == Action.LEFT_CLICK_BLOCK || action == Action.LEFT_CLICK_AIR) { + if (p.isSneaking()) { + Adapt.verbose("Linking eye"); + linkEye(p, location); + } + } else if (action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK) { + if (isBound(hand)) { + openEye(p); + } + } + } + } + + + private void handleEyeOfEnderInteraction(PlayerInteractEvent event, Player player, Block block) { + boolean sneaking = player.isSneaking(); + ItemStack mainHand = player.getInventory().getItemInMainHand(); + Location location = block == null ? player.getLocation() : block.getLocation(); + + Action action = event.getAction(); + if (action == Action.LEFT_CLICK_BLOCK || action == Action.LEFT_CLICK_AIR) { + if (sneaking) { + if (isBound(mainHand)) { + unlinkEye(player); + } else { + linkEye(player, location); + } + } + } else if (action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK) { + if (isBound(mainHand)) { + openEye(player); + } + } + } + + private boolean isBound(ItemStack stack) { + return stack.getType().equals(Material.ENDER_EYE) && BoundEyeOfEnder.getLocation(stack) != null; + } + + private void unlinkEye(Player p) { + ItemStack hand = p.getInventory().getItemInMainHand(); + decrementItemstack(hand, p); + ItemStack eye = new ItemStack(Material.ENDER_EYE); + p.getInventory().addItem(eye).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); + } + + private void linkEye(Player p, Location location) { + if (areParticlesEnabled()) { + vfxCuboidOutline(location.getBlock(), location.add(0, 1, 0).getBlock(), Particle.REVERSE_PORTAL); + } + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.ENTITY_ENDER_EYE_DEATH, 0.50f, 0.22f); + ItemStack hand = p.getInventory().getItemInMainHand(); + + if (hand.getAmount() == 1) { + BoundEyeOfEnder.setData(hand, location); + } else { + hand.setAmount(hand.getAmount() - 1); + ItemStack eye = BoundEyeOfEnder.withData(location); + p.getInventory().addItem(eye).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); + } + } + + + private void openEye(Player p) { + Adapt.verbose("Using eye"); + SoundPlayer sp = SoundPlayer.of(p); + Location l = BoundEyeOfEnder.getLocation(p.getInventory().getItemInMainHand()); + ItemStack hand = p.getInventory().getItemInMainHand(); + + if (getConfig().consumeOnUse) { + xp(p, 75); + decrementItemstack(hand, p); + } else { + if (p.getCooldown(Material.ENDER_EYE) > 0) { + sp.play(p.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 1, 1); + return; + } + } + p.setCooldown(Material.ENDER_EYE, 150); + + + if (RiftResist.hasRiftResistPerk(getPlayer(p))) { + RiftResist.riftResistStackAdd(p, 150, 3); + } + + p.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 100, 10, true, false, false)); + p.addPotionEffect(new PotionEffect(PotionEffectType.LEVITATION, 85, 0, true, false, false)); + sp.play(l, Sound.BLOCK_LODESTONE_PLACE, 1f, 0.1f); + sp.play(l, Sound.BLOCK_BELL_RESONATE, 1f, 0.1f); + + int[] remainingSteps = {80}; + double[] radius = {2.0}; + double[] adder = {0.0}; + boolean[] initialRingShown = {false}; + final Color color = Color.fromBGR(0, 0, 0); + Runnable[] ringTask = new Runnable[1]; + ringTask[0] = () -> { + if (!p.isOnline()) { + return; + } + + if (!initialRingShown[0]) { + vfxFastRing(p.getLocation(), radius[0], color); + initialRingShown[0] = true; + } + + remainingSteps[0]--; + if (remainingSteps[0] <= 0) { + return; + } + + adder[0] += 0.02; + radius[0] *= 0.9; + vfxFastRing(p.getLocation().add(0, adder[0], 0), radius[0], color); + J.runEntity(p, ringTask[0], 1); + }; + J.runEntity(p, ringTask[0]); + vfxLevelUp(p); + sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 5.35f, 0.1f); + J.runEntity(p, () -> { + AdaptAdaptationTeleportEvent event = new AdaptAdaptationTeleportEvent(!Bukkit.isPrimaryThread(), getPlayer(p), this, p.getLocation(), l); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + + getPlayer(p).getData().addStat("rift.teleports", 1); + getPlayer(p).getData().addStat("rift.gate.teleports", 1); + getPlayer(p).getData().addStat("rift.gate.total-distance", (int) p.getLocation().distance(l)); + J.teleport(p, l, PlayerTeleportEvent.TeleportCause.PLUGIN); + vfxLevelUp(p); + sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 5.35f, 0.1f); + }, 85); + } + + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Craft a gate item to teleport to a marked location.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Consume On Use for the Rift Gate adaptation.", impact = "True enables this behavior and false disables it.") + boolean consumeOnUse = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Rift Gate adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftInflatedPocketDimension.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftInflatedPocketDimension.java new file mode 100644 index 000000000..a965e2745 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftInflatedPocketDimension.java @@ -0,0 +1,334 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; + +public class RiftInflatedPocketDimension extends SimpleAdaptation { + public RiftInflatedPocketDimension() { + super("rift-inflated-pocket-dimension"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.inflated_pocket_dimension.description")); + setDisplayName(Localizer.dLocalize("rift.inflated_pocket_dimension.name")); + setIcon(Material.ENDER_EYE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(600); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_CHEST) + .key("challenge_rift_pocket_5k") + .title(Localizer.dLocalize("advancement.challenge_rift_pocket_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_pocket_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_CHEST) + .key("challenge_rift_pocket_store_10k") + .title(Localizer.dLocalize("advancement.challenge_rift_pocket_store_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_pocket_store_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_pocket_5k", "rift.inflated-pocket.items-pulled", 5000, 400); + registerMilestone("challenge_rift_pocket_store_10k", "rift.inflated-pocket.items-stored", 10000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.inflated_pocket_dimension.lore1")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.inflated_pocket_dimension.lore2")); + v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.inflated_pocket_dimension.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerInteractEvent e) { + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { + return; + } + + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (!hand.getType().isAir()) { + return; + } + + Block target = action == Action.RIGHT_CLICK_BLOCK ? e.getClickedBlock() : p.getTargetBlockExact(5); + if (target == null) { + return; + } + + Material requested = target.getType(); + if (!requested.isItem() || requested.isAir()) { + return; + } + + int moved = moveFromEnderToPlayer(p, requested, getConfig().rightClickPullAmount, true); + if (moved <= 0) { + return; + } + + e.setCancelled(true); + SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 0.4f, 1.7f); + getPlayer(p).getData().addStat("rift.inflated-pocket.items-pulled", moved); + xp(p, moved * getConfig().xpPerTransferredItem, "rift:inflated-pocket:pull"); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPlaceEvent e) { + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + + Material placed = e.getBlockPlaced().getType(); + if (!placed.isItem() || placed.isAir()) { + return; + } + + J.runEntity(p, () -> { + ItemStack hand = p.getInventory().getItemInMainHand(); + int needed = 0; + if (hand.getType().isAir()) { + needed = Math.min(getConfig().buildRefillAmount, placed.getMaxStackSize()); + } else if (hand.getType() == placed && hand.getAmount() < placed.getMaxStackSize()) { + needed = Math.min(getConfig().buildRefillAmount, placed.getMaxStackSize() - hand.getAmount()); + } + + if (needed <= 0) { + return; + } + + int moved = moveFromEnderToPlayer(p, placed, needed, true); + if (moved <= 0) { + return; + } + + SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 0.3f, 1.9f); + getPlayer(p).getData().addStat("rift.inflated-pocket.items-pulled", moved); + xp(p, moved * getConfig().xpPerTransferredItem, "rift:inflated-pocket:build-refill"); + }, 1); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerDropItemEvent e) { + Player p = e.getPlayer(); + if (getActiveLevel(p, Player::isSneaking) <= 0) { + return; + } + + ItemStack dropped = e.getItemDrop().getItemStack().clone(); + if (!canFullyFitInInventory(p.getEnderChest().getContents(), dropped, p.getEnderChest().getMaxStackSize())) { + e.setCancelled(true); + e.getItemDrop().remove(); + SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.8f, 0.8f); + return; + } + + e.getItemDrop().remove(); + p.getEnderChest().addItem(dropped); + + SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_CLOSE, 0.5f, 1.4f); + getPlayer(p).getData().addStat("rift.inflated-pocket.items-stored", dropped.getAmount()); + xp(p, dropped.getAmount() * getConfig().xpPerTransferredItem, "rift:inflated-pocket:store"); + } + + private int moveFromEnderToPlayer(Player p, Material type, int amount, boolean preferMainHand) { + if (amount <= 0) { + return 0; + } + + int moved = 0; + ItemStack[] chest = p.getEnderChest().getContents(); + for (int slot = 0; slot < chest.length && moved < amount; slot++) { + ItemStack stack = chest[slot]; + if (!isItem(stack) || stack.getType() != type) { + continue; + } + + int take = Math.min(amount - moved, stack.getAmount()); + ItemStack transfer = stack.clone(); + transfer.setAmount(take); + + int inserted = insertIntoPlayerInventory(p, transfer, preferMainHand); + if (inserted <= 0) { + continue; + } + + moved += inserted; + if (stack.getAmount() <= inserted) { + chest[slot] = null; + } else { + stack.setAmount(stack.getAmount() - inserted); + chest[slot] = stack; + } + } + + p.getEnderChest().setContents(chest); + return moved; + } + + private int insertIntoPlayerInventory(Player p, ItemStack transfer, boolean preferMainHand) { + int requested = transfer.getAmount(); + if (requested <= 0) { + return 0; + } + + ItemStack hand = p.getInventory().getItemInMainHand(); + if (preferMainHand && (hand == null || hand.getType().isAir())) { + p.getInventory().setItemInMainHand(transfer); + return requested; + } + + if (preferMainHand && hand.getType() == transfer.getType() && hand.getAmount() < hand.getMaxStackSize()) { + int room = hand.getMaxStackSize() - hand.getAmount(); + int moved = Math.min(room, transfer.getAmount()); + hand.setAmount(hand.getAmount() + moved); + p.getInventory().setItemInMainHand(hand); + if (moved >= requested) { + return requested; + } + + int remainingRequest = requested - moved; + transfer.setAmount(remainingRequest); + Map overflow = p.getInventory().addItem(transfer); + int remaining = sumItemAmounts(overflow); + return moved + Math.max(0, remainingRequest - remaining); + } + + Map overflow = p.getInventory().addItem(transfer); + int remaining = sumItemAmounts(overflow); + return Math.max(0, requested - remaining); + } + + private int sumItemAmounts(Map overflow) { + int sum = 0; + for (ItemStack itemStack : overflow.values()) { + if (itemStack == null || itemStack.getType().isAir()) { + continue; + } + sum += itemStack.getAmount(); + } + return sum; + } + + private boolean canFullyFitInInventory(ItemStack[] contents, ItemStack stack, int inventoryMaxStackSize) { + if (!isItem(stack)) { + return false; + } + + int remaining = stack.getAmount(); + int maxPerSlot = Math.min(stack.getMaxStackSize(), inventoryMaxStackSize); + + for (ItemStack existing : contents) { + if (!isItem(existing) || !existing.isSimilar(stack)) { + continue; + } + + remaining -= Math.max(0, maxPerSlot - existing.getAmount()); + if (remaining <= 0) { + return true; + } + } + + for (ItemStack existing : contents) { + if (existing == null || existing.getType().isAir()) { + remaining -= maxPerSlot; + if (remaining <= 0) { + return true; + } + } + } + + return false; + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Building and empty-hand block targeting can fetch materials from your ender chest, and sneak-drop stores items into it.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Build Refill Amount for the Rift Inflated Pocket Dimension adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int buildRefillAmount = 64; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Right Click Pull Amount for the Rift Inflated Pocket Dimension adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int rightClickPullAmount = 64; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP Per Transferred Item for the Rift Inflated Pocket Dimension adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerTransferredItem = 0.08; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftResist.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftResist.java new file mode 100644 index 000000000..528903b05 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftResist.java @@ -0,0 +1,143 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; + + +public class RiftResist extends SimpleAdaptation { + public RiftResist() { + super("rift-resist"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.resist.description")); + setDisplayName(Localizer.dLocalize("rift.resist.name")); + setIcon(Material.SCULK_VEIN); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(10288); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_resist_200") + .title(Localizer.dLocalize("advancement.challenge_rift_resist_200.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_resist_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_rift_resist_200", "rift.resist.activations", 200, 300); + } + + static void riftResistStackAdd(Player p, int duration, int amplifier) { + if (p.getLocation().getWorld() == null) { + return; + } + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_IRON, 1f, 1.24f); + spw.play(p.getLocation(), Sound.BLOCK_CONDUIT_AMBIENT_SHORT, 1f, 0.01f); + spw.play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 1f, 0.01f); + p.addPotionEffect(new PotionEffect(PotionEffectTypes.DAMAGE_RESISTANCE, duration, amplifier, true, false, false)); + } + + public static boolean hasRiftResistPerk(AdaptPlayer p) { + return p.getData().getLevel() > 0; + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.ITALIC + Localizer.dLocalize("rift.resist.lore1")); + v.addLore(C.UNDERLINE + Localizer.dLocalize("rift.resist.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + ItemStack hand = p.getInventory().getItemInMainHand(); + + if (e.getAction() == Action.RIGHT_CLICK_AIR) { + switch (hand.getType()) { + case ENDER_EYE, ENDER_PEARL -> { + xp(p, 3); + riftResistStackAdd(p, getConfig().duration, getConfig().amplitude); + getPlayer(p).getData().addStat("rift.resist.activations", 1); + } + } + } + + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain resistance when using Ender items and abilities.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Amplitude for the Rift Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int amplitude = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Duration for the Rift Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int duration = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftVisage.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftVisage.java new file mode 100644 index 000000000..682b22d73 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftVisage.java @@ -0,0 +1,109 @@ +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Enderman; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.inventory.ItemStack; + +public class RiftVisage extends SimpleAdaptation { + public RiftVisage() { + super("rift-visage"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.visage.description")); + setDisplayName(Localizer.dLocalize("rift.visage.name")); + setIcon(Material.POPPED_CHORUS_FRUIT); + setBaseCost(getConfig().baseCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_rift_visage_100") + .title(Localizer.dLocalize("advancement.challenge_rift_visage_100.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_visage_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DRAGON_HEAD) + .key("challenge_rift_visage_1k") + .title(Localizer.dLocalize("advancement.challenge_rift_visage_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_visage_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_visage_100", "rift.visage.stares-survived", 100, 300); + registerMilestone("challenge_rift_visage_1k", "rift.visage.stares-survived", 1000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.ITALIC + Localizer.dLocalize("rift.visage.lore1")); + } + + @EventHandler + public void onEntityTarget(EntityTargetEvent event) { + Entity entity = event.getEntity(); + if (entity instanceof Enderman) { + if (event.getTarget() instanceof Player player) { + if (hasActiveAdaptation(player) && hasEnderPearl(player)) { + event.setCancelled(true); + getPlayer(player).getData().addStat("rift.visage.stares-survived", 1); + } + } + } + } + + private boolean hasEnderPearl(Player player) { + for (ItemStack item : player.getInventory().getContents()) { + if (item != null && item.getType() == Material.ENDER_PEARL) { + return true; + } + } + return false; + } + + @Override + public void onTick() { + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Prevents Endermen from becoming aggressive when you carry Enderpearls.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftVoidMagnet.java b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftVoidMagnet.java new file mode 100644 index 000000000..3809d6c2b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/rift/RiftVoidMagnet.java @@ -0,0 +1,240 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.rift; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; + +public class RiftVoidMagnet extends SimpleAdaptation { + public RiftVoidMagnet() { + super("rift-void-magnet"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("rift.void_magnet.description")); + setDisplayName(Localizer.dLocalize("rift.void_magnet.name")); + setIcon(Material.HOPPER_MINECART); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(20); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_void_magnet_5k") + .title(Localizer.dLocalize("advancement.challenge_rift_void_magnet_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_void_magnet_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_rift_void_magnet_50k") + .title(Localizer.dLocalize("advancement.challenge_rift_void_magnet_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_void_magnet_50k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_void_magnet_5k", "rift.void-magnet.items-pulled", 5000, 400); + registerMilestone("challenge_rift_void_magnet_50k", "rift.void-magnet.items-pulled", 50000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("rift.void_magnet.lore1")); + v.addLore(C.GREEN + "+ " + getMaxItems(level) + C.GRAY + " " + Localizer.dLocalize("rift.void_magnet.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getPulseTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("rift.void_magnet.lore3")); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0 || p.getTicksLived() % getPulseTicks(level) != 0) { + continue; + } + + int moved = collectNearbyItems(p, level); + if (moved <= 0) { + continue; + } + + if (areParticlesEnabled()) { + + p.spawnParticle(Particle.PORTAL, p.getLocation().add(0, 1, 0), 8, 0.3, 0.5, 0.3, 0.05); + + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 0.45f, 1.6f); + getPlayer(p).getData().addStat("rift.void-magnet.items-pulled", moved); + xp(p, moved * getConfig().xpPerMovedItem, "rift:void-magnet:item-pull"); + } + } + + private int collectNearbyItems(Player p, int level) { + int moved = 0; + int max = getMaxItems(level); + double r = getRadius(level); + for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), r, r, r)) { + if (!(entity instanceof Item item)) { + continue; + } + + if (moved >= max || item.isDead() || !item.isValid()) { + continue; + } + + if (!canSnatchItem(p, item)) { + continue; + } + + ItemStack stack = item.getItemStack(); + if (stack == null || stack.getType().isAir()) { + continue; + } + + int requestAmount = Math.min(stack.getAmount(), max - moved); + if (requestAmount <= 0) { + continue; + } + + EntityPickupItemEvent pickupEvent = new EntityPickupItemEvent(p, item, 0); + Bukkit.getPluginManager().callEvent(pickupEvent); + if (pickupEvent.isCancelled()) { + continue; + } + + ItemStack toChest = stack.clone(); + toChest.setAmount(requestAmount); + Map chestOverflow = p.getEnderChest().addItem(toChest); + int chestRemaining = sumItemAmounts(chestOverflow); + int movedAmount = Math.max(0, requestAmount - chestRemaining); + + if (chestRemaining > 0 && getConfig().allowEnderChestOverflow) { + ItemStack toInventory = stack.clone(); + toInventory.setAmount(chestRemaining); + Map inventoryOverflow = p.getInventory().addItem(toInventory); + int inventoryRemaining = sumItemAmounts(inventoryOverflow); + movedAmount += Math.max(0, chestRemaining - inventoryRemaining); + } + + if (movedAmount <= 0) { + continue; + } + + if (movedAmount >= stack.getAmount()) { + item.remove(); + } else { + stack.setAmount(stack.getAmount() - movedAmount); + item.setItemStack(stack); + } + moved += movedAmount; + } + + return moved; + } + + private int sumItemAmounts(Map overflow) { + int sum = 0; + for (ItemStack itemStack : overflow.values()) { + if (itemStack == null || itemStack.getType().isAir()) { + continue; + } + sum += itemStack.getAmount(); + } + return sum; + } + + private double getRadius(int level) { + return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); + } + + private int getMaxItems(int level) { + return Math.max(1, (int) Math.round(getConfig().maxItemsBase + (getLevelPercent(level) * getConfig().maxItemsFactor))); + } + + private int getPulseTicks(int level) { + return Math.max(2, (int) Math.round(getConfig().pulseTicksBase - (getLevelPercent(level) * getConfig().pulseTicksFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak to periodically pull nearby dropped items into your ender chest first.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Allow Ender Chest Overflow for the Rift Void Magnet adaptation.", impact = "When true, leftovers that do not fit in ender chest can spill into player inventory.") + boolean allowEnderChestOverflow = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Items Base for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxItemsBase = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Items Factor for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxItemsFactor = 22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Pulse Ticks Base for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pulseTicksBase = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Pulse Ticks Factor for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pulseTicksFactor = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Moved Item for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerMovedItem = 0.7; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneFishersFantasy.java b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneFishersFantasy.java new file mode 100644 index 000000000..16ed633dd --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneFishersFantasy.java @@ -0,0 +1,131 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.seaborrne; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.concurrent.ThreadLocalRandom; + +public class SeaborneFishersFantasy extends SimpleAdaptation { + + public SeaborneFishersFantasy() { + super("seaborne-fishers-fantasy"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("seaborn.fishers_fantasy.description")); + setDisplayName(Localizer.dLocalize("seaborn.fishers_fantasy.name")); + setIcon(Material.FISHING_ROD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(8080); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FISHING_ROD) + .key("challenge_seaborne_fish_500") + .title(Localizer.dLocalize("advancement.challenge_seaborne_fish_500.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_fish_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TROPICAL_FISH) + .key("challenge_seaborne_fish_5k") + .title(Localizer.dLocalize("advancement.challenge_seaborne_fish_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_fish_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_seaborne_fish_500", "seaborne.fishers-fantasy.fish-caught", 500, 300); + registerMilestone("challenge_seaborne_fish_5k", "seaborne.fishers-fantasy.fish-caught", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("seaborn.fishers_fantasy.lore1")); + } + + @EventHandler + public void on(PlayerFishEvent e) { + Player p = e.getPlayer(); + withAdaptedPlayer(p, e, () -> { + if (e.getState() == PlayerFishEvent.State.CAUGHT_FISH) { + getPlayer(p).getData().addStat("seaborne.fishers-fantasy.fish-caught", 1); + int level = getActiveLevel(p); + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (int i = 0; i < level; i++) { + ItemStack item = new ItemStack(ItemListings.getFishingDrops().getRandom(), 1); + if (random.nextBoolean()) { + p.getWorld().dropItemNaturally(p.getLocation(), item); + p.getWorld().spawn(p.getLocation(), ExperienceOrb.class); + Adapt.verbose("Fishing Gift Donated!"); + xp(p, 15 * level); + } + } + } + }); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Earn more XP from fishing and catch more fish.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.9; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneOxygen.java b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneOxygen.java new file mode 100644 index 000000000..73bf6f101 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneOxygen.java @@ -0,0 +1,122 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.seaborrne; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class SeaborneOxygen extends SimpleAdaptation { + + public SeaborneOxygen() { + super("seaborne-oxygen"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("seaborn.oxygen.description")); + setDisplayName(Localizer.dLocalize("seaborn.oxygen.name")); + setIcon(Material.GLASS_PANE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(3750); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TURTLE_HELMET) + .key("challenge_seaborne_oxygen_12k") + .title(Localizer.dLocalize("advancement.challenge_seaborne_oxygen_12k.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_oxygen_12k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_seaborne_oxygen_12k", "seaborne.oxygen.bonus-air-ticks", 12000, 300); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getAirBoost(level), 0) + C.GRAY + Localizer.dLocalize("seaborn.oxygen.lore1")); + } + + public double getAirBoost(int level) { + return getLevelPercent(level); + } + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player player = adaptPlayer.getPlayer(); + if (player == null || !player.isOnline()) { + continue; + } + + withPlayerThread(player, () -> { + if (!player.isOnline() || player.getWorld() == null) { + return; + } + + int level = getActiveLevel(player); + if (level <= 0 || (!player.isInWater() && !player.isSwimming())) { + return; + } + + int airTicks = level * getConfig().airPerLevelTics; + player.addPotionEffect(new PotionEffect(PotionEffectType.WATER_BREATHING, airTicks, level)); + getPlayer(player).getData().addStat("seaborne.oxygen.bonus-air-ticks", airTicks); + }); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Hold more oxygen underwater.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.525; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Air Per Level Tics for the Seaborne Oxygen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int airPerLevelTics = 15; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeabornePressureDiver.java b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeabornePressureDiver.java new file mode 100644 index 000000000..1174e8cfa --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeabornePressureDiver.java @@ -0,0 +1,262 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.seaborrne; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class SeabornePressureDiver extends SimpleAdaptation { + private final Map xpCooldowns = new ConcurrentHashMap<>(); + + public SeabornePressureDiver() { + super("seaborne-pressure-diver"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("seaborn.pressure_diver.description")); + setDisplayName(Localizer.dLocalize("seaborn.pressure_diver.name")); + setIcon(Material.NAUTILUS_SHELL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(20); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE) + .key("challenge_seaborne_pressure_1k") + .title(Localizer.dLocalize("advancement.challenge_seaborne_pressure_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_pressure_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_seaborne_pressure_1k", "seaborne.pressure-diver.deep-blocks-mined", 1000, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getDepthThreshold(level), 1) + C.GRAY + " " + Localizer.dLocalize("seaborn.pressure_diver.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getDamageReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("seaborn.pressure_diver.lore2")); + v.addLore(C.GREEN + "+ " + Form.pc(getFatigueTrimChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("seaborn.pressure_diver.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + xpCooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + withPlayerThread(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (!p.isInWater()) { + return; + } + + if (!isDeepEnough(p, level)) { + return; + } + + e.setDamage(e.getDamage() * (1D - getDamageReduction(level))); + }); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + + withPlayerThread(p, () -> { + if (!p.isOnline()) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0 || !p.isInWater()) { + return; + } + + if (!isDeepEnough(p, level)) { + return; + } + + applyDepthBuffs(p, level); + awardDepthXp(p, now); + getPlayer(p).getData().addStat("seaborne.pressure-diver.deep-blocks-mined", 1); + }); + } + } + + private void applyDepthBuffs(Player p, int level) { + int resistanceAmp = getResistanceAmplifier(level, p); + p.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, getConfig().effectTicks, resistanceAmp, false, false, true), true); + p.addPotionEffect(new PotionEffect(PotionEffectType.WATER_BREATHING, getConfig().effectTicks, 0, false, false, true), true); + + PotionEffect fatigue = p.getPotionEffect(PotionEffectType.MINING_FATIGUE); + if (fatigue == null) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getFatigueTrimChance(level)) { + return; + } + + int reducedAmp = Math.max(0, fatigue.getAmplifier() - getFatigueTrimAmount(level)); + p.addPotionEffect(new PotionEffect(PotionEffectType.MINING_FATIGUE, + Math.max(20, Math.min(fatigue.getDuration(), getConfig().fatigueReplaceTicks)), + reducedAmp, + false, + true, + true), true); + } + + private void awardDepthXp(Player p, long now) { + UUID id = p.getUniqueId(); + long next = xpCooldowns.getOrDefault(id, 0L); + if (now < next) { + return; + } + + xp(p, getConfig().xpPerDepthPulse); + xpCooldowns.put(id, now + getConfig().xpPulseCooldownMillis); + } + + private boolean isDeepEnough(Player p, int level) { + double seaLevel = p.getWorld().getSeaLevel(); + double depth = seaLevel - p.getEyeLocation().getY(); + return depth >= getDepthThreshold(level); + } + + private int getResistanceAmplifier(int level, Player p) { + double seaLevel = p.getWorld().getSeaLevel(); + double depth = seaLevel - p.getEyeLocation().getY(); + if (depth >= getDeepThreshold(level)) { + return 1; + } + + return 0; + } + + private double getDepthThreshold(int level) { + return Math.max(2, getConfig().depthThresholdBase - (getLevelPercent(level) * getConfig().depthThresholdFactor)); + } + + private double getDeepThreshold(int level) { + return Math.max(4, getConfig().deepThresholdBase - (getLevelPercent(level) * getConfig().deepThresholdFactor)); + } + + private double getDamageReduction(int level) { + return Math.min(getConfig().maxDamageReduction, getConfig().damageReductionBase + (getLevelPercent(level) * getConfig().damageReductionFactor)); + } + + private double getFatigueTrimChance(int level) { + return Math.min(1.0, getConfig().fatigueTrimChanceBase + (getLevelPercent(level) * getConfig().fatigueTrimChanceFactor)); + } + + private int getFatigueTrimAmount(int level) { + return Math.max(1, (int) Math.round(getConfig().fatigueTrimAmountBase + (getLevelPercent(level) * getConfig().fatigueTrimAmountFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain depth scaling protection underwater and partially counter mining fatigue in deep ocean play.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Depth Threshold Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double depthThresholdBase = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Depth Threshold Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double depthThresholdFactor = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Deep Threshold Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double deepThresholdBase = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Deep Threshold Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double deepThresholdFactor = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Reduction Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageReductionBase = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Reduction Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageReductionFactor = 0.26; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Maximum Damage Reduction for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxDamageReduction = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fatigue Trim Chance Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fatigueTrimChanceBase = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fatigue Trim Chance Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fatigueTrimChanceFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fatigue Trim Amount Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fatigueTrimAmountBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fatigue Trim Amount Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double fatigueTrimAmountFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effect Ticks for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int effectTicks = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Fatigue Replace Ticks for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int fatigueReplaceTicks = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP Per Depth Pulse for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerDepthPulse = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP Pulse Cooldown Millis for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long xpPulseCooldownMillis = 3000; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneSpeed.java b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneSpeed.java new file mode 100644 index 000000000..0b9bee3a3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneSpeed.java @@ -0,0 +1,129 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.seaborrne; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class SeaborneSpeed extends SimpleAdaptation { + + public SeaborneSpeed() { + super("seaborne-speed"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("seaborn.dolphin_grace.description")); + setDisplayName(Localizer.dLocalize("seaborn.dolphin_grace.name")); + setIcon(Material.PRISMARINE_CRYSTALS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(1020); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.HEART_OF_THE_SEA) + .key("challenge_seaborne_speed_10k") + .title(Localizer.dLocalize("advancement.challenge_seaborne_speed_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_speed_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TRIDENT) + .key("challenge_seaborne_speed_100k") + .title(Localizer.dLocalize("advancement.challenge_seaborne_speed_100k.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_speed_100k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_seaborne_speed_10k", "seaborne.speed.blocks-swum", 10000, 300); + registerMilestone("challenge_seaborne_speed_100k", "seaborne.speed.blocks-swum", 100000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("seaborn.dolphin_grace.lore1") + C.GREEN + (level) + C.GRAY + Localizer.dLocalize("seaborn.dolphin_grace.lore2")); + v.addLore(C.ITALIC + Localizer.dLocalize("seaborn.dolphin_grace.lore3")); + } + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player player = adaptPlayer.getPlayer(); + if (player == null || !player.isOnline()) { + continue; + } + + withPlayerThread(player, () -> { + if (!player.isOnline()) { + return; + } + + int level = getActiveLevel(player); + if (level <= 0 || !player.isInWater()) { + return; + } + + if (player.getInventory().getBoots() != null && player.getInventory().getBoots().containsEnchantment(Enchantment.DEPTH_STRIDER)) { + return; + } + + player.addPotionEffect(new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 62, level)); + getPlayer(player).getData().addStat("seaborne.speed.blocks-swum", 1); + }); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Swim faster with dolphin-like grace.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.525; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTidecaller.java b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTidecaller.java new file mode 100644 index 000000000..08aab25a9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTidecaller.java @@ -0,0 +1,469 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.seaborrne; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.FluidCollisionMode; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerAnimationEvent; +import org.bukkit.event.player.PlayerAnimationType; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.util.Vector; + +public class SeaborneTidecaller extends SimpleAdaptation { + public SeaborneTidecaller() { + super("seaborne-tidecaller"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("seaborn.tidecaller.description")); + setDisplayName(Localizer.dLocalize("seaborn.tidecaller.name")); + setIcon(Material.HEART_OF_THE_SEA); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1600); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TRIDENT) + .key("challenge_seaborne_tidecaller_200") + .title(Localizer.dLocalize("advancement.challenge_seaborne_tidecaller_200.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_tidecaller_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.HEART_OF_THE_SEA) + .key("challenge_seaborne_tidecaller_5k") + .title(Localizer.dLocalize("advancement.challenge_seaborne_tidecaller_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_tidecaller_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_seaborne_tidecaller_200", "seaborne.tidecaller.dashes", 200, 300); + registerMilestone("challenge_seaborne_tidecaller_5k", "seaborne.tidecaller.dashes", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getDashDistance(level), 1) + C.GRAY + " " + Localizer.dLocalize("seaborn.tidecaller.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("seaborn.tidecaller.lore2")); + java.util.List combos = getTriggerCombos(); + if (combos.isEmpty()) { + v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + "none"); + } else { + for (String combo : combos) { + v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + combo); + } + } + v.addLore(C.AQUA + "* " + C.GRAY + "Environment: " + C.WHITE + getEnvironmentSummary()); + } + + @Override + public String getDescription() { + return "Surge forward with a water burst in valid environments. " + summarizeTriggerDescription(); + } + + private String summarizeTriggerDescription() { + java.util.List combos = getTriggerCombos(); + if (combos.isEmpty()) { + return "No active triggers are currently enabled."; + } + + if (combos.size() == 1) { + return "Trigger: " + combos.get(0) + "."; + } + + if (combos.size() == 2) { + return "Triggers: " + combos.get(0) + " or " + combos.get(1) + "."; + } + + return "Triggers: " + combos.get(0) + ", " + combos.get(1) + ", +" + (combos.size() - 2) + " more."; + } + + private java.util.List getTriggerCombos() { + java.util.List triggers = new java.util.ArrayList<>(); + String env = getEnvironmentSummary(); + if (getConfig().enableSneakTrigger) { + triggers.add("Sneak" + (env.equals("none") ? "" : " (" + env + ")")); + } + + if (getConfig().enableAttackTrigger) { + String combo = getConfig().attackTriggerRequiresSneak ? "Sneak + Attack" : "Attack"; + if (getConfig().attackTriggerWaterOnly) { + combo += " (water only)"; + } else if (!env.equals("none")) { + combo += " (" + env + ")"; + } + triggers.add(combo); + } + + return triggers; + } + + private String getEnvironmentSummary() { + java.util.List modes = new java.util.ArrayList<>(); + if (getConfig().allowWaterTrigger) { + modes.add("water"); + } + + if (getConfig().allowRainTrigger) { + modes.add("rain"); + } + + if (modes.isEmpty()) { + return "none"; + } + + return String.join("/", modes); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + if (!e.isSneaking()) { + return; + } + + tryDash(p, TriggerType.SNEAK); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerAnimationEvent e) { + if (e.getAnimationType() != PlayerAnimationType.ARM_SWING) { + return; + } + + tryDash(e.getPlayer(), TriggerType.ATTACK); + } + + private void tryDash(Player p, TriggerType triggerType) { + withAdaptedPlayer(p, () -> { + if (p.hasCooldown(Material.HEART_OF_THE_SEA)) { + return; + } + + if (triggerType == TriggerType.SNEAK && !getConfig().enableSneakTrigger) { + return; + } + + if (triggerType == TriggerType.ATTACK) { + if (!getConfig().enableAttackTrigger) { + return; + } + + if (getConfig().attackTriggerRequiresSneak && !p.isSneaking()) { + return; + } + + if (getConfig().attackTriggerWaterOnly && !isInWaterDashState(p)) { + return; + } + } + + if (!isDashEnvironmentValid(p)) { + return; + } + + int level = getActiveLevel(p); + Vector direction = resolveDashDirection(p); + if (direction.lengthSquared() <= 0.000001) { + return; + } + + if (isBlockedAhead(p, direction)) { + return; + } + + boolean wasSwimming = p.isSwimming(); + org.bukkit.Location target; + org.bukkit.Location origin = null; + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.SPLASH, p.getLocation().add(0, 1, 0), 20, 0.25, 0.35, 0.25, 0.08); + } + + if (getConfig().useVelocityDash) { + applyVelocityDash(p, direction, level); + target = p.getLocation().clone().add(direction.clone().multiply(Math.max(0.35, getDashDistance(level) * 0.35))); + } else { + origin = p.getLocation().clone(); + target = findSafeDashTarget(p, getDashDistance(level)); + if (target == null) { + return; + } + J.teleport(p, target); + applyDashMomentum(p, origin, target); + } + + preserveSwimStateAfterDash(p, wasSwimming); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.SPLASH, target.clone().add(0, 1, 0), 30, 0.35, 0.45, 0.35, 0.08); + } + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.BUBBLE, target.clone().add(0, 0.9, 0), 18, 0.4, 0.35, 0.4, 0.05); + } + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.ITEM_TRIDENT_RIPTIDE_2, 0.75f, 1.2f); + sp.play(target, Sound.ENTITY_DOLPHIN_SPLASH, 0.65f, 1.15f); + p.setCooldown(Material.HEART_OF_THE_SEA, getCooldownTicks(level)); + xp(p, getConfig().xpPerBurst); + getPlayer(p).getData().addStat("seaborne.tidecaller.dashes", 1); + }); + } + + private void applyVelocityDash(Player p, Vector direction, int level) { + Vector velocity = direction.clone().normalize(); + double scalar = Math.max(0, getConfig().velocityStrengthBase + (getLevelPercent(level) * getConfig().velocityStrengthFactor)); + velocity.multiply(scalar); + + double y = getConfig().velocityVerticalBase + (getLevelPercent(level) * getConfig().velocityVerticalFactor); + if (getConfig().velocityAdditive) { + velocity = p.getVelocity().clone().add(velocity).add(new Vector(0, y, 0)); + } else { + velocity.setY(y); + } + + double max = Math.max(0, getConfig().maxResultingVelocity); + if (max > 0 && velocity.lengthSquared() > max * max) { + velocity = velocity.normalize().multiply(max); + } + + p.setVelocity(velocity); + } + + private void applyDashMomentum(Player p, org.bukkit.Location origin, org.bukkit.Location target) { + if (!getConfig().applyForwardMomentumAfterDash) { + return; + } + + Vector momentum = target.toVector().subtract(origin.toVector()); + if (momentum.lengthSquared() <= 0.000001) { + momentum = origin.getDirection().clone(); + } + + momentum.normalize().multiply(Math.max(0, getConfig().forwardMomentum)); + double y = p.getVelocity().getY(); + if (getConfig().replaceVerticalMomentum) { + y = getConfig().verticalMomentum; + } else { + y += getConfig().verticalMomentum; + } + + momentum.setY(y); + p.setVelocity(momentum); + } + + private Vector resolveDashDirection(Player p) { + Vector direction = p.getLocation().getDirection().clone(); + if (getConfig().flattenVelocityDashDirection) { + direction.setY(0); + } + + if (direction.lengthSquared() <= 0.000001) { + return new Vector(); + } + + return direction.normalize(); + } + + private boolean isBlockedAhead(Player p, Vector direction) { + if (!getConfig().blockDashWhenWallAhead) { + return false; + } + + double distance = Math.max(0.1, getConfig().wallCheckDistance); + org.bukkit.util.RayTraceResult hit = p.getWorld().rayTraceBlocks(p.getEyeLocation(), direction, distance, FluidCollisionMode.NEVER, true); + return hit != null && hit.getHitBlock() != null; + } + + private void preserveSwimStateAfterDash(Player p, boolean wasSwimming) { + if (!getConfig().preserveSwimmingAfterDash || !wasSwimming) { + return; + } + + if (!isInWaterDashState(p)) { + return; + } + + p.setSwimming(true); + J.runEntity(p, () -> { + if (p.isOnline() && isInWaterDashState(p)) { + p.setSwimming(true); + } + }, 1); + } + + private boolean isDashEnvironmentValid(Player p) { + if (getConfig().allowWaterTrigger && isInWaterDashState(p)) { + return true; + } + + return getConfig().allowRainTrigger && isRainingAt(p); + } + + private boolean isInWaterDashState(Player p) { + return p.isInWater() || p.isSwimming() || p.getLocation().getBlock().isLiquid() || p.getEyeLocation().getBlock().isLiquid(); + } + + private boolean isRainingAt(Player p) { + if (!p.getWorld().hasStorm()) { + return false; + } + + int topY = p.getWorld().getHighestBlockYAt(p.getLocation()); + return p.getLocation().getY() >= topY - 1; + } + + private org.bukkit.Location findSafeDashTarget(Player p, double maxDistance) { + Vector direction = p.getLocation().getDirection().clone(); + if (direction.lengthSquared() <= 0.0001) { + return null; + } + + direction.normalize(); + for (double d = maxDistance; d >= 1.0; d -= 0.5) { + org.bukkit.Location c = p.getLocation().clone().add(direction.clone().multiply(d)); + if (isSafe(c)) { + return c; + } + } + + return null; + } + + private boolean isSafe(org.bukkit.Location location) { + Block feet = location.getBlock(); + Block head = location.clone().add(0, 1, 0).getBlock(); + Block floor = location.clone().subtract(0, 1, 0).getBlock(); + return feet.isPassable() && head.isPassable() && (floor.getType().isSolid() || floor.isLiquid()); + } + + private double getDashDistance(int level) { + return getConfig().dashDistanceBase + (getLevelPercent(level) * getConfig().dashDistanceFactor); + } + + private int getCooldownTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private enum TriggerType { + SNEAK, + ATTACK + } + + @NoArgsConstructor + @ConfigDescription("Sneak while it is raining to dash like a water blink through the storm.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Dash Distance Base for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dashDistanceBase = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Dash Distance Factor for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dashDistanceFactor = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 140; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Burst for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBurst = 11; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows the original rain-based trigger for Tidecaller dashes.", impact = "Disable this to make dashes water-only.") + boolean allowRainTrigger = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows Tidecaller dashes while the player is in water.", impact = "Enable this to make sneak/attack water woosh work consistently.") + boolean allowWaterTrigger = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables sneak-to-dash trigger.", impact = "Disable this if you only want attack-based trigger behavior.") + boolean enableSneakTrigger = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables attack-swing trigger (any item or empty hand).", impact = "Enable this for left-click water flings without requiring special items.") + boolean enableAttackTrigger = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Requires sneaking for attack-swing trigger.", impact = "True makes attack trigger only fire while sneaking; false allows it anytime in valid dash environments.") + boolean attackTriggerRequiresSneak = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Restricts attack-swing trigger to water states only.", impact = "True prevents accidental attack-trigger dashes on land even if rain trigger is enabled.") + boolean attackTriggerWaterOnly = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Uses velocity-based dash movement instead of teleporting to a target.", impact = "True makes tidecaller behave like a movement burst and prevents blink-style wall bypass.") + boolean useVelocityDash = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "If true, removes pitch from velocity dash direction.", impact = "True keeps movement mostly horizontal; false follows exact look direction including up/down.") + boolean flattenVelocityDashDirection = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base forward velocity strength of a velocity dash.", impact = "Higher values produce faster bursts.") + double velocityStrengthBase = 1.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional forward velocity strength gained by adaptation level.", impact = "Higher values make higher levels burst farther/faster.") + double velocityStrengthFactor = 0.85; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base vertical velocity contribution for velocity dashes.", impact = "Small positive values keep water movement fluid; lower values stay flatter.") + double velocityVerticalBase = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional vertical velocity contribution gained by adaptation level.", impact = "Higher values increase upward kick at higher levels.") + double velocityVerticalFactor = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Adds dash velocity on top of current velocity when true.", impact = "True preserves momentum chains; false applies a fresh velocity vector.") + boolean velocityAdditive = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on resulting velocity magnitude after dash.", impact = "Lower values are safer for anticheat and collisions.") + double maxResultingVelocity = 2.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cancels dash if a solid block is detected directly ahead.", impact = "Prevents wall clipping and blink-like wall bypass.") + boolean blockDashWhenWallAhead = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Distance ahead used to detect blocking walls.", impact = "Higher values are safer but can block dashes near tight spaces.") + double wallCheckDistance = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Applies forward velocity after the dash teleport.", impact = "True makes the dash feel fluid instead of instantly stopping at the target.") + boolean applyForwardMomentumAfterDash = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Horizontal forward momentum applied after the dash teleport.", impact = "Higher values create a stronger forward burst after each dash.") + double forwardMomentum = 1.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Vertical momentum added or set after the dash teleport.", impact = "Small positive values help keep water movement smooth; negative values push downward.") + double verticalMomentum = 0.02; + @art.arcane.adapt.util.config.ConfigDoc(value = "If true, replaces current vertical velocity with verticalMomentum.", impact = "False adds verticalMomentum on top of existing vertical motion.") + boolean replaceVerticalMomentum = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Re-applies swimming pose after dash when the player started swimming and remains in water.", impact = "Prevents dash teleports from popping swimmers out of swim posture.") + boolean preserveSwimmingAfterDash = true; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTurtlesMiningSpeed.java b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTurtlesMiningSpeed.java new file mode 100644 index 000000000..1f14ee72f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTurtlesMiningSpeed.java @@ -0,0 +1,124 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.seaborrne; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; + +public class SeaborneTurtlesMiningSpeed extends SimpleAdaptation { + + public SeaborneTurtlesMiningSpeed() { + super("seaborne-turtles-mining-speed"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("seaborn.haste.description")); + setDisplayName(Localizer.dLocalize("seaborn.haste.name")); + setIcon(Material.PRISMARINE_SHARD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(3000); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE) + .key("challenge_seaborne_mining_2500") + .title(Localizer.dLocalize("advancement.challenge_seaborne_mining_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_mining_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_PICKAXE) + .key("challenge_seaborne_mining_25k") + .title(Localizer.dLocalize("advancement.challenge_seaborne_mining_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_mining_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_seaborne_mining_2500", "seaborne.turtles-mining.blocks-underwater", 2500, 300); + registerMilestone("challenge_seaborne_mining_25k", "seaborne.turtles-mining.blocks-underwater", 25000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("seaborn.haste.lore1")); + } + + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player player = adaptPlayer.getPlayer(); + if (player == null || !player.isOnline()) { + continue; + } + + withPlayerThread(player, () -> { + if (!player.isOnline()) { + return; + } + + int level = getActiveLevel(player); + if (level <= 0 || !player.isInWater()) { + return; + } + + player.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, 62, 1, false, false)); + getPlayer(player).getData().addStat("seaborne.turtles-mining.blocks-underwater", 1); + }); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain haste while mining underwater.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTurtlesVision.java b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTurtlesVision.java new file mode 100644 index 000000000..439a28a56 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/seaborrne/SeaborneTurtlesVision.java @@ -0,0 +1,115 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.seaborrne; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class SeaborneTurtlesVision extends SimpleAdaptation { + + public SeaborneTurtlesVision() { + super("seaborne-turtles-vision"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("seaborn.night_vision.description")); + setDisplayName(Localizer.dLocalize("seaborn.night_vision.name")); + setIcon(Material.DIAMOND_HORSE_ARMOR); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(3000); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TURTLE_HELMET) + .key("challenge_seaborne_vision_72k") + .title(Localizer.dLocalize("advancement.challenge_seaborne_vision_72k.title")) + .description(Localizer.dLocalize("advancement.challenge_seaborne_vision_72k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_seaborne_vision_72k", "seaborne.turtles-vision.time-underwater", 72000, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("seaborn.night_vision.lore1")); + } + + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player player = adaptPlayer.getPlayer(); + if (player == null || !player.isOnline()) { + continue; + } + + withPlayerThread(player, () -> { + if (!player.isOnline()) { + return; + } + + int level = getActiveLevel(player); + if (level <= 0 || !player.isInWater()) { + return; + } + + player.addPotionEffect(new PotionEffect(PotionEffectType.NIGHT_VISION, 62, 0, false, false)); + getPlayer(player).getData().addStat("seaborne.turtles-vision.time-underwater", 1); + }); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain night vision while underwater.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthEnderVeil.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthEnderVeil.java new file mode 100644 index 000000000..26a76662f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthEnderVeil.java @@ -0,0 +1,116 @@ +package art.arcane.adapt.content.adaptation.stealth; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.events.api.ReflectiveHandler; +import art.arcane.adapt.util.reflect.events.api.entity.EndermanAttackPlayerEvent; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityTargetLivingEntityEvent; + +public class StealthEnderVeil extends SimpleAdaptation { + + public StealthEnderVeil() { + super("stealth-enderveil"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("stealth.ender_veil.description")); + setDisplayName(Localizer.dLocalize("stealth.ender_veil.name")); + setIcon(Material.CARVED_PUMPKIN); + setBaseCost(getConfig().baseCost); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + setInterval(9182); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_stealth_ender_veil_200") + .title(Localizer.dLocalize("advancement.challenge_stealth_ender_veil_200.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_ender_veil_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_stealth_ender_veil_200", "stealth.ender-veil.stares-survived", 200, 300); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("stealth.ender_veil.lore" + (level < 2 ? 1 : 2))); + } + + @Override + public void onTick() { + + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onTarget(EntityTargetLivingEntityEvent event) { + org.bukkit.entity.LivingEntity target = event.getTarget(); + if (target == null + || target.getType() != EntityType.PLAYER + || event.getEntityType() != EntityType.ENDERMAN + || !(event.getTarget() instanceof Player player)) { + return; + } + + int level = getActiveLevel(player); + if (level <= 0) { + return; + } + + if (level > 1 || player.isSneaking()) { + event.setCancelled(true); + getPlayer(player).getData().addStat("stealth.ender-veil.stares-survived", 1); + } + } + + @ReflectiveHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onTarget(EndermanAttackPlayerEvent event) { + org.bukkit.entity.Player player = event.getPlayer(); + int level = getActiveLevel(player); + if (level <= 0) { + return; + } + + if (level > 1 || player.isSneaking()) { + event.setCancelled(true); + getPlayer(player).getData().addStat("stealth.ender-veil.stares-survived", 1); + } + } + + @NoArgsConstructor + @ConfigDescription("Prevent Enderman aggression without wearing a pumpkin.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 1.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthGhostArmor.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthGhostArmor.java new file mode 100644 index 000000000..d0cb6f834 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthGhostArmor.java @@ -0,0 +1,175 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.stealth; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.IAttribute; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; + +import java.util.UUID; + +public class StealthGhostArmor extends SimpleAdaptation { + private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-ghost-armor".getBytes()); + private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString("adapt:ghost-armor"); + + public StealthGhostArmor() { + super("stealth-ghost-armor"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("stealth.ghost_armor.description")); + setDisplayName(Localizer.dLocalize("stealth.ghost_armor.name")); + setIcon(Material.CHAINMAIL_HELMET); + setInterval(5353); + setBaseCost(getConfig().baseCost); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEATHER_CHESTPLATE) + .key("challenge_stealth_ghost_100") + .title(Localizer.dLocalize("advancement.challenge_stealth_ghost_100.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_ghost_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CHAINMAIL_CHESTPLATE) + .key("challenge_stealth_ghost_500") + .title(Localizer.dLocalize("advancement.challenge_stealth_ghost_500.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_ghost_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_stealth_ghost_100", "stealth.ghost-armor.armor-consumed", 100, 300); + registerMilestone("challenge_stealth_ghost_500", "stealth.ghost-armor.armor-consumed", 500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getMaxArmorPoints(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("stealth.ghost_armor.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getMaxArmorPerTick(getLevelPercent(level)), 1) + C.GRAY + " " + Localizer.dLocalize("stealth.ghost_armor.lore2")); + } + + public double getMaxArmorPoints(double factor) { + return M.lerp(getConfig().minArmor, getConfig().maxArmor, factor); + } + + public double getMaxArmorPerTick(double factor) { + return M.lerp(getConfig().minArmorPerTick, getConfig().maxArmorPerTick, factor); + } + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + IAttribute attribute = Version.get().getAttribute(p, Attributes.GENERIC_ARMOR); + if (attribute == null) { + continue; + } + + if (!hasActiveAdaptation(p)) { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + continue; + } + double oldArmor = 0; + for (IAttribute.Modifier modifier : attribute.getModifier(MODIFIER, MODIFIER_KEY)) { + double amount = modifier.getAmount(); + if (!Double.isNaN(amount) && amount > oldArmor) { + oldArmor = amount; + } + } + double armor = getMaxArmorPoints(getLevelPercent(p)); + armor = Double.isNaN(armor) ? 0 : armor; + + if (oldArmor < armor) { + attribute.setModifier(MODIFIER, MODIFIER_KEY, Math.min(armor, oldArmor + getMaxArmorPerTick(getLevelPercent(p))), AttributeModifier.Operation.ADD_NUMBER); + } else if (oldArmor > armor) { + attribute.setModifier(MODIFIER, MODIFIER_KEY, armor, AttributeModifier.Operation.ADD_NUMBER); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageEvent e) { + if (e.getEntity() instanceof Player p && hasActiveAdaptation(p) && e.getDamage() > 0) { + // Check if 2.5 * e.getDamage() is greater than 10 if so just set it to 10 otherwise use the value of 2.5 * e.getDamage() + int damageXP = (int) Math.min(10, 2.5 * e.getDamage()); + xp(p, damageXP); + getPlayer(p).getData().addStat("stealth.ghost-armor.armor-consumed", 1); + J.runEntity(p, () -> { + IAttribute attribute = Version.get().getAttribute(p, Attributes.GENERIC_ARMOR); + if (attribute == null) return; + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + }); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Slowly build armor when not taking damage, consumed on the next hit.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Armor for the Stealth Ghost Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxArmor = 16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Armor for the Stealth Ghost Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int minArmor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Armor Per Tick for the Stealth Ghost Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int maxArmorPerTick = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Armor Per Tick for the Stealth Ghost Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int minArmorPerTick = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.335; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthShadowDecoy.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthShadowDecoy.java new file mode 100644 index 000000000..76288dc83 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthShadowDecoy.java @@ -0,0 +1,481 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.stealth; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerAnimationEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; + +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +public class StealthShadowDecoy extends SimpleAdaptation { + private static final PacketDecoyBridge PACKET_DECOY = PacketDecoyBridge.create(); + + private final Map cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map activeDecoys = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map anchorOwners = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map ownerEquipmentMaskSync = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map ownerTrailNextAt = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map ownerAggroNextAt = new java.util.concurrent.ConcurrentHashMap<>(); + + public StealthShadowDecoy() { + super("stealth-shadow-decoy"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("stealth.shadow_decoy.description")); + setDisplayName(Localizer.dLocalize("stealth.shadow_decoy.name")); + setIcon(Material.PLAYER_HEAD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(5); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ARMOR_STAND) + .key("challenge_stealth_decoy_100") + .title(Localizer.dLocalize("advancement.challenge_stealth_decoy_100.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_decoy_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ARMOR_STAND) + .key("challenge_stealth_decoy_distract_500") + .title(Localizer.dLocalize("advancement.challenge_stealth_decoy_distract_500.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_decoy_distract_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_stealth_decoy_100", "stealth.shadow-decoy.decoys-spawned", 100, 300); + registerMilestone("challenge_stealth_decoy_distract_500", "stealth.shadow-decoy.mobs-distracted", 500, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.duration(getDecoyTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("stealth.shadow_decoy.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getDecoyRadius(level)) + C.GRAY + " " + Localizer.dLocalize("stealth.shadow_decoy.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("stealth.shadow_decoy.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + cooldowns.remove(id); + ownerEquipmentMaskSync.remove(id); + ownerTrailNextAt.remove(id); + ownerAggroNextAt.remove(id); + DecoyState state = activeDecoys.remove(id); + if (state != null) { + removeDecoy(state, null); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageEvent e) { + if (anchorOwners.containsKey(e.getEntity().getUniqueId())) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + UUID ownerId = anchorOwners.get(e.getEntity().getUniqueId()); + if (ownerId == null) { + return; + } + + e.setCancelled(true); + DecoyState state = activeDecoys.get(ownerId); + if (state == null) { + return; + } + + if (!(e.getEntity() instanceof ArmorStand stand) || !(e.getDamager() instanceof LivingEntity attacker)) { + return; + } + + reactToDecoyHit(state, stand, attacker); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerAnimationEvent e) { + Player attacker = e.getPlayer(); + if (activeDecoys.isEmpty()) { + return; + } + + RayTraceResult hit = attacker.getWorld().rayTraceEntities( + attacker.getEyeLocation(), + attacker.getEyeLocation().getDirection(), + Math.max(1.0, getConfig().decoySwingDetectionReach), + entity -> anchorOwners.containsKey(entity.getUniqueId()) + ); + + if (hit == null || !(hit.getHitEntity() instanceof ArmorStand stand)) { + return; + } + + UUID ownerId = anchorOwners.get(stand.getUniqueId()); + if (ownerId == null) { + return; + } + + DecoyState state = activeDecoys.get(ownerId); + if (state == null) { + return; + } + + reactToDecoyHit(state, stand, attacker); + } + + private void reactToDecoyHit(DecoyState state, ArmorStand stand, LivingEntity attacker) { + if (state.packetDecoy() != null) { + state.packetDecoy().hitFrom(attacker.getLocation()); + } + + Vector push = stand.getLocation().toVector().subtract(attacker.getLocation().toVector()); + if (push.lengthSquared() < 0.0001) { + push = attacker.getLocation().getDirection().multiply(-1); + } + + push.setY(0); + push.normalize().multiply(Math.max(0, getConfig().decoyHitKnockback)); + push.setY(Math.max(0, getConfig().decoyHitLift)); + stand.setVelocity(push); + SoundPlayer.of(stand.getWorld()).play(stand.getLocation(), Sound.ENTITY_PLAYER_HURT, 0.8f, 1.2f); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + if (e.isSneaking()) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + long now = System.currentTimeMillis(); + if (now < cooldowns.getOrDefault(p.getUniqueId(), 0L)) { + return; + } + + spawnDecoy(p, level); + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + xp(p, getConfig().xpOnDecoy); + getPlayer(p).getData().addStat("stealth.shadow-decoy.decoys-spawned", 1); + } + + private void spawnDecoy(Player owner, int level) { + DecoyState previous = activeDecoys.remove(owner.getUniqueId()); + if (previous != null) { + removeDecoy(previous, owner); + } + + ArmorStand anchor = spawnAnchor(owner.getLocation()); + anchorOwners.put(anchor.getUniqueId(), owner.getUniqueId()); + PacketPlayerDecoy packetDecoy = PACKET_DECOY.spawnDecoy(owner, anchor, getConfig().tabListRemoveDelayTicks, getConfig().decoySkinLayerMask); + + if (packetDecoy == null && getConfig().legacyFallbackEnabled) { + configureLegacyVisual(anchor, owner); + } + + long expiresAt = System.currentTimeMillis() + (getDecoyTicks(level) * 50L); + UUID ownerId = owner.getUniqueId(); + activeDecoys.put(ownerId, new DecoyState(ownerId, anchor.getUniqueId(), packetDecoy, expiresAt, level)); + ownerTrailNextAt.put(ownerId, 0L); + ownerAggroNextAt.put(ownerId, 0L); + + redirectAggro(owner, anchor, level); + if (areParticlesEnabled()) { + anchor.getWorld().spawnParticle(Particle.SMOKE, anchor.getLocation().add(0, 1, 0), 18, 0.2, 0.4, 0.2, 0.03); + } + SoundPlayer.of(owner.getWorld()).play(owner.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.6f, 1.7f); + } + + private ArmorStand spawnAnchor(Location location) { + return location.getWorld().spawn(location, ArmorStand.class, stand -> { + stand.setMarker(false); + stand.setVisible(false); + stand.setInvisible(true); + stand.setGravity(true); + stand.setInvulnerable(false); + stand.setSilent(true); + stand.setBasePlate(false); + stand.setSmall(false); + stand.setArms(false); + stand.setCollidable(true); + }); + } + + private void configureLegacyVisual(ArmorStand stand, Player owner) { + stand.setMarker(false); + stand.setVisible(true); + stand.setInvisible(false); + stand.setSmall(false); + stand.setArms(true); + stand.setCustomNameVisible(true); + stand.setCustomName(C.GRAY + owner.getName()); + + EntityEquipment equipment = stand.getEquipment(); + if (equipment == null) { + return; + } + + equipment.setHelmet(owner.getInventory().getHelmet()); + equipment.setChestplate(owner.getInventory().getChestplate()); + equipment.setLeggings(owner.getInventory().getLeggings()); + equipment.setBoots(owner.getInventory().getBoots()); + equipment.setItemInMainHand(owner.getInventory().getItemInMainHand()); + equipment.setItemInOffHand(owner.getInventory().getItemInOffHand()); + } + + private void redirectAggro(Player owner, LivingEntity target, int level) { + double radius = getDecoyRadius(level); + Location center = target.getLocation(); + for (Entity entity : owner.getWorld().getNearbyEntities(center, radius, radius, radius)) { + if (!(entity instanceof Mob mob)) { + continue; + } + + if (isProtectedFriendly(owner, mob)) { + continue; + } + + if (mob.getTarget() == owner || mob.hasLineOfSight(owner)) { + mob.setTarget(target); + getPlayer(owner).getData().addStat("stealth.shadow-decoy.mobs-distracted", 1); + } + } + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + Iterator> it = activeDecoys.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + UUID ownerId = entry.getKey(); + DecoyState state = entry.getValue(); + Player owner = Bukkit.getPlayer(ownerId); + + if (owner == null || !owner.isOnline() || state.expiresAt() <= now) { + removeDecoy(state, owner); + it.remove(); + continue; + } + + Entity entity = Bukkit.getEntity(state.anchorId()); + if (!(entity instanceof ArmorStand anchor) || !anchor.isValid()) { + removeDecoy(state, owner); + it.remove(); + continue; + } + + PacketPlayerDecoy packetDecoy = state.packetDecoy(); + if (packetDecoy != null) { + packetDecoy.tick(); + packetDecoy.syncToAnchor(anchor.getLocation(), anchor.isOnGround()); + packetDecoy.lookAtViewers(anchor.getLocation().add(0, getConfig().decoyEyeHeight, 0)); + } + + applyOwnerInvisibility(owner); + syncOwnerEquipmentHidden(owner); + if (now >= ownerTrailNextAt.getOrDefault(ownerId, 0L)) { + spawnOwnerTrail(owner); + ownerTrailNextAt.put(ownerId, now + Math.max(25L, getConfig().ownerTrailIntervalMillis)); + } + if (now >= ownerAggroNextAt.getOrDefault(ownerId, 0L)) { + redirectAggro(owner, anchor, state.level()); + ownerAggroNextAt.put(ownerId, now + Math.max(25L, getConfig().aggroRedirectIntervalMillis)); + } + } + } + + private void applyOwnerInvisibility(Player owner) { + int duration = Math.max(20, getConfig().ownerInvisibilityRefreshTicks); + PotionEffect current = owner.getPotionEffect(PotionEffectType.INVISIBILITY); + if (current != null && current.getDuration() > duration + 5) { + return; + } + + owner.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, duration, getConfig().ownerInvisibilityAmplifier, false, false, false), true); + } + + private void spawnOwnerTrail(Player owner) { + owner.getWorld().spawnParticle( + Particle.SMOKE, + owner.getLocation().add(0, getConfig().ownerTrailYOffset, 0), + Math.max(1, getConfig().ownerTrailParticles), + Math.max(0, getConfig().ownerTrailHorizontalSpread), + Math.max(0, getConfig().ownerTrailVerticalSpread), + Math.max(0, getConfig().ownerTrailHorizontalSpread), + Math.max(0, getConfig().ownerTrailSpeed) + ); + } + + private void syncOwnerEquipmentHidden(Player owner) { + long now = System.currentTimeMillis(); + long nextAt = ownerEquipmentMaskSync.getOrDefault(owner.getUniqueId(), 0L); + if (now < nextAt) { + return; + } + + PACKET_DECOY.sendOwnerEquipment(owner, true); + ownerEquipmentMaskSync.put(owner.getUniqueId(), now + Math.max(100L, getConfig().ownerEquipmentHideResendMillis)); + } + + private void restoreOwnerEquipment(Player owner) { + if (owner == null || !owner.isOnline()) { + return; + } + + ownerEquipmentMaskSync.remove(owner.getUniqueId()); + PACKET_DECOY.sendOwnerEquipment(owner, false); + } + + private void removeDecoy(DecoyState state, Player owner) { + if (state.packetDecoy() != null) { + state.packetDecoy().destroy(); + } + + Entity entity = Bukkit.getEntity(state.anchorId()); + anchorOwners.remove(state.anchorId()); + ownerTrailNextAt.remove(state.ownerId()); + ownerAggroNextAt.remove(state.ownerId()); + if (entity instanceof ArmorStand stand && stand.isValid()) { + stand.remove(); + } + + restoreOwnerEquipment(owner); + } + + private long getCooldownMillis(int level) { + return Math.max(1000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + private int getDecoyTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().decoyTicksBase + (getLevelPercent(level) * getConfig().decoyTicksFactor))); + } + + private double getDecoyRadius(int level) { + return getConfig().decoyRadiusBase + (getLevelPercent(level) * getConfig().decoyRadiusFactor); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Stopping sneak spawns a short-lived shadow decoy that pulls your current aggro.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base cooldown after creating a decoy, in milliseconds.", impact = "Higher values mean longer time between activations.") + double cooldownMillisBase = 18000; + @art.arcane.adapt.util.config.ConfigDoc(value = "How much cooldown is reduced by leveling.", impact = "Higher values reduce cooldown more at higher levels.") + double cooldownMillisFactor = 12000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base active duration in ticks.", impact = "Higher values keep decoys active longer.") + double decoyTicksBase = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration scaling from level, in ticks.", impact = "Higher values extend duration more per level.") + double decoyTicksFactor = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base aggro redirect radius.", impact = "Higher values pull aggro from farther away.") + double decoyRadiusBase = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Aggro radius scaling from level.", impact = "Higher values expand pull range more per level.") + double decoyRadiusFactor = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Visual eye height used for fake player facing.", impact = "Adjust if head rotation appears too high or too low.") + double decoyEyeHeight = 1.62; + @art.arcane.adapt.util.config.ConfigDoc(value = "Delay before removing the fake player from tab list, in ticks.", impact = "Small values hide tab entries faster; larger values help skins load.") + int tabListRemoveDelayTicks = -1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows armor stand visual fallback if packet NPC creation fails.", impact = "Turn off to disable fallback visuals on incompatible server builds.") + boolean legacyFallbackEnabled = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Refresh duration for owner invisibility while a decoy is active.", impact = "Higher values keep invisibility active longer between refreshes.") + int ownerInvisibilityRefreshTicks = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Amplifier for the temporary invisibility effect.", impact = "Most servers should leave this at 0.") + int ownerInvisibilityAmplifier = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Smoke particles emitted around the invisible owner each tick while decoy is active.", impact = "Higher values create a stronger visible trail.") + int ownerTrailParticles = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Horizontal spread for owner smoke trail.", impact = "Higher values make the trail wider.") + double ownerTrailHorizontalSpread = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Vertical spread for owner smoke trail.", impact = "Higher values make the trail taller.") + double ownerTrailVerticalSpread = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Vertical offset for smoke trail spawn location.", impact = "Adjust to move trail closer to feet or torso.") + double ownerTrailYOffset = 0.1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Particle speed for owner smoke trail.", impact = "Higher values make trail movement more turbulent.") + double ownerTrailSpeed = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds between owner trail particle bursts while decoy is active.", impact = "Lower values make the owner trail denser; higher values reduce particle cost.") + long ownerTrailIntervalMillis = 75; + @art.arcane.adapt.util.config.ConfigDoc(value = "How often owner equipment-hide packets are resent while invisible, in milliseconds.", impact = "Lower values keep visuals tighter for joining viewers, higher values reduce packet traffic.") + long ownerEquipmentHideResendMillis = 250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds between aggro redirect scans while a decoy is active.", impact = "Lower values pull mobs more aggressively; higher values reduce nearby-entity scan cost.") + long aggroRedirectIntervalMillis = 150; + @art.arcane.adapt.util.config.ConfigDoc(value = "Horizontal knockback applied when the decoy is hit.", impact = "Higher values make the decoy react more dramatically when struck.") + double decoyHitKnockback = 0.28; + @art.arcane.adapt.util.config.ConfigDoc(value = "Vertical lift applied when the decoy is hit.", impact = "Higher values make impacts pop the decoy upward more.") + double decoyHitLift = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Swing ray distance used to detect decoy hits.", impact = "Higher values make swings connect from farther away.") + double decoySwingDetectionReach = 4.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Bitmask for visible skin layers on the fake player decoy.", impact = "127 enables all standard skin layers (hat, jacket, sleeves, pants).") + int decoySkinLayerMask = 127; + @art.arcane.adapt.util.config.ConfigDoc(value = "Experience granted on each decoy spawn.", impact = "Higher values level the adaptation faster.") + double xpOnDecoy = 18; + } + +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthShadowDecoyPackets.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthShadowDecoyPackets.java new file mode 100644 index 000000000..1d2ce7731 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthShadowDecoyPackets.java @@ -0,0 +1,861 @@ +package art.arcane.adapt.content.adaptation.stealth; + +import art.arcane.adapt.Adapt; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +record DecoyState(UUID ownerId, UUID anchorId, PacketPlayerDecoy packetDecoy, + long expiresAt, int level) { +} + +final class PacketPlayerDecoy { + final PacketDecoyBridge bridge; + final int entityId; + final Object nmsEntity; + private final World world; + private final UUID profileId; + private final long removeTabAt; + private final Set knownViewers; + private boolean removedFromTab; + private Object spawnPlayerInfoPacket; + private Object spawnAddEntityPacket; + private Object spawnMetadataPacket; + private Object spawnEquipmentPacket; + private long lastPositionSyncAt; + private double lastX; + private double lastY; + private double lastZ; + private float lastYaw; + private float lastPitch; + + PacketPlayerDecoy(PacketDecoyBridge bridge, World world, UUID profileId, int entityId, Object nmsEntity, int tabListRemoveDelayTicks) { + this.bridge = bridge; + this.world = world; + this.profileId = profileId; + this.entityId = entityId; + this.nmsEntity = nmsEntity; + this.removeTabAt = System.currentTimeMillis() + Math.max(0, tabListRemoveDelayTicks) * 50L; + this.knownViewers = java.util.concurrent.ConcurrentHashMap.newKeySet(); + this.removedFromTab = false; + this.spawnPlayerInfoPacket = null; + this.spawnAddEntityPacket = null; + this.spawnMetadataPacket = null; + this.spawnEquipmentPacket = null; + this.lastPositionSyncAt = 0L; + this.lastX = Double.NaN; + this.lastY = Double.NaN; + this.lastZ = Double.NaN; + this.lastYaw = Float.NaN; + this.lastPitch = Float.NaN; + } + + public void spawn(Object playerInfoPacket, Object addEntityPacket, Object metadataPacket, Object equipmentPacket) { + this.spawnPlayerInfoPacket = playerInfoPacket; + this.spawnAddEntityPacket = addEntityPacket; + this.spawnMetadataPacket = metadataPacket; + this.spawnEquipmentPacket = equipmentPacket; + ensureViewerState(); + } + + public void tick() { + ensureViewerState(); + if (removeTabAt < 0 || removedFromTab || System.currentTimeMillis() < removeTabAt) { + return; + } + + Object removePacket = bridge.createPlayerInfoRemovePacket(profileId); + if (removePacket != null) { + for (Player viewer : spawnedViewerPlayers()) { + bridge.sendPacket(viewer, removePacket); + } + } + + removedFromTab = true; + } + + public void lookAtViewers(Location origin) { + ensureViewerState(); + for (Player viewer : spawnedViewerPlayers()) { + Location to = viewer.getEyeLocation(); + if (origin.getWorld() != to.getWorld()) { + continue; + } + + double dx = to.getX() - origin.getX(); + double dy = to.getY() - origin.getY(); + double dz = to.getZ() - origin.getZ(); + double horizontal = Math.sqrt(dx * dx + dz * dz); + float yaw = (float) Math.toDegrees(Math.atan2(-dx, dz)); + float pitch = (float) Math.toDegrees(-Math.atan2(dy, horizontal)); + bridge.applyLook(this, yaw, pitch, viewer); + } + } + + public void syncToAnchor(Location anchor, boolean onGround) { + ensureViewerState(); + long now = System.currentTimeMillis(); + double dx = Double.isFinite(lastX) ? anchor.getX() - lastX : 1; + double dy = Double.isFinite(lastY) ? anchor.getY() - lastY : 1; + double dz = Double.isFinite(lastZ) ? anchor.getZ() - lastZ : 1; + double distanceSq = (dx * dx) + (dy * dy) + (dz * dz); + float yawDiff = Float.isFinite(lastYaw) ? Math.abs(anchor.getYaw() - lastYaw) : 360f; + float pitchDiff = Float.isFinite(lastPitch) ? Math.abs(anchor.getPitch() - lastPitch) : 360f; + + if (distanceSq < 0.0004 && yawDiff < 0.8f && pitchDiff < 0.8f && now - lastPositionSyncAt < 45L) { + return; + } + + if (bridge.syncPosition(this, anchor, onGround, spawnedViewerPlayers())) { + lastPositionSyncAt = now; + lastX = anchor.getX(); + lastY = anchor.getY(); + lastZ = anchor.getZ(); + lastYaw = anchor.getYaw(); + lastPitch = anchor.getPitch(); + } + } + + public void hitFrom(Location source) { + ensureViewerState(); + if (!Double.isFinite(lastX) || !Double.isFinite(lastZ)) { + bridge.sendHurtAnimation(this, 0f, spawnedViewerPlayers()); + return; + } + + double dx = source.getX() - lastX; + double dz = source.getZ() - lastZ; + float yaw = (float) Math.toDegrees(Math.atan2(-dx, dz)); + bridge.sendHurtAnimation(this, yaw, spawnedViewerPlayers()); + } + + public void destroy() { + Object removeEntityPacket = bridge.createRemoveEntityPacket(entityId); + Object removePlayerInfoPacket = bridge.createPlayerInfoRemovePacket(profileId); + + for (Player viewer : spawnedViewerPlayers()) { + if (removeEntityPacket != null) { + bridge.sendPacket(viewer, removeEntityPacket); + } + if (removePlayerInfoPacket != null) { + bridge.sendPacket(viewer, removePlayerInfoPacket); + } + } + + knownViewers.clear(); + } + + private void ensureViewerState() { + Set online = new HashSet<>(); + for (Player viewer : world.getPlayers()) { + if (!viewer.isOnline()) { + continue; + } + + UUID id = viewer.getUniqueId(); + online.add(id); + if (!knownViewers.contains(id)) { + spawnFor(viewer); + knownViewers.add(id); + } + } + + knownViewers.retainAll(online); + } + + private void spawnFor(Player viewer) { + if (spawnPlayerInfoPacket != null) { + bridge.sendPacket(viewer, spawnPlayerInfoPacket); + } + + if (spawnAddEntityPacket != null) { + bridge.sendPacket(viewer, spawnAddEntityPacket); + } + + if (spawnMetadataPacket != null) { + bridge.sendPacket(viewer, spawnMetadataPacket); + } + + if (spawnEquipmentPacket != null) { + bridge.sendPacket(viewer, spawnEquipmentPacket); + } + + if (removedFromTab) { + Object removePacket = bridge.createPlayerInfoRemovePacket(profileId); + if (removePacket != null) { + bridge.sendPacket(viewer, removePacket); + } + } + } + + private List spawnedViewerPlayers() { + List viewers = new ArrayList<>(); + for (Player viewer : world.getPlayers()) { + if (viewer.isOnline() && knownViewers.contains(viewer.getUniqueId())) { + viewers.add(viewer); + } + } + + return viewers; + } +} + +final class PacketDecoyBridge { + private final boolean supported; + + private final Method craftServerGetServer; + private final Method craftWorldGetHandle; + private final Method craftPlayerGetHandle; + + private final Constructor serverPlayerConstructor; + private final Method clientInformationCreateDefault; + + private final Constructor gameProfileBasicConstructor; + private final Constructor gameProfileWithPropertiesConstructor; + private final Method gameProfilePropertiesAccessor; + private final Method playerGetGameProfile; + + private final Method entitySetPos; + private final Method entitySetRot; + private final Method entitySetOnGround; + private final Method livingSetYHeadRot; + private final Method livingSetYBodyRot; + private final Method entityGetId; + private final Method entityGetType; + private final Method entityGetEntityData; + private final Method synchedEntityDataGetNonDefaultValues; + private final Method synchedEntityDataPackAll; + private final Method synchedEntityDataSet; + + private final Method playerInfoCreateSingleInitializing; + private final Constructor playerInfoActionConstructor; + private final Constructor playerInfoFromEntriesConstructor; + private final Constructor playerInfoEntryExplicitConstructor; + private final Class playerInfoActionClass; + private final Object defaultGameType; + + private final Constructor addEntityConstructor; + private final Constructor addEntityExplicitConstructor; + private final Field blockPosZero; + private final Field vec3Zero; + private final Constructor setEntityDataConstructor; + private final Constructor moveEntityRotConstructor; + private final Constructor rotateHeadConstructor; + private final Constructor removeEntitiesConstructor; + private final Constructor playerInfoRemoveConstructor; + private final Method entityPositionSyncOf; + private final Constructor hurtAnimationConstructor; + private final Constructor setEquipmentConstructor; + private final Method pairOfMethod; + private final Method craftItemStackAsNmsCopy; + private final Class equipmentSlotClass; + private final Field avatarModelCustomizationAccessor; + + private final Field serverPlayerConnectionField; + private final Method connectionSendPacket; + + private PacketDecoyBridge() throws ReflectiveOperationException { + String craftPackage = Bukkit.getServer().getClass().getPackage().getName(); + if (!craftPackage.startsWith("org.bukkit.craftbukkit")) { + throw new ClassNotFoundException("CraftBukkit package not detected: " + craftPackage); + } + + Class craftServerClass = Class.forName(craftPackage + ".CraftServer"); + Class craftWorldClass = Class.forName(craftPackage + ".CraftWorld"); + Class craftPlayerClass = Class.forName(craftPackage + ".entity.CraftPlayer"); + + Class minecraftServerClass = Class.forName("net.minecraft.server.MinecraftServer"); + Class serverLevelClass = Class.forName("net.minecraft.server.level.ServerLevel"); + Class serverPlayerClass = Class.forName("net.minecraft.server.level.ServerPlayer"); + Class clientInformationClass = Class.forName("net.minecraft.server.level.ClientInformation"); + Class nmsPlayerClass = Class.forName("net.minecraft.world.entity.player.Player"); + Class livingEntityClass = Class.forName("net.minecraft.world.entity.LivingEntity"); + Class entityClass = Class.forName("net.minecraft.world.entity.Entity"); + Class entityTypeClass = Class.forName("net.minecraft.world.entity.EntityType"); + Class avatarClass = Class.forName("net.minecraft.world.entity.Avatar"); + Class equipmentSlotClass = Class.forName("net.minecraft.world.entity.EquipmentSlot"); + Class entityDataAccessorClass = Class.forName("net.minecraft.network.syncher.EntityDataAccessor"); + Class synchedEntityDataClass = Class.forName("net.minecraft.network.syncher.SynchedEntityData"); + Class gameProfileClass = Class.forName("com.mojang.authlib.GameProfile"); + Class vec3Class = Class.forName("net.minecraft.world.phys.Vec3"); + Class pairClass = Class.forName("com.mojang.datafixers.util.Pair"); + Class packetClass = Class.forName("net.minecraft.network.protocol.Packet"); + Class connectionClass = Class.forName("net.minecraft.server.network.ServerCommonPacketListenerImpl"); + Class playerInfoPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket"); + Class playerInfoEntryClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry"); + Class playerInfoActionEnumClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action"); + Class gameTypeClass = Class.forName("net.minecraft.world.level.GameType"); + Class componentClass = Class.forName("net.minecraft.network.chat.Component"); + Class remoteChatSessionDataClass = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data"); + Class addEntityPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket"); + Class entityPositionSyncPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket"); + Class hurtAnimationPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket"); + Class blockPosClass = Class.forName("net.minecraft.core.BlockPos"); + Class setEntityDataPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket"); + Class moveEntityRotClass = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot"); + Class rotateHeadPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundRotateHeadPacket"); + Class removeEntitiesPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket"); + Class playerInfoRemovePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket"); + Class setEquipmentPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket"); + Class craftItemStackClass = Class.forName(craftPackage + ".inventory.CraftItemStack"); + + this.craftServerGetServer = findMethod(craftServerClass, "getServer"); + this.craftWorldGetHandle = findMethod(craftWorldClass, "getHandle"); + this.craftPlayerGetHandle = findMethod(craftPlayerClass, "getHandle"); + + this.serverPlayerConstructor = serverPlayerClass.getConstructor(minecraftServerClass, serverLevelClass, gameProfileClass, clientInformationClass); + this.clientInformationCreateDefault = findMethod(clientInformationClass, "createDefault"); + + this.gameProfileBasicConstructor = findConstructor(gameProfileClass, UUID.class, String.class); + this.gameProfileWithPropertiesConstructor = findOptionalConstructor(gameProfileClass, UUID.class, String.class, findPropertyMapClass(gameProfileClass)); + this.gameProfilePropertiesAccessor = findOptionalMethod(gameProfileClass, "properties", "getProperties"); + this.playerGetGameProfile = findMethod(nmsPlayerClass, "getGameProfile"); + + this.entitySetPos = findMethod(entityClass, "setPos", double.class, double.class, double.class); + this.entitySetRot = findMethod(entityClass, "setRot", float.class, float.class); + this.entitySetOnGround = findMethod(entityClass, "setOnGround", boolean.class); + this.livingSetYHeadRot = findMethod(livingEntityClass, "setYHeadRot", float.class); + this.livingSetYBodyRot = findMethod(livingEntityClass, "setYBodyRot", float.class); + this.entityGetId = findMethod(entityClass, "getId"); + this.entityGetType = findMethod(entityClass, "getType"); + this.entityGetEntityData = findMethod(entityClass, "getEntityData"); + this.synchedEntityDataGetNonDefaultValues = findMethod(synchedEntityDataClass, "getNonDefaultValues"); + this.synchedEntityDataPackAll = findOptionalMethod(synchedEntityDataClass, "packAll", new Class[0]); + this.synchedEntityDataSet = findMethod(synchedEntityDataClass, "set", entityDataAccessorClass, Object.class); + + this.playerInfoCreateSingleInitializing = findOptionalMethod(playerInfoPacketClass, "createSinglePlayerInitializing", serverPlayerClass, boolean.class); + this.playerInfoActionConstructor = findOptionalConstructor(playerInfoPacketClass, playerInfoActionEnumClass, serverPlayerClass); + this.playerInfoFromEntriesConstructor = findOptionalConstructor(playerInfoPacketClass, EnumSet.class, List.class); + this.playerInfoEntryExplicitConstructor = findOptionalConstructor(playerInfoEntryClass, UUID.class, gameProfileClass, boolean.class, int.class, gameTypeClass, componentClass, boolean.class, int.class, remoteChatSessionDataClass); + this.playerInfoActionClass = playerInfoActionEnumClass; + this.defaultGameType = resolveDefaultGameType(gameTypeClass); + + this.addEntityConstructor = addEntityPacketClass.getConstructor(entityClass, int.class, blockPosClass); + this.addEntityExplicitConstructor = findOptionalConstructor(addEntityPacketClass, int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, entityTypeClass, int.class, vec3Class, double.class); + this.blockPosZero = blockPosClass.getField("ZERO"); + this.vec3Zero = vec3Class.getField("ZERO"); + this.setEntityDataConstructor = setEntityDataPacketClass.getConstructor(int.class, List.class); + this.moveEntityRotConstructor = moveEntityRotClass.getConstructor(int.class, byte.class, byte.class, boolean.class); + this.rotateHeadConstructor = rotateHeadPacketClass.getConstructor(entityClass, byte.class); + this.removeEntitiesConstructor = removeEntitiesPacketClass.getConstructor(int[].class); + this.playerInfoRemoveConstructor = playerInfoRemovePacketClass.getConstructor(List.class); + this.entityPositionSyncOf = findMethod(entityPositionSyncPacketClass, "of", entityClass); + this.hurtAnimationConstructor = findOptionalConstructor(hurtAnimationPacketClass, int.class, float.class); + this.setEquipmentConstructor = findOptionalConstructor(setEquipmentPacketClass, int.class, List.class); + this.pairOfMethod = findMethod(pairClass, "of", Object.class, Object.class); + this.craftItemStackAsNmsCopy = findMethod(craftItemStackClass, "asNMSCopy", ItemStack.class); + this.equipmentSlotClass = equipmentSlotClass; + this.avatarModelCustomizationAccessor = avatarClass.getField("DATA_PLAYER_MODE_CUSTOMISATION"); + + this.serverPlayerConnectionField = serverPlayerClass.getField("connection"); + this.connectionSendPacket = findMethod(connectionClass, "send", packetClass); + this.supported = true; + } + + private PacketDecoyBridge(boolean supported) { + this.supported = supported; + this.craftServerGetServer = null; + this.craftWorldGetHandle = null; + this.craftPlayerGetHandle = null; + this.serverPlayerConstructor = null; + this.clientInformationCreateDefault = null; + this.gameProfileBasicConstructor = null; + this.gameProfileWithPropertiesConstructor = null; + this.gameProfilePropertiesAccessor = null; + this.playerGetGameProfile = null; + this.entitySetPos = null; + this.entitySetRot = null; + this.entitySetOnGround = null; + this.livingSetYHeadRot = null; + this.livingSetYBodyRot = null; + this.entityGetId = null; + this.entityGetType = null; + this.entityGetEntityData = null; + this.synchedEntityDataGetNonDefaultValues = null; + this.synchedEntityDataPackAll = null; + this.synchedEntityDataSet = null; + this.playerInfoCreateSingleInitializing = null; + this.playerInfoActionConstructor = null; + this.playerInfoFromEntriesConstructor = null; + this.playerInfoEntryExplicitConstructor = null; + this.playerInfoActionClass = null; + this.defaultGameType = null; + this.addEntityConstructor = null; + this.addEntityExplicitConstructor = null; + this.blockPosZero = null; + this.vec3Zero = null; + this.setEntityDataConstructor = null; + this.moveEntityRotConstructor = null; + this.rotateHeadConstructor = null; + this.removeEntitiesConstructor = null; + this.playerInfoRemoveConstructor = null; + this.entityPositionSyncOf = null; + this.hurtAnimationConstructor = null; + this.setEquipmentConstructor = null; + this.pairOfMethod = null; + this.craftItemStackAsNmsCopy = null; + this.equipmentSlotClass = null; + this.avatarModelCustomizationAccessor = null; + this.serverPlayerConnectionField = null; + this.connectionSendPacket = null; + } + + public static PacketDecoyBridge create() { + try { + return new PacketDecoyBridge(); + } catch (Throwable e) { + Adapt.warn("Shadow decoy fake-player bridge unavailable: " + e.getClass().getSimpleName() + " " + e.getMessage()); + return new PacketDecoyBridge(false); + } + } + + private static byte toAngle(float degrees) { + return (byte) (degrees * 256.0F / 360.0F); + } + + private static Constructor findConstructor(Class type, Class... parameterTypes) throws NoSuchMethodException { + Constructor constructor = type.getConstructor(parameterTypes); + constructor.setAccessible(true); + return constructor; + } + + private static Constructor findOptionalConstructor(Class type, Class... parameterTypes) { + if (Arrays.stream(parameterTypes).anyMatch(c -> c == null)) { + return null; + } + + try { + Constructor constructor = type.getConstructor(parameterTypes); + constructor.setAccessible(true); + return constructor; + } catch (NoSuchMethodException e) { + return null; + } + } + + private static Method findMethod(Class type, String name, Class... parameterTypes) throws NoSuchMethodException { + try { + return type.getMethod(name, parameterTypes); + } catch (NoSuchMethodException e) { + Class current = type; + while (current != null) { + try { + Method method = current.getDeclaredMethod(name, parameterTypes); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException ignored) { + } + + current = current.getSuperclass(); + } + + throw e; + } + } + + private static Method findOptionalMethod(Class type, String name, Class... parameterTypes) { + try { + return findMethod(type, name, parameterTypes); + } catch (NoSuchMethodException e) { + return null; + } + } + + private static Method findOptionalMethod(Class type, String... methodNames) { + for (String methodName : methodNames) { + try { + Method method = type.getMethod(methodName); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException ignored) { + } + } + + return null; + } + + private static Class findPropertyMapClass(Class gameProfileClass) { + for (Constructor constructor : gameProfileClass.getConstructors()) { + Class[] params = constructor.getParameterTypes(); + if (params.length == 3 && params[0] == UUID.class && params[1] == String.class) { + return params[2]; + } + } + + return null; + } + + private static Object resolveDefaultGameType(Class gameTypeClass) throws ReflectiveOperationException { + try { + Field defaultMode = gameTypeClass.getField("DEFAULT_MODE"); + Object value = defaultMode.get(null); + if (value != null) { + return value; + } + } catch (NoSuchFieldException ignored) { + } + + if (gameTypeClass.isEnum()) { + try { + return Enum.valueOf((Class) gameTypeClass, "SURVIVAL"); + } catch (IllegalArgumentException ignored) { + } + + Object[] values = gameTypeClass.getEnumConstants(); + if (values != null && values.length > 0) { + return values[0]; + } + } + + throw new ReflectiveOperationException("No default game mode could be resolved."); + } + + private static Throwable unwrapRootCause(Throwable throwable) { + Throwable cursor = throwable; + while (true) { + if (cursor instanceof InvocationTargetException invocation && invocation.getCause() != null) { + cursor = invocation.getCause(); + continue; + } + + Throwable cause = cursor.getCause(); + if (cause == null || cause == cursor) { + return cursor; + } + + cursor = cause; + } + } + + public PacketPlayerDecoy spawnDecoy(Player owner, ArmorStand anchor, int tabListRemoveDelayTicks, int skinLayerMask) { + Location location = anchor.getLocation(); + if (!supported || location.getWorld() == null) { + return null; + } + + try { + Object ownerHandle = craftPlayerGetHandle.invoke(owner); + Object ownerProfile = playerGetGameProfile.invoke(ownerHandle); + UUID profileId = UUID.randomUUID(); + Object profile = createProfile(profileId, owner.getName(), ownerProfile); + + Object minecraftServer = craftServerGetServer.invoke(Bukkit.getServer()); + Object worldHandle = craftWorldGetHandle.invoke(location.getWorld()); + Object clientInfo = clientInformationCreateDefault.invoke(null); + Object nmsDecoy = serverPlayerConstructor.newInstance(minecraftServer, worldHandle, profile, clientInfo); + + entitySetPos.invoke(nmsDecoy, location.getX(), location.getY(), location.getZ()); + entitySetRot.invoke(nmsDecoy, location.getYaw(), location.getPitch()); + livingSetYHeadRot.invoke(nmsDecoy, location.getYaw()); + livingSetYBodyRot.invoke(nmsDecoy, location.getYaw()); + applySkinLayers(nmsDecoy, skinLayerMask); + + int entityId = (int) entityGetId.invoke(nmsDecoy); + Object playerInfoPacket = createPlayerInfoAddPacket(nmsDecoy, profileId, profile); + Object addEntityPacket = createAddEntityPacket(nmsDecoy, entityId, profileId, location); + Object metadataPacket = createMetadataPacket(nmsDecoy, entityId); + Object equipmentPacket = createEquipmentPacket(entityId, owner, false, true); + + PacketPlayerDecoy decoy = new PacketPlayerDecoy(this, location.getWorld(), profileId, entityId, nmsDecoy, tabListRemoveDelayTicks); + decoy.spawn(playerInfoPacket, addEntityPacket, metadataPacket, equipmentPacket); + return decoy; + } catch (Throwable e) { + Throwable root = unwrapRootCause(e); + Adapt.warn("Failed to spawn fake-player shadow decoy: " + root.getClass().getSimpleName() + " " + String.valueOf(root.getMessage())); + return null; + } + } + + private Object createProfile(UUID id, String ownerName, Object ownerProfile) throws ReflectiveOperationException { + String profileName = ownerName; + if (profileName.length() > 16) { + profileName = profileName.substring(0, 16); + } + + if (gameProfileWithPropertiesConstructor != null && gameProfilePropertiesAccessor != null && ownerProfile != null) { + Object properties = gameProfilePropertiesAccessor.invoke(ownerProfile); + if (properties != null) { + return gameProfileWithPropertiesConstructor.newInstance(id, profileName, properties); + } + } + + return gameProfileBasicConstructor.newInstance(id, profileName); + } + + private Object createPlayerInfoAddPacket(Object nmsDecoy, UUID profileId, Object profile) throws ReflectiveOperationException { + ReflectiveOperationException last = null; + + if (playerInfoFromEntriesConstructor != null && playerInfoEntryExplicitConstructor != null && playerInfoActionClass != null && defaultGameType != null) { + try { + Enum addAction = Enum.valueOf((Class) playerInfoActionClass, "ADD_PLAYER"); + EnumSet actions = buildInitializationActions(addAction); + Object entry = playerInfoEntryExplicitConstructor.newInstance(profileId, profile, true, 0, defaultGameType, null, true, 0, null); + return playerInfoFromEntriesConstructor.newInstance(actions, List.of(entry)); + } catch (ReflectiveOperationException e) { + last = e; + } + } + + if (playerInfoCreateSingleInitializing != null) { + try { + return playerInfoCreateSingleInitializing.invoke(null, nmsDecoy, true); + } catch (ReflectiveOperationException e) { + last = e; + } + } + + if (playerInfoActionConstructor != null && playerInfoActionClass != null) { + try { + Object addAction = Enum.valueOf((Class) playerInfoActionClass, "ADD_PLAYER"); + return playerInfoActionConstructor.newInstance(addAction, nmsDecoy); + } catch (ReflectiveOperationException e) { + last = e; + } + } + + if (last != null) { + throw last; + } + + throw new ReflectiveOperationException("No supported player-info add packet constructor found."); + } + + private Object createAddEntityPacket(Object nmsDecoy, int entityId, UUID profileId, Location location) throws ReflectiveOperationException { + if (addEntityExplicitConstructor != null && entityGetType != null && vec3Zero != null) { + Object entityType = entityGetType.invoke(nmsDecoy); + Object velocity = vec3Zero.get(null); + return addEntityExplicitConstructor.newInstance( + entityId, + profileId, + location.getX(), + location.getY(), + location.getZ(), + location.getPitch(), + location.getYaw(), + entityType, + 0, + velocity, + (double) location.getYaw() + ); + } + + return addEntityConstructor.newInstance(nmsDecoy, 0, blockPosZero.get(null)); + } + + private void applySkinLayers(Object nmsDecoy, int mask) throws ReflectiveOperationException { + if (avatarModelCustomizationAccessor == null || synchedEntityDataSet == null) { + return; + } + + Object accessor = avatarModelCustomizationAccessor.get(null); + Object synchedData = entityGetEntityData.invoke(nmsDecoy); + synchedEntityDataSet.invoke(synchedData, accessor, (byte) (mask & 0xFF)); + } + + private Object createEquipmentPacket(int entityId, Player owner, boolean hide, boolean includeEmptySlots) throws ReflectiveOperationException { + if (setEquipmentConstructor == null || pairOfMethod == null || craftItemStackAsNmsCopy == null || equipmentSlotClass == null) { + return null; + } + + List slots = new ArrayList<>(); + ItemStack air = new ItemStack(Material.AIR); + appendEquipment(slots, "HEAD", hide ? air : owner.getInventory().getHelmet(), includeEmptySlots); + appendEquipment(slots, "CHEST", hide ? air : owner.getInventory().getChestplate(), includeEmptySlots); + appendEquipment(slots, "LEGS", hide ? air : owner.getInventory().getLeggings(), includeEmptySlots); + appendEquipment(slots, "FEET", hide ? air : owner.getInventory().getBoots(), includeEmptySlots); + appendEquipment(slots, "MAINHAND", hide ? air : owner.getInventory().getItemInMainHand(), includeEmptySlots); + appendEquipment(slots, "OFFHAND", hide ? air : owner.getInventory().getItemInOffHand(), includeEmptySlots); + + if (slots.isEmpty()) { + return null; + } + + return setEquipmentConstructor.newInstance(entityId, slots); + } + + private void appendEquipment(List slots, String slotName, ItemStack stack, boolean includeEmptySlots) throws ReflectiveOperationException { + ItemStack resolved = stack == null ? new ItemStack(Material.AIR) : stack; + if (!includeEmptySlots && resolved.getType().isAir()) { + return; + } + + Object slot = Enum.valueOf((Class) equipmentSlotClass, slotName); + Object nmsStack = craftItemStackAsNmsCopy.invoke(null, resolved.clone()); + Object pair = pairOfMethod.invoke(null, slot, nmsStack); + slots.add(pair); + } + + private Object createMetadataPacket(Object nmsDecoy, int entityId) throws ReflectiveOperationException { + Object synchedData = entityGetEntityData.invoke(nmsDecoy); + Object packed = synchedEntityDataPackAll != null + ? synchedEntityDataPackAll.invoke(synchedData) + : synchedEntityDataGetNonDefaultValues.invoke(synchedData); + if (!(packed instanceof List values) || values.isEmpty()) { + return null; + } + + return setEntityDataConstructor.newInstance(entityId, values); + } + + public Object createPlayerInfoRemovePacket(UUID profileId) { + if (!supported) { + return null; + } + + try { + return playerInfoRemoveConstructor.newInstance(List.of(profileId)); + } catch (Throwable e) { + return null; + } + } + + public Object createRemoveEntityPacket(int entityId) { + if (!supported) { + return null; + } + + try { + return removeEntitiesConstructor.newInstance((Object) new int[]{entityId}); + } catch (Throwable e) { + return null; + } + } + + public boolean applyLook(PacketPlayerDecoy decoy, float yaw, float pitch, Player viewer) { + if (!supported) { + return false; + } + + try { + entitySetRot.invoke(decoy.nmsEntity, yaw, pitch); + livingSetYHeadRot.invoke(decoy.nmsEntity, yaw); + livingSetYBodyRot.invoke(decoy.nmsEntity, yaw); + + byte yawByte = toAngle(yaw); + byte pitchByte = toAngle(pitch); + Object rotatePacket = moveEntityRotConstructor.newInstance(decoy.entityId, yawByte, pitchByte, true); + Object headPacket = rotateHeadConstructor.newInstance(decoy.nmsEntity, yawByte); + + sendPacket(viewer, rotatePacket); + sendPacket(viewer, headPacket); + + return true; + } catch (Throwable e) { + return false; + } + } + + public boolean syncPosition(PacketPlayerDecoy decoy, Location location, boolean onGround, List viewers) { + if (!supported || entityPositionSyncOf == null) { + return false; + } + + try { + entitySetPos.invoke(decoy.nmsEntity, location.getX(), location.getY(), location.getZ()); + entitySetRot.invoke(decoy.nmsEntity, location.getYaw(), location.getPitch()); + if (entitySetOnGround != null) { + entitySetOnGround.invoke(decoy.nmsEntity, onGround); + } + + Object packet = entityPositionSyncOf.invoke(null, decoy.nmsEntity); + if (packet == null) { + return false; + } + + for (Player viewer : viewers) { + sendPacket(viewer, packet); + } + + return true; + } catch (Throwable e) { + return false; + } + } + + public void sendHurtAnimation(PacketPlayerDecoy decoy, float yaw, List viewers) { + if (!supported || hurtAnimationConstructor == null) { + return; + } + + try { + Object packet = hurtAnimationConstructor.newInstance(decoy.entityId, yaw); + for (Player viewer : viewers) { + sendPacket(viewer, packet); + } + } catch (Throwable ignored) { + } + } + + public void sendOwnerEquipment(Player owner, boolean hide) { + if (!supported || owner == null || !owner.isOnline()) { + return; + } + + try { + Object packet = createEquipmentPacket(owner.getEntityId(), owner, hide, true); + if (packet == null) { + return; + } + + for (Player viewer : new ArrayList<>(owner.getWorld().getPlayers())) { + if (viewer.getUniqueId().equals(owner.getUniqueId())) { + continue; + } + sendPacket(viewer, packet); + } + } catch (Throwable ignored) { + } + } + + public void sendPacket(Player viewer, Object packet) { + if (!supported || packet == null || viewer == null || !viewer.isOnline()) { + return; + } + + try { + Object handle = craftPlayerGetHandle.invoke(viewer); + Object connection = serverPlayerConnectionField.get(handle); + if (connection == null) { + return; + } + + connectionSendPacket.invoke(connection, packet); + } catch (Throwable ignored) { + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private EnumSet buildInitializationActions(Enum addAction) { + EnumSet actions = EnumSet.noneOf((Class) playerInfoActionClass); + actions.add(addAction); + + String[] optionalActions = new String[]{ + "INITIALIZE_CHAT", + "UPDATE_GAME_MODE", + "UPDATE_LISTED", + "UPDATE_LATENCY", + "UPDATE_DISPLAY_NAME", + "UPDATE_HAT", + "UPDATE_LIST_ORDER" + }; + + for (String name : optionalActions) { + try { + actions.add(Enum.valueOf((Class) playerInfoActionClass, name)); + } catch (IllegalArgumentException ignored) { + } + } + + return actions; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSight.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSight.java new file mode 100644 index 000000000..e46b5ef39 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSight.java @@ -0,0 +1,182 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.stealth; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class StealthSight extends SimpleAdaptation { + private final Set sneaking; + private final Set appliedNightVision; + + + public StealthSight() { + super("stealth-vision"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("stealth.night_vision.description")); + setDisplayName(Localizer.dLocalize("stealth.night_vision.name")); + setIcon(Material.POTION); + setBaseCost(getConfig().baseCost); + setInterval(1500); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setMaxLevel(getConfig().maxLevel); + sneaking = ConcurrentHashMap.newKeySet(); + appliedNightVision = ConcurrentHashMap.newKeySet(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_stealth_sight_72k") + .title(Localizer.dLocalize("advancement.challenge_stealth_sight_72k.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_sight_72k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_stealth_sight_72k", "stealth.sight.time-in-darkness", 72000, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GRAY + Localizer.dLocalize("stealth.night_vision.lore1") + C.GREEN + Localizer.dLocalize("stealth.night_vision.lore2") + C.GRAY + Localizer.dLocalize("stealth.night_vision.lore3")); + } + + @EventHandler + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + withPlayerThread(p, e, () -> { + UUID id = p.getUniqueId(); + SoundPlayer sp = SoundPlayer.of(p); + if (!hasActiveAdaptation(p)) { + sneaking.remove(id); + clearNightVisionIfApplied(p, id); + return; + } + + if (e.isSneaking()) { + sneaking.add(id); + sp.play(p.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 1, 0.99f); + applyNightVisionIfNeeded(p, id); + getPlayer(p).getData().addStat("stealth.sight.time-in-darkness", 1); + return; + } + + sneaking.remove(id); + clearNightVisionIfApplied(p, id); + }); + } + + + @Override + public void onTick() { + Set snapshot = new HashSet<>(sneaking); + for (UUID id : snapshot) { + Player p = Bukkit.getPlayer(id); + if (p == null || !p.isOnline()) { + sneaking.remove(id); + appliedNightVision.remove(id); + continue; + } + + Runnable check = () -> { + if (getActiveLevel(p, Player::isSneaking) <= 0) { + sneaking.remove(id); + J.runEntity(p, () -> clearNightVisionIfApplied(p, id)); + } + }; + + if (J.isFoliaThreading() && !J.isOwnedByCurrentRegion(p)) { + J.runEntity(p, check); + } else { + check.run(); + } + } + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private void applyNightVisionIfNeeded(Player player, UUID id) { + if (player.hasPotionEffect(PotionEffectType.NIGHT_VISION)) { + appliedNightVision.remove(id); + return; + } + + PotionEffect effect = new PotionEffect(PotionEffectType.NIGHT_VISION, 1000, 0, false, false); + boolean applied = player.addPotionEffect(effect); + if (applied) { + appliedNightVision.add(id); + } else { + appliedNightVision.remove(id); + } + } + + private void clearNightVisionIfApplied(Player player, UUID id) { + if (!appliedNightVision.remove(id)) { + return; + } + + player.removePotionEffect(PotionEffectType.NIGHT_VISION); + } + + @NoArgsConstructor + @ConfigDescription("Gain night vision while sneaking.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSilentStep.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSilentStep.java new file mode 100644 index 000000000..a04642829 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSilentStep.java @@ -0,0 +1,564 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.stealth; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import fr.skytasul.glowingentities.GlowingEntities; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityTargetLivingEntityEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.*; + +public class StealthSilentStep extends SimpleAdaptation { + private final Map dimmed = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map> recentBackstabs = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map> threatGlows = new java.util.concurrent.ConcurrentHashMap<>(); + private final Set activeSneakers = java.util.concurrent.ConcurrentHashMap.newKeySet(); + private final Map lastTargetDropScan = new java.util.concurrent.ConcurrentHashMap<>(); + + public StealthSilentStep() { + super("stealth-silent-step"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("stealth.silent_step.description")); + setDisplayName(Localizer.dLocalize("stealth.silent_step.name")); + setIcon(Material.WHITE_WOOL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(50); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_stealth_silent_200") + .title(Localizer.dLocalize("advancement.challenge_stealth_silent_200.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_silent_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_stealth_silent_5in10") + .title(Localizer.dLocalize("advancement.challenge_stealth_silent_5in10.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_silent_5in10.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_stealth_silent_200", "stealth.silent-step.backstabs", 200, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getStealthRadius(level)) + C.GRAY + " " + Localizer.dLocalize("stealth.silent_step.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getMobBackstabMultiplier(level) - 1D, 0) + C.GRAY + " " + Localizer.dLocalize("stealth.silent_step.lore2")); + v.addLore(C.GREEN + "+ " + Form.pc(getPlayerBackstabMultiplier(level) - 1D, 0) + C.GRAY + " " + Localizer.dLocalize("stealth.silent_step.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + Player player = e.getPlayer(); + UUID id = player.getUniqueId(); + clearDimming(player); + clearThreatGlows(player); + recentBackstabs.remove(id); + activeSneakers.remove(id); + lastTargetDropScan.remove(id); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + if (e.isSneaking()) { + activeSneakers.add(id); + return; + } + + activeSneakers.remove(id); + lastTargetDropScan.remove(id); + clearDimming(p); + clearThreatGlows(p); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityTargetLivingEntityEvent e) { + if (!(e.getTarget() instanceof Player p)) { + return; + } + + if (getActiveLevel(p, Player::isSneaking) <= 0) { + return; + } + + if (isTargetBlacklistType(e.getEntity().getType())) { + return; + } + + e.setCancelled(true); + if (e.getEntity() instanceof Mob mob && mob.getTarget() == p) { + mob.setTarget(null); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerMoveEvent e) { + if (e.getTo() == null) { + return; + } + + if (e.getFrom().getWorld() == e.getTo().getWorld() + && e.getFrom().distanceSquared(e.getTo()) < Math.max(0D, getConfig().minimumMoveSquared)) { + return; + } + Player p = e.getPlayer(); + UUID id = p.getUniqueId(); + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0) { + activeSneakers.remove(id); + lastTargetDropScan.remove(id); + return; + } + + activeSneakers.add(id); + long now = System.currentTimeMillis(); + long lastScan = lastTargetDropScan.getOrDefault(id, 0L); + if (now - lastScan < Math.max(20L, getConfig().targetDropScanIntervalMillis)) { + return; + } + lastTargetDropScan.put(id, now); + + double radius = getStealthRadius(level); + for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), radius, radius, radius)) { + if (!(entity instanceof Mob mob)) { + continue; + } + + if (isTargetBlacklistType(mob.getType())) { + continue; + } + + if (mob.getTarget() == p) { + mob.setTarget(null); + xp(p, getConfig().xpPerTargetDrop); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.MeleeContext combat = resolveMeleeContext(e); + if (combat == null) { + return; + } + + Player attacker = combat.attacker(); + LivingEntity target = combat.target(); + boolean unseen = attacker.hasPotionEffect(PotionEffectType.INVISIBILITY) || !isLookingAt(target, attacker); + if (target == attacker || !unseen) { + return; + } + + int level = combat.level(); + double multiplier = (target instanceof Player) ? getPlayerBackstabMultiplier(level) : getMobBackstabMultiplier(level); + e.setDamage(e.getDamage() * multiplier); + xp(attacker, e.getDamage() * getConfig().xpPerBonusDamage); + getPlayer(attacker).getData().addStat("stealth.silent-step.backstabs", 1); + + long now = System.currentTimeMillis(); + UUID uid = attacker.getUniqueId(); + recentBackstabs.computeIfAbsent(uid, k -> new ArrayList<>()).add(now); + recentBackstabs.get(uid).removeIf(t -> now - t > 10000); + if (recentBackstabs.get(uid).size() >= 5 + && AdaptConfig.get().isAdvancements() + && !getPlayer(attacker).getData().isGranted("challenge_stealth_silent_5in10")) { + getPlayer(attacker).getAdvancementHandler().grant("challenge_stealth_silent_5in10"); + } + } + + @Override + public void onTick() { + Set tracked = new HashSet<>(activeSneakers); + for (UUID id : tracked) { + Player p = Bukkit.getPlayer(id); + if (p == null || !p.isOnline()) { + activeSneakers.remove(id); + lastTargetDropScan.remove(id); + continue; + } + + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0) { + clearDimming(p); + clearThreatGlows(p); + activeSneakers.remove(id); + lastTargetDropScan.remove(id); + continue; + } + + p.setFallDistance(Math.min(p.getFallDistance(), getConfig().maxSilentFallDistance)); + ThreatSnapshot threatSnapshot = collectThreatSnapshot(p, level); + if (threatSnapshot.canDetect.isEmpty()) { + applyDimming(p, level); + } else { + clearDimming(p); + } + updateThreatGlows(p, threatSnapshot); + } + } + + private ThreatSnapshot collectThreatSnapshot(Player p, int level) { + ThreatSnapshot snapshot = new ThreatSnapshot(); + double detectionLookDotThreshold = getDetectionLookDotThreshold(); + double mobRadius = getStealthRadius(level); + for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), mobRadius, mobRadius, mobRadius)) { + if (!(entity instanceof Mob mob)) { + continue; + } + + if (!getConfig().allMobsAffectStealthVisibility && !isTargetBlacklistType(mob.getType())) { + continue; + } + + snapshot.add(mob, getThreatLevel(mob, p, detectionLookDotThreshold)); + } + + double playerRadius = getPlayerDetectionRadius(level); + for (Entity nearby : p.getWorld().getNearbyEntities(p.getLocation(), playerRadius, playerRadius, playerRadius)) { + if (!(nearby instanceof Player other)) { + continue; + } + if (other == p || other.isDead()) { + continue; + } + + snapshot.add(other, getThreatLevel(other, p, detectionLookDotThreshold)); + } + + return snapshot; + } + + private void applyDimming(Player p, int level) { + p.addPotionEffect(new PotionEffect(PotionEffectType.DARKNESS, getDimDurationTicks(level), getConfig().dimAmplifier, false, false, false), true); + dimmed.put(p.getUniqueId(), true); + } + + private void clearDimming(Player p) { + if (dimmed.remove(p.getUniqueId()) != null) { + p.removePotionEffect(PotionEffectType.DARKNESS); + } + } + + private void updateThreatGlows(Player p, ThreatSnapshot snapshot) { + if (!getConfig().showThreatGlows) { + clearThreatGlows(p); + return; + } + + GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); + if (glowingEntities == null) { + clearThreatGlows(p); + return; + } + + UUID viewerId = p.getUniqueId(); + Map active = threatGlows.computeIfAbsent(viewerId, k -> new java.util.concurrent.ConcurrentHashMap<>()); + + List stale = new ArrayList<>(); + for (UUID entityId : active.keySet()) { + if (!snapshot.threats.containsKey(entityId)) { + stale.add(entityId); + } + } + + for (UUID entityId : stale) { + Entity entity = Bukkit.getEntity(entityId); + if (entity != null) { + try { + glowingEntities.unsetGlowing(entity, p); + } catch (ReflectiveOperationException ignored) { + // Ignore reflective failures and continue clearing other entities. + } + } + active.remove(entityId); + } + + for (Map.Entry entry : snapshot.threats.entrySet()) { + UUID entityId = entry.getKey(); + ThreatLevel desired = entry.getValue(); + ThreatLevel current = active.get(entityId); + if (desired == current) { + continue; + } + + Entity entity = snapshot.entities.get(entityId); + if (entity == null) { + entity = Bukkit.getEntity(entityId); + } + if (entity == null || !entity.isValid()) { + continue; + } + + try { + glowingEntities.setGlowing(entity, p, getThreatColor(desired)); + active.put(entityId, desired); + } catch (ReflectiveOperationException ignored) { + // Ignore reflective failures and keep runtime behavior intact. + } + } + + if (active.isEmpty()) { + threatGlows.remove(viewerId); + } + } + + private void clearThreatGlows(Player p) { + Map active = threatGlows.remove(p.getUniqueId()); + if (active == null || active.isEmpty()) { + return; + } + + GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); + if (glowingEntities == null) { + return; + } + + for (UUID entityId : active.keySet()) { + Entity entity = Bukkit.getEntity(entityId); + if (entity == null) { + continue; + } + + try { + glowingEntities.unsetGlowing(entity, p); + } catch (ReflectiveOperationException ignored) { + // Ignore reflective failures and continue clearing other entities. + } + } + } + + private ThreatLevel getThreatLevel(LivingEntity observer, LivingEntity target, double detectThreshold) { + if (!observer.hasLineOfSight(target)) { + return ThreatLevel.NONE; + } + + double lookDot = getLookDot(observer, target); + if (lookDot >= detectThreshold) { + return ThreatLevel.CAN_DETECT; + } + + double almostThreshold = Math.max(-1, detectThreshold - Math.max(0, getConfig().almostLookDotMargin)); + if (lookDot >= almostThreshold) { + return ThreatLevel.ALMOST_DETECT; + } + + return ThreatLevel.NONE; + } + + private double getDetectionLookDotThreshold() { + return Math.max(-1, Math.min(1, getConfig().detectionLookDotThreshold)); + } + + private ChatColor getThreatColor(ThreatLevel level) { + return switch (level) { + case CAN_DETECT -> ChatColor.RED; + case ALMOST_DETECT -> ChatColor.GRAY; + default -> ChatColor.WHITE; + }; + } + + private boolean isLookingAt(LivingEntity observer, LivingEntity target) { + return getLookDot(observer, target) >= getConfig().lookDotThreshold; + } + + private double getLookDot(LivingEntity observer, LivingEntity target) { + Vector look = observer.getEyeLocation().getDirection().normalize(); + Vector toTarget = target.getEyeLocation().toVector().subtract(observer.getEyeLocation().toVector()); + if (toTarget.lengthSquared() <= 0.0001) { + return 1; + } + + toTarget.normalize(); + return look.dot(toTarget); + } + + private boolean isTargetBlacklistType(EntityType type) { + if (type == null) { + return false; + } + + for (String raw : getConfig().targetingBlacklistTypes) { + if (raw == null || raw.isBlank()) { + continue; + } + + try { + EntityType configured = EntityType.valueOf(raw.trim().toUpperCase(Locale.ROOT)); + if (configured == type) { + return true; + } + } catch (IllegalArgumentException ignored) { + // Ignore invalid enum names in config. + } + } + + return false; + } + + private double getStealthRadius(int level) { + return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); + } + + private double getPlayerDetectionRadius(int level) { + return getConfig().playerDetectionRadiusBase + (getLevelPercent(level) * getConfig().playerDetectionRadiusFactor); + } + + private int getDimDurationTicks(int level) { + return Math.max(10, (int) Math.round(getConfig().dimDurationTicksBase + (getLevelPercent(level) * getConfig().dimDurationTicksFactor))); + } + + private double getMobBackstabMultiplier(int level) { + return getConfig().mobBackstabBase + (getLevelPercent(level) * getConfig().mobBackstabFactor); + } + + private double getPlayerBackstabMultiplier(int level) { + return getConfig().playerBackstabBase + (getLevelPercent(level) * getConfig().playerBackstabFactor); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private enum ThreatLevel { + NONE, + ALMOST_DETECT, + CAN_DETECT + } + + @NoArgsConstructor + @ConfigDescription("Sneaking prevents hostile mob detection, and unseen hits deal backstab damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Player Detection Radius Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double playerDetectionRadiusBase = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Player Detection Radius Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double playerDetectionRadiusFactor = 14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Dim Duration Ticks Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dimDurationTicksBase = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Dim Duration Ticks Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double dimDurationTicksFactor = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Dim Amplifier for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int dimAmplifier = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mob Backstab Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double mobBackstabBase = 1.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mob Backstab Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double mobBackstabFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Player Backstab Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double playerBackstabBase = 1.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Player Backstab Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double playerBackstabFactor = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Look Dot Threshold for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lookDotThreshold = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Target Drop for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerTargetDrop = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Bonus Damage for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBonusDamage = 3.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Silent Fall Distance for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + float maxSilentFallDistance = 1.6f; + @art.arcane.adapt.util.config.ConfigDoc(value = "Shows nearby threats with per-player glowing while sneaking (red = can detect, gray = almost).", impact = "Enable to get visual awareness of entities that can or almost can spot you.") + boolean showThreatGlows = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Look-dot margin below the full detection threshold used for gray 'almost detect' glow.", impact = "Higher values make gray warnings appear earlier; lower values make warnings stricter.") + double almostLookDotMargin = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Look-dot threshold for stealth visibility checks while sneaking.", impact = "Lower values make crossing an entity's view count as seen more easily; higher values require a more direct look.") + double detectionLookDotThreshold = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "If true, all nearby mobs (including passive) can break hidden state when they have line-of-sight.", impact = "Enable to prevent stealth from feeling hidden in front of passive mobs like pigs.") + boolean allMobsAffectStealthVisibility = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Entity types that are NOT ignored by stealth targeting suppression.", impact = "Mobs listed here can still detect/target sneaking players with Silent Step.") + List targetingBlacklistTypes = new ArrayList<>(List.of("WARDEN", "WITHER", "PHANTOM", "ENDER_DRAGON")); + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum squared movement distance required before running target-drop scans.", impact = "Higher values skip tiny movement jitter and reduce move-event scan pressure.") + double minimumMoveSquared = 0.0025; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds between mob target-drop scans while sneaking.", impact = "Lower values react faster but increase nearby-entity scan frequency.") + long targetDropScanIntervalMillis = 120; + } + + private static class ThreatSnapshot { + private final Map threats = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map entities = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map canDetect = new java.util.concurrent.ConcurrentHashMap<>(); + + private void add(Entity entity, ThreatLevel level) { + if (entity == null || level == ThreatLevel.NONE) { + return; + } + + UUID id = entity.getUniqueId(); + entities.put(id, entity); + ThreatLevel existing = threats.get(id); + if (existing == null || level.ordinal() > existing.ordinal()) { + threats.put(id, level); + } + + if (level == ThreatLevel.CAN_DETECT) { + canDetect.put(id, level); + } + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSnatch.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSnatch.java new file mode 100644 index 000000000..137e45c3f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSnatch.java @@ -0,0 +1,208 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.stealth; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.inventorygui.Inventories; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class StealthSnatch extends SimpleAdaptation { + private final Set holds; + + public StealthSnatch() { + super("stealth-snatch"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("stealth.snatch.description")); + setDisplayName(Localizer.dLocalize("stealth.snatch.name")); + setIcon(Material.CHEST_MINECART); + setBaseCost(getConfig().baseCost); + setInterval(getConfig().snatchRate); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + holds = ConcurrentHashMap.newKeySet(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_stealth_snatch_2500") + .title(Localizer.dLocalize("advancement.challenge_stealth_snatch_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_snatch_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.HOPPER) + .key("challenge_stealth_snatch_25k") + .title(Localizer.dLocalize("advancement.challenge_stealth_snatch_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_snatch_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_stealth_snatch_2500", "stealth.snatch.items-snatched", 2500, 400); + registerMilestone("challenge_stealth_snatch_25k", "stealth.snatch.items-snatched", 25000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRange(getLevelPercent(level)), 1) + C.GRAY + " " + Localizer.dLocalize("stealth.snatch.lore1")); + } + + @EventHandler + public void on(PlayerToggleSneakEvent e) { + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p)) { + return; + } + if (e.isSneaking()) { + snatch(p); + } + } + + private void snatch(Player player) { + double factor = getLevelPercent(player); + + if (factor == 0) { + return; + } + + if (!canAccessChest(player, player.getLocation())) { + return; + } + + double range = getRange(factor); + HashSet items = new HashSet<>(); + for (Entity droppedItemEntity : player.getWorld().getNearbyEntities(player.getLocation(), range, range / 1.5, range)) { + if (droppedItemEntity instanceof Item droppedItem && canSnatchItem(player, droppedItem)) { + items.add(droppedItem); + } + } + + for (Item droppedItemEntity : items) { + if (holds.contains(droppedItemEntity.getEntityId())) { + continue; + } + + double dist = droppedItemEntity.getLocation().distanceSquared(player.getLocation()); + if (dist >= range * range) { + continue; + } + + ItemStack is = droppedItemEntity.getItemStack().clone(); + if (!Inventories.hasSpace(player.getInventory(), is)) { + continue; + } + + holds.add(droppedItemEntity.getEntityId()); + int id = droppedItemEntity.getEntityId(); + if (safeGiveItem(player, droppedItemEntity, is)) { + SoundPlayer spw = SoundPlayer.of(player.getWorld()); + spw.play(player.getLocation(), Sound.BLOCK_LAVA_POP, 1f, (float) (1.0 + (ThreadLocalRandom.current().nextDouble() / 3D))); + getPlayer(player).getData().addStat("stealth.snatch.items-snatched", 1); + } + J.runEntity(player, () -> holds.remove(Integer.valueOf(id)), 1); + } + + } + + private double getRange(double factor) { + return (factor * getConfig().radiusFactor) + 1; + } + + /* + public void sendCollected(Player p, Item item) { + try { + PacketPlayOutCollect packet = new PacketPlayOutCollect(item.getEntityId(), p.getEntityId(), item.getItemStack().getAmount()); + for (Entity i : p.getWorld().getNearbyEntities(p.getLocation(), 8, 8, 8, entity -> entity instanceof Player)) { + ((CraftPlayer) i).getHandle().c.a(packet); + } + } catch (Exception e) { + Adapt.error("Failed to send collected packet"); + e.printStackTrace(); + } + }*/ + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player i = adaptPlayer.getPlayer(); + if (i.isSneaking()) { + J.runEntity(i, () -> snatch(i)); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + protected void onConfigReload(Config previousConfig, Config newConfig) { + super.onConfigReload(previousConfig, newConfig); + setInterval(newConfig.snatchRate); + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Snatch dropped items instantly while sneaking.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Snatch Rate for the Stealth Snatch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int snatchRate = 250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Stealth Snatch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 5.55; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSpeed.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSpeed.java new file mode 100644 index 000000000..4fcd8ef32 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/StealthSpeed.java @@ -0,0 +1,474 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.stealth; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.math.VelocitySpeed; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class StealthSpeed extends SimpleAdaptation { + private static final Sound DEFAULT_ACTIVATION_SOUND = Sound.PARTICLE_SOUL_ESCAPE; + private final Map states; + + public StealthSpeed() { + super("stealth-speed"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("stealth.speed.description")); + setDisplayName(Localizer.dLocalize("stealth.speed.name")); + setIcon(Material.MUSHROOM_STEW); + setBaseCost(getConfig().baseCost); + setInterval(getConfig().setInterval); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + states = new java.util.concurrent.ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEATHER_BOOTS) + .key("challenge_stealth_speed_5k") + .title(Localizer.dLocalize("advancement.challenge_stealth_speed_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_speed_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_stealth_speed_5k", "stealth.speed.blocks-sneak-sprinted", 5000, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getSpeed(getLevelPercent(level)), 0) + C.GRAY + Localizer.dLocalize("stealth.speed.lore1")); + } + + @EventHandler + public void on(PlayerQuitEvent e) { + clearAndRemoveState(e.getPlayer()); + } + + @EventHandler + public void on(PlayerDeathEvent e) { + clearAndRemoveState(e.getEntity()); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + long statIntervalMs = Math.max(50L, getConfig().statIntervalMs); + + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + RuntimeState state = states.computeIfAbsent(p.getUniqueId(), key -> new RuntimeState()); + + if (!isEligible(p)) { + clearBoost(p, state); + continue; + } + + double levelFactor = getLevelPercent(p); + if (levelFactor <= 0) { + clearBoost(p, state); + continue; + } + + boolean crawling = isCrawlingOnLand(p); + float targetWalkSpeed = computeTargetWalkSpeed(state, p, levelFactor, crawling); + applyBoost(p, state, targetWalkSpeed, now); + applyAutoStep(p, state, now); + + if (!isMovingHorizontally(p, getConfig().movementVelocityThreshold)) { + continue; + } + + if (getConfig().showSoulParticles && M.r(getConfig().soulParticleChance)) { + p.spawnParticle(Particle.SOUL, p.getLocation().clone().add(0, getConfig().soulParticleYOffset, 0), 1, 0.14, 0.02, 0.14, 0); + } + + if (now - state.lastStatMillis >= statIntervalMs) { + getPlayer(p).getData().addStat("stealth.speed.blocks-sneak-sprinted", 1); + state.lastStatMillis = now; + } + } + } + + private void applyBoost(Player p, RuntimeState state, float targetWalkSpeed, long now) { + if (!state.boosting) { + state.boosting = true; + state.originalWalkSpeed = p.getWalkSpeed(); + + long cooldown = Math.max(0, getConfig().activationSoundCooldownMs); + if (cooldown <= 0 || now - state.lastSoundMillis >= cooldown) { + p.playSound(p.getLocation(), DEFAULT_ACTIVATION_SOUND, getConfig().activationSoundVolume, getConfig().activationSoundPitch); + state.lastSoundMillis = now; + } + } + + float current = p.getWalkSpeed(); + if (Math.abs(current - targetWalkSpeed) > 0.0001f) { + p.setWalkSpeed(targetWalkSpeed); + } + } + + private void clearBoost(Player p, RuntimeState state) { + if (!state.boosting) { + return; + } + + state.boosting = false; + float restore = clampWalkSpeed(state.originalWalkSpeed); + float current = p.getWalkSpeed(); + if (Math.abs(current - restore) > 0.0001f) { + p.setWalkSpeed(restore); + } + } + + private void clearAndRemoveState(Player p) { + if (p == null) { + return; + } + + RuntimeState state = states.remove(p.getUniqueId()); + if (state == null) { + return; + } + + clearBoost(p, state); + } + + private boolean isEligible(Player p) { + if (!hasActiveAdaptation(p)) { + return false; + } + + boolean crawlingOnLand = isCrawlingOnLand(p); + if (!p.isSneaking() && !crawlingOnLand) { + return false; + } + + GameMode mode = p.getGameMode(); + if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { + return false; + } + + if (p.isDead() || p.getVehicle() != null || p.isFlying() || p.isGliding()) { + return false; + } + + if ((p.isSwimming() || p.isInWater()) && !crawlingOnLand && !getConfig().allowWhileInWater) { + return false; + } + + return !getConfig().requireGrounded || p.isOnGround(); + } + + private boolean isCrawlingOnLand(Player p) { + if (p.getBoundingBox().getHeight() > getConfig().crawlHeightMax) { + return false; + } + + return !p.getEyeLocation().getBlock().isLiquid() && !p.getLocation().getBlock().isLiquid(); + } + + private float computeTargetWalkSpeed(RuntimeState state, Player p, double levelFactor, boolean crawling) { + float base = state.boosting ? state.originalWalkSpeed : clampWalkSpeed(p.getWalkSpeed()); + if (!state.boosting && Math.abs(base) < 0.0001f) { + base = clampWalkSpeed(getConfig().baselineWalkSpeed); + } + + double bonus = getSpeed(levelFactor); + if (crawling) { + bonus *= Math.max(0, getConfig().crawlBonusMultiplier); + } + + return clampWalkSpeed(base + (float) bonus); + } + + private void applyAutoStep(Player p, RuntimeState state, long now) { + if (!getConfig().enableAutoStep || !p.isOnGround()) { + return; + } + + long cooldown = Math.max(0, getConfig().autoStepCooldownMs); + if (cooldown > 0 && now - state.lastStepMillis < cooldown) { + return; + } + + Vector direction = resolveAutoStepDirection(p); + if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { + return; + } + + double probe = Math.max(0.1, getConfig().autoStepProbeDistance); + org.bukkit.Location feet = p.getLocation(); + org.bukkit.Location front = feet.clone().add(direction.multiply(probe)); + + if (getConfig().enableAutoStepUp && tryStepUp(p, front, direction)) { + state.lastStepMillis = now; + return; + } + + if (getConfig().enableAutoStepDown && tryStepDown(p, front, direction)) { + state.lastStepMillis = now; + } + } + + private Vector resolveAutoStepDirection(Player p) { + if (getConfig().autoStepUseInput) { + try { + Input input = p.getCurrentInput(); + if (input != null) { + VelocitySpeed.InputSnapshot snapshot = new VelocitySpeed.InputSnapshot(input.isForward(), input.isBackward(), input.isLeft(), input.isRight()); + if (snapshot.hasHorizontal()) { + Vector inputDirection = VelocitySpeed.resolveHorizontalDirection(p, snapshot); + if (inputDirection.lengthSquared() > VelocitySpeed.EPSILON) { + return inputDirection; + } + } + } + } catch (NoSuchMethodError ex) { + // Runtime does not expose input API. Use velocity-based fallback. + Adapt.verbose("Player input API is unavailable on this runtime; using velocity fallback for stealth auto-step."); + } + } + + Vector movement = new Vector(p.getVelocity().getX(), 0, p.getVelocity().getZ()); + double velocityThreshold = Math.max(0, getConfig().autoStepVelocityThreshold); + if (movement.lengthSquared() <= velocityThreshold * velocityThreshold) { + return new Vector(); + } + + return movement.normalize(); + } + + private boolean tryStepUp(Player p, org.bukkit.Location front, Vector direction) { + if (!isStepObstacle(front, 0)) { + return false; + } + + if (!hasStepHeadroom(p, front)) { + return false; + } + + org.bukkit.Location destination = p.getLocation().clone() + .add(direction.clone().multiply(Math.max(0.05, getConfig().autoStepForwardPush))) + .add(0, 1, 0); + if (!isDestinationSafe(p, destination, true)) { + return false; + } + + J.teleport(p, destination); + return true; + } + + private boolean tryStepDown(Player p, org.bukkit.Location front, Vector direction) { + if (!isPassable(front, 0)) { + return false; + } + + if (!isPassable(front, -1)) { + return false; + } + + if (!isSolid(front, -2)) { + return false; + } + + org.bukkit.Location destination = p.getLocation().clone() + .add(direction.clone().multiply(Math.max(0.05, getConfig().autoStepForwardPush))) + .add(0, -1, 0); + if (!isDestinationSafe(p, destination, true)) { + return false; + } + + J.teleport(p, destination); + p.setFallDistance(0); + return true; + } + + private boolean isStepObstacle(org.bukkit.Location base, int yOffset) { + org.bukkit.block.Block block = base.clone().add(0, yOffset, 0).getBlock(); + if (block.isLiquid() || block.isPassable() || !block.getType().isSolid()) { + return false; + } + + double obstacleHeight = block.getBoundingBox().getHeight(); + return obstacleHeight >= Math.max(0.05, getConfig().stepObstacleMinHeight); + } + + private boolean isSolid(org.bukkit.Location base, int yOffset) { + org.bukkit.block.Block block = base.clone().add(0, yOffset, 0).getBlock(); + return block.getType().isSolid() && !block.isLiquid() && !block.isPassable(); + } + + private boolean isPassable(org.bukkit.Location base, int yOffset) { + org.bukkit.block.Block block = base.clone().add(0, yOffset, 0).getBlock(); + return block.isPassable() && !block.isLiquid(); + } + + private boolean hasStepHeadroom(Player p, org.bukkit.Location front) { + if (!isPassable(front, 1)) { + return false; + } + + if (requiresDoubleHeadroom(p) && !isPassable(front, 2)) { + return false; + } + + return true; + } + + private boolean isDestinationSafe(Player p, org.bukkit.Location destination, boolean requireFloor) { + if (!isPassable(destination, 0)) { + return false; + } + + if (requiresDoubleHeadroom(p) && !isPassable(destination, 1)) { + return false; + } + + if (requireFloor && !isSolid(destination, -1)) { + return false; + } + + return true; + } + + private boolean requiresDoubleHeadroom(Player p) { + return p.getBoundingBox().getHeight() >= Math.max(0.5, getConfig().doubleHeadroomHeightThreshold); + } + + private boolean isMovingHorizontally(Player p, double threshold) { + Vector horizontal = new Vector(p.getVelocity().getX(), 0, p.getVelocity().getZ()); + double t = Math.max(0, threshold); + return horizontal.lengthSquared() > t * t; + } + + private float clampWalkSpeed(float value) { + float min = Math.max(-1f, getConfig().minWalkSpeed); + float max = Math.min(1f, Math.max(min, getConfig().maxWalkSpeed)); + return Math.max(min, Math.min(max, value)); + } + + private double getSpeed(double factor) { + return Math.max(0, factor * getConfig().maxSpeedBonus); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private static class RuntimeState { + private boolean boosting; + private float originalWalkSpeed = 0.2f; + private long lastSoundMillis; + private long lastStatMillis; + private long lastStepMillis; + } + + @NoArgsConstructor + @ConfigDescription("Gain speed while sneaking.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Tick interval (ms) used to update stealth speed.", impact = "Lower values feel more responsive but run updates more frequently.") + long setInterval = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fallback baseline walk speed if no original speed has been captured yet.", impact = "Usually keep this at vanilla default unless another plugin changes baseline speeds globally.") + float baselineWalkSpeed = 0.2f; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum additional walk speed granted at max level.", impact = "Higher values make stealth speed more noticeable.") + double maxSpeedBonus = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Multiplier applied to bonus speed while crawling on land.", impact = "Higher values make crawling keep pace with sneaking.") + double crawlBonusMultiplier = 1.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum walk speed clamp used when applying the boost.", impact = "Keep near default to avoid unexpected slowdowns from conflicting systems.") + float minWalkSpeed = -1f; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum walk speed clamp used when applying the boost.", impact = "Lower values are safer for anticheat; higher values feel faster.") + float maxWalkSpeed = 1f; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables automatic vertical stepping while stealth speed is active.", impact = "Helps smooth sneaking over one-block terrain changes.") + boolean enableAutoStep = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows stepping up one block while moving.", impact = "Reduces sneak interruption when encountering small ledges.") + boolean enableAutoStepUp = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows stepping down one block while moving.", impact = "Only steps down when the drop is exactly one block.") + boolean enableAutoStepDown = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Forward probe distance for auto-step checks.", impact = "Higher values detect ledges earlier but can feel more aggressive.") + double autoStepProbeDistance = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Horizontal push applied during each auto-step teleport.", impact = "Higher values move farther onto/off the next block and reduce repeat stepping in place.") + double autoStepForwardPush = 0.36; + @art.arcane.adapt.util.config.ConfigDoc(value = "Uses direct movement input for auto-step direction when available.", impact = "Helps auto-step trigger while pressing into obstacles, even when velocity is near zero.") + boolean autoStepUseInput = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum horizontal velocity required before auto-step runs.", impact = "Higher values avoid accidental stepping while nearly idle.") + double autoStepVelocityThreshold = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum delay between auto-step teleports.", impact = "Higher values reduce repeated stepping in tight terrain.") + long autoStepCooldownMs = 90; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum obstacle collision height that counts as a step-up blocker.", impact = "Higher values ignore small lips/slabs; lower values step up more aggressively.") + double stepObstacleMinHeight = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Bounding-box height above which two-block headroom is required for step-up.", impact = "Lower values are stricter; higher values allow sneaking/crawling to step in tighter spaces.") + double doubleHeadroomHeightThreshold = 1.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum bounding-box height counted as crawling on land.", impact = "Higher values make crawl detection more permissive.") + double crawlHeightMax = 0.61; + @art.arcane.adapt.util.config.ConfigDoc(value = "Requires players to be grounded for stealth speed to run.", impact = "True avoids midair acceleration and keeps behavior stable.") + boolean requireGrounded = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows stealth speed to run while the player is in water.", impact = "False prevents stealth from overriding seaborne-style underwater movement effects.") + boolean allowWhileInWater = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum horizontal velocity used to count the player as moving for FX/stat tracking.", impact = "Higher values reduce effects while nearly stationary.") + double movementVelocityThreshold = 0.005; + @art.arcane.adapt.util.config.ConfigDoc(value = "Shows a subtle soul particle near the player's feet while stealth speed is active.", impact = "Visual feedback visible only to the boosted player.") + boolean showSoulParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Chance per tick to spawn a soul particle while moving.", impact = "Higher values make the effect denser; lower values are subtler.") + double soulParticleChance = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Vertical offset for the soul particle effect.", impact = "Small positive values keep particles around floor level.") + double soulParticleYOffset = 0.02; + @art.arcane.adapt.util.config.ConfigDoc(value = "Activation sound volume heard by the boosted player.", impact = "Higher values are louder; lower values are subtler.") + float activationSoundVolume = 1.6f; + @art.arcane.adapt.util.config.ConfigDoc(value = "Activation sound pitch heard by the boosted player.", impact = "Higher values raise tone; lower values deepen it.") + float activationSoundPitch = 0.9f; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum time between activation sounds.", impact = "Higher values reduce audio spam when repeatedly starting/stopping.") + long activationSoundCooldownMs = 250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum time between progression stat increments while moving with stealth speed.", impact = "Controls how quickly the sneak-speed progression stat accumulates.") + long statIntervalMs = 200; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/stealth/util/EntityListing.java b/src/main/java/art/arcane/adapt/content/adaptation/stealth/util/EntityListing.java new file mode 100644 index 000000000..b82de6083 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/stealth/util/EntityListing.java @@ -0,0 +1,66 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.stealth.util; + +import lombok.Getter; +import org.bukkit.entity.EntityType; + +import java.util.List; + +public class EntityListing { + + @Getter + public static List aggroMobs = List.of( + EntityType.EVOKER, + EntityType.VINDICATOR, + EntityType.PILLAGER, + EntityType.RAVAGER, + EntityType.VEX, + EntityType.ENDERMITE, + EntityType.GUARDIAN, + EntityType.ELDER_GUARDIAN, + EntityType.SKELETON, + EntityType.SHULKER, + EntityType.SKELETON_HORSE, + EntityType.HUSK, + EntityType.STRAY, + EntityType.PHANTOM, + EntityType.BLAZE, + EntityType.CREEPER, + EntityType.GHAST, + EntityType.MAGMA_CUBE, + EntityType.SILVERFISH, + EntityType.SLIME, + EntityType.SPIDER, + EntityType.CAVE_SPIDER, + EntityType.ZOMBIE, + EntityType.ZOMBIE_HORSE, + EntityType.ZOMBIE_VILLAGER, + EntityType.DROWNED, + EntityType.WITHER_SKELETON, + EntityType.WITCH, + EntityType.HOGLIN, + EntityType.ZOGLIN, + EntityType.PIGLIN, + EntityType.PIGLIN_BRUTE, + EntityType.ENDERMAN + ); + + +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsBloodyBlade.java b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsBloodyBlade.java new file mode 100644 index 000000000..dfe3b42d0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsBloodyBlade.java @@ -0,0 +1,194 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.sword; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.adaptation.sword.effects.DamagingBleedEffect; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import de.slikey.effectlib.effect.BleedEffect; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class SwordsBloodyBlade extends SimpleAdaptation { + private final Map cooldowns; + private final Set bleedingEntities = java.util.concurrent.ConcurrentHashMap.newKeySet(); + private final Map bleedSource = new java.util.concurrent.ConcurrentHashMap<>(); + + public SwordsBloodyBlade() { + super("sword-bloody-blade"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("sword.bloody_blade.description")); + setDisplayName(Localizer.dLocalize("sword.bloody_blade.name")); + setIcon(Material.RED_DYE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(5534); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_swords_bloody_500") + .title(Localizer.dLocalize("advancement.challenge_swords_bloody_500.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_bloody_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_swords_bloody_500", "swords.bloody-blade.bleed-damage", 500, 400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_swords_bloody_kills_100") + .title(Localizer.dLocalize("advancement.challenge_swords_bloody_kills_100.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_bloody_kills_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_swords_bloody_kills_100", "swords.bloody-blade.bleed-kills", 100, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + " " + Localizer.dLocalize("sword.bloody_blade.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getDurationOfEffect(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.bloody_blade.lore2")); + v.addLore(C.RED + "* " + Form.duration(getCooldown(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.bloody_blade.lore3")); + } + + public long getCooldown(int level) { + return getConfig().cooldown * level; + } + + public long getDurationOfEffect(int level) { + return getConfig().effectDuration * level; + } + + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p && hasActiveAdaptation(p) && ItemListings.getToolSwords().contains(p.getInventory().getItemInMainHand().getType())) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown > System.currentTimeMillis()) + return; + Entity victim = e.getEntity(); + cooldowns.put(p.getUniqueId(), System.currentTimeMillis() + getCooldown(getLevel(p))); + if (!canDamageTarget(p, victim)) return; + if (areParticlesEnabled()) { + BleedEffect blood = victim instanceof LivingEntity l ? new DamagingBleedEffect(Adapt.instance.adaptEffectManager, getConfig().damagePerBleedProc, l) : new BleedEffect(Adapt.instance.adaptEffectManager); + blood.setEntity(victim); + blood.material = Material.CRIMSON_ROOTS; + blood.height = -1; + blood.iterations = Math.toIntExact(2 * (3 + (getDurationOfEffect(getLevel(p)) / 1000))); + blood.period = 5; //5 Every second, make a proc + blood.hurt = false; +// blood.callback = () -> { +// Adapt.mAdapt.msgp(sender.player(),(p,"You bled out.."); +// p.setHealth(1d); +// }; + blood.start(); + } else { + BleedEffect blood = victim instanceof LivingEntity l ? new DamagingBleedEffect(Adapt.instance.adaptEffectManager, getConfig().damagePerBleedProc, l) : new BleedEffect(Adapt.instance.adaptEffectManager); + blood.setEntity(victim); + blood.material = Material.VOID_AIR; + blood.height = -1; + blood.iterations = Math.toIntExact(2 * (3 + (getDurationOfEffect(getLevel(p)) / 1000))); + blood.period = 5; //5 Every second, make a proc + blood.hurt = false; + blood.start(); + } + bleedingEntities.add(victim.getUniqueId()); + bleedSource.put(victim.getUniqueId(), p.getUniqueId()); + getPlayer(p).getData().addStat("swords.bloody-blade.bleed-damage", 1); + + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + UUID victimId = e.getEntity().getUniqueId(); + if (bleedingEntities.remove(victimId)) { + UUID sourceId = bleedSource.remove(victimId); + Player source = sourceId == null ? null : Bukkit.getPlayer(sourceId); + if (source != null && source.isOnline()) { + getPlayer(source).getData().addStat("swords.bloody-blade.bleed-kills", 1); + } + } + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sword strikes cause bleeding over time.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Swords Bloody Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public long cooldown = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Per Bleed Proc for the Swords Bloody Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double damagePerBleedProc = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effect Duration for the Swords Bloody Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public long effectDuration = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Swords Bloody Blade adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.325; + + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsCrimsonCyclone.java b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsCrimsonCyclone.java new file mode 100644 index 000000000..58bff34fe --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsCrimsonCyclone.java @@ -0,0 +1,305 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.sword; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.adaptation.sword.effects.DamagingBleedEffect; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import de.slikey.effectlib.effect.BleedEffect; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; + +public class SwordsCrimsonCyclone extends SimpleAdaptation { + public SwordsCrimsonCyclone() { + super("sword-crimson-cyclone"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("sword.crimson_cyclone.description")); + setDisplayName(Localizer.dLocalize("sword.crimson_cyclone.name")); + setIcon(Material.NETHERITE_SWORD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_swords_cyclone_500") + .title(Localizer.dLocalize("advancement.challenge_swords_cyclone_500.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_cyclone_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_swords_cyclone_5k") + .title(Localizer.dLocalize("advancement.challenge_swords_cyclone_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_cyclone_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_swords_cyclone_500", "swords.crimson-cyclone.mobs-hit", 500, 400); + registerMilestone("challenge_swords_cyclone_5k", "swords.crimson-cyclone.mobs-hit", 5000, 1500); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_swords_cyclone_6") + .title(Localizer.dLocalize("advancement.challenge_swords_cyclone_6.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_cyclone_6.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("sword.crimson_cyclone.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getBaseDamage(level), 2) + C.GRAY + " " + Localizer.dLocalize("sword.crimson_cyclone.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("sword.crimson_cyclone.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.MeleeContext combat = resolveMeleeContext(e, this::isSword); + if (combat == null) { + return; + } + + Player p = combat.attacker(); + LivingEntity primaryTarget = combat.target(); + ItemStack hand = combat.mainHand(); + if (!isCritTrigger(p)) { + return; + } + + int level = combat.level(); + int hungerCost = getHungerCost(level); + if (p.getFoodLevel() < hungerCost) { + return; + } + + if (!applyDurabilityCost(hand, getDurabilityCost(level))) { + return; + } + + int hits = 0; + double radius = getRadius(level); + double damage = getBaseDamage(level); + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + p.setCooldown(hand.getType(), getCooldownTicks(level)); + + e.setDamage(e.getDamage() + damage); + applyBleed(primaryTarget, level); + if (areParticlesEnabled()) { + primaryTarget.getWorld().spawnParticle(Particle.CRIMSON_SPORE, primaryTarget.getLocation().add(0, 0.8, 0), 8, 0.2, 0.35, 0.2, 0.01); + } + hits++; + + for (Entity entity : primaryTarget.getWorld().getNearbyEntities(primaryTarget.getLocation(), radius, radius, radius)) { + if (!(entity instanceof LivingEntity target)) { + continue; + } + + if (target == p || target == primaryTarget) { + continue; + } + + if (!canDamageTarget(p, target)) { + continue; + } + + target.damage(damage, p); + applyBleed(target, level); + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particle.CRIMSON_SPORE, target.getLocation().add(0, 0.8, 0), 8, 0.2, 0.35, 0.2, 0.01); + } + hits++; + } + + if (hits <= 0) { + return; + } + + if (areParticlesEnabled()) { + + p.getWorld().spawnParticle(Particle.SWEEP_ATTACK, primaryTarget.getLocation().add(0, 1, 0), 2, 0.4, 0.1, 0.4, 0.02); + + } + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.CRIMSON_SPORE, primaryTarget.getLocation().add(0, 1, 0), 36, 0.8, 0.4, 0.8, 0.02); + } + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(primaryTarget.getLocation(), Sound.ENTITY_PLAYER_ATTACK_SWEEP, 1f, 0.7f); + sp.play(primaryTarget.getLocation(), Sound.ENTITY_WITHER_HURT, 0.65f, 1.45f); + xp(p, hits * getConfig().xpPerTargetHit); + getPlayer(p).getData().addStat("swords.crimson-cyclone.mobs-hit", hits); + + // Special achievement: hit 6+ mobs with one activation + if (hits >= 6 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_swords_cyclone_6")) { + getPlayer(p).getAdvancementHandler().grant("challenge_swords_cyclone_6"); + } + } + + private boolean isCritTrigger(Player p) { + return p.getFallDistance() >= getConfig().minFallDistanceForCrit + && !p.isOnGround() + && !p.isInWater() + && !p.isInsideVehicle() + && !p.isClimbing(); + } + + private boolean applyDurabilityCost(ItemStack hand, int durabilityCost) { + if (!(hand.getItemMeta() instanceof Damageable damageable) || hand.getType().getMaxDurability() <= 0) { + return true; + } + + int max = hand.getType().getMaxDurability(); + int next = damageable.getDamage() + durabilityCost; + if (next >= max) { + return false; + } + + damageable.setDamage(next); + hand.setItemMeta(damageable); + return true; + } + + private double getRadius(int level) { + return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); + } + + private double getBaseDamage(int level) { + return getConfig().baseDamage + (getLevelPercent(level) * getConfig().damageFactor); + } + + private void applyBleed(LivingEntity target, int level) { + BleedEffect bleed = new DamagingBleedEffect(Adapt.instance.adaptEffectManager, getBleedDamagePerProc(level), target); + bleed.setEntity(target); + bleed.material = getConfig().showBleedParticles ? Material.CRIMSON_ROOTS : Material.VOID_AIR; + bleed.height = -1; + bleed.period = 5; + bleed.hurt = false; + bleed.iterations = Math.max(4, (int) Math.ceil(getBleedTicks(level) / 5D)); + bleed.start(); + } + + private int getBleedTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().bleedTicksBase + (getLevelPercent(level) * getConfig().bleedTicksFactor))); + } + + private double getBleedDamagePerProc(int level) { + return Math.max(0.01, getConfig().bleedDamagePerProcBase + (getLevelPercent(level) * getConfig().bleedDamagePerProcFactor)); + } + + private int getHungerCost(int level) { + return Math.max(1, (int) Math.round(getConfig().hungerCostBase - (getLevelPercent(level) * getConfig().hungerCostFactor))); + } + + private int getDurabilityCost(int level) { + return Math.max(1, (int) Math.round(getConfig().durabilityCostBase - (getLevelPercent(level) * getConfig().durabilityCostFactor))); + } + + private int getCooldownTicks(int level) { + return Math.max(40, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Land a sword crit while falling to unleash a bleeding crimson cyclone around your target.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Bleed Particles for the Swords Crimson Cyclone adaptation.", impact = "True enables this behavior and false disables it.") + boolean showBleedParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.76; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 2.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 2.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Damage for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseDamage = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageFactor = 4.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bleed Ticks Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bleedTicksBase = 40; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bleed Ticks Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bleedTicksFactor = 90; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bleed Damage Per Proc Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bleedDamagePerProcBase = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bleed Damage Per Proc Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bleedDamagePerProcFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hungerCostBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hungerCostFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durabilityCostBase = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double durabilityCostFactor = 1.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 320; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 160; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Fall Distance For Crit for the Swords Crimson Cyclone adaptation.", impact = "Minimum fall distance required to trigger the cyclone on hit.") + float minFallDistanceForCrit = 0.08f; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Target Hit for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerTargetHit = 10; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsDualWield.java b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsDualWield.java new file mode 100644 index 000000000..138ca4c4e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsDualWield.java @@ -0,0 +1,148 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.sword; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +public class SwordsDualWield extends SimpleAdaptation { + public SwordsDualWield() { + super("sword-dual-wield"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("sword.dual_wield.description")); + setDisplayName(Localizer.dLocalize("sword.dual_wield.name")); + setIcon(Material.GOLDEN_SWORD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1800); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_swords_dual_1k") + .title(Localizer.dLocalize("advancement.challenge_swords_dual_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_dual_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_swords_dual_25k") + .title(Localizer.dLocalize("advancement.challenge_swords_dual_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_dual_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_swords_dual_1k", "swords.dual-wield.bonus-damage", 1000, 400); + registerMilestone("challenge_swords_dual_25k", "swords.dual-wield.bonus-damage", 25000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getSameMultiplier(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.dual_wield.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getMixedMultiplier(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.dual_wield.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.MeleeContext combat = resolveMeleeContext(e); + if (combat == null) { + return; + } + + Player p = combat.attacker(); + ItemStack main = p.getInventory().getItemInMainHand(); + ItemStack off = p.getInventory().getItemInOffHand(); + if (!isItem(main) || !isItem(off) || main.getType() == Material.AIR || off.getType() == Material.AIR) { + return; + } + + boolean sameWeapon = main.getType() == off.getType(); + double multiplier = sameWeapon ? getSameMultiplier(combat.level()) : getMixedMultiplier(combat.level()); + double originalDamage = e.getDamage(); + e.setDamage(originalDamage * multiplier); + double bonusDamage = e.getDamage() - originalDamage; + xp(p, e.getDamage() * getConfig().xpPerDamage); + getPlayer(p).getData().addStat("swords.dual-wield.bonus-damage", bonusDamage); + } + + private double getSameMultiplier(int level) { + return getConfig().sameWeaponBase + (getLevelPercent(level) * getConfig().sameWeaponFactor); + } + + private double getMixedMultiplier(int level) { + return getConfig().mixedWeaponBase + (getLevelPercent(level) * getConfig().mixedWeaponFactor); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Wield a sword in both hands for increased damage output.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Same Weapon Base for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sameWeaponBase = 1.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Same Weapon Factor for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sameWeaponFactor = 0.43; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mixed Weapon Base for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double mixedWeaponBase = 1.06; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mixed Weapon Factor for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double mixedWeaponFactor = 0.28; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Damage for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerDamage = 2.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsExecutionersEdge.java b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsExecutionersEdge.java new file mode 100644 index 000000000..ff2464b70 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsExecutionersEdge.java @@ -0,0 +1,186 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.sword; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public class SwordsExecutionersEdge extends SimpleAdaptation { + public SwordsExecutionersEdge() { + super("sword-executioners-edge"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("sword.executioners_edge.description")); + setDisplayName(Localizer.dLocalize("sword.executioners_edge.name")); + setIcon(Material.STONE_SWORD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1900); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_swords_execute_200") + .title(Localizer.dLocalize("advancement.challenge_swords_execute_200.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_execute_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_swords_execute_2500") + .title(Localizer.dLocalize("advancement.challenge_swords_execute_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_execute_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_swords_execute_200", "swords.executioners-edge.executions", 200, 400); + registerMilestone("challenge_swords_execute_2500", "swords.executioners-edge.executions", 2500, 1500); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.NETHERITE_AXE) + .key("challenge_swords_execute_5in10") + .title(Localizer.dLocalize("advancement.challenge_swords_execute_5in10.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_execute_5in10.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getBonusDamage(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.executioners_edge.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getThreshold(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.executioners_edge.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.MeleeContext combat = resolveMeleeContext(e, this::isSword); + if (combat == null) { + return; + } + + Player p = combat.attacker(); + LivingEntity target = combat.target(); + + double maxHealth = getMaxHealth(target); + if (maxHealth <= 0) { + return; + } + + double hpPercent = Math.max(0, target.getHealth() / maxHealth); + double threshold = getThreshold(combat.level()); + if (hpPercent > threshold) { + return; + } + + double multiplier = 1D + getBonusDamage(combat.level()); + e.setDamage(e.getDamage() * multiplier); + xp(p, e.getDamage() * getConfig().xpPerBuffedDamage); + getPlayer(p).getData().addStat("swords.executioners-edge.executions", 1); + + // Special achievement: execute 5 in 10 seconds + long now = System.currentTimeMillis(); + long windowStart = getStorageLong(p, "executeWindowStart", 0L); + int windowCount = getStorageInt(p, "executeWindowCount", 0); + if (now - windowStart > 10000L) { + windowStart = now; + windowCount = 1; + } else { + windowCount++; + } + setStorage(p, "executeWindowStart", windowStart); + setStorage(p, "executeWindowCount", windowCount); + if (windowCount >= 5 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_swords_execute_5in10")) { + getPlayer(p).getAdvancementHandler().grant("challenge_swords_execute_5in10"); + } + } + + private double getMaxHealth(LivingEntity entity) { + AttributeInstance attr = entity.getAttribute(Attribute.MAX_HEALTH); + return attr == null ? entity.getHealth() : attr.getValue(); + } + + private double getBonusDamage(int level) { + return getConfig().bonusDamageBase + (getLevelPercent(level) * getConfig().bonusDamageFactor); + } + + private double getThreshold(int level) { + return Math.min(getConfig().maxThreshold, getConfig().thresholdBase + (getLevelPercent(level) * getConfig().thresholdFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sword strikes deal bonus damage to low-health targets.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Damage Base for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusDamageBase = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Damage Factor for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusDamageFactor = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Threshold Base for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double thresholdBase = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Threshold Factor for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double thresholdFactor = 0.33; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Threshold for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxThreshold = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Buffed Damage for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBuffedDamage = 1.9; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsMachete.java b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsMachete.java new file mode 100644 index 000000000..394877142 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsMachete.java @@ -0,0 +1,243 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.sword; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Materials; +import art.arcane.volmlib.util.data.Cuboid; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import art.arcane.volmlib.util.math.RNG; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import java.util.concurrent.ThreadLocalRandom; + +public class SwordsMachete extends SimpleAdaptation { + public SwordsMachete() { + super("sword-machete"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("sword.machete.description")); + setDisplayName(Localizer.dLocalize("sword.machete.name")); + setIcon(Material.IRON_SWORD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(5234); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_swords_machete_2500") + .title(Localizer.dLocalize("advancement.challenge_swords_machete_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_machete_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_swords_machete_25k") + .title(Localizer.dLocalize("advancement.challenge_swords_machete_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_machete_25k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_swords_machete_2500", "swords.machete.foliage-cut", 2500, 300); + registerMilestone("challenge_swords_machete_25k", "swords.machete.foliage-cut", 25000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getRadius(level) + C.GRAY + " " + Localizer.dLocalize("sword.machete.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTime(getLevelPercent(level)) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("sword.machete.lore2")); + v.addLore(C.RED + "- " + getDamagePerBlock(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("sword.machete.lore3")); + } + + public double getRadius(int level) { + return (getLevelPercent(level) * getConfig().radiusFactor) + getConfig().radiusBase; + } + + @EventHandler + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if ((action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) || e.getHand() != EquipmentSlot.HAND) { + return; + } + + Player p = e.getPlayer(); + ItemStack is = e.getItem(); + if (is == null || !isSword(is) || p.hasCooldown(is.getType()) || !hasActiveAdaptation(p)) { + return; + } + + SoundPlayer spw = SoundPlayer.of(p.getWorld()); + int dmg = 0; + Location ctr = p.getEyeLocation().clone().add(p.getLocation().getDirection().clone().multiply(2.25)).add(0, -0.5, 0); + + int lvl = getLevel(p); + Cuboid c = new Cuboid(ctr); + c = c.expand(Cuboid.CuboidDirection.Up, (int) Math.floor(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.Down, (int) Math.floor(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.North, (int) Math.round(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.South, (int) Math.round(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.East, (int) Math.round(getRadius(lvl))); + c = c.expand(Cuboid.CuboidDirection.West, (int) Math.round(getRadius(lvl))); + + for (Block i : c) { + if (M.r((getLevelPercent(lvl) * 2.8) / (i.getLocation().distanceSquared(ctr)))) { + if (i.getType().equals(Material.TALL_GRASS) + || i.getType().equals(Material.CACTUS) + || i.getType().equals(Material.SUGAR_CANE) + || i.getType().equals(Material.CARROT) + || i.getType().equals(Material.POTATO) + || i.getType().equals(Material.NETHER_WART) + || i.getType().equals(Materials.GRASS) + || i.getType().equals(Material.FERN) + || i.getType().equals(Material.LARGE_FERN) + || i.getType().equals(Material.VINE) + || i.getType().equals(Material.ROSE_BUSH) + || i.getType().equals(Material.WITHER_ROSE) + || i.getType().equals(Material.ACACIA_LEAVES) + || i.getType().equals(Material.BIRCH_LEAVES) + || i.getType().equals(Material.DARK_OAK_LEAVES) + || i.getType().equals(Material.JUNGLE_LEAVES) + || i.getType().equals(Material.OAK_LEAVES) + || i.getType().equals(Material.SPRUCE_LEAVES) + || i.getType().equals(Material.BROWN_MUSHROOM) + || i.getType().equals(Material.RED_MUSHROOM) + || i.getType().equals(Material.DEAD_BUSH) + || i.getType().equals(Material.DANDELION) + || i.getType().equals(Material.TALL_SEAGRASS) + || i.getType().equals(Material.SEAGRASS) + || i.getType().equals(Material.WHITE_TULIP) + || i.getType().equals(Material.RED_TULIP) + || i.getType().equals(Material.PINK_TULIP) + || i.getType().equals(Material.ORANGE_TULIP) + || i.getType().equals(Material.LILY_OF_THE_VALLEY) + || i.getType().equals(Material.ALLIUM) + || i.getType().equals(Material.AZURE_BLUET) + || i.getType().equals(Material.SUNFLOWER) + || i.getType().equals(Material.CORNFLOWER) + || i.getType().equals(Material.CHORUS_FLOWER) + || i.getType().equals(Material.BAMBOO) + || i.getType().equals(Material.BAMBOO_SAPLING) + || i.getType().equals(Material.LILAC) + || i.getType().equals(Material.PEONY) + || i.getType().equals(Material.LILY_PAD) + || i.getType().equals(Material.COCOA) + || i.getType().equals(Material.MANGROVE_LEAVES)) { + if (!canBlockBreak(p, i.getLocation())) { + continue; + } + BlockBreakEvent ee = new BlockBreakEvent(i, p); + Bukkit.getPluginManager().callEvent(ee); + + if (!ee.isCancelled()) { + dmg += 1; + J.runAt(i.getLocation(), () -> { + i.breakNaturally(); + spw.play(i.getLocation(), Sound.BLOCK_GRASS_BREAK, 0.4f, (float) (ThreadLocalRandom.current().nextDouble() * 1.85D)); + }, RNG.r.i(0, (getMaxLevel() - lvl * 2) + 1)); + } + } + } + } + + if (dmg > 0) { + p.setCooldown(is.getType(), getCooldownTime(getLevelPercent(lvl))); + spw.play(p.getEyeLocation(), Sound.ENTITY_PLAYER_ATTACK_SWEEP, 1f, (float) (ThreadLocalRandom.current().nextDouble() / 2D) + 0.65f); + damageHand(p, dmg * getDamagePerBlock(getLevelPercent(lvl))); + xp(p, dmg * 11.25, "foliage-cut"); + getPlayer(p).getData().addStat("swords.machete.foliage-cut", dmg); + } + } + + private int getCooldownTime(double levelPercent) { + return (int) (((int) ((1D - levelPercent) * getConfig().cooldownTicksSlowest)) + getConfig().cooldownTicksBase); + } + + private int getDamagePerBlock(double levelPercent) { + return (int) (getConfig().toolDamageBase + (getConfig().toolDamageInverseLevelFactor * ((1D - levelPercent)))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Cut through foliage with ease using a sword.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Swords Machete adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.225; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 2.36; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Slowest for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksSlowest = 35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Tool Damage Base for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double toolDamageBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Tool Damage Inverse Level Factor for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double toolDamageInverseLevelFactor = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsPoisonedBlade.java b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsPoisonedBlade.java new file mode 100644 index 000000000..eec569eb1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsPoisonedBlade.java @@ -0,0 +1,186 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.sword; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.content.adaptation.sword.effects.DamagingBleedEffect; +import art.arcane.adapt.content.item.ItemListings; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import de.slikey.effectlib.effect.BleedEffect; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class SwordsPoisonedBlade extends SimpleAdaptation { + private final Map cooldowns; + private final Set poisonedEntities = java.util.concurrent.ConcurrentHashMap.newKeySet(); + private final Map poisonSource = new java.util.concurrent.ConcurrentHashMap<>(); + + public SwordsPoisonedBlade() { + super("sword-poison-blade"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("sword.poisoned_blade.description")); + setDisplayName(Localizer.dLocalize("sword.poisoned_blade.name")); + setIcon(Material.GREEN_DYE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInterval(4984); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new java.util.concurrent.ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPIDER_EYE) + .key("challenge_swords_poison_500") + .title(Localizer.dLocalize("advancement.challenge_swords_poison_500.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_poison_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_swords_poison_500", "swords.poisoned-blade.poison-applied", 500, 400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FERMENTED_SPIDER_EYE) + .key("challenge_swords_poison_kills_50") + .title(Localizer.dLocalize("advancement.challenge_swords_poison_kills_50.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_poison_kills_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_swords_poison_kills_50", "swords.poisoned-blade.poison-kills", 50, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + C.GRAY + " " + Localizer.dLocalize("sword.poisoned_blade.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getDurationOfEffect(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.poisoned_blade.lore2")); + v.addLore(C.RED + "* " + Form.duration(getCooldown(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.poisoned_blade.lore3")); + } + + public long getCooldown(int level) { + return getConfig().cooldown * level; + } + + public long getDurationOfEffect(int level) { + return getConfig().effectDuration * level; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p && hasActiveAdaptation(p) && ItemListings.getToolSwords().contains(p.getInventory().getItemInMainHand().getType())) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown > System.currentTimeMillis()) + return; + Entity victim = e.getEntity(); + cooldowns.put(p.getUniqueId(), System.currentTimeMillis() + getCooldown(getLevel(p))); + if (!canDamageTarget(p, victim)) return; + if (victim instanceof Player pvic) { + BleedEffect blood = new BleedEffect(Adapt.instance.adaptEffectManager); + blood.setEntity(pvic); + blood.material = Material.LARGE_FERN; + blood.height = -1; + blood.iterations = Math.toIntExact(2 * (3 + (getDurationOfEffect(getLevel(p)) / 1000))); + blood.period = 5; //5 Every second, make a proc + blood.hurt = false; + blood.start(); + addPotionStacks(pvic, PotionEffectType.POISON, 2, 50 * getLevel(p), true); + } else { + BleedEffect blood = victim instanceof LivingEntity l ? new DamagingBleedEffect(Adapt.instance.adaptEffectManager, 1, l) : new BleedEffect(Adapt.instance.adaptEffectManager); + blood.setEntity(victim); + blood.material = Material.LARGE_FERN; + blood.height = -1; + blood.iterations = Math.toIntExact(2 * (3 + (getDurationOfEffect(getLevel(p)) / 1000))); + blood.period = 5; //5 Every second, make a proc + blood.hurt = false; + blood.start(); + } + poisonedEntities.add(victim.getUniqueId()); + poisonSource.put(victim.getUniqueId(), p.getUniqueId()); + getPlayer(p).getData().addStat("swords.poisoned-blade.poison-applied", 1); + + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + UUID victimId = e.getEntity().getUniqueId(); + if (poisonedEntities.remove(victimId)) { + UUID sourceId = poisonSource.remove(victimId); + Player source = sourceId == null ? null : Bukkit.getPlayer(sourceId); + if (source != null && source.isOnline()) { + getPlayer(source).getData().addStat("swords.poisoned-blade.poison-kills", 1); + } + } + } + + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sword strikes apply poison.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Swords Poisoned Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public long cooldown = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effect Duration for the Swords Poisoned Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public long effectDuration = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.325; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsRiposteWindow.java b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsRiposteWindow.java new file mode 100644 index 000000000..068d1fc9a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/sword/SwordsRiposteWindow.java @@ -0,0 +1,208 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.sword; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; + +public class SwordsRiposteWindow extends SimpleAdaptation { + private final Map riposteUntil = new java.util.concurrent.ConcurrentHashMap<>(); + + public SwordsRiposteWindow() { + super("sword-riposte-window"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("sword.riposte_window.description")); + setDisplayName(Localizer.dLocalize("sword.riposte_window.name")); + setIcon(Material.GOLDEN_CHESTPLATE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2100); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_swords_riposte_200") + .title(Localizer.dLocalize("advancement.challenge_swords_riposte_200.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_riposte_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_swords_riposte_2500") + .title(Localizer.dLocalize("advancement.challenge_swords_riposte_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_riposte_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_swords_riposte_200", "swords.riposte.ripostes-landed", 200, 400); + registerMilestone("challenge_swords_riposte_2500", "swords.riposte.ripostes-landed", 2500, 1500); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_swords_riposte_3in5") + .title(Localizer.dLocalize("advancement.challenge_swords_riposte_3in5.title")) + .description(Localizer.dLocalize("advancement.challenge_swords_riposte_3in5.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.duration(getWindowMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.riposte_window.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getDamageBonus(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.riposte_window.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + riposteUntil.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (e.getEntity() instanceof Player defender) { + armRiposte(defender); + } + + if (!(e.getDamager() instanceof Player attacker)) { + return; + } + + long now = System.currentTimeMillis(); + long until = riposteUntil.getOrDefault(attacker.getUniqueId(), 0L); + if (until < now) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.MeleeContext combat = resolveMeleeContext(e, this::isSword); + if (combat == null) { + return; + } + + attacker = combat.attacker(); + e.setDamage(e.getDamage() * (1D + getDamageBonus(combat.level()))); + riposteUntil.remove(attacker.getUniqueId()); + if (areParticlesEnabled()) { + attacker.getWorld().spawnParticle(Particle.SWEEP_ATTACK, attacker.getLocation().add(0, 1, 0), 1, 0, 0, 0, 0); + } + SoundPlayer sp = SoundPlayer.of(attacker.getWorld()); + sp.play(attacker.getLocation(), Sound.ITEM_SHIELD_BLOCK, 0.7f, 1.6f); + sp.play(attacker.getLocation(), Sound.ENTITY_PLAYER_ATTACK_CRIT, 0.8f, 1.2f); + xp(attacker, e.getDamage() * getConfig().xpPerBuffedDamage); + getPlayer(attacker).getData().addStat("swords.riposte.ripostes-landed", 1); + + // Special achievement: 3 ripostes within 5 seconds + long riposteWindowStart = getStorageLong(attacker, "riposteWindowStart", 0L); + int riposteWindowCount = getStorageInt(attacker, "riposteWindowCount", 0); + if (now - riposteWindowStart > 5000L) { + riposteWindowStart = now; + riposteWindowCount = 1; + } else { + riposteWindowCount++; + } + setStorage(attacker, "riposteWindowStart", riposteWindowStart); + setStorage(attacker, "riposteWindowCount", riposteWindowCount); + if (riposteWindowCount >= 3 && AdaptConfig.get().isAdvancements() && !getPlayer(attacker).getData().isGranted("challenge_swords_riposte_3in5")) { + getPlayer(attacker).getAdvancementHandler().grant("challenge_swords_riposte_3in5"); + } + } + + private void armRiposte(Player defender) { + boolean hasShield = defender.getInventory().getItemInOffHand().getType() == Material.SHIELD + || defender.getInventory().getItemInMainHand().getType() == Material.SHIELD; + int level = getActiveLevel(defender); + if (level <= 0 || !defender.isBlocking() || !hasShield) { + return; + } + + riposteUntil.put(defender.getUniqueId(), System.currentTimeMillis() + getWindowMillis(level)); + SoundPlayer.of(defender.getWorld()).play(defender.getLocation(), Sound.ITEM_SHIELD_BLOCK, 0.6f, 0.9f); + } + + private long getWindowMillis(int level) { + return Math.max(150L, (long) Math.round(getConfig().windowMillisBase + (getLevelPercent(level) * getConfig().windowMillisFactor))); + } + + private double getDamageBonus(int level) { + return getConfig().damageBonusBase + (getLevelPercent(level) * getConfig().damageBonusFactor); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Blocking with a shield arms a short riposte window for your next sword strike.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.71; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Window Millis Base for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windowMillisBase = 350; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Window Millis Factor for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double windowMillisFactor = 550; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Base for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageBonusBase = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Factor for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageBonusFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Buffed Damage for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBuffedDamage = 1.8; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/sword/effects/DamagingBleedEffect.java b/src/main/java/art/arcane/adapt/content/adaptation/sword/effects/DamagingBleedEffect.java new file mode 100644 index 000000000..6a6d305e6 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/sword/effects/DamagingBleedEffect.java @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.sword.effects; + +import art.arcane.adapt.util.common.scheduling.J; +import de.slikey.effectlib.EffectManager; +import de.slikey.effectlib.effect.BleedEffect; +import org.bukkit.entity.LivingEntity; + +public class DamagingBleedEffect extends BleedEffect { + private final double damage; + private final LivingEntity target; + + public DamagingBleedEffect(EffectManager effectManager, double damage, LivingEntity target) { + super(effectManager); + this.damage = damage; + this.target = target; + } + + @Override + public void onRun() { + super.onRun(); + J.runEntity(target, () -> target.damage(damage)); + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingBeastRecall.java b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingBeastRecall.java new file mode 100644 index 000000000..d8e5b651a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingBeastRecall.java @@ -0,0 +1,235 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.taming; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; + +public class TamingBeastRecall extends SimpleAdaptation { + public TamingBeastRecall() { + super("tame-beast-recall"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("taming.beast_recall.description")); + setDisplayName(Localizer.dLocalize("taming.beast_recall.name")); + setIcon(Material.LEAD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2200); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEAD) + .key("challenge_taming_recall_100") + .title(Localizer.dLocalize("advancement.challenge_taming_recall_100.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_recall_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_taming_recall_1k") + .title(Localizer.dLocalize("advancement.challenge_taming_recall_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_recall_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_taming_recall_100", "taming.beast-recall.recalls", 100, 300); + registerMilestone("challenge_taming_recall_1k", "taming.beast-recall.recalls", 1000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getSearchRadius(level)) + C.GRAY + " " + Localizer.dLocalize("taming.beast_recall.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("taming.beast_recall.lore2")); + if (getConfig().hungerCost > 0) { + v.addLore(C.RED + "* " + getConfig().hungerCost + C.GRAY + " " + Localizer.dLocalize("taming.beast_recall.lore_cost_hunger")); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + if (e.getHand() != EquipmentSlot.HAND) { + return; + } + + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { + return; + } + + Player p = e.getPlayer(); + int level = getActiveLevel(p, Player::isSneaking); + if (level <= 0 || p.getInventory().getItemInMainHand().getType() != Material.LEAD || p.hasCooldown(Material.LEAD)) { + return; + } + + int hungerCost = Math.max(0, getConfig().hungerCost); + if (hungerCost > 0 && p.getFoodLevel() < hungerCost) { + return; + } + + Tameable tameable = findNearestOwnedTameable(p, getSearchRadius(level)); + if (tameable == null) { + return; + } + + Location safe = findSafeRecallLocation(p); + if (safe == null || !canPVE(p, safe)) { + return; + } + + J.teleport(tameable, safe); + tameable.setFallDistance(0); + p.setCooldown(Material.LEAD, getCooldownTicks(level)); + if (hungerCost > 0) { + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + } + e.setCancelled(true); + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.75f, 1.45f); + sp.play(safe, Sound.ITEM_LEAD_BREAK, 0.6f, 1.2f); + xp(p, getConfig().xpOnRecall); + getPlayer(p).getData().addStat("taming.beast-recall.recalls", 1); + } + + private Tameable findNearestOwnedTameable(Player p, double radius) { + double best = Double.MAX_VALUE; + Tameable out = null; + double minDistSq = getConfig().minRecallDistanceSquared; + for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), radius, radius, radius)) { + if (!(entity instanceof Tameable t) || !t.isTamed() || !(t.getOwner() instanceof Player owner) || !owner.getUniqueId().equals(p.getUniqueId())) { + continue; + } + + double d = t.getLocation().distanceSquared(p.getLocation()); + if (d <= minDistSq || d >= best) { + continue; + } + + out = t; + best = d; + } + + return out; + } + + private Location findSafeRecallLocation(Player p) { + Location base = p.getLocation(); + int[][] offsets = { + {0, 0}, {1, 0}, {-1, 0}, {0, 1}, {0, -1}, + {1, 1}, {1, -1}, {-1, 1}, {-1, -1}, + {2, 0}, {-2, 0}, {0, 2}, {0, -2} + }; + + for (int y = 0; y <= 2; y++) { + for (int[] offset : offsets) { + Location candidate = base.clone().add(offset[0], y, offset[1]); + if (isSafeSpot(candidate)) { + return candidate; + } + } + } + + return null; + } + + private boolean isSafeSpot(Location location) { + Block feet = location.getBlock(); + Block head = location.clone().add(0, 1, 0).getBlock(); + Block below = location.clone().add(0, -1, 0).getBlock(); + return feet.isPassable() && head.isPassable() && below.getType().isSolid(); + } + + private double getSearchRadius(int level) { + return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); + } + + private int getCooldownTicks(int level) { + return Math.max(40, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-right-click with a lead to recall your nearest tamed companion.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 38; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Recall Distance Squared for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minRecallDistanceSquared = 9.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 420; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 280; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Recall for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnRecall = 26; + @art.arcane.adapt.util.config.ConfigDoc(value = "Food points consumed per beast recall.", impact = "Higher values make each recall cost more hunger; 0 disables the cost.") + int hungerCost = 2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingDamage.java b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingDamage.java new file mode 100644 index 000000000..1d3624b41 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingDamage.java @@ -0,0 +1,278 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.taming; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.IAttribute; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class TamingDamage extends SimpleAdaptation { + private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-tame-damage-boost".getBytes()); + private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString("adapt:tame-damage-boost"); + private static final double FOLIA_SCAN_RADIUS = 48D; + private final Map appliedLevels = new ConcurrentHashMap<>(); + + public TamingDamage() { + super("tame-damage"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("taming.damage.description")); + setDisplayName(Localizer.dLocalize("taming.damage.name")); + setIcon(Material.FLINT); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(6119); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_taming_damage_500") + .title(Localizer.dLocalize("advancement.challenge_taming_damage_500.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_damage_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_taming_damage_5k") + .title(Localizer.dLocalize("advancement.challenge_taming_damage_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_damage_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_taming_damage_500", "taming.damage.pet-kills", 500, 400); + registerMilestone("challenge_taming_damage_5k", "taming.damage.pet-kills", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getDamageBoost(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.damage.lore1")); + } + + private double getDamageBoost(int level) { + return ((getLevelPercent(level) * getConfig().damageFactor) + getConfig().baseDamage); + } + + @Override + public void onTick() { + if (J.isFoliaThreading()) { + onFoliaTick(); + pruneInvalidAppliedLevels(); + return; + } + + Map ownerLevels = new HashMap<>(); + boolean hasActiveOwners = false; + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player owner = adaptPlayer.getPlayer(); + int level = getActiveLevel(owner); + if (level > 0) { + ownerLevels.put(owner.getUniqueId(), level); + hasActiveOwners = true; + } + } + + if (!hasActiveOwners) { + clearAppliedLevels(); + return; + } + + Set seen = new HashSet<>(); + for (World world : Bukkit.getServer().getWorlds()) { + Collection tameables = world.getEntitiesByClass(Tameable.class); + for (Tameable tameable : tameables) { + if (tameable.isTamed() && tameable.getOwner() instanceof Player p) { + seen.add(tameable.getUniqueId()); + update(tameable, ownerLevels.getOrDefault(p.getUniqueId(), 0)); + } + } + } + clearMissingAppliedLevels(seen); + } + + private void onFoliaTick() { + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player owner = adaptPlayer.getPlayer(); + int level = getActiveLevel(owner); + J.runEntity(owner, () -> updateNearbyOwnedTameables(owner, level)); + } + } + + private void updateNearbyOwnedTameables(Player owner, int level) { + if (owner == null || !owner.isOnline()) { + return; + } + + for (Entity nearby : owner.getNearbyEntities(FOLIA_SCAN_RADIUS, FOLIA_SCAN_RADIUS, FOLIA_SCAN_RADIUS)) { + if (!(nearby instanceof Tameable tameable) || !tameable.isTamed()) { + continue; + } + if (!(tameable.getOwner() instanceof Player tameOwner) || !tameOwner.getUniqueId().equals(owner.getUniqueId())) { + continue; + } + + update(tameable, level); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent dmgEvent + && dmgEvent.getDamager() instanceof Tameable tam + && tam.isTamed() + && tam.getOwner() instanceof Player p + && hasActiveAdaptation(p)) { + getPlayer(p).getData().addStat("taming.damage.pet-kills", 1); + } + appliedLevels.remove(e.getEntity().getUniqueId()); + } + + private void update(Tameable j, int level) { + UUID tameableId = j.getUniqueId(); + IAttribute attribute = Version.get().getAttribute(j, Attributes.GENERIC_ATTACK_DAMAGE); + if (attribute == null) { + appliedLevels.remove(tameableId); + return; + } + + Integer appliedLevel = appliedLevels.get(tameableId); + if (level <= 0) { + if (appliedLevel != null || attribute.hasModifier(MODIFIER, MODIFIER_KEY)) { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + } + appliedLevels.remove(tameableId); + return; + } + + if (appliedLevel != null && appliedLevel == level) { + return; + } + + attribute.setModifier(MODIFIER, MODIFIER_KEY, getDamageBoost(level), AttributeModifier.Operation.ADD_SCALAR); + appliedLevels.put(tameableId, level); + } + + private void clearAppliedLevels() { + if (appliedLevels.isEmpty()) { + return; + } + + for (UUID tameableId : new HashSet<>(appliedLevels.keySet())) { + Entity entity = Bukkit.getEntity(tameableId); + if (entity instanceof Tameable tameable) { + IAttribute attribute = Version.get().getAttribute(tameable, Attributes.GENERIC_ATTACK_DAMAGE); + if (attribute != null && attribute.hasModifier(MODIFIER, MODIFIER_KEY)) { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + } + } + appliedLevels.remove(tameableId); + } + } + + private void clearMissingAppliedLevels(Set seen) { + if (appliedLevels.isEmpty()) { + return; + } + + for (UUID tameableId : new HashSet<>(appliedLevels.keySet())) { + if (seen.contains(tameableId)) { + continue; + } + + Entity entity = Bukkit.getEntity(tameableId); + if (entity instanceof Tameable tameable) { + IAttribute attribute = Version.get().getAttribute(tameable, Attributes.GENERIC_ATTACK_DAMAGE); + if (attribute != null && attribute.hasModifier(MODIFIER, MODIFIER_KEY)) { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + } + } + appliedLevels.remove(tameableId); + } + } + + private void pruneInvalidAppliedLevels() { + if (appliedLevels.isEmpty()) { + return; + } + + for (UUID tameableId : new HashSet<>(appliedLevels.keySet())) { + Entity entity = Bukkit.getEntity(tameableId); + if (!(entity instanceof Tameable tameable) || !tameable.isValid() || tameable.isDead()) { + appliedLevels.remove(tameableId); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Increase your tamed animal damage dealt.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Damage for the Taming Damage adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseDamage = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Taming Damage adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageFactor = 0.65; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingHealthBoost.java b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingHealthBoost.java new file mode 100644 index 000000000..3e54e5f58 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingHealthBoost.java @@ -0,0 +1,265 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.taming; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.IAttribute; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.attribute.AttributeModifier; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class TamingHealthBoost extends SimpleAdaptation { + private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-tame-health-boost".getBytes()); + private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString("adapt:tame-health-boost"); + private static final double FOLIA_SCAN_RADIUS = 48D; + private final Map appliedLevels = new ConcurrentHashMap<>(); + + public TamingHealthBoost() { + super("tame-health"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("taming.health.description")); + setDisplayName(Localizer.dLocalize("taming.health.name")); + setIcon(Material.COOKED_BEEF); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(4753); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_APPLE) + .key("challenge_taming_health_boost_1728k") + .title(Localizer.dLocalize("advancement.challenge_taming_health_boost_1728k.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_health_boost_1728k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_taming_health_boost_1728k", "taming.health-boost.ticks-active", 1728000, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getHealthBoost(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.health.lore1")); + } + + private double getHealthBoost(int level) { + return ((getLevelPercent(level) * getConfig().healthBoostFactor) + getConfig().healthBoostBase); + } + + @Override + public void onTick() { + if (J.isFoliaThreading()) { + onFoliaTick(); + pruneInvalidAppliedLevels(); + return; + } + + Map ownerStates = new HashMap<>(); + boolean hasActiveOwners = false; + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player owner = adaptPlayer.getPlayer(); + int level = getLevel(owner); + if (level > 0) { + ownerStates.put(owner.getUniqueId(), new OwnerState(adaptPlayer, owner, level)); + hasActiveOwners = true; + } + } + + if (!hasActiveOwners) { + clearAppliedLevels(); + return; + } + + Set seen = new HashSet<>(); + for (World world : Bukkit.getServer().getWorlds()) { + Collection tameables = world.getEntitiesByClass(Tameable.class); + for (Tameable tameable : tameables) { + if (tameable.isTamed() && tameable.getOwner() instanceof Player p) { + seen.add(tameable.getUniqueId()); + OwnerState state = ownerStates.get(p.getUniqueId()); + int level = state == null ? 0 : state.level(); + update(tameable, level); + if (level > 0 && state != null) { + state.ownerData().getData().addStat("taming.health-boost.ticks-active", 1); + } + } + } + } + clearMissingAppliedLevels(seen); + } + + private void onFoliaTick() { + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player owner = adaptPlayer.getPlayer(); + OwnerState state = new OwnerState(adaptPlayer, owner, getLevel(owner)); + J.runEntity(owner, () -> updateNearbyOwnedTameables(state)); + } + } + + private void updateNearbyOwnedTameables(OwnerState state) { + Player owner = state.owner(); + if (owner == null || !owner.isOnline()) { + return; + } + + for (Entity nearby : owner.getNearbyEntities(FOLIA_SCAN_RADIUS, FOLIA_SCAN_RADIUS, FOLIA_SCAN_RADIUS)) { + if (!(nearby instanceof Tameable tameable) || !tameable.isTamed()) { + continue; + } + if (!(tameable.getOwner() instanceof Player tameOwner) || !tameOwner.getUniqueId().equals(owner.getUniqueId())) { + continue; + } + + update(tameable, state.level()); + if (state.level() > 0) { + state.ownerData().getData().addStat("taming.health-boost.ticks-active", 1); + } + } + } + + private void update(Tameable j, int level) { + UUID tameableId = j.getUniqueId(); + IAttribute attribute = Version.get().getAttribute(j, Attributes.GENERIC_MAX_HEALTH); + if (attribute == null) { + appliedLevels.remove(tameableId); + return; + } + + Integer appliedLevel = appliedLevels.get(tameableId); + if (level <= 0) { + if (appliedLevel != null || attribute.hasModifier(MODIFIER, MODIFIER_KEY)) { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + } + appliedLevels.remove(tameableId); + return; + } + + if (appliedLevel != null && appliedLevel == level) { + return; + } + + attribute.setModifier(MODIFIER, MODIFIER_KEY, getHealthBoost(level), AttributeModifier.Operation.ADD_SCALAR); + appliedLevels.put(tameableId, level); + } + + private void clearAppliedLevels() { + if (appliedLevels.isEmpty()) { + return; + } + + for (UUID tameableId : new HashSet<>(appliedLevels.keySet())) { + Entity entity = Bukkit.getEntity(tameableId); + if (entity instanceof Tameable tameable) { + IAttribute attribute = Version.get().getAttribute(tameable, Attributes.GENERIC_MAX_HEALTH); + if (attribute != null && attribute.hasModifier(MODIFIER, MODIFIER_KEY)) { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + } + } + appliedLevels.remove(tameableId); + } + } + + private void clearMissingAppliedLevels(Set seen) { + if (appliedLevels.isEmpty()) { + return; + } + + for (UUID tameableId : new HashSet<>(appliedLevels.keySet())) { + if (seen.contains(tameableId)) { + continue; + } + + Entity entity = Bukkit.getEntity(tameableId); + if (entity instanceof Tameable tameable) { + IAttribute attribute = Version.get().getAttribute(tameable, Attributes.GENERIC_MAX_HEALTH); + if (attribute != null && attribute.hasModifier(MODIFIER, MODIFIER_KEY)) { + attribute.removeModifier(MODIFIER, MODIFIER_KEY); + } + } + appliedLevels.remove(tameableId); + } + } + + private void pruneInvalidAppliedLevels() { + if (appliedLevels.isEmpty()) { + return; + } + + for (UUID tameableId : new HashSet<>(appliedLevels.keySet())) { + Entity entity = Bukkit.getEntity(tameableId); + if (!(entity instanceof Tameable tameable) || !tameable.isValid() || tameable.isDead()) { + appliedLevels.remove(tameableId); + } + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record OwnerState(AdaptPlayer ownerData, Player owner, int level) { + } + + @NoArgsConstructor + @ConfigDescription("Increase your tamed animal maximum health.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Health Boost Factor for the Taming Health Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double healthBoostFactor = 2.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Health Boost Base for the Taming Health Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double healthBoostBase = 0.57; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingHealthRegeneration.java b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingHealthRegeneration.java new file mode 100644 index 000000000..80b48ed1e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingHealthRegeneration.java @@ -0,0 +1,158 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.taming; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.UUID; + +import static org.bukkit.Particle.HEART; + +public class TamingHealthRegeneration extends SimpleAdaptation { + private final Map lastDamage = new java.util.concurrent.ConcurrentHashMap<>(); + + public TamingHealthRegeneration() { + super("tame-health-regeneration"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("taming.regeneration.description")); + setDisplayName(Localizer.dLocalize("taming.regeneration.name")); + setIcon(Material.GOLDEN_APPLE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setInterval(1033); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLISTERING_MELON_SLICE) + .key("challenge_taming_regen_1k") + .title(Localizer.dLocalize("advancement.challenge_taming_regen_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_regen_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_taming_regen_1k", "taming.health-regen.health-regened", 1000, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRegenSpeed(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.regeneration.lore1")); + } + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + if (e.getEntity() instanceof Tameable tam + && tam.getOwner() instanceof Player p) { + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (lastDamage.containsKey(tam.getUniqueId())) { + Adapt.verbose("Tamed Entity " + tam.getUniqueId() + " last damaged " + (M.ms() - lastDamage.get(tam.getUniqueId())) + "ms ago"); + return; + } + art.arcane.adapt.api.version.IAttribute attribute = Version.get().getAttribute(tam, Attributes.GENERIC_MAX_HEALTH); + double mh = attribute == null ? tam.getHealth() : attribute.getValue(); + if (tam.isTamed() && tam.getOwner() instanceof Player && tam.getHealth() < mh) { + Adapt.verbose("Successfully healed tamed entity " + tam.getUniqueId()); + Adapt.verbose("[PRE] Current Health: " + tam.getHealth() + " Max Health: " + mh); + tam.addPotionEffect(PotionEffectType.REGENERATION.createEffect(25 * level, 3)); + getPlayer(p).getData().addStat("taming.health-regen.health-regened", 1); + + if (areParticlesEnabled()) { + Adapt.verbose("Healing tamed entity " + tam.getUniqueId() + " with particles"); + tam.getWorld().spawnParticle(HEART, tam.getLocation().add(0, 1, 0), 2 * p.getLevel()); + } else { + Adapt.verbose("Healing tamed entity " + tam.getUniqueId() + " without particles"); + } + } + lastDamage.put(e.getEntity().getUniqueId(), M.ms()); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDeathEvent e) { + lastDamage.remove(e.getEntity().getUniqueId()); + } + + + private double getRegenSpeed(int level) { + return ((getLevelPercent(level) * (getLevelPercent(level)) * getConfig().regenFactor) + getConfig().regenBase); + } + + @Override + public void onTick() { + lastDamage.entrySet().removeIf(i -> M.ms() - i.getValue() > 8000); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Increase your tamed animal regeneration rate.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Taming Health Regeneration adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Regen Factor for the Taming Health Regeneration adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double regenFactor = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Regen Base for the Taming Health Regeneration adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double regenBase = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingMountedTactics.java b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingMountedTactics.java new file mode 100644 index 000000000..ec32ae232 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingMountedTactics.java @@ -0,0 +1,301 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.taming; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.math.VelocitySpeed; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class TamingMountedTactics extends SimpleAdaptation { + private final Map lastMountedLocation = new java.util.concurrent.ConcurrentHashMap<>(); + + public TamingMountedTactics() { + super("tame-mounted-tactics"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("taming.mounted_tactics.description")); + setDisplayName(Localizer.dLocalize("taming.mounted_tactics.name")); + setIcon(Material.SADDLE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(10); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SADDLE) + .key("challenge_taming_mounted_200") + .title(Localizer.dLocalize("advancement.challenge_taming_mounted_200.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_mounted_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_HORSE_ARMOR) + .key("challenge_taming_mounted_50k") + .title(Localizer.dLocalize("advancement.challenge_taming_mounted_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_mounted_50k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_taming_mounted_200", "taming.mounted-tactics.mounted-kills", 200, 400); + registerMilestone("challenge_taming_mounted_50k", "taming.mounted-tactics.distance", 50000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getMountedDamageBonus(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.mounted_tactics.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getMountedDamageReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.mounted_tactics.lore2")); + } + + @Override + public void onTick() { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + int level = getActiveLevel(p); + if (level <= 0) { + continue; + } + + Entity vehicle = p.getVehicle(); + if (vehicle != null) { + Location last = lastMountedLocation.get(p.getUniqueId()); + Location current = p.getLocation(); + if (last != null && last.getWorld() == current.getWorld()) { + double dist = last.distance(current); + if (dist > 0.1 && dist < 100) { + getPlayer(p).getData().addStat("taming.mounted-tactics.distance", dist); + } + } + lastMountedLocation.put(p.getUniqueId(), current); + } else { + lastMountedLocation.remove(p.getUniqueId()); + } + if (vehicle instanceof AbstractHorse horse) { + if (hasForwardInput(p)) { + applyMountForwardSpeed(horse, p, getHorseTargetSpeed(level)); + } + if (p.isSprinting()) { + Vector push = p.getLocation().getDirection().clone().setY(0).normalize().multiply(getHorsePush(level)); + horse.setVelocity(horse.getVelocity().multiply(0.8).add(push)); + } + } else if (vehicle instanceof Strider strider) { + p.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 40, 0, false, false, true), true); + if (hasForwardInput(p)) { + applyMountForwardSpeed(strider, p, getStriderTargetSpeed(level)); + } + if (strider.getLocation().getBlock().getType() == Material.LAVA || strider.getLocation().clone().subtract(0, 1, 0).getBlock().getType() == Material.LAVA) { + strider.setShivering(false); + } + } else if (vehicle instanceof Pig pig) { + p.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, 25, getPigResistanceAmplifier(level), false, false, true), true); + if (p.isSprinting()) { + Vector forward = p.getLocation().getDirection().clone().setY(0).normalize().multiply(getPigPush(level)); + pig.setVelocity(pig.getVelocity().multiply(0.7).add(forward)); + } + } + } + } + + private void applyMountForwardSpeed(Entity mount, Player rider, double targetSpeed) { + Vector direction = rider.getLocation().getDirection().setY(0); + if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { + return; + } + + direction.normalize(); + Vector velocity = mount.getVelocity(); + Vector horizontal = VelocitySpeed.horizontalOnly(velocity); + Vector targetHorizontal = direction.multiply(Math.max(0, targetSpeed)); + Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().mountAccelPerTick)); + nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().mountMaxHorizontalSpeed); + mount.setVelocity(new Vector(nextHorizontal.getX(), velocity.getY(), nextHorizontal.getZ())); + } + + private boolean hasForwardInput(Player p) { + VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); + return input.forward() && !input.backward(); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (e.getEntity().getKiller() instanceof Player p + && p.getVehicle() != null + && hasActiveAdaptation(p)) { + getPlayer(p).getData().addStat("taming.mounted-tactics.mounted-kills", 1); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player attacker && attacker.getVehicle() != null) { + int level = getActiveLevel(attacker); + if (level > 0) { + if (!canDamageTarget(attacker, e.getEntity())) { + return; + } + + e.setDamage(e.getDamage() * (1D + getMountedDamageBonus(level))); + xp(attacker, e.getDamage() * getConfig().xpPerMountedDamage); + } + } + + if (e.getEntity() instanceof Player defender && defender.getVehicle() != null) { + int level = getActiveLevel(defender); + if (level > 0) { + e.setDamage(e.getDamage() * (1D - getMountedDamageReduction(level))); + } + } + } + + private double getMountedDamageBonus(int level) { + return Math.min(getConfig().maxMountedDamageBonus, getConfig().mountedDamageBonusBase + (getLevelPercent(level) * getConfig().mountedDamageBonusFactor)); + } + + private double getMountedDamageReduction(int level) { + return Math.min(getConfig().maxMountedDamageReduction, getConfig().mountedDamageReductionBase + (getLevelPercent(level) * getConfig().mountedDamageReductionFactor)); + } + + private int getHorseSpeedAmplifier(int level) { + return Math.max(0, (int) Math.round(getConfig().horseSpeedAmplifierBase + (getLevelPercent(level) * getConfig().horseSpeedAmplifierFactor))); + } + + private int getStriderSpeedAmplifier(int level) { + return Math.max(0, (int) Math.round(getConfig().striderSpeedAmplifierBase + (getLevelPercent(level) * getConfig().striderSpeedAmplifierFactor))); + } + + private int getPigResistanceAmplifier(int level) { + return Math.max(0, (int) Math.round(getConfig().pigResistanceAmplifierBase + (getLevelPercent(level) * getConfig().pigResistanceAmplifierFactor))); + } + + private double getHorseTargetSpeed(int level) { + int amplifier = getHorseSpeedAmplifier(level); + double base = Math.max(0, getConfig().horseBaseHorizontalSpeed); + return Math.min(getConfig().mountMaxHorizontalSpeed, base * VelocitySpeed.speedAmplifierScalar(amplifier)); + } + + private double getStriderTargetSpeed(int level) { + int amplifier = getStriderSpeedAmplifier(level); + double base = Math.max(0, getConfig().striderBaseHorizontalSpeed); + return Math.min(getConfig().mountMaxHorizontalSpeed, base * VelocitySpeed.speedAmplifierScalar(amplifier)); + } + + private double getHorsePush(int level) { + return getConfig().horsePushBase + (getLevelPercent(level) * getConfig().horsePushFactor); + } + + private double getPigPush(int level) { + return getConfig().pigPushBase + (getLevelPercent(level) * getConfig().pigPushFactor); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Gain mount-specific combat and control bonuses while riding.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mounted Damage Bonus Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double mountedDamageBonusBase = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mounted Damage Bonus Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double mountedDamageBonusFactor = 0.22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Mounted Damage Bonus for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxMountedDamageBonus = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mounted Damage Reduction Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double mountedDamageReductionBase = 0.06; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Mounted Damage Reduction Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double mountedDamageReductionFactor = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Mounted Damage Reduction for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxMountedDamageReduction = 0.28; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Horse Speed Amplifier Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double horseSpeedAmplifierBase = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Horse Speed Amplifier Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double horseSpeedAmplifierFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Strider Speed Amplifier Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double striderSpeedAmplifierBase = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Strider Speed Amplifier Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double striderSpeedAmplifierFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Pig Resistance Amplifier Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pigResistanceAmplifierBase = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Pig Resistance Amplifier Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pigResistanceAmplifierFactor = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base horizontal speed target used for horse mounted speed scaling.", impact = "Higher values increase steady mounted horse acceleration when moving forward.") + double horseBaseHorizontalSpeed = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base horizontal speed target used for strider mounted speed scaling.", impact = "Higher values increase steady mounted strider acceleration when moving forward.") + double striderBaseHorizontalSpeed = 0.24; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force on mounts.", impact = "Acts as a hard cap to prevent runaway mounted momentum.") + double mountMaxHorizontalSpeed = 0.78; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast mounts accelerate toward the target speed per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") + double mountAccelPerTick = 0.065; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") + double fallbackInputVelocityThreshold = 0.0008; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Horse Push Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double horsePushBase = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Horse Push Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double horsePushFactor = 0.16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Pig Push Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pigPushBase = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Pig Push Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double pigPushFactor = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mounted Damage for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerMountedDamage = 1.5; + + double fallbackInputVelocityThresholdSquared() { + double threshold = Math.max(0, fallbackInputVelocityThreshold); + return threshold * threshold; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingPackLeaderAura.java b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingPackLeaderAura.java new file mode 100644 index 000000000..40e6df26f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingPackLeaderAura.java @@ -0,0 +1,202 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.taming; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.List; + +public class TamingPackLeaderAura extends SimpleAdaptation { + private int ownerCursor = 0; + + public TamingPackLeaderAura() { + super("tame-pack-leader-aura"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("taming.pack_leader_aura.description")); + setDisplayName(Localizer.dLocalize("taming.pack_leader_aura.name")); + setIcon(Material.BONE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(30); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_taming_pack_72k") + .title(Localizer.dLocalize("advancement.challenge_taming_pack_72k.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_pack_72k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_taming_pack_72k", "taming.pack-leader.buffed-ticks", 72000, 400); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("taming.pack_leader_aura.lore1")); + v.addLore(C.GREEN + "+ " + (1 + getAmplifier(level)) + C.GRAY + " " + Localizer.dLocalize("taming.pack_leader_aura.lore2")); + } + + @Override + public void onTick() { + List owners = collectOwners(); + if (owners.isEmpty()) { + return; + } + + List batch = selectBatch(owners); + if (J.isFoliaThreading()) { + for (OwnerAuraState state : batch) { + J.runEntity(state.owner(), () -> applyAura(state)); + } + return; + } + + if (!J.isPrimaryThread()) { + J.s(this::onTick); + return; + } + + for (OwnerAuraState state : batch) { + applyAura(state); + } + } + + private List collectOwners() { + List owners = new ArrayList<>(); + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player owner = adaptPlayer.getPlayer(); + int level = getActiveLevel(owner); + if (level <= 0) { + continue; + } + + double radius = getRadius(level); + owners.add(new OwnerAuraState(adaptPlayer, owner, radius, radius * radius, getAmplifier(level))); + } + + return owners; + } + + private List selectBatch(List owners) { + int size = owners.size(); + int limit = Math.max(1, Math.min(size, getConfig().maxOwnersPerPass)); + int start = Math.floorMod(ownerCursor, size); + List batch = new ArrayList<>(limit); + for (int i = 0; i < limit; i++) { + int index = (start + i) % size; + batch.add(owners.get(index)); + } + ownerCursor = (start + limit) % size; + return batch; + } + + private void applyAura(OwnerAuraState state) { + Player owner = state.owner(); + if (owner == null || !owner.isOnline()) { + return; + } + + Location ownerLocation = owner.getLocation(); + for (Entity nearby : owner.getNearbyEntities(state.radius(), state.radius(), state.radius())) { + if (!(nearby instanceof Tameable tameable) || !tameable.isTamed()) { + continue; + } + if (!(tameable.getOwner() instanceof Player petOwner) || !petOwner.getUniqueId().equals(owner.getUniqueId())) { + continue; + } + if (ownerLocation.distanceSquared(tameable.getLocation()) > state.radiusSquared()) { + continue; + } + + tameable.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, getConfig().effectTicks, state.amplifier(), false, false)); + tameable.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, getConfig().effectTicks, state.amplifier(), false, false)); + state.ownerData().getData().addStat("taming.pack-leader.buffed-ticks", 1); + } + } + + private double getRadius(int level) { + return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); + } + + private int getAmplifier(int level) { + return Math.max(0, (int) Math.floor(getLevelPercent(level) * getConfig().maxAmplifier)); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record OwnerAuraState(AdaptPlayer ownerData, Player owner, + double radius, double radiusSquared, + int amplifier) { + } + + @NoArgsConstructor + @ConfigDescription("Nearby tamed companions gain speed and regeneration near their owner.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Taming Pack Leader Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusBase = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Taming Pack Leader Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double radiusFactor = 14; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Amplifier for the Taming Pack Leader Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxAmplifier = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effect Ticks for the Taming Pack Leader Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int effectTicks = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum owners processed per aura pass.", impact = "Lower values reduce burst workload but spread updates across more passes.") + int maxOwnersPerPass = 120; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingSharedPain.java b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingSharedPain.java new file mode 100644 index 000000000..6fd90e55e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/taming/TamingSharedPain.java @@ -0,0 +1,165 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.taming; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; + +public class TamingSharedPain extends SimpleAdaptation { + public TamingSharedPain() { + super("tame-shared-pain"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("taming.shared_pain.description")); + setDisplayName(Localizer.dLocalize("taming.shared_pain.name")); + setIcon(Material.POPPY); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1700); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHIELD) + .key("challenge_taming_shared_500") + .title(Localizer.dLocalize("advancement.challenge_taming_shared_500.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_shared_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TOTEM_OF_UNDYING) + .key("challenge_taming_shared_5k") + .title(Localizer.dLocalize("advancement.challenge_taming_shared_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_shared_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_taming_shared_500", "taming.shared-pain.damage-taken", 500, 400); + registerMilestone("challenge_taming_shared_5k", "taming.shared-pain.damage-taken", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getRedirectPercent(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.shared_pain.lore1")); + v.addLore(C.YELLOW + "* " + Form.f(getOwnerHealthFloor(level), 1) + C.GRAY + " " + Localizer.dLocalize("taming.shared_pain.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Tameable tameable) || !tameable.isTamed() || !(tameable.getOwner() instanceof Player owner)) { + return; + } + + int level = getActiveDamageLevel(owner, tameable); + if (level <= 0) { + return; + } + + double redirect = e.getDamage() * getRedirectPercent(level); + if (redirect <= 0) { + return; + } + + double floor = getOwnerHealthFloor(level); + double allowed = Math.max(0, owner.getHealth() - floor); + redirect = Math.min(redirect, allowed); + if (redirect <= 0.01) { + return; + } + + e.setDamage(Math.max(0, e.getDamage() - redirect)); + if (e.getDamage() <= 0.01) { + e.setCancelled(true); + } + + owner.damage(redirect); + getPlayer(owner).getData().addStat("taming.shared-pain.damage-taken", redirect); + SoundPlayer sp = SoundPlayer.of(owner.getWorld()); + sp.play(owner.getLocation(), Sound.BLOCK_AMETHYST_CLUSTER_HIT, 0.65f, 0.7f); + sp.play(tameable.getLocation(), Sound.ENTITY_WOLF_WHINE, 0.55f, 1.2f); + xp(owner, redirect * getConfig().xpPerRedirectedDamage); + } + + private double getRedirectPercent(int level) { + return Math.min(getConfig().maxRedirectPercent, getConfig().redirectPercentBase + (getLevelPercent(level) * getConfig().redirectPercentFactor)); + } + + private double getOwnerHealthFloor(int level) { + return Math.max(1.0, getConfig().ownerHealthFloorBase + (getLevelPercent(level) * getConfig().ownerHealthFloorFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Redirect part of your pet's incoming damage to you, preserving companion survivability.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Redirect Percent Base for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double redirectPercentBase = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Redirect Percent Factor for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double redirectPercentFactor = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Redirect Percent for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxRedirectPercent = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Owner Health Floor Base for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double ownerHealthFloorBase = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Owner Health Floor Factor for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double ownerHealthFloorFactor = 4.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Redirected Damage for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerRedirectedDamage = 2.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulBloodPact.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulBloodPact.java new file mode 100644 index 000000000..2fa45271a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulBloodPact.java @@ -0,0 +1,442 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.math.VelocitySpeed; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class TragoulBloodPact extends SimpleAdaptation { + private static final PotionEffectType[] EFFECT_POOL = { + PotionEffectType.SPEED, + PotionEffectType.REGENERATION, + PotionEffectType.RESISTANCE, + PotionEffectType.FIRE_RESISTANCE, + PotionEffectType.ABSORPTION, + PotionEffectType.JUMP_BOOST, + PotionEffectType.NIGHT_VISION + }; + + private final Map procCooldowns = new ConcurrentHashMap<>(); + private final Map lowHealthProcs = new ConcurrentHashMap<>(); + private final Map speedBursts = new ConcurrentHashMap<>(); + + public TragoulBloodPact() { + super("tragoul-blood-pact"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.blood_pact.description")); + setDisplayName(Localizer.dLocalize("tragoul.blood_pact.name")); + setIcon(Material.NETHER_WART); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(20); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.REDSTONE) + .key("challenge_tragoul_pact_200") + .title(Localizer.dLocalize("advancement.challenge_tragoul_pact_200.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_pact_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_tragoul_pact_kills_500") + .title(Localizer.dLocalize("advancement.challenge_tragoul_pact_kills_500.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_pact_kills_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_tragoul_pact_200", "tragoul.blood-pact.health-sacrificed", 200, 400); + registerMilestone("challenge_tragoul_pact_kills_500", "tragoul.blood-pact.empowered-kills", 500, 1000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.REDSTONE) + .key("challenge_tragoul_pact_all_in") + .title(Localizer.dLocalize("advancement.challenge_tragoul_pact_all_in.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_pact_all_in.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getProcChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("tragoul.blood_pact.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getEffectDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.blood_pact.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getProcCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.blood_pact.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + procCooldowns.remove(e.getPlayer().getUniqueId()); + lowHealthProcs.remove(e.getPlayer().getUniqueId()); + speedBursts.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerDeathEvent e) { + speedBursts.remove(e.getEntity().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0 || e.getFinalDamage() < getMinTriggerDamage()) { + return; + } + + long now = System.currentTimeMillis(); + long until = procCooldowns.getOrDefault(p.getUniqueId(), 0L); + if (now < until) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getProcChance(level)) { + return; + } + + procCooldowns.put(p.getUniqueId(), now + getProcCooldownMillis(level)); + getPlayer(p).getData().addStat("tragoul.blood-pact.health-sacrificed", (int) e.getFinalDamage()); + if (p.getHealth() - e.getFinalDamage() <= 6.0) { + lowHealthProcs.put(p.getUniqueId(), true); + } + applyRandomBuffs(p, level, e.getFinalDamage()); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.CRIMSON_SPORE, p.getLocation().add(0, 1.0, 0), 22, 0.28, 0.42, 0.28, 0.02); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.62f, 1.25f); + xp(p, getConfig().xpPerProc); + }); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent dmgEvent) { + if (dmgEvent.getDamager() instanceof Player p) { + withAdaptedPlayer(p, () -> { + if (p.hasPotionEffect(PotionEffectType.ABSORPTION) || p.hasPotionEffect(PotionEffectType.RESISTANCE)) { + getPlayer(p).getData().addStat("tragoul.blood-pact.empowered-kills", 1); + if (lowHealthProcs.getOrDefault(p.getUniqueId(), false) && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_tragoul_pact_all_in")) { + getPlayer(p).getAdvancementHandler().grant("challenge_tragoul_pact_all_in"); + } + } + }); + } + } + } + + private void applyRandomBuffs(Player p, int level, double takenDamage) { + int count = getBuffCount(level); + if (takenDamage >= (getMinTriggerDamage() * 1.6)) { + count++; + } + if (ThreadLocalRandom.current().nextDouble() <= getBonusBuffChance(level)) { + count++; + } + + List pool = new ArrayList<>(List.of(EFFECT_POOL)); + Collections.shuffle(pool); + count = Math.min(count, pool.size()); + + int duration = getEffectDurationTicks(level); + for (int i = 0; i < count; i++) { + PotionEffectType type = pool.get(i); + int amplifier = getEffectAmplifier(type, level); + int d = type == PotionEffectType.ABSORPTION ? Math.max(40, duration - 20) : duration; + if (type == PotionEffectType.SPEED) { + grantSpeedBurst(p, amplifier, d); + continue; + } + p.addPotionEffect(new PotionEffect(type, d, amplifier, false, true, true), true); + } + } + + private void grantSpeedBurst(Player p, int amplifier, int durationTicks) { + if (durationTicks <= 0) { + return; + } + + UUID id = p.getUniqueId(); + long now = System.currentTimeMillis(); + long durationMs = Math.max(50L, durationTicks * 50L); + SpeedBurst burst = speedBursts.get(id); + if (burst != null && burst.expiresAt > now) { + burst.expiresAt += durationMs; + burst.amplifier = Math.max(burst.amplifier, amplifier); + return; + } + + speedBursts.put(id, new SpeedBurst(now + durationMs, amplifier)); + } + + private double getProcChance(int level) { + return Math.min(getConfig().maxProcChance, + Math.max(0, getConfig().procChanceBase + (getLevelPercent(level) * getConfig().procChanceFactor))); + } + + private long getProcCooldownMillis(int level) { + return Math.max(500L, (long) Math.round(getConfig().procCooldownMillisBase - (getLevelPercent(level) * getConfig().procCooldownMillisFactor))); + } + + private int getEffectDurationTicks(int level) { + return Math.max(40, (int) Math.round(getConfig().effectDurationTicksBase + (getLevelPercent(level) * getConfig().effectDurationTicksFactor))); + } + + private int getBuffCount(int level) { + return Math.max(1, (int) Math.round(getConfig().buffCountBase + (getLevelPercent(level) * getConfig().buffCountFactor))); + } + + private double getBonusBuffChance(int level) { + return Math.min(0.9, Math.max(0, getConfig().bonusBuffChanceBase + (getLevelPercent(level) * getConfig().bonusBuffChanceFactor))); + } + + private int getEffectAmplifier(PotionEffectType type, int level) { + double progress = getLevelPercent(level); + if (type == PotionEffectType.ABSORPTION || type == PotionEffectType.RESISTANCE || type == PotionEffectType.REGENERATION) { + return progress >= 0.85 ? 1 : 0; + } + return progress >= 0.7 ? 1 : 0; + } + + private double getMinTriggerDamage() { + return Math.max(1, getConfig().minDamageTriggerHearts * 2D); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + procCooldowns.entrySet().removeIf(i -> i.getValue() <= now); + applySpeedBursts(now); + } + + private void applySpeedBursts(long now) { + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + + withPlayerThread(p, () -> { + UUID id = p.getUniqueId(); + SpeedBurst burst = speedBursts.get(id); + if (burst == null) { + return; + } + + if (burst.expiresAt <= now) { + invalidateSpeedBurst(p, burst, false); + speedBursts.remove(id); + return; + } + + if (!isVelocityEligible(p)) { + invalidateSpeedBurst(p, burst, true); + return; + } + + VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); + if (!input.hasHorizontal()) { + brakeSpeedBurst(p, burst); + return; + } + + applySpeedBurst(p, burst, input); + }); + } + } + + private void applySpeedBurst(Player p, SpeedBurst burst, VelocitySpeed.InputSnapshot input) { + Vector direction = VelocitySpeed.resolveHorizontalDirection(p, input); + if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { + brakeSpeedBurst(p, burst); + return; + } + + double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, + Math.max(0, getConfig().baseHorizontalSpeed * VelocitySpeed.speedAmplifierScalar(burst.amplifier))); + Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); + Vector targetHorizontal = direction.multiply(targetSpeed); + Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); + nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); + VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); + burst.boosting = true; + } + + private void brakeSpeedBurst(Player p, SpeedBurst burst) { + if (!burst.boosting) { + return; + } + + Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); + double stopThreshold = Math.max(0, getConfig().stopThreshold); + if (horizontal.lengthSquared() <= stopThreshold * stopThreshold) { + VelocitySpeed.hardStopHorizontal(p); + burst.boosting = false; + return; + } + + Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); + if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { + VelocitySpeed.hardStopHorizontal(p); + burst.boosting = false; + return; + } + + VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); + } + + private void invalidateSpeedBurst(Player p, SpeedBurst burst, boolean invalidState) { + if (!burst.boosting) { + return; + } + + if (invalidState && getConfig().hardStopOnInvalidState) { + VelocitySpeed.hardStopHorizontal(p); + } + + burst.boosting = false; + } + + private boolean isVelocityEligible(Player p) { + GameMode mode = p.getGameMode(); + if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { + return false; + } + + return !p.isDead() && !p.isFlying() && !p.isGliding() && !p.isSwimming() && p.getVehicle() == null; + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private static class SpeedBurst { + private long expiresAt; + private int amplifier; + private boolean boosting; + + private SpeedBurst(long expiresAt, int amplifier) { + this.expiresAt = expiresAt; + this.amplifier = amplifier; + } + } + + @NoArgsConstructor + @ConfigDescription("Taking at least 2 hearts of damage can trigger temporary beneficial effects.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.62; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Damage Trigger Hearts for the Tragoul Blood Pact adaptation.", impact = "Minimum damage taken in hearts required before the proc roll happens.") + double minDamageTriggerHearts = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Proc Chance Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double procChanceBase = 0.12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Proc Chance Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double procChanceFactor = 0.38; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Proc Chance for the Tragoul Blood Pact adaptation.", impact = "Caps chance at the requested maximum.") + double maxProcChance = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Proc Cooldown Millis Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double procCooldownMillisBase = 18000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Proc Cooldown Millis Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double procCooldownMillisFactor = 12000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effect Duration Ticks Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double effectDurationTicksBase = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Effect Duration Ticks Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double effectDurationTicksFactor = 150; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Buff Count Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double buffCountBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Buff Count Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double buffCountFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Buff Chance Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusBuffChanceBase = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Buff Chance Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusBuffChanceFactor = 0.34; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP Per Proc for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerProc = 24; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for blood pact speed bursts.", impact = "Higher values increase movement speed when a speed burst is active.") + double baseHorizontalSpeed = 0.13; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent runaway momentum.") + double maxHorizontalSpeed = 0.33; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the burst target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") + double accelPerTick = 0.045; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast velocity decays when movement input is released.", impact = "Higher values reduce carry momentum more aggressively.") + double brakePerTick = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny motion longer.") + double stopThreshold = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "If true, burst velocity is force-cleared when entering invalid states.", impact = "Prevents retained speed from skipped state transitions.") + boolean hardStopOnInvalidState = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") + double fallbackInputVelocityThreshold = 0.0008; + + double fallbackInputVelocityThresholdSquared() { + double threshold = Math.max(0, fallbackInputVelocityThreshold); + return threshold * threshold; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulBoneHarvest.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulBoneHarvest.java new file mode 100644 index 000000000..6b9a13244 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulBoneHarvest.java @@ -0,0 +1,423 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.math.VelocitySpeed; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class TragoulBoneHarvest extends SimpleAdaptation { + private static final PotionEffectType[] BONE_EFFECT_POOL = { + PotionEffectType.SPEED, + PotionEffectType.REGENERATION, + PotionEffectType.RESISTANCE, + PotionEffectType.FIRE_RESISTANCE, + PotionEffectType.ABSORPTION, + PotionEffectType.JUMP_BOOST, + PotionEffectType.NIGHT_VISION + }; + + private final Set bloodGlobes = ConcurrentHashMap.newKeySet(); + private final Set boneGlobes = ConcurrentHashMap.newKeySet(); + private final Map speedBursts = new ConcurrentHashMap<>(); + + public TragoulBoneHarvest() { + super("tragoul-bone-harvest"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.bone_harvest.description")); + setDisplayName(Localizer.dLocalize("tragoul.bone_harvest.name")); + setIcon(Material.BONE_BLOCK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_tragoul_bone_500") + .title(Localizer.dLocalize("advancement.challenge_tragoul_bone_500.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_bone_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BONE_BLOCK) + .key("challenge_tragoul_bone_5k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_bone_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_bone_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_bone_500", "tragoul.bone-harvest.orbs-collected", 500, 300); + registerMilestone("challenge_tragoul_bone_5k", "tragoul.bone-harvest.orbs-collected", 5000, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getGlobeChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("tragoul.bone_harvest.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getGlobeLifetimeTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.bone_harvest.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + Player killer = e.getEntity().getKiller(); + if (killer == null) { + return; + } + + withAdaptedPlayer(killer, () -> { + if (!canDamageTarget(killer, e.getEntity())) { + return; + } + + int level = getActiveLevel(killer); + ThreadLocalRandom random = ThreadLocalRandom.current(); + if (random.nextDouble() > getGlobeChance(level)) { + return; + } + + spawnGlobe(killer, e, random.nextBoolean(), level); + }); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityPickupItemEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + UUID id = e.getItem().getUniqueId(); + boolean blood = bloodGlobes.contains(id); + boolean bone = boneGlobes.contains(id); + if (!blood && !bone) { + return; + } + + e.setCancelled(true); + e.getItem().remove(); + bloodGlobes.remove(id); + boneGlobes.remove(id); + applyBuff(p, blood, getLevel(p)); + getPlayer(p).getData().addStat("tragoul.bone-harvest.orbs-collected", 1); + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + speedBursts.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerDeathEvent e) { + speedBursts.remove(e.getEntity().getUniqueId()); + } + + private void spawnGlobe(Player owner, EntityDeathEvent e, boolean blood, int level) { + ItemStack item = new ItemStack(blood ? Material.MAGMA_CREAM : Material.SNOWBALL); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName((blood ? C.RED : C.WHITE) + (blood ? "Blood Globe" : "Bone Globe")); + item.setItemMeta(meta); + } + + Item dropped = e.getEntity().getWorld().dropItemNaturally(e.getEntity().getLocation().add(0, 0.35, 0), item); + dropped.setPickupDelay(10); + if (blood) { + bloodGlobes.add(dropped.getUniqueId()); + } else { + boneGlobes.add(dropped.getUniqueId()); + } + + int life = getGlobeLifetimeTicks(level); + J.runEntity(dropped, () -> { + bloodGlobes.remove(dropped.getUniqueId()); + boneGlobes.remove(dropped.getUniqueId()); + if (dropped.isValid()) { + dropped.remove(); + } + }, life); + xp(owner, getConfig().xpPerGlobeSpawned); + } + + private void applyBuff(Player p, boolean blood, int level) { + if (blood) { + p.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, getConfig().bloodBuffTicks, getConfig().bloodBuffAmplifier, false, true, true), true); + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.7f, 1.4f); + } else { + applyRandomBoneBuffs(p, level); + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_SKELETON_HURT, 0.7f, 1.2f); + } + } + + private void applyRandomBoneBuffs(Player p, int level) { + int buffs = Math.max(1, (int) Math.round(getConfig().boneBuffCountBase + (getLevelPercent(level) * getConfig().boneBuffCountFactor))); + List pool = new ArrayList<>(List.of(BONE_EFFECT_POOL)); + Collections.shuffle(pool); + buffs = Math.min(pool.size(), buffs); + + int duration = getConfig().boneBuffTicks; + int amp = Math.max(0, getConfig().boneBuffAmplifier); + for (int i = 0; i < buffs; i++) { + PotionEffectType type = pool.get(i); + int a = type == PotionEffectType.ABSORPTION && getLevelPercent(level) >= 0.75 ? amp + 1 : amp; + if (type == PotionEffectType.SPEED) { + grantSpeedBurst(p, a, duration); + continue; + } + p.addPotionEffect(new PotionEffect(type, duration, a, false, true, true), true); + } + } + + private void grantSpeedBurst(Player p, int amplifier, int durationTicks) { + if (durationTicks <= 0) { + return; + } + + UUID id = p.getUniqueId(); + long now = System.currentTimeMillis(); + long durationMs = Math.max(50L, durationTicks * 50L); + SpeedBurst burst = speedBursts.get(id); + if (burst != null && burst.expiresAt > now) { + burst.expiresAt += durationMs; + burst.amplifier = Math.max(burst.amplifier, amplifier); + return; + } + + speedBursts.put(id, new SpeedBurst(now + durationMs, amplifier)); + } + + private double getGlobeChance(int level) { + return Math.min(getConfig().maxGlobeChance, getConfig().globeChanceBase + (getLevelPercent(level) * getConfig().globeChanceFactor)); + } + + private int getGlobeLifetimeTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().globeLifetimeTicksBase + (getLevelPercent(level) * getConfig().globeLifetimeTicksFactor))); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + + withPlayerThread(p, () -> { + UUID id = p.getUniqueId(); + SpeedBurst burst = speedBursts.get(id); + if (burst == null) { + return; + } + + if (burst.expiresAt <= now) { + invalidateSpeedBurst(p, burst, false); + speedBursts.remove(id); + return; + } + + if (!isVelocityEligible(p)) { + invalidateSpeedBurst(p, burst, true); + return; + } + + VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); + if (!input.hasHorizontal()) { + brakeSpeedBurst(p, burst); + return; + } + + applySpeedBurst(p, burst, input); + }); + } + } + + private void applySpeedBurst(Player p, SpeedBurst burst, VelocitySpeed.InputSnapshot input) { + Vector direction = VelocitySpeed.resolveHorizontalDirection(p, input); + if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { + brakeSpeedBurst(p, burst); + return; + } + + double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, + Math.max(0, getConfig().baseHorizontalSpeed * VelocitySpeed.speedAmplifierScalar(burst.amplifier))); + Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); + Vector targetHorizontal = direction.multiply(targetSpeed); + Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); + nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); + VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); + burst.boosting = true; + } + + private void brakeSpeedBurst(Player p, SpeedBurst burst) { + if (!burst.boosting) { + return; + } + + Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); + double stopThreshold = Math.max(0, getConfig().stopThreshold); + if (horizontal.lengthSquared() <= stopThreshold * stopThreshold) { + VelocitySpeed.hardStopHorizontal(p); + burst.boosting = false; + return; + } + + Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); + if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { + VelocitySpeed.hardStopHorizontal(p); + burst.boosting = false; + return; + } + + VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); + } + + private void invalidateSpeedBurst(Player p, SpeedBurst burst, boolean invalidState) { + if (!burst.boosting) { + return; + } + + if (invalidState && getConfig().hardStopOnInvalidState) { + VelocitySpeed.hardStopHorizontal(p); + } + + burst.boosting = false; + } + + private boolean isVelocityEligible(Player p) { + GameMode mode = p.getGameMode(); + if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { + return false; + } + + return !p.isDead() && !p.isFlying() && !p.isGliding() && !p.isSwimming() && p.getVehicle() == null; + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private static class SpeedBurst { + private long expiresAt; + private int amplifier; + private boolean boosting; + + private SpeedBurst(long expiresAt, int amplifier) { + this.expiresAt = expiresAt; + this.amplifier = amplifier; + } + } + + @NoArgsConstructor + @ConfigDescription("Kills can spawn temporary blood and bone globes that grant short buffs when picked up.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Globe Chance Base for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double globeChanceBase = 0.16; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Globe Chance Factor for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double globeChanceFactor = 0.42; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Globe Chance for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxGlobeChance = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Globe Lifetime Ticks Base for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double globeLifetimeTicksBase = 120; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Globe Lifetime Ticks Factor for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double globeLifetimeTicksFactor = 220; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Blood Buff Ticks for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int bloodBuffTicks = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Blood Buff Amplifier for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int bloodBuffAmplifier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bone Buff Ticks for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int boneBuffTicks = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bone Buff Amplifier for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int boneBuffAmplifier = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bone Buff Count Base for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double boneBuffCountBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bone Buff Count Factor for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double boneBuffCountFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Globe Spawned for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerGlobeSpawned = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for bone-harvest speed bursts.", impact = "Higher values increase movement speed when a speed burst is active.") + double baseHorizontalSpeed = 0.13; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent runaway momentum.") + double maxHorizontalSpeed = 0.33; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the burst target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") + double accelPerTick = 0.045; + @art.arcane.adapt.util.config.ConfigDoc(value = "How fast velocity decays when movement input is released.", impact = "Higher values reduce carry momentum more aggressively.") + double brakePerTick = 0.08; + @art.arcane.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny motion longer.") + double stopThreshold = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "If true, burst velocity is force-cleared when entering invalid states.", impact = "Prevents retained speed from skipped state transitions.") + boolean hardStopOnInvalidState = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") + double fallbackInputVelocityThreshold = 0.0008; + + double fallbackInputVelocityThresholdSquared() { + double threshold = Math.max(0, fallbackInputVelocityThreshold); + return threshold * threshold; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulCorpseExplosion.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulCorpseExplosion.java new file mode 100644 index 000000000..8934ff09c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulCorpseExplosion.java @@ -0,0 +1,225 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.IAttribute; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.persistence.PersistentDataType; + +public class TragoulCorpseExplosion extends SimpleAdaptation { + private static final NamespacedKey NOVA_KEY = NamespacedKey.fromString("adapt:tragoul_nova_stamp"); + + public TragoulCorpseExplosion() { + super("tragoul-corpse-explosion"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.corpse_explosion.description")); + setDisplayName(Localizer.dLocalize("tragoul.corpse_explosion.name")); + setIcon(Material.WITHER_ROSE); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_ROSE) + .key("challenge_tragoul_corpse_500") + .title(Localizer.dLocalize("advancement.challenge_tragoul_corpse_500.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_corpse_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERRACK) + .key("challenge_tragoul_corpse_5k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_corpse_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_corpse_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_corpse_500", "tragoul.corpse-explosion.mobs-detonated", 500, 400); + registerMilestone("challenge_tragoul_corpse_5k", "tragoul.corpse-explosion.mobs-detonated", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.corpse_explosion.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getRadius(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.corpse_explosion.lore2")); + v.addLore(C.GREEN + "+ " + Form.pc(getVictimHealthFraction(level), 0) + C.GRAY + " " + Localizer.dLocalize("tragoul.corpse_explosion.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + Player killer = e.getEntity().getKiller(); + if (killer == null) { + return; + } + + LivingEntity victim = e.getEntity(); + long now = System.currentTimeMillis(); + Long novaStamp = victim.getPersistentDataContainer().get(NOVA_KEY, PersistentDataType.LONG); + if (novaStamp != null && now - novaStamp < getConfig().chainSuppressionMillis) { + return; + } + + withAdaptedPlayer(killer, () -> detonate(killer, victim, now)); + } + + static void detonateServantKill(TragoulCorpseExplosion nova, Player owner, LivingEntity victim) { + long now = System.currentTimeMillis(); + Long novaStamp = victim.getPersistentDataContainer().get(NOVA_KEY, PersistentDataType.LONG); + if (novaStamp != null && now - novaStamp < nova.getConfig().chainSuppressionMillis) { + return; + } + + nova.withAdaptedPlayer(owner, () -> nova.detonate(owner, victim, now)); + } + + private void detonate(Player credited, LivingEntity victim, long now) { + int level = getActiveLevel(credited); + if (level <= 0 || !canDamageTarget(credited, victim)) { + return; + } + + double radius = getRadius(level); + double damage = getNovaDamage(level, victim); + int hit = 0; + for (Entity entity : victim.getNearbyEntities(radius, radius, radius)) { + if (!(entity instanceof Monster monster) || monster.isDead() || !monster.isValid()) { + continue; + } + + if (!canDamageTarget(credited, monster)) { + continue; + } + + monster.getPersistentDataContainer().set(NOVA_KEY, PersistentDataType.LONG, now); + J.runEntity(monster, () -> { + if (monster.isValid() && !monster.isDead()) { + monster.damage(damage, credited); + } + }); + hit++; + if (hit >= getConfig().maxTargets) { + break; + } + } + + if (hit <= 0) { + return; + } + + if (areParticlesEnabled()) { + Particle.DustOptions dust = new Particle.DustOptions(Color.fromRGB(150, 12, 12), 1.4f); + victim.getWorld().spawnParticle(Particle.DUST, victim.getLocation().add(0, 0.9, 0), 70, radius * 0.4, 0.6, radius * 0.4, 0.01, dust); + victim.getWorld().spawnParticle(Particle.DAMAGE_INDICATOR, victim.getLocation().add(0, 1, 0), 12, 0.5, 0.5, 0.5, 0.04); + } + SoundPlayer.of(victim.getWorld()).play(victim.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.55f, 1.35f); + getPlayer(credited).getData().addStat("tragoul.corpse-explosion.mobs-detonated", hit); + xp(credited, hit * getConfig().xpPerMobHit); + } + + private double getRadius(int level) { + return Math.max(1, getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor)); + } + + private double getVictimHealthFraction(int level) { + return Math.max(0, getConfig().victimHealthFractionBase + (getLevelPercent(level) * getConfig().victimHealthFractionFactor)); + } + + private double getNovaDamage(int level, LivingEntity victim) { + IAttribute attribute = Version.get().getAttribute(victim, Attributes.GENERIC_MAX_HEALTH); + double victimMaxHealth = attribute == null ? 20D : attribute.getValue(); + double damage = getConfig().baseDamage + (victimMaxHealth * getVictimHealthFraction(level)); + return Math.min(getConfig().maxDamage, damage); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Mobs you kill detonate in a blood nova that damages nearby hostile mobs.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base nova radius before level scaling.", impact = "Higher values damage hostile mobs further from the corpse.") + double radiusBase = 3.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional nova radius granted at max level.", impact = "Higher values increase the level-scaled radius growth.") + double radiusFactor = 3.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Flat nova damage applied to every hostile mob hit.", impact = "Higher values increase the guaranteed damage portion of the nova.") + double baseDamage = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fraction of the victim's max health added to nova damage before level scaling.", impact = "Higher values make tanky kills detonate harder.") + double victimHealthFractionBase = 0.10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional victim max-health fraction granted at max level.", impact = "Higher values increase the level-scaled damage growth.") + double victimHealthFractionFactor = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on nova damage per mob.", impact = "Prevents extreme bosses from producing one-shot novas.") + double maxDamage = 16.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum hostile mobs damaged per nova.", impact = "Caps per-kill work to protect server performance.") + int maxTargets = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Window in milliseconds during which a nova-damaged mob cannot trigger another nova.", impact = "Prevents chain-reaction detonations.") + long chainSuppressionMillis = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per hostile mob hit by a nova.", impact = "Higher values accelerate skill progression from this adaptation.") + double xpPerMobHit = 6; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulCurseOfFrailty.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulCurseOfFrailty.java new file mode 100644 index 000000000..a7cfb5b8f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulCurseOfFrailty.java @@ -0,0 +1,186 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TragoulCurseOfFrailty extends SimpleAdaptation { + private final Map attackerCooldowns = new ConcurrentHashMap<>(); + + public TragoulCurseOfFrailty() { + super("tragoul-curse-of-frailty"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.curse_of_frailty.description")); + setDisplayName(Localizer.dLocalize("tragoul.curse_of_frailty.name")); + setIcon(Material.FERMENTED_SPIDER_EYE); + setInterval(5000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FERMENTED_SPIDER_EYE) + .key("challenge_tragoul_frailty_100") + .title(Localizer.dLocalize("advancement.challenge_tragoul_frailty_100.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_frailty_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_tragoul_frailty_1k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_frailty_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_frailty_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_frailty_100", "tragoul.curse-of-frailty.curses-applied", 100, 400); + registerMilestone("challenge_tragoul_frailty_1k", "tragoul.curse-of-frailty.curses-applied", 1000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.curse_of_frailty.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getCurseDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.curse_of_frailty.lore2")); + if (getLevelPercent(level) >= getConfig().slownessUnlockPercent) { + v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.curse_of_frailty.lore3")); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + LivingEntity attacker = null; + if (e.getDamager() instanceof LivingEntity living) { + attacker = living; + } else if (e.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof LivingEntity shooter) { + attacker = shooter; + } + + if (attacker == null || attacker == p) { + return; + } + + long now = System.currentTimeMillis(); + Long until = attackerCooldowns.get(attacker.getUniqueId()); + if (until != null && until > now) { + return; + } + + LivingEntity target = attacker; + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0 || !canDamageTarget(p, target)) { + return; + } + + attackerCooldowns.put(target.getUniqueId(), now + getConfig().perAttackerCooldownMillis); + int duration = getCurseDurationTicks(level); + int weaknessAmplifier = getLevelPercent(level) >= 0.8 ? 1 : 0; + target.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS, duration, weaknessAmplifier, true, true, true), true); + if (getLevelPercent(level) >= getConfig().slownessUnlockPercent) { + target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, duration, getConfig().slownessAmplifier, true, true, true), true); + } + + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particle.WARPED_SPORE, target.getLocation().add(0, 1.0, 0), 20, 0.3, 0.5, 0.3, 0.02); + } + SoundPlayer.of(target.getWorld()).play(target.getLocation(), Sound.ENTITY_ELDER_GUARDIAN_CURSE, 0.35f, 1.7f); + getPlayer(p).getData().addStat("tragoul.curse-of-frailty.curses-applied", 1); + xp(p, getConfig().xpPerCurse); + }); + } + + private int getCurseDurationTicks(int level) { + return Math.max(40, (int) Math.round(getConfig().curseDurationTicksBase + (getLevelPercent(level) * getConfig().curseDurationTicksFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + attackerCooldowns.entrySet().removeIf(i -> i.getValue() <= now); + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Enemies that strike you are cursed with weakness, and slowness at higher levels.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Curse duration in ticks before level scaling.", impact = "Higher values keep attackers weakened longer.") + double curseDurationTicksBase = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional curse duration ticks granted at max level.", impact = "Higher values increase level-scaled duration growth.") + double curseDurationTicksFactor = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Level progress required before slowness is added to the curse.", impact = "Lower values unlock the slowness component earlier.") + double slownessUnlockPercent = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Amplifier of the slowness component once unlocked.", impact = "Higher values slow cursed attackers more.") + int slownessAmplifier = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Per-attacker cooldown in milliseconds between curses.", impact = "Higher values stop one attacker from being re-cursed in quick succession.") + long perAttackerCooldownMillis = 4000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per curse applied.", impact = "Higher values accelerate skill progression from this adaptation.") + double xpPerCurse = 5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulDeathSense.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulDeathSense.java new file mode 100644 index 000000000..6b706804f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulDeathSense.java @@ -0,0 +1,177 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.IAttribute; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class TragoulDeathSense extends SimpleAdaptation { + public TragoulDeathSense() { + super("tragoul-death-sense"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.death_sense.description")); + setDisplayName(Localizer.dLocalize("tragoul.death_sense.name")); + setIcon(Material.SPIDER_EYE); + setInterval(1250); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPIDER_EYE) + .key("challenge_tragoul_death_sense_1k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_death_sense_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_death_sense_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_tragoul_death_sense_1k", "tragoul.death-sense.prey-sensed", 1000, 600); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.death_sense.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getHealthThreshold(level), 0) + C.GRAY + " " + Localizer.dLocalize("tragoul.death_sense.lore2")); + v.addLore(C.YELLOW + "* " + Form.f(getRadius(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.death_sense.lore3")); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + + withPlayerThread(p, () -> { + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + double radius = getRadius(level); + double threshold = getHealthThreshold(level); + int glowTicks = getConfig().glowTicks; + int sensed = 0; + for (Entity entity : p.getNearbyEntities(radius, radius, radius)) { + if (!(entity instanceof Monster monster) || monster.isDead() || !monster.isValid()) { + continue; + } + + if (monster.hasPotionEffect(PotionEffectType.GLOWING)) { + continue; + } + + if (isProtectedFriendly(p, monster)) { + continue; + } + + IAttribute attribute = Version.get().getAttribute(monster, Attributes.GENERIC_MAX_HEALTH); + double maxHealth = attribute == null ? 20D : attribute.getValue(); + if (maxHealth <= 0 || monster.getHealth() / maxHealth > threshold) { + continue; + } + + J.runEntity(monster, () -> { + if (monster.isValid() && !monster.isDead()) { + monster.addPotionEffect(new PotionEffect(PotionEffectType.GLOWING, glowTicks, 0, true, false, false), true); + } + }); + sensed++; + if (sensed >= getConfig().maxMarksPerPulse) { + break; + } + } + + if (sensed > 0) { + adaptPlayer.getData().addStat("tragoul.death-sense.prey-sensed", sensed); + } + }); + } + } + + private double getRadius(int level) { + return Math.max(2, getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor)); + } + + private double getHealthThreshold(int level) { + return Math.min(getConfig().maxHealthThreshold, + Math.max(0, getConfig().healthThresholdBase + (getLevelPercent(level) * getConfig().healthThresholdFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Weakened hostile mobs near you briefly glow so you can sense dying prey through walls.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scan radius before level scaling.", impact = "Higher values sense prey further away but cost more scan work.") + double radiusBase = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional scan radius granted at max level.", impact = "Higher values increase level-scaled radius growth.") + double radiusFactor = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Health fraction below which hostile mobs are sensed, before level scaling.", impact = "Higher values mark healthier mobs as weakened prey.") + double healthThresholdBase = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional health fraction granted at max level.", impact = "Higher values increase level-scaled threshold growth.") + double healthThresholdFactor = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on the sensed health fraction.", impact = "Prevents marking near-full-health mobs at high levels.") + double maxHealthThreshold = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of the glow applied to sensed mobs.", impact = "Higher values keep prey highlighted longer between pulses.") + int glowTicks = 35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum mobs marked per scan pulse.", impact = "Caps per-pulse work to protect server performance.") + int maxMarksPerPulse = 8; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulGlobe.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulGlobe.java new file mode 100644 index 000000000..3c5432bbe --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulGlobe.java @@ -0,0 +1,181 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TragoulGlobe extends SimpleAdaptation { + private final Map cooldowns; + + public TragoulGlobe() { + super("tragoul-globe"); + registerConfiguration(TragoulGlobe.Config.class); + setDescription(Localizer.dLocalize("tragoul.globe.description")); + setDisplayName(Localizer.dLocalize("tragoul.globe.name")); + setIcon(Material.CRYING_OBSIDIAN); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLASS) + .key("challenge_tragoul_globe_1k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_globe_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_globe_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_tragoul_globe_1k", "tragoul.globe.mobs-shared-with", 1000, 400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLASS) + .key("challenge_tragoul_globe_5") + .title(Localizer.dLocalize("advancement.challenge_tragoul_globe_5.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_globe_5.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.globe.lore1")); + v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.globe.lore2") + ((getConfig().rangePerLevel * level) + getConfig().initalRange)); + v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.globe.lore3") + (getConfig().bonusDamagePerLevel * level)); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player p)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0 || !canDamageTarget(p, e.getEntity())) { + return; + } + + Long cooldownTime = cooldowns.get(p.getUniqueId()); + if (cooldownTime != null && cooldownTime + (1000 * getConfig().cooldown) > System.currentTimeMillis()) { + return; + } + + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + double range = (getConfig().rangePerLevel * level) + getConfig().initalRange; + + int entitiesCount = 0; + for (Entity entity : p.getNearbyEntities(range, range, range)) { + if (entity instanceof LivingEntity && !entity.equals(p)) { + entitiesCount++; + } + } + + if (entitiesCount <= 1) { + return; + } + + double damagePerEntity = e.getDamage() / entitiesCount + (getConfig().bonusDamagePerLevel * level); + e.setDamage(damagePerEntity); + + int mobsSharedWith = 0; + for (Entity entity : p.getNearbyEntities(range, range, range)) { + if (entity instanceof LivingEntity && !entity.equals(p) && canDamageTarget(p, entity)) { + ((LivingEntity) entity).damage(damagePerEntity, p); + mobsSharedWith++; + } + } + + getPlayer(p).getData().addStat("tragoul.globe.mobs-shared-with", mobsSharedWith); + if (mobsSharedWith >= 5 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_tragoul_globe_5")) { + getPlayer(p).getAdvancementHandler().grant("challenge_tragoul_globe_5"); + } + + if (areParticlesEnabled()) { + J.runEntity(p, () -> vfxFastSphere(p.getLocation(), range, Color.BLACK, 400)); + } + }); + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + + @NoArgsConstructor + @ConfigDescription("Spread your damage among all nearby enemies.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Tragoul Globe adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Tragoul Globe adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldown = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Range Per Level for the Tragoul Globe adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double rangePerLevel = 3.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Inital Range for the Tragoul Globe adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double initalRange = 5.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Bonus Damage Per Level for the Tragoul Globe adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bonusDamagePerLevel = 1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulHealing.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulHealing.java new file mode 100644 index 000000000..7f48c2067 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulHealing.java @@ -0,0 +1,174 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TragoulHealing extends SimpleAdaptation { + private final Map cooldowns; + private final Map healingWindow; + + public TragoulHealing() { + super("tragoul-healing"); + registerConfiguration(TragoulHealing.Config.class); + setDescription(Localizer.dLocalize("tragoul.healing.description")); + setDisplayName(Localizer.dLocalize("tragoul.healing.name")); + setIcon(Material.GLISTERING_MELON_SLICE); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new ConcurrentHashMap<>(); + healingWindow = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.REDSTONE) + .key("challenge_tragoul_healing_500") + .title(Localizer.dLocalize("advancement.challenge_tragoul_healing_500.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_healing_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.RED_DYE) + .key("challenge_tragoul_healing_10k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_healing_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_healing_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_healing_500", "tragoul.healing.health-stolen", 500, 400); + registerMilestone("challenge_tragoul_healing_10k", "tragoul.healing.health-stolen", 10000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.healing.lore1")); + v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.healing.lore2")); + v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.healing.lore3") + (getConfig().minHealPercent + (getConfig().maxHealPercent - getConfig().minHealPercent) * (level - 1) / (getConfig().maxLevel - 1)) + "%"); + } + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p) { + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0 || !canDamageTarget(p, e.getEntity())) { + return; + } + + if (isOnCooldown(p)) { + return; + } + + if (!healingWindow.containsKey(p.getUniqueId())) { + Adapt.verbose("Starting healing window for " + p.getName()); + startHealingWindow(p); + } + + if (areParticlesEnabled()) { + vfxParticleLine(p.getLocation(), e.getEntity().getLocation(), 25, Particle.WHITE_ASH); + } + + double healPercentage = getConfig().minHealPercent + (getConfig().maxHealPercent - getConfig().minHealPercent) * (level - 1) / (getConfig().maxLevel - 1); + double healAmount = e.getDamage() * healPercentage; + Adapt.verbose("Healing " + p.getName() + " for " + healAmount + " (" + healPercentage * 100 + "% of " + e.getDamage() + " damage)"); + art.arcane.adapt.api.version.IAttribute attribute = Version.get().getAttribute(p, Attributes.GENERIC_MAX_HEALTH); + p.setHealth(Math.min(attribute == null ? p.getHealth() : attribute.getValue(), p.getHealth() + healAmount)); + getPlayer(p).getData().addStat("tragoul.healing.health-stolen", (int) healAmount); + }); + } + } + + private boolean isOnCooldown(Player p) { + Long cooldown = cooldowns.get(p.getUniqueId()); + return cooldown != null && cooldown > System.currentTimeMillis(); + } + + private void startHealingWindow(Player p) { + long currentTime = System.currentTimeMillis(); + healingWindow.put(p.getUniqueId(), currentTime + getConfig().windowDuration); + J.runEntity(p, () -> { + healingWindow.remove(p.getUniqueId()); + cooldowns.put(p.getUniqueId(), currentTime + getConfig().windowDuration + getConfig().cooldownDuration); + }, getConfig().windowDuration / 50); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Regain health based on the damage you deal.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Tragoul Healing adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Min Heal Percent for the Tragoul Healing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minHealPercent = 0.10; // 0.10% + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Heal Percent for the Tragoul Healing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxHealPercent = 0.45; // 0.45% + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Duration for the Tragoul Healing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int cooldownDuration = 1000; // 1 second + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Window Duration for the Tragoul Healing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int windowDuration = 3000; // 3 seconds + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulLance.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulLance.java new file mode 100644 index 000000000..926088abd --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulLance.java @@ -0,0 +1,188 @@ +package art.arcane.adapt.content.adaptation.tragoul;/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TragoulLance extends SimpleAdaptation { + private final Map cooldowns; + + public TragoulLance() { + super("tragoul-lance"); + registerConfiguration(TragoulLance.Config.class); + setDescription(Localizer.dLocalize("tragoul.lance.description")); + setDisplayName(Localizer.dLocalize("tragoul.lance.name")); + setIcon(Material.TRIDENT); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_tragoul_lance_200") + .title(Localizer.dLocalize("advancement.challenge_tragoul_lance_200.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_lance_200.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_tragoul_lance_kills_100") + .title(Localizer.dLocalize("advancement.challenge_tragoul_lance_kills_100.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_lance_kills_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_tragoul_lance_200", "tragoul.lance.lances-spawned", 200, 400); + registerMilestone("challenge_tragoul_lance_kills_100", "tragoul.lance.lance-kills", 100, 1000); + } + + + @EventHandler(priority = EventPriority.LOWEST) + public void onEntityDeath(EntityDeathEvent event) { + if (event.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p) { + withAdaptedPlayer(p, () -> { + int level = getActiveLevel(p); + if (level <= 0 || !canDamageTarget(p, event.getEntity())) { + return; + } + + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + 5000 > System.currentTimeMillis()) { + return; + } + + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + double baseSeekerRange = 5 + 4 * level; + double damageDealt = e.getDamage(); + double seekerDamage = getConfig().seekerDamageMultiplier * damageDealt; + + triggerSeeker(p, event.getEntity(), seekerDamage, level, baseSeekerRange); + getPlayer(p).getData().addStat("tragoul.lance.lance-kills", 1); + }); + } + } + } + + private void triggerSeeker(Player p, Entity origin, double damage, int remainingSeekers, double range) { + if (remainingSeekers <= 0) { + return; + } + + LivingEntity nearest = null; + double minDistance = range; + + for (Entity e : origin.getNearbyEntities(range, range, range)) { + if (e instanceof LivingEntity le && le != p) { + double distance = origin.getLocation().distance(le.getLocation()); + if (distance < minDistance) { + nearest = le; + minDistance = distance; + } + } + } + + if (nearest != null) { + getPlayer(p).getData().addStat("tragoul.lance.lances-spawned", 1); + vfxMovingSphere(origin.getLocation(), nearest.getLocation(), getConfig().seekerDelay, Color.MAROON, 0.25, 4); + double seekerDamage = getConfig().seekerDamageMultiplier * damage; + double selfDamage = getConfig().selfDamageMultiplier * seekerDamage; + Adapt.verbose("Seeker damage: " + seekerDamage + " Self damage: " + selfDamage); + + p.damage(selfDamage, p); + + LivingEntity finalNearest = nearest; + J.runEntity(finalNearest, () -> { + double remainingHealth = finalNearest.getHealth() - damage; + finalNearest.damage(damage, p); + if (remainingHealth <= 0) { + triggerSeeker(p, finalNearest, damage * 0.5, remainingSeekers - 1, range); + } + }, getConfig().seekerDelay); + } + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.lance.lore1")); + v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.lance.lore2")); + v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.lance.lore3") + level); + } + + @NoArgsConstructor + @ConfigDescription("Killing an enemy spawns a lance that seeks and damages a nearby enemy.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Seeker Delay for the Tragoul Lance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int seekerDelay = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Seeker Damage Multiplier for the Tragoul Lance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double seekerDamageMultiplier = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Self Damage Multiplier for the Tragoul Lance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double selfDamageMultiplier = 0.5; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulLastRites.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulLastRites.java new file mode 100644 index 000000000..ae83bb573 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulLastRites.java @@ -0,0 +1,195 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TragoulLastRites extends SimpleAdaptation { + private final Map cooldowns = new ConcurrentHashMap<>(); + + public TragoulLastRites() { + super("tragoul-last-rites"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.last_rites.description")); + setDisplayName(Localizer.dLocalize("tragoul.last_rites.name")); + setIcon(Material.TOTEM_OF_UNDYING); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TOTEM_OF_UNDYING) + .key("challenge_tragoul_last_rites_5") + .title(Localizer.dLocalize("advancement.challenge_tragoul_last_rites_5.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_last_rites_5.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SOUL_TORCH) + .key("challenge_tragoul_last_rites_50") + .title(Localizer.dLocalize("advancement.challenge_tragoul_last_rites_50.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_last_rites_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_last_rites_5", "tragoul.last-rites.deaths-defied", 5, 500); + registerMilestone("challenge_tragoul_last_rites_50", "tragoul.last-rites.deaths-defied", 50, 2000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.last_rites.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getConfig().spiritDurationTicks * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.last_rites.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration((double) getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.last_rites.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + if (e.getFinalDamage() < p.getHealth()) { + return; + } + + long now = System.currentTimeMillis(); + Long until = cooldowns.get(p.getUniqueId()); + if (until != null && until > now) { + return; + } + + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0 || p.isDead()) { + return; + } + + cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); + e.setCancelled(true); + p.setHealth(1.0); + + int spiritTicks = getConfig().spiritDurationTicks; + p.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, spiritTicks, 0, true, false, true), true); + p.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, spiritTicks, getConfig().resistanceAmplifier, true, false, true), true); + + double radius = getConfig().targetClearRadius; + for (Entity entity : p.getNearbyEntities(radius, radius, radius)) { + if (!(entity instanceof Mob mob) || mob.getTarget() != p) { + continue; + } + + J.runEntity(mob, () -> { + if (mob.isValid() && mob.getTarget() == p) { + mob.setTarget(null); + } + }); + } + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.SCULK_SOUL, p.getLocation().add(0, 1.0, 0), 30, 0.4, 0.7, 0.4, 0.03); + p.getWorld().spawnParticle(Particle.SOUL, p.getLocation().add(0, 0.6, 0), 18, 0.35, 0.5, 0.35, 0.02); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ITEM_TOTEM_USE, 0.8f, 1.5f); + getPlayer(p).getData().addStat("tragoul.last-rites.deaths-defied", 1); + xp(p, getConfig().xpPerSave); + }); + } + + private long getCooldownMillis(int level) { + return Math.max(30000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("A killing blow leaves you at 1 HP as a fleeting spirit instead of dying.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.85; + @art.arcane.adapt.util.config.ConfigDoc(value = "Duration in ticks of the spirit state after defying death.", impact = "Higher values keep the protection window open longer.") + int spiritDurationTicks = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Amplifier of the resistance effect during the spirit state.", impact = "Higher values reduce more damage while in spirit form.") + int resistanceAmplifier = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Radius in which hostile mob targets locked onto you are cleared.", impact = "Higher values shake off pursuers from further away.") + double targetClearRadius = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown in milliseconds before level scaling.", impact = "Higher values make death defiance rarer.") + double cooldownMillisBase = 600000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown reduction in milliseconds granted at max level.", impact = "Higher values let high levels defy death more often.") + double cooldownMillisFactor = 300000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per death defied.", impact = "Higher values accelerate skill progression from this adaptation.") + double xpPerSave = 120; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulMarrowArmor.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulMarrowArmor.java new file mode 100644 index 000000000..020d1b116 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulMarrowArmor.java @@ -0,0 +1,187 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TragoulMarrowArmor extends SimpleAdaptation { + private final Map cooldowns = new ConcurrentHashMap<>(); + + public TragoulMarrowArmor() { + super("tragoul-marrow-armor"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.marrow_armor.description")); + setDisplayName(Localizer.dLocalize("tragoul.marrow_armor.name")); + setIcon(Material.BONE_MEAL); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE_MEAL) + .key("challenge_tragoul_marrow_500") + .title(Localizer.dLocalize("advancement.challenge_tragoul_marrow_500.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_marrow_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BONE_BLOCK) + .key("challenge_tragoul_marrow_5k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_marrow_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_marrow_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_marrow_500", "tragoul.marrow-armor.damage-absorbed", 500, 400); + registerMilestone("challenge_tragoul_marrow_5k", "tragoul.marrow-armor.damage-absorbed", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.marrow_armor.lore1")); + v.addLore(C.GREEN + "+ " + Form.pc(getAbsorbPercent(level), 0) + C.GRAY + " " + Localizer.dLocalize("tragoul.marrow_armor.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration((double) getInternalCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.marrow_armor.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + cooldowns.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + + if (e.getFinalDamage() < getConfig().minDamageToTrigger) { + return; + } + + long now = System.currentTimeMillis(); + Long until = cooldowns.get(p.getUniqueId()); + if (until != null && until > now) { + return; + } + + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + if (!p.getInventory().containsAtLeast(new ItemStack(Material.BONE), 1)) { + return; + } + + cooldowns.put(p.getUniqueId(), now + getInternalCooldownMillis(level)); + p.getInventory().removeItem(new ItemStack(Material.BONE, 1)); + double absorbed = e.getDamage() * getAbsorbPercent(level); + e.setDamage(Math.max(0, e.getDamage() - absorbed)); + + if (areParticlesEnabled()) { + Particle.DustOptions dust = new Particle.DustOptions(Color.fromRGB(235, 230, 210), 1.1f); + p.getWorld().spawnParticle(Particle.DUST, p.getLocation().add(0, 1.0, 0), 18, 0.3, 0.45, 0.3, 0.01, dust); + } + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_BONE_BLOCK_BREAK, 0.8f, 1.1f); + getPlayer(p).getData().addStat("tragoul.marrow-armor.damage-absorbed", absorbed); + xp(p, getConfig().xpPerAbsorb); + }); + } + + private double getAbsorbPercent(int level) { + return Math.min(getConfig().maxAbsorbPercent, + Math.max(0, getConfig().absorbPercentBase + (getLevelPercent(level) * getConfig().absorbPercentFactor))); + } + + private long getInternalCooldownMillis(int level) { + return Math.max(500L, (long) Math.round(getConfig().internalCooldownMillisBase - (getLevelPercent(level) * getConfig().internalCooldownMillisFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Consume a bone from your inventory to absorb part of incoming damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Minimum final damage required before a bone is consumed.", impact = "Higher values ignore chip damage and save bones for real hits.") + double minDamageToTrigger = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fraction of the hit absorbed before level scaling.", impact = "Higher values absorb more damage per bone.") + double absorbPercentBase = 0.20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional absorbed fraction granted at max level.", impact = "Higher values increase level-scaled absorption growth.") + double absorbPercentFactor = 0.30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hard cap on the absorbed fraction of a hit.", impact = "Prevents full damage immunity at high levels.") + double maxAbsorbPercent = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Internal cooldown in milliseconds before level scaling.", impact = "Higher values stop damage bursts from draining the bone supply.") + double internalCooldownMillisBase = 4000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown reduction in milliseconds granted at max level.", impact = "Higher values let high levels absorb hits more often.") + double internalCooldownMillisFactor = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per absorbed hit.", impact = "Higher values accelerate skill progression from this adaptation.") + double xpPerAbsorb = 8; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulPlagueBearer.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulPlagueBearer.java new file mode 100644 index 000000000..f55c5671b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulPlagueBearer.java @@ -0,0 +1,278 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.UUID; + +public class TragoulPlagueBearer extends SimpleAdaptation { + private static final NamespacedKey PLAGUE_OWNER_KEY = NamespacedKey.fromString("adapt:tragoul_plague_owner"); + private static final NamespacedKey PLAGUE_GENERATION_KEY = NamespacedKey.fromString("adapt:tragoul_plague_generation"); + private static final NamespacedKey PLAGUE_STAMP_KEY = NamespacedKey.fromString("adapt:tragoul_plague_stamp"); + + public TragoulPlagueBearer() { + super("tragoul-plague-bearer"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.plague_bearer.description")); + setDisplayName(Localizer.dLocalize("tragoul.plague_bearer.name")); + setIcon(Material.POISONOUS_POTATO); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.POISONOUS_POTATO) + .key("challenge_tragoul_plague_100") + .title(Localizer.dLocalize("advancement.challenge_tragoul_plague_100.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_plague_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SPIDER_EYE) + .key("challenge_tragoul_plague_1k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_plague_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_plague_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_plague_100", "tragoul.plague-bearer.mobs-infected", 100, 400); + registerMilestone("challenge_tragoul_plague_1k", "tragoul.plague-bearer.mobs-infected", 1000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.plague_bearer.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getSpreadRadius(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.plague_bearer.lore2")); + v.addLore(C.GREEN + "+ " + Form.duration(getSpreadDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.plague_bearer.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Monster monster)) { + return; + } + + Player p = null; + if (e.getDamager() instanceof Player player) { + p = player; + } else if (e.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof Player shooter) { + p = shooter; + } + + if (p == null) { + return; + } + + Player owner = p; + withAdaptedPlayer(owner, e, () -> { + if (getActiveLevel(owner) <= 0 || isProtectedFriendly(owner, monster)) { + return; + } + + UUID ownerId = owner.getUniqueId(); + if (markIfAfflicted(monster, ownerId)) { + return; + } + + J.runEntity(monster, () -> { + if (monster.isValid() && !monster.isDead()) { + markIfAfflicted(monster, ownerId); + } + }, 2); + }); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + LivingEntity victim = e.getEntity(); + PersistentDataContainer pdc = victim.getPersistentDataContainer(); + String ownerRaw = pdc.get(PLAGUE_OWNER_KEY, PersistentDataType.STRING); + if (ownerRaw == null) { + return; + } + + long now = System.currentTimeMillis(); + Long stamp = pdc.get(PLAGUE_STAMP_KEY, PersistentDataType.LONG); + if (stamp == null || now - stamp > getConfig().afflictionFreshnessMillis) { + return; + } + + boolean withered = victim.hasPotionEffect(PotionEffectType.WITHER); + if (!withered && !victim.hasPotionEffect(PotionEffectType.POISON)) { + return; + } + + int generation = pdc.getOrDefault(PLAGUE_GENERATION_KEY, PersistentDataType.INTEGER, 0); + if (generation >= getConfig().maxGenerations) { + return; + } + + Player owner = Bukkit.getPlayer(UUID.fromString(ownerRaw)); + if (owner == null || !owner.isOnline()) { + return; + } + + withAdaptedPlayer(owner, () -> { + int level = getActiveLevel(owner); + if (level <= 0) { + return; + } + + double radius = getSpreadRadius(level); + int durationTicks = getSpreadDurationTicks(level); + PotionEffectType effect = withered ? PotionEffectType.WITHER : PotionEffectType.POISON; + int spread = 0; + for (Entity entity : victim.getNearbyEntities(radius, radius, radius)) { + if (!(entity instanceof Monster monster) || monster.isDead() || !monster.isValid()) { + continue; + } + + if (!canDamageTarget(owner, monster)) { + continue; + } + + PersistentDataContainer targetPdc = monster.getPersistentDataContainer(); + targetPdc.set(PLAGUE_OWNER_KEY, PersistentDataType.STRING, ownerRaw); + targetPdc.set(PLAGUE_GENERATION_KEY, PersistentDataType.INTEGER, generation + 1); + targetPdc.set(PLAGUE_STAMP_KEY, PersistentDataType.LONG, now); + J.runEntity(monster, () -> { + if (monster.isValid() && !monster.isDead()) { + monster.addPotionEffect(new PotionEffect(effect, durationTicks, 0, true, true, true), true); + } + }); + spread++; + if (spread >= getConfig().maxSpreadTargets) { + break; + } + } + + if (spread <= 0) { + return; + } + + if (areParticlesEnabled()) { + victim.getWorld().spawnParticle(Particle.SMOKE, victim.getLocation().add(0, 0.8, 0), 24, radius * 0.35, 0.5, radius * 0.35, 0.01); + } + SoundPlayer.of(victim.getWorld()).play(victim.getLocation(), Sound.ENTITY_ZOMBIE_INFECT, 0.6f, 1.4f); + getPlayer(owner).getData().addStat("tragoul.plague-bearer.mobs-infected", spread); + xp(owner, spread * getConfig().xpPerInfection); + }); + } + + private boolean markIfAfflicted(Monster monster, UUID ownerId) { + if (!monster.hasPotionEffect(PotionEffectType.POISON) && !monster.hasPotionEffect(PotionEffectType.WITHER)) { + return false; + } + + PersistentDataContainer pdc = monster.getPersistentDataContainer(); + pdc.set(PLAGUE_OWNER_KEY, PersistentDataType.STRING, ownerId.toString()); + pdc.set(PLAGUE_STAMP_KEY, PersistentDataType.LONG, System.currentTimeMillis()); + if (!pdc.has(PLAGUE_GENERATION_KEY, PersistentDataType.INTEGER)) { + pdc.set(PLAGUE_GENERATION_KEY, PersistentDataType.INTEGER, 0); + } + return true; + } + + private double getSpreadRadius(int level) { + return Math.max(1, getConfig().spreadRadiusBase + (getLevelPercent(level) * getConfig().spreadRadiusFactor)); + } + + private int getSpreadDurationTicks(int level) { + return Math.max(40, (int) Math.round(getConfig().spreadDurationTicksBase + (getLevelPercent(level) * getConfig().spreadDurationTicksFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Mobs that die poisoned or withered by you spread the affliction to nearby hostile mobs.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Spread radius before level scaling.", impact = "Higher values infect hostile mobs further from the corpse.") + double spreadRadiusBase = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional spread radius granted at max level.", impact = "Higher values increase level-scaled radius growth.") + double spreadRadiusFactor = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Spread effect duration in ticks before level scaling.", impact = "Higher values keep infected mobs afflicted longer.") + double spreadDurationTicksBase = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional spread effect duration ticks granted at max level.", impact = "Higher values increase level-scaled duration growth.") + double spreadDurationTicksFactor = 120; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum plague generations a single affliction can chain through.", impact = "Caps chain length to prevent infinite plague cascades.") + int maxGenerations = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum hostile mobs infected per death.", impact = "Caps per-death work to protect server performance.") + int maxSpreadTargets = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Window in milliseconds during which an affliction mark is considered fresh at death.", impact = "Higher values let older poisons still spread on death.") + long afflictionFreshnessMillis = 15000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per mob infected by the spread.", impact = "Higher values accelerate skill progression from this adaptation.") + double xpPerInfection = 6; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulSkeletalServant.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulSkeletalServant.java new file mode 100644 index 000000000..f664a714c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulSkeletalServant.java @@ -0,0 +1,932 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.IAttribute; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Monster; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Skeleton; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityTargetLivingEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ThreadLocalRandom; + +public class TragoulSkeletalServant extends SimpleAdaptation { + private static final NamespacedKey SERVANT_KEY = NamespacedKey.fromString("adapt:tragoul_servant_owner"); + private static final NamespacedKey PLAGUE_OWNER_KEY = NamespacedKey.fromString("adapt:tragoul_plague_owner"); + private static final NamespacedKey PLAGUE_GENERATION_KEY = NamespacedKey.fromString("adapt:tragoul_plague_generation"); + private static final NamespacedKey PLAGUE_STAMP_KEY = NamespacedKey.fromString("adapt:tragoul_plague_stamp"); + + private static final Material[][] HELMETS = { + {Material.LEATHER_HELMET, Material.CHAINMAIL_HELMET}, + {Material.CHAINMAIL_HELMET, Material.IRON_HELMET}, + {Material.IRON_HELMET, Material.DIAMOND_HELMET} + }; + private static final Material[][] CHESTPLATES = { + {Material.LEATHER_CHESTPLATE, Material.CHAINMAIL_CHESTPLATE}, + {Material.CHAINMAIL_CHESTPLATE, Material.IRON_CHESTPLATE}, + {Material.IRON_CHESTPLATE, Material.DIAMOND_CHESTPLATE} + }; + private static final Material[][] LEGGINGS = { + {Material.LEATHER_LEGGINGS, Material.CHAINMAIL_LEGGINGS}, + {Material.CHAINMAIL_LEGGINGS, Material.IRON_LEGGINGS}, + {Material.IRON_LEGGINGS, Material.DIAMOND_LEGGINGS} + }; + private static final Material[][] BOOTS = { + {Material.LEATHER_BOOTS, Material.CHAINMAIL_BOOTS}, + {Material.CHAINMAIL_BOOTS, Material.IRON_BOOTS}, + {Material.IRON_BOOTS, Material.DIAMOND_BOOTS} + }; + private static final Material[][] SWORDS = { + {Material.WOODEN_SWORD, Material.STONE_SWORD}, + {Material.IRON_SWORD, Material.IRON_SWORD}, + {Material.IRON_SWORD, Material.DIAMOND_SWORD} + }; + private static final Material[] BOW_POOL = {Material.BOW}; + + private final Map> servants = new ConcurrentHashMap<>(); + private final Map cooldowns = new ConcurrentHashMap<>(); + + public static boolean isServant(org.bukkit.entity.Entity entity) { + return entity.getPersistentDataContainer().has(SERVANT_KEY, PersistentDataType.STRING); + } + private final Map threats = new ConcurrentHashMap<>(); + private final Map servantThornsCooldowns = new ConcurrentHashMap<>(); + private final Map servantCurseCooldowns = new ConcurrentHashMap<>(); + private volatile PerkRefs perkRefs; + + public TragoulSkeletalServant() { + super("tragoul-skeletal-servant"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.skeletal_servant.description")); + setDisplayName(Localizer.dLocalize("tragoul.skeletal_servant.name")); + setIcon(Material.SKELETON_SKULL); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_tragoul_servant_50") + .title(Localizer.dLocalize("advancement.challenge_tragoul_servant_50.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_servant_50.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SKELETON_SKULL) + .key("challenge_tragoul_servant_500") + .title(Localizer.dLocalize("advancement.challenge_tragoul_servant_500.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_servant_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_servant_50", "tragoul.skeletal-servant.servants-summoned", 50, 400); + registerMilestone("challenge_tragoul_servant_500", "tragoul.skeletal-servant.servants-summoned", 500, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + Localizer.dLocalize("tragoul.skeletal_servant.lore1")); + v.addLore(C.GREEN + "+ " + getServantCap(level) + C.GRAY + " " + Localizer.dLocalize("tragoul.skeletal_servant.lore5")); + v.addLore(C.GREEN + "+ " + Form.duration(getDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.skeletal_servant.lore2")); + v.addLore(C.YELLOW + "* " + getBoneCost(level) + C.GRAY + " " + Localizer.dLocalize("tragoul.skeletal_servant.lore3")); + v.addLore(C.YELLOW + "* " + Form.duration((double) getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.skeletal_servant.lore4")); + v.addLore(C.GRAY + Localizer.dLocalize("tragoul.skeletal_servant.lore6")); + } + + @EventHandler + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + Action action = e.getAction(); + if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { + return; + } + + if (e.getHand() != EquipmentSlot.HAND || e.getItem() == null || e.getMaterial() != Material.BONE) { + return; + } + + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + UUID id = p.getUniqueId(); + long now = System.currentTimeMillis(); + SoundPlayer sp = SoundPlayer.of(p); + Long until = cooldowns.get(id); + if (until != null && until > now) { + sp.play(p, Sound.BLOCK_CONDUIT_DEACTIVATE, 0.8f, 0.8f); + return; + } + + CopyOnWriteArrayList list = servants.computeIfAbsent(id, k -> new CopyOnWriteArrayList<>()); + list.removeIf(servant -> !servant.isValid() || servant.isDead()); + int cap = getServantCap(level); + if (list.size() >= cap && !getConfig().replaceOldestAtCap) { + sp.play(p, Sound.BLOCK_CONDUIT_DEACTIVATE, 0.8f, 1.2f); + return; + } + + int boneCost = getBoneCost(level); + if (p.getGameMode() != GameMode.CREATIVE) { + if (!p.getInventory().containsAtLeast(new ItemStack(Material.BONE), boneCost)) { + sp.play(p, Sound.BLOCK_CONDUIT_DEACTIVATE, 0.8f, 0.6f); + return; + } + p.getInventory().removeItem(new ItemStack(Material.BONE, boneCost)); + } + + while (list.size() >= cap) { + Skeleton oldest = list.remove(0); + J.runEntity(oldest, () -> despawnServant(oldest)); + } + + cooldowns.put(id, now + getCooldownMillis(level)); + int durationTicks = getDurationTicks(level); + ThreadLocalRandom random = ThreadLocalRandom.current(); + Skeleton servant = p.getWorld().spawn(p.getLocation(), Skeleton.class, s -> { + s.getPersistentDataContainer().set(SERVANT_KEY, PersistentDataType.STRING, id.toString()); + s.setPersistent(false); + s.setRemoveWhenFarAway(false); + s.setShouldBurnInDay(false); + applyServantAttributes(s, level); + equipServant(s, level, random); + }); + list.add(servant); + Player priorityTarget = resolvePriorityTarget(id, p, now); + if (priorityTarget != null) { + servant.setTarget(priorityTarget); + } + scheduleServantPulse(servant, id, now + (durationTicks * 50L)); + J.runEntity(servant, () -> despawnServant(servant), durationTicks); + + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.SOUL, servant.getLocation().add(0, 1, 0), 16, 0.3, 0.6, 0.3, 0.02); + } + sp.play(servant.getLocation(), Sound.ENTITY_SKELETON_AMBIENT, 0.9f, 0.7f); + getPlayer(p).getData().addStat("tragoul.skeletal-servant.servants-summoned", 1); + xp(p, getConfig().xpPerSummon); + }); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityTargetLivingEntityEvent e) { + if (!(e.getEntity() instanceof Skeleton skeleton)) { + return; + } + + LivingEntity target = e.getTarget(); + if (target == null) { + return; + } + + String ownerRaw = skeleton.getPersistentDataContainer().get(SERVANT_KEY, PersistentDataType.STRING); + if (ownerRaw == null) { + return; + } + + if (target instanceof Player player) { + UUID ownerId = UUID.fromString(ownerRaw); + if (player.getUniqueId().equals(ownerId) || !isPriorityTarget(ownerId, player)) { + e.setCancelled(true); + if (skeleton.getTarget() instanceof Player) { + skeleton.setTarget(null); + } + } + return; + } + + if (target instanceof Skeleton other && other.getPersistentDataContainer().has(SERVANT_KEY, PersistentDataType.STRING)) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Player victim)) { + return; + } + + Skeleton servant = resolveServantDamager(e.getDamager()); + if (servant == null) { + return; + } + + String ownerRaw = servant.getPersistentDataContainer().get(SERVANT_KEY, PersistentDataType.STRING); + if (ownerRaw == null) { + return; + } + + UUID ownerId = UUID.fromString(ownerRaw); + if (victim.getUniqueId().equals(ownerId) || !isPriorityTarget(ownerId, victim)) { + e.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onCombatPerks(EntityDamageByEntityEvent e) { + Entity entity = e.getEntity(); + if (entity instanceof Skeleton skeleton) { + String ownerRaw = skeleton.getPersistentDataContainer().get(SERVANT_KEY, PersistentDataType.STRING); + if (ownerRaw != null) { + handleServantHit(e, skeleton, ownerRaw); + return; + } + } + + if (entity instanceof Player ownerCandidate) { + handleOwnerDamaged(e, ownerCandidate); + } + + if (!(entity instanceof LivingEntity victim)) { + return; + } + + Skeleton servant = resolveServantDamager(e.getDamager()); + if (servant != null && servant != victim) { + handleServantDealtDamage(e, servant, victim); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (!(e.getEntity() instanceof Skeleton skeleton)) { + return; + } + + String ownerRaw = skeleton.getPersistentDataContainer().get(SERVANT_KEY, PersistentDataType.STRING); + if (ownerRaw == null) { + return; + } + + e.getDrops().clear(); + e.setDroppedExp(0); + servantThornsCooldowns.remove(skeleton.getUniqueId()); + removeServantRef(UUID.fromString(ownerRaw), skeleton); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onServantKill(EntityDeathEvent e) { + LivingEntity victim = e.getEntity(); + if (!(victim instanceof Monster)) { + return; + } + + if (victim.getPersistentDataContainer().has(SERVANT_KEY, PersistentDataType.STRING)) { + return; + } + + if (!(victim.getLastDamageCause() instanceof EntityDamageByEntityEvent damageEvent)) { + return; + } + + Skeleton servant = resolveServantDamager(damageEvent.getDamager()); + if (servant == null) { + return; + } + + String ownerRaw = servant.getPersistentDataContainer().get(SERVANT_KEY, PersistentDataType.STRING); + if (ownerRaw == null) { + return; + } + + Player owner = Bukkit.getPlayer(UUID.fromString(ownerRaw)); + if (owner == null || !owner.isOnline()) { + return; + } + + TragoulCorpseExplosion nova = perks().nova(); + if (nova != null) { + TragoulCorpseExplosion.detonateServantKill(nova, owner, victim); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + cooldowns.remove(id); + threats.remove(id); + servantCurseCooldowns.remove(id); + CopyOnWriteArrayList list = servants.remove(id); + if (list == null) { + return; + } + + for (Skeleton servant : list) { + servantThornsCooldowns.remove(servant.getUniqueId()); + J.runEntity(servant, () -> { + if (servant.isValid() && !servant.isDead()) { + servant.remove(); + } + }); + } + } + + private void handleOwnerDamaged(EntityDamageByEntityEvent e, Player owner) { + Player attacker = null; + if (e.getDamager() instanceof Player player) { + attacker = player; + } else if (e.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof Player shooter) { + attacker = shooter; + } + + if (attacker == null || attacker == owner) { + return; + } + + if (getLevel(owner) <= 0) { + return; + } + + UUID ownerId = owner.getUniqueId(); + threats.put(ownerId, new PlayerThreat(attacker.getUniqueId(), System.currentTimeMillis())); + CopyOnWriteArrayList list = servants.get(ownerId); + if (list == null || list.isEmpty()) { + return; + } + + if (!canDamageTarget(owner, attacker)) { + return; + } + + Player target = attacker; + for (Skeleton servant : list) { + J.runEntity(servant, () -> { + if (servant.isValid() && !servant.isDead() && target.isOnline() && !target.isDead() && servant.getWorld() == target.getWorld()) { + servant.setTarget(target); + } + }); + } + } + + private void handleServantHit(EntityDamageByEntityEvent e, Skeleton servant, String ownerRaw) { + LivingEntity attacker = resolveLivingDamager(e.getDamager()); + if (attacker == null || attacker == servant) { + return; + } + + if (attacker instanceof Skeleton other && other.getPersistentDataContainer().has(SERVANT_KEY, PersistentDataType.STRING)) { + return; + } + + UUID ownerId = UUID.fromString(ownerRaw); + if (attacker instanceof Player player && player.getUniqueId().equals(ownerId)) { + return; + } + + Player owner = Bukkit.getPlayer(ownerId); + if (owner == null || !owner.isOnline()) { + return; + } + + long now = System.currentTimeMillis(); + if (attacker instanceof Player player) { + PlayerThreat threat = threats.get(ownerId); + if (threat != null && threat.attackerId().equals(player.getUniqueId()) && now - threat.stamp() < getConfig().playerThreatWindowMillis) { + threats.put(ownerId, new PlayerThreat(player.getUniqueId(), now)); + } + } + + PerkRefs refs = perks(); + applyThorns(refs.thorns(), owner, servant, attacker, now); + applyFrailty(refs.frailty(), owner, attacker, now); + } + + private void handleServantDealtDamage(EntityDamageByEntityEvent e, Skeleton servant, LivingEntity victim) { + String ownerRaw = servant.getPersistentDataContainer().get(SERVANT_KEY, PersistentDataType.STRING); + if (ownerRaw == null) { + return; + } + + Player owner = Bukkit.getPlayer(UUID.fromString(ownerRaw)); + if (owner == null || !owner.isOnline()) { + return; + } + + PerkRefs refs = perks(); + applySoulSiphon(refs.siphon(), owner, servant, victim, e); + applyPlagueMark(refs.plague(), owner, victim); + } + + private void applyThorns(TragoulThorns thorns, Player owner, Skeleton servant, LivingEntity attacker, long now) { + if (thorns == null) { + return; + } + + Long until = servantThornsCooldowns.get(servant.getUniqueId()); + if (until != null && until > now) { + return; + } + + int level = thorns.getActiveLevel(owner); + if (level <= 0 || !canDamageTarget(owner, attacker)) { + return; + } + + servantThornsCooldowns.put(servant.getUniqueId(), now + 1500L); + double reflected = thorns.getConfig().damageMultiplierPerLevel * level; + J.runEntity(attacker, () -> { + if (attacker.isValid() && !attacker.isDead()) { + attacker.damage(reflected, owner); + } + }); + } + + private void applyFrailty(TragoulCurseOfFrailty frailty, Player owner, LivingEntity attacker, long now) { + if (frailty == null) { + return; + } + + Long until = servantCurseCooldowns.get(attacker.getUniqueId()); + if (until != null && until > now) { + return; + } + + int level = frailty.getActiveLevel(owner); + if (level <= 0 || !canDamageTarget(owner, attacker)) { + return; + } + + TragoulCurseOfFrailty.Config curseConfig = frailty.getConfig(); + servantCurseCooldowns.put(attacker.getUniqueId(), now + curseConfig.perAttackerCooldownMillis); + double levelPercent = frailty.getLevelPercent(level); + int duration = Math.max(40, (int) Math.round(curseConfig.curseDurationTicksBase + (levelPercent * curseConfig.curseDurationTicksFactor))); + int weaknessAmplifier = levelPercent >= 0.8 ? 1 : 0; + boolean slowness = levelPercent >= curseConfig.slownessUnlockPercent; + int slownessAmplifier = curseConfig.slownessAmplifier; + J.runEntity(attacker, () -> { + if (!attacker.isValid() || attacker.isDead()) { + return; + } + + attacker.addPotionEffect(new PotionEffect(PotionEffectType.WEAKNESS, duration, weaknessAmplifier, true, true, true), true); + if (slowness) { + attacker.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, duration, slownessAmplifier, true, true, true), true); + } + }); + } + + private void applySoulSiphon(TragoulSoulSiphon siphon, Player owner, Skeleton servant, LivingEntity victim, EntityDamageByEntityEvent e) { + if (siphon == null) { + return; + } + + EntityDamageEvent.DamageCause cause = e.getCause(); + if (cause != EntityDamageEvent.DamageCause.ENTITY_ATTACK && cause != EntityDamageEvent.DamageCause.ENTITY_SWEEP_ATTACK) { + return; + } + + int level = siphon.getActiveLevel(owner); + if (level <= 0 || !canDamageTarget(owner, victim)) { + return; + } + + TragoulSoulSiphon.Config siphonConfig = siphon.getConfig(); + double levelPercent = siphon.getLevelPercent(level); + double percent = Math.max(0, siphonConfig.healPercentBase + (levelPercent * siphonConfig.healPercentFactor)); + double cap = Math.max(0.5, siphonConfig.healCapPerSecondBase + (levelPercent * siphonConfig.healCapPerSecondFactor)); + double heal = Math.min(cap, e.getFinalDamage() * percent); + if (heal <= 0) { + return; + } + + IAttribute attribute = Version.get().getAttribute(servant, Attributes.GENERIC_MAX_HEALTH); + double maxHealth = attribute == null ? 20D : attribute.getValue(); + double newHealth = Math.min(maxHealth, servant.getHealth() + heal); + if (newHealth <= servant.getHealth()) { + return; + } + + servant.setHealth(newHealth); + if (areParticlesEnabled()) { + servant.getWorld().spawnParticle(Particle.SOUL, servant.getLocation().add(0, 1.2, 0), 4, 0.2, 0.3, 0.2, 0.01); + } + } + + private void applyPlagueMark(TragoulPlagueBearer plague, Player owner, LivingEntity victim) { + if (plague == null || !(victim instanceof Monster monster)) { + return; + } + + if (!monster.hasPotionEffect(PotionEffectType.POISON) && !monster.hasPotionEffect(PotionEffectType.WITHER)) { + return; + } + + if (plague.getActiveLevel(owner) <= 0) { + return; + } + + PersistentDataContainer pdc = monster.getPersistentDataContainer(); + pdc.set(PLAGUE_OWNER_KEY, PersistentDataType.STRING, owner.getUniqueId().toString()); + pdc.set(PLAGUE_STAMP_KEY, PersistentDataType.LONG, System.currentTimeMillis()); + if (!pdc.has(PLAGUE_GENERATION_KEY, PersistentDataType.INTEGER)) { + pdc.set(PLAGUE_GENERATION_KEY, PersistentDataType.INTEGER, 0); + } + } + + private void scheduleServantPulse(Skeleton servant, UUID ownerId, long expiresAt) { + J.runEntity(servant, () -> { + if (!servant.isValid() || servant.isDead() || System.currentTimeMillis() >= expiresAt) { + removeServantRef(ownerId, servant); + return; + } + + retarget(servant, ownerId); + scheduleServantPulse(servant, ownerId, expiresAt); + }, getConfig().retargetIntervalTicks); + } + + private void retarget(Skeleton servant, UUID ownerId) { + PlayerThreat threat = threats.get(ownerId); + if (threat != null && System.currentTimeMillis() - threat.stamp() < getConfig().playerThreatWindowMillis) { + Player attacker = Bukkit.getPlayer(threat.attackerId()); + if (attacker != null && attacker.isOnline() && !attacker.isDead() && attacker.getWorld() == servant.getWorld()) { + Player owner = Bukkit.getPlayer(ownerId); + if (owner != null && owner.isOnline() && canDamageTarget(owner, attacker)) { + if (servant.getTarget() != attacker) { + servant.setTarget(attacker); + } + return; + } + } + } + + LivingEntity current = servant.getTarget(); + if (current instanceof Monster && current.isValid() && !current.isDead()) { + return; + } + + double range = getConfig().targetSearchRadius; + Monster nearest = null; + double best = Double.MAX_VALUE; + for (Entity entity : servant.getNearbyEntities(range, range, range)) { + if (!(entity instanceof Monster monster) || monster.isDead() || !monster.isValid()) { + continue; + } + + if (monster.getPersistentDataContainer().has(SERVANT_KEY, PersistentDataType.STRING)) { + continue; + } + + double distance = monster.getLocation().distanceSquared(servant.getLocation()); + if (distance < best) { + best = distance; + nearest = monster; + } + } + + if (nearest != null) { + servant.setTarget(nearest); + } + } + + private void despawnServant(Skeleton servant) { + String ownerRaw = servant.getPersistentDataContainer().get(SERVANT_KEY, PersistentDataType.STRING); + if (ownerRaw != null) { + removeServantRef(UUID.fromString(ownerRaw), servant); + } + + servantThornsCooldowns.remove(servant.getUniqueId()); + if (servant.isValid() && !servant.isDead()) { + if (areParticlesEnabled()) { + servant.getWorld().spawnParticle(Particle.SOUL, servant.getLocation().add(0, 1, 0), 12, 0.3, 0.6, 0.3, 0.02); + } + SoundPlayer.of(servant.getWorld()).play(servant.getLocation(), Sound.ENTITY_SKELETON_DEATH, 0.7f, 1.3f); + servant.remove(); + } + } + + private void removeServantRef(UUID ownerId, Skeleton servant) { + CopyOnWriteArrayList list = servants.get(ownerId); + if (list != null) { + list.remove(servant); + } + } + + private Player resolvePriorityTarget(UUID ownerId, Player owner, long now) { + PlayerThreat threat = threats.get(ownerId); + if (threat == null || now - threat.stamp() >= getConfig().playerThreatWindowMillis) { + return null; + } + + Player attacker = Bukkit.getPlayer(threat.attackerId()); + if (attacker == null || !attacker.isOnline() || attacker.isDead()) { + return null; + } + + if (attacker.getWorld() != owner.getWorld()) { + return null; + } + + if (!canDamageTarget(owner, attacker)) { + return null; + } + + return attacker; + } + + private boolean isPriorityTarget(UUID ownerId, Player candidate) { + PlayerThreat threat = threats.get(ownerId); + if (threat == null || !threat.attackerId().equals(candidate.getUniqueId())) { + return false; + } + + if (System.currentTimeMillis() - threat.stamp() >= getConfig().playerThreatWindowMillis) { + return false; + } + + Player owner = Bukkit.getPlayer(ownerId); + return owner != null && owner.isOnline() && canDamageTarget(owner, candidate); + } + + private void applyServantAttributes(Skeleton servant, int level) { + IAttribute maxHealth = Version.get().getAttribute(servant, Attributes.GENERIC_MAX_HEALTH); + if (maxHealth != null) { + maxHealth.setBaseValue(maxHealth.getBaseValue() + (level * getConfig().healthBonusPerLevel)); + servant.setHealth(maxHealth.getValue()); + } + + IAttribute attack = Version.get().getAttribute(servant, Attributes.GENERIC_ATTACK_DAMAGE); + if (attack != null) { + attack.setBaseValue(attack.getBaseValue() + (level * getConfig().attackBonusPerLevel)); + } + } + + private void equipServant(Skeleton servant, int level, ThreadLocalRandom random) { + EntityEquipment equipment = servant.getEquipment(); + if (equipment == null) { + return; + } + + int tier = getGearTier(level); + double pieceChance = getConfig().gearChancePerPiece; + double enchantChance = tier == 0 ? 0 : Math.max(0, getConfig().enchantChanceBase + (getLevelPercent(level) * getConfig().enchantChanceFactor)); + if (random.nextDouble() < pieceChance) { + equipment.setHelmet(rollPiece(random, HELMETS[tier], enchantChance, Enchantment.PROTECTION)); + } + if (random.nextDouble() < pieceChance) { + equipment.setChestplate(rollPiece(random, CHESTPLATES[tier], enchantChance, Enchantment.PROTECTION)); + } + if (random.nextDouble() < pieceChance) { + equipment.setLeggings(rollPiece(random, LEGGINGS[tier], enchantChance, Enchantment.PROTECTION)); + } + if (random.nextDouble() < pieceChance) { + equipment.setBoots(rollPiece(random, BOOTS[tier], enchantChance, Enchantment.PROTECTION)); + } + + ItemStack weapon = random.nextDouble() < getConfig().bowChance + ? rollPiece(random, BOW_POOL, enchantChance, Enchantment.POWER) + : rollPiece(random, SWORDS[tier], enchantChance, Enchantment.SHARPNESS); + equipment.setItemInMainHand(weapon); + + equipment.setHelmetDropChance(0f); + equipment.setChestplateDropChance(0f); + equipment.setLeggingsDropChance(0f); + equipment.setBootsDropChance(0f); + equipment.setItemInMainHandDropChance(0f); + equipment.setItemInOffHandDropChance(0f); + } + + private ItemStack rollPiece(ThreadLocalRandom random, Material[] pool, double enchantChance, Enchantment enchantment) { + Material material = pool[random.nextInt(pool.length)]; + ItemStack item = new ItemStack(material); + if (enchantChance > 0 && random.nextDouble() < enchantChance) { + item.addEnchantment(enchantment, 1 + random.nextInt(2)); + } + return item; + } + + private int getGearTier(int level) { + return switch (Math.min(Math.max(level, 1), 5)) { + case 1, 2 -> 0; + case 3, 4 -> 1; + default -> 2; + }; + } + + private PerkRefs perks() { + PerkRefs local = perkRefs; + if (local != null) { + return local; + } + + if (getSkill() == null) { + return new PerkRefs(null, null, null, null, null); + } + + TragoulThorns thornsRef = null; + TragoulSoulSiphon siphonRef = null; + TragoulCurseOfFrailty frailtyRef = null; + TragoulCorpseExplosion novaRef = null; + TragoulPlagueBearer plagueRef = null; + for (Adaptation adaptation : getSkill().getAdaptations()) { + if (adaptation instanceof TragoulThorns found) { + thornsRef = found; + } else if (adaptation instanceof TragoulSoulSiphon found) { + siphonRef = found; + } else if (adaptation instanceof TragoulCurseOfFrailty found) { + frailtyRef = found; + } else if (adaptation instanceof TragoulCorpseExplosion found) { + novaRef = found; + } else if (adaptation instanceof TragoulPlagueBearer found) { + plagueRef = found; + } + } + + local = new PerkRefs(thornsRef, siphonRef, frailtyRef, novaRef, plagueRef); + perkRefs = local; + return local; + } + + private static LivingEntity resolveLivingDamager(Entity damager) { + if (damager instanceof LivingEntity living) { + return living; + } + + if (damager instanceof Projectile projectile && projectile.getShooter() instanceof LivingEntity shooter) { + return shooter; + } + + return null; + } + + private static Skeleton resolveServantDamager(Entity damager) { + Entity source = damager; + if (source instanceof Projectile projectile && projectile.getShooter() instanceof Entity shooter) { + source = shooter; + } + + if (source instanceof Skeleton skeleton && skeleton.getPersistentDataContainer().has(SERVANT_KEY, PersistentDataType.STRING)) { + return skeleton; + } + + return null; + } + + private int getServantCap(int level) { + return Math.max(1, (int) Math.round(level * getConfig().servantCapPerLevel)); + } + + private int getBoneCost(int level) { + return Math.max(1, (int) Math.round(getConfig().boneCostBase - (getLevelPercent(level) * getConfig().boneCostReduction))); + } + + private int getDurationTicks(int level) { + return Math.max(100, (int) Math.round(getConfig().durationTicksBase + (getLevelPercent(level) * getConfig().durationTicksFactor))); + } + + private long getCooldownMillis(int level) { + return Math.max(1000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + servantCurseCooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + servantThornsCooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); + long window = getConfig().playerThreatWindowMillis; + threats.entrySet().removeIf(entry -> now - entry.getValue().stamp() > window); + for (CopyOnWriteArrayList list : servants.values()) { + list.removeIf(servant -> !servant.isValid() || servant.isDead()); + } + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private record PlayerThreat(UUID attackerId, long stamp) { + } + + private record PerkRefs(TragoulThorns thorns, TragoulSoulSiphon siphon, + TragoulCurseOfFrailty frailty, + TragoulCorpseExplosion nova, + TragoulPlagueBearer plague) { + } + + @NoArgsConstructor + @ConfigDescription("Sneak right-click with bones to raise a pack of temporary skeletal servants that gear up with your level, inherit your Tragoul perks, and hunt whoever attacks you.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Bones consumed per summon before level scaling.", impact = "Higher values make summoning more expensive at low levels.") + double boneCostBase = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Bone cost reduction granted at max level.", impact = "Higher values make summoning cheaper as the player levels.") + double boneCostReduction = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Servant lifetime in ticks before level scaling.", impact = "Higher values keep each servant alive longer.") + double durationTicksBase = 400; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional servant lifetime ticks granted at max level.", impact = "Higher values increase the level-scaled lifetime growth.") + double durationTicksFactor = 800; + @art.arcane.adapt.util.config.ConfigDoc(value = "Summon cooldown in milliseconds before level scaling.", impact = "Higher values slow how often a servant can be summoned.") + double cooldownMillisBase = 10000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown reduction in milliseconds granted at max level.", impact = "Higher values let high levels summon more often.") + double cooldownMillisFactor = 9000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Living servants allowed per adaptation level.", impact = "Higher values let one necromancer field a larger pack of servants.") + double servantCapPerLevel = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Replaces the oldest living servant when summoning at the cap.", impact = "False quietly refuses the summon instead of recycling the oldest servant.") + boolean replaceOldestAtCap = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Window in milliseconds during which a player who attacked the owner stays the priority target.", impact = "Higher values keep servants hunting an aggressor for longer after the last hit.") + long playerThreatWindowMillis = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Chance for each armor piece to be equipped on a freshly summoned servant.", impact = "Higher values produce more heavily armored servants.") + double gearChancePerPiece = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base chance for an equipped piece to receive an enchantment at mid gear tiers.", impact = "Higher values enchant servant gear more often before level scaling.") + double enchantChanceBase = 0.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional enchant chance granted at max level.", impact = "Higher values make high-level servants spawn with enchanted gear more often.") + double enchantChanceFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Chance a servant spawns with a bow instead of a sword.", impact = "Higher values produce more ranged servants.") + double bowChance = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Bonus max health granted to servants per adaptation level.", impact = "Higher values make servants tankier as the owner levels.") + double healthBonusPerLevel = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Bonus attack damage granted to servants per adaptation level.", impact = "Higher values make servants hit harder as the owner levels.") + double attackBonusPerLevel = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Ticks between servant retarget pulses.", impact = "Lower values retarget faster but cost more scheduler work.") + int retargetIntervalTicks = 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Radius the servant scans for hostile mobs to attack.", impact = "Higher values let the servant acquire targets further away.") + double targetSearchRadius = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per servant summon.", impact = "Higher values accelerate skill progression from this adaptation.") + double xpPerSummon = 30; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulSoulSiphon.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulSoulSiphon.java new file mode 100644 index 000000000..c6914dd57 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulSoulSiphon.java @@ -0,0 +1,202 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.version.IAttribute; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class TragoulSoulSiphon extends SimpleAdaptation { + private final Map budgets = new ConcurrentHashMap<>(); + + public TragoulSoulSiphon() { + super("tragoul-soul-siphon"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("tragoul.soul_siphon.description")); + setDisplayName(Localizer.dLocalize("tragoul.soul_siphon.name")); + setIcon(Material.SOUL_LANTERN); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SOUL_LANTERN) + .key("challenge_tragoul_siphon_500") + .title(Localizer.dLocalize("advancement.challenge_tragoul_siphon_500.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_siphon_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SOUL_CAMPFIRE) + .key("challenge_tragoul_siphon_10k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_siphon_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_siphon_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_siphon_500", "tragoul.soul-siphon.health-siphoned", 500, 400); + registerMilestone("challenge_tragoul_siphon_10k", "tragoul.soul-siphon.health-siphoned", 10000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getHealPercent(level), 0) + C.GRAY + " " + Localizer.dLocalize("tragoul.soul_siphon.lore1")); + v.addLore(C.YELLOW + "* " + Form.f(getHealCapPerSecond(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.soul_siphon.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + budgets.remove(e.getPlayer().getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player p)) { + return; + } + + EntityDamageEvent.DamageCause cause = e.getCause(); + if (cause != EntityDamageEvent.DamageCause.ENTITY_ATTACK && cause != EntityDamageEvent.DamageCause.ENTITY_SWEEP_ATTACK) { + return; + } + + if (!(e.getEntity() instanceof LivingEntity)) { + return; + } + + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0 || !canDamageTarget(p, e.getEntity())) { + return; + } + + long now = System.currentTimeMillis(); + HealBudget budget = budgets.computeIfAbsent(p.getUniqueId(), k -> new HealBudget()); + if (now - budget.windowStart >= 1000L) { + budget.windowStart = now; + budget.healed = 0; + } + + double remaining = getHealCapPerSecond(level) - budget.healed; + if (remaining <= 0) { + return; + } + + double heal = Math.min(remaining, e.getFinalDamage() * getHealPercent(level)); + if (heal <= 0) { + return; + } + + IAttribute attribute = Version.get().getAttribute(p, Attributes.GENERIC_MAX_HEALTH); + double maxHealth = attribute == null ? 20D : attribute.getValue(); + double newHealth = Math.min(maxHealth, p.getHealth() + heal); + if (newHealth <= p.getHealth()) { + return; + } + + budget.healed += heal; + p.setHealth(newHealth); + if (areParticlesEnabled()) { + J.runEntity(p, () -> p.getWorld().spawnParticle(Particle.SOUL, p.getLocation().add(0, 1.2, 0), 5, 0.25, 0.3, 0.25, 0.01)); + } + getPlayer(p).getData().addStat("tragoul.soul-siphon.health-siphoned", heal); + xp(p, getConfig().xpPerHeal); + }); + } + + private double getHealPercent(int level) { + return Math.max(0, getConfig().healPercentBase + (getLevelPercent(level) * getConfig().healPercentFactor)); + } + + private double getHealCapPerSecond(int level) { + return Math.max(0.5, getConfig().healCapPerSecondBase + (getLevelPercent(level) * getConfig().healCapPerSecondFactor)); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + private static class HealBudget { + private long windowStart; + private double healed; + } + + @NoArgsConstructor + @ConfigDescription("Melee hits heal you for a portion of the damage dealt, capped per second.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + @art.arcane.adapt.util.config.ConfigDoc(value = "Fraction of melee damage returned as healing before level scaling.", impact = "Higher values increase baseline lifesteal.") + double healPercentBase = 0.05; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional lifesteal fraction granted at max level.", impact = "Higher values increase level-scaled lifesteal growth.") + double healPercentFactor = 0.20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum health restored per second before level scaling.", impact = "Lower values harden the anti-abuse healing cap.") + double healCapPerSecondBase = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional per-second healing cap granted at max level.", impact = "Higher values let high levels sustain more healing per second.") + double healCapPerSecondFactor = 4.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per successful siphon heal.", impact = "Higher values accelerate skill progression from this adaptation.") + double xpPerHeal = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulThorns.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulThorns.java new file mode 100644 index 000000000..b425620da --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/TragoulThorns.java @@ -0,0 +1,199 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import de.slikey.effectlib.effect.BleedEffect; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + + +public class TragoulThorns extends SimpleAdaptation { + private final Map cooldowns; + + public TragoulThorns() { + super("tragoul-thorns"); + registerConfiguration(TragoulThorns.Config.class); + setDescription(Localizer.dLocalize("tragoul.thorns.description")); + setDisplayName(Localizer.dLocalize("tragoul.thorns.name")); + setIcon(Material.CACTUS); + setInterval(25000); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + cooldowns = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CACTUS) + .key("challenge_tragoul_thorns_500") + .title(Localizer.dLocalize("advancement.challenge_tragoul_thorns_500.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_thorns_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_CHESTPLATE) + .key("challenge_tragoul_thorns_5k") + .title(Localizer.dLocalize("advancement.challenge_tragoul_thorns_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_thorns_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_tragoul_thorns_500", "tragoul.thorns.damage-reflected", 500, 400); + registerMilestone("challenge_tragoul_thorns_5k", "tragoul.thorns.damage-reflected", 5000, 1500); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CACTUS) + .key("challenge_tragoul_thorns_kill") + .title(Localizer.dLocalize("advancement.challenge_tragoul_thorns_kill.title")) + .description(Localizer.dLocalize("advancement.challenge_tragoul_thorns_kill.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "" + getConfig().damageMultiplierPerLevel * level + "x " + Localizer.dLocalize("tragoul.thorns.lore1")); + } + + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + if (e.getEntity() instanceof Player p) { + withAdaptedPlayer(p, e, () -> { + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + UUID id = p.getUniqueId(); + Long cooldown = cooldowns.get(id); + if (cooldown != null && cooldown + 1500 > System.currentTimeMillis()) { + return; + } + + cooldowns.put(id, System.currentTimeMillis()); + + LivingEntity le = null; + + if (e.getDamager() instanceof LivingEntity) { + le = (LivingEntity) e.getDamager(); + } else if (e.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof LivingEntity) { + le = (LivingEntity) projectile.getShooter(); + } + + if (le != null && canDamageTarget(p, le)) { + if (areParticlesEnabled()) { + playThornsParticles(le); + } + double reflectedDamage = getConfig().damageMultiplierPerLevel * level; + double healthBefore = le.getHealth(); + le.damage(reflectedDamage, p); + getPlayer(p).getData().addStat("tragoul.thorns.damage-reflected", (int) reflectedDamage); + if (healthBefore <= reflectedDamage && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_tragoul_thorns_kill")) { + getPlayer(p).getAdvancementHandler().grant("challenge_tragoul_thorns_kill"); + } + } + }); + } + } + + private void playThornsParticles(LivingEntity target) { + Runnable burst = () -> { + if (target == null || !target.isValid()) { + return; + } + + target.getWorld().spawnParticle(Particle.DAMAGE_INDICATOR, target.getLocation().clone().add(0, 1, 0), 8, 0.35, 0.45, 0.35, 0.01); + target.getWorld().spawnParticle(Particle.CRIT, target.getLocation().clone().add(0, 1, 0), 10, 0.4, 0.5, 0.4, 0.06); + }; + + if (J.isFoliaThreading()) { + J.runEntity(target, burst); + return; + } + + try { + BleedEffect blood = new BleedEffect(Adapt.instance.adaptEffectManager); + blood.setEntity(target); + blood.height = -1; + blood.iterations = 1; + blood.start(); + } catch (UnsupportedOperationException | IllegalStateException ex) { + Adapt.verbose("Falling back to native thorns particles: " + ex.getMessage()); + burst.run(); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + + @Override + public void onTick() { + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Reflect damage back to your attacker.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Tragoul Thorns adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Multiplier Per Level for the Tragoul Thorns adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageMultiplierPerLevel = 1.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.72; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/tragoul/utils/EntityThings.java b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/utils/EntityThings.java new file mode 100644 index 000000000..4e858500f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/tragoul/utils/EntityThings.java @@ -0,0 +1,49 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.tragoul.utils; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class EntityThings { + + public static LivingEntity findNearestEntity(Entity referenceEntity, double range, Player player) { + Location location = referenceEntity.getLocation(); + List nearbyEntities = referenceEntity.getNearbyEntities(range, range, range); + + // Filter out non-living entities and the player + List livingEntities = nearbyEntities.stream() + .filter(entity -> entity instanceof LivingEntity && !entity.equals(player)) + .map(entity -> (LivingEntity) entity) + .collect(Collectors.toList()); + + // Sort by distance to the reference entity + livingEntities.sort(Comparator.comparingDouble(entity -> entity.getLocation().distance(location))); + + // Return the nearest living entity, or null if no valid entity is found + return livingEntities.isEmpty() ? null : livingEntities.get(0); + } +} + diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedBatteringCharge.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedBatteringCharge.java new file mode 100644 index 000000000..f6f510b7f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedBatteringCharge.java @@ -0,0 +1,291 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class UnarmedBatteringCharge extends SimpleAdaptation { + private final Map primedState = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map primedTrailNextAt = new java.util.concurrent.ConcurrentHashMap<>(); + + public UnarmedBatteringCharge() { + super("unarmed-battering-charge"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.battering_charge.description")); + setDisplayName(Localizer.dLocalize("unarmed.battering_charge.name")); + setIcon(Material.BLAZE_ROD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(50); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_charge_300") + .title(Localizer.dLocalize("advancement.challenge_unarmed_charge_300.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_charge_300.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_unarmed_charge_300", "unarmed.battering-charge.charges", 300, 400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_charge_kills_100") + .title(Localizer.dLocalize("advancement.challenge_unarmed_charge_kills_100.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_charge_kills_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_unarmed_charge_kills_100", "unarmed.battering-charge.charge-kills", 100, 1000); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getDamageBonus(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.battering_charge.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getKnockback(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.battering_charge.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.battering_charge.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + if (p.isInsideVehicle()) { + return; + } + + if (!isChargeLoadout(p) || !p.isSprinting()) { + return; + } + + ItemStack cooldownItem = getCooldownItem(p); + if (cooldownItem != null && p.hasCooldown(cooldownItem.getType())) { + return; + } + + Vector v = p.getVelocity(); + if (v.lengthSquared() < getConfig().minimumVelocitySquared) { + return; + } + + int level = attack.level(); + e.setDamage(e.getDamage() + getDamageBonus(level)); + Entity target = e.getEntity(); + target.setVelocity(target.getVelocity().add(p.getLocation().getDirection().normalize().multiply(getKnockback(level)))); + + if (cooldownItem != null) { + p.setCooldown(cooldownItem.getType(), getCooldownTicks(level)); + } + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.ENTITY_PLAYER_ATTACK_KNOCKBACK, 1f, 0.95f); + sp.play(target.getLocation(), Sound.ENTITY_ZOGLIN_ATTACK, 0.5f, 0.8f); + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particle.EXPLOSION, target.getLocation().add(0, 0.8, 0), 1, 0.06, 0.06, 0.06, 0.02); + } + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particle.CLOUD, target.getLocation().add(0, 0.6, 0), 14, 0.25, 0.25, 0.25, 0.05); + } + primedState.put(p.getUniqueId(), false); + xp(p, e.getDamage() * getConfig().xpPerDamage); + getPlayer(p).getData().addStat("unarmed.battering-charge.charges", 1); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (!(e.getEntity() instanceof LivingEntity victim)) { + return; + } + if (victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg + && dmg.getDamager() instanceof Player p + && hasActiveAdaptation(p) + && isChargeLoadout(p)) { + getPlayer(p).getData().addStat("unarmed.battering-charge.charge-kills", 1); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + primedState.remove(id); + primedTrailNextAt.remove(id); + } + + private boolean isChargeLoadout(Player p) { + ItemStack main = p.getInventory().getItemInMainHand(); + ItemStack off = p.getInventory().getItemInOffHand(); + boolean fists = (!isItem(main) || main.getType() == Material.AIR) && (!isItem(off) || off.getType() == Material.AIR); + boolean shieldLoadout = (isItem(main) && main.getType() == Material.SHIELD) || (isItem(off) && off.getType() == Material.SHIELD); + return fists || shieldLoadout; + } + + private ItemStack getCooldownItem(Player p) { + ItemStack main = p.getInventory().getItemInMainHand(); + ItemStack off = p.getInventory().getItemInOffHand(); + if (isItem(main) && main.getType() == Material.SHIELD) { + return main; + } + + if (isItem(off) && off.getType() == Material.SHIELD) { + return off; + } + + return null; + } + + private boolean isChargeReady(Player p) { + if (p.isInsideVehicle() || !isChargeLoadout(p) || !p.isSprinting()) { + return false; + } + + ItemStack cooldownItem = getCooldownItem(p); + if (cooldownItem != null && p.hasCooldown(cooldownItem.getType())) { + return false; + } + + Vector v = p.getVelocity(); + return v.lengthSquared() >= getConfig().minimumVelocitySquared; + } + + private double getDamageBonus(int level) { + return getConfig().damageBase + (getLevelPercent(level) * getConfig().damageFactor); + } + + private double getKnockback(int level) { + return getConfig().knockbackBase + (getLevelPercent(level) * getConfig().knockbackFactor); + } + + private int getCooldownTicks(int level) { + return Math.max(10, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + UUID id = p.getUniqueId(); + int level = getActiveLevel(p); + if (level <= 0) { + primedState.remove(id); + primedTrailNextAt.remove(id); + continue; + } + + boolean primed = isChargeReady(p); + boolean wasPrimed = primedState.getOrDefault(id, false); + + if (primed) { + long nextTrail = primedTrailNextAt.getOrDefault(id, 0L); + if (areParticlesEnabled() && now >= nextTrail) { + p.getWorld().spawnParticle(Particle.CLOUD, p.getLocation().add(0, 0.2, 0), 2, 0.2, 0.05, 0.2, 0.02); + primedTrailNextAt.put(id, now + Math.max(25L, getConfig().primedTrailIntervalMillis)); + } + if (!wasPrimed) { + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASEDRUM, 0.55f, 1.15f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.CRIT, p.getLocation().add(0, 1.0, 0), 8, 0.2, 0.3, 0.2, 0.1); + } + } + } else { + primedTrailNextAt.remove(id); + } + + primedState.put(id, primed); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sprint into enemies with fists or a shield to deal impact damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Base for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageBase = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageFactor = 4.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Knockback Base for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double knockbackBase = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Knockback Factor for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double knockbackFactor = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksBase = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double cooldownTicksFactor = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Velocity Squared for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minimumVelocitySquared = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Damage for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerDamage = 3.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds between primed trail particle pulses while charge is ready.", impact = "Lower values increase visual frequency and particle cost; higher values reduce trail spam.") + long primedTrailIntervalMillis = 120; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedComboChain.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedComboChain.java new file mode 100644 index 000000000..885c77fa2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedComboChain.java @@ -0,0 +1,244 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; + +public class UnarmedComboChain extends SimpleAdaptation { + private final Map combos = new java.util.concurrent.ConcurrentHashMap<>(); + + public UnarmedComboChain() { + super("unarmed-combo-chain"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.combo_chain.description")); + setDisplayName(Localizer.dLocalize("unarmed.combo_chain.name")); + setIcon(Material.CHAINMAIL_BOOTS); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1800); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_combo_5k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_combo_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_combo_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_unarmed_combo_5k", "unarmed.combo-chain.total-combo-hits", 5000, 400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BLAZE_POWDER) + .key("challenge_unarmed_combo_10") + .title(Localizer.dLocalize("advancement.challenge_unarmed_combo_10.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_combo_10.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BLAZE_ROD) + .key("challenge_unarmed_combo_25") + .title(Localizer.dLocalize("advancement.challenge_unarmed_combo_25.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_combo_25.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getMaxStacks(level) + C.GRAY + " " + Localizer.dLocalize("unarmed.combo_chain.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getDamagePerStack(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.combo_chain.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration(getComboWindowMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.combo_chain.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + ItemStack hand = p.getInventory().getItemInMainHand(); + if (isMelee(hand)) { + combos.remove(p.getUniqueId()); + return; + } + + long now = System.currentTimeMillis(); + int level = attack.level(); + ComboState state = combos.computeIfAbsent(p.getUniqueId(), id -> new ComboState()); + + if (now - state.lastHitMillis > getComboWindowMillis(level)) { + state.stacks = 0; + } + + state.lastHitMillis = now; + state.stacks = Math.min(getMaxStacks(level), state.stacks + 1); + + double bonus = state.stacks * getDamagePerStack(level); + e.setDamage(e.getDamage() + bonus); + playComboFeedback(p, e.getEntity().getLocation(), state.stacks, getMaxStacks(level)); + xp(p, bonus * getConfig().xpPerBonusDamage); + getPlayer(p).getData().addStat("unarmed.combo-chain.total-combo-hits", 1); + + // Special achievements: reach a 10-hit or 25-hit combo + if (state.stacks >= 10 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_unarmed_combo_10")) { + getPlayer(p).getAdvancementHandler().grant("challenge_unarmed_combo_10"); + } + if (state.stacks >= 25 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_unarmed_combo_25")) { + getPlayer(p).getAdvancementHandler().grant("challenge_unarmed_combo_25"); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) { + return; + } + + Player p = e.getPlayer(); + if (!hasActiveAdaptation(p) || isMelee(p.getInventory().getItemInMainHand())) { + return; + } + + ComboState state = combos.get(p.getUniqueId()); + if (state == null) { + return; + } + + long now = System.currentTimeMillis(); + if (now - state.lastHitMillis > getConfig().missResetGraceMillis) { + combos.remove(p.getUniqueId()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + combos.remove(e.getPlayer().getUniqueId()); + } + + private int getMaxStacks(int level) { + return Math.max(1, (int) Math.round(getConfig().maxStacksBase + (getLevelPercent(level) * getConfig().maxStacksFactor))); + } + + private double getDamagePerStack(int level) { + return getConfig().damagePerStackBase + (getLevelPercent(level) * getConfig().damagePerStackFactor); + } + + private long getComboWindowMillis(int level) { + return Math.max(250, (long) Math.round(getConfig().comboWindowMillisBase + (getLevelPercent(level) * getConfig().comboWindowMillisFactor))); + } + + private void playComboFeedback(Player p, org.bukkit.Location hitLocation, int stacks, int maxStacks) { + float pitch = Math.min(2.0f, 0.85f + (stacks * 0.09f)); + if (stacks >= maxStacks) { + SoundPlayer.of(p.getWorld()).play(hitLocation, Sound.BLOCK_ANVIL_PLACE, 0.55f, 1.7f); + if (areParticlesEnabled()) { + p.spawnParticle(Particle.TOTEM_OF_UNDYING, hitLocation.clone().add(0, 1, 0), 5, 0.2, 0.4, 0.2, 0.05); + } + return; + } + + SoundPlayer.of(p.getWorld()).play(hitLocation, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.55f, pitch); + if (areParticlesEnabled()) { + p.spawnParticle(Particle.CRIT, hitLocation.clone().add(0, 0.9, 0), 6 + Math.min(16, stacks * 2), 0.22, 0.34, 0.22, 0.1); + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Consecutive unarmed hits build combo stacks for increased punch damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Stacks Base for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxStacksBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Stacks Factor for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxStacksFactor = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Per Stack Base for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damagePerStackBase = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Per Stack Factor for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damagePerStackFactor = 0.85; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Combo Window Millis Base for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double comboWindowMillisBase = 1300; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Combo Window Millis Factor for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double comboWindowMillisFactor = 1400; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Miss Reset Grace Millis for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long missResetGraceMillis = 280; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Per Bonus Damage for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpPerBonusDamage = 4.1; + } + + private static class ComboState { + private int stacks = 0; + private long lastHitMillis = 0L; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedDisarm.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedDisarm.java new file mode 100644 index 000000000..220480504 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedDisarm.java @@ -0,0 +1,259 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.content.adaptation.tragoul.TragoulSkeletalServant; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class UnarmedDisarm extends SimpleAdaptation { + private final Map targetLockUntil = new java.util.concurrent.ConcurrentHashMap<>(); + + public UnarmedDisarm() { + super("unarmed-disarm"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.disarm.description")); + setDisplayName(Localizer.dLocalize("unarmed.disarm.name")); + setIcon(Material.STICK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(5125); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_disarm_100") + .title(Localizer.dLocalize("advancement.challenge_unarmed_disarm_100.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_disarm_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_disarm_1k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_disarm_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_disarm_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_disarm_100", "unarmed.disarm.disarms", 100, 400); + registerMilestone("challenge_unarmed_disarm_1k", "unarmed.disarm.disarms", 1000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("unarmed.disarm.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration((double) getConfig().targetCooldownMillis, 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.disarm.lore2")); + v.addLore(C.GREEN + "+ " + Form.pc(getConfig().mobArmorDropChance, 0) + C.GRAY + " " + Localizer.dLocalize("unarmed.disarm.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + + if (!(attack.target() instanceof LivingEntity victim)) { + return; + } + + boolean victimIsPlayer = victim instanceof Player; + if (victimIsPlayer && !getConfig().allowDisarmPlayers) { + return; + } + + long now = System.currentTimeMillis(); + Long lock = targetLockUntil.get(victim.getUniqueId()); + if (lock != null && now < lock) { + return; + } + + if (ThreadLocalRandom.current().nextDouble() > getChance(attack.level())) { + return; + } + + if (TragoulSkeletalServant.isServant(victim)) { + return; + } + + EntityEquipment equipment = victim.getEquipment(); + if (equipment == null) { + return; + } + + ItemStack main = equipment.getItemInMainHand(); + ItemStack off = equipment.getItemInOffHand(); + ItemStack knocked = null; + if (isItem(main)) { + knocked = main.clone(); + equipment.setItemInMainHand(null); + } else if (isItem(off) && off.getType() == Material.SHIELD) { + knocked = off.clone(); + equipment.setItemInOffHand(null); + } + + ItemStack armor = null; + if (!victimIsPlayer && ThreadLocalRandom.current().nextDouble() < getConfig().mobArmorDropChance) { + armor = takeRandomArmorPiece(equipment); + } + + if (knocked == null && armor == null) { + return; + } + + if (knocked != null) { + Item dropped = victim.getWorld().dropItemNaturally(victim.getLocation().add(0, 0.4, 0), knocked); + dropped.setPickupDelay(getConfig().pickupDelayTicks); + } + + if (armor != null) { + Item droppedArmor = victim.getWorld().dropItemNaturally(victim.getLocation().add(0, 0.4, 0), armor); + droppedArmor.setPickupDelay(getConfig().pickupDelayTicks); + } + + targetLockUntil.put(victim.getUniqueId(), now + getConfig().targetCooldownMillis); + + SoundPlayer sp = SoundPlayer.of(victim.getWorld()); + sp.play(victim.getLocation(), Sound.ENTITY_PLAYER_ATTACK_KNOCKBACK, 0.8f, 1.35f); + sp.play(victim.getLocation(), Sound.ITEM_LEAD_BREAK, 0.9f, 0.8f); + if (areParticlesEnabled()) { + victim.getWorld().spawnParticle(Particle.CRIT, victim.getLocation().add(0, 1.1, 0), 10, 0.25, 0.3, 0.25, 0.08); + } + xp(p, getConfig().xpPerDisarm, "disarm"); + getPlayer(p).getData().addStat("unarmed.disarm.disarms", 1); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + targetLockUntil.remove(e.getPlayer().getUniqueId()); + } + + private double getChance(int level) { + return Math.min(1, getConfig().chanceBase + (getLevelPercent(level) * getConfig().chanceFactor)); + } + + private ItemStack takeRandomArmorPiece(EntityEquipment equipment) { + ItemStack[] armor = equipment.getArmorContents(); + int worn = 0; + for (ItemStack piece : armor) { + if (isItem(piece)) { + worn++; + } + } + + if (worn == 0) { + return null; + } + + int pick = ThreadLocalRandom.current().nextInt(worn); + for (int i = 0; i < armor.length; i++) { + if (!isItem(armor[i])) { + continue; + } + + if (pick == 0) { + ItemStack taken = armor[i].clone(); + armor[i] = null; + equipment.setArmorContents(armor); + return taken; + } + pick--; + } + + return null; + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + targetLockUntil.values().removeIf(until -> until <= now); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Bare-hand hits can knock the target's held item to the ground.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows disarming other players, not just mobs.", impact = "True lets bare-hand hits knock items out of player hands in PVP.") + boolean allowDisarmPlayers = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Chance that a successful disarm against a mob also knocks loose a worn armor piece.", impact = "Higher values strip mob armor faster; players never lose armor.") + double mobArmorDropChance = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base chance for a bare-hand hit to disarm the target.", impact = "Higher values make disarms proc more often at level 1.") + double chanceBase = 0.04; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional disarm chance granted at max level.", impact = "Higher values make disarms proc more often as levels increase.") + double chanceFactor = 0.18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Pickup delay ticks applied to the knocked item.", impact = "Higher values keep the victim from re-grabbing the item for longer.") + int pickupDelayTicks = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Per-target cooldown in milliseconds between disarms.", impact = "Higher values prevent chain-disarming the same target.") + long targetCooldownMillis = 8000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per successful disarm.", impact = "Higher values speed up unarmed skill progression from disarms.") + double xpPerDisarm = 28; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedFlurry.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedFlurry.java new file mode 100644 index 000000000..1d2ff9233 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedFlurry.java @@ -0,0 +1,197 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.UUID; + +public class UnarmedFlurry extends SimpleAdaptation { + private final Map flurries = new java.util.concurrent.ConcurrentHashMap<>(); + + public UnarmedFlurry() { + super("unarmed-flurry"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.flurry.description")); + setDisplayName(Localizer.dLocalize("unarmed.flurry.name")); + setIcon(Material.FEATHER); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4811); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_flurry_1k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_flurry_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_flurry_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_flurry_10k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_flurry_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_flurry_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_flurry_1k", "unarmed.flurry.flurry-hits", 1000, 400); + registerMilestone("challenge_unarmed_flurry_10k", "unarmed.flurry.flurry-hits", 10000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getMaxStacks(level) + C.GRAY + " " + Localizer.dLocalize("unarmed.flurry.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getDamagePerStack(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.flurry.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration((double) getConfig().windowMillis, 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.flurry.lore3")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + flurries.remove(p.getUniqueId()); + return; + } + + long now = System.currentTimeMillis(); + int level = attack.level(); + FlurryState state = flurries.computeIfAbsent(p.getUniqueId(), id -> new FlurryState()); + if (now - state.lastHitMillis > getConfig().windowMillis) { + state.stacks = 0; + } + + state.lastHitMillis = now; + int max = getMaxStacks(level); + state.stacks = Math.min(max, state.stacks + 1); + + double bonus = state.stacks * getDamagePerStack(level); + e.setDamage(e.getDamage() + bonus); + if (state.stacks >= max) { + SoundPlayer.of(p.getWorld()).play(e.getEntity().getLocation(), Sound.BLOCK_NOTE_BLOCK_BIT, 0.5f, 1.8f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.CRIT, e.getEntity().getLocation().add(0, 1, 0), 8, 0.2, 0.3, 0.2, 0.08); + } + } + xp(p, bonus * getConfig().xpPerBonusDamage); + getPlayer(p).getData().addStat("unarmed.flurry.flurry-hits", 1); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerItemHeldEvent e) { + FlurryState state = flurries.get(e.getPlayer().getUniqueId()); + if (state == null) { + return; + } + + ItemStack next = e.getPlayer().getInventory().getItem(e.getNewSlot()); + if (next != null && isTool(next)) { + flurries.remove(e.getPlayer().getUniqueId()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + flurries.remove(e.getPlayer().getUniqueId()); + } + + private int getMaxStacks(int level) { + return Math.max(1, (int) Math.round(getConfig().maxStacksBase + (getLevelPercent(level) * getConfig().maxStacksFactor))); + } + + private double getDamagePerStack(int level) { + return getConfig().damagePerStackBase + (getLevelPercent(level) * getConfig().damagePerStackFactor); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Rapid consecutive bare-hand hits build flurry stacks that add bonus damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base maximum flurry stacks at level 1.", impact = "Higher values allow more stacked bonus damage early.") + double maxStacksBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional maximum flurry stacks granted at max level.", impact = "Higher values raise the stack cap as levels increase.") + double maxStacksFactor = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base bonus damage added per flurry stack.", impact = "Higher values make each stack hit harder.") + double damagePerStackBase = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional per-stack bonus damage granted at max level.", impact = "Higher values make stacks hit harder as levels increase.") + double damagePerStackFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Window in milliseconds between hits before stacks reset.", impact = "Higher values make the flurry easier to maintain.") + long windowMillis = 900; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per point of flurry bonus damage dealt.", impact = "Higher values speed up unarmed skill progression from flurries.") + double xpPerBonusDamage = 3.7; + } + + private static class FlurryState { + private int stacks = 0; + private long lastHitMillis = 0L; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedGlassCannon.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedGlassCannon.java new file mode 100644 index 000000000..9f15ace0a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedGlassCannon.java @@ -0,0 +1,154 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +public class UnarmedGlassCannon extends SimpleAdaptation { + public UnarmedGlassCannon() { + super("unarmed-glass-cannon"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.glass_cannon.description")); + setDisplayName(Localizer.dLocalize("unarmed.glass_cannon.name")); + setIcon(Material.POINTED_DRIPSTONE); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4544); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLASS) + .key("challenge_unarmed_glass_100") + .title(Localizer.dLocalize("advancement.challenge_unarmed_glass_100.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_glass_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GLASS_PANE) + .key("challenge_unarmed_glass_500") + .title(Localizer.dLocalize("advancement.challenge_unarmed_glass_500.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_glass_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_glass_100", "unarmed.glass-cannon.naked-kills", 100, 300); + registerMilestone("challenge_unarmed_glass_500", "unarmed.glass-cannon.naked-kills", 500, 1000); + } + + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + (getConfig().maxDamageFactor + (level * getConfig().maxDamagePerLevelMultiplier)) + C.GRAY + " " + Localizer.dLocalize("unarmed.glass_cannon.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(level * getConfig().perLevelBonusMultiplier) + C.GRAY + " " + Localizer.dLocalize("unarmed.glass_cannon.lore2")); + } + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + + double armor = getArmorValue(p); + double damage = e.getDamage(); + + if (armor == 0) { + e.setDamage(damage * getConfig().maxDamageFactor); + } else { + e.setDamage(damage - (damage * armor)); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (!(e.getEntity() instanceof LivingEntity victim)) { + return; + } + if (victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg + && dmg.getDamager() instanceof Player p + && hasActiveAdaptation(p) + && !isTool(p.getInventory().getItemInMainHand()) + && !isTool(p.getInventory().getItemInOffHand()) + && getArmorValue(p) == 0) { + getPlayer(p).getData().addStat("unarmed.glass-cannon.naked-kills", 1); + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Bonus unarmed damage the lower your armor value is.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.425; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Per Level Bonus Multiplier for the Unarmed Glass Cannon adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double perLevelBonusMultiplier = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Damage Factor for the Unarmed Glass Cannon adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxDamageFactor = 4.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Damage Per Level Multiplier for the Unarmed Glass Cannon adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxDamagePerLevelMultiplier = 0.15; + } + +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedGrapple.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedGrapple.java new file mode 100644 index 000000000..5f6ac9b6c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedGrapple.java @@ -0,0 +1,273 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Boss; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class UnarmedGrapple extends SimpleAdaptation { + private final Map grabs = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map cooldownUntil = new java.util.concurrent.ConcurrentHashMap<>(); + + public UnarmedGrapple() { + super("unarmed-grapple"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.grapple.description")); + setDisplayName(Localizer.dLocalize("unarmed.grapple.name")); + setIcon(Material.LEAD); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(2750); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_grapple_100") + .title(Localizer.dLocalize("advancement.challenge_unarmed_grapple_100.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_grapple_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_grapple_1k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_grapple_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_grapple_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_grapple_100", "unarmed.grapple.hurled-mobs", 100, 400); + registerMilestone("challenge_unarmed_grapple_1k", "unarmed.grapple.hurled-mobs", 1000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getForce(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.grapple.lore1")); + v.addLore(C.YELLOW + "* " + Form.duration((double) getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.grapple.lore2")); + v.addLore(C.GRAY + Localizer.dLocalize("unarmed.grapple.lore3")); + v.addLore(C.RED + "- " + Form.f(getConfig().exhaustionPerThrow, 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.grapple.lore4")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + + long now = System.currentTimeMillis(); + GrabState state = grabs.remove(p.getUniqueId()); + if (state != null && now - state.grabbedAtMillis <= getConfig().grabTimeoutMillis) { + hurl(p, state.target, attack.level(), now); + return; + } + + if (!p.isSneaking()) { + return; + } + + Long lock = cooldownUntil.get(p.getUniqueId()); + if (lock != null && now < lock) { + return; + } + + if (!(attack.target() instanceof LivingEntity victim)) { + return; + } + + if (victim instanceof Player && !getConfig().allowGrapplePlayers) { + return; + } + + if (victim instanceof Boss) { + return; + } + + grabs.put(p.getUniqueId(), new GrabState(victim, now)); + SoundPlayer.of(p.getWorld()).play(victim.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1f, 0.7f); + if (areParticlesEnabled()) { + victim.getWorld().spawnParticle(Particle.CRIT, victim.getLocation().add(0, 1, 0), 6, 0.2, 0.3, 0.2, 0.05); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerToggleSneakEvent e) { + if (e.isSneaking()) { + return; + } + + Player p = e.getPlayer(); + GrabState state = grabs.remove(p.getUniqueId()); + if (state == null) { + return; + } + + long now = System.currentTimeMillis(); + if (now - state.grabbedAtMillis > getConfig().grabTimeoutMillis) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + hurl(p, state.target, level, now); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + grabs.remove(id); + cooldownUntil.remove(id); + } + + private void hurl(Player p, LivingEntity target, int level, long now) { + if (!target.isValid() || target.isDead() || target.getWorld() != p.getWorld()) { + return; + } + + double maxRange = getConfig().maxHurlRange; + if (target.getLocation().distanceSquared(p.getLocation()) > maxRange * maxRange) { + return; + } + + cooldownUntil.put(p.getUniqueId(), now + getCooldownMillis(level)); + p.setExhaustion(p.getExhaustion() + (float) getConfig().exhaustionPerThrow); + Vector velocity = p.getLocation().getDirection().normalize().multiply(getForce(level)).setY(getConfig().upwardBoost + (getLevelPercent(level) * getConfig().upwardBoostFactor)); + J.runEntity(target, () -> { + if (target.isValid() && !target.isDead()) { + target.setVelocity(velocity); + } + }); + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(p.getLocation(), Sound.ENTITY_PLAYER_ATTACK_SWEEP, 1f, 0.75f); + sp.play(target.getLocation(), Sound.ENTITY_PLAYER_ATTACK_KNOCKBACK, 0.9f, 1.2f); + if (areParticlesEnabled()) { + target.getWorld().spawnParticle(Particle.CLOUD, target.getLocation().add(0, 0.8, 0), 12, 0.25, 0.25, 0.25, 0.05); + } + xp(p, getConfig().xpPerHurl, "grapple"); + getPlayer(p).getData().addStat("unarmed.grapple.hurled-mobs", 1); + } + + private double getForce(int level) { + return getConfig().forceBase + (getLevelPercent(level) * getConfig().forceFactor); + } + + private long getCooldownMillis(int level) { + return Math.max(1000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + grabs.values().removeIf(state -> now - state.grabbedAtMillis > getConfig().grabTimeoutMillis || !state.target.isValid() || state.target.isDead()); + cooldownUntil.values().removeIf(until -> until <= now); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak-punch a mob to grab it, then hurl it where you look.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Allows grappling other players, not just mobs.", impact = "True lets sneak-punches grab players in PVP.") + boolean allowGrapplePlayers = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base hurl force at level 1.", impact = "Higher values throw grabbed mobs further.") + double forceBase = 0.9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional hurl force granted at max level.", impact = "Higher values throw grabbed mobs further as levels increase.") + double forceFactor = 1.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base upward component added to the hurl velocity.", impact = "Higher values arc thrown mobs higher.") + double upwardBoost = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional upward hurl component granted at max level.", impact = "Higher values arc thrown mobs higher as levels increase.") + double upwardBoostFactor = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum distance in blocks a grabbed mob can be hurled from.", impact = "Higher values let the grab persist over larger gaps.") + double maxHurlRange = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds before an unused grab expires.", impact = "Higher values keep the grab primed for longer.") + long grabTimeoutMillis = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base grapple cooldown in milliseconds at level 1.", impact = "Higher values make the ability usable less often.") + double cooldownMillisBase = 9000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown reduction in milliseconds granted at max level.", impact = "Higher values make the ability recharge faster as levels increase.") + double cooldownMillisFactor = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Exhaustion added to the player per hurled mob.", impact = "Higher values drain saturation and food faster with each throw. Set to 0 to disable the exhaustion cost.") + double exhaustionPerThrow = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per hurled mob.", impact = "Higher values speed up unarmed skill progression from grapples.") + double xpPerHurl = 32; + } + + private static class GrabState { + private final LivingEntity target; + private final long grabbedAtMillis; + + private GrabState(LivingEntity target, long grabbedAtMillis) { + this.target = target; + this.grabbedAtMillis = grabbedAtMillis; + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedIronFists.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedIronFists.java new file mode 100644 index 000000000..2edafd1fb --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedIronFists.java @@ -0,0 +1,166 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.PotionEffectTypes; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.potion.PotionEffect; + +public class UnarmedIronFists extends SimpleAdaptation { + public UnarmedIronFists() { + super("unarmed-iron-fists"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.iron_fists.description")); + setDisplayName(Localizer.dLocalize("unarmed.iron_fists.name")); + setIcon(Material.ANVIL); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4622); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_iron_1k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_iron_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_iron_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_iron_10k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_iron_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_iron_10k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_iron_1k", "unarmed.iron-fists.iron-hits", 1000, 400); + registerMilestone("challenge_unarmed_iron_10k", "unarmed.iron-fists.iron-hits", 10000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getDamageBonus(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.iron_fists.lore1")); + v.addLore(C.GREEN + "+ " + (getHasteAmplifier(level) + 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.iron_fists.lore2")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + + e.setDamage(e.getDamage() + getDamageBonus(attack.level())); + xp(p, getConfig().xpPerHit); + getPlayer(p).getData().addStat("unarmed.iron-fists.iron-hits", 1); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(BlockDamageEvent e) { + Player p = e.getPlayer(); + if (isItem(p.getInventory().getItemInMainHand())) { + return; + } + + float hardness = e.getBlock().getType().getHardness(); + if (hardness < 0 || hardness > getConfig().softBlockMaxHardness) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveInteractContext(p, e.getBlock().getLocation()); + if (context == null) { + return; + } + + p.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, getConfig().hasteDurationTicks, getHasteAmplifier(context.level()), false, false, true)); + } + + private double getDamageBonus(int level) { + return getConfig().damageBase + (getLevelPercent(level) * getConfig().damageFactor); + } + + private int getHasteAmplifier(int level) { + return Math.max(0, (int) Math.round(getLevelPercent(level) * getConfig().hasteAmplifierFactor)); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Bare fists hit harder and punch through soft blocks faster.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base flat bare-hand damage bonus at level 1.", impact = "Higher values make every bare-hand hit stronger.") + double damageBase = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional flat damage bonus granted at max level.", impact = "Higher values make bare-hand hits stronger as levels increase.") + double damageFactor = 2.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum block hardness still considered a soft block.", impact = "Higher values let the punch-haste apply to tougher blocks.") + double softBlockMaxHardness = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Haste duration in ticks while punching soft blocks.", impact = "Higher values keep the dig-speed buff active longer.") + int hasteDurationTicks = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Haste amplifier granted at max level while punching soft blocks.", impact = "Higher values speed up bare-hand block breaking.") + double hasteAmplifierFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per bare-hand hit.", impact = "Higher values speed up unarmed skill progression from hits.") + double xpPerHit = 2.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedMeditation.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedMeditation.java new file mode 100644 index 000000000..53854d584 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedMeditation.java @@ -0,0 +1,206 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; + +public class UnarmedMeditation extends SimpleAdaptation { + private final Map lastCombatMillis = new java.util.concurrent.ConcurrentHashMap<>(); + private final Map lastPositions = new java.util.concurrent.ConcurrentHashMap<>(); + + public UnarmedMeditation() { + super("unarmed-meditation"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.meditation.description")); + setDisplayName(Localizer.dLocalize("unarmed.meditation.name")); + setIcon(Material.AMETHYST_CLUSTER); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(1000); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_meditate_500") + .title(Localizer.dLocalize("advancement.challenge_unarmed_meditate_500.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_meditate_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_meditate_5k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_meditate_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_meditate_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_meditate_500", "unarmed.meditation.absorption-gained", 500, 400); + registerMilestone("challenge_unarmed_meditate_5k", "unarmed.meditation.absorption-gained", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getAbsorptionCap(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.meditation.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getConfig().gainPerPulse) + C.GRAY + " " + Localizer.dLocalize("unarmed.meditation.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration((double) getConfig().combatLockoutMillis, 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.meditation.lore3")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDamageByEntityEvent e) { + long now = System.currentTimeMillis(); + if (e.getDamager() instanceof Player attacker) { + lastCombatMillis.put(attacker.getUniqueId(), now); + } + if (e.getEntity() instanceof Player victim) { + lastCombatMillis.put(victim.getUniqueId(), now); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID id = e.getPlayer().getUniqueId(); + lastCombatMillis.remove(id); + lastPositions.remove(id); + } + + @Override + public void onTick() { + long now = System.currentTimeMillis(); + for (art.arcane.adapt.api.world.AdaptPlayer adaptPlayer : learnedCandidates(now)) { + Player p = adaptPlayer.getPlayer(); + if (p == null || !p.isOnline()) { + continue; + } + + UUID id = p.getUniqueId(); + if (!p.isSneaking()) { + lastPositions.remove(id); + continue; + } + + if (isItem(p.getInventory().getItemInMainHand()) || isItem(p.getInventory().getItemInOffHand())) { + lastPositions.remove(id); + continue; + } + + Long combat = lastCombatMillis.get(id); + if (combat != null && now - combat < getConfig().combatLockoutMillis) { + continue; + } + + Location current = p.getLocation(); + Location previous = lastPositions.put(id, current); + if (previous == null || previous.getWorld() != current.getWorld() || previous.distanceSquared(current) > getConfig().stationaryEpsilonSquared) { + continue; + } + + int level = getActiveLevel(p); + if (level <= 0) { + continue; + } + + double cap = getAbsorptionCap(level); + double absorption = p.getAbsorptionAmount(); + if (absorption >= cap) { + continue; + } + + double gained = Math.min(cap - absorption, getConfig().gainPerPulse); + J.runEntity(p, () -> { + if (p.isOnline() && !p.isDead()) { + p.setAbsorptionAmount(Math.min(cap, p.getAbsorptionAmount() + gained)); + } + }); + + SoundPlayer.of(p.getWorld()).play(current, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.3f, 1.5f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particles.ENCHANTMENT_TABLE, current.clone().add(0, 1.4, 0), 8, 0.3, 0.4, 0.3, 0.04); + } + xpSilent(p, getConfig().xpPerPulse, "meditation"); + adaptPlayer.getData().addStat("unarmed.meditation.absorption-gained", gained); + } + } + + private double getAbsorptionCap(int level) { + return getConfig().absorptionCapBase + (getLevelPercent(level) * getConfig().absorptionCapFactor); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Meditate while sneaking, still, and empty-handed to slowly build absorption hearts.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base absorption cap in health points at level 1.", impact = "Higher values allow more stored absorption early.") + double absorptionCapBase = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional absorption cap granted at max level.", impact = "Higher values allow more stored absorption as levels increase.") + double absorptionCapFactor = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Absorption health points gained per meditation pulse.", impact = "Higher values build absorption faster.") + double gainPerPulse = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Milliseconds after combat before meditation can resume.", impact = "Higher values force a longer calm period after fighting.") + long combatLockoutMillis = 8000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum squared movement distance still considered stationary.", impact = "Higher values tolerate more drift while meditating.") + double stationaryEpsilonSquared = 0.01; + @art.arcane.adapt.util.config.ConfigDoc(value = "Silent XP granted per meditation pulse.", impact = "Higher values speed up unarmed skill progression from meditating.") + double xpPerPulse = 1.2; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedPower.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedPower.java new file mode 100644 index 000000000..2b7778ece --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedPower.java @@ -0,0 +1,148 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +public class UnarmedPower extends SimpleAdaptation { + public UnarmedPower() { + super("unarmed-power"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.power.description")); + setDisplayName(Localizer.dLocalize("unarmed.power.name")); + setIcon(Material.IRON_INGOT); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4444); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_power_500") + .title(Localizer.dLocalize("advancement.challenge_unarmed_power_500.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_power_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_power_5k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_power_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_power_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_power_500", "unarmed.power.unarmed-kills", 500, 400); + registerMilestone("challenge_unarmed_power_5k", "unarmed.power.unarmed-kills", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.pc(getUnarmedDamage(level), 0) + C.GRAY + Localizer.dLocalize("unarmed.power.lore1")); + } + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + double factor = getLevelPercent(attack.level()); + + if (factor <= 0) { + return; + } + e.setDamage(e.getDamage() * (1 + getUnarmedDamage(attack.level()))); + xp(p, 0.321 * factor * e.getDamage(), "unarmed-hit"); + + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (!(e.getEntity() instanceof LivingEntity victim)) { + return; + } + if (victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg + && dmg.getDamager() instanceof Player p + && hasActiveAdaptation(p) + && !isTool(p.getInventory().getItemInMainHand()) + && !isTool(p.getInventory().getItemInOffHand())) { + getPlayer(p).getData().addStat("unarmed.power.unarmed-kills", 1); + } + } + + private double getUnarmedDamage(int level) { + return getLevelPercent(level) * getConfig().damageFactor; + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Improved base unarmed damage.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.425; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Unarmed Power adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageFactor = 2.57; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedPressurePoint.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedPressurePoint.java new file mode 100644 index 000000000..deabe9e80 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedPressurePoint.java @@ -0,0 +1,173 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class UnarmedPressurePoint extends SimpleAdaptation { + public UnarmedPressurePoint() { + super("unarmed-pressure-point"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.pressure_point.description")); + setDisplayName(Localizer.dLocalize("unarmed.pressure_point.name")); + setIcon(Material.TRIPWIRE_HOOK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4733); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_pressure_500") + .title(Localizer.dLocalize("advancement.challenge_unarmed_pressure_500.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_pressure_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_pressure_5k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_pressure_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_pressure_5k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_pressure_500", "unarmed.pressure-point.pressure-strikes", 500, 400); + registerMilestone("challenge_unarmed_pressure_5k", "unarmed.pressure-point.pressure-strikes", 5000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + (getMaxSlownessAmplifier(level) + 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.pressure_point.lore1")); + if (isWeaknessUnlocked(level)) { + v.addLore(C.GREEN + "+ " + (getConfig().maxWeaknessAmplifier + 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.pressure_point.lore2")); + } else { + v.addLore(C.GRAY + Localizer.dLocalize("unarmed.pressure_point.lore3")); + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + + if (!(attack.target() instanceof LivingEntity victim)) { + return; + } + + int level = attack.level(); + applyStack(victim, PotionEffectType.SLOWNESS, getMaxSlownessAmplifier(level), getConfig().slownessDurationTicks); + if (isWeaknessUnlocked(level)) { + applyStack(victim, PotionEffectType.WEAKNESS, getConfig().maxWeaknessAmplifier, getConfig().weaknessDurationTicks); + } + + SoundPlayer.of(victim.getWorld()).play(victim.getLocation(), Sound.BLOCK_POINTED_DRIPSTONE_LAND, 0.6f, 1.6f); + if (areParticlesEnabled()) { + victim.getWorld().spawnParticle(Particle.CRIT, victim.getLocation().add(0, 1.2, 0), 5, 0.15, 0.2, 0.15, 0.05); + } + xp(p, getConfig().xpPerStrike); + getPlayer(p).getData().addStat("unarmed.pressure-point.pressure-strikes", 1); + } + + private void applyStack(LivingEntity victim, PotionEffectType type, int maxAmplifier, int durationTicks) { + PotionEffect existing = victim.getPotionEffect(type); + int amplifier = existing == null ? 0 : Math.min(maxAmplifier, existing.getAmplifier() + 1); + victim.addPotionEffect(new PotionEffect(type, durationTicks, amplifier, false, true, true), true); + } + + private int getMaxSlownessAmplifier(int level) { + return Math.max(0, (int) Math.round(getConfig().maxSlownessAmplifierBase + (getLevelPercent(level) * getConfig().maxSlownessAmplifierFactor))); + } + + private boolean isWeaknessUnlocked(int level) { + return getLevelPercent(level) >= getConfig().weaknessUnlockPercent; + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Bare-hand hits apply stacking slowness, with weakness at higher levels.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base maximum slowness amplifier at level 1.", impact = "Higher values allow stronger slowness stacking early.") + double maxSlownessAmplifierBase = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional maximum slowness amplifier granted at max level.", impact = "Higher values allow stronger slowness stacking as levels increase.") + double maxSlownessAmplifierFactor = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Slowness duration in ticks per pressure strike.", impact = "Higher values keep targets slowed for longer.") + int slownessDurationTicks = 60; + @art.arcane.adapt.util.config.ConfigDoc(value = "Level percent required before weakness stacking unlocks.", impact = "Lower values unlock the weakness effect at earlier levels.") + double weaknessUnlockPercent = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum weakness amplifier once unlocked.", impact = "Higher values allow stronger weakness stacking.") + int maxWeaknessAmplifier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Weakness duration in ticks per pressure strike.", impact = "Higher values keep targets weakened for longer.") + int weaknessDurationTicks = 50; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per pressure strike.", impact = "Higher values speed up unarmed skill progression from strikes.") + double xpPerStrike = 3.1; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedSecondWind.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedSecondWind.java new file mode 100644 index 000000000..d51bb684f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedSecondWind.java @@ -0,0 +1,198 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.Map; +import java.util.UUID; + +public class UnarmedSecondWind extends SimpleAdaptation { + private final Map lastTriggerMillis = new java.util.concurrent.ConcurrentHashMap<>(); + + public UnarmedSecondWind() { + super("unarmed-second-wind"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.second_wind.description")); + setDisplayName(Localizer.dLocalize("unarmed.second_wind.name")); + setIcon(Material.COOKED_BEEF); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4960); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_second_wind_100") + .title(Localizer.dLocalize("advancement.challenge_unarmed_second_wind_100.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_second_wind_100.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_second_wind_1k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_second_wind_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_second_wind_1k.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_second_wind_100", "unarmed.second-wind.second-winds", 100, 400); + registerMilestone("challenge_unarmed_second_wind_1k", "unarmed.second-wind.second-winds", 1000, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + getFoodRestore(level) + C.GRAY + " " + Localizer.dLocalize("unarmed.second_wind.lore1")); + v.addLore(C.GREEN + "+ " + Form.duration(getRegenDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.second_wind.lore2")); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (!(e.getEntity() instanceof LivingEntity victim) || victim instanceof Player) { + return; + } + + if (!(victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg) || !(dmg.getDamager() instanceof Player p)) { + return; + } + + if (isProtectedFriendly(p, victim)) { + return; + } + + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + + int level = getActiveLevel(p); + if (level <= 0) { + return; + } + + long now = System.currentTimeMillis(); + Long last = lastTriggerMillis.get(p.getUniqueId()); + if (last != null && now - last < getConfig().cooldownMillis) { + return; + } + + lastTriggerMillis.put(p.getUniqueId(), now); + int foodRestore = getFoodRestore(level); + int regenTicks = getRegenDurationTicks(level); + J.runEntity(p, () -> { + if (!p.isOnline() || p.isDead()) { + return; + } + p.setFoodLevel(Math.min(20, p.getFoodLevel() + foodRestore)); + p.setSaturation((float) Math.min(p.getFoodLevel(), p.getSaturation() + getConfig().saturationRestore)); + p.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, regenTicks, getConfig().regenAmplifier, false, true, true), true); + }); + + SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.5f, 1.7f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.HEART, p.getLocation().add(0, 1.6, 0), 3, 0.3, 0.2, 0.3, 0); + } + xp(p, getConfig().xpPerSecondWind, "second-wind"); + getPlayer(p).getData().addStat("unarmed.second-wind.second-winds", 1); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + lastTriggerMillis.remove(e.getPlayer().getUniqueId()); + } + + private int getFoodRestore(int level) { + return Math.max(1, (int) Math.round(getConfig().foodRestoreBase + (getLevelPercent(level) * getConfig().foodRestoreFactor))); + } + + private int getRegenDurationTicks(int level) { + return Math.max(20, (int) Math.round(getConfig().regenDurationTicksBase + (getLevelPercent(level) * getConfig().regenDurationTicksFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Bare-hand kills restore hunger and grant a short regeneration burst.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base hunger points restored per bare-hand kill.", impact = "Higher values restore more food per kill.") + double foodRestoreBase = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional hunger points restored at max level.", impact = "Higher values restore more food per kill as levels increase.") + double foodRestoreFactor = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Saturation restored per bare-hand kill.", impact = "Higher values keep the hunger bar full for longer.") + double saturationRestore = 1.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base regeneration duration in ticks per bare-hand kill.", impact = "Higher values keep the regen burst active longer.") + double regenDurationTicksBase = 40; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional regeneration duration ticks granted at max level.", impact = "Higher values keep the regen burst active longer as levels increase.") + double regenDurationTicksFactor = 80; + @art.arcane.adapt.util.config.ConfigDoc(value = "Regeneration amplifier applied by the burst.", impact = "Higher values heal faster during the burst.") + int regenAmplifier = 0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown in milliseconds between second wind triggers.", impact = "Higher values prevent rapid kill-chains from spamming the heal.") + long cooldownMillis = 3000; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per second wind trigger.", impact = "Higher values speed up unarmed skill progression from kills.") + double xpPerSecondWind = 18; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedShockwaveClap.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedShockwaveClap.java new file mode 100644 index 000000000..a1ffb9018 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedShockwaveClap.java @@ -0,0 +1,245 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.util.Vector; + +import java.util.Map; +import java.util.UUID; + +public class UnarmedShockwaveClap extends SimpleAdaptation { + private final Map nextClapAt = new java.util.concurrent.ConcurrentHashMap<>(); + + public UnarmedShockwaveClap() { + super("unarmed-shockwave-clap"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.shockwave_clap.description")); + setDisplayName(Localizer.dLocalize("unarmed.shockwave_clap.name")); + setIcon(Material.NOTE_BLOCK); + setBaseCost(getConfig().baseCost); + setMaxLevel(getConfig().maxLevel); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(5230); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_clap_250") + .title(Localizer.dLocalize("advancement.challenge_unarmed_clap_250.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_clap_250.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_clap_2500") + .title(Localizer.dLocalize("advancement.challenge_unarmed_clap_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_clap_2500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_clap_250", "unarmed.shockwave-clap.mobs-clapped", 250, 400); + registerMilestone("challenge_unarmed_clap_2500", "unarmed.shockwave-clap.mobs-clapped", 2500, 1500); + } + + @Override + public void addStats(int level, Element v) { + v.addLore(C.GREEN + "+ " + Form.f(getRange(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.shockwave_clap.lore1")); + v.addLore(C.GREEN + "+ " + Form.f(getForce(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.shockwave_clap.lore2")); + v.addLore(C.YELLOW + "* " + Form.duration((double) getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.shockwave_clap.lore3")); + v.addLore(C.RED + "- " + getConfig().hungerCost + C.GRAY + " " + Localizer.dLocalize("unarmed.shockwave_clap.lore4")); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent e) { + Action action = e.getAction(); + if (action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) { + return; + } + + Player p = e.getPlayer(); + if (!p.isSneaking()) { + return; + } + + long now = System.currentTimeMillis(); + Long next = nextClapAt.get(p.getUniqueId()); + if (next != null && now < next) { + return; + } + + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + + int hungerCost = getConfig().hungerCost; + if (p.getFoodLevel() < hungerCost) { + return; + } + + art.arcane.adapt.api.adaptation.Adaptation.BlockActionContext context = resolveInteractContext(p, p.getLocation()); + if (context == null) { + return; + } + + int level = context.level(); + nextClapAt.put(p.getUniqueId(), now + getCooldownMillis(level)); + p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); + + double range = getRange(level); + double force = getForce(level); + Location origin = p.getLocation(); + Vector look = origin.getDirection().normalize(); + int affected = 0; + for (Entity nearby : p.getWorld().getNearbyEntities(origin, range, range, range)) { + if (!(nearby instanceof LivingEntity hit) || hit == p) { + continue; + } + + Vector to = hit.getLocation().toVector().subtract(origin.toVector()); + double lengthSquared = to.lengthSquared(); + if (lengthSquared <= 0.0001 || lengthSquared > range * range) { + continue; + } + + to.multiply(1 / Math.sqrt(lengthSquared)); + if (look.dot(to) < getConfig().coneDotThreshold) { + continue; + } + + if (!canDamageTarget(p, hit)) { + continue; + } + + hit.setVelocity(hit.getVelocity().multiply(0.2).add(to.multiply(force).setY(getUpwardForce(level)))); + affected++; + } + + SoundPlayer sp = SoundPlayer.of(p.getWorld()); + sp.play(origin, Sound.ENTITY_PLAYER_ATTACK_SWEEP, 1f, 0.6f); + sp.play(origin, Sound.BLOCK_BELL_RESONATE, 0.7f, 1.4f); + if (areParticlesEnabled()) { + p.getWorld().spawnParticle(Particle.EXPLOSION, origin.clone().add(look.clone().multiply(1.2)).add(0, 1, 0), 1, 0.1, 0.1, 0.1, 0.02); + p.getWorld().spawnParticle(Particle.CLOUD, origin.clone().add(look.clone().multiply(1.5)).add(0, 0.8, 0), 20, 0.6, 0.4, 0.6, 0.08); + } + + getPlayer(p).getData().addStat("unarmed.shockwave-clap.claps", 1); + if (affected > 0) { + xp(p, getConfig().xpPerTargetHit * affected); + getPlayer(p).getData().addStat("unarmed.shockwave-clap.mobs-clapped", affected); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + nextClapAt.remove(e.getPlayer().getUniqueId()); + } + + private double getRange(int level) { + return getConfig().rangeBase + (getLevelPercent(level) * getConfig().rangeFactor); + } + + private double getForce(int level) { + return getConfig().forceBase + (getLevelPercent(level) * getConfig().forceFactor); + } + + private double getUpwardForce(int level) { + return getConfig().upwardForceBase + (getLevelPercent(level) * getConfig().upwardForceFactor); + } + + private long getCooldownMillis(int level) { + return Math.max(1000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sneak and punch the air to clap a shockwave that knocks back enemies in a cone.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") + int maxLevel = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.7; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base shockwave range in blocks at level 1.", impact = "Higher values let the clap reach more distant enemies.") + double rangeBase = 3.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional shockwave range granted at max level.", impact = "Higher values extend the clap reach as levels increase.") + double rangeFactor = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knockback force at level 1.", impact = "Higher values launch enemies further.") + double forceBase = 0.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional knockback force granted at max level.", impact = "Higher values launch enemies further as levels increase.") + double forceFactor = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base upward knockback component at level 1.", impact = "Higher values pop enemies into the air more.") + double upwardForceBase = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Additional upward knockback granted at max level.", impact = "Higher values pop enemies higher as levels increase.") + double upwardForceFactor = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Look-direction dot threshold that defines the cone width.", impact = "Lower values widen the cone; higher values narrow it.") + double coneDotThreshold = 0.45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base clap cooldown in milliseconds at level 1.", impact = "Higher values make the ability usable less often.") + double cooldownMillisBase = 10000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Cooldown reduction in milliseconds granted at max level.", impact = "Higher values make the ability recharge faster as levels increase.") + double cooldownMillisFactor = 6000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Hunger points consumed per shockwave clap.", impact = "Higher values drain more food per clap; activation fails when food is below the cost. Set to 0 to disable the hunger cost.") + int hungerCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "XP granted per enemy knocked back by a clap.", impact = "Higher values speed up unarmed skill progression from claps.") + double xpPerTargetHit = 14; + } +} diff --git a/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedSuckerPunch.java b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedSuckerPunch.java new file mode 100644 index 000000000..17fa16dc5 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/adaptation/unarmed/UnarmedSuckerPunch.java @@ -0,0 +1,178 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.adaptation.unarmed; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.config.ConfigDescription; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.inventorygui.Element; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +public class UnarmedSuckerPunch extends SimpleAdaptation { + public UnarmedSuckerPunch() { + super("unarmed-sucker-punch"); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("unarmed.sucker_punch.description")); + setDisplayName(Localizer.dLocalize("unarmed.sucker_punch.name")); + setIcon(Material.OBSIDIAN); + setBaseCost(getConfig().baseCost); + setInitialCost(getConfig().initialCost); + setCostFactor(getConfig().costFactor); + setInterval(4944); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_unarmed_sucker_500") + .title(Localizer.dLocalize("advancement.challenge_unarmed_sucker_500.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_sucker_500.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_unarmed_sucker_500", "unarmed.sucker-punch.sucker-punches", 500, 400); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_unarmed_knockout") + .title(Localizer.dLocalize("advancement.challenge_unarmed_knockout.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_knockout.description")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()); + registerMilestone("challenge_unarmed_knockout", "unarmed.sucker-punch.one-punch-kills", 50, 1000); + } + + + @Override + public void addStats(int level, Element v) { + double f = getLevelPercent(level); + double d = getDamage(f); + v.addLore(C.GREEN + "+ " + Form.pc(d, 0) + C.GRAY + " " + Localizer.dLocalize("unarmed.sucker_punch.lore1")); + v.addLore(C.GRAY + Localizer.dLocalize("unarmed.sucker_punch.lore2")); + } + + private double getDamage(double f) { + return getConfig().baseDamage + (f * getConfig().damageFactor); + } + + @EventHandler + public void on(EntityDamageByEntityEvent e) { + art.arcane.adapt.api.adaptation.Adaptation.AttackContext attack = resolveAttackContext(e); + if (attack == null) { + return; + } + + Player p = attack.attacker(); + if (p.getInventory().getItemInMainHand().getType() != Material.AIR && p.getInventory().getItemInOffHand().getType() != Material.AIR) { + return; + } + double factor = getLevelPercent(attack.level()); + + if (!p.isSprinting()) { + return; + } + + if (factor <= 0) { + return; + } + + if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { + return; + } + + e.setDamage(e.getDamage() * getDamage(factor)); + SoundPlayer spw = SoundPlayer.of(e.getEntity().getWorld()); + spw.play(e.getEntity().getLocation(), Sound.ENTITY_PLAYER_ATTACK_STRONG, 1f, 1.8f); + spw.play(e.getEntity().getLocation(), Sound.BLOCK_BASALT_BREAK, 1f, 0.6f); + xp(p, 6.221 * e.getDamage(), "sucker-punch"); + getPlayer(p).getData().addStat("unarmed.sucker-punch.sucker-punches", 1); + if (e.getDamage() > 5) { + xp(p, 0.42 * e.getDamage(), "bonus-damage"); + if (areParticlesEnabled()) { + e.getEntity().getWorld().spawnParticle(Particle.FLASH, e.getEntity().getLocation(), 1); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(EntityDeathEvent e) { + if (!(e.getEntity() instanceof LivingEntity victim)) { + return; + } + if (victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg + && dmg.getDamager() instanceof Player p + && hasActiveAdaptation(p) + && p.isSprinting() + && !isTool(p.getInventory().getItemInMainHand()) + && !isTool(p.getInventory().getItemInOffHand())) { + // One-punch kill: entity was at full health before the killing blow + if (victim.getMaxHealth() <= dmg.getFinalDamage()) { + getPlayer(p).getData().addStat("unarmed.sucker-punch.one-punch-kills", 1); + } + } + } + + @Override + public void onTick() { + + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + public boolean isPermanent() { + return getConfig().permanent; + } + + @NoArgsConstructor + @ConfigDescription("Sprint punches deal extra damage based on your speed.") + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") + boolean permanent = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Unarmed Sucker Punch adaptation.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") + int baseCost = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") + int initialCost = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") + double costFactor = 0.225; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Damage for the Unarmed Sucker Punch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseDamage = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Unarmed Sucker Punch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageFactor = 0.55; + } +} diff --git a/src/main/java/art/arcane/adapt/content/block/ScaffoldMatter.java b/src/main/java/art/arcane/adapt/content/block/ScaffoldMatter.java new file mode 100644 index 000000000..53a9d59c2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/block/ScaffoldMatter.java @@ -0,0 +1,61 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.block; + +import art.arcane.spatial.matter.slices.RawMatter; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.UUID; + +public class ScaffoldMatter extends RawMatter { + public ScaffoldMatter(int width, int height, int depth) { + super(width, height, depth, ScaffoldData.class); + } + + @Override + public void writeNode(ScaffoldData scaffoldData, DataOutputStream dos) throws IOException { + dos.writeLong(scaffoldData.getUuid().getMostSignificantBits()); + dos.writeLong(scaffoldData.getUuid().getLeastSignificantBits()); + dos.writeLong(scaffoldData.getTime()); + } + + @Override + public ScaffoldData readNode(DataInputStream din) throws IOException { + return ScaffoldData.builder() + .uuid(new UUID(din.readLong(), din.readLong())) + .time(din.readLong()) + .build(); + } + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class ScaffoldData { + private UUID uuid; + @Builder.Default + private long time = 0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/event/AdaptAdaptationEvent.java b/src/main/java/art/arcane/adapt/content/event/AdaptAdaptationEvent.java new file mode 100644 index 000000000..8dcbae91a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/event/AdaptAdaptationEvent.java @@ -0,0 +1,49 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.event; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.PlayerSkillLine; + +public class AdaptAdaptationEvent extends AdaptPlayerEvent { + private final Skill skill; + private final PlayerSkillLine playerSkill; + private final Adaptation adaptation; + + public AdaptAdaptationEvent(boolean async, AdaptPlayer player, Adaptation adaptation) { + super(async, player); + this.adaptation = adaptation; + this.playerSkill = player.getSkillLine(adaptation.getSkill().getId()); + this.skill = adaptation.getSkill(); + } + + public Skill getSkill() { + return skill; + } + + public Adaptation getAdaptation() { + return adaptation; + } + + public PlayerSkillLine getPlayerSkill() { + return playerSkill; + } +} diff --git a/src/main/java/art/arcane/adapt/content/event/AdaptAdaptationTeleportEvent.java b/src/main/java/art/arcane/adapt/content/event/AdaptAdaptationTeleportEvent.java new file mode 100644 index 000000000..61d1812f5 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/event/AdaptAdaptationTeleportEvent.java @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.event; + +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.world.AdaptPlayer; +import lombok.Getter; +import org.bukkit.Location; + +public class AdaptAdaptationTeleportEvent extends AdaptAdaptationEvent { + @Getter + Location fromLocation, toLocation; + + public AdaptAdaptationTeleportEvent(boolean async, AdaptPlayer player, Adaptation adaptation, Location fromLocation, Location toLocation) { + super(async, player, adaptation); + this.fromLocation = fromLocation; + this.toLocation = toLocation; + } +} diff --git a/src/main/java/com/volmit/adapt/content/event/AdaptAdaptationUseEvent.java b/src/main/java/art/arcane/adapt/content/event/AdaptAdaptationUseEvent.java similarity index 78% rename from src/main/java/com/volmit/adapt/content/event/AdaptAdaptationUseEvent.java rename to src/main/java/art/arcane/adapt/content/event/AdaptAdaptationUseEvent.java index 87452030c..94e8b53d5 100644 --- a/src/main/java/com/volmit/adapt/content/event/AdaptAdaptationUseEvent.java +++ b/src/main/java/art/arcane/adapt/content/event/AdaptAdaptationUseEvent.java @@ -16,13 +16,13 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.content.event; +package art.arcane.adapt.content.event; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.world.AdaptPlayer; public class AdaptAdaptationUseEvent extends AdaptAdaptationEvent { - public AdaptAdaptationUseEvent(boolean async, AdaptPlayer player, Adaptation adaptation) { - super(async, player, adaptation); - } + public AdaptAdaptationUseEvent(boolean async, AdaptPlayer player, Adaptation adaptation) { + super(async, player, adaptation); + } } diff --git a/src/main/java/art/arcane/adapt/content/event/AdaptEvent.java b/src/main/java/art/arcane/adapt/content/event/AdaptEvent.java new file mode 100644 index 000000000..41bd61a83 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/event/AdaptEvent.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.event; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.world.AdaptServer; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class AdaptEvent extends Event implements Cancellable { + private static final HandlerList HANDLERS = new HandlerList(); + private boolean canceled; + + + public AdaptEvent(boolean async) { + super(async); + canceled = false; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + + public AdaptServer getServer() { + return Adapt.instance.getAdaptServer(); + } + + @Override + public boolean isCancelled() { + return canceled; + } + + @Override + public void setCancelled(boolean b) { + canceled = b; + } + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } +} diff --git a/src/main/java/com/volmit/adapt/content/event/AdaptPlayerEvent.java b/src/main/java/art/arcane/adapt/content/event/AdaptPlayerEvent.java similarity index 76% rename from src/main/java/com/volmit/adapt/content/event/AdaptPlayerEvent.java rename to src/main/java/art/arcane/adapt/content/event/AdaptPlayerEvent.java index d7b35bf1b..3b99ebc73 100644 --- a/src/main/java/com/volmit/adapt/content/event/AdaptPlayerEvent.java +++ b/src/main/java/art/arcane/adapt/content/event/AdaptPlayerEvent.java @@ -16,19 +16,19 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.content.event; +package art.arcane.adapt.content.event; -import com.volmit.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.AdaptPlayer; public class AdaptPlayerEvent extends AdaptEvent { - private final AdaptPlayer player; + private final AdaptPlayer player; - public AdaptPlayerEvent(boolean async, AdaptPlayer player) { - super(async); - this.player = player; - } + public AdaptPlayerEvent(boolean async, AdaptPlayer player) { + super(async); + this.player = player; + } - public AdaptPlayer getPlayer() { - return player; - } + public AdaptPlayer getPlayer() { + return player; + } } diff --git a/src/main/java/art/arcane/adapt/content/gui/ConfigGui.java b/src/main/java/art/arcane/adapt/content/gui/ConfigGui.java new file mode 100644 index 000000000..7260b803b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/gui/ConfigGui.java @@ -0,0 +1,1509 @@ +package art.arcane.adapt.content.gui; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.service.ConfigInputSVC; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.inventorygui.GuiEffects; +import art.arcane.adapt.util.common.inventorygui.GuiLayout; +import art.arcane.adapt.util.common.inventorygui.GuiTheme; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigDocumentation; +import art.arcane.adapt.util.config.TomlCodec; +import art.arcane.volmlib.util.data.MaterialBlock; +import art.arcane.volmlib.util.format.ColorFormatter; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.inventorygui.UIElement; +import art.arcane.volmlib.util.inventorygui.UIWindow; +import art.arcane.volmlib.util.inventorygui.Window; +import art.arcane.volmlib.util.io.IO; +import art.arcane.volmlib.util.math.M; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public final class ConfigGui { + private static final String TAG_PREFIX = "config/adapt"; + private static final String SOURCE_TAG_CORE = "core-config"; + private static final String ROOT_CORE = "core"; + private static final String ROOT_CORE_GENERAL = ROOT_CORE + ".$general"; + private static final String ROOT_SKILLS = "skills"; + private static final String ROOT_ADAPTATIONS = "adaptations"; + private static final String ROOT_ADAPTATIONS_SKILLS = ROOT_ADAPTATIONS + ".$skills"; + private static final String ROOT_ADAPTATIONS_ALL = ROOT_ADAPTATIONS + ".$all"; + private static final Object WRITE_LOCK = new Object(); + private static final int MAX_VALUE_PREVIEW = 64; + private static final int PAGE_JUMP = 5; + private static final int CONFIG_CONTENT_ROWS = GuiLayout.MAX_ROWS - 1; + private static final long CLOSE_SUPPRESS_MS = 1200L; + private static final int CLOSE_SUPPRESS_CLEAR_TICKS = 4; + private static final Map CLOSE_SUPPRESS_UNTIL = new ConcurrentHashMap<>(); + + private ConfigGui() { + } + + public static boolean canConfigure(Player player) { + return player != null && (player.isOp() || player.hasPermission("adapt.configurator")); + } + + public static void open(Player player) { + open(player, "", 0); + } + + public static void open(Player player, String sectionPath) { + open(player, sectionPath, 0); + } + + public static void open(Player player, String sectionPath, int page) { + if (player == null) { + return; + } + + if (!canConfigure(player)) { + Adapt.messagePlayer(player, C.RED + "You do not have permission to use the config menu."); + return; + } + + if (!J.isPrimaryThread()) { + String path = sectionPath; + int targetPage = page; + J.runEntity(player, () -> open(player, path, targetPage)); + return; + } + + playPageTurn(player); + String safePath = normalizePath(sectionPath); + if (safePath.isBlank()) { + openRoot(player, page); + return; + } + + if (safePath.equals(ROOT_SKILLS)) { + openSkillIndex(player, page); + return; + } + + if (safePath.equals(ROOT_CORE)) { + openCoreIndex(player, page); + return; + } + + if (safePath.equals(ROOT_CORE_GENERAL)) { + openCoreGeneral(player, page); + return; + } + + if (safePath.equals(ROOT_ADAPTATIONS) || safePath.equals(ROOT_ADAPTATIONS_SKILLS)) { + openAdaptationSkillIndex(player, page); + return; + } + + if (safePath.equals(ROOT_ADAPTATIONS_ALL)) { + openAdaptationIndex(player, page); + return; + } + + if (safePath.startsWith(ROOT_ADAPTATIONS_SKILLS + ".")) { + String skillName = safePath.substring(ROOT_ADAPTATIONS_SKILLS.length() + 1); + openAdaptationIndexForSkill(player, skillName, page); + return; + } + + SectionTarget target = resolveSectionTarget(safePath, false); + if (target == null || target.sectionObject() == null) { + Adapt.messagePlayer(player, C.RED + "Unable to open config section: " + C.WHITE + safePath); + return; + } + + List entries = buildEntries(safePath, target.sectionObject(), target.sourceTag()); + openFieldEntries(player, safePath, entries, page); + } + + public static void reopenFromTag(Player player, String tag) { + if (player == null) { + return; + } + + if (tag == null || tag.isBlank()) { + open(player); + return; + } + + if (!tag.startsWith(TAG_PREFIX)) { + open(player); + return; + } + + String path = ""; + if (tag.length() > TAG_PREFIX.length()) { + path = tag.substring(TAG_PREFIX.length()); + if (path.startsWith("/")) { + path = path.substring(1); + } + } + + navigateTo(player, path, 0); + } + + public static ParseResult parseInputValue(Class type, String raw) { + return ConfigGuiValueCodec.parseInputValue(type, raw); + } + + public static boolean applyAndSave(Player actor, String valuePath, Object value) { + String path = normalizePath(valuePath); + if (path.isBlank()) { + return false; + } + + synchronized (WRITE_LOCK) { + EditTarget target = resolveEditTarget(path); + if (target == null) { + if (actor != null) { + Adapt.messagePlayer(actor, C.RED + "Failed to set config value at " + C.WHITE + path); + } + return false; + } + + Object before = readPathValue(target.rootObject(), target.objectPath()); + String beforeToml = TomlCodec.toToml(target.rootObject(), target.sourceTag()); + + if (!setPathValue(target.rootObject(), target.objectPath(), value, true)) { + if (actor != null) { + Adapt.messagePlayer(actor, C.RED + "Failed to set config value at " + C.WHITE + path); + } + return false; + } + + try { + String updatedToml = TomlCodec.toToml(target.rootObject(), target.sourceTag()); + IO.writeAll(target.file(), updatedToml); + } catch (Throwable e) { + J.attempt(() -> IO.writeAll(target.file(), beforeToml)); + target.reload().getAsBoolean(); + if (actor != null) { + Adapt.messagePlayer(actor, C.RED + "Failed to persist config update: " + C.WHITE + e.getMessage()); + } + return false; + } + + if (!target.reload().getAsBoolean()) { + J.attempt(() -> IO.writeAll(target.file(), beforeToml)); + target.reload().getAsBoolean(); + if (actor != null) { + Adapt.messagePlayer(actor, C.RED + "Config reload failed. Reverted file changes."); + } + return false; + } + + target.afterReload().run(); + if (actor != null) { + Adapt.messagePlayer(actor, C.GREEN + "Updated " + C.WHITE + path + + C.GRAY + " [" + summarizeValue(before) + C.GRAY + " -> " + summarizeValue(value) + C.GRAY + "]"); + } + return true; + } + } + + public static void confirmAndApply(Player actor, String returnSectionPath, String valuePath, Object value) { + confirmAndApply(actor, returnSectionPath, 0, valuePath, value); + } + + public static void confirmAndApply(Player actor, String returnSectionPath, int returnPage, String valuePath, Object value) { + String path = normalizePath(valuePath); + if (path.isBlank()) { + return; + } + + String section = normalizePath(returnSectionPath); + applyAndSave(actor, path, value); + navigateTo(actor, section, Math.max(0, returnPage)); + } + + public static String typeName(Class type) { + return ConfigGuiValueCodec.typeName(type); + } + + private static ElementDescriptor describe(Field field, Object value) { + Class type = normalizeType(field.getType()); + if (type == Boolean.class) { + return new ElementDescriptor(ElementKind.BOOLEAN, true); + } + + if (isNumericType(type)) { + return new ElementDescriptor(ElementKind.NUMBER, true); + } + + if (type == String.class || type == Character.class) { + return new ElementDescriptor(ElementKind.STRING, true); + } + + if (type.isEnum()) { + return new ElementDescriptor(ElementKind.ENUM, true); + } + + if (Map.class.isAssignableFrom(type)) { + return new ElementDescriptor(ElementKind.MAP, false); + } + + if (Collection.class.isAssignableFrom(type) || type.isArray()) { + return new ElementDescriptor(ElementKind.LIST, false); + } + + if (value != null || isSectionType(type)) { + return new ElementDescriptor(ElementKind.SECTION, false); + } + + return new ElementDescriptor(ElementKind.UNSUPPORTED, false); + } + + private static Element createElementForEntry(Player player, String sectionPath, int currentPage, FieldEntry entry) { + Material material = materialFor(entry); + String typePrefix = switch (entry.descriptor().kind()) { + case BOOLEAN -> C.GREEN + "[Boolean] "; + case NUMBER -> C.AQUA + "[Number] "; + case STRING -> C.YELLOW + "[Text] "; + case ENUM -> C.LIGHT_PURPLE + "[Enum] "; + case SECTION -> C.BLUE + "[Section] "; + case MAP -> C.GOLD + "[Map] "; + case LIST -> C.GOLD + "[List] "; + case UNSUPPORTED -> C.RED + "[Unsupported] "; + }; + String name = displayName(entry.field().getName()); + String value = summarizeValue(entry.value()); + + Element element = new UIElement("cfg-" + entry.path()) + .setMaterial(new MaterialBlock(material)) + .setName(typePrefix + C.WHITE + name); + element.addLore(C.GRAY + "Value: " + C.AQUA + value); + element.addLore(C.DARK_GRAY + "Path: " + entry.path()); + element.setProgress(1D); + if (entry.descriptor().kind() == ElementKind.BOOLEAN && Boolean.TRUE.equals(entry.value())) { + element.setEnchanted(true); + } + + if (entry.descriptor().kind() == ElementKind.SECTION && entry.value() != null) { + int nested = getSerializableFields(entry.value().getClass()).size(); + element.addLore(C.GRAY + "Contains " + C.WHITE + nested + C.GRAY + " setting" + (nested == 1 ? "" : "s")); + element.addLore(C.DARK_GRAY + "Category: " + sectionCategory(entry.field().getName())); + } + + int docsShown = 0; + for (String line : entry.docs()) { + if (line == null || line.isBlank()) { + continue; + } + if (docsShown >= 3) { + element.addLore(C.DARK_GRAY + "..."); + break; + } + element.addLore(C.GRAY + line); + docsShown++; + } + + ElementKind kind = entry.descriptor().kind(); + if (kind == ElementKind.BOOLEAN) { + element.addLore(Boolean.TRUE.equals(entry.value()) + ? C.GREEN + "State: Enabled" + : C.RED + "State: Disabled"); + element.addLore(C.GREEN + "Left click: toggle"); + element.onLeftClick((e) -> { + boolean toggled = !Boolean.TRUE.equals(entry.value()); + confirmAndApply(player, sectionPath, currentPage, entry.path(), toggled); + }); + } else if (kind == ElementKind.ENUM) { + element.addLore(C.GREEN + "Left click: next value"); + element.addLore(C.GREEN + "Right click: previous value"); + element.onLeftClick((e) -> { + Object next = cycleEnum(entry.field().getType(), entry.value(), 1); + if (next != null) { + confirmAndApply(player, sectionPath, currentPage, entry.path(), next); + } + }); + element.onRightClick((e) -> { + Object previous = cycleEnum(entry.field().getType(), entry.value(), -1); + if (previous != null) { + confirmAndApply(player, sectionPath, currentPage, entry.path(), previous); + } + }); + } else if (kind == ElementKind.NUMBER || kind == ElementKind.STRING) { + element.addLore(C.YELLOW + "Left click: edit in chat"); + element.onLeftClick((e) -> { + ConfigInputSVC service = Adapt.service(ConfigInputSVC.class); + if (service == null) { + Adapt.messagePlayer(player, C.RED + "Config input service is unavailable."); + return; + } + + service.beginSession(player, entry.path(), sectionPath, currentPage, entry.field().getType(), displayName(entry.field().getName())); + }); + } else if (kind == ElementKind.SECTION) { + element.addLore(C.GREEN + "Left click: open section"); + element.onLeftClick((e) -> navigateTo(player, entry.path(), 0)); + } else if (kind == ElementKind.MAP || kind == ElementKind.LIST) { + element.addLore(C.RED + "Read-only in Phase 1"); + } else if (kind == ElementKind.UNSUPPORTED) { + element.addLore(C.RED + "Unsupported type"); + } + + return element; + } + + private static Material materialFor(FieldEntry entry) { + return switch (entry.descriptor().kind()) { + case BOOLEAN -> + Boolean.TRUE.equals(entry.value()) ? Material.LIME_DYE : Material.GRAY_DYE; + case NUMBER -> Material.CLOCK; + case STRING -> Material.NAME_TAG; + case ENUM -> Material.BOOK; + case SECTION -> materialForSection(entry.field().getName()); + case MAP -> Material.CHEST_MINECART; + case LIST -> Material.BARREL; + case UNSUPPORTED -> Material.BARRIER; + }; + } + + private static Material materialForSection(String name) { + String key = normalizeSortKey(name); + if (key.contains("gui") || key.contains("menu") || key.contains("display") || key.contains("hud")) { + return Material.BOOKSHELF; + } + if (key.contains("sound") || key.contains("audio")) { + return Material.JUKEBOX; + } + if (key.contains("lang") || key.contains("locale") || key.contains("translation")) { + return Material.WRITABLE_BOOK; + } + if (key.contains("sql") || key.contains("database") || key.contains("storage") || key.contains("mysql")) { + return Material.ENDER_CHEST; + } + if (key.contains("xp") || key.contains("level") || key.contains("knowledge") || key.contains("power")) { + return Material.EXPERIENCE_BOTTLE; + } + if (key.contains("world") || key.contains("biome") || key.contains("dimension") || key.contains("region")) { + return Material.GRASS_BLOCK; + } + if (key.contains("thread") || key.contains("tick") || key.contains("async") || key.contains("performance") || key.contains("cache")) { + return Material.REDSTONE; + } + if (key.contains("permission") || key.contains("blacklist") || key.contains("whitelist") || key.contains("security")) { + return Material.SHIELD; + } + if (key.contains("debug") || key.contains("dev") || key.contains("test") || key.contains("verbose")) { + return Material.SPYGLASS; + } + return Material.CHEST; + } + + private static String sectionCategory(String name) { + String key = normalizeSortKey(name); + if (key.contains("gui") || key.contains("menu") || key.contains("display") || key.contains("hud")) { + return "UI"; + } + if (key.contains("sound") || key.contains("audio")) { + return "Audio"; + } + if (key.contains("lang") || key.contains("locale") || key.contains("translation")) { + return "Localization"; + } + if (key.contains("sql") || key.contains("database") || key.contains("storage") || key.contains("mysql")) { + return "Storage"; + } + if (key.contains("xp") || key.contains("level") || key.contains("knowledge") || key.contains("power")) { + return "Progression"; + } + if (key.contains("world") || key.contains("biome") || key.contains("dimension") || key.contains("region")) { + return "World"; + } + if (key.contains("thread") || key.contains("tick") || key.contains("async") || key.contains("performance") || key.contains("cache")) { + return "Performance"; + } + if (key.contains("permission") || key.contains("blacklist") || key.contains("whitelist") || key.contains("security")) { + return "Access"; + } + if (key.contains("debug") || key.contains("dev") || key.contains("test") || key.contains("verbose")) { + return "Debug"; + } + return "General"; + } + + private static List buildEntries(String sectionPath, Object sectionObject, String sourceTag) { + List sections = new ArrayList<>(); + List values = new ArrayList<>(); + for (Field field : getSerializableFields(sectionObject.getClass())) { + Object value = getFieldValue(field, sectionObject); + String childPath = joinPath(sectionPath, field.getName()); + ElementDescriptor descriptor = describe(field, value); + List docs = ConfigDocumentation.buildFieldComments(sourceTag, childPath, field, value); + FieldEntry entry = new FieldEntry(field, childPath, value, descriptor, docs); + if (descriptor.kind() == ElementKind.SECTION) { + sections.add(entry); + } else { + values.add(entry); + } + } + + sections.sort(Comparator.comparing(e -> normalizeSortKey(e.field().getName()))); + values.sort(Comparator.comparing(e -> normalizeSortKey(e.field().getName()))); + sections.addAll(values); + return sections; + } + + private static List buildCoreGeneralEntries() { + List values = new ArrayList<>(); + Object root = AdaptConfig.get(); + for (Field field : getSerializableFields(root.getClass())) { + Object value = getFieldValue(field, root); + ElementDescriptor descriptor = describe(field, value); + if (descriptor.kind() == ElementKind.SECTION) { + continue; + } + + String childPath = joinPath(ROOT_CORE, field.getName()); + List docs = ConfigDocumentation.buildFieldComments(SOURCE_TAG_CORE, childPath, field, value); + values.add(new FieldEntry(field, childPath, value, descriptor, docs)); + } + + values.sort(Comparator.comparing(e -> normalizeSortKey(e.field().getName()))); + return values; + } + + private static void openFieldEntries(Player player, String safePath, List entries, int page) { + GuiLayout.PagePlan plan = configPagePlan(entries.size()); + int currentPage = GuiLayout.clampPage(page, plan.pageCount()); + int start = currentPage * plan.itemsPerPage(); + int end = Math.min(entries.size(), start + plan.itemsPerPage()); + + UIWindow w = new UIWindow(Adapt.instance, player); + GuiTheme.apply(w, tagForSection(safePath)); + w.setViewportHeight(plan.rows()); + + if (entries.isEmpty()) { + w.setElement(0, 0, new UIElement("cfg-empty") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.GRAY + "No settings in this section")); + } else { + List reveal = new ArrayList<>(); + for (int row = 0; row < plan.contentRows(); row++) { + int rowStart = start + (row * GuiLayout.WIDTH); + if (rowStart >= end) { + break; + } + + int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); + for (int i = 0; i < rowCount; i++) { + FieldEntry entry = entries.get(rowStart + i); + int pos = GuiLayout.centeredPosition(i, rowCount); + Element element = createElementForEntry(player, safePath, currentPage, entry); + reveal.add(new GuiEffects.Placement(pos, row, element)); + } + } + GuiEffects.applyReveal(w, reveal); + } + + int navRow = plan.rows() - 1; + applyPageControls(w, player, safePath, navRow, currentPage, plan.pageCount(), entries.size(), start, end); + if (!safePath.isBlank()) { + String parent = parentPath(safePath); + w.setElement(0, navRow, new UIElement("cfg-back") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.GRAY + "Back") + .onLeftClick((e) -> navigateTo(player, parent, 0))); + } + addSectionOverview(w, navRow, safePath, entries, currentPage, plan.pageCount()); + + String titlePath = safePath.isBlank() ? "root" : safePath; + if (safePath.equals(ROOT_CORE_GENERAL)) { + titlePath = "core.general"; + } + if (titlePath.length() > 24) { + titlePath = "..." + titlePath.substring(titlePath.length() - 21); + } + w.setTitle(C.GRAY + "Configure: " + C.WHITE + titlePath); + w.onClosed((window) -> onGuiClosed(player, safePath)); + w.open(); + Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); + } + + private static void openCoreIndex(Player player, int page) { + Object root = AdaptConfig.get(); + List entries = new ArrayList<>(); + int generalValues = 0; + for (Field field : getSerializableFields(root.getClass())) { + Object value = getFieldValue(field, root); + ElementDescriptor descriptor = describe(field, value); + if (descriptor.kind() == ElementKind.SECTION) { + int nested = value == null ? 0 : getSerializableFields(value.getClass()).size(); + entries.add(new SectionIndexEntry( + ROOT_CORE + "." + field.getName(), + displayName(field.getName()), + materialForSection(field.getName()), + "Open " + nested + " setting" + (nested == 1 ? "" : "s") + )); + } else { + generalValues++; + } + } + + if (generalValues > 0) { + entries.add(new SectionIndexEntry( + ROOT_CORE_GENERAL, + "General Settings", + Material.COMPARATOR, + "Open " + generalValues + " global option" + (generalValues == 1 ? "" : "s") + )); + } + + entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); + openSectionIndex(player, ROOT_CORE, page, "Configure: core", entries); + } + + private static void openCoreGeneral(Player player, int page) { + openFieldEntries(player, ROOT_CORE_GENERAL, buildCoreGeneralEntries(), page); + } + + private static void openRoot(Player player, int page) { + List entries = new ArrayList<>(); + entries.add(new SectionIndexEntry(ROOT_ADAPTATIONS_SKILLS, "Adaptations", Material.NETHER_STAR, "Configure adaptation settings")); + entries.add(new SectionIndexEntry(ROOT_CORE, "Core", Material.COMPARATOR, "Configure global Adapt settings")); + entries.add(new SectionIndexEntry(ROOT_SKILLS, "Skills", Material.ENCHANTED_BOOK, "Configure skill settings")); + entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); + openSectionIndex(player, "", page, "Configure Adapt", entries); + } + + private static void openSkillIndex(Player player, int page) { + List> skills = getLoadedSkills(); + List entries = new ArrayList<>(); + for (Skill skill : skills) { + if (skill == null) { + continue; + } + + entries.add(new SectionIndexEntry( + ROOT_SKILLS + "." + skill.getName(), + ColorFormatter.stripColor(skill.getDisplayName()), + skill.getIcon(), + "Configure " + skill.getName() + )); + } + + entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); + openSectionIndex(player, ROOT_SKILLS, page, "Configure: skills", entries); + } + + private static void openAdaptationSkillIndex(Player player, int page) { + List entries = new ArrayList<>(); + entries.add(new SectionIndexEntry( + ROOT_ADAPTATIONS_ALL, + "All Adaptations (A-Z)", + Material.NETHER_STAR, + "Browse every adaptation alphabetically" + )); + + for (Skill skill : getLoadedSkills()) { + if (skill == null) { + continue; + } + + int adaptationCount = skill.getAdaptations() == null ? 0 : skill.getAdaptations().size(); + if (adaptationCount <= 0) { + continue; + } + + entries.add(new SectionIndexEntry( + ROOT_ADAPTATIONS_SKILLS + "." + skill.getName(), + ColorFormatter.stripColor(skill.getDisplayName()), + skill.getIcon(), + "Browse " + adaptationCount + " adaptation" + (adaptationCount == 1 ? "" : "s") + )); + } + + List head = new ArrayList<>(); + List skillEntries = new ArrayList<>(); + for (SectionIndexEntry entry : entries) { + if (ROOT_ADAPTATIONS_ALL.equals(entry.path())) { + head.add(entry); + } else { + skillEntries.add(entry); + } + } + skillEntries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); + head.addAll(skillEntries); + openSectionIndex(player, ROOT_ADAPTATIONS_SKILLS, page, "Configure: adaptations", head); + } + + private static void openAdaptationIndexForSkill(Player player, String skillName, int page) { + Skill skill = resolveSkill(skillName); + if (skill == null) { + Adapt.messagePlayer(player, C.RED + "Unknown skill for adaptation config: " + C.WHITE + skillName); + navigateTo(player, ROOT_ADAPTATIONS_SKILLS, 0); + return; + } + + List entries = new ArrayList<>(); + for (Adaptation adaptation : skill.getAdaptations()) { + if (adaptation == null) { + continue; + } + entries.add(new SectionIndexEntry( + ROOT_ADAPTATIONS + "." + adaptation.getName(), + ColorFormatter.stripColor(adaptation.getDisplayName()), + adaptation.getIcon(), + "Open " + adaptation.getName() + )); + } + + entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); + openSectionIndex(player, ROOT_ADAPTATIONS_SKILLS + "." + skill.getName(), page, "Configure: " + ColorFormatter.stripColor(skill.getDisplayName()), entries); + } + + private static void openAdaptationIndex(Player player, int page) { + List entries = new ArrayList<>(); + for (Adaptation adaptation : getLoadedAdaptations()) { + if (adaptation == null || adaptation.getSkill() == null) { + continue; + } + + entries.add(new SectionIndexEntry( + ROOT_ADAPTATIONS + "." + adaptation.getName(), + ColorFormatter.stripColor(adaptation.getDisplayName()), + adaptation.getIcon(), + "Skill: " + adaptation.getSkill().getName() + )); + } + + entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); + openSectionIndex(player, ROOT_ADAPTATIONS_ALL, page, "Configure: all adaptations", entries); + } + + private static void openSectionIndex(Player player, String sectionPath, int page, String title, List entries) { + String safePath = normalizePath(sectionPath); + GuiLayout.PagePlan plan = configPagePlan(entries.size()); + int currentPage = GuiLayout.clampPage(page, plan.pageCount()); + int start = currentPage * plan.itemsPerPage(); + int end = Math.min(entries.size(), start + plan.itemsPerPage()); + + UIWindow w = new UIWindow(Adapt.instance, player); + GuiTheme.apply(w, tagForSection(safePath)); + w.setViewportHeight(plan.rows()); + + if (entries.isEmpty()) { + w.setElement(0, 0, new UIElement("cfg-empty") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.GRAY + "No entries")); + } else { + List reveal = new ArrayList<>(); + for (int row = 0; row < plan.contentRows(); row++) { + int rowStart = start + (row * GuiLayout.WIDTH); + if (rowStart >= end) { + break; + } + + int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); + for (int i = 0; i < rowCount; i++) { + SectionIndexEntry entry = entries.get(rowStart + i); + int pos = GuiLayout.centeredPosition(i, rowCount); + Element element = new UIElement("cfg-index-" + entry.path()) + .setMaterial(new MaterialBlock(entry.material())) + .setName(C.WHITE + entry.displayName()) + .addLore(C.GRAY + entry.lore()) + .addLore(C.DARK_GRAY + "Path: " + entry.path()) + .setProgress(1D) + .onLeftClick((e) -> navigateTo(player, entry.path(), 0)); + reveal.add(new GuiEffects.Placement(pos, row, element)); + } + } + GuiEffects.applyReveal(w, reveal); + } + + int navRow = plan.rows() - 1; + applyPageControls(w, player, safePath, navRow, currentPage, plan.pageCount(), entries.size(), start, end); + if (!safePath.isBlank()) { + w.setElement(0, navRow, new UIElement("cfg-back") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.GRAY + "Back") + .onLeftClick((e) -> navigateTo(player, parentPath(safePath), 0))); + } + addIndexOverview(w, navRow, safePath, entries.size(), currentPage, plan.pageCount(), title); + + w.setTitle(C.GRAY + title); + w.onClosed((window) -> onGuiClosed(player, safePath)); + w.open(); + Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); + } + + private static GuiLayout.PagePlan configPagePlan(int totalEntries) { + int items = Math.max(0, totalEntries); + int rows = GuiLayout.MAX_ROWS; + int contentRows = CONFIG_CONTENT_ROWS; + int itemsPerPage = contentRows * GuiLayout.WIDTH; + int pageCount = Math.max(1, (int) Math.ceil(items / (double) itemsPerPage)); + return new GuiLayout.PagePlan(rows, contentRows, true, itemsPerPage, pageCount); + } + + private static void applyPageControls( + Window window, + Player player, + String safePath, + int navRow, + int currentPage, + int pageCount, + int totalEntries, + int start, + int end + ) { + if (pageCount <= 1) { + return; + } + + int jumpBack = Math.max(0, currentPage - PAGE_JUMP); + int jumpForward = Math.min(pageCount - 1, currentPage + PAGE_JUMP); + + if (currentPage > 0) { + window.setElement(-4, navRow, new UIElement("cfg-prev") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.WHITE + "Previous") + .addLore(C.GRAY + "Left click: previous page") + .addLore(C.GRAY + "Right click: jump -" + PAGE_JUMP + " pages") + .onLeftClick((e) -> navigateTo(player, safePath, currentPage - 1)) + .onRightClick((e) -> navigateTo(player, safePath, jumpBack))); + window.setElement(-3, navRow, new UIElement("cfg-first") + .setMaterial(new MaterialBlock(Material.LECTERN)) + .setName(C.GRAY + "First") + .onLeftClick((e) -> navigateTo(player, safePath, 0))); + } + + if (currentPage < pageCount - 1) { + window.setElement(4, navRow, new UIElement("cfg-next") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.WHITE + "Next") + .addLore(C.GRAY + "Left click: next page") + .addLore(C.GRAY + "Right click: jump +" + PAGE_JUMP + " pages") + .onLeftClick((e) -> navigateTo(player, safePath, currentPage + 1)) + .onRightClick((e) -> navigateTo(player, safePath, jumpForward))); + window.setElement(3, navRow, new UIElement("cfg-last") + .setMaterial(new MaterialBlock(Material.LECTERN)) + .setName(C.GRAY + "Last") + .onLeftClick((e) -> navigateTo(player, safePath, pageCount - 1))); + } + + int from = totalEntries <= 0 ? 0 : (start + 1); + int to = totalEntries <= 0 ? 0 : end; + window.setElement(-1, navRow, new UIElement("cfg-page-info") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.AQUA + "Page " + (currentPage + 1) + "/" + pageCount) + .addLore(C.GRAY + "Showing " + from + "-" + to + " of " + totalEntries) + .setProgress(1D)); + } + + private static void addSectionOverview( + Window window, + int navRow, + String path, + List entries, + int currentPage, + int pageCount + ) { + int sections = 0; + int editable = 0; + for (FieldEntry entry : entries) { + if (entry.descriptor().kind() == ElementKind.SECTION) { + sections++; + } + if (entry.descriptor().editable()) { + editable++; + } + } + + String safePath = path == null || path.isBlank() ? "root" : path; + window.setElement(1, navRow, new UIElement("cfg-overview") + .setMaterial(new MaterialBlock(Material.BOOK)) + .setName(C.AQUA + "Overview") + .addLore(C.GRAY + "Path: " + C.WHITE + safePath) + .addLore(C.GRAY + "Sections: " + C.WHITE + sections) + .addLore(C.GRAY + "Editable: " + C.WHITE + editable) + .addLore(C.GRAY + "Entries: " + C.WHITE + entries.size()) + .setProgress(1D)); + + window.setElement(2, navRow, new UIElement("cfg-help") + .setMaterial(new MaterialBlock(Material.KNOWLEDGE_BOOK)) + .setName(C.GRAY + "Help") + .addLore(C.GRAY + "LMB: open/edit/toggle") + .addLore(C.GRAY + "RMB: enum prev / page jump") + .addLore(C.GRAY + "ESC: back to parent page") + .addLore(C.DARK_GRAY + "Page " + (currentPage + 1) + "/" + pageCount) + .setProgress(1D)); + } + + private static void addIndexOverview( + Window window, + int navRow, + String path, + int totalEntries, + int currentPage, + int pageCount, + String title + ) { + String safePath = path == null || path.isBlank() ? "root" : path; + window.setElement(1, navRow, new UIElement("cfg-index-overview") + .setMaterial(new MaterialBlock(Material.BOOK)) + .setName(C.AQUA + "Directory") + .addLore(C.GRAY + "Path: " + C.WHITE + safePath) + .addLore(C.GRAY + "Entries: " + C.WHITE + totalEntries) + .addLore(C.GRAY + "Page: " + C.WHITE + (currentPage + 1) + "/" + pageCount) + .addLore(C.DARK_GRAY + title) + .setProgress(1D)); + + window.setElement(2, navRow, new UIElement("cfg-index-help") + .setMaterial(new MaterialBlock(Material.KNOWLEDGE_BOOK)) + .setName(C.GRAY + "Navigation") + .addLore(C.GRAY + "LMB: open section") + .addLore(C.GRAY + "RMB on arrows: jump pages") + .addLore(C.GRAY + "ESC: back to parent page") + .setProgress(1D)); + } + + private static String tagForSection(String sectionPath) { + String path = normalizePath(sectionPath); + if (path.isBlank()) { + return TAG_PREFIX; + } + return TAG_PREFIX + "/" + path; + } + + private static String normalizePath(String path) { + if (path == null) { + return ""; + } + + String normalized = path.trim(); + while (normalized.startsWith(".")) { + normalized = normalized.substring(1); + } + while (normalized.endsWith(".")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + return normalized; + } + + private static String parentPath(String path) { + String normalized = normalizePath(path); + if (normalized.isBlank()) { + return ""; + } + + if (normalized.equals(ROOT_ADAPTATIONS) || normalized.equals(ROOT_ADAPTATIONS_SKILLS)) { + return ""; + } + + if (normalized.equals(ROOT_CORE_GENERAL)) { + return ROOT_CORE; + } + + if (normalized.equals(ROOT_ADAPTATIONS_ALL)) { + return ROOT_ADAPTATIONS_SKILLS; + } + + if (normalized.startsWith(ROOT_ADAPTATIONS_SKILLS + ".")) { + return ROOT_ADAPTATIONS_SKILLS; + } + + int dot = normalized.lastIndexOf('.'); + if (dot < 0) { + return ""; + } + return normalized.substring(0, dot); + } + + private static String joinPath(String base, String child) { + String left = normalizePath(base); + if (left.isBlank()) { + return child; + } + return left + "." + child; + } + + private static Object resolveSectionObject(Object root, String sectionPath, boolean createMissing) { + if (root == null) { + return null; + } + + String normalized = normalizePath(sectionPath); + if (normalized.isBlank()) { + return root; + } + + Object current = root; + for (String segment : normalized.split("\\Q.\\E")) { + Field field = findField(current.getClass(), segment); + if (field == null) { + return null; + } + + Object next = getFieldValue(field, current); + if (next == null && createMissing) { + next = instantiate(field.getType()); + if (next == null) { + return null; + } + if (!setFieldValue(field, current, next)) { + return null; + } + } + + if (next == null) { + return null; + } + + current = next; + } + + return current; + } + + private static boolean setPathValue(Object root, String path, Object value, boolean createMissing) { + String normalized = normalizePath(path); + if (normalized.isBlank()) { + return false; + } + + String[] segments = normalized.split("\\Q.\\E"); + if (segments.length == 0) { + return false; + } + + StringBuilder parentPath = new StringBuilder(); + for (int i = 0; i < segments.length - 1; i++) { + if (i > 0) { + parentPath.append('.'); + } + parentPath.append(segments[i]); + } + + Object section = resolveSectionObject(root, parentPath.toString(), createMissing); + if (section == null) { + return false; + } + + Field targetField = findField(section.getClass(), segments[segments.length - 1]); + if (targetField == null) { + return false; + } + + Object typedValue = coerceValue(value, targetField.getType()); + return setFieldValue(targetField, section, typedValue); + } + + private static Object readPathValue(Object root, String path) { + String normalized = normalizePath(path); + if (normalized.isBlank()) { + return null; + } + + String[] segments = normalized.split("\\Q.\\E"); + if (segments.length == 0) { + return null; + } + + StringBuilder parentPath = new StringBuilder(); + for (int i = 0; i < segments.length - 1; i++) { + if (i > 0) { + parentPath.append('.'); + } + parentPath.append(segments[i]); + } + + Object section = resolveSectionObject(root, parentPath.toString(), false); + if (section == null) { + return null; + } + + Field field = findField(section.getClass(), segments[segments.length - 1]); + if (field == null) { + return null; + } + + return getFieldValue(field, section); + } + + private static Field findField(Class type, String name) { + Class current = type; + while (current != null && current != Object.class) { + try { + Field field = current.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException ex) { + current = current.getSuperclass(); + } + } + + return null; + } + + private static List getSerializableFields(Class type) { + List fields = new ArrayList<>(); + collectFields(type, fields); + return fields; + } + + private static void collectFields(Class type, List out) { + if (type == null || type == Object.class) { + return; + } + + collectFields(type.getSuperclass(), out); + for (Field field : type.getDeclaredFields()) { + if (field.isSynthetic()) { + continue; + } + + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) { + continue; + } + + field.setAccessible(true); + out.add(field); + } + } + + private static Object instantiate(Class type) { + Class normalized = normalizeType(type); + if (normalized.isPrimitive() || normalized.isEnum() || normalized == String.class || isNumericType(normalized) || normalized == Boolean.class) { + return null; + } + + try { + return normalized.getDeclaredConstructor().newInstance(); + } catch (Throwable ex) { + Adapt.verbose("Failed to instantiate config type " + normalized.getName() + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + return null; + } + } + + private static boolean setFieldValue(Field field, Object target, Object value) { + try { + field.setAccessible(true); + field.set(target, value); + return true; + } catch (Throwable ex) { + Adapt.verbose("Failed to set field '" + field.getName() + "' on " + + (target == null ? "null" : target.getClass().getName()) + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + return false; + } + } + + private static Object getFieldValue(Field field, Object target) { + try { + field.setAccessible(true); + return field.get(target); + } catch (Throwable ex) { + Adapt.verbose("Failed to read field '" + field.getName() + "' on " + + (target == null ? "null" : target.getClass().getName()) + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + return null; + } + } + + private static Object coerceValue(Object value, Class targetType) { + return ConfigGuiValueCodec.coerceValue(value, targetType); + } + + private static Class normalizeType(Class type) { + return ConfigGuiValueCodec.normalizeType(type); + } + + private static boolean isNumericType(Class type) { + return ConfigGuiValueCodec.isNumericType(type); + } + + private static boolean isSectionType(Class type) { + return ConfigGuiValueCodec.isSectionType(type); + } + + private static Object cycleEnum(Class enumType, Object current, int direction) { + return ConfigGuiValueCodec.cycleEnum(enumType, current, direction); + } + + private static Object parseEnumConstant(Class enumType, String value) { + return ConfigGuiValueCodec.parseEnumConstant(enumType, value); + } + + private static String enumConstants(Class enumType) { + return ConfigGuiValueCodec.enumConstants(enumType); + } + + private static String displayName(String key) { + if (key == null || key.isBlank()) { + return "Unnamed"; + } + + String spaced = key + .replace('_', ' ') + .replace('-', ' ') + .replaceAll("([a-z])([A-Z])", "$1 $2") + .trim(); + if (spaced.isBlank()) { + return key; + } + return Character.toUpperCase(spaced.charAt(0)) + spaced.substring(1); + } + + private static String summarizeValue(Object value) { + if (value == null) { + return "null"; + } + + if (value instanceof Map map) { + return "map(" + map.size() + ")"; + } + if (value instanceof Collection collection) { + return "list(" + collection.size() + ")"; + } + if (value.getClass().isArray()) { + return "array"; + } + + String text = String.valueOf(value) + .replace("\n", "\\n") + .replace("\r", "\\r"); + if (text.length() > MAX_VALUE_PREVIEW) { + return text.substring(0, MAX_VALUE_PREVIEW - 3) + "..."; + } + return text; + } + + private static void refreshGlobalRuntimeSettings() { + Adapt.wordKey.clear(); + if (AdaptConfig.get().isAutoUpdateLanguage()) { + Localizer.updateLanguageFile(); + } + + if (AdaptConfig.get().isCustomModels()) { + CustomModel.reloadFromDisk(); + } else { + CustomModel.clear(); + } + } + + private static SectionTarget resolveSectionTarget(String path, boolean createMissing) { + String normalized = normalizePath(path); + if (normalized.isBlank()) { + return new SectionTarget(SOURCE_TAG_CORE, resolveSectionObject(AdaptConfig.get(), "", createMissing)); + } + + if (normalized.equals(ROOT_CORE) || normalized.startsWith(ROOT_CORE + ".")) { + String objectPath = stripPrefix(normalized, ROOT_CORE); + return new SectionTarget(SOURCE_TAG_CORE, resolveSectionObject(AdaptConfig.get(), objectPath, createMissing)); + } + + if (normalized.equals(ROOT_SKILLS) || normalized.startsWith(ROOT_SKILLS + ".")) { + String payload = stripPrefix(normalized, ROOT_SKILLS); + if (payload.isBlank()) { + return null; + } + + String[] parts = payload.split("\\Q.\\E", 2); + Skill skill = resolveSkill(parts[0]); + if (skill == null || skill.getConfig() == null) { + return null; + } + + String objectPath = parts.length > 1 ? parts[1] : ""; + return new SectionTarget("skill:" + skill.getName(), resolveSectionObject(skill.getConfig(), objectPath, createMissing)); + } + + if (normalized.equals(ROOT_ADAPTATIONS) || normalized.startsWith(ROOT_ADAPTATIONS + ".")) { + String payload = stripPrefix(normalized, ROOT_ADAPTATIONS); + if (payload.isBlank()) { + return null; + } + + String[] parts = payload.split("\\Q.\\E", 2); + Adaptation adaptation = resolveAdaptation(parts[0]); + if (adaptation == null || adaptation.getConfig() == null) { + return null; + } + + String objectPath = parts.length > 1 ? parts[1] : ""; + return new SectionTarget("adaptation:" + adaptation.getName(), resolveSectionObject(adaptation.getConfig(), objectPath, createMissing)); + } + + return new SectionTarget(SOURCE_TAG_CORE, resolveSectionObject(AdaptConfig.get(), normalized, createMissing)); + } + + private static EditTarget resolveEditTarget(String path) { + String normalized = normalizePath(path); + if (normalized.isBlank()) { + return null; + } + + if (normalized.equals(ROOT_CORE) || normalized.startsWith(ROOT_CORE + ".")) { + String objectPath = stripPrefix(normalized, ROOT_CORE); + if (objectPath.isBlank()) { + return null; + } + return new EditTarget( + SOURCE_TAG_CORE, + AdaptConfig.get(), + objectPath, + Adapt.instance.getDataFile("adapt", "adapt.toml"), + AdaptConfig::reload, + ConfigGui::refreshGlobalRuntimeSettings + ); + } + + if (normalized.equals(ROOT_SKILLS) || normalized.startsWith(ROOT_SKILLS + ".")) { + String payload = stripPrefix(normalized, ROOT_SKILLS); + if (payload.isBlank()) { + return null; + } + + String[] parts = payload.split("\\Q.\\E", 2); + Skill skill = resolveSkill(parts[0]); + if (skill == null || skill.getConfig() == null || Adapt.instance == null || Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { + return null; + } + + String objectPath = parts.length > 1 ? parts[1] : ""; + if (objectPath.isBlank()) { + return null; + } + + return new EditTarget( + "skill:" + skill.getName(), + skill.getConfig(), + objectPath, + Adapt.instance.getDataFile("adapt", "skills", skill.getName() + ".toml"), + () -> Adapt.instance.getAdaptServer().getSkillRegistry().hotReloadSkillConfig(skill.getName()), + () -> { + } + ); + } + + if (normalized.equals(ROOT_ADAPTATIONS) || normalized.startsWith(ROOT_ADAPTATIONS + ".")) { + String payload = stripPrefix(normalized, ROOT_ADAPTATIONS); + if (payload.isBlank()) { + return null; + } + + String[] parts = payload.split("\\Q.\\E", 2); + Adaptation adaptation = resolveAdaptation(parts[0]); + if (adaptation == null || adaptation.getConfig() == null || !(adaptation instanceof SimpleAdaptation simpleAdaptation)) { + return null; + } + + String objectPath = parts.length > 1 ? parts[1] : ""; + if (objectPath.isBlank()) { + return null; + } + + return new EditTarget( + "adaptation:" + adaptation.getName(), + adaptation.getConfig(), + objectPath, + Adapt.instance.getDataFile("adapt", "adaptations", adaptation.getName() + ".toml"), + () -> simpleAdaptation.reloadConfigFromDisk(false), + () -> { + } + ); + } + + return new EditTarget( + SOURCE_TAG_CORE, + AdaptConfig.get(), + normalized, + Adapt.instance.getDataFile("adapt", "adapt.toml"), + AdaptConfig::reload, + ConfigGui::refreshGlobalRuntimeSettings + ); + } + + private static String stripPrefix(String path, String prefix) { + String normalized = normalizePath(path); + if (normalized.equals(prefix)) { + return ""; + } + + String withDot = prefix + "."; + if (normalized.startsWith(withDot)) { + return normalized.substring(withDot.length()); + } + return normalized; + } + + private static Skill resolveSkill(String skillName) { + if (skillName == null || skillName.isBlank()) { + return null; + } + if (Adapt.instance == null || Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { + return null; + } + return Adapt.instance.getAdaptServer().getSkillRegistry().getAnySkill(skillName); + } + + private static Adaptation resolveAdaptation(String adaptationName) { + if (adaptationName == null || adaptationName.isBlank()) { + return null; + } + + for (Skill skill : getLoadedSkills()) { + if (skill == null) { + continue; + } + + for (Adaptation adaptation : skill.getAdaptations()) { + if (adaptation == null || adaptation.getName() == null) { + continue; + } + + if (adaptation.getName().equalsIgnoreCase(adaptationName)) { + return adaptation; + } + } + } + + return null; + } + + private static List> getLoadedSkills() { + if (Adapt.instance == null || Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { + return List.of(); + } + + List> skills = new ArrayList<>(Adapt.instance.getAdaptServer().getSkillRegistry().getAllSkills()); + skills.sort(Comparator.comparing(skill -> normalizeSortKey(ColorFormatter.stripColor(skill.getDisplayName())))); + return skills; + } + + private static List> getLoadedAdaptations() { + List> adaptations = new ArrayList<>(); + for (Skill skill : getLoadedSkills()) { + if (skill == null) { + continue; + } + for (Adaptation adaptation : skill.getAdaptations()) { + if (adaptation == null) { + continue; + } + adaptations.add(adaptation); + } + } + + adaptations.sort(Comparator.comparing(adaptation -> normalizeSortKey(ColorFormatter.stripColor(adaptation.getDisplayName())))); + return adaptations; + } + + private static String normalizeSortKey(String value) { + if (value == null) { + return ""; + } + + String normalized = ColorFormatter.stripColor(value).toLowerCase(Locale.ROOT).trim(); + return normalized.replaceFirst("^[^\\p{L}\\p{N}]+", ""); + } + + private static void playPageTurn(Player player) { + SoundPlayer spw = SoundPlayer.of(player.getWorld()); + spw.play(player.getLocation(), org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); + spw.play(player.getLocation(), org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 1.455f); + spw.play(player.getLocation(), org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 1.855f); + } + + private static void navigateTo(Player player, String path, int page) { + if (player == null) { + return; + } + suppressClose(player); + open(player, path, page); + } + + public static void suppressClose(Player player) { + if (player == null) { + return; + } + + UUID playerId = player.getUniqueId(); + long suppressUntil = M.ms() + CLOSE_SUPPRESS_MS; + CLOSE_SUPPRESS_UNTIL.put(playerId, suppressUntil); + J.s(() -> { + Long current = CLOSE_SUPPRESS_UNTIL.get(playerId); + if (current != null && current == suppressUntil) { + CLOSE_SUPPRESS_UNTIL.remove(playerId); + } + }, CLOSE_SUPPRESS_CLEAR_TICKS); + } + + private static boolean consumeCloseSuppression(Player player) { + if (player == null) { + return false; + } + + Long until = CLOSE_SUPPRESS_UNTIL.get(player.getUniqueId()); + if (until == null) { + return false; + } + + if (until >= M.ms()) { + CLOSE_SUPPRESS_UNTIL.remove(player.getUniqueId()); + return true; + } + + CLOSE_SUPPRESS_UNTIL.remove(player.getUniqueId()); + return false; + } + + private static void onGuiClosed(Player player, String currentPath) { + if (player == null) { + return; + } + + Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString()); + if (consumeCloseSuppression(player)) { + return; + } + + if (AdaptConfig.get().isEscClosesAllGuis()) { + return; + } + + String safePath = normalizePath(currentPath); + if (safePath.isBlank()) { + return; + } + + String parent = parentPath(safePath); + J.runEntity(player, () -> { + if (player.isOnline() && player.getOpenInventory().getTopInventory().getType() == InventoryType.CRAFTING) { + open(player, parent, 0); + } + }, 1); + } + + public record ParseResult(boolean success, Object value, String error) { + public static ParseResult ok(Object value) { + return new ParseResult(true, value, ""); + } + + public static ParseResult fail(String error) { + return new ParseResult(false, null, error == null ? "Invalid value." : error); + } + } + +} diff --git a/src/main/java/art/arcane/adapt/content/gui/ConfigGuiTypes.java b/src/main/java/art/arcane/adapt/content/gui/ConfigGuiTypes.java new file mode 100644 index 000000000..0eb3653ec --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/gui/ConfigGuiTypes.java @@ -0,0 +1,43 @@ +package art.arcane.adapt.content.gui; + +import org.bukkit.Material; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.List; +import java.util.function.BooleanSupplier; + +enum ElementKind { + BOOLEAN, + NUMBER, + STRING, + ENUM, + SECTION, + MAP, + LIST, + UNSUPPORTED +} + +record FieldEntry(Field field, String path, Object value, + ElementDescriptor descriptor, List docs) { +} + +record ElementDescriptor(ElementKind kind, boolean editable) { +} + +record SectionIndexEntry(String path, String displayName, Material material, + String lore) { +} + +record SectionTarget(String sourceTag, Object sectionObject) { +} + +record EditTarget( + String sourceTag, + Object rootObject, + String objectPath, + File file, + BooleanSupplier reload, + Runnable afterReload +) { +} diff --git a/src/main/java/art/arcane/adapt/content/gui/ConfigGuiValueCodec.java b/src/main/java/art/arcane/adapt/content/gui/ConfigGuiValueCodec.java new file mode 100644 index 000000000..359816b53 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/gui/ConfigGuiValueCodec.java @@ -0,0 +1,215 @@ +package art.arcane.adapt.content.gui; + +import java.util.*; + +final class ConfigGuiValueCodec { + private ConfigGuiValueCodec() { + } + + static ConfigGui.ParseResult parseInputValue(Class type, String raw) { + if (type == null) { + return ConfigGui.ParseResult.fail("Unknown target type."); + } + + Class normalized = normalizeType(type); + String trimmed = raw == null ? "" : raw.trim(); + + try { + if (normalized == String.class) { + return ConfigGui.ParseResult.ok(raw == null ? "" : raw); + } + + if (normalized == Character.class) { + if (trimmed.length() != 1) { + return ConfigGui.ParseResult.fail("Expected exactly one character."); + } + return ConfigGui.ParseResult.ok(trimmed.charAt(0)); + } + + if (normalized == Boolean.class) { + if (trimmed.equalsIgnoreCase("true") || trimmed.equalsIgnoreCase("yes") || trimmed.equalsIgnoreCase("on")) { + return ConfigGui.ParseResult.ok(true); + } + if (trimmed.equalsIgnoreCase("false") || trimmed.equalsIgnoreCase("no") || trimmed.equalsIgnoreCase("off")) { + return ConfigGui.ParseResult.ok(false); + } + return ConfigGui.ParseResult.fail("Expected boolean value: true/false."); + } + + if (normalized.isEnum()) { + Object constant = parseEnumConstant(normalized, trimmed); + if (constant == null) { + return ConfigGui.ParseResult.fail("Expected one of: " + enumConstants(normalized)); + } + return ConfigGui.ParseResult.ok(constant); + } + + if (normalized == Integer.class) { + return ConfigGui.ParseResult.ok(Integer.parseInt(trimmed)); + } + if (normalized == Long.class) { + return ConfigGui.ParseResult.ok(Long.parseLong(trimmed)); + } + if (normalized == Double.class) { + double v = Double.parseDouble(trimmed); + if (!Double.isFinite(v)) { + return ConfigGui.ParseResult.fail("Expected a finite number."); + } + return ConfigGui.ParseResult.ok(v); + } + if (normalized == Float.class) { + float v = Float.parseFloat(trimmed); + if (!Float.isFinite(v)) { + return ConfigGui.ParseResult.fail("Expected a finite number."); + } + return ConfigGui.ParseResult.ok(v); + } + if (normalized == Short.class) { + return ConfigGui.ParseResult.ok(Short.parseShort(trimmed)); + } + if (normalized == Byte.class) { + return ConfigGui.ParseResult.ok(Byte.parseByte(trimmed)); + } + } catch (Throwable e) { + return ConfigGui.ParseResult.fail("Invalid value for type " + typeName(type) + "."); + } + + return ConfigGui.ParseResult.fail("Unsupported type: " + typeName(type) + "."); + } + + static String typeName(Class type) { + if (type == null) { + return "unknown"; + } + + Class normalized = normalizeType(type); + if (normalized.isEnum()) { + return "enum"; + } + return normalized.getSimpleName().toLowerCase(Locale.ROOT); + } + + static Object coerceValue(Object value, Class targetType) { + if (value == null) { + return null; + } + + Class normalizedTarget = normalizeType(targetType); + Class valueType = value.getClass(); + if (normalizedTarget.isAssignableFrom(valueType)) { + return value; + } + + ConfigGui.ParseResult parsed = parseInputValue(targetType, String.valueOf(value)); + return parsed.success() ? parsed.value() : value; + } + + static Class normalizeType(Class type) { + if (type == null || !type.isPrimitive()) { + return type; + } + + if (type == int.class) return Integer.class; + if (type == long.class) return Long.class; + if (type == double.class) return Double.class; + if (type == float.class) return Float.class; + if (type == short.class) return Short.class; + if (type == byte.class) return Byte.class; + if (type == boolean.class) return Boolean.class; + if (type == char.class) return Character.class; + return type; + } + + static boolean isNumericType(Class type) { + return type == Integer.class + || type == Long.class + || type == Double.class + || type == Float.class + || type == Short.class + || type == Byte.class; + } + + static boolean isSectionType(Class type) { + Class normalized = normalizeType(type); + if (normalized == null) { + return false; + } + + if (normalized.isPrimitive() || normalized.isEnum()) { + return false; + } + + if (normalized == String.class || normalized == Character.class || normalized == Boolean.class || isNumericType(normalized)) { + return false; + } + + if (Map.class.isAssignableFrom(normalized) || Collection.class.isAssignableFrom(normalized) || normalized.isArray()) { + return false; + } + + return true; + } + + static Object cycleEnum(Class enumType, Object current, int direction) { + Class normalized = normalizeType(enumType); + if (normalized == null || !normalized.isEnum()) { + return null; + } + + Object[] constants = normalized.getEnumConstants(); + if (constants == null || constants.length == 0) { + return null; + } + + int currentIndex = 0; + if (current != null) { + for (int i = 0; i < constants.length; i++) { + if (Objects.equals(constants[i], current)) { + currentIndex = i; + break; + } + } + } + + int nextIndex = currentIndex + direction; + if (nextIndex < 0) { + nextIndex = constants.length - 1; + } else if (nextIndex >= constants.length) { + nextIndex = 0; + } + return constants[nextIndex]; + } + + static Object parseEnumConstant(Class enumType, String value) { + if (enumType == null || !enumType.isEnum() || value == null) { + return null; + } + + for (Object constant : enumType.getEnumConstants()) { + if (constant == null) { + continue; + } + + if (constant.toString().equalsIgnoreCase(value)) { + return constant; + } + } + + return null; + } + + static String enumConstants(Class enumType) { + if (enumType == null || !enumType.isEnum()) { + return ""; + } + + List values = new ArrayList<>(); + for (Object constant : enumType.getEnumConstants()) { + if (constant == null) { + continue; + } + values.add(constant.toString()); + } + return String.join(", ", values); + } +} diff --git a/src/main/java/art/arcane/adapt/content/gui/SkillsGui.java b/src/main/java/art/arcane/adapt/content/gui/SkillsGui.java new file mode 100644 index 000000000..4b55aa4e3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/gui/SkillsGui.java @@ -0,0 +1,229 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.gui; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.api.xp.XP; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.inventorygui.GuiEffects; +import art.arcane.adapt.util.common.inventorygui.GuiLayout; +import art.arcane.adapt.util.common.inventorygui.GuiTheme; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.data.MaterialBlock; +import art.arcane.volmlib.util.format.ColorFormatter; +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.inventorygui.UIElement; +import art.arcane.volmlib.util.inventorygui.UIWindow; +import art.arcane.volmlib.util.inventorygui.Window; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +public class SkillsGui { + private static final int PAGE_JUMP = 5; + + public static void open(Player player) { + open(player, 0); + } + + public static void open(Player player, int page) { + if (!J.isPrimaryThread()) { + int targetPage = page; + J.runEntity(player, () -> open(player, targetPage)); + return; + } + + AdaptPlayer adaptPlayer = Adapt.instance.getAdaptServer().getPlayer(player); + if (adaptPlayer == null) { + Adapt.error("Failed to open skills gui for " + player.getName() + " because they are not Online, Were Kicked, Or are a fake player."); + return; + } + + List entries = new ArrayList<>(); + for (Skill skill : adaptPlayer.getServer().getSkillRegistry().getSkills()) { + if (skill == null) { + continue; + } + if (!skill.isEnabled()) { + continue; + } + PlayerSkillLine line = adaptPlayer.getData().getSkillLineNullable(skill.getName()); + if (line == null) { + continue; + } + if (!skill.hasUsePermission(adaptPlayer.getPlayer(), skill) || line.getLevel() < 0) { + continue; + } + + int adaptationLevel = sumAdaptationLevels(line); + if (!hasVisibleProgress(line, adaptationLevel)) { + continue; + } + + entries.add(new SkillPageEntry(skill, line, adaptationLevel)); + } + + entries.sort( + Comparator.comparing((SkillPageEntry entry) -> normalizeSortKey(entry.skill().getDisplayName())) + .thenComparing(entry -> entry.skill().getName(), String.CASE_INSENSITIVE_ORDER) + ); + + boolean reserveNavigation = false; + GuiLayout.PagePlan plan = GuiLayout.plan(entries.size(), reserveNavigation); + int currentPage = GuiLayout.clampPage(page, plan.pageCount()); + int start = currentPage * plan.itemsPerPage(); + int end = Math.min(entries.size(), start + plan.itemsPerPage()); + + UIWindow w = new UIWindow(Adapt.instance, player); + GuiTheme.apply(w, "/"); + w.setViewportHeight(plan.rows()); + + if (entries.isEmpty()) { + w.setElement(0, 0, new UIElement("skills-empty") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.GRAY + "No skills available") + .addLore(C.DARK_GRAY + "No eligible skills were found for this player.")); + } else { + List reveal = new ArrayList<>(); + for (int row = 0; row < plan.contentRows(); row++) { + int rowStart = start + (row * GuiLayout.WIDTH); + if (rowStart >= end) { + break; + } + + int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); + for (int i = 0; i < rowCount; i++) { + SkillPageEntry entry = entries.get(rowStart + i); + int pos = GuiLayout.centeredPosition(i, rowCount); + Element element = new UIElement("skill-" + entry.skill().getName()) + .setMaterial(new MaterialBlock(entry.skill().getIcon())) + .setBaseItemStack(entry.skill().getModel().toItemStack()) + .setName(entry.skill().getDisplayName(entry.line().getLevel())) + .setProgress(1D) + .addLore(C.ITALIC + "" + C.GRAY + entry.skill().getDescription()) + .addLore(C.UNDERLINE + "" + C.WHITE + entry.line().getKnowledge() + C.RESET + " " + C.GRAY + Localizer.dLocalize("snippets.gui.knowledge")) + .addLore(C.ITALIC + "" + C.GRAY + Localizer.dLocalize("snippets.gui.power_used") + " " + C.DARK_GREEN + entry.adaptationLevel()) + .onLeftClick((e) -> entry.skill().openGui(player)); + reveal.add(new GuiEffects.Placement(pos, row, element)); + } + } + GuiEffects.applyReveal(w, reveal); + } + + if (plan.hasNavigationRow()) { + int navRow = plan.rows() - 1; + applyPageControls(w, player, navRow, currentPage, plan.pageCount(), entries.size(), start, end); + + } + + w.setTitle(Localizer.dLocalize("snippets.gui.level") + " " + (int) XP.getLevelForXp(adaptPlayer.getData().getMasterXp()) + " (" + adaptPlayer.getData().getUsedPower() + "/" + adaptPlayer.getData().getMaxPower() + " " + Localizer.dLocalize("snippets.gui.power_used") + ")"); + w.open(); + w.onClosed((e) -> Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString())); + Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); + } + + private static int sumAdaptationLevels(PlayerSkillLine line) { + int total = 0; + for (PlayerAdaptation adaptation : line.getAdaptations().values()) { + if (adaptation == null) { + continue; + } + total += Math.max(0, adaptation.getLevel()); + } + return total; + } + + private static boolean hasVisibleProgress(PlayerSkillLine line, int adaptationLevel) { + return line.getXp() > 0D || line.getKnowledge() > 0L || adaptationLevel > 0; + } + + private static String normalizeSortKey(String value) { + if (value == null) { + return ""; + } + + String normalized = ColorFormatter.stripColor(value).toLowerCase(Locale.ROOT).trim(); + return normalized.replaceFirst("^[^\\p{L}\\p{N}]+", ""); + } + + private static void applyPageControls( + Window window, + Player player, + int navRow, + int currentPage, + int pageCount, + int totalEntries, + int start, + int end + ) { + if (pageCount <= 1) { + return; + } + + int jumpBack = Math.max(0, currentPage - PAGE_JUMP); + int jumpForward = Math.min(pageCount - 1, currentPage + PAGE_JUMP); + + if (currentPage > 0) { + window.setElement(-4, navRow, new UIElement("skills-prev") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.WHITE + "Previous") + .addLore(C.GRAY + "Right click: jump -" + PAGE_JUMP + " pages") + .onLeftClick((e) -> open(player, currentPage - 1)) + .onRightClick((e) -> open(player, jumpBack))); + window.setElement(-3, navRow, new UIElement("skills-first") + .setMaterial(new MaterialBlock(Material.LECTERN)) + .setName(C.GRAY + "First") + .onLeftClick((e) -> open(player, 0))); + } + + if (currentPage < pageCount - 1) { + window.setElement(4, navRow, new UIElement("skills-next") + .setMaterial(new MaterialBlock(Material.ARROW)) + .setName(C.WHITE + "Next") + .addLore(C.GRAY + "Right click: jump +" + PAGE_JUMP + " pages") + .onLeftClick((e) -> open(player, currentPage + 1)) + .onRightClick((e) -> open(player, jumpForward))); + window.setElement(3, navRow, new UIElement("skills-last") + .setMaterial(new MaterialBlock(Material.LECTERN)) + .setName(C.GRAY + "Last") + .onLeftClick((e) -> open(player, pageCount - 1))); + } + + int from = totalEntries <= 0 ? 0 : (start + 1); + int to = totalEntries <= 0 ? 0 : end; + window.setElement(-1, navRow, new UIElement("skills-page-info") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.AQUA + "Page " + (currentPage + 1) + "/" + pageCount) + .addLore(C.GRAY + "Showing " + from + "-" + to + " of " + totalEntries) + .setProgress(1D)); + } + + private record SkillPageEntry(Skill skill, PlayerSkillLine line, + int adaptationLevel) { + } +} diff --git a/src/main/java/art/arcane/adapt/content/integration/hiddenore/HiddenOreBridge.java b/src/main/java/art/arcane/adapt/content/integration/hiddenore/HiddenOreBridge.java new file mode 100644 index 000000000..2379b76ae --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/integration/hiddenore/HiddenOreBridge.java @@ -0,0 +1,254 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.integration.hiddenore; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.skill.SkillRegistry; +import art.arcane.adapt.content.adaptation.pickaxe.PickaxeAutosmelt; +import art.arcane.adapt.content.adaptation.pickaxe.PickaxeDropToInventory; +import art.arcane.adapt.content.adaptation.pickaxe.PickaxeGemPolish; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.hiddenore.HiddenOre; +import art.arcane.hiddenore.api.HiddenOreAPI; +import art.arcane.hiddenore.api.HiddenVein; +import art.arcane.hiddenore.api.event.HiddenOreDropsEvent; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public final class HiddenOreBridge implements Listener { + static HiddenOreLink.VeinTarget nearestVein(Location origin, int range) { + HiddenOreAPI api = api(); + if (api == null) { + return null; + } + + HiddenVein best = null; + double bestDistanceSq = Double.MAX_VALUE; + for (HiddenVein vein : api.veinsNear(origin, range)) { + double distanceSq = distanceSquared(origin, vein); + if (distanceSq < bestDistanceSq) { + best = vein; + bestDistanceSq = distanceSq; + } + } + + if (best == null) { + return null; + } + return toTarget(origin.getWorld(), best); + } + + static List veins(Location origin, int radius) { + HiddenOreAPI api = api(); + if (api == null) { + return List.of(); + } + + List found = api.veinsNear(origin, radius); + if (found.isEmpty()) { + return List.of(); + } + List result = new ArrayList<>(found.size()); + for (HiddenVein vein : found) { + result.add(toTarget(origin.getWorld(), vein)); + } + return result; + } + + static List veinSiblings(Block block) { + HiddenOreAPI api = api(); + if (api == null) { + return List.of(); + } + + List siblings = api.veinSiblings(block); + if (siblings.isEmpty()) { + return List.of(); + } + World world = block.getWorld(); + List result = new ArrayList<>(siblings.size()); + for (HiddenVein vein : siblings) { + result.add(world.getBlockAt(vein.x(), vein.y(), vein.z())); + } + return result; + } + + @EventHandler + public void on(HiddenOreDropsEvent e) { + Player p = e.getPlayer(); + applyGemPolish(p, e); + applyAutosmelt(p, e); + applyDropToInventory(p, e); + awardOreXp(p, e); + } + + private void applyGemPolish(Player p, HiddenOreDropsEvent e) { + HiddenVein vein = e.getVein(); + if (vein == null) { + return; + } + + Material gem = switch (vein.item()) { + case DIAMOND, EMERALD, LAPIS_LAZULI -> vein.item(); + default -> null; + }; + if (gem == null) { + return; + } + + PickaxeGemPolish gemPolish = adaptation(PickaxeGemPolish.class); + if (gemPolish == null) { + return; + } + int level = gemPolish.getActiveLevel(p); + if (level <= 0) { + return; + } + + e.setExperience(e.getExperience() + gemPolish.getBonusXp(level)); + if (ThreadLocalRandom.current().nextDouble() < gemPolish.getGemChance(level)) { + e.getDrops().add(new ItemStack(gem)); + Adapt.instance.getAdaptServer().getPlayer(p).getData().addStat("pickaxe.gem-polish.gems-polished", 1); + if (particlesEnabled()) { + e.getBlock().getWorld().spawnParticle(Particle.HAPPY_VILLAGER, e.getBlock().getLocation().add(0.5, 0.5, 0.5), 6, 0.25, 0.25, 0.25); + } + } + } + + private void applyAutosmelt(Player p, HiddenOreDropsEvent e) { + PickaxeAutosmelt autosmelt = adaptation(PickaxeAutosmelt.class); + if (autosmelt == null || autosmelt.getActiveLevel(p) <= 0) { + return; + } + + boolean smelted = false; + List drops = e.getDrops(); + for (int i = 0; i < drops.size(); i++) { + ItemStack stack = drops.get(i); + if (stack == null) { + continue; + } + Material result = switch (stack.getType()) { + case RAW_IRON -> Material.IRON_INGOT; + case RAW_GOLD -> Material.GOLD_INGOT; + case RAW_COPPER -> Material.COPPER_INGOT; + default -> null; + }; + if (result == null) { + continue; + } + drops.set(i, new ItemStack(result, stack.getAmount())); + smelted = true; + } + + if (!smelted) { + return; + } + Adapt.instance.getAdaptServer().getPlayer(p).getData().addStat("pickaxe.autosmelt.ores-smelted", 1); + Location at = e.getBlock().getLocation(); + if (soundsEnabled()) { + SoundPlayer.of(e.getBlock().getWorld()).play(at, Sound.BLOCK_LAVA_POP, 1, 1); + } + if (particlesEnabled()) { + e.getBlock().getWorld().spawnParticle(Particle.LAVA, at, 3, 0.5, 0.5, 0.5); + } + } + + private void applyDropToInventory(Player p, HiddenOreDropsEvent e) { + PickaxeDropToInventory dropToInventory = adaptation(PickaxeDropToInventory.class); + if (dropToInventory != null && dropToInventory.getActiveLevel(p) > 0) { + e.setToInventory(true); + } + } + + private void awardOreXp(Player p, HiddenOreDropsEvent e) { + HiddenVein vein = e.getVein(); + if (vein == null) { + return; + } + Material display = vein.oreDisplay() != null ? vein.oreDisplay() : vein.item(); + Skill pickaxe = SkillRegistry.skills.get("pickaxe"); + if (pickaxe == null) { + return; + } + double value = pickaxe.getValue(display); + if (value <= 0) { + return; + } + pickaxe.xp(p, e.getBlock().getLocation(), value); + } + + private static T adaptation(Class type) { + Skill pickaxe = SkillRegistry.skills.get("pickaxe"); + if (pickaxe == null) { + return null; + } + for (Adaptation adaptation : pickaxe.getAdaptations()) { + if (type.isInstance(adaptation)) { + return type.cast(adaptation); + } + } + return null; + } + + private static HiddenOreAPI api() { + if (!(Bukkit.getPluginManager().getPlugin("HiddenOre") instanceof HiddenOre hiddenOre)) { + return null; + } + return hiddenOre.getApi(); + } + + private static HiddenOreLink.VeinTarget toTarget(World world, HiddenVein vein) { + Material display = vein.oreDisplay() != null ? vein.oreDisplay() : vein.item(); + return new HiddenOreLink.VeinTarget(new Location(world, vein.x(), vein.y(), vein.z()), display); + } + + private static double distanceSquared(Location origin, HiddenVein vein) { + double dx = (vein.x() + 0.5) - origin.getX(); + double dy = (vein.y() + 0.5) - origin.getY(); + double dz = (vein.z() + 0.5) - origin.getZ(); + return (dx * dx) + (dy * dy) + (dz * dz); + } + + private static boolean particlesEnabled() { + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + return effects == null || effects.isParticlesEnabled(); + } + + private static boolean soundsEnabled() { + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + return effects == null || effects.isSoundsEnabled(); + } +} diff --git a/src/main/java/art/arcane/adapt/content/integration/hiddenore/HiddenOreLink.java b/src/main/java/art/arcane/adapt/content/integration/hiddenore/HiddenOreLink.java new file mode 100644 index 000000000..2f6ea32c0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/integration/hiddenore/HiddenOreLink.java @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.integration.hiddenore; + +import art.arcane.adapt.Adapt; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; + +import java.util.List; + +public final class HiddenOreLink { + private static volatile boolean active = false; + + private HiddenOreLink() { + } + + public record VeinTarget(Location location, Material display) { + } + + public static void activate(Adapt plugin) { + plugin.registerListener(new HiddenOreBridge()); + active = true; + Adapt.info("HiddenOre link active: seismic ping, quarry sense, veinminer + drop transforms wired to hidden veins"); + } + + public static boolean isActive() { + return active; + } + + public static VeinTarget nearestVein(Location origin, int range) { + if (!active) { + return null; + } + return HiddenOreBridge.nearestVein(origin, range); + } + + public static List veins(Location origin, int radius) { + if (!active) { + return List.of(); + } + return HiddenOreBridge.veins(origin, radius); + } + + public static List veinSiblings(Block block) { + if (!active) { + return List.of(); + } + return HiddenOreBridge.veinSiblings(block); + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/BoundEnderPearl.java b/src/main/java/art/arcane/adapt/content/item/BoundEnderPearl.java new file mode 100644 index 000000000..b4e74de55 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/BoundEnderPearl.java @@ -0,0 +1,103 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.item.DataItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; + +@AllArgsConstructor +@Data +public class BoundEnderPearl implements DataItem { + public static BoundEnderPearl io = new BoundEnderPearl(); + + public static Block getBlock(ItemStack stack) { + if (io.getData(stack) != null) { + return io.getData(stack).getBlock(); + } + + return null; + } + + public static void setData(ItemStack item, Block t) { + io.setData(item, new Data(t)); + } + + public static ItemStack withData(Block t) { + return io.withData(new Data(t)); + } + + public static boolean isBindableItem(ItemStack t) { + if (t.getType().equals(Material.ENDER_PEARL)) { + if (t.getItemMeta() != null && t.getItemMeta().getLore() != null) { + if (t.getItemMeta().getLore().get(0).contains(Localizer.dLocalize("items.bound_ender_peral.name"))) { + Adapt.verbose("Enderpearl is bindable: " + t.getType().name()); + return true; + } + } + } + return false; + } + + @Override + public Material getMaterial() { + return Material.ENDER_PEARL; + } + + @Override + public Class getType() { + return BoundEnderPearl.Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + lore.add(C.WHITE + Localizer.dLocalize("items.bound_ender_peral.name")); + lore.add(C.GRAY + Localizer.dLocalize("items.bound_ender_peral.usage1")); + lore.add(C.GRAY + Localizer.dLocalize("items.bound_ender_peral.usage2")); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS); + meta.setDisplayName(Localizer.dLocalize("items.bound_ender_peral.name")); + + } + + @AllArgsConstructor + @lombok.Data + public static class Data { + private Block block; + + public static BoundEnderPearl.Data at(Block l) { + return new BoundEnderPearl.Data(l); + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/BoundEyeOfEnder.java b/src/main/java/art/arcane/adapt/content/item/BoundEyeOfEnder.java new file mode 100644 index 000000000..5b0dca711 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/BoundEyeOfEnder.java @@ -0,0 +1,102 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.item.DataItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; + +@AllArgsConstructor +@Data +public class BoundEyeOfEnder implements DataItem { + public static BoundEyeOfEnder io = new BoundEyeOfEnder(); + + public static Location getLocation(ItemStack stack) { + if (io.getData(stack) != null) { + return io.getData(stack).getLocation(); + } + + return null; + } + + public static void setData(ItemStack item, Location t) { + io.setData(item, new Data(t)); + } + + public static ItemStack withData(Location t) { + return io.withData(new Data(t)); + } + + public static boolean isBindableItem(ItemStack t) { + if (t.getType().equals(Material.ENDER_EYE)) { + if (t.getItemMeta() != null && t.getItemMeta().getLore() != null) { + if (t.getItemMeta().getLore().get(0).contains(Localizer.dLocalize("items.bound_eye_of_ender.name"))) { + Adapt.verbose("Eye of ender is bindable: " + t.getType().name()); + return true; + } + } + } + return false; + } + + @Override + public Material getMaterial() { + return Material.ENDER_EYE; + } + + @Override + public Class getType() { + return BoundEyeOfEnder.Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + lore.add(C.WHITE + Localizer.dLocalize("items.bound_eye_of_ender.name")); + lore.add(C.GRAY + Localizer.dLocalize("items.bound_eye_of_ender.usage1")); + lore.add(C.GRAY + Localizer.dLocalize("items.bound_eye_of_ender.usage2")); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_DYE); + meta.setDisplayName(Localizer.dLocalize("items.bound_eye_of_ender.name")); + } + + @AllArgsConstructor + @lombok.Data + public static class Data { + private Location location; + + public static BoundEyeOfEnder.Data at(Location l) { + return new BoundEyeOfEnder.Data(l); + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/BoundRedstoneTorch.java b/src/main/java/art/arcane/adapt/content/item/BoundRedstoneTorch.java new file mode 100644 index 000000000..42eef5dad --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/BoundRedstoneTorch.java @@ -0,0 +1,109 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.item.DataItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; + +@AllArgsConstructor +@Data +public class BoundRedstoneTorch implements DataItem { + public static BoundRedstoneTorch io = new BoundRedstoneTorch(); + + public static Location getLocation(ItemStack stack) { + if (io.getData(stack) != null) { + return io.getData(stack).getLocation(); + } + + return null; + } + + /* + renamed from hasData as the types are the same (ItemStack -> boolean), but this is static + */ + public static boolean hasItemData(ItemStack stack) { + return io.hasData(stack); + } + + public static void setData(ItemStack item, Location t) { + io.setData(item, new Data(t)); + } + + public static ItemStack withData(Location t) { + return io.withData(new Data(t)); + } + + public static boolean isBindableItem(ItemStack t) { + if (t.getType().equals(Material.REDSTONE_TORCH)) { + if (t.getItemMeta() != null && t.getItemMeta().getLore() != null) { + if (t.getItemMeta().getLore().get(0).contains(Localizer.dLocalize("items.bound_redstone_torch.name"))) { + Adapt.verbose("Torch is bindable: " + t.getType().name()); + return true; + } + } + } + return false; + } + + @Override + public Material getMaterial() { + return Material.REDSTONE_TORCH; + } + + @Override + public Class getType() { + return BoundRedstoneTorch.Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + lore.add(C.WHITE + Localizer.dLocalize("items.bound_redstone_torch.name")); + lore.add(C.GRAY + Localizer.dLocalize("items.bound_redstone_torch.usage1")); + lore.add(C.GRAY + Localizer.dLocalize("items.bound_redstone_torch.usage2")); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_DYE); + meta.setDisplayName(Localizer.dLocalize("items.bound_redstone_torch.name")); + } + + @AllArgsConstructor + @lombok.Data + public static class Data { + private Location location; + + public static BoundRedstoneTorch.Data at(Location l) { + return new BoundRedstoneTorch.Data(l); + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/BoundSnowBall.java b/src/main/java/art/arcane/adapt/content/item/BoundSnowBall.java new file mode 100644 index 000000000..cd3383b81 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/BoundSnowBall.java @@ -0,0 +1,102 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.item.DataItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; + +@AllArgsConstructor +@Data +public class BoundSnowBall implements DataItem { + public static BoundSnowBall io = new BoundSnowBall(); + + public static Player getPlayer(ItemStack stack) { + if (io.getData(stack) != null) { + return io.getData(stack).getPlayer(); + } + + return null; + } + + public static void setData(ItemStack item, Player t) { + io.setData(item, new Data(t)); + } + + public static ItemStack withData(Player t) { + return io.withData(new Data(t)); + } + + public static boolean isBindableItem(ItemStack t) { + if (t.getType().equals(Material.SNOWBALL)) { + if (t.getItemMeta() != null && t.getItemMeta().getLore() != null) { + if (t.getItemMeta().getLore().get(0).contains(Localizer.dLocalize("items.bound_snowball.name"))) { + Adapt.verbose("Snowball is bindable: " + t.getType().name()); + return true; + } + } + } + return false; + } + + + @Override + public Material getMaterial() { + return Material.SNOWBALL; + } + + @Override + public Class getType() { + return BoundSnowBall.Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + lore.add(C.WHITE + Localizer.dLocalize("items.bound_snowball.name")); + lore.add(C.GRAY + Localizer.dLocalize("items.bound_snowball.usage1")); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_DYE); + meta.setDisplayName(Localizer.dLocalize("items.bound_snowball.name")); + } + + @AllArgsConstructor + @lombok.Data + public static class Data { + private Player player; + + public static BoundSnowBall.Data at(Player p) { + return new BoundSnowBall.Data(p); + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/ChronoTimeBombItem.java b/src/main/java/art/arcane/adapt/content/item/ChronoTimeBombItem.java new file mode 100644 index 000000000..3b01fd070 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/ChronoTimeBombItem.java @@ -0,0 +1,103 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.item.DataItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.reflect.registries.ItemFlags; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.potion.PotionType; + +import java.util.List; + +@AllArgsConstructor +@Data +public class ChronoTimeBombItem implements DataItem { + public static ChronoTimeBombItem io = new ChronoTimeBombItem(); + + public static boolean isBindableItem(ItemStack stack) { + if (stack == null || stack.getItemMeta() == null) { + return false; + } + + if (stack.getType() == Material.LINGERING_POTION) { + return io.hasData(stack); + } + + if (stack.getType() == Material.CLOCK) { + if (Adapt.instance == null) { + return false; + } + + NamespacedKey key = new NamespacedKey(Adapt.instance, Data.class.getCanonicalName().hashCode() + ""); + return stack.getItemMeta().getPersistentDataContainer().has(key, PersistentDataType.STRING); + } + + return false; + } + + public static ItemStack withData() { + return io.withData(new Data(System.currentTimeMillis())); + } + + @Override + public Material getMaterial() { + return Material.LINGERING_POTION; + } + + @Override + public Class getType() { + return Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + lore.add(C.WHITE + Localizer.dLocalize("items.chrono_time_bomb.name")); + lore.add(C.GRAY + Localizer.dLocalize("items.chrono_time_bomb.usage1")); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + if (meta instanceof PotionMeta potionMeta) { + potionMeta.setBasePotionType(PotionType.WEAKNESS); + meta = potionMeta; + } + + meta.addEnchant(Enchantment.BINDING_CURSE, 1, true); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlags.HIDE_POTION_EFFECTS); + meta.setDisplayName(Localizer.dLocalize("items.chrono_time_bomb.name")); + } + + @AllArgsConstructor + @lombok.Data + public static class Data { + private long created; + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/ChronoTimeBottle.java b/src/main/java/art/arcane/adapt/content/item/ChronoTimeBottle.java new file mode 100644 index 000000000..976ebf877 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/ChronoTimeBottle.java @@ -0,0 +1,97 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.api.item.DataItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.reflect.registries.ItemFlags; +import art.arcane.volmlib.util.format.Form; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionType; + +import java.util.List; + +@AllArgsConstructor +@Data +public class ChronoTimeBottle implements DataItem { + public static ChronoTimeBottle io = new ChronoTimeBottle(); + + public static boolean isBindableItem(ItemStack stack) { + return stack != null && stack.getType() == Material.POTION && io.hasData(stack); + } + + public static double getStoredSeconds(ItemStack stack) { + Data data = io.getData(stack); + return data == null ? 0 : Math.max(0, data.getStoredSeconds()); + } + + public static void setStoredSeconds(ItemStack stack, double seconds) { + io.setData(stack, new Data(Math.max(0, seconds))); + } + + public static ItemStack withStoredSeconds(double seconds) { + return io.withData(new Data(Math.max(0, seconds))); + } + + @Override + public Material getMaterial() { + return Material.POTION; + } + + @Override + public Class getType() { + return Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + lore.add(C.WHITE + Localizer.dLocalize("items.chrono_time_bottle.name")); + lore.add(C.GRAY + Localizer.dLocalize("items.chrono_time_bottle.usage1")); + lore.add(C.GRAY + Localizer.dLocalize("items.chrono_time_bottle.usage2")); + lore.add(C.AQUA + Localizer.dLocalize("items.chrono_time_bottle.stored") + ": " + C.WHITE + Form.duration((long) (Math.max(0, data.getStoredSeconds()) * 1000D), 1)); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + if (meta instanceof PotionMeta potionMeta) { + potionMeta.setBasePotionType(PotionType.WATER); + potionMeta.setColor(Color.fromRGB(235, 245, 255)); + meta = potionMeta; + } + + meta.addEnchant(Enchantment.BINDING_CURSE, 1, true); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlags.HIDE_POTION_EFFECTS); + meta.setDisplayName(Localizer.dLocalize("items.chrono_time_bottle.name")); + } + + @AllArgsConstructor + @lombok.Data + public static class Data { + private double storedSeconds; + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/ExperienceOrb.java b/src/main/java/art/arcane/adapt/content/item/ExperienceOrb.java new file mode 100644 index 000000000..c3d08fbef --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/ExperienceOrb.java @@ -0,0 +1,113 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.item.DataItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.volmlib.util.format.Form; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@AllArgsConstructor +@Data +public class ExperienceOrb implements DataItem { + public static ExperienceOrb io = new ExperienceOrb(); + + public static Data get(ItemStack is) { + return io.getData(is); + } + + public static void set(ItemStack item, String skill, double xp) { + io.setData(item, new Data(skill, xp)); + } + + public static ItemStack with(String skill, double xp) { + return io.withData(new Data(skill, xp)); + } + + public static ItemStack with(Map experienceMap) { + return io.withData(new Data(experienceMap)); + } + + @Override + public Material getMaterial() { + return Material.SNOWBALL; + } + + @Override + public Class getType() { + return ExperienceOrb.Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + for (Map.Entry entry : data.getExperienceMap().entrySet()) { + String skill = entry.getKey(); + double experience = entry.getValue(); + lore.add(C.WHITE + Form.capitalize(Localizer.dLocalize("snippets.experience_orb.contains")) + " " + C.UNDERLINE + C.WHITE + Form.f(experience, 0) + " " + Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skill).getDisplayName() + C.GRAY + " " + Localizer.dLocalize("snippets.experience_orb.xp")); + } + lore.add(C.LIGHT_PURPLE + Localizer.dLocalize("snippets.experience_orb.rightclick") + " " + C.GRAY + Localizer.dLocalize("snippets.experience_orb.togainxp")); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS); + meta.setDisplayName(Localizer.dLocalize("snippets.experience_orb.xporb")); + } + + @AllArgsConstructor + @lombok.Data + public static class Data { + private Map experienceMap; + + public Data(String skill, double experience) { + this.experienceMap = new HashMap<>(); + this.experienceMap.put(skill, experience); + } + + public String getSkill() { + return experienceMap.keySet().iterator().next(); + } + + public double getExperience() { + return experienceMap.values().iterator().next(); + } + + public void apply(Player p) { + for (Map.Entry entry : experienceMap.entrySet()) { + String skill = entry.getKey(); + double experience = entry.getValue(); + Adapt.instance.getAdaptServer().getPlayer(p).getSkillLine(skill).giveXPFresh(Adapt.instance.getAdaptServer().getPlayer(p).getNot(), experience); + } + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/ItemListings.java b/src/main/java/art/arcane/adapt/content/item/ItemListings.java new file mode 100644 index 000000000..80666b629 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/ItemListings.java @@ -0,0 +1,760 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.reflect.registries.Materials; +import art.arcane.volmlib.util.collection.KList; +import lombok.Getter; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; + +import java.util.HashMap; +import java.util.List; + +public class ItemListings { + + @Getter + public static final List shearList = new KList<>( + Material.ACACIA_LEAVES, + Material.AZALEA_LEAVES, + Material.BIRCH_LEAVES, + Material.DARK_OAK_LEAVES, + Material.JUNGLE_LEAVES, + Material.OAK_LEAVES, + Material.SPRUCE_LEAVES, + Material.MANGROVE_LEAVES, + Materials.CHERRY_LEAVES, + Materials.PALE_OAK_LEAVES + ).nonNull(); + + @Getter + public static final List invalidDamageableEntities = Version.get().getInvalidDamageableEntities(); + + @Getter + public static final List smeltOre = List.of( + Material.IRON_ORE, + Material.GOLD_ORE, + Material.COPPER_ORE, + Material.DEEPSLATE_IRON_ORE, + Material.DEEPSLATE_GOLD_ORE, + Material.DEEPSLATE_COPPER_ORE + ); + + @Getter + public static final List ores = List.of( + Material.IRON_ORE, + Material.GOLD_ORE, + Material.COPPER_ORE, + Material.LAPIS_ORE, + Material.REDSTONE_ORE, + Material.EMERALD_ORE, + Material.DIAMOND_ORE, + Material.COAL_ORE, + Material.DEEPSLATE_IRON_ORE, + Material.DEEPSLATE_GOLD_ORE, + Material.DEEPSLATE_COPPER_ORE, + Material.DEEPSLATE_LAPIS_ORE, + Material.DEEPSLATE_REDSTONE_ORE, + Material.DEEPSLATE_EMERALD_ORE, + Material.DEEPSLATE_DIAMOND_ORE, + Material.DEEPSLATE_COAL_ORE, + Material.NETHER_GOLD_ORE, + Material.NETHER_QUARTZ_ORE, + Material.ANCIENT_DEBRIS + ); + + @Getter + public static HashMap oreColorsChatColor = new HashMap<>() {{ + put(Material.IRON_ORE, ChatColor.GRAY); + put(Material.GOLD_ORE, ChatColor.YELLOW); + put(Material.COPPER_ORE, ChatColor.GOLD); + put(Material.LAPIS_ORE, ChatColor.BLUE); + put(Material.REDSTONE_ORE, ChatColor.RED); + put(Material.EMERALD_ORE, ChatColor.GREEN); + put(Material.DIAMOND_ORE, ChatColor.AQUA); + put(Material.COAL_ORE, ChatColor.DARK_GRAY); + put(Material.DEEPSLATE_IRON_ORE, ChatColor.GRAY); + put(Material.DEEPSLATE_GOLD_ORE, ChatColor.YELLOW); + put(Material.DEEPSLATE_COPPER_ORE, ChatColor.GOLD); + put(Material.DEEPSLATE_LAPIS_ORE, ChatColor.BLUE); + put(Material.DEEPSLATE_REDSTONE_ORE, ChatColor.RED); + put(Material.DEEPSLATE_EMERALD_ORE, ChatColor.GREEN); + put(Material.DEEPSLATE_DIAMOND_ORE, ChatColor.AQUA); + put(Material.DEEPSLATE_COAL_ORE, ChatColor.DARK_GRAY); + put(Material.NETHER_GOLD_ORE, ChatColor.YELLOW); + put(Material.NETHER_QUARTZ_ORE, ChatColor.WHITE); + put(Material.ANCIENT_DEBRIS, ChatColor.DARK_PURPLE); + }}; + + @Getter + public static HashMap oreColorColor = new HashMap<>() {{ + put(Material.IRON_ORE, C.GRAY); + put(Material.GOLD_ORE, C.YELLOW); + put(Material.COPPER_ORE, C.GOLD); + put(Material.LAPIS_ORE, C.BLUE); + put(Material.REDSTONE_ORE, C.RED); + put(Material.EMERALD_ORE, C.GREEN); + put(Material.DIAMOND_ORE, C.AQUA); + put(Material.COAL_ORE, C.DARK_GRAY); + put(Material.DEEPSLATE_IRON_ORE, C.GRAY); + put(Material.DEEPSLATE_GOLD_ORE, C.YELLOW); + put(Material.DEEPSLATE_COPPER_ORE, C.GOLD); + put(Material.DEEPSLATE_LAPIS_ORE, C.BLUE); + put(Material.DEEPSLATE_REDSTONE_ORE, C.RED); + put(Material.DEEPSLATE_EMERALD_ORE, C.GREEN); + put(Material.DEEPSLATE_DIAMOND_ORE, C.AQUA); + put(Material.DEEPSLATE_COAL_ORE, C.DARK_GRAY); + put(Material.NETHER_GOLD_ORE, C.YELLOW); + put(Material.NETHER_QUARTZ_ORE, C.WHITE); + put(Material.ANCIENT_DEBRIS, C.DARK_PURPLE); + }}; + + @Getter + public static KList herbalLuckSeeds = new KList<>( + Material.MELON_SEEDS, + Material.PUMPKIN_SEEDS, + Material.COCOA_BEANS + ); + + @Getter + public static List swordPreference = List.of( + Material.COBWEB, + Material.CAVE_VINES, + Material.CAVE_VINES_PLANT, + Material.BAMBOO, + Material.COCOA, + Material.COCOA_BEANS, + Material.HAY_BLOCK + ); + + @Getter + public static KList herbalLuckFood = new KList<>( + Material.POTATOES, + Material.CARROTS, + Material.BEETROOTS, + Material.APPLE + ); + + @Getter + public static List flowers = List.of( + Material.DANDELION, + Material.POPPY, + Material.BLUE_ORCHID, + Material.ALLIUM, + Material.AZURE_BLUET, + Material.RED_TULIP, + Material.ORANGE_TULIP, + Material.WHITE_TULIP, + Material.PINK_TULIP, + Material.OXEYE_DAISY, + Material.CORNFLOWER, + Material.LILY_OF_THE_VALLEY, + Material.LILAC, + Material.ROSE_BUSH, + Material.PEONY, + Material.WITHER_ROSE + ); + + @Getter + public static List food = List.of( + Material.APPLE, + Material.BAKED_POTATO, + Material.BEETROOT, + Material.BEETROOT_SOUP, + Material.BREAD, + Material.CARROT, + Material.CHORUS_FRUIT, + Material.COOKED_CHICKEN, + Material.COOKED_COD, + Material.COOKED_MUTTON, + Material.COOKED_PORKCHOP, + Material.COOKED_RABBIT, + Material.COOKED_SALMON, + Material.COOKIE, + Material.DRIED_KELP, + Material.GOLDEN_APPLE, + Material.GLOW_BERRIES, + Material.GOLDEN_CARROT, + Material.HONEY_BLOCK, + Material.MELON_SLICE, + Material.MUSHROOM_STEW, + Material.POISONOUS_POTATO, + Material.POTATO, + Material.PUFFERFISH, + Material.PUMPKIN_PIE, + Material.RABBIT_STEW, + Material.BEEF, + Material.CHICKEN, + Material.COD, + Material.MUTTON, + Material.PORKCHOP, + Material.SALMON, + Material.ROTTEN_FLESH, + Material.SPIDER_EYE, + Material.COOKED_BEEF, + Material.SUSPICIOUS_STEW, + Material.SWEET_BERRIES, + Material.TROPICAL_FISH + + ); + + @Getter + public static List stripList = new KList<>( + Material.ACACIA_LOG, + Material.ACACIA_WOOD, + Material.STRIPPED_ACACIA_LOG, + Material.STRIPPED_ACACIA_WOOD, + Material.BIRCH_LOG, + Material.BIRCH_WOOD, + Material.STRIPPED_BIRCH_LOG, + Material.STRIPPED_BIRCH_WOOD, + Material.DARK_OAK_LOG, + Material.DARK_OAK_WOOD, + Material.STRIPPED_DARK_OAK_LOG, + Material.STRIPPED_DARK_OAK_WOOD, + Material.JUNGLE_LOG, + Material.JUNGLE_WOOD, + Material.STRIPPED_JUNGLE_LOG, + Material.STRIPPED_JUNGLE_WOOD, + Material.OAK_LOG, + Material.OAK_WOOD, + Material.STRIPPED_OAK_LOG, + Material.STRIPPED_OAK_WOOD, + Material.SPRUCE_LOG, + Material.SPRUCE_WOOD, + Material.STRIPPED_SPRUCE_LOG, + Material.STRIPPED_SPRUCE_WOOD, + Material.MANGROVE_LOG, + Material.MANGROVE_WOOD, + Material.STRIPPED_MANGROVE_LOG, + Material.STRIPPED_MANGROVE_WOOD, + Material.CRIMSON_STEM, + Material.CRIMSON_HYPHAE, + Materials.CHERRY_LOG, + Materials.CHERRY_WOOD, + Materials.STRIPPED_CHERRY_LOG, + Materials.STRIPPED_CHERRY_WOOD, + Materials.BAMBOO_BLOCK, + Materials.STRIPPED_BAMBOO_BLOCK, + Materials.PALE_OAK_LOG, + Materials.PALE_OAK_WOOD, + Materials.STRIPPED_PALE_OAK_LOG, + Materials.STRIPPED_PALE_OAK_WOOD + ).nonNull(); + + + @Getter + public static List ignitable = List.of( + Material.OBSIDIAN, + Material.NETHERRACK, + Material.SOUL_SAND, + Material.TNT + ); + + @Getter + public static List multiArmorable = List.of( + Material.ELYTRA, + Material.CHAINMAIL_CHESTPLATE, + Material.DIAMOND_CHESTPLATE, + Material.GOLDEN_CHESTPLATE, + Material.IRON_CHESTPLATE, + Material.LEATHER_CHESTPLATE, + Material.NETHERITE_CHESTPLATE + ); + + @Getter + public static List farmable = List.of( + Material.GRASS_BLOCK, + Material.DIRT, + Material.COARSE_DIRT, + Material.ROOTED_DIRT, + Material.WHEAT, + Material.ATTACHED_MELON_STEM, + Material.ATTACHED_PUMPKIN_STEM, + Material.MELON_STEM, + Material.PUMPKIN_STEM, + Material.POTATOES, + Material.SWEET_BERRY_BUSH, + Material.CARROTS, + Material.BEETROOTS, + Material.DIRT_PATH + + ); + + @Getter + public static List tillable = List.of( + ); + + @Getter + public static List burnable = new KList<>( + Material.OBSIDIAN, + Material.NETHERRACK, + Material.SOUL_SAND, + Material.ACACIA_LEAVES, + Material.BIRCH_LEAVES, + Material.DARK_OAK_LEAVES, + Material.JUNGLE_LEAVES, + Material.OAK_LEAVES, + Material.SPRUCE_LEAVES, + Material.MANGROVE_LEAVES, + Materials.CHERRY_LEAVES, + Materials.PALE_OAK_LEAVES, + Material.WHITE_WOOL, + Material.ORANGE_WOOL, + Material.MAGENTA_WOOL, + Material.LIGHT_BLUE_WOOL, + Material.YELLOW_WOOL, + Material.LIME_WOOL, + Material.PINK_WOOL, + Material.GRAY_WOOL, + Material.LIGHT_GRAY_WOOL, + Material.CYAN_WOOL, + Material.PURPLE_WOOL, + Material.BLUE_WOOL, + Material.BROWN_WOOL, + Material.GREEN_WOOL, + Material.RED_WOOL, + Material.BLACK_WOOL + ).nonNull(); + + @Getter + public static List toolPickaxes = List.of( + Material.WOODEN_PICKAXE, + Material.STONE_PICKAXE, + Material.IRON_PICKAXE, + Material.GOLDEN_PICKAXE, + Material.DIAMOND_PICKAXE, + Material.NETHERITE_PICKAXE + ); + + @Getter + public static List toolAxes = List.of( + Material.WOODEN_AXE, + Material.STONE_AXE, + Material.IRON_AXE, + Material.GOLDEN_AXE, + Material.DIAMOND_AXE, + Material.NETHERITE_AXE + ); + + @Getter + public static List toolSwords = List.of( + Material.WOODEN_SWORD, + Material.STONE_SWORD, + Material.IRON_SWORD, + Material.GOLDEN_SWORD, + Material.DIAMOND_SWORD, + Material.NETHERITE_SWORD + ); + + @Getter + public static List toolShovels = List.of( + Material.WOODEN_SHOVEL, + Material.STONE_SHOVEL, + Material.IRON_SHOVEL, + Material.GOLDEN_SHOVEL, + Material.DIAMOND_SHOVEL, + Material.NETHERITE_SHOVEL + ); + + @Getter + public static List toolHoes = List.of( + Material.WOODEN_HOE, + Material.STONE_HOE, + Material.IRON_HOE, + Material.GOLDEN_HOE, + Material.DIAMOND_HOE, + Material.NETHERITE_HOE + ); + + @Getter + public static List tool = List.of( + Material.WOODEN_PICKAXE, + Material.STONE_PICKAXE, + Material.IRON_PICKAXE, + Material.GOLDEN_PICKAXE, + Material.DIAMOND_PICKAXE, + Material.NETHERITE_PICKAXE, + //AXE + Material.WOODEN_AXE, + Material.STONE_AXE, + Material.IRON_AXE, + Material.GOLDEN_AXE, + Material.DIAMOND_AXE, + Material.NETHERITE_AXE, + //SWORD + Material.WOODEN_SWORD, + Material.STONE_SWORD, + Material.IRON_SWORD, + Material.GOLDEN_SWORD, + Material.DIAMOND_SWORD, + Material.NETHERITE_SWORD, + //SHOVEL + Material.WOODEN_SHOVEL, + Material.STONE_SHOVEL, + Material.IRON_SHOVEL, + Material.GOLDEN_SHOVEL, + Material.DIAMOND_SHOVEL, + Material.NETHERITE_SHOVEL, + //HOE + Material.WOODEN_HOE, + Material.STONE_HOE, + Material.IRON_HOE, + Material.GOLDEN_HOE, + Material.DIAMOND_HOE, + Material.NETHERITE_HOE, + + //EXTRA + Material.SHEARS + ); + + @Getter + public static List shovelPreference = List.of( + Material.CLAY, + Material.DIRT, + Material.FARMLAND, + Material.GRASS_BLOCK, + Material.GRAVEL, + Material.MYCELIUM, + Material.SAND, + Material.SOUL_SAND, + Material.SOUL_SOIL, + Material.SNOW, + Material.SNOW_BLOCK, + Material.POWDER_SNOW, + Material.PODZOL, + Material.RED_SAND, + Material.MUD, + Material.MUDDY_MANGROVE_ROOTS + ); + + @Getter + public static KList fishingDrops = new KList<>( + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.PUFFERFISH, + Material.TROPICAL_FISH, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.PUFFERFISH, + Material.TROPICAL_FISH, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.PUFFERFISH, + Material.TROPICAL_FISH, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.PUFFERFISH, + Material.TROPICAL_FISH, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.COD, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.SALMON, + Material.PUFFERFISH, + Material.TROPICAL_FISH, + + Material.BOW, + Material.FISHING_ROD, + Material.NAME_TAG, + Material.NAUTILUS_SHELL, + Material.SADDLE, + + Material.LILY_PAD, + Material.BOWL, + Material.LEATHER, + Material.STICK, + Material.ROTTEN_FLESH, + Material.STRING, + Material.GLASS_BOTTLE, + Material.BONE, + Material.INK_SAC, + Material.TRIPWIRE_HOOK + ); + + @Getter + public static List axePreference = new KList<>( + //FENCES + Material.ACACIA_FENCE, + Material.BIRCH_FENCE, + Material.DARK_OAK_FENCE, + Material.JUNGLE_FENCE, + Material.SPRUCE_FENCE, + Material.MANGROVE_FENCE, + Material.OAK_FENCE, + Material.CRIMSON_FENCE, + Material.WARPED_FENCE, + Materials.CHERRY_FENCE, + Materials.BAMBOO_FENCE, + Materials.PALE_OAK_FENCE, + //GATES + Material.ACACIA_FENCE_GATE, + Material.BIRCH_FENCE_GATE, + Material.DARK_OAK_FENCE_GATE, + Material.JUNGLE_FENCE_GATE, + Material.SPRUCE_FENCE_GATE, + Material.MANGROVE_FENCE_GATE, + Material.OAK_FENCE_GATE, + Material.CRIMSON_FENCE_GATE, + Material.WARPED_FENCE_GATE, + Materials.CHERRY_FENCE_GATE, + Materials.BAMBOO_FENCE_GATE, + Materials.PALE_OAK_FENCE_GATE, + //WOODS + Material.ACACIA_LOG, + Material.ACACIA_WOOD, + Material.STRIPPED_ACACIA_LOG, + Material.BIRCH_LOG, + Material.BIRCH_WOOD, + Material.STRIPPED_BIRCH_LOG, + Material.DARK_OAK_LOG, + Material.DARK_OAK_WOOD, + Material.STRIPPED_DARK_OAK_LOG, + Material.JUNGLE_LOG, + Material.JUNGLE_WOOD, + Material.STRIPPED_JUNGLE_LOG, + Material.OAK_LOG, + Material.OAK_WOOD, + Material.STRIPPED_OAK_LOG, + Material.SPRUCE_LOG, + Material.SPRUCE_WOOD, + Material.STRIPPED_SPRUCE_LOG, + Material.MANGROVE_LOG, + Material.MANGROVE_WOOD, + Material.STRIPPED_MANGROVE_LOG, + Material.CRIMSON_STEM, + Material.CRIMSON_HYPHAE, + Materials.CHERRY_LOG, + Materials.CHERRY_WOOD, + Materials.STRIPPED_CHERRY_LOG, + Materials.STRIPPED_CHERRY_WOOD, + Materials.BAMBOO_BLOCK, + Materials.STRIPPED_BAMBOO_BLOCK, + Materials.PALE_OAK_LOG, + Materials.PALE_OAK_WOOD, + Materials.STRIPPED_PALE_OAK_LOG, + Materials.STRIPPED_PALE_OAK_WOOD, + //SIGNS + Material.ACACIA_SIGN, + Material.ACACIA_WALL_SIGN, + Materials.ACACIA_HANGING_SIGN, + Materials.ACACIA_WALL_HANGING_SIGN, + Material.BIRCH_SIGN, + Material.BIRCH_WALL_SIGN, + Materials.BIRCH_HANGING_SIGN, + Materials.BIRCH_WALL_HANGING_SIGN, + Material.DARK_OAK_SIGN, + Material.DARK_OAK_WALL_SIGN, + Materials.DARK_OAK_HANGING_SIGN, + Materials.DARK_OAK_WALL_HANGING_SIGN, + Material.JUNGLE_SIGN, + Material.JUNGLE_WALL_SIGN, + Materials.JUNGLE_HANGING_SIGN, + Materials.JUNGLE_WALL_HANGING_SIGN, + Material.OAK_SIGN, + Material.OAK_WALL_SIGN, + Materials.OAK_HANGING_SIGN, + Materials.OAK_WALL_HANGING_SIGN, + Material.SPRUCE_SIGN, + Material.SPRUCE_WALL_SIGN, + Materials.SPRUCE_HANGING_SIGN, + Materials.SPRUCE_WALL_HANGING_SIGN, + Material.MANGROVE_SIGN, + Material.MANGROVE_WALL_SIGN, + Materials.MANGROVE_HANGING_SIGN, + Materials.MANGROVE_WALL_HANGING_SIGN, + Material.CRIMSON_SIGN, + Material.CRIMSON_WALL_SIGN, + Materials.CRIMSON_HANGING_SIGN, + Materials.CRIMSON_WALL_HANGING_SIGN, + Material.WARPED_SIGN, + Material.WARPED_WALL_SIGN, + Materials.WARPED_HANGING_SIGN, + Materials.WARPED_WALL_HANGING_SIGN, + Materials.CHERRY_SIGN, + Materials.CHERRY_WALL_SIGN, + Materials.CHERRY_HANGING_SIGN, + Materials.CHERRY_WALL_HANGING_SIGN, + Materials.BAMBOO_SIGN, + Materials.BAMBOO_WALL_SIGN, + Materials.BAMBOO_HANGING_SIGN, + Materials.BAMBOO_WALL_HANGING_SIGN, + Materials.PALE_OAK_SIGN, + Materials.PALE_OAK_WALL_SIGN, + Materials.PALE_OAK_HANGING_SIGN, + Materials.PALE_OAK_WALL_HANGING_SIGN, + //WOODEN_BUTTONS + Material.ACACIA_BUTTON, + Material.BIRCH_BUTTON, + Material.DARK_OAK_BUTTON, + Material.JUNGLE_BUTTON, + Material.OAK_BUTTON, + Material.SPRUCE_BUTTON, + Material.MANGROVE_BUTTON, + Material.CRIMSON_BUTTON, + Material.WARPED_BUTTON, + Materials.CHERRY_BUTTON, + Materials.BAMBOO_BUTTON, + Materials.PALE_OAK_BUTTON, + //WOODEN_DOORS + Material.ACACIA_DOOR, + Material.BIRCH_DOOR, + Material.DARK_OAK_DOOR, + Material.JUNGLE_DOOR, + Material.OAK_DOOR, + Material.SPRUCE_DOOR, + Material.MANGROVE_DOOR, + Material.CRIMSON_DOOR, + Material.WARPED_DOOR, + Materials.CHERRY_DOOR, + Materials.BAMBOO_DOOR, + Materials.PALE_OAK_DOOR, + //WOODEN_PRESSURE_PLATES + Material.ACACIA_PRESSURE_PLATE, + Material.BIRCH_PRESSURE_PLATE, + Material.DARK_OAK_PRESSURE_PLATE, + Material.JUNGLE_PRESSURE_PLATE, + Material.OAK_PRESSURE_PLATE, + Material.SPRUCE_PRESSURE_PLATE, + Material.MANGROVE_PRESSURE_PLATE, + Material.CRIMSON_PRESSURE_PLATE, + Material.WARPED_PRESSURE_PLATE, + Materials.CHERRY_PRESSURE_PLATE, + Materials.BAMBOO_PRESSURE_PLATE, + Materials.PALE_OAK_PRESSURE_PLATE, + //WOODEN_TRAPDOORS + Material.ACACIA_TRAPDOOR, + Material.BIRCH_TRAPDOOR, + Material.DARK_OAK_TRAPDOOR, + Material.JUNGLE_TRAPDOOR, + Material.OAK_TRAPDOOR, + Material.SPRUCE_TRAPDOOR, + Material.MANGROVE_TRAPDOOR, + Material.CRIMSON_TRAPDOOR, + Material.WARPED_TRAPDOOR, + Materials.CHERRY_TRAPDOOR, + Materials.BAMBOO_TRAPDOOR, + Materials.PALE_OAK_TRAPDOOR, + //WOODEN_STAIRS + Material.ACACIA_STAIRS, + Material.BIRCH_STAIRS, + Material.DARK_OAK_STAIRS, + Material.JUNGLE_STAIRS, + Material.OAK_STAIRS, + Material.SPRUCE_STAIRS, + Material.MANGROVE_STAIRS, + Material.CRIMSON_STAIRS, + Material.WARPED_STAIRS, + Materials.CHERRY_STAIRS, + Materials.BAMBOO_STAIRS, + Materials.BAMBOO_MOSAIC_STAIRS, + Materials.PALE_OAK_STAIRS, + //WOODEN_SLABS + Material.ACACIA_SLAB, + Material.BIRCH_SLAB, + Material.DARK_OAK_SLAB, + Material.JUNGLE_SLAB, + Material.OAK_SLAB, + Material.SPRUCE_SLAB, + Material.MANGROVE_SLAB, + Material.CRIMSON_SLAB, + Material.WARPED_SLAB, + Materials.CHERRY_SLAB, + Materials.BAMBOO_SLAB, + Materials.BAMBOO_MOSAIC_SLAB, + Materials.PALE_OAK_SLAB, + //PLANKS + Material.ACACIA_PLANKS, + Material.BIRCH_PLANKS, + Material.DARK_OAK_PLANKS, + Material.JUNGLE_PLANKS, + Material.OAK_PLANKS, + Material.SPRUCE_PLANKS, + Material.MANGROVE_PLANKS, + Material.CRIMSON_PLANKS, + Material.WARPED_PLANKS, + Materials.CHERRY_PLANKS, + Materials.BAMBOO_PLANKS, + Materials.BAMBOO_MOSAIC, + Materials.PALE_OAK_PLANKS, + //MISC + Material.BEE_NEST, + Material.DRIED_KELP_BLOCK, + Material.BEEHIVE, + Material.CHEST, + Material.CRAFTING_TABLE, + Material.JUKEBOX, + Material.LADDER, + Material.LOOM, + Material.NOTE_BLOCK, + Material.BARREL, + Material.BOOKSHELF, + Material.CARTOGRAPHY_TABLE, + Material.FLETCHING_TABLE, + Material.CAMPFIRE, + Material.SOUL_CAMPFIRE, + Material.HONEYCOMB, + Material.LECTERN, + Material.COCOA, + Material.JACK_O_LANTERN, + Material.PUMPKIN, + Material.MELON, + Material.TRAPPED_CHEST + ).nonNull(); +} diff --git a/src/main/java/art/arcane/adapt/content/item/KnowledgeOrb.java b/src/main/java/art/arcane/adapt/content/item/KnowledgeOrb.java new file mode 100644 index 000000000..98ac2ec2a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/KnowledgeOrb.java @@ -0,0 +1,128 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.item.DataItem; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@AllArgsConstructor +@Data +public class KnowledgeOrb implements DataItem { + public static KnowledgeOrb io = new KnowledgeOrb(); + + public static Data get(ItemStack is) { + return io.getData(is); + } + + public static String getSkill(ItemStack stack) { + if (io.getData(stack) != null) { + return io.getData(stack).getSkill(); + } + + return null; + } + + public static long getKnowledge(ItemStack stack) { + if (io.getData(stack) != null) { + return io.getData(stack).getKnowledge(); + } + + return 0; + } + + public static void set(ItemStack item, String skill, int knowledge) { + io.setData(item, new Data(skill, knowledge)); + } + + public static ItemStack with(String skill, int knowledge) { + return io.withData(new Data(skill, knowledge)); + } + + public static ItemStack with(Map knowledgeMap) { + return io.withData(new Data(knowledgeMap)); + } + + @Override + public Material getMaterial() { + return Material.SNOWBALL; + } + + @Override + public Class getType() { + return KnowledgeOrb.Data.class; + } + + @Override + public void applyLore(Data data, List lore) { + for (Map.Entry entry : data.getKnowledgeMap().entrySet()) { + String skill = entry.getKey(); + int knowledge = entry.getValue(); + lore.add(C.WHITE + Localizer.dLocalize("snippets.knowledge_orb.contains") + " " + C.UNDERLINE + C.WHITE + "" + knowledge + " " + Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skill).getDisplayName() + " " + Localizer.dLocalize("snippets.knowledge_orb.knowledge")); + } + lore.add(C.LIGHT_PURPLE + Localizer.dLocalize("snippets.knowledge_orb.rightclick") + " " + C.GRAY + Localizer.dLocalize("snippets.knowledge_orb.togainknowledge")); + } + + @Override + public void applyMeta(Data data, ItemMeta meta) { + meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); + meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS); + meta.setDisplayName(Localizer.dLocalize("snippets.knowledge_orb.knowledge_orb")); + } + + @AllArgsConstructor + @lombok.Data + public static class Data { + private Map knowledgeMap; + + public Data(String skill, int knowledge) { + this.knowledgeMap = new HashMap<>(); + this.knowledgeMap.put(skill, knowledge); + } + + public String getSkill() { + return knowledgeMap.keySet().iterator().next(); + } + + public int getKnowledge() { + return knowledgeMap.values().iterator().next(); + } + + public void apply(Player p) { + for (Map.Entry entry : knowledgeMap.entrySet()) { + String skill = entry.getKey(); + int knowledge = entry.getValue(); + Adapt.instance.getAdaptServer().getPlayer(p).getSkillLine(skill).giveKnowledge(knowledge); + } + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/multiItems/MultiArmor.java b/src/main/java/art/arcane/adapt/content/item/multiItems/MultiArmor.java new file mode 100644 index 000000000..489c76c02 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/multiItems/MultiArmor.java @@ -0,0 +1,62 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item.multiItems; + +import art.arcane.volmlib.util.format.Form; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; + +public class MultiArmor implements MultiItem { + @Override + public boolean supportsItem(ItemStack itemStack) { + return true; + } + + @Override + public String getKey() { + return "multiarmor"; + } + + @Override + public void onApplyMeta(ItemStack item, ItemMeta meta, List otherItems) { + List lore = new ArrayList<>(); + lore.add("MultiArmor (" + (otherItems.size() + 1) + " Items)"); + lore.add("-> " + Form.capitalizeWords(item.getType().name().toLowerCase().replaceAll("\\Q_\\E", " "))); + + for (ItemStack i : otherItems) { + lore.add("- " + Form.capitalizeWords(i.getType().name().toLowerCase().replaceAll("\\Q_\\E", " "))); + } + + meta.setLore(lore); + } + + public ItemStack nextElytra(ItemStack item) { + return nextMatching(item, i -> i.getType().equals(Material.ELYTRA)); + } + + public ItemStack nextChestplate(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("_CHESTPLATE")); + } + + +} diff --git a/src/main/java/art/arcane/adapt/content/item/multiItems/MultiItem.java b/src/main/java/art/arcane/adapt/content/item/multiItems/MultiItem.java new file mode 100644 index 000000000..5fc482232 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/multiItems/MultiItem.java @@ -0,0 +1,182 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item.multiItems; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.nms.NMS; +import art.arcane.adapt.util.common.io.BukkitGson; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.inventorygui.WindowResolution; +import lombok.*; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public interface MultiItem { + boolean supportsItem(ItemStack itemStack); + + String getKey(); + + default WindowResolution getWindowType() { + return WindowResolution.W5_H1; + } + + default ItemStack build(ItemStack... stacks) { + ItemStack s = stacks[0]; + + for (int i = 1; i < stacks.length; i++) { + add(s, stacks[i]); + } + + return s; + } + + default boolean remove(ItemStack multi, ItemStack toRemove) { + int ind = getItems(multi).indexOf(toRemove); + if (ind == -1) { + return false; + } + + remove(multi, ind); + return true; + } + + default void remove(ItemStack multi, int index) { + List it = getItems(multi); + it.remove(index); + setItems(multi, it); + } + + default void add(ItemStack multi, ItemStack item) { + if (isMultiItem(item)) { + explode(item).forEach(i -> add(multi, i)); + } else { + setItems(multi, getItems(multi).qadd(item)); + } + } + + default ItemStack nextMatching(ItemStack item, Predicate predicate) { + KList items = getItems(item); + for (int i = 0; i < items.size(); i++) { + if (predicate.test(items.get(i))) { + return switchTo(item, i); + } + } + + return item; + } + + default ItemStack nextTool(ItemStack multi) { + return switchTo(multi, 0); + } + + default ItemStack switchTo(ItemStack multi, int index) { + List items = getItems(multi); + ItemStack next = items.remove(index); + items.add(getRealItem(multi)); + setItems(next, items); + return next; + } + + default void setItems(ItemStack multi, List itemStacks) { + setMultiItemData(multi, MultiItemData.builder() + .rawItems(itemStacks.stream().filter(this::supportsItem) + .map(NMS::serializeStack) + .collect(Collectors.toList())) + .build()); + } + + default KList getItems(ItemStack multi) { + MultiItemData d = getMultiItemData(multi); + + if (d == null) { + return new KList<>(); + } + + return d.getItems(); + } + + default KList explode(ItemStack multi) { + KList it = new KList<>(); + it.add(getRealItem(multi)); + it.add(getItems(multi)); + return it; + } + + void onApplyMeta(ItemStack item, ItemMeta meta, List otherItems); + + default boolean isMultiItem(ItemStack item) { + return supportsItem(item) && getMultiItemData(item) != null; + } + + default ItemStack getRealItem(ItemStack multi) { + ItemStack c = multi.clone(); + if (c.hasItemMeta()) { + ItemMeta meta = c.getItemMeta(); + meta.getPersistentDataContainer().remove(new NamespacedKey(Adapt.instance, getKey())); + c.setItemMeta(meta); + } + + return c; + } + + default MultiItemData getMultiItemData(ItemStack multi) { + try { + ItemMeta meta = multi.getItemMeta(); + String st = meta.getPersistentDataContainer() + .get(new NamespacedKey(Adapt.instance, getKey()), PersistentDataType.STRING); + return BukkitGson.gson.fromJson(st, MultiItemData.class); + } catch (Throwable e) { + return null; + } + } + + default void setMultiItemData(ItemStack multi, MultiItemData data) { + String s = BukkitGson.gson.toJson(data); + ItemMeta meta = multi.getItemMeta(); + meta.getPersistentDataContainer() + .set(new NamespacedKey(Adapt.instance, getKey()), PersistentDataType.STRING, s); + multi.setItemMeta(meta); + meta = multi.getItemMeta(); + onApplyMeta(multi, meta, getItems(multi)); + multi.setItemMeta(meta); + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + class MultiItemData { + @Singular + List rawItems; + + KList getItems() { + return rawItems.stream().map(NMS::deserializeStack).collect(KList.collector()); + } + + void setItems(List is) { + rawItems = is.stream().map(NMS::serializeStack).collect(KList.collector()); + } + } +} diff --git a/src/main/java/art/arcane/adapt/content/item/multiItems/OmniTool.java b/src/main/java/art/arcane/adapt/content/item/multiItems/OmniTool.java new file mode 100644 index 000000000..9b0bfb7d3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/item/multiItems/OmniTool.java @@ -0,0 +1,104 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.item.multiItems; + +import art.arcane.volmlib.util.format.Form; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; + +public class OmniTool implements MultiItem { + @Override + public boolean supportsItem(ItemStack itemStack) { + return true; + } + + @Override + public String getKey() { + return "omnitool"; + } + + @Override + public void onApplyMeta(ItemStack item, ItemMeta meta, List otherItems) { + List lore = new ArrayList<>(); + lore.add("Leatherman (" + (otherItems.size() + 1) + " Items)"); + lore.add("-> " + Form.capitalizeWords(item.getType().name().toLowerCase().replaceAll("\\Q_\\E", " "))); + + for (ItemStack i : otherItems) { + lore.add("- " + Form.capitalizeWords(i.getType().name().toLowerCase().replaceAll("\\Q_\\E", " "))); + } + + meta.setLore(lore); + } + + public ItemStack nextPickaxe(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("_PICKAXE")); + } + + public ItemStack nextAxe(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("_AXE")); + } + + public ItemStack nextSword(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("_SWORD")); + } + + public ItemStack nextShovel(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("_SHOVEL")); + } + + public ItemStack nextHoe(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("_HOE")); + } + + public ItemStack nextShears(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("SHEARS")); + } + + public ItemStack nextFnS(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("FLINT_AND_STEEL")); + } + + public ItemStack nextItem(ItemStack item) { + return nextMatching(item, i -> i.getType().name().endsWith("_PICKAXE") || i.getType().name().endsWith("_AXE") || i.getType().name().endsWith("_SWORD") || i.getType().name().endsWith("_SHOVEL") || i.getType().name().endsWith("_HOE") || i.getType().name().endsWith("SHEARS")); + } + + public ItemStack nextNonMatchingItem(ItemStack item, Material material) { + if (material.toString().contains("_PICKAXE")) { + return nextAxe(item); + } else if (material.toString().contains("_AXE")) { + return nextSword(item); + } else if (material.toString().contains("_SWORD")) { + return nextShovel(item); + } else if (material.toString().contains("_SHOVEL")) { + return nextHoe(item); + } else if (material.toString().contains("_HOE")) { + return nextShears(item); + } else if (material.toString().contains("SHEARS")) { + return nextPickaxe(item); + } else if (material.toString().contains("FLINT_AND_STEEL")) { + return nextFnS(item); + } else { + return nextItem(item); + } + } +} diff --git a/src/main/java/com/volmit/adapt/content/matter/BrewingStandOwner.java b/src/main/java/art/arcane/adapt/content/matter/BrewingStandOwner.java similarity index 94% rename from src/main/java/com/volmit/adapt/content/matter/BrewingStandOwner.java rename to src/main/java/art/arcane/adapt/content/matter/BrewingStandOwner.java index 166448632..683620b60 100644 --- a/src/main/java/com/volmit/adapt/content/matter/BrewingStandOwner.java +++ b/src/main/java/art/arcane/adapt/content/matter/BrewingStandOwner.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.content.matter; +package art.arcane.adapt.content.matter; import lombok.AllArgsConstructor; import lombok.Data; @@ -26,5 +26,5 @@ @AllArgsConstructor @Data public class BrewingStandOwner { - private UUID owner; + private UUID owner; } diff --git a/src/main/java/art/arcane/adapt/content/matter/BrewingStandOwnerMatter.java b/src/main/java/art/arcane/adapt/content/matter/BrewingStandOwnerMatter.java new file mode 100644 index 000000000..681aefcf0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/matter/BrewingStandOwnerMatter.java @@ -0,0 +1,47 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.matter; + +import art.arcane.spatial.matter.slices.RawMatter; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.UUID; + +public class BrewingStandOwnerMatter extends RawMatter { + public BrewingStandOwnerMatter(int w, int h, int d) { + super(w, h, d, BrewingStandOwner.class); + } + + public BrewingStandOwnerMatter() { + this(1, 1, 1); + } + + @Override + public void writeNode(BrewingStandOwner brewingStandOwner, DataOutputStream dataOutputStream) throws IOException { + dataOutputStream.writeLong(brewingStandOwner.getOwner().getMostSignificantBits()); + dataOutputStream.writeLong(brewingStandOwner.getOwner().getLeastSignificantBits()); + } + + @Override + public BrewingStandOwner readNode(DataInputStream dataInputStream) throws IOException { + return new BrewingStandOwner(new UUID(dataInputStream.readLong(), dataInputStream.readLong())); + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/content/protector/ChestProtectProtector.java b/src/main/java/art/arcane/adapt/content/protector/ChestProtectProtector.java new file mode 100644 index 000000000..7127b8dca --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/protector/ChestProtectProtector.java @@ -0,0 +1,36 @@ +package art.arcane.adapt.content.protector; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.protection.Protector; +import me.angeschossen.chestprotect.api.addons.ChestProtectAddon; +import me.angeschossen.chestprotect.api.protection.block.BlockProtection; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class ChestProtectProtector implements Protector { + private final ChestProtectAddon chestProtect; + + public ChestProtectProtector() { + this.chestProtect = new ChestProtectAddon(Adapt.instance); + } + + @Override + public boolean canAccessChest(Player player, Location chestlocation, Adaptation adaptation) { + if (!chestProtect.isProtectable(chestlocation.getBlock())) return true; + BlockProtection blockProtection = chestProtect.getProtection(chestlocation); + if (blockProtection == null) return true; + return blockProtection.isTrusted(player.getUniqueId()); + } + + @Override + public String getName() { + return "ChestProtect"; + } + + @Override + public boolean isEnabledByDefault() { + return AdaptConfig.get().getProtectorSupport().isChestProtect(); + } +} diff --git a/src/main/java/art/arcane/adapt/content/protector/FactionsClaimProtector.java b/src/main/java/art/arcane/adapt/content/protector/FactionsClaimProtector.java new file mode 100644 index 000000000..09ded074f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/protector/FactionsClaimProtector.java @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.protector; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.protection.Protector; +import com.massivecraft.factions.*; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class FactionsClaimProtector implements Protector { + + @Override + public boolean checkRegion(Player player, Location location, Adaptation adaptation) { + Faction f = Board.getInstance().getFactionAt(new FLocation(player.getLocation())); + return checkPerm(player, f, adaptation) || f.isWilderness(); + } + + @Override + public boolean canPVP(Player player, Location victimLocation, Adaptation adaptation) { + Faction f = Board.getInstance().getFactionAt(new FLocation(victimLocation)); + return checkPerm(player, f, adaptation) || !f.noPvPInTerritory(); + } + + private boolean checkPerm(Player player, Faction f, Adaptation adaptation) { + FPlayer fp = FPlayers.getInstance().getByPlayer(player); + return f == null + || fp.getFaction() == f + || fp.isAdminBypassing(); + } + + @Override + public String getName() { + return "Factions"; + } + + @Override + public boolean isEnabledByDefault() { + return AdaptConfig.get().getProtectorSupport().isFactionsClaim(); + } +} diff --git a/src/main/java/art/arcane/adapt/content/protector/GriefDefenderProtector.java b/src/main/java/art/arcane/adapt/content/protector/GriefDefenderProtector.java new file mode 100644 index 000000000..310b198dd --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/protector/GriefDefenderProtector.java @@ -0,0 +1,102 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.protector; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.protection.Protector; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.UUID; + +public class GriefDefenderProtector implements Protector { + /** + * This api is garbage, and obfuscated. If i can get a jar ill improve it, but + * for now this is the best i can do. Or if someone wants to make a PR feel + * free. + *

+ * I as an author do not support this api, and do not recommend it, as they + * are making ME pay $15(spigot) + $5(patreon) per month to be able to ask + * questions in their discord, and get unobfuscated jars. + * + */ + + @Override + public boolean checkRegion(Player player, Location location, Adaptation adaptation) { + final Claim claim = GriefDefender.getCore().getClaimAt(location); + return checkPerm(player, claim, adaptation) || claim.isWilderness(); + } + + @Override + public boolean canPVP(Player player, Location entityLocation, Adaptation adaptation) { + final Claim claim = GriefDefender.getCore().getClaimAt(entityLocation); + if (checkPerm(player, claim, adaptation)) { + return claim.isPvpAllowed(); + } + return false; + } + + private boolean checkPerm(Player player, Claim claim, Adaptation adaptation) { + if (claim == null) { + return true; + } + UUID uuid = player.getUniqueId(); + return claim.isWilderness() + || claim.getOwnerUniqueId().equals(uuid) + || claim.getUserTrusts().contains(uuid); + } + + @Override + public boolean canPVE(Player player, Location entityLocation, Adaptation adaptation) { + return checkPerm(player, GriefDefender.getCore().getClaimAt(entityLocation), adaptation); + } + + @Override + public boolean canInteract(Player player, Location targetLocation, Adaptation adaptation) { + return checkPerm(player, GriefDefender.getCore().getClaimAt(targetLocation), adaptation); + } + + @Override + public boolean canAccessChest(Player player, Location chestLocation, Adaptation adaptation) { + return checkPerm(player, GriefDefender.getCore().getClaimAt(chestLocation), adaptation); + } + + @Override + public boolean canBlockBreak(Player player, Location blockLocation, Adaptation adaptation) { + return checkPerm(player, GriefDefender.getCore().getClaimAt(blockLocation), adaptation); + } + + @Override + public boolean canBlockPlace(Player player, Location blockLocation, Adaptation adaptation) { + return checkPerm(player, GriefDefender.getCore().getClaimAt(blockLocation), adaptation); + } + + @Override + public String getName() { + return "GriefDefender"; + } + + @Override + public boolean isEnabledByDefault() { + return AdaptConfig.get().getProtectorSupport().isFactionsClaim(); + } +} diff --git a/src/main/java/art/arcane/adapt/content/protector/GriefPreventionProtector.java b/src/main/java/art/arcane/adapt/content/protector/GriefPreventionProtector.java new file mode 100644 index 000000000..a7e2b7aba --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/protector/GriefPreventionProtector.java @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.protector; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.protection.Protector; +import me.ryanhamshire.GriefPrevention.Claim; +import me.ryanhamshire.GriefPrevention.ClaimPermission; +import me.ryanhamshire.GriefPrevention.GriefPrevention; +import me.ryanhamshire.GriefPrevention.PlayerData; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.Objects; + +public class GriefPreventionProtector implements Protector { + + + @Override + public boolean canBlockBreak(Player player, Location location, Adaptation adaptation) { + return canEditClaim(player, location); + } + + @Override + public boolean canBlockPlace(Player player, Location location, Adaptation adaptation) { + return canEditClaim(player, location); + } + + @Override + public String getName() { + return "GriefPrevention"; + } + + @Override + public boolean isEnabledByDefault() { + return AdaptConfig.get().getProtectorSupport().isGriefprevention(); + } + + @Override + public void unregister() { + Protector.super.unregister(); + } + + + private boolean canEditClaim(Player player, Location location) { + Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, true, null); + PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); + + if (claim == null) { + return true; + } + //If doesn't check is adminclaim getting ownerid return null + if (!claim.isAdminClaim() && Objects.equals(claim.getOwnerID(), player.getUniqueId())) { + return true; + } else if (claim.getPermission(player.getUniqueId().toString()) == ClaimPermission.Build) { + return true; + } + + return playerData.ignoreClaims || claim.isAdminClaim() && player.hasPermission("griefprevention.adminclaims"); + + } + + +} + diff --git a/src/main/java/art/arcane/adapt/content/protector/LocketteProProtector.java b/src/main/java/art/arcane/adapt/content/protector/LocketteProProtector.java new file mode 100644 index 000000000..e7477f745 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/protector/LocketteProProtector.java @@ -0,0 +1,25 @@ +package art.arcane.adapt.content.protector; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.protection.Protector; +import me.crafter.mc.lockettepro.LocketteProAPI; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public class LocketteProProtector implements Protector { + @Override + public boolean canAccessChest(Player player, Location chestlocation, Adaptation adaptation) { + return LocketteProAPI.isOwner(chestlocation.getBlock(), player); + } + + @Override + public String getName() { + return "LockettePro"; + } + + @Override + public boolean isEnabledByDefault() { + return AdaptConfig.get().getProtectorSupport().isLockettePro(); + } +} diff --git a/src/main/java/art/arcane/adapt/content/protector/ResidenceProtector.java b/src/main/java/art/arcane/adapt/content/protector/ResidenceProtector.java new file mode 100644 index 000000000..14fe9e05f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/protector/ResidenceProtector.java @@ -0,0 +1,93 @@ +package art.arcane.adapt.content.protector; + +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.protection.Protector; +import art.arcane.adapt.util.common.scheduling.J; +import com.bekvon.bukkit.residence.Residence; +import com.bekvon.bukkit.residence.containers.Flags; +import com.bekvon.bukkit.residence.protection.ClaimedResidence; +import com.bekvon.bukkit.residence.protection.FlagPermissions; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class ResidenceProtector implements Protector { + + public ResidenceProtector() { + FlagPermissions.addFlag("use-adaptations"); + } + + @Override + public boolean checkRegion(Player player, Location location, Adaptation adaptation) { + return checkPerm(player, location, "use-adaptations"); + } + + @Override + public boolean canBlockBreak(Player player, Location blockLocation, Adaptation adaptation) { + return checkRegion(player, blockLocation, adaptation) && checkPerm(player, blockLocation, Flags.destroy); + } + + @Override + public boolean canBlockPlace(Player player, Location blockLocation, Adaptation adaptation) { + return checkRegion(player, blockLocation, adaptation) && checkPerm(player, blockLocation, Flags.place); + } + + @Override + public boolean canPVP(Player player, Location entityLocation, Adaptation adaptation) { + return checkRegion(player, entityLocation, adaptation) && checkPerm(player, entityLocation, Flags.pvp); + } + + @Override + public boolean canPVE(Player player, Location entityLocation, Adaptation adaptation) { + return checkRegion(player, entityLocation, adaptation) && checkPerm(player, entityLocation, Flags.damage); + } + + @Override + public boolean canInteract(Player player, Location targetLocation, Adaptation adaptation) { + return checkRegion(player, targetLocation, adaptation) && checkPerm(player, targetLocation, Flags.use); + } + + @Override + public boolean canAccessChest(Player player, Location chestLocation, Adaptation adaptation) { + return checkRegion(player, chestLocation, adaptation) && checkPerm(player, chestLocation, Flags.container); + } + + private boolean checkPerm(Player player, Location location, Flags flag) { + AtomicBoolean perm = new AtomicBoolean(true); + J.s(() -> { + if (!Residence.getInstance().isDisabledWorld(location.getWorld())) { + ClaimedResidence res = Residence.getInstance().getResidenceManager().getByLoc(location); + if (res != null) { + perm.set(res.getPermissions().playerHas(player.getName(), flag, true)); + } + } + }); + return perm.get(); + } + + private boolean checkPerm(Player player, Location location, String flag) { + AtomicBoolean perm = new AtomicBoolean(true); + J.s(() -> { + if (!Residence.getInstance().isDisabledWorld(location.getWorld())) { + ClaimedResidence res = Residence.getInstance().getResidenceManager().getByLoc(location); + if (res != null) { + perm.set(res.getPermissions().playerHas(player.getName(), flag, true)); + } + } + }); + return perm.get(); + } + + @Override + public String getName() { + return "Residence"; + } + + @Override + public boolean isEnabledByDefault() { + return AdaptConfig.get().getProtectorSupport().isResidence(); + } + +} diff --git a/src/main/java/art/arcane/adapt/content/protector/WorldGuardProtector.java b/src/main/java/art/arcane/adapt/content/protector/WorldGuardProtector.java new file mode 100644 index 000000000..06ec781c1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/protector/WorldGuardProtector.java @@ -0,0 +1,130 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.protector; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.protection.Protector; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.util.concurrent.ConcurrentMap; + +public class WorldGuardProtector implements Protector { + + private final RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + private final StateFlag flag = registerFlag(); + + public static StateFlag registerFlag() { + FlagRegistry registry = WorldGuard.getInstance().getFlagRegistry(); + StateFlag flag = (StateFlag) registry.get("use-adaptations"); + if (flag != null) return flag; + flag = new StateFlag("use-adaptations", false); + + try { + registry.register(flag); + } catch (IllegalStateException ignored) { + Adapt.warn("WorldGuard flag was not registered! Injecting it now..."); + try { + // Access the flags field of the registry + Field field = registry.getClass().getDeclaredField("flags"); + // This line makes the private field accessible + field.setAccessible(true); + // Get the flags from the registry + ConcurrentMap> flags = (ConcurrentMap>) field.get(registry); + // Add it to the registry + flags.put(flag.getName().toLowerCase(), flag); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + return flag; + } + + @Override + public boolean checkRegion(Player player, Location location, Adaptation adaptation) { + return checkPerm(player, location, flag); + } + + @Override + public boolean canBlockBreak(Player player, Location blockLocation, Adaptation adaptation) { + return checkRegion(player, blockLocation, adaptation) && checkPerm(player, blockLocation, Flags.BLOCK_BREAK); + } + + @Override + public boolean canBlockPlace(Player player, Location blockLocation, Adaptation adaptation) { + return checkRegion(player, blockLocation, adaptation) && checkPerm(player, blockLocation, Flags.BLOCK_PLACE); + } + + @Override + public boolean canPVP(Player player, Location entityLocation, Adaptation adaptation) { + return checkRegion(player, entityLocation, adaptation) && checkPerm(player, entityLocation, Flags.PVP); + } + + @Override + public boolean canPVE(Player player, Location entityLocation, Adaptation adaptation) { + return checkRegion(player, entityLocation, adaptation) && checkPerm(player, entityLocation, Flags.DAMAGE_ANIMALS); + } + + @Override + public boolean canInteract(Player player, Location targetLocation, Adaptation adaptation) { + return checkRegion(player, targetLocation, adaptation) && checkPerm(player, targetLocation, Flags.INTERACT); + } + + @Override + public boolean canAccessChest(Player player, Location chestLocation, Adaptation adaptation) { + return checkRegion(player, chestLocation, adaptation) && checkPerm(player, chestLocation, Flags.CHEST_ACCESS); + } + + private boolean checkPerm(Player player, Location location, StateFlag flag) { + RegionQuery regionQuery = container.createQuery(); + com.sk89q.worldedit.util.Location loc = BukkitAdapter.adapt(location); + if (!hasBypass(player, location)) + return regionQuery.queryState(loc, WorldGuardPlugin.inst().wrapPlayer(player), flag) != StateFlag.State.DENY; + return true; + } + + @Override + public String getName() { + return "WorldGuard"; + } + + @Override + public boolean isEnabledByDefault() { + return AdaptConfig.get().getProtectorSupport().isWorldguard(); + } + + private boolean hasBypass(Player p, Location l) { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(p); + com.sk89q.worldedit.world.World world = BukkitAdapter.adapt(l.getWorld()); + return WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, world); + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillAgility.java b/src/main/java/art/arcane/adapt/content/skill/SkillAgility.java new file mode 100644 index 000000000..bae6a0177 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillAgility.java @@ -0,0 +1,263 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.agility.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerMoveEvent; + +public class SkillAgility extends SimpleSkill { + public SkillAgility() { + super("agility", Localizer.dLocalize("skill.agility.icon")); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("skill.agility.description")); + setDisplayName(Localizer.dLocalize("skill.agility.name")); + setColor(C.GREEN); + setInterval(975); + setIcon(Material.FEATHER); + registerAdaptation(new AgilityWindUp()); + registerAdaptation(new AgilityWallJump()); + registerAdaptation(new AgilitySuperJump()); + registerAdaptation(new AgilityArmorUp()); + registerAdaptation(new AgilityLadderSlide()); + registerAdaptation(new AgilityParkourMomentum()); + registerAdaptation(new AgilityRollLanding()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEATHER_BOOTS) + .key("challenge_move_1k") + .title(Localizer.dLocalize("advancement.challenge_move_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_move_1k.description")) + .model(CustomModel.get(Material.LEATHER_BOOTS, "advancement", "agility", "challenge_move_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_BOOTS) + .key("challenge_sprint_5k") + .title(Localizer.dLocalize("advancement.challenge_sprint_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_sprint_5k.description")) + .model(CustomModel.get(Material.IRON_BOOTS, "advancement", "agility", "challenge_sprint_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_BOOTS) + .key("challenge_sprint_50k") + .title(Localizer.dLocalize("advancement.challenge_sprint_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_sprint_50k.description")) + .model(CustomModel.get(Material.DIAMOND_BOOTS, "advancement", "agility", "challenge_sprint_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_BOOTS) + .key("challenge_sprint_500k") + .title(Localizer.dLocalize("advancement.challenge_sprint_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_sprint_500k.description")) + .model(CustomModel.get(Material.NETHERITE_BOOTS, "advancement", "agility", "challenge_sprint_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .child(AdaptAdvancement.builder() + .icon(Material.GOLDEN_BOOTS) + .key("challenge_sprint_marathon") + .title(Localizer.dLocalize("advancement.challenge_sprint_marathon.title")) + .description(Localizer.dLocalize("advancement.challenge_sprint_marathon.description")) + .model(CustomModel.get(Material.GOLDEN_BOOTS, "advancement", "agility", "challenge_sprint_marathon")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_move_1k", "move", 1000, getConfig().challengeMove1kReward); + registerMilestone("challenge_sprint_5k", "move", 5000, getConfig().challengeSprint5kReward); + registerMilestone("challenge_sprint_50k", "move", 50000, getConfig().challengeSprint5kReward); + registerMilestone("challenge_sprint_500k", "move", 500000, getConfig().challengeSprint5kReward); + registerMilestone("challenge_sprint_marathon", "move", 42195, getConfig().challengeSprintMarathonReward); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_BOOTS).key("challenge_sprint_dist_5k") + .title(Localizer.dLocalize("advancement.challenge_sprint_dist_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_sprint_dist_5k.description")) + .model(CustomModel.get(Material.GOLDEN_BOOTS, "advancement", "agility", "challenge_sprint_dist_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_BOOTS) + .key("challenge_sprint_dist_50k") + .title(Localizer.dLocalize("advancement.challenge_sprint_dist_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_sprint_dist_50k.description")) + .model(CustomModel.get(Material.DIAMOND_BOOTS, "advancement", "agility", "challenge_sprint_dist_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_sprint_dist_5k", "move.sprint", 5000, getConfig().challengeSprint5kReward); + registerMilestone("challenge_sprint_dist_50k", "move.sprint", 50000, getConfig().challengeSprint5kReward * 2); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LILY_PAD).key("challenge_agility_swim_1k") + .title(Localizer.dLocalize("advancement.challenge_agility_swim_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_swim_1k.description")) + .model(CustomModel.get(Material.LILY_PAD, "advancement", "agility", "challenge_agility_swim_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.HEART_OF_THE_SEA) + .key("challenge_agility_swim_10k") + .title(Localizer.dLocalize("advancement.challenge_agility_swim_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_swim_10k.description")) + .model(CustomModel.get(Material.HEART_OF_THE_SEA, "advancement", "agility", "challenge_agility_swim_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_agility_swim_1k", "move.swim", 1000, getConfig().challengeSprint5kReward); + registerMilestone("challenge_agility_swim_10k", "move.swim", 10000, getConfig().challengeSprint5kReward * 2); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FEATHER).key("challenge_fly_1k") + .title(Localizer.dLocalize("advancement.challenge_fly_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_fly_1k.description")) + .model(CustomModel.get(Material.FEATHER, "advancement", "agility", "challenge_fly_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ELYTRA) + .key("challenge_fly_10k") + .title(Localizer.dLocalize("advancement.challenge_fly_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_fly_10k.description")) + .model(CustomModel.get(Material.ELYTRA, "advancement", "agility", "challenge_fly_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_fly_1k", "move.fly", 1000, getConfig().challengeSprint5kReward); + registerMilestone("challenge_fly_10k", "move.fly", 10000, getConfig().challengeSprint5kReward * 2); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEATHER_LEGGINGS).key("challenge_agility_sneak_500") + .title(Localizer.dLocalize("advancement.challenge_agility_sneak_500.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_sneak_500.description")) + .model(CustomModel.get(Material.LEATHER_LEGGINGS, "advancement", "agility", "challenge_agility_sneak_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_LEGGINGS) + .key("challenge_agility_sneak_5k") + .title(Localizer.dLocalize("advancement.challenge_agility_sneak_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_agility_sneak_5k.description")) + .model(CustomModel.get(Material.IRON_LEGGINGS, "advancement", "agility", "challenge_agility_sneak_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_agility_sneak_500", "move.sneak", 500, getConfig().challengeSprint5kReward); + registerMilestone("challenge_agility_sneak_5k", "move.sneak", 5000, getConfig().challengeSprint5kReward * 2); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerMoveEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, e, () -> { + if (e.getFrom().getWorld() != null && e.getTo() != null && e.getFrom().getWorld().equals(e.getTo().getWorld())) { + double d = e.getFrom().distance(e.getTo()); + AdaptPlayer adaptPlayer = getPlayer(p); + adaptPlayer.getData().addStat("move", d); + + if (p.isSneaking()) { + adaptPlayer.getData().addStat("move.sneak", d); + } else if (p.isFlying()) { + adaptPlayer.getData().addStat("move.fly", d); + } else if (p.isSwimming()) { + adaptPlayer.getData().addStat("move.swim", d); + } else if (p.isSprinting()) { + adaptPlayer.getData().addStat("move.sprint", d); + } + + // Add XP for moving + xpSilent(p, getConfig().moveXpPassive * d, "agility:move"); + } + }); + } + + + @Override + public void onTick() { + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player i = adaptPlayer.getPlayer(); + shouldReturnForPlayer(i, () -> { + checkStatTrackers(adaptPlayer); + + // Check for sprinting + if (i.isSprinting() && !i.isFlying() && !i.isSwimming() && !i.isSneaking()) { + xpSilent(i, getConfig().sprintXpPassive, "agility:sprint"); + } + + // Check for swimming + if (i.isSwimming() && !i.isFlying() && !i.isSprinting() && !i.isSneaking()) { + xpSilent(i, getConfig().swimXpPassive, "agility:swim"); + } + + // Check for jumping + if (!i.isOnGround() && !i.isFlying() && !i.isSneaking()) { + xpSilent(i, getConfig().jumpXpPassive, "agility:jump"); + } + + // Check for climbing ladders + if (i.isClimbing() && !i.isFlying() && !i.isSneaking()) { + xpSilent(i, getConfig().climbXpPassive, "agility:climb"); + } + }); + } + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&a"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Move1k Reward for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeMove1kReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sprint5k Reward for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSprint5kReward = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sprint Marathon Reward for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSprintMarathonReward = 6500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sprint Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sprintXpPassive = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Swim Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double swimXpPassive = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Jump Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double jumpXpPassive = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Climb Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double climbXpPassive = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Move Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double moveXpPassive = 0.05; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillArchitect.java b/src/main/java/art/arcane/adapt/content/skill/SkillArchitect.java new file mode 100644 index 000000000..dedab1ede --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillArchitect.java @@ -0,0 +1,270 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.xp.XpNovelty; +import art.arcane.adapt.api.xp.XpProvenance; +import art.arcane.adapt.content.adaptation.architect.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillArchitect extends SimpleSkill { + private final Map cooldowns; + + public SkillArchitect() { + super("architect", Localizer.dLocalize("skill.architect.icon")); + registerConfiguration(Config.class); + setColor(C.AQUA); + setDescription(Localizer.dLocalize("skill.architect.description")); + setDisplayName(Localizer.dLocalize("skill.architect.name")); + setInterval(3100); + setIcon(Material.IRON_BARS); + cooldowns = new HashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BRICK).key("challenge_place_1k") + .title(Localizer.dLocalize("advancement.challenge_place_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_place_1k.description")) + .model(CustomModel.get(Material.BRICK, "advancement", "architect", "challenge_place_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.BRICK) + .key("challenge_place_5k") + .title(Localizer.dLocalize("advancement.challenge_place_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_place_5k.description")) + .model(CustomModel.get(Material.BRICK, "advancement", "architect", "challenge_place_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.NETHER_BRICK) + .key("challenge_place_50k") + .title(Localizer.dLocalize("advancement.challenge_place_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_place_50k.description")) + .model(CustomModel.get(Material.NETHER_BRICK, "advancement", "architect", "challenge_place_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.NETHER_BRICK) + .key("challenge_place_500k") + .title(Localizer.dLocalize("advancement.challenge_place_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_place_500k.description")) + .model(CustomModel.get(Material.NETHER_BRICK, "advancement", "architect", "challenge_place_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.IRON_INGOT) + .key("challenge_place_5m") + .title(Localizer.dLocalize("advancement.challenge_place_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_place_5m.description")) + .model(CustomModel.get(Material.IRON_INGOT, "advancement", "architect", "challenge_place_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + registerMilestone("challenge_place_1k", "blocks.placed", 1000, getConfig().challengePlace1kReward); + registerMilestone("challenge_place_5k", "blocks.placed", 5000, getConfig().challengePlace1kReward); + registerMilestone("challenge_place_50k", "blocks.placed", 50000, getConfig().challengePlace1kReward); + registerMilestone("challenge_place_500k", "blocks.placed", 500000, getConfig().challengePlace1kReward); + registerMilestone("challenge_place_5m", "blocks.placed", 5000000, getConfig().challengePlace1kReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE).key("challenge_demolish_500") + .title(Localizer.dLocalize("advancement.challenge_demolish_500.title")) + .description(Localizer.dLocalize("advancement.challenge_demolish_500.description")) + .model(CustomModel.get(Material.IRON_PICKAXE, "advancement", "architect", "challenge_demolish_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TNT) + .key("challenge_demolish_5k") + .title(Localizer.dLocalize("advancement.challenge_demolish_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_demolish_5k.description")) + .model(CustomModel.get(Material.TNT, "advancement", "architect", "challenge_demolish_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_demolish_500", "blocks.broken", 500, getConfig().challengePlace1kReward); + registerMilestone("challenge_demolish_5k", "blocks.broken", 5000, getConfig().challengePlace1kReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLD_INGOT).key("challenge_value_placed_10k") + .title(Localizer.dLocalize("advancement.challenge_value_placed_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_value_placed_10k.description")) + .model(CustomModel.get(Material.GOLD_INGOT, "advancement", "architect", "challenge_value_placed_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_value_placed_100k") + .title(Localizer.dLocalize("advancement.challenge_value_placed_100k.title")) + .description(Localizer.dLocalize("advancement.challenge_value_placed_100k.description")) + .model(CustomModel.get(Material.DIAMOND, "advancement", "architect", "challenge_value_placed_100k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_value_placed_10k", "blocks.placed.value", 10000, getConfig().challengePlace1kReward); + registerMilestone("challenge_value_placed_100k", "blocks.placed.value", 100000, getConfig().challengePlace1kReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TNT_MINECART).key("challenge_demolish_val_5k") + .title(Localizer.dLocalize("advancement.challenge_demolish_val_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_demolish_val_5k.description")) + .model(CustomModel.get(Material.TNT_MINECART, "advancement", "architect", "challenge_demolish_val_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.END_CRYSTAL) + .key("challenge_demolish_val_50k") + .title(Localizer.dLocalize("advancement.challenge_demolish_val_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_demolish_val_50k.description")) + .model(CustomModel.get(Material.END_CRYSTAL, "advancement", "architect", "challenge_demolish_val_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_demolish_val_5k", "architect.demolish.value", 5000, getConfig().challengePlace1kReward); + registerMilestone("challenge_demolish_val_50k", "architect.demolish.value", 50000, getConfig().challengePlace1kReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SCAFFOLDING).key("challenge_high_build_100") + .title(Localizer.dLocalize("advancement.challenge_high_build_100.title")) + .description(Localizer.dLocalize("advancement.challenge_high_build_100.description")) + .model(CustomModel.get(Material.SCAFFOLDING, "advancement", "architect", "challenge_high_build_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.LIGHTNING_ROD) + .key("challenge_high_build_1k") + .title(Localizer.dLocalize("advancement.challenge_high_build_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_high_build_1k.description")) + .model(CustomModel.get(Material.LIGHTNING_ROD, "advancement", "architect", "challenge_high_build_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_high_build_100", "architect.builds.high", 100, getConfig().challengePlace1kReward); + registerMilestone("challenge_high_build_1k", "architect.builds.high", 1000, getConfig().challengePlace1kReward * 2); + + setIcon(Material.SMITHING_TABLE); + registerAdaptation(new ArchitectGlass()); + registerAdaptation(new ArchitectFoundation()); + registerAdaptation(new ArchitectPlacement()); + registerAdaptation(new ArchitectWirelessRedstone()); + registerAdaptation(new ArchitectElevator()); + registerAdaptation(new ArchitectSmartShape()); + registerAdaptation(new ArchitectScaffolder()); + registerAdaptation(new ArchitectSupplyLine()); + registerAdaptation(new ArchitectSteadyHands()); + registerAdaptation(new ArchitectChalkLine()); + registerAdaptation(new ArchitectDemolition()); + registerAdaptation(new ArchitectStonecutterSavant()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockPlaceEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, e, () -> { + if (!isStorage(e.getBlock().getType().createBlockData())) { + double v = getValue(e.getBlock()) * getConfig().xpValueMultiplier; + AdaptPlayer adaptPlayer = getPlayer(p); + adaptPlayer.getData().addStat("blocks.placed", 1); + adaptPlayer.getData().addStat("blocks.placed.value", v); + if (e.getBlock().getY() > 128) { + adaptPlayer.getData().addStat("architect.builds.high", 1); + } + + handleBlockCooldown(p, () -> { + try { + double integrity = XpProvenance.placeXpMultiplier(e.getBlock()); + if (integrity <= 0) { + return; + } + double adjacency = XpNovelty.adjacencyBonusMultiplier(p, e.getBlock()); + xp(p, e.getBlock().getLocation().clone().add(0.5, 0.5, 0.5), blockXP(e.getBlock(), getConfig().xpBase + v) * integrity * adjacency); + } catch (Exception ignored) { + Adapt.verbose("Failed to give XP to " + p.getName() + " for placing " + e.getBlock().getType().name()); + } + }); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, e, () -> { + AdaptPlayer adaptPlayer = getPlayer(p); + adaptPlayer.getData().addStat("blocks.broken", 1); + adaptPlayer.getData().addStat("architect.demolish.value", getValue(e.getBlock())); + }); + } + + @Override + public void onTick() { + checkStatTrackersForOnlinePlayers(); + } + + private void handleBlockCooldown(Player p, Runnable action) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + action.run(); + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&b"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Place1k Reward for the Architect skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengePlace1kReward = 1750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Value Multiplier for the Architect skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpValueMultiplier = 1.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Architect skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp Base for the Architect skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpBase = 3; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillAxes.java b/src/main/java/art/arcane/adapt/content/skill/SkillAxes.java new file mode 100644 index 000000000..8bd0a0b2b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillAxes.java @@ -0,0 +1,301 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.xp.XpProvenance; +import art.arcane.adapt.content.adaptation.axe.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillAxes extends SimpleSkill { + private final Map cooldowns; + + public SkillAxes() { + super("axes", Localizer.dLocalize("skill.axes.icon")); + registerConfiguration(Config.class); + setColor(C.YELLOW); + setDescription(Localizer.dLocalize("skill.axes.description1") + C.ITALIC + Localizer.dLocalize("skill.axes.description2") + C.GRAY + " " + Localizer.dLocalize("skill.axes.description3")); + setDisplayName(Localizer.dLocalize("skill.axes.name")); + setInterval(5251); + setIcon(Material.GOLDEN_AXE); + cooldowns = new HashMap<>(); + registerAdaptation(new AxeGroundSmash()); + registerAdaptation(new AxeChop()); + registerAdaptation(new AxeDropToInventory()); + registerAdaptation(new AxeLeafVeinminer()); + registerAdaptation(new AxeWoodVeinminer()); + registerAdaptation(new AxeCraftLogSwap()); + registerAdaptation(new AxeTimberMark()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WOODEN_AXE).key("challenge_chop_1k") + .title(Localizer.dLocalize("advancement.challenge_chop_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_chop_1k.description")) + .model(CustomModel.get(Material.WOODEN_AXE, "advancement", "axes", "challenge_chop_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.STONE_AXE) + .key("challenge_chop_5k") + .title(Localizer.dLocalize("advancement.challenge_chop_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_chop_5k.description")) + .model(CustomModel.get(Material.STONE_AXE, "advancement", "axes", "challenge_chop_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.IRON_AXE) + .key("challenge_chop_50k") + .title(Localizer.dLocalize("advancement.challenge_chop_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_chop_50k.description")) + .model(CustomModel.get(Material.IRON_AXE, "advancement", "axes", "challenge_chop_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_AXE) + .key("challenge_chop_500k") + .title(Localizer.dLocalize("advancement.challenge_chop_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_chop_500k.description")) + .model(CustomModel.get(Material.DIAMOND_AXE, "advancement", "axes", "challenge_chop_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_AXE) + .key("challenge_chop_5m") + .title(Localizer.dLocalize("advancement.challenge_chop_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_chop_5m.description")) + .model(CustomModel.get(Material.NETHERITE_AXE, "advancement", "axes", "challenge_chop_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + registerMilestone("challenge_chop_1k", "axes.blocks.broken", 1000, getConfig().challengeChopReward); + registerMilestone("challenge_chop_5k", "axes.blocks.broken", 5000, getConfig().challengeChopReward); + registerMilestone("challenge_chop_50k", "axes.blocks.broken", 50000, getConfig().challengeChopReward); + registerMilestone("challenge_chop_500k", "axes.blocks.broken", 500000, getConfig().challengeChopReward); + registerMilestone("challenge_chop_5m", "axes.blocks.broken", 5000000, getConfig().challengeChopReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WOODEN_AXE).key("challenge_axe_swing_500") + .title(Localizer.dLocalize("advancement.challenge_axe_swing_500.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_swing_500.description")) + .model(CustomModel.get(Material.WOODEN_AXE, "advancement", "axes", "challenge_axe_swing_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_AXE) + .key("challenge_axe_swing_5k") + .title(Localizer.dLocalize("advancement.challenge_axe_swing_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_swing_5k.description")) + .model(CustomModel.get(Material.IRON_AXE, "advancement", "axes", "challenge_axe_swing_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_axe_swing_500", "axes.swings", 500, getConfig().challengeChopReward); + registerMilestone("challenge_axe_swing_5k", "axes.swings", 5000, getConfig().challengeChopReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_AXE).key("challenge_axe_damage_1k") + .title(Localizer.dLocalize("advancement.challenge_axe_damage_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_damage_1k.description")) + .model(CustomModel.get(Material.GOLDEN_AXE, "advancement", "axes", "challenge_axe_damage_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_AXE) + .key("challenge_axe_damage_10k") + .title(Localizer.dLocalize("advancement.challenge_axe_damage_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_damage_10k.description")) + .model(CustomModel.get(Material.DIAMOND_AXE, "advancement", "axes", "challenge_axe_damage_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_axe_damage_1k", "axes.damage", 1000, getConfig().challengeChopReward); + registerMilestone("challenge_axe_damage_10k", "axes.damage", 10000, getConfig().challengeChopReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.OAK_LOG).key("challenge_axe_value_5k") + .title(Localizer.dLocalize("advancement.challenge_axe_value_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_value_5k.description")) + .model(CustomModel.get(Material.OAK_LOG, "advancement", "axes", "challenge_axe_value_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DARK_OAK_LOG) + .key("challenge_axe_value_50k") + .title(Localizer.dLocalize("advancement.challenge_axe_value_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_axe_value_50k.description")) + .model(CustomModel.get(Material.DARK_OAK_LOG, "advancement", "axes", "challenge_axe_value_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_axe_value_5k", "axes.blocks.value", 5000, getConfig().challengeChopReward); + registerMilestone("challenge_axe_value_50k", "axes.blocks.value", 50000, getConfig().challengeChopReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.OAK_LEAVES).key("challenge_leaves_500") + .title(Localizer.dLocalize("advancement.challenge_leaves_500.title")) + .description(Localizer.dLocalize("advancement.challenge_leaves_500.description")) + .model(CustomModel.get(Material.OAK_LEAVES, "advancement", "axes", "challenge_leaves_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.AZALEA_LEAVES) + .key("challenge_leaves_5k") + .title(Localizer.dLocalize("advancement.challenge_leaves_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_leaves_5k.description")) + .model(CustomModel.get(Material.AZALEA_LEAVES, "advancement", "axes", "challenge_leaves_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_leaves_500", "axes.leaves", 500, getConfig().challengeChopReward); + registerMilestone("challenge_leaves_5k", "axes.leaves", 5000, getConfig().challengeChopReward * 2); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p && checkValidEntity(e.getEntity().getType())) { + if (!getConfig().getXpForAttackingWithTools) { + return; + } + shouldReturnForPlayer(p, () -> { + if (e.getEntity().isDead() || e.getEntity().isInvulnerable() || p.isDead() || p.isInvulnerable()) { + return; + } + AdaptPlayer a = getPlayer(p); + ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); + + if (isAxe(hand)) { + handleCooldown(p, () -> { + a.getData().addStat("axes.swings", 1); + a.getData().addStat("axes.damage", e.getDamage()); + xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().axeDamageXPMultiplier * e.getDamage()); + }); + } + }); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, () -> { + if (isAxe(p.getInventory().getItemInMainHand())) { + if (isLog(new ItemStack(e.getBlock().getType()))) { + double v = getValue(e.getBlock().getType()); + AdaptPlayer a = getPlayer(p); + a.getData().addStat("axes.blocks.broken", 1); + a.getData().addStat("axes.blocks.value", getValue(e.getBlock().getBlockData())); + handleCooldown(p, () -> { + if (XpProvenance.breakXpMultiplier(e.getBlock()) <= 0) { + return; + } + xp(p, e.getBlock().getLocation().clone().add(0.5, 0.5, 0.5), blockXP(e.getBlock(), v)); + }); + } + if (e.getBlock().getType().name().endsWith("_LEAVES")) { + getPlayer(p).getData().addStat("axes.leaves", 1); + } + } + }); + } + + private void handleCooldown(Player p, Runnable action) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + action.run(); + } + + public double getValue(Material type) { + double value = super.getValue(type) * getConfig().valueXPMultiplier; + value += Math.min(getConfig().maxHardnessBonus, type.getHardness()); + value += Math.min(getConfig().maxBlastResistanceBonus, type.getBlastResistance()); + + if (type.name().endsWith("_LOG") || type.name().endsWith("_WOOD")) { + value += getConfig().logOrWoodXPMultiplier; + } + if (type.name().endsWith("_LEAVES")) { + value += getConfig().leavesMultiplier; + } + + if (type.getHardness() == 0) { + value = 0; + } + + return value; + } + + + @Override + public void onTick() { + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&e"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Get Xp For Attacking With Tools for the Axes skill.", impact = "True enables this behavior and false disables it.") + boolean getXpForAttackingWithTools = true; + + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Hardness Bonus for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxHardnessBonus = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Blast Resistance Bonus for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxBlastResistanceBonus = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Chop Reward for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeChopReward = 1750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Log Or Wood XPMultiplier for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double logOrWoodXPMultiplier = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Leaves Multiplier for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double leavesMultiplier = 0.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Value XPMultiplier for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double valueXPMultiplier = 0.175; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Axe Damage XPMultiplier for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double axeDamageXPMultiplier = 7.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillBlocking.java b/src/main/java/art/arcane/adapt/content/skill/SkillBlocking.java new file mode 100644 index 000000000..0c36cddb5 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillBlocking.java @@ -0,0 +1,268 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.blocking.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillBlocking extends SimpleSkill { + private final Map cooldowns; + + public SkillBlocking() { + super("blocking", Localizer.dLocalize("skill.blocking.icon")); + registerConfiguration(Config.class); + setColor(C.DARK_GRAY); + setDescription(Localizer.dLocalize("skill.blocking.description")); + setDisplayName(Localizer.dLocalize("skill.blocking.name")); + setInterval(5000); + setIcon(Material.SHIELD); + registerAdaptation(new BlockingMultiArmor()); + registerAdaptation(new BlockingChainArmorer()); + registerAdaptation(new BlockingSaddlecrafter()); + registerAdaptation(new BlockingHorseArmorer()); + registerAdaptation(new BlockingCounterGuard()); + registerAdaptation(new BlockingBastionStance()); + registerAdaptation(new BlockingMirrorBlock()); + registerAdaptation(new BlockingBulwarkBash()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEATHER_CHESTPLATE).key("challenge_block_1k") + .title(Localizer.dLocalize("advancement.challenge_block_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_block_1k.description")) + .model(CustomModel.get(Material.LEATHER_CHESTPLATE, "advancement", "blocking", "challenge_block_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.CHAINMAIL_CHESTPLATE) + .key("challenge_block_5k") + .title(Localizer.dLocalize("advancement.challenge_block_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_block_5k.description")) + .model(CustomModel.get(Material.CHAINMAIL_CHESTPLATE, "advancement", "blocking", "challenge_block_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.IRON_CHESTPLATE) + .key("challenge_block_50k") + .title(Localizer.dLocalize("advancement.challenge_block_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_block_50k.description")) + .model(CustomModel.get(Material.IRON_CHESTPLATE, "advancement", "blocking", "challenge_block_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.GOLDEN_CHESTPLATE) + .key("challenge_block_500k") + .title(Localizer.dLocalize("advancement.challenge_block_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_block_500k.description")) + .model(CustomModel.get(Material.GOLDEN_CHESTPLATE, "advancement", "blocking", "challenge_block_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_CHESTPLATE) + .key("challenge_block_5m") + .title(Localizer.dLocalize("advancement.challenge_block_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_block_5m.description")) + .model(CustomModel.get(Material.DIAMOND_CHESTPLATE, "advancement", "blocking", "challenge_block_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + registerMilestone("challenge_block_1k", "blocked.hits", 1000, getConfig().challengeBlock1kReward); + registerMilestone("challenge_block_5k", "blocked.hits", 5000, getConfig().challengeBlock1kReward); + registerMilestone("challenge_block_50k", "blocked.hits", 50000, getConfig().challengeBlock5kReward); + registerMilestone("challenge_block_500k", "blocked.hits", 500000, getConfig().challengeBlock5kReward); + registerMilestone("challenge_block_5m", "blocked.hits", 5000000, getConfig().challengeBlock5kReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_CHESTPLATE).key("challenge_block_dmg_1k") + .title(Localizer.dLocalize("advancement.challenge_block_dmg_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_block_dmg_1k.description")) + .model(CustomModel.get(Material.IRON_CHESTPLATE, "advancement", "blocking", "challenge_block_dmg_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_CHESTPLATE) + .key("challenge_block_dmg_10k") + .title(Localizer.dLocalize("advancement.challenge_block_dmg_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_block_dmg_10k.description")) + .model(CustomModel.get(Material.NETHERITE_CHESTPLATE, "advancement", "blocking", "challenge_block_dmg_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_block_dmg_1k", "blocked.damage", 1000, getConfig().challengeBlock1kReward); + registerMilestone("challenge_block_dmg_10k", "blocked.damage", 10000, getConfig().challengeBlock5kReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ARROW).key("challenge_block_proj_100") + .title(Localizer.dLocalize("advancement.challenge_block_proj_100.title")) + .description(Localizer.dLocalize("advancement.challenge_block_proj_100.description")) + .model(CustomModel.get(Material.ARROW, "advancement", "blocking", "challenge_block_proj_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_block_proj_1k") + .title(Localizer.dLocalize("advancement.challenge_block_proj_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_block_proj_1k.description")) + .model(CustomModel.get(Material.SPECTRAL_ARROW, "advancement", "blocking", "challenge_block_proj_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_block_proj_100", "blocked.projectiles", 100, getConfig().challengeBlock1kReward); + registerMilestone("challenge_block_proj_1k", "blocked.projectiles", 1000, getConfig().challengeBlock5kReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD).key("challenge_block_melee_500") + .title(Localizer.dLocalize("advancement.challenge_block_melee_500.title")) + .description(Localizer.dLocalize("advancement.challenge_block_melee_500.description")) + .model(CustomModel.get(Material.IRON_SWORD, "advancement", "blocking", "challenge_block_melee_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_block_melee_5k") + .title(Localizer.dLocalize("advancement.challenge_block_melee_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_block_melee_5k.description")) + .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "blocking", "challenge_block_melee_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_block_melee_500", "blocked.melee", 500, getConfig().challengeBlock1kReward); + registerMilestone("challenge_block_melee_5k", "blocked.melee", 5000, getConfig().challengeBlock5kReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_CHESTPLATE).key("challenge_block_heavy_50") + .title(Localizer.dLocalize("advancement.challenge_block_heavy_50.title")) + .description(Localizer.dLocalize("advancement.challenge_block_heavy_50.description")) + .model(CustomModel.get(Material.DIAMOND_CHESTPLATE, "advancement", "blocking", "challenge_block_heavy_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_CHESTPLATE) + .key("challenge_block_heavy_500") + .title(Localizer.dLocalize("advancement.challenge_block_heavy_500.title")) + .description(Localizer.dLocalize("advancement.challenge_block_heavy_500.description")) + .model(CustomModel.get(Material.NETHERITE_CHESTPLATE, "advancement", "blocking", "challenge_block_heavy_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_block_heavy_50", "blocked.heavy", 50, getConfig().challengeBlock1kReward); + registerMilestone("challenge_block_heavy_500", "blocked.heavy", 500, getConfig().challengeBlock5kReward); + + cooldowns = new HashMap<>(); + } + + private void handleCooldown(Player p, Runnable runnable) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + runnable.run(); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (e.getEntity() instanceof Player p) { + SoundPlayer sp = SoundPlayer.of(p); + shouldReturnForPlayer(p, e, () -> { + if (p.isBlocking()) { + AdaptPlayer adaptPlayer = getPlayer(p); + adaptPlayer.getData().addStat("blocked.hits", 1); + adaptPlayer.getData().addStat("blocked.damage", e.getDamage()); + if (e.getDamager() instanceof Projectile) { + adaptPlayer.getData().addStat("blocked.projectiles", 1); + } else { + adaptPlayer.getData().addStat("blocked.melee", 1); + } + if (e.getDamage() > 5) { + adaptPlayer.getData().addStat("blocked.heavy", 1); + } + + handleCooldown(p, () -> { + xp(p, getConfig().xpOnBlockedAttack); + sp.play(p.getLocation(), Sound.BLOCK_IRON_DOOR_CLOSE, 0.5f, 0.77f); + sp.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 0.5f, 0.77f); + }); + } + }); + } + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player i = adaptPlayer.getPlayer(); + shouldReturnForPlayer(i, () -> { + checkStatTrackers(adaptPlayer); + if (getConfig().passiveXpForUsingShield > 0 && (i.getInventory().getItemInOffHand().getType().equals(Material.SHIELD) || i.getInventory().getItemInMainHand().getType().equals(Material.SHIELD))) { + xpSilent(i, getConfig().passiveXpForUsingShield, "blocking:shield-hold"); + } + }); + } + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&8"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Xp On Blocked Attack for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double xpOnBlockedAttack = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Block1k Reward for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeBlock1kReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Block5k Reward for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeBlock5kReward = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Passive Xp For Using Shield for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long passiveXpForUsingShield = 0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillBrewing.java b/src/main/java/art/arcane/adapt/content/skill/SkillBrewing.java new file mode 100644 index 000000000..d29ac055f --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillBrewing.java @@ -0,0 +1,367 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.data.WorldData; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.brewing.*; +import art.arcane.adapt.content.matter.BrewingStandOwner; +import art.arcane.adapt.content.matter.BrewingStandOwnerMatter; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.spatial.matter.SpatialMatter; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.PotionSplashEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.BrewerInventory; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionType; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillBrewing extends SimpleSkill { + private final Map cooldowns; + + public SkillBrewing() { + super("brewing", Localizer.dLocalize("skill.brewing.icon")); + registerConfiguration(Config.class); + setColor(C.LIGHT_PURPLE); + setDescription(Localizer.dLocalize("skill.brewing.description")); + setDisplayName(Localizer.dLocalize("skill.brewing.name")); + setInterval(5851); + setIcon(Material.LINGERING_POTION); + cooldowns = new HashMap<>(); + registerAdaptation(new BrewingLingering()); // Features + registerAdaptation(new BrewingSuperHeated()); + registerAdaptation(new BrewingAbsorption()); // Brews + registerAdaptation(new BrewingBlindness()); + registerAdaptation(new BrewingDarkness()); + registerAdaptation(new BrewingDecay()); + registerAdaptation(new BrewingFatigue()); + registerAdaptation(new BrewingHaste()); + registerAdaptation(new BrewingHealthBoost()); + registerAdaptation(new BrewingHunger()); + registerAdaptation(new BrewingNausea()); + registerAdaptation(new BrewingResistance()); + registerAdaptation(new BrewingSaturation()); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.POTION).key("challenge_brew_1k") + .title(Localizer.dLocalize("advancement.challenge_brew_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_1k.description")) + .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.POTION) + .key("challenge_brew_5k") + .title(Localizer.dLocalize("advancement.challenge_brew_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_5k.description")) + .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.POTION) + .key("challenge_brew_50k") + .title(Localizer.dLocalize("advancement.challenge_brew_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_50k.description")) + .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.POTION) + .key("challenge_brew_500k") + .title(Localizer.dLocalize("advancement.challenge_brew_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_500k.description")) + .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.POTION) + .key("challenge_brew_5m") + .title(Localizer.dLocalize("advancement.challenge_brew_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_5m.description")) + .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + registerMilestone("challenge_brew_1k", "brewing.consumed", 1000, getConfig().challengeBrew1k); + registerMilestone("challenge_brew_5k", "brewing.consumed", 5000, getConfig().challengeBrew1k); + registerMilestone("challenge_brew_50k", "brewing.consumed", 50000, getConfig().challengeBrew1k); + registerMilestone("challenge_brew_500k", "brewing.consumed", 500000, getConfig().challengeBrew1k); + registerMilestone("challenge_brew_5m", "brewing.consumed", 5000000, getConfig().challengeBrew1k); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPLASH_POTION).key("challenge_brewsplash_1k") + .title(Localizer.dLocalize("advancement.challenge_brewsplash_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_brewsplash_1k.description")) + .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.SPLASH_POTION) + .key("challenge_brewsplash_5k") + .title(Localizer.dLocalize("advancement.challenge_brewsplash_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_brewsplash_5k.description")) + .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.SPLASH_POTION) + .key("challenge_brewsplash_50k") + .title(Localizer.dLocalize("advancement.challenge_brewsplash_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_brewsplash_50k.description")) + .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.SPLASH_POTION) + .key("challenge_brewsplash_500k") + .title(Localizer.dLocalize("advancement.challenge_brewsplash_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_brewsplash_500k.description")) + .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.SPLASH_POTION) + .key("challenge_brewsplash_5m") + .title(Localizer.dLocalize("advancement.challenge_brewsplash_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_brewsplash_5m.description")) + .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + registerMilestone("challenge_brewsplash_1k", "brewing.splashes", 1000, getConfig().challengeBrewSplash1k); + registerMilestone("challenge_brewsplash_5k", "brewing.splashes", 5000, getConfig().challengeBrewSplash1k); + registerMilestone("challenge_brewsplash_50k", "brewing.splashes", 50000, getConfig().challengeBrewSplash1k); + registerMilestone("challenge_brewsplash_500k", "brewing.splashes", 500000, getConfig().challengeBrewSplash1k); + registerMilestone("challenge_brewsplash_5m", "brewing.splashes", 5000000, getConfig().challengeBrewSplash1k); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BREWING_STAND).key("challenge_brew_stands_10") + .title(Localizer.dLocalize("advancement.challenge_brew_stands_10.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_stands_10.description")) + .model(CustomModel.get(Material.BREWING_STAND, "advancement", "brewing", "challenge_brew_stands_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BLAZE_ROD) + .key("challenge_brew_stands_50") + .title(Localizer.dLocalize("advancement.challenge_brew_stands_50.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_stands_50.description")) + .model(CustomModel.get(Material.BLAZE_ROD, "advancement", "brewing", "challenge_brew_stands_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_brew_stands_10", "brewing.stands.placed", 10, getConfig().challengeBrew1k); + registerMilestone("challenge_brew_stands_50", "brewing.stands.placed", 50, getConfig().challengeBrew1k * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GLOWSTONE_DUST).key("challenge_brew_strong_25") + .title(Localizer.dLocalize("advancement.challenge_brew_strong_25.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_strong_25.description")) + .model(CustomModel.get(Material.GLOWSTONE_DUST, "advancement", "brewing", "challenge_brew_strong_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DRAGON_BREATH) + .key("challenge_brew_strong_250") + .title(Localizer.dLocalize("advancement.challenge_brew_strong_250.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_strong_250.description")) + .model(CustomModel.get(Material.DRAGON_BREATH, "advancement", "brewing", "challenge_brew_strong_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_brew_strong_25", "brewing.strong", 25, getConfig().challengeBrew1k); + registerMilestone("challenge_brew_strong_250", "brewing.strong", 250, getConfig().challengeBrew1k * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPLASH_POTION).key("challenge_brew_splash_hits_50") + .title(Localizer.dLocalize("advancement.challenge_brew_splash_hits_50.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_splash_hits_50.description")) + .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "challenge_brew_splash_hits_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.LINGERING_POTION) + .key("challenge_brew_splash_hits_500") + .title(Localizer.dLocalize("advancement.challenge_brew_splash_hits_500.title")) + .description(Localizer.dLocalize("advancement.challenge_brew_splash_hits_500.description")) + .model(CustomModel.get(Material.LINGERING_POTION, "advancement", "brewing", "challenge_brew_splash_hits_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_brew_splash_hits_50", "brewing.splash.hits", 50, getConfig().challengeBrewSplash1k); + registerMilestone("challenge_brew_splash_hits_500", "brewing.splash.hits", 500, getConfig().challengeBrewSplash1k * 2); + + SpatialMatter.registerSliceType(new BrewingStandOwnerMatter()); + } + + private void handleCooldown(Player p, Runnable runnable) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + runnable.run(); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerItemConsumeEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, e, () -> { + if (!(e.getItem().getItemMeta() instanceof PotionMeta potionMeta)) { + return; + } + + PotionType baseType = potionMeta.getBasePotionType(); + if (isBasePotionExcluded(baseType)) { + return; + } + + getPlayer(p).getData().addStat("brewing.consumed", 1); + if (potionMeta.getBasePotionData().isUpgraded()) { + getPlayer(p).getData().addStat("brewing.strong", 1); + } + + double customEffectPower = sumPotionEffects(potionMeta.getCustomEffects()); + double upgradeBonus = potionMeta.getBasePotionData().isUpgraded() ? 50 : 25; + handleCooldown(p, () -> xp(p, p.getLocation(), + getConfig().splashXP + + (getConfig().splashMultiplier * customEffectPower) + + (getConfig().splashMultiplier * upgradeBonus))); + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PotionSplashEvent e) { + if (e.getPotion().getShooter() instanceof Player p) { + shouldReturnForPlayer(p, e, () -> { + AdaptPlayer a = getPlayer(p); + getPlayer(p).getData().addStat("brewing.splashes", 1); + getPlayer(p).getData().addStat("brewing.splash.hits", e.getAffectedEntities().size()); + double effectPower = sumPotionEffects(e.getPotion().getEffects()); + xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().splashXP + (getConfig().splashMultiplier * effectPower)); + }); + } + } + + private boolean isBasePotionExcluded(PotionType baseType) { + if (baseType == null) { + return true; + } + return baseType == PotionType.WATER + || baseType == PotionType.MUNDANE + || baseType == PotionType.THICK + || baseType == PotionType.AWKWARD; + } + + private double sumPotionEffects(Iterable effects) { + double sum = 0D; + for (PotionEffect effect : effects) { + sum += (effect.getAmplifier() + 1) * (effect.getDuration() / 20D); + } + return sum; + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockPlaceEvent e) { + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (e.getBlock().getType().equals(Material.BREWING_STAND)) { + WorldData.of(e.getBlock().getWorld()).set(e.getBlock(), new BrewingStandOwner(e.getPlayer().getUniqueId())); + getPlayer(e.getPlayer()).getData().addStat("brewing.stands.placed", 1); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(InventoryOpenEvent e) { + if (!(e.getPlayer() instanceof Player player) + || !(e.getInventory() instanceof BrewerInventory inv)) { + return; + } + + org.bukkit.block.BrewingStand holder = inv.getHolder(); + if (holder == null) return; + org.bukkit.block.Block block = holder.getBlock(); + if (block.getType() != Material.BREWING_STAND) return; + + shouldReturnForPlayer(player, e, () -> { + art.arcane.adapt.api.data.WorldData data = WorldData.of(block.getWorld()); + art.arcane.adapt.content.matter.BrewingStandOwner owner = data.get(block, BrewingStandOwner.class); + if (owner != null) return; + data.set(block, new BrewingStandOwner(player.getUniqueId())); + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (!e.getBlock().getType().equals(Material.BREWING_STAND)) { + return; + } + WorldData.of(e.getBlock().getWorld()).remove(e.getBlock(), BrewingStandOwner.class); + }); + } + + @Override + public void onTick() { + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&d"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Brew1k for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeBrew1k = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Brew Splash1k for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeBrewSplash1k = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Splash XP for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double splashXP = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 2500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Splash Multiplier for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double splashMultiplier = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillChronos.java b/src/main/java/art/arcane/adapt/content/skill/SkillChronos.java new file mode 100644 index 000000000..3f63f9243 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillChronos.java @@ -0,0 +1,622 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.chronos.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.scheduling.J; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerBedEnterEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.potion.PotionType; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class SkillChronos extends SimpleSkill { + private final Map lastPositions; + private final Map> positionHistory; + private final Map> recentActionTypes; + private final Map actionTypeResetTimestamps; + private final Map lastActivityTimestamps; + private final Map sleepCooldowns; + private final Map sleepEntryWorldTime; + private final Map speedPotionTrackers; + private final Map enderPearlCooldowns; + private final Map survivalStreakStart; + private final Map lastSurvivalCheck; + + public SkillChronos() { + super("chronos", Localizer.dLocalize("skill.chronos.icon")); + registerConfiguration(Config.class); + setColor(C.AQUA); + setInterval(600000); + setDescription(Localizer.dLocalize("skill.chronos.description")); + setDisplayName(Localizer.dLocalize("skill.chronos.name")); + setInterval(getConfig().setInterval); + setIcon(Material.CLOCK); + registerAdaptation(new ChronosTimeInABottle()); + registerAdaptation(new ChronosAberrantTouch()); + registerAdaptation(new ChronosInstantRecall()); + registerAdaptation(new ChronosTimeBomb()); + registerAdaptation(new ChronosTemporalEcho()); + registerAdaptation(new ChronosStasisField()); + registerAdaptation(new ChronosRewind()); + registerAdaptation(new ChronosBorrowedTime()); + registerAdaptation(new ChronosOvertime()); + registerAdaptation(new ChronosAccelerate()); + registerAdaptation(new ChronosHourglassGuard()); + registerAdaptation(new ChronosPocketWatch()); + registerAdaptation(new ChronosDejaVu()); + lastPositions = new ConcurrentHashMap<>(); + positionHistory = new ConcurrentHashMap<>(); + recentActionTypes = new ConcurrentHashMap<>(); + actionTypeResetTimestamps = new ConcurrentHashMap<>(); + lastActivityTimestamps = new ConcurrentHashMap<>(); + sleepCooldowns = new ConcurrentHashMap<>(); + sleepEntryWorldTime = new ConcurrentHashMap<>(); + speedPotionTrackers = new ConcurrentHashMap<>(); + enderPearlCooldowns = new ConcurrentHashMap<>(); + survivalStreakStart = new ConcurrentHashMap<>(); + lastSurvivalCheck = new ConcurrentHashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CLOCK) + .key("challenge_chronos_1h") + .title(Localizer.dLocalize("advancement.challenge_chronos_1h.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_1h.description")) + .model(CustomModel.get(Material.CLOCK, "advancement", "chronos", "challenge_chronos_1h")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.COMPASS) + .key("challenge_chronos_24h") + .title(Localizer.dLocalize("advancement.challenge_chronos_24h.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_24h.description")) + .model(CustomModel.get(Material.COMPASS, "advancement", "chronos", "challenge_chronos_24h")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.RECOVERY_COMPASS) + .key("challenge_chronos_168h") + .title(Localizer.dLocalize("advancement.challenge_chronos_168h.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_168h.description")) + .model(CustomModel.get(Material.RECOVERY_COMPASS, "advancement", "chronos", "challenge_chronos_168h")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_chronos_1h", "minutes.online", 60, getConfig().challengeChronosReward); + registerMilestone("challenge_chronos_24h", "minutes.online", 1440, getConfig().challengeChronosReward * 2); + registerMilestone("challenge_chronos_168h", "minutes.online", 10080, getConfig().challengeChronosReward * 5); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COMPASS).key("challenge_active_dist_1k") + .title(Localizer.dLocalize("advancement.challenge_active_dist_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_active_dist_1k.description")) + .model(CustomModel.get(Material.COMPASS, "advancement", "chronos", "challenge_active_dist_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.RECOVERY_COMPASS) + .key("challenge_active_dist_10k") + .title(Localizer.dLocalize("advancement.challenge_active_dist_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_active_dist_10k.description")) + .model(CustomModel.get(Material.RECOVERY_COMPASS, "advancement", "chronos", "challenge_active_dist_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.LODESTONE) + .key("challenge_active_dist_100k") + .title(Localizer.dLocalize("advancement.challenge_active_dist_100k.title")) + .description(Localizer.dLocalize("advancement.challenge_active_dist_100k.description")) + .model(CustomModel.get(Material.LODESTONE, "advancement", "chronos", "challenge_active_dist_100k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_active_dist_1k", "chronos.active.distance", 1000, getConfig().challengeChronosReward); + registerMilestone("challenge_active_dist_10k", "chronos.active.distance", 10000, getConfig().challengeChronosReward * 2); + registerMilestone("challenge_active_dist_100k", "chronos.active.distance", 100000, getConfig().challengeChronosReward * 5); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WHITE_BED).key("challenge_beds_10") + .title(Localizer.dLocalize("advancement.challenge_beds_10.title")) + .description(Localizer.dLocalize("advancement.challenge_beds_10.description")) + .model(CustomModel.get(Material.WHITE_BED, "advancement", "chronos", "challenge_beds_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.RED_BED) + .key("challenge_beds_100") + .title(Localizer.dLocalize("advancement.challenge_beds_100.title")) + .description(Localizer.dLocalize("advancement.challenge_beds_100.description")) + .model(CustomModel.get(Material.RED_BED, "advancement", "chronos", "challenge_beds_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_beds_10", "chronos.beds.used", 10, getConfig().challengeChronosReward); + registerMilestone("challenge_beds_100", "chronos.beds.used", 100, getConfig().challengeChronosReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL).key("challenge_chronos_tp_50") + .title(Localizer.dLocalize("advancement.challenge_chronos_tp_50.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_tp_50.description")) + .model(CustomModel.get(Material.ENDER_PEARL, "advancement", "chronos", "challenge_chronos_tp_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CHORUS_FRUIT) + .key("challenge_chronos_tp_500") + .title(Localizer.dLocalize("advancement.challenge_chronos_tp_500.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_tp_500.description")) + .model(CustomModel.get(Material.CHORUS_FRUIT, "advancement", "chronos", "challenge_chronos_tp_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_chronos_tp_50", "chronos.teleports", 50, getConfig().challengeChronosReward); + registerMilestone("challenge_chronos_tp_500", "chronos.teleports", 500, getConfig().challengeChronosReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SKELETON_SKULL).key("challenge_chronos_deaths_10") + .title(Localizer.dLocalize("advancement.challenge_chronos_deaths_10.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_deaths_10.description")) + .model(CustomModel.get(Material.SKELETON_SKULL, "advancement", "chronos", "challenge_chronos_deaths_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_chronos_deaths_100") + .title(Localizer.dLocalize("advancement.challenge_chronos_deaths_100.title")) + .description(Localizer.dLocalize("advancement.challenge_chronos_deaths_100.description")) + .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "chronos", "challenge_chronos_deaths_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_chronos_deaths_10", "chronos.deaths", 10, getConfig().challengeChronosReward); + registerMilestone("challenge_chronos_deaths_100", "chronos.deaths", 100, getConfig().challengeChronosReward * 2); + } + + private void trackAction(UUID uuid, String actionType) { + long now = System.currentTimeMillis(); + lastActivityTimestamps.put(uuid, now); + + Long resetTime = actionTypeResetTimestamps.get(uuid); + if (resetTime == null || now - resetTime > getConfig().activityWindow) { + recentActionTypes.put(uuid, ConcurrentHashMap.newKeySet()); + actionTypeResetTimestamps.put(uuid, now); + } + recentActionTypes.computeIfAbsent(uuid, k -> ConcurrentHashMap.newKeySet()).add(actionType); + } + + private boolean isAfk(UUID uuid) { + Deque history = positionHistory.get(uuid); + if (history == null || history.size() < 3) { + return false; + } + + double avgX = 0; + double avgZ = 0; + int count = 0; + for (Location loc : history) { + avgX += loc.getX(); + avgZ += loc.getZ(); + count++; + } + avgX /= count; + avgZ /= count; + + double variance = 0; + for (Location loc : history) { + double dx = loc.getX() - avgX; + double dz = loc.getZ() - avgZ; + variance += Math.sqrt(dx * dx + dz * dz); + } + variance /= count; + + Set actions = recentActionTypes.getOrDefault(uuid, Set.of()); + return variance < getConfig().afkVarianceThreshold && actions.size() < getConfig().afkMinActionTypes; + } + + private double getAfkMultiplier(UUID uuid) { + return isAfk(uuid) ? getConfig().afkPenaltyMultiplier : 1.0; + } + + private boolean isNight(Player p) { + long time = p.getWorld().getTime(); + return time >= 12542 && time <= 23460; + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + long now = System.currentTimeMillis(); + + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player p = adaptPlayer.getPlayer(); + shouldReturnForPlayer(p, () -> { + UUID uuid = p.getUniqueId(); + Location current = p.getLocation(); + Location last = lastPositions.get(uuid); + + // Update position history + Deque history = positionHistory.computeIfAbsent(uuid, k -> new ArrayDeque<>()); + history.addLast(current.clone()); + while (history.size() > getConfig().positionHistorySize) { + history.removeFirst(); + } + + double moved = (last != null && last.getWorld() != null && last.getWorld().equals(current.getWorld())) + ? last.distance(current) + : 0; + + // Track movement as an action type + if (moved >= getConfig().minimumMovementForActiveCheck) { + trackAction(uuid, "movement"); + } + + double afkMult = getAfkMultiplier(uuid); + + // Movement XP (existing behavior, now with AFK penalty) + if (moved >= getConfig().minimumMovementForActiveCheck) { + adaptPlayer.getData().addStat("minutes.online", 10); + adaptPlayer.getData().addStat("chronos.active.distance", moved); + double bonus = (moved / getConfig().distancePerBonusXP) * getConfig().activeMovementXP; + xpSilent(p, Math.min(getConfig().activeMovementXPCapPerTick, bonus) * afkMult, "chronos:movement"); + } + + // Passive active-play XP + Long lastActivity = lastActivityTimestamps.get(uuid); + if (lastActivity != null && now - lastActivity < getConfig().activityWindow) { + double passiveXP = getConfig().passiveActiveXP; + + // Night activity multiplier + if (isNight(p)) { + passiveXP *= getConfig().nightActivityMultiplier; + } + + // Activity variety bonus + Set actions = recentActionTypes.getOrDefault(uuid, Set.of()); + if (actions.size() >= getConfig().activityTypesForBonus) { + passiveXP *= getConfig().activityBonusMultiplier; + } + + xpSilent(p, passiveXP * afkMult, "chronos:passive"); + } + + // Survival streak XP + survivalStreakStart.putIfAbsent(uuid, now); + Long lastCheck = lastSurvivalCheck.get(uuid); + if (lastCheck == null || now - lastCheck >= 60000) { + lastSurvivalCheck.put(uuid, now); + long aliveMs = now - survivalStreakStart.getOrDefault(uuid, now); + double aliveHours = aliveMs / 3600000.0; + double streakBonus = 1.0 + Math.min( + aliveHours * getConfig().survivalStreakBonusPerHour, + getConfig().survivalStreakHourCap * getConfig().survivalStreakBonusPerHour + ); + xpSilent(p, getConfig().survivalXPPerMinute * streakBonus * afkMult, "chronos:survival"); + } + + checkStatTrackers(adaptPlayer); + lastPositions.put(uuid, current.clone()); + }); + } + } + + // --- Sleep XP --- + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerBedEnterEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, () -> { + UUID uuid = p.getUniqueId(); + long now = System.currentTimeMillis(); + + Long lastSleep = sleepCooldowns.get(uuid); + if (lastSleep != null && now - lastSleep < getConfig().sleepCooldown) { + return; + } + + trackAction(uuid, "sleep"); + long worldTime = p.getWorld().getTime(); + sleepEntryWorldTime.put(uuid, worldTime); + sleepCooldowns.put(uuid, now); + getPlayer(p).getData().addStat("chronos.beds.used", 1); + + J.runEntity(p, () -> { + if (!p.isOnline()) { + return; + } + long currentWorldTime = p.getWorld().getTime(); + boolean nightSkipped = currentWorldTime < 1000 || currentWorldTime < worldTime - 100; + if (nightSkipped) { + xp(p, p.getLocation(), getConfig().sleepSkipXP); + } else { + xp(p, p.getLocation(), getConfig().sleepAttemptXP); + } + }, 40); + }); + } + + // --- Speed Potion XP --- + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerItemConsumeEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, () -> { + ItemStack item = e.getItem(); + if (item.getType() != Material.POTION) { + return; + } + if (!(item.getItemMeta() instanceof PotionMeta meta)) { + return; + } + + boolean isSpeedPotion = false; + boolean isSpeedII = false; + + PotionType baseType = meta.getBasePotionType(); + if (baseType == PotionType.SWIFTNESS) { + isSpeedPotion = true; + } + if (baseType == PotionType.STRONG_SWIFTNESS) { + isSpeedPotion = true; + isSpeedII = true; + } + + if (!isSpeedPotion && meta.hasCustomEffects()) { + for (PotionEffect customEffect : meta.getCustomEffects()) { + if (!customEffect.getType().equals(PotionEffectType.SPEED)) { + continue; + } + isSpeedPotion = true; + if (customEffect.getAmplifier() >= 1) { + isSpeedII = true; + break; + } + } + } + + if (!isSpeedPotion) { + return; + } + + UUID uuid = p.getUniqueId(); + trackAction(uuid, "potion"); + long now = System.currentTimeMillis(); + + SpeedPotionTracker tracker = speedPotionTrackers.computeIfAbsent(uuid, k -> new SpeedPotionTracker()); + + if (now - tracker.lastUseTime > getConfig().speedPotionResetWindow) { + tracker.consecutiveUses = 0; + } + + double decay = getConfig().speedPotionDiminishingDecay; + double floor = getConfig().speedPotionDiminishingFloor; + double multiplier = Math.max(floor, Math.pow(1.0 - decay, tracker.consecutiveUses)); + + double xpAmount = getConfig().speedPotionBaseXP * multiplier; + if (isSpeedII) { + xpAmount *= getConfig().speedPotionLevelMultiplier; + } + + tracker.consecutiveUses++; + tracker.lastUseTime = now; + + xp(p, p.getLocation(), xpAmount); + }); + } + + // --- Ender Pearl XP --- + + @EventHandler(priority = EventPriority.MONITOR) + public void on(ProjectileLaunchEvent e) { + if (!(e.getEntity() instanceof EnderPearl pearl)) { + return; + } + if (!(pearl.getShooter() instanceof Player p)) { + return; + } + shouldReturnForPlayer(p, () -> { + UUID uuid = p.getUniqueId(); + long now = System.currentTimeMillis(); + + Long lastThrow = enderPearlCooldowns.get(uuid); + if (lastThrow != null && now - lastThrow < getConfig().enderPearlCooldown) { + return; + } + + trackAction(uuid, "teleport"); + enderPearlCooldowns.put(uuid, now); + xp(p, p.getLocation(), getConfig().enderPearlThrowXP); + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerTeleportEvent e) { + if (e.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { + return; + } + Player p = e.getPlayer(); + if (ChronosInstantRecall.isRecallTeleportSuppressed(p)) { + return; + } + shouldReturnForPlayer(p, () -> { + trackAction(p.getUniqueId(), "teleport"); + getPlayer(p).getData().addStat("chronos.teleports", 1); + xp(p, e.getTo(), getConfig().enderPearlTeleportXP); + }); + } + + // --- Death / Survival Streak Reset --- + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerDeathEvent e) { + Player p = e.getEntity(); + UUID uuid = p.getUniqueId(); + survivalStreakStart.put(uuid, System.currentTimeMillis()); + lastSurvivalCheck.remove(uuid); + trackAction(uuid, "combat"); + shouldReturnForPlayer(p, () -> getPlayer(p).getData().addStat("chronos.deaths", 1)); + } + + // --- Player Quit Cleanup --- + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + UUID uuid = e.getPlayer().getUniqueId(); + lastPositions.remove(uuid); + positionHistory.remove(uuid); + recentActionTypes.remove(uuid); + actionTypeResetTimestamps.remove(uuid); + lastActivityTimestamps.remove(uuid); + sleepCooldowns.remove(uuid); + sleepEntryWorldTime.remove(uuid); + speedPotionTrackers.remove(uuid); + enderPearlCooldowns.remove(uuid); + survivalStreakStart.remove(uuid); + lastSurvivalCheck.remove(uuid); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @Override + protected void onConfigReload(Config previousConfig, Config newConfig) { + super.onConfigReload(previousConfig, newConfig); + setInterval(newConfig.setInterval); + } + + private static class SpeedPotionTracker { + int consecutiveUses; + long lastUseTime; + } + + @NoArgsConstructor + protected static class Config { + // Existing + @art.arcane.adapt.util.config.ConfigDoc(value = "Tick interval used by this logic.", impact = "Lower values run logic more often; higher values run it less often.") + long setInterval = 5050; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&b"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Minimum Movement For Active Check for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double minimumMovementForActiveCheck = 0.35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Distance Per Bonus XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double distancePerBonusXP = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Active Movement XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double activeMovementXP = 3.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Active Movement XPCap Per Tick for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double activeMovementXPCapPerTick = 6; + + // Anti-AFK + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Position History Size for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int positionHistorySize = 12; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Afk Variance Threshold for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double afkVarianceThreshold = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Afk Min Action Types for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int afkMinActionTypes = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Afk Penalty Multiplier for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double afkPenaltyMultiplier = 0.03; + + // Passive active XP + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Passive Active XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double passiveActiveXP = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Activity Window for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long activityWindow = 15000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Activity Types For Bonus for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int activityTypesForBonus = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Activity Bonus Multiplier for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double activityBonusMultiplier = 1.5; + + // Night bonus + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Night Activity Multiplier for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double nightActivityMultiplier = 1.3; + + // Sleep + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sleep Skip XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sleepSkipXP = 150; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sleep Attempt XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sleepAttemptXP = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sleep Cooldown for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long sleepCooldown = 30000; + + // Speed potion + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Base XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedPotionBaseXP = 45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Level Multiplier for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedPotionLevelMultiplier = 1.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Diminishing Decay for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedPotionDiminishingDecay = 0.15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Diminishing Floor for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double speedPotionDiminishingFloor = 0.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Reset Window for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long speedPotionResetWindow = 300000; + + // Ender pearl + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ender Pearl Throw XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double enderPearlThrowXP = 35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ender Pearl Teleport XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double enderPearlTeleportXP = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Ender Pearl Cooldown for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long enderPearlCooldown = 10000; + + // Survival streak + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Survival XPPer Minute for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double survivalXPPerMinute = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Survival Streak Bonus Per Hour for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double survivalStreakBonusPerHour = 0.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Survival Streak Hour Cap for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int survivalStreakHourCap = 5; + + // Challenge rewards + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Chronos Reward for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeChronosReward = 500; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillCrafting.java b/src/main/java/art/arcane/adapt/content/skill/SkillCrafting.java new file mode 100644 index 000000000..e91ce8c2c --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillCrafting.java @@ -0,0 +1,337 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.content.adaptation.crafting.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.FurnaceSmeltEvent; +import org.bukkit.inventory.CraftingInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillCrafting extends SimpleSkill { + private final Map cooldowns; + + public SkillCrafting() { + super("crafting", Localizer.dLocalize("skill.crafting.icon")); + registerConfiguration(Config.class); + setColor(C.YELLOW); + setDescription(Localizer.dLocalize("skill.crafting.description")); + setDisplayName(Localizer.dLocalize("skill.crafting.name")); + setInterval(3789); + setIcon(Material.CRAFTING_TABLE); + registerAdaptation(new CraftingDeconstruction()); + registerAdaptation(new CraftingXP()); + registerAdaptation(new CraftingLeather()); + registerAdaptation(new CraftingSkulls()); + registerAdaptation(new CraftingBackpacks()); + registerAdaptation(new CraftingStations()); + registerAdaptation(new CraftingReconstruction()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CRAFTING_TABLE).key("challenge_craft_1k") + .title(Localizer.dLocalize("advancement.challenge_craft_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_1k.description")) + .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.CRAFTING_TABLE) + .key("challenge_craft_5k") + .title(Localizer.dLocalize("advancement.challenge_craft_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_5k.description")) + .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.CRAFTING_TABLE) + .key("challenge_craft_50k") + .title(Localizer.dLocalize("advancement.challenge_craft_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_50k.description")) + .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.CRAFTING_TABLE) + .key("challenge_craft_500k") + .title(Localizer.dLocalize("advancement.challenge_craft_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_500k.description")) + .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.CRAFTING_TABLE) + .key("challenge_craft_5m") + .title(Localizer.dLocalize("advancement.challenge_craft_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_5m.description")) + .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + registerMilestone("challenge_craft_1k", "crafted.items", 1000, getConfig().challengeCraft1kReward); + registerMilestone("challenge_craft_5k", "crafted.items", 5000, getConfig().challengeCraft1kReward); + registerMilestone("challenge_craft_50k", "crafted.items", 50000, getConfig().challengeCraft1kReward); + registerMilestone("challenge_craft_500k", "crafted.items", 500000, getConfig().challengeCraft1kReward); + registerMilestone("challenge_craft_5m", "crafted.items", 5000000, getConfig().challengeCraft1kReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLD_INGOT).key("challenge_craft_value_10k") + .title(Localizer.dLocalize("advancement.challenge_craft_value_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_value_10k.description")) + .model(CustomModel.get(Material.GOLD_INGOT, "advancement", "crafting", "challenge_craft_value_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND) + .key("challenge_craft_value_100k") + .title(Localizer.dLocalize("advancement.challenge_craft_value_100k.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_value_100k.description")) + .model(CustomModel.get(Material.DIAMOND, "advancement", "crafting", "challenge_craft_value_100k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_craft_value_10k", "crafted.value", 10000, getConfig().challengeCraft1kReward); + registerMilestone("challenge_craft_value_100k", "crafted.value", 100000, getConfig().challengeCraft1kReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE).key("challenge_craft_tools_25") + .title(Localizer.dLocalize("advancement.challenge_craft_tools_25.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_tools_25.description")) + .model(CustomModel.get(Material.IRON_PICKAXE, "advancement", "crafting", "challenge_craft_tools_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_PICKAXE) + .key("challenge_craft_tools_250") + .title(Localizer.dLocalize("advancement.challenge_craft_tools_250.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_tools_250.description")) + .model(CustomModel.get(Material.DIAMOND_PICKAXE, "advancement", "crafting", "challenge_craft_tools_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_craft_tools_25", "crafting.tools", 25, getConfig().challengeCraft1kReward); + registerMilestone("challenge_craft_tools_250", "crafting.tools", 250, getConfig().challengeCraft1kReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_CHESTPLATE).key("challenge_craft_armor_25") + .title(Localizer.dLocalize("advancement.challenge_craft_armor_25.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_armor_25.description")) + .model(CustomModel.get(Material.IRON_CHESTPLATE, "advancement", "crafting", "challenge_craft_armor_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_CHESTPLATE) + .key("challenge_craft_armor_250") + .title(Localizer.dLocalize("advancement.challenge_craft_armor_250.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_armor_250.description")) + .model(CustomModel.get(Material.DIAMOND_CHESTPLATE, "advancement", "crafting", "challenge_craft_armor_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_craft_armor_25", "crafting.armor", 25, getConfig().challengeCraft1kReward); + registerMilestone("challenge_craft_armor_250", "crafting.armor", 250, getConfig().challengeCraft1kReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.HOPPER).key("challenge_craft_mass_25k") + .title(Localizer.dLocalize("advancement.challenge_craft_mass_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_mass_25k.description")) + .model(CustomModel.get(Material.HOPPER, "advancement", "crafting", "challenge_craft_mass_25k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_craft_mass_250k") + .title(Localizer.dLocalize("advancement.challenge_craft_mass_250k.title")) + .description(Localizer.dLocalize("advancement.challenge_craft_mass_250k.description")) + .model(CustomModel.get(Material.CHEST, "advancement", "crafting", "challenge_craft_mass_250k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_craft_mass_25k", "crafted.items", 25000, getConfig().challengeCraft1kReward * 2); + registerMilestone("challenge_craft_mass_250k", "crafted.items", 250000, getConfig().challengeCraft1kReward * 5); + + cooldowns = new HashMap<>(); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(CraftItemEvent e) { + Player p = (Player) e.getWhoClicked(); + shouldReturnForPlayer(p, e, () -> { + if (!isValidCraftEvent(e)) { + return; + } + int recipeAmount = calculateRecipeAmount(e); + if (recipeAmount > 0) { + double v = recipeAmount * getValue(e.getRecipe().getResult()) * getConfig().craftingValueXPMultiplier; + getPlayer(p).getData().addStat("crafted.items", recipeAmount); + getPlayer(p).getData().addStat("crafted.value", v); + Material resultType = e.getRecipe().getResult().getType(); + String typeName = resultType.name(); + if (typeName.contains("_PICKAXE") || typeName.contains("_AXE") || typeName.contains("_SHOVEL") || typeName.contains("_HOE") || typeName.contains("_SWORD")) { + getPlayer(p).getData().addStat("crafting.tools", recipeAmount); + } + if (typeName.contains("_HELMET") || typeName.contains("_CHESTPLATE") || typeName.contains("_LEGGINGS") || typeName.contains("_BOOTS")) { + getPlayer(p).getData().addStat("crafting.armor", recipeAmount); + } + xp(p, v + getConfig().baseCraftingXP); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(FurnaceSmeltEvent e) { + if (shouldReturnForWorld(e.getBlock().getWorld(), this)) { + return; + } + xp(e.getBlock().getLocation(), getConfig().furnaceBaseXP + (getValue(e.getResult()) * getConfig().furnaceValueXPMultiplier), getConfig().furnaceXPRadius, getConfig().furnaceXPDuration); + } + + @Override + public void onTick() { + checkStatTrackersForOnlinePlayers(); + } + + + private boolean isValidCraftEvent(CraftItemEvent e) { + Player p = (Player) e.getWhoClicked(); + + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return false; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + + ItemStack result = e.getInventory().getResult(); + ItemStack cursor = e.getCursor(); + + return result != null && result.getAmount() > 0 && (cursor == null || cursor.getAmount() < 64); + } + + private int calculateRecipeAmount(CraftItemEvent e) { + ItemStack test = e.getRecipe().getResult().clone(); + int recipeAmount = e.getInventory().getResult().getAmount(); + switch (e.getClick()) { + case NUMBER_KEY -> { + if (e.getWhoClicked().getInventory().getItem(e.getHotbarButton()) != null) { + recipeAmount = 0; + } + } + case DROP, CONTROL_DROP -> { + ItemStack cursor = e.getCursor(); + if (!(cursor == null || cursor.getType().isAir())) { + recipeAmount = 0; + } + } + case SHIFT_RIGHT, SHIFT_LEFT -> { + if (recipeAmount == 0) { + break; + } + int maxCraftable = getMaxCraftAmount(e.getInventory()); + int capacity = fits(test, e.getView().getBottomInventory()); + if (capacity < maxCraftable) { + maxCraftable = ((capacity + recipeAmount - 1) / recipeAmount) * recipeAmount; + } + recipeAmount = maxCraftable; + } + default -> { + } + } + return recipeAmount; + } + + private int fits(ItemStack stack, Inventory inv) { + ItemStack[] contents = inv.getContents(); + int result = 0; + + for (ItemStack is : contents) { + if (is == null) { + result += stack.getMaxStackSize(); + } else if (is.isSimilar(stack)) { + result += Math.max(stack.getMaxStackSize() - is.getAmount(), 0); + } + } + + return result; + } + + private int getMaxCraftAmount(CraftingInventory inv) { + if (inv.getResult() == null) { + return 0; + } + + int resultCount = inv.getResult().getAmount(); + int materialCount = Integer.MAX_VALUE; + + for (ItemStack is : inv.getMatrix()) { + if (is != null && is.getAmount() < materialCount) { + materialCount = is.getAmount(); + } + } + + return resultCount * materialCount; + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&e"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Furnace Base XP for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double furnaceBaseXP = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Furnace Value XPMultiplier for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double furnaceValueXPMultiplier = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Furnace XPRadius for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + int furnaceXPRadius = 32; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 3000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Furnace XPDuration for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long furnaceXPDuration = 10000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Crafting Value XPMultiplier for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double craftingValueXPMultiplier = 2.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Base Crafting XP for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double baseCraftingXP = 3.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Craft1k Reward for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeCraft1kReward = 1200; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillDiscovery.java b/src/main/java/art/arcane/adapt/content/skill/SkillDiscovery.java new file mode 100644 index 000000000..70320c434 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillDiscovery.java @@ -0,0 +1,421 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.Discovery; +import art.arcane.adapt.content.adaptation.discovery.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.reflect.registries.Particles; +import art.arcane.volmlib.util.format.Form; +import lombok.NoArgsConstructor; +import org.bukkit.*; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.player.*; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import org.bukkit.potion.PotionEffect; + +import java.util.Map; + +public class SkillDiscovery extends SimpleSkill { + public SkillDiscovery() { + super("discovery", Localizer.dLocalize("skill.discovery.icon")); + registerConfiguration(Config.class); + setColor(C.AQUA); + setDescription(Localizer.dLocalize("skill.discovery.description")); + setDisplayName(Localizer.dLocalize("skill.discovery.name")); + setInterval(500); + setIcon(Material.FILLED_MAP); + registerAdaptation(new DiscoveryUnity()); + registerAdaptation(new DiscoveryArmor()); + registerAdaptation(new DiscoveryXpResist()); + registerAdaptation(new DiscoveryVillagerAtt()); + registerAdaptation(new DiscoveryBetterMending()); + registerAdaptation(new DiscoveryArchaeologist()); + registerAdaptation(new DiscoveryCartographerPulse()); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ITEM_FRAME).key("challenge_discover_items_50") + .title(Localizer.dLocalize("advancement.challenge_discover_items_50.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_items_50.description")) + .model(CustomModel.get(Material.ITEM_FRAME, "advancement", "discovery", "challenge_discover_items_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CHEST) + .key("challenge_discover_items_250") + .title(Localizer.dLocalize("advancement.challenge_discover_items_250.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_items_250.description")) + .model(CustomModel.get(Material.CHEST, "advancement", "discovery", "challenge_discover_items_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discover_items_50", "discovery.items", 50, 500); + registerMilestone("challenge_discover_items_250", "discovery.items", 250, 2500); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GRASS_BLOCK).key("challenge_discover_blocks_50") + .title(Localizer.dLocalize("advancement.challenge_discover_blocks_50.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_blocks_50.description")) + .model(CustomModel.get(Material.GRASS_BLOCK, "advancement", "discovery", "challenge_discover_blocks_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.STONE_BRICKS) + .key("challenge_discover_blocks_250") + .title(Localizer.dLocalize("advancement.challenge_discover_blocks_250.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_blocks_250.description")) + .model(CustomModel.get(Material.STONE_BRICKS, "advancement", "discovery", "challenge_discover_blocks_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discover_blocks_50", "discovery.blocks", 50, 500); + registerMilestone("challenge_discover_blocks_250", "discovery.blocks", 250, 2500); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EGG).key("challenge_discover_mobs_25") + .title(Localizer.dLocalize("advancement.challenge_discover_mobs_25.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_mobs_25.description")) + .model(CustomModel.get(Material.EGG, "advancement", "discovery", "challenge_discover_mobs_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SPAWNER) + .key("challenge_discover_mobs_75") + .title(Localizer.dLocalize("advancement.challenge_discover_mobs_75.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_mobs_75.description")) + .model(CustomModel.get(Material.SPAWNER, "advancement", "discovery", "challenge_discover_mobs_75")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discover_mobs_25", "discovery.mobs", 25, 500); + registerMilestone("challenge_discover_mobs_75", "discovery.mobs", 75, 2500); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.MAP).key("challenge_discover_biomes_10") + .title(Localizer.dLocalize("advancement.challenge_discover_biomes_10.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_biomes_10.description")) + .model(CustomModel.get(Material.MAP, "advancement", "discovery", "challenge_discover_biomes_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.FILLED_MAP) + .key("challenge_discover_biomes_40") + .title(Localizer.dLocalize("advancement.challenge_discover_biomes_40.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_biomes_40.description")) + .model(CustomModel.get(Material.FILLED_MAP, "advancement", "discovery", "challenge_discover_biomes_40")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discover_biomes_10", "discovery.biomes", 10, 500); + registerMilestone("challenge_discover_biomes_40", "discovery.biomes", 40, 2500); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.APPLE).key("challenge_discover_foods_10") + .title(Localizer.dLocalize("advancement.challenge_discover_foods_10.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_foods_10.description")) + .model(CustomModel.get(Material.APPLE, "advancement", "discovery", "challenge_discover_foods_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GOLDEN_APPLE) + .key("challenge_discover_foods_30") + .title(Localizer.dLocalize("advancement.challenge_discover_foods_30.title")) + .description(Localizer.dLocalize("advancement.challenge_discover_foods_30.description")) + .model(CustomModel.get(Material.GOLDEN_APPLE, "advancement", "discovery", "challenge_discover_foods_30")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_discover_foods_10", "discovery.foods", 10, 500); + registerMilestone("challenge_discover_foods_30", "discovery.foods", 30, 2500); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerChangedWorldEvent e) { + shouldReturnForPlayer(e.getPlayer(), () -> scheduleSeeWorld(e.getPlayer())); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerInteractAtEntityEvent e) { + shouldReturnForPlayer(e.getPlayer(), e, () -> seeEntity(e.getPlayer(), e.getRightClicked())); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityPickupItemEvent e) { + if (e.getEntity() instanceof Player p) { + shouldReturnForPlayer(p, e, () -> seeItem(p, e.getItem().getItemStack())); + } + + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(CraftItemEvent e) { + if (!(e.getWhoClicked() instanceof Player p)) return; + shouldReturnForPlayer(p, e, () -> { + try { + NamespacedKey key = (NamespacedKey) Recipe.class.getDeclaredMethod("getKey()").invoke(e.getRecipe()); + if (key != null) { + seeCraftedRecipe(p, key.toString()); + } + } catch (Throwable ex) { + Adapt.verbose("No recipe key found for " + e.getRecipe().getResult().getType().name() + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerItemConsumeEvent e) { + shouldReturnForPlayer(e.getPlayer(), e, () -> { + seeItem(e.getPlayer(), e.getItem()); + seeFood(e.getPlayer(), e.getItem().getType()); + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerInteractEvent e) { + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (e.getClickedBlock() != null) { + seeBlock(e.getPlayer(), e.getClickedBlock().getBlockData(), e.getClickedBlock().getLocation()); + } + }); + + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerExpChangeEvent e) { + shouldReturnForPlayer(e.getPlayer(), () -> { + if (e.getAmount() > 0 && getLevel(e.getPlayer()) > 0) { + xp(e.getPlayer(), e.getAmount()); + } + }); + } + + private void scheduleSeeWorld(Player p) { + try { + J.runEntity(p, () -> seeWorld(p, p.getWorld()), 15); + } catch (Exception e) { + Adapt.error("Failed to discover world " + p.getWorld().getName()); + } + } + + public void seeBlock(Player p, BlockData bd, Location l) { + Discovery d = getPlayer(p).getData().getSeenBlocks(); + if (d.isNewDiscovery(bd.getAsString())) { + xp(p, getConfig().discoverBlockBaseXP + (getValue(bd) * getConfig().discoverBlockValueXPMultiplier)); + getPlayer(p).getData().addStat("discovery.blocks", 1); + if (areParticlesEnabled()) { + p.spawnParticle(Particles.TOTEM, l.clone().add(0.5, 0.5, 0.5), 9, 0, 0, 0, 0.3); + } + } + + seeItem(p, bd.getMaterial()); + } + + public void seeItem(Player p, Material bd) { + Discovery d = getPlayer(p).getData().getSeenItems(); + if (d.isNewDiscovery(bd)) { + xp(p, getConfig().discoverItemBaseXP + (getValue(bd) * getConfig().discoverItemValueXPMultiplier)); + getPlayer(p).getData().addStat("discovery.items", 1); + } + } + + public void seeItem(Player p, ItemStack bd) { + seeItem(p, bd.getType()); + Map m = bd.getEnchantments(); + + for (Enchantment i : m.keySet()) { + seeEnchant(p, i, m.get(i)); + } + } + + public void seeCraftedRecipe(Player p, String key) { + Discovery d = getPlayer(p).getData().getSeenRecipes(); + if (d.isNewDiscovery(key)) { + xp(p, getConfig().discoverRecipeBaseXP); + } + } + + public void seeFood(Player p, Material bd) { + Discovery d = getPlayer(p).getData().getSeenFoods(); + if (d.isNewDiscovery(bd)) { + xp(p, getConfig().discoverFoodTypeXP); + getPlayer(p).getData().addStat("discovery.foods", 1); + } + } + + public void seeEntity(Player p, Entity bd) { + Discovery d = getPlayer(p).getData().getSeenMobs(); + if (d.isNewDiscovery(bd.getType())) { + xp(p, getConfig().discoverEntityTypeXP); + getPlayer(p).getData().addStat("discovery.mobs", 1); + } + + if (bd instanceof Player) { + seePlayer(p, (Player) bd); + } + + if (bd instanceof LivingEntity) { + for (PotionEffect i : ((LivingEntity) bd).getActivePotionEffects()) { + seePotionEffect(p, i); + } + } + } + + public void seePlayer(Player p, Player bd) { + Discovery d = getPlayer(p).getData().getSeenPeople(); + if (d.isNewDiscovery(bd.getUniqueId().toString())) { + xp(p, getConfig().discoverPlayerXP); + } + } + + public void seeEnchant(Player p, Enchantment bd, int level) { + Discovery d = getPlayer(p).getData().getSeenEnchants(); + if (d.isNewDiscovery(bd.getName() + " " + Form.toRoman(level))) { + xp(p, getConfig().discoverEnchantBaseXP + Math.min(getConfig().discoverEnchantMaxXP, level * getConfig().discoverEnchantLevelXPMultiplier)); + } + } + + public void seeWorld(Player p, World world) { + Discovery d = getPlayer(p).getData().getSeenWorlds(); + if (d.isNewDiscovery(world.getName() + "-" + world.getSeed())) { + xp(p, getConfig().discoverWorldXP); + } + + seeEnvironment(p, world.getEnvironment()); + } + + public void seeEnvironment(Player p, World.Environment world) { + Discovery d = getPlayer(p).getData().getSeenEnvironments(); + if (d.isNewDiscovery(world)) { + xp(p, getConfig().discoverEnvironmentXP); + } + } + + public void seePotionEffect(Player p, PotionEffect e) { + Discovery d = getPlayer(p).getData().getSeenPotionEffects(); + if (d.isNewDiscovery(e.getType().getName() + " " + Form.toRoman(e.getAmplifier()).trim())) { + xp(p, getConfig().discoverPotionXP); + } + } + + public void seeBiome(Player p, Biome e) { + Discovery d = getPlayer(p).getData().getSeenBiomes(); + if (d.isNewDiscovery(e.getKey().toString())) { + xp(p, getConfig().discoverBiomeXP); + getPlayer(p).getData().addStat("discovery.biomes", 1); + } + } + + @Override + public void onTick() { + if (!this.isEnabled()) return; + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player i = adaptPlayer.getPlayer(); + shouldReturnForPlayer(i, () -> { + checkStatTrackers(adaptPlayer); + seeTargetBlock(i); + }); + } + } + + private void seeTargetBlock(Player i) { + try { + Block b = i.getTargetBlockExact(5, FluidCollisionMode.NEVER); + if (b != null) { + seeBlock(i, b.getBlockData(), b.getLocation()); + seeBiome(i, b.getBiome()); + } + } catch (Throwable ex) { + Adapt.verbose("Failed to get target block for " + i.getName() + ": " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&b"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Discovery skill.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Biome XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverBiomeXP = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Potion XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverPotionXP = 36; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Entity Type XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverEntityTypeXP = 125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Food Type XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverFoodTypeXP = 75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Player XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverPlayerXP = 125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Environment XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverEnvironmentXP = 750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover World XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverWorldXP = 750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Enchant Max XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverEnchantMaxXP = 250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Enchant Level XPMultiplier for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverEnchantLevelXPMultiplier = 52; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Enchant Base XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverEnchantBaseXP = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Item Base XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverItemBaseXP = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Recipe Base XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverRecipeBaseXP = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Item Value XPMultiplier for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverItemValueXPMultiplier = 1; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Block Base XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverBlockBaseXP = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Discover Block Value XPMultiplier for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double discoverBlockValueXPMultiplier = 0.333; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillEnchanting.java b/src/main/java/art/arcane/adapt/content/skill/SkillEnchanting.java new file mode 100644 index 000000000..21a363c3e --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillEnchanting.java @@ -0,0 +1,242 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.enchanting.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.enchantment.EnchantItemEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillEnchanting extends SimpleSkill { + private final Map cooldowns; + + public SkillEnchanting() { + super("enchanting", Localizer.dLocalize("skill.enchanting.icon")); + registerConfiguration(Config.class); + setColor(C.LIGHT_PURPLE); + setDescription(Localizer.dLocalize("skill.enchanting.description")); + setDisplayName(Localizer.dLocalize("skill.enchanting.name")); + setInterval(3909); + setIcon(Material.KNOWLEDGE_BOOK); + cooldowns = new HashMap<>(); + registerAdaptation(new EnchantingQuickEnchant()); + registerAdaptation(new EnchantingLapisReturn()); + registerAdaptation(new EnchantingXPReturn()); // + registerAdaptation(new EnchantingAnvilSavant()); + registerAdaptation(new EnchantingOfferReroll()); + registerAdaptation(new EnchantingBookshelfAttunement()); + registerAdaptation(new EnchantingGrindstoneRecovery()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CRAFTING_TABLE).key("challenge_enchant_1k") + .title(Localizer.dLocalize("advancement.challenge_enchant_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_1k.description")) + .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "enchanting", "challenge_enchant_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.KNOWLEDGE_BOOK) + .key("challenge_enchant_5k") + .title(Localizer.dLocalize("advancement.challenge_enchant_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_5k.description")) + .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "enchanting", "challenge_enchant_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.KNOWLEDGE_BOOK) + .key("challenge_enchant_50k") + .title(Localizer.dLocalize("advancement.challenge_enchant_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_50k.description")) + .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "enchanting", "challenge_enchant_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.KNOWLEDGE_BOOK) + .key("challenge_enchant_500k") + .title(Localizer.dLocalize("advancement.challenge_enchant_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_500k.description")) + .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "enchanting", "challenge_enchant_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.KNOWLEDGE_BOOK) + .key("challenge_enchant_5m") + .title(Localizer.dLocalize("advancement.challenge_enchant_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_5m.description")) + .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "enchanting", "challenge_enchant_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + registerMilestone("challenge_enchant_1k", "enchanted.items", 1000, getConfig().challengeEnchantReward); + registerMilestone("challenge_enchant_5k", "enchanted.items", 5000, getConfig().challengeEnchantReward); + registerMilestone("challenge_enchant_50k", "enchanted.items", 50000, getConfig().challengeEnchantReward); + registerMilestone("challenge_enchant_500k", "enchanted.items", 500000, getConfig().challengeEnchantReward); + registerMilestone("challenge_enchant_5m", "enchanted.items", 5000000, getConfig().challengeEnchantReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EXPERIENCE_BOTTLE) + .key("challenge_enchant_power_100") + .title(Localizer.dLocalize("advancement.challenge_enchant_power_100.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_power_100.description")) + .model(CustomModel.get(Material.EXPERIENCE_BOTTLE, "advancement", "enchanting", "challenge_enchant_power_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENCHANTING_TABLE) + .key("challenge_enchant_power_1k") + .title(Localizer.dLocalize("advancement.challenge_enchant_power_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_power_1k.description")) + .model(CustomModel.get(Material.ENCHANTING_TABLE, "advancement", "enchanting", "challenge_enchant_power_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchant_power_100", "enchanted.power", 100, getConfig().challengeEnchantReward); + registerMilestone("challenge_enchant_power_1k", "enchanted.power", 1000, getConfig().challengeEnchantReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LAPIS_LAZULI) + .key("challenge_enchant_levels_1k") + .title(Localizer.dLocalize("advancement.challenge_enchant_levels_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_levels_1k.description")) + .model(CustomModel.get(Material.LAPIS_LAZULI, "advancement", "enchanting", "challenge_enchant_levels_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.LAPIS_BLOCK) + .key("challenge_enchant_levels_10k") + .title(Localizer.dLocalize("advancement.challenge_enchant_levels_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_levels_10k.description")) + .model(CustomModel.get(Material.LAPIS_BLOCK, "advancement", "enchanting", "challenge_enchant_levels_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchant_levels_1k", "enchanted.levels.spent", 1000, getConfig().challengeEnchantReward); + registerMilestone("challenge_enchant_levels_10k", "enchanted.levels.spent", 10000, getConfig().challengeEnchantReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BOOKSHELF) + .key("challenge_enchant_high_25") + .title(Localizer.dLocalize("advancement.challenge_enchant_high_25.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_high_25.description")) + .model(CustomModel.get(Material.BOOKSHELF, "advancement", "enchanting", "challenge_enchant_high_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENCHANTED_BOOK) + .key("challenge_enchant_high_250") + .title(Localizer.dLocalize("advancement.challenge_enchant_high_250.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_high_250.description")) + .model(CustomModel.get(Material.ENCHANTED_BOOK, "advancement", "enchanting", "challenge_enchant_high_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchant_high_25", "enchanting.high.level", 25, getConfig().challengeEnchantReward); + registerMilestone("challenge_enchant_high_250", "enchanting.high.level", 250, getConfig().challengeEnchantReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.EXPERIENCE_BOTTLE) + .key("challenge_enchant_total_500") + .title(Localizer.dLocalize("advancement.challenge_enchant_total_500.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_total_500.description")) + .model(CustomModel.get(Material.EXPERIENCE_BOTTLE, "advancement", "enchanting", "challenge_enchant_total_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DRAGON_BREATH) + .key("challenge_enchant_total_5k") + .title(Localizer.dLocalize("advancement.challenge_enchant_total_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_enchant_total_5k.description")) + .model(CustomModel.get(Material.DRAGON_BREATH, "advancement", "enchanting", "challenge_enchant_total_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_enchant_total_500", "enchanting.total.levels", 500, getConfig().challengeEnchantReward); + registerMilestone("challenge_enchant_total_5k", "enchanting.total.levels", 5000, getConfig().challengeEnchantReward * 2); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EnchantItemEvent e) { + Player p = e.getEnchanter(); + shouldReturnForPlayer(p, e, () -> { + handleEnchantItemEvent(p, e); + }); + + } + + private void handleEnchantItemEvent(Player p, EnchantItemEvent e) { + AdaptPlayer adaptPlayer = getPlayer(p); + adaptPlayer.getData().addStat("enchanted.items", 1); + adaptPlayer.getData().addStat("enchanted.power", e.getEnchantsToAdd().values().stream().mapToInt(i -> i).sum()); + adaptPlayer.getData().addStat("enchanted.levels.spent", e.getExpLevelCost()); + if (e.getExpLevelCost() >= 30) { + adaptPlayer.getData().addStat("enchanting.high.level", 1); + } + adaptPlayer.getData().addStat("enchanting.total.levels", e.getExpLevelCost()); + + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + xp(p, getConfig().enchantPowerXPMultiplier * e.getEnchantsToAdd().values().stream().mapToInt((i) -> i).sum()); + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&d"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Enchant Power XPMultiplier for the Enchanting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double enchantPowerXPMultiplier = 45; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Enchanting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 5250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Enchant Reward for the Enchanting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeEnchantReward = 2500; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillExcavation.java b/src/main/java/art/arcane/adapt/content/skill/SkillExcavation.java new file mode 100644 index 000000000..51fb0c648 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillExcavation.java @@ -0,0 +1,304 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.xp.XpProvenance; +import art.arcane.adapt.content.adaptation.excavation.ExcavationBurrow; +import art.arcane.adapt.content.adaptation.excavation.ExcavationDowsing; +import art.arcane.adapt.content.adaptation.excavation.ExcavationDropToInventory; +import art.arcane.adapt.content.adaptation.excavation.ExcavationEarthMover; +import art.arcane.adapt.content.adaptation.excavation.ExcavationGraveDigger; +import art.arcane.adapt.content.adaptation.excavation.ExcavationHaste; +import art.arcane.adapt.content.adaptation.excavation.ExcavationMudlark; +import art.arcane.adapt.content.adaptation.excavation.ExcavationOmniTool; +import art.arcane.adapt.content.adaptation.excavation.ExcavationSeismicPing; +import art.arcane.adapt.content.adaptation.excavation.ExcavationSoftFall; +import art.arcane.adapt.content.adaptation.excavation.ExcavationSpelunker; +import art.arcane.adapt.content.adaptation.excavation.ExcavationTreasureHunter; +import art.arcane.adapt.content.adaptation.excavation.ExcavationTunneler; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillExcavation extends SimpleSkill { + private final Map cooldowns; + + public SkillExcavation() { + super("excavation", Localizer.dLocalize("skill.excavation.icon")); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("skill.excavation.description")); + setDisplayName(Localizer.dLocalize("skill.excavation.name")); + setColor(C.YELLOW); + setInterval(5953); + setIcon(Material.DIAMOND_SHOVEL); + cooldowns = new HashMap<>(); + registerAdaptation(new ExcavationHaste()); + registerAdaptation(new ExcavationSpelunker()); + registerAdaptation(new ExcavationOmniTool()); + registerAdaptation(new ExcavationDropToInventory()); + registerAdaptation(new ExcavationSeismicPing()); + registerAdaptation(new ExcavationTunneler()); + registerAdaptation(new ExcavationTreasureHunter()); + registerAdaptation(new ExcavationSoftFall()); + registerAdaptation(new ExcavationEarthMover()); + registerAdaptation(new ExcavationDowsing()); + registerAdaptation(new ExcavationBurrow()); + registerAdaptation(new ExcavationGraveDigger()); + registerAdaptation(new ExcavationMudlark()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WOODEN_SHOVEL).key("challenge_excavate_1k") + .title(Localizer.dLocalize("advancement.challenge_excavate_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavate_1k.description")) + .model(CustomModel.get(Material.WOODEN_SHOVEL, "advancement", "excavation", "challenge_excavate_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.KNOWLEDGE_BOOK) + .key("challenge_excavate_5k") + .title(Localizer.dLocalize("advancement.challenge_excavate_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavate_5k.description")) + .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "excavation", "challenge_excavate_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.STONE_SHOVEL) + .key("challenge_excavate_50k") + .title(Localizer.dLocalize("advancement.challenge_excavate_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavate_50k.description")) + .model(CustomModel.get(Material.STONE_SHOVEL, "advancement", "excavation", "challenge_excavate_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.IRON_SHOVEL) + .key("challenge_excavate_500k") + .title(Localizer.dLocalize("advancement.challenge_excavate_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_excavate_500k.description")) + .model(CustomModel.get(Material.IRON_SHOVEL, "advancement", "excavation", "challenge_excavate_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SHOVEL) + .key("challenge_excavate_5m") + .title(Localizer.dLocalize("advancement.challenge_excavate_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_excavate_5m.description")) + .model(CustomModel.get(Material.DIAMOND_SHOVEL, "advancement", "excavation", "challenge_excavate_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + registerMilestone("challenge_excavate_1k", "excavation.blocks.broken", 1000, getConfig().challengeExcavationReward); + registerMilestone("challenge_excavate_5k", "excavation.blocks.broken", 5000, getConfig().challengeExcavationReward); + registerMilestone("challenge_excavate_50k", "excavation.blocks.broken", 50000, getConfig().challengeExcavationReward); + registerMilestone("challenge_enchant_500k", "excavation.blocks.broken", 500000, getConfig().challengeExcavationReward); + registerMilestone("challenge_excavate_5m", "excavation.blocks.broken", 5000000, getConfig().challengeExcavationReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WOODEN_SHOVEL).key("challenge_dig_swing_500") + .title(Localizer.dLocalize("advancement.challenge_dig_swing_500.title")) + .description(Localizer.dLocalize("advancement.challenge_dig_swing_500.description")) + .model(CustomModel.get(Material.WOODEN_SHOVEL, "advancement", "excavation", "challenge_dig_swing_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_SHOVEL) + .key("challenge_dig_swing_5k") + .title(Localizer.dLocalize("advancement.challenge_dig_swing_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_dig_swing_5k.description")) + .model(CustomModel.get(Material.IRON_SHOVEL, "advancement", "excavation", "challenge_dig_swing_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_dig_swing_500", "excavation.swings", 500, getConfig().challengeExcavationReward); + registerMilestone("challenge_dig_swing_5k", "excavation.swings", 5000, getConfig().challengeExcavationReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_SHOVEL).key("challenge_dig_damage_1k") + .title(Localizer.dLocalize("advancement.challenge_dig_damage_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_dig_damage_1k.description")) + .model(CustomModel.get(Material.GOLDEN_SHOVEL, "advancement", "excavation", "challenge_dig_damage_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SHOVEL) + .key("challenge_dig_damage_10k") + .title(Localizer.dLocalize("advancement.challenge_dig_damage_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_dig_damage_10k.description")) + .model(CustomModel.get(Material.DIAMOND_SHOVEL, "advancement", "excavation", "challenge_dig_damage_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_dig_damage_1k", "excavation.damage", 1000, getConfig().challengeExcavationReward); + registerMilestone("challenge_dig_damage_10k", "excavation.damage", 10000, getConfig().challengeExcavationReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CLAY_BALL).key("challenge_dig_value_5k") + .title(Localizer.dLocalize("advancement.challenge_dig_value_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_dig_value_5k.description")) + .model(CustomModel.get(Material.CLAY_BALL, "advancement", "excavation", "challenge_dig_value_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BRICK) + .key("challenge_dig_value_50k") + .title(Localizer.dLocalize("advancement.challenge_dig_value_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_dig_value_50k.description")) + .model(CustomModel.get(Material.BRICK, "advancement", "excavation", "challenge_dig_value_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_dig_value_5k", "excavation.blocks.value", 5000, getConfig().challengeExcavationReward); + registerMilestone("challenge_dig_value_50k", "excavation.blocks.value", 50000, getConfig().challengeExcavationReward * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GRAVEL).key("challenge_dig_gravel_500") + .title(Localizer.dLocalize("advancement.challenge_dig_gravel_500.title")) + .description(Localizer.dLocalize("advancement.challenge_dig_gravel_500.description")) + .model(CustomModel.get(Material.GRAVEL, "advancement", "excavation", "challenge_dig_gravel_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.FLINT) + .key("challenge_dig_gravel_5k") + .title(Localizer.dLocalize("advancement.challenge_dig_gravel_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_dig_gravel_5k.description")) + .model(CustomModel.get(Material.FLINT, "advancement", "excavation", "challenge_dig_gravel_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_dig_gravel_500", "excavation.gravel", 500, getConfig().challengeExcavationReward); + registerMilestone("challenge_dig_gravel_5k", "excavation.gravel", 5000, getConfig().challengeExcavationReward * 2); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p && checkValidEntity(e.getEntity().getType())) { + if (!getConfig().getXpForAttackingWithTools) { + return; + } + shouldReturnForPlayer(p, e, () -> handleEntityDamageByPlayer(p, e)); + } + } + + private void handleEntityDamageByPlayer(Player p, EntityDamageByEntityEvent e) { + AdaptPlayer a = getPlayer(p); + ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); + if (isShovel(hand)) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + getPlayer(p).getData().addStat("excavation.swings", 1); + getPlayer(p).getData().addStat("excavation.damage", e.getDamage()); + xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().axeDamageXPMultiplier * e.getDamage()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, e, () -> { + if (isShovel(p.getInventory().getItemInMainHand())) { + handleBlockBreakWithShovel(p, e); + } + }); + + } + + private void handleBlockBreakWithShovel(Player p, BlockBreakEvent e) { + getPlayer(p).getData().addStat("excavation.blocks.broken", 1); + getPlayer(p).getData().addStat("excavation.blocks.value", getValue(e.getBlock().getBlockData())); + Material blockType = e.getBlock().getType(); + if (blockType == Material.GRAVEL || blockType == Material.SAND || blockType == Material.RED_SAND + || blockType == Material.CLAY || blockType == Material.SOUL_SAND || blockType == Material.SOUL_SOIL) { + getPlayer(p).getData().addStat("excavation.gravel", 1); + } + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + if (XpProvenance.breakXpMultiplier(e.getBlock()) <= 0) { + return; + } + double v = getValue(e.getBlock().getType()); + xp(p, e.getBlock().getLocation().clone().add(0.5, 0.5, 0.5), blockXP(e.getBlock(), v)); + } + + public double getValue(Material type) { + double value = super.getValue(type) * getConfig().valueXPMultiplier; + value += Math.min(getConfig().maxHardnessBonus, type.getHardness()); + value += Math.min(getConfig().maxBlastResistanceBonus, type.getBlastResistance()); + return value; + } + + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&e"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Get Xp For Attacking With Tools for the Excavation skill.", impact = "True enables this behavior and false disables it.") + boolean getXpForAttackingWithTools = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Hardness Bonus for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxHardnessBonus = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Blast Resistance Bonus for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxBlastResistanceBonus = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Excavation Reward for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeExcavationReward = 1200; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Value XPMultiplier for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double valueXPMultiplier = 0.6; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Axe Damage XPMultiplier for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double axeDamageXPMultiplier = 4.0; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillHerbalism.java b/src/main/java/art/arcane/adapt/content/skill/SkillHerbalism.java new file mode 100644 index 000000000..955a356fe --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillHerbalism.java @@ -0,0 +1,363 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.xp.XpNovelty; +import art.arcane.adapt.api.xp.XpProvenance; +import art.arcane.adapt.content.adaptation.herbalism.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.scheduling.J; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.Levelled; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.*; +import org.bukkit.inventory.meta.PotionMeta; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillHerbalism extends SimpleSkill { + private final Map cooldown = new HashMap<>(); + + public SkillHerbalism() { + super("herbalism", Localizer.dLocalize("skill.herbalism.icon")); + registerConfiguration(Config.class); + setColor(C.GREEN); + setInterval(3990); + setDescription(Localizer.dLocalize("skill.herbalism.description")); + setDisplayName(Localizer.dLocalize("skill.herbalism.name")); + setIcon(Material.WHEAT); + registerAdaptation(new HerbalismGrowthAura()); + registerAdaptation(new HerbalismReplant()); + registerAdaptation(new HerbalismHungryShield()); + registerAdaptation(new HerbalismHungryHippo()); + registerAdaptation(new HerbalismDropToInventory()); + registerAdaptation(new HerbalismLuck()); + registerAdaptation(new HerbalismMyconid()); + registerAdaptation(new HerbalismTerralid()); + registerAdaptation(new HerbalismCraftableMushroomBlocks()); + registerAdaptation(new HerbalismCraftableCobweb()); + registerAdaptation(new HerbalismSeedSower()); + registerAdaptation(new HerbalismCompostCascade()); + registerAdaptation(new HerbalismRootedFooting()); + registerAdaptation(new HerbalismBeeShepherd()); + registerAdaptation(new HerbalismSporeBloom()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COOKED_BEEF) + .key("challenge_eat_100") + .title(Localizer.dLocalize("advancement.challenge_eat_100.title")) + .description(Localizer.dLocalize("advancement.challenge_eat_100.description")) + .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "challenge_eat_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.COOKED_BEEF) + .key("challenge_eat_1000") + .title(Localizer.dLocalize("advancement.challenge_eat_1000.title")) + .description(Localizer.dLocalize("advancement.challenge_eat_1000.description")) + .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "challenge_eat_1000")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() + .icon(Material.COOKED_BEEF) + .key("challenge_eat_10000") + .title(Localizer.dLocalize("advancement.challenge_eat_10000.title")) + .description(Localizer.dLocalize("advancement.challenge_eat_10000.description")) + .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "challenge_eat_10000")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_eat_100", "food.eaten", 100, getConfig().challengeEat100Reward); + registerMilestone("challenge_eat_1000", "food.eaten", 1000, getConfig().challengeEat1kReward); + registerMilestone("challenge_eat_10000", "food.eaten", 10000, getConfig().challengeEat1kReward); + + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COOKED_BEEF) + .key("challenge_harvest_100") + .title(Localizer.dLocalize("advancement.challenge_harvest_100.title")) + .description(Localizer.dLocalize("advancement.challenge_harvest_100.description")) + .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "harvest_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.COOKED_BEEF) + .key("challenge_harvest_1000") + .title(Localizer.dLocalize("advancement.challenge_harvest_1000.title")) + .description(Localizer.dLocalize("advancement.challenge_harvest_1000.description")) + .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "harvest_1000")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_harvest_100", "harvest.blocks", 100, getConfig().challengeHarvest100Reward); + registerMilestone("challenge_harvest_1000", "harvest.blocks", 1000, getConfig().challengeHarvest1kReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WHEAT_SEEDS) + .key("challenge_plant_100") + .title(Localizer.dLocalize("advancement.challenge_plant_100.title")) + .description(Localizer.dLocalize("advancement.challenge_plant_100.description")) + .model(CustomModel.get(Material.WHEAT_SEEDS, "advancement", "herbalism", "challenge_plant_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BEETROOT_SEEDS) + .key("challenge_plant_1k") + .title(Localizer.dLocalize("advancement.challenge_plant_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_plant_1k.description")) + .model(CustomModel.get(Material.BEETROOT_SEEDS, "advancement", "herbalism", "challenge_plant_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GOLDEN_CARROT) + .key("challenge_plant_5k") + .title(Localizer.dLocalize("advancement.challenge_plant_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_plant_5k.description")) + .model(CustomModel.get(Material.GOLDEN_CARROT, "advancement", "herbalism", "challenge_plant_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_plant_100", "harvest.planted", 100, getConfig().challengePlant100Reward); + registerMilestone("challenge_plant_1k", "harvest.planted", 1000, getConfig().challengePlant1kReward); + registerMilestone("challenge_plant_5k", "harvest.planted", 5000, getConfig().challengePlant5kReward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.COMPOSTER) + .key("challenge_compost_50") + .title(Localizer.dLocalize("advancement.challenge_compost_50.title")) + .description(Localizer.dLocalize("advancement.challenge_compost_50.description")) + .model(CustomModel.get(Material.COMPOSTER, "advancement", "herbalism", "challenge_compost_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BONE_MEAL) + .key("challenge_compost_500") + .title(Localizer.dLocalize("advancement.challenge_compost_500.title")) + .description(Localizer.dLocalize("advancement.challenge_compost_500.description")) + .model(CustomModel.get(Material.BONE_MEAL, "advancement", "herbalism", "challenge_compost_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_compost_50", "harvest.composted", 50, getConfig().challengeCompost50Reward); + registerMilestone("challenge_compost_500", "harvest.composted", 500, getConfig().challengeCompost500Reward); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SHEARS) + .key("challenge_shear_50") + .title(Localizer.dLocalize("advancement.challenge_shear_50.title")) + .description(Localizer.dLocalize("advancement.challenge_shear_50.description")) + .model(CustomModel.get(Material.SHEARS, "advancement", "herbalism", "challenge_shear_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.WHITE_WOOL) + .key("challenge_shear_250") + .title(Localizer.dLocalize("advancement.challenge_shear_250.title")) + .description(Localizer.dLocalize("advancement.challenge_shear_250.description")) + .model(CustomModel.get(Material.WHITE_WOOL, "advancement", "herbalism", "challenge_shear_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_shear_50", "herbalism.sheared", 50, getConfig().challengeShear50Reward); + registerMilestone("challenge_shear_250", "herbalism.sheared", 250, getConfig().challengeShear250Reward); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + Player p = e.getPlayer(); + cooldown.remove(p.getUniqueId()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerItemConsumeEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (e.getItem().getItemMeta() instanceof PotionMeta o) { + return; + } + + handleHerbCooldown(p, () -> { + xp(p, getConfig().foodConsumeXP); + getPlayer(p).getData().addStat("food.eaten", 1); + }); + + + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerShearEntityEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(e.getPlayer(), e, () -> { + getPlayer(p).getData().addStat("herbalism.sheared", 1); + xp(p, e.getEntity().getLocation(), getConfig().shearXP); + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerHarvestBlockEvent e) { + shouldReturnForPlayer(e.getPlayer(), e, () -> handleEvent(e, e.getPlayer(), e.getHarvestedBlock(), "harvest.blocks")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockPlaceEvent e) { + shouldReturnForPlayer(e.getPlayer(), e, () -> handleEvent(e, e.getPlayer(), e.getBlock(), "harvest.planted")); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (e.useItemInHand().equals(Event.Result.DENY)) { + return; + } + if (e.getClickedBlock() == null) { + return; + } + if (e.getClickedBlock().getType().equals(Material.COMPOSTER)) { + handleComposterInteraction(e, p); + } + }); + + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + shouldReturnForPlayer(e.getPlayer(), e, () -> handleEvent(e, e.getPlayer(), e.getBlock(), "harvest.blocks")); + } + + private void handleHerbCooldown(Player p, Runnable action) { + if (cooldown.containsKey(p.getUniqueId())) { + if (cooldown.get(p.getUniqueId()) + getConfig().harvestXpCooldown > System.currentTimeMillis()) { + return; + } else { + cooldown.remove(p.getUniqueId()); + } + } + + cooldown.put(p.getUniqueId(), System.currentTimeMillis()); + action.run(); + } + + private void handleEvent(Cancellable e, Player p, Block block, String stat) { + handleHerbCooldown(p, () -> { + if (block.getBlockData() instanceof Ageable ageableBlock) { + double integrity = XpProvenance.harvestXpMultiplier(block) * XpNovelty.fieldCycleMultiplier(p, block); + xp(p, block.getLocation().clone().add(0.5, 0.5, 0.5), getConfig().harvestPerAgeXP * ageableBlock.getAge() * integrity); + getPlayer(p).getData().addStat(stat, 1); + } + }); + } + + private void handleComposterInteraction(PlayerInteractEvent e, Player p) { + Block b = e.getClickedBlock(); + assert b != null; + if (!(b.getBlockData() instanceof Levelled oldData)) + return; + int ol = oldData.getLevel(); + J.runAt(b.getLocation(), () -> { + if (!(b.getBlockData() instanceof Levelled newData)) + return; + int nl = newData.getLevel(); + if (nl > ol || (ol > 0 && nl == 0)) { + xp(p, e.getClickedBlock().getLocation().clone().add(0.5, 0.5, 0.5), getConfig().composterBaseXP + (nl * getConfig().composterLevelXPMultiplier) + (nl == 0 ? getConfig().composterNonZeroLevelBonus : 5)); + getPlayer(p).getData().addStat("harvest.composted", 1); + } + }); + } + + + @Override + public void onTick() { + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + public static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + public boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Harvest Xp Cooldown for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double harvestXpCooldown = 3500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Food Consume XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double foodConsumeXP = 35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Shear XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double shearXP = 35; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Harvest Per Age XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double harvestPerAgeXP = 5.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Plant Crop Seeds XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double plantCropSeedsXP = 4.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Composter Base XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double composterBaseXP = 2.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Composter Level XPMultiplier for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double composterLevelXPMultiplier = 1.25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Composter Non Zero Level Bonus for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double composterNonZeroLevelBonus = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Eat100Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengeEat100Reward = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Eat1k Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengeEat1kReward = 6250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Harvest100Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengeHarvest100Reward = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Harvest1k Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengeHarvest1kReward = 6250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Plant100 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengePlant100Reward = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Plant1k Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengePlant1kReward = 6250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Plant5k Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengePlant5kReward = 25000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Compost50 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengeCompost50Reward = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Compost500 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengeCompost500Reward = 6250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Shear50 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengeShear50Reward = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Shear250 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double challengeShear250Reward = 6250; + String skillColor = "&a"; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillHunter.java b/src/main/java/art/arcane/adapt/content/skill/SkillHunter.java new file mode 100644 index 000000000..9ad2261dc --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillHunter.java @@ -0,0 +1,329 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.content.adaptation.excavation.ExcavationGraveDigger; +import art.arcane.adapt.content.adaptation.hunter.*; +import art.arcane.adapt.content.adaptation.tragoul.TragoulSkeletalServant; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.reflect.registries.Attributes; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public class SkillHunter extends SimpleSkill { + private final Map cooldowns; + + public SkillHunter() { + super("hunter", Localizer.dLocalize("skill.hunter.icon")); + registerConfiguration(Config.class); + setColor(C.RED); + setDescription(Localizer.dLocalize("skill.hunter.description")); + setDisplayName(Localizer.dLocalize("skill.hunter.name")); + setInterval(4150); + setIcon(Material.BONE); + cooldowns = new HashMap<>(); + registerAdaptation(new HunterAdrenaline()); + registerAdaptation(new HunterRegen()); + registerAdaptation(new HunterInvis()); + registerAdaptation(new HunterJumpBoost()); + registerAdaptation(new HunterLuck()); + registerAdaptation(new HunterSpeed()); + registerAdaptation(new HunterStrength()); + registerAdaptation(new HunterResistance()); + registerAdaptation(new HunterDropToInventory()); + registerAdaptation(new HunterTrophySkinner()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TURTLE_EGG) + .key("horrible_person") + .title(Localizer.dLocalize("advancement.horrible_person.title")) + .description(Localizer.dLocalize("advancement.horrible_person.description")) + .model(CustomModel.get(Material.TURTLE_EGG, "advancement", "hunter", "horrible_person")) + .frame(AdaptAdvancementFrame.GOAL) + .visibility(AdvancementVisibility.HIDDEN) + .build() + ); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TURTLE_EGG) + .key("challenge_turtle_egg_smasher") + .title(Localizer.dLocalize("advancement.challenge_turtle_egg_smasher.title")) + .description(Localizer.dLocalize("advancement.challenge_turtle_egg_smasher.description")) + .model(CustomModel.get(Material.TURTLE_EGG, "advancement", "hunter", "challenge_turtle_egg_smasher")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TURTLE_EGG) + .key("challenge_turtle_egg_annihilator") + .title(Localizer.dLocalize("advancement.challenge_turtle_egg_annihilator.title")) + .description(Localizer.dLocalize("advancement.challenge_turtle_egg_annihilator.description")) + .model(CustomModel.get(Material.TURTLE_EGG, "advancement", "hunter", "challenge_turtle_egg_annihilator")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_novice_hunter") + .title(Localizer.dLocalize("advancement.challenge_novice_hunter.title")) + .description(Localizer.dLocalize("advancement.challenge_novice_hunter.description")) + .model(CustomModel.get(Material.BONE, "advancement", "hunter", "challenge_novice_hunter")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_intermediate_hunter") + .title(Localizer.dLocalize("advancement.challenge_intermediate_hunter.title")) + .description(Localizer.dLocalize("advancement.challenge_intermediate_hunter.description")) + .model(CustomModel.get(Material.IRON_SWORD, "advancement", "hunter", "challenge_intermediate_hunter")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_advanced_hunter") + .title(Localizer.dLocalize("advancement.challenge_advanced_hunter.title")) + .description(Localizer.dLocalize("advancement.challenge_advanced_hunter.description")) + .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "hunter", "challenge_advanced_hunter")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CREEPER_HEAD) + .key("challenge_creeper_conqueror") + .title(Localizer.dLocalize("advancement.challenge_creeper_conqueror.title")) + .description(Localizer.dLocalize("advancement.challenge_creeper_conqueror.description")) + .model(CustomModel.get(Material.CREEPER_HEAD, "advancement", "hunter", "challenge_creeper_conqueror")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TNT) + .key("challenge_creeper_annihilator") + .title(Localizer.dLocalize("advancement.challenge_creeper_annihilator.title")) + .description(Localizer.dLocalize("advancement.challenge_creeper_annihilator.description")) + .model(CustomModel.get(Material.TNT, "advancement", "hunter", "challenge_creeper_annihilator")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_kills_500") + .title(Localizer.dLocalize("advancement.challenge_kills_500.title")) + .description(Localizer.dLocalize("advancement.challenge_kills_500.description")) + .model(CustomModel.get(Material.BONE, "advancement", "hunter", "challenge_kills_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_kills_5k") + .title(Localizer.dLocalize("advancement.challenge_kills_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_kills_5k.description")) + .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "hunter", "challenge_kills_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DRAGON_HEAD) + .key("challenge_boss_1") + .title(Localizer.dLocalize("advancement.challenge_boss_1.title")) + .description(Localizer.dLocalize("advancement.challenge_boss_1.description")) + .model(CustomModel.get(Material.DRAGON_HEAD, "advancement", "hunter", "challenge_boss_1")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHER_STAR) + .key("challenge_boss_10") + .title(Localizer.dLocalize("advancement.challenge_boss_10.title")) + .description(Localizer.dLocalize("advancement.challenge_boss_10.description")) + .model(CustomModel.get(Material.NETHER_STAR, "advancement", "hunter", "challenge_boss_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + + registerMilestone("horrible_person", "killed.turtleeggs", 1, getConfig().turtleEggKillXP); + registerMilestone("challenge_turtle_egg_smasher", "killed.turtleeggs", 100, getConfig().turtleEggKillXP * 10); + registerMilestone("challenge_turtle_egg_annihilator", "killed.turtleeggs", 1000, getConfig().turtleEggKillXP * 10); + registerMilestone("challenge_novice_hunter", "killed.monsters", 100, getConfig().turtleEggKillXP * 3); + registerMilestone("challenge_intermediate_hunter", "killed.monsters", 1000, getConfig().turtleEggKillXP * 3); + registerMilestone("challenge_advanced_hunter", "killed.monsters", 10000, getConfig().turtleEggKillXP * 3); + registerMilestone("challenge_creeper_conqueror", "killed.creepers", 100, getConfig().turtleEggKillXP * 3); + registerMilestone("challenge_creeper_annihilator", "killed.creepers", 1000, getConfig().turtleEggKillXP * 3); + registerMilestone("challenge_kills_500", "killed.kills", 500, getConfig().killsChallengeReward); + registerMilestone("challenge_kills_5k", "killed.kills", 5000, getConfig().killsChallengeReward * 5); + registerMilestone("challenge_boss_1", "hunter.boss.kills", 1, getConfig().bossKillReward); + registerMilestone("challenge_boss_10", "hunter.boss.kills", 10, getConfig().bossKillReward * 5); + } + + private void handleCooldownAndXp(Player p, double xpAmount) { + handleCooldownAndXp(p, xpAmount, null); + } + + private void handleCooldownAndXp(Player p, double xpAmount, String rewardKey) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + xp(p, xpAmount, rewardKey); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (e.getBlock().getType().equals(Material.TURTLE_EGG)) { + handleCooldownAndXp(p, getConfig().turtleEggKillXP, "hunter:turtle-egg:break"); + getPlayer(p).getData().addStat("killed.turtleeggs", 1); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerInteractEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (e.getAction().equals(Action.PHYSICAL) && e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.TURTLE_EGG)) { + handleCooldownAndXp(p, getConfig().turtleEggKillXP, "hunter:turtle-egg:step"); + getPlayer(p).getData().addStat("killed.turtleeggs", 1); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + if (e.getEntity().getKiller() == null) { + return; + } + Player p = e.getEntity().getKiller(); + + if (!getConfig().getXpForAttackingWithTools) { + return; + } + + if (TragoulSkeletalServant.isServant(e.getEntity()) || ExcavationGraveDigger.isGraveMob(e.getEntity())) { + return; + } + + shouldReturnForPlayer(p, () -> { + if (e.getEntity().getType().equals(EntityType.CREEPER)) { + double cmult = getConfig().creeperKillMultiplier; + art.arcane.adapt.api.version.IAttribute attribute = Version.get().getAttribute(e.getEntity(), Attributes.GENERIC_MAX_HEALTH); + double xpAmount = (attribute == null ? 1 : attribute.getValue()) * getConfig().killMaxHealthXPMultiplier * cmult; + if (e.getEntity().getPortalCooldown() > 0) { + xpAmount *= getConfig().spawnerMobReductionXpMultiplier; + } + getPlayer(p).getData().addStat("killed.kills", 1); + handleCooldownAndXp(p, xpAmount, "hunter:kill:creeper"); + } else { + handleEntityKill(p, e.getEntity()); + } + EntityType type = e.getEntity().getType(); + if (type == EntityType.ENDER_DRAGON || type == EntityType.WITHER || type == EntityType.ELDER_GUARDIAN || type == EntityType.WARDEN) { + getPlayer(p).getData().addStat("hunter.boss.kills", 1); + } + }); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(CreatureSpawnEvent e) { + if (!isEnabled()) { + return; + } + if (e.getSpawnReason().equals(CreatureSpawnEvent.SpawnReason.SPAWNER)) { + Entity ent = e.getEntity(); + ent.setPortalCooldown(630726000); + } + } + + private void handleEntityKill(Player p, Entity entity) { + if (entity instanceof LivingEntity livingEntity) { + art.arcane.adapt.api.version.IAttribute attribute = Version.get().getAttribute(livingEntity, Attributes.GENERIC_MAX_HEALTH); + double xpAmount = (attribute == null ? 1 : attribute.getValue()) * getConfig().killMaxHealthXPMultiplier; + if (entity.getPortalCooldown() > 0) { + xpAmount *= getConfig().spawnerMobReductionXpMultiplier; + } + getPlayer(p).getData().addStat("killed.kills", 1); + String rewardKey = "hunter:kill:" + entity.getType().name().toLowerCase(Locale.ROOT); + handleCooldownAndXp(p, xpAmount, rewardKey); + } + } + + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&c"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Get Xp For Attacking With Tools for the Hunter skill.", impact = "True enables this behavior and false disables it.") + boolean getXpForAttackingWithTools = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Turtle Egg Kill XP for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double turtleEggKillXP = 100; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Creeper Kill Multiplier for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double creeperKillMultiplier = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Kill Max Health XPMultiplier for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double killMaxHealthXPMultiplier = 3.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Spawner Mob Reduction Xp Multiplier for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double spawnerMobReductionXpMultiplier = 0.3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Kills Challenge Reward for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double killsChallengeReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Boss Kill Reward for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double bossKillReward = 1000; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillNether.java b/src/main/java/art/arcane/adapt/content/skill/SkillNether.java new file mode 100644 index 000000000..11603e6fc --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillNether.java @@ -0,0 +1,276 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.nether.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByBlockEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDeathEvent; + +public class SkillNether extends SimpleSkill { + private int witherRoseCooldown; + + public SkillNether() { + super("nether", Localizer.dLocalize("skill.nether.icon")); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("skill.nether.description")); + setDisplayName(Localizer.dLocalize("skill.nether.name")); + setInterval(7425); + setColor(C.DARK_GRAY); + setIcon(Material.NETHER_STAR); + registerAdaptation(new NetherWitherResist()); + registerAdaptation(new NetherSkullYeet()); + registerAdaptation(new NetherFireResist()); + registerAdaptation(new NetherLavaWalker()); + registerAdaptation(new NetherGhastWard()); + registerAdaptation(new NetherBlazeLeech()); + registerAdaptation(new NetherPiglinBroker()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_nether_50") + .title(Localizer.dLocalize("advancement.challenge_nether_50.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_50.description")) + .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "nether", "challenge_nether_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHER_STAR) + .key("challenge_nether_500") + .title(Localizer.dLocalize("advancement.challenge_nether_500.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_500.description")) + .model(CustomModel.get(Material.NETHER_STAR, "advancement", "nether", "challenge_nether_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BEACON) + .key("challenge_nether_5k") + .title(Localizer.dLocalize("advancement.challenge_nether_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_nether_5k.description")) + .model(CustomModel.get(Material.BEACON, "advancement", "nether", "challenge_nether_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_nether_50", "nether.kills", 50, getConfig().getChallengeNetherReward()); + registerMilestone("challenge_nether_500", "nether.kills", 500, getConfig().getChallengeNetherReward() * 2); + registerMilestone("challenge_nether_5k", "nether.kills", 5000, getConfig().getChallengeNetherReward() * 5); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_ROSE) + .key("challenge_wither_dmg_500") + .title(Localizer.dLocalize("advancement.challenge_wither_dmg_500.title")) + .description(Localizer.dLocalize("advancement.challenge_wither_dmg_500.description")) + .model(CustomModel.get(Material.WITHER_ROSE, "advancement", "nether", "challenge_wither_dmg_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SOUL_LANTERN) + .key("challenge_wither_dmg_5k") + .title(Localizer.dLocalize("advancement.challenge_wither_dmg_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_wither_dmg_5k.description")) + .model(CustomModel.get(Material.SOUL_LANTERN, "advancement", "nether", "challenge_wither_dmg_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_wither_dmg_500", "nether.wither.damage", 500, getConfig().getChallengeWitherDmgReward()); + registerMilestone("challenge_wither_dmg_5k", "nether.wither.damage", 5000, getConfig().getChallengeWitherDmgReward() * 2); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_wither_skel_25") + .title(Localizer.dLocalize("advancement.challenge_wither_skel_25.title")) + .description(Localizer.dLocalize("advancement.challenge_wither_skel_25.description")) + .model(CustomModel.get(Material.BONE, "advancement", "nether", "challenge_wither_skel_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_wither_skel_250") + .title(Localizer.dLocalize("advancement.challenge_wither_skel_250.title")) + .description(Localizer.dLocalize("advancement.challenge_wither_skel_250.description")) + .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "nether", "challenge_wither_skel_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_wither_skel_25", "nether.skeleton.kills", 25, getConfig().getChallengeWitherSkelReward()); + registerMilestone("challenge_wither_skel_250", "nether.skeleton.kills", 250, getConfig().getChallengeWitherSkelReward() * 2); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.NETHER_STAR) + .key("challenge_wither_boss_1") + .title(Localizer.dLocalize("advancement.challenge_wither_boss_1.title")) + .description(Localizer.dLocalize("advancement.challenge_wither_boss_1.description")) + .model(CustomModel.get(Material.NETHER_STAR, "advancement", "nether", "challenge_wither_boss_1")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BEACON) + .key("challenge_wither_boss_10") + .title(Localizer.dLocalize("advancement.challenge_wither_boss_10.title")) + .description(Localizer.dLocalize("advancement.challenge_wither_boss_10.description")) + .model(CustomModel.get(Material.BEACON, "advancement", "nether", "challenge_wither_boss_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_wither_boss_1", "nether.boss.kills", 1, getConfig().getChallengeWitherBossReward()); + registerMilestone("challenge_wither_boss_10", "nether.boss.kills", 10, getConfig().getChallengeWitherBossReward() * 2); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WITHER_ROSE) + .key("challenge_roses_10") + .title(Localizer.dLocalize("advancement.challenge_roses_10.title")) + .description(Localizer.dLocalize("advancement.challenge_roses_10.description")) + .model(CustomModel.get(Material.WITHER_ROSE, "advancement", "nether", "challenge_roses_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.FLOWER_POT) + .key("challenge_roses_100") + .title(Localizer.dLocalize("advancement.challenge_roses_100.title")) + .description(Localizer.dLocalize("advancement.challenge_roses_100.description")) + .model(CustomModel.get(Material.FLOWER_POT, "advancement", "nether", "challenge_roses_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_roses_10", "nether.roses.broken", 10, getConfig().getChallengeRosesReward()); + registerMilestone("challenge_roses_100", "nether.roses.broken", 100, getConfig().getChallengeRosesReward() * 2); + } + + private boolean isWitherDamageCause(EntityDamageEvent.DamageCause cause) { + return cause == EntityDamageEvent.DamageCause.WITHER; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageEvent e) { + if (!this.isEnabled() || !(e.getEntity() instanceof Player p) || !isWitherDamageCause(e.getCause()) || e instanceof EntityDamageByBlockEvent) { + return; + } + shouldReturnForPlayer(p, e, () -> { + getPlayer(p).getData().addStat("nether.wither.damage", e.getDamage()); + xp(p, getConfig().getWitherDamageXp()); + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (e.getBlock().getType() == Material.WITHER_ROSE && witherRoseCooldown == 0) { + witherRoseCooldown = getConfig().getWitherRoseBreakCooldown(); + getPlayer(p).getData().addStat("nether.roses.broken", 1); + xp(p, e.getBlock().getLocation().add(.5D, .5D, .5D), getConfig().getWitherRoseBreakXp()); + } + }); + + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + Player p = e.getEntity().getKiller(); + if (p == null || !p.getClass().getSimpleName().equals("CraftPlayer")) { + return; + } + shouldReturnForPlayer(p, () -> { + if (e.getEntityType() == EntityType.WITHER_SKELETON) { + getPlayer(p).getData().addStat("nether.kills", 1); + getPlayer(p).getData().addStat("nether.skeleton.kills", 1); + xp(p, getConfig().getWitherSkeletonKillXp()); + } else if (e.getEntityType() == EntityType.WITHER) { + getPlayer(p).getData().addStat("nether.kills", 1); + getPlayer(p).getData().addStat("nether.boss.kills", 1); + xp(p, getConfig().getWitherKillXp()); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player p) || !isWitherDamageCause(e.getCause())) { + return; + } + shouldReturnForPlayer(p, e, () -> xp(p, getConfig().getWitherAttackXp())); + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + if (witherRoseCooldown > 0) { + witherRoseCooldown--; + } + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player i = adaptPlayer.getPlayer(); + shouldReturnForPlayer(i, () -> checkStatTrackers(adaptPlayer)); + } + } + + @Override + public boolean isEnabled() { + return getConfig().isEnabled(); + } + + @Data + @NoArgsConstructor + public static class Config { + String skillColor = "&8"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + private boolean enabled = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Wither Damage Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double witherDamageXp = 26.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Wither Attack Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double witherAttackXp = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Wither Skeleton Kill Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double witherSkeletonKillXp = 225; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Wither Kill Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double witherKillXp = 900; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Wither Rose Break Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double witherRoseBreakXp = 125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Wither Rose Break Cooldown for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private int witherRoseBreakCooldown = 60 * 20; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Nether Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double challengeNetherReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Wither Damage Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double challengeWitherDmgReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Wither Skeleton Kill Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double challengeWitherSkelReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Wither Boss Kill Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double challengeWitherBossReward = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Roses Broken Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + private double challengeRosesReward = 500; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillPickaxes.java b/src/main/java/art/arcane/adapt/content/skill/SkillPickaxes.java new file mode 100644 index 000000000..c5cb68b6d --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillPickaxes.java @@ -0,0 +1,350 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.xp.XpProvenance; +import art.arcane.adapt.content.adaptation.pickaxe.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillPickaxes extends SimpleSkill { + private final Map cooldowns; + + public SkillPickaxes() { + super("pickaxe", Localizer.dLocalize("skill.pickaxe.icon")); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("skill.pickaxe.description")); + setDisplayName(Localizer.dLocalize("skill.pickaxe.name")); + setColor(C.GOLD); + setInterval(2750); + setIcon(Material.NETHERITE_PICKAXE); + cooldowns = new HashMap<>(); + registerAdaptation(new PickaxeChisel()); + registerAdaptation(new PickaxeVeinminer()); + registerAdaptation(new PickaxeAutosmelt()); + registerAdaptation(new PickaxeDropToInventory()); + registerAdaptation(new PickaxeSilkSpawner()); + registerAdaptation(new PickaxeQuarrySense()); + registerAdaptation(new PickaxeTunnelBore()); + registerAdaptation(new PickaxeDeepCore()); + registerAdaptation(new PickaxeObsidianRush()); + registerAdaptation(new PickaxeUnbreakablePact()); + registerAdaptation(new PickaxeRepairRhythm()); + registerAdaptation(new PickaxeGemPolish()); + registerAdaptation(new PickaxeStoneSkin()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WOODEN_PICKAXE) + .key("challenge_pickaxe_1k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_1k.description")) + .model(CustomModel.get(Material.WOODEN_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.STONE_PICKAXE) + .key("challenge_pickaxe_5k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_5k.description")) + .model(CustomModel.get(Material.STONE_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE) + .key("challenge_pickaxe_50k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_50k.description")) + .model(CustomModel.get(Material.IRON_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_PICKAXE) + .key("challenge_pickaxe_500k") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_500k.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_500k.description")) + .model(CustomModel.get(Material.DIAMOND_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_500k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_PICKAXE) + .key("challenge_pickaxe_5m") + .title(Localizer.dLocalize("advancement.challenge_pickaxe_5m.title")) + .description(Localizer.dLocalize("advancement.challenge_pickaxe_5m.description")) + .model(CustomModel.get(Material.NETHERITE_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_5m")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()) + .build()) + .build()); + + registerMilestone("challenge_pickaxe_1k", "pickaxe.blocks.broken", 100, getConfig().emeraldBonus * 2); + registerMilestone("challenge_pickaxe_5k", "pickaxe.blocks.broken", 500, getConfig().emeraldBonus * 5); + registerMilestone("challenge_pickaxe_50k", "pickaxe.blocks.broken", 5000, getConfig().emeraldBonus * 10); + registerMilestone("challenge_pickaxe_500k", "pickaxe.blocks.broken", 50000, getConfig().emeraldBonus * 10); + registerMilestone("challenge_pickaxe_5m", "pickaxe.blocks.broken", 500000, getConfig().emeraldBonus * 50); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WOODEN_PICKAXE).key("challenge_pick_swing_500") + .title(Localizer.dLocalize("advancement.challenge_pick_swing_500.title")) + .description(Localizer.dLocalize("advancement.challenge_pick_swing_500.description")) + .model(CustomModel.get(Material.WOODEN_PICKAXE, "advancement", "pickaxe", "challenge_pick_swing_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_PICKAXE) + .key("challenge_pick_swing_5k") + .title(Localizer.dLocalize("advancement.challenge_pick_swing_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_pick_swing_5k.description")) + .model(CustomModel.get(Material.IRON_PICKAXE, "advancement", "pickaxe", "challenge_pick_swing_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_pick_swing_500", "pickaxe.swings", 500, getConfig().emeraldBonus); + registerMilestone("challenge_pick_swing_5k", "pickaxe.swings", 5000, getConfig().emeraldBonus * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_PICKAXE).key("challenge_pick_damage_1k") + .title(Localizer.dLocalize("advancement.challenge_pick_damage_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_pick_damage_1k.description")) + .model(CustomModel.get(Material.GOLDEN_PICKAXE, "advancement", "pickaxe", "challenge_pick_damage_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_PICKAXE) + .key("challenge_pick_damage_10k") + .title(Localizer.dLocalize("advancement.challenge_pick_damage_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_pick_damage_10k.description")) + .model(CustomModel.get(Material.DIAMOND_PICKAXE, "advancement", "pickaxe", "challenge_pick_damage_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_pick_damage_1k", "pickaxe.damage", 1000, getConfig().emeraldBonus); + registerMilestone("challenge_pick_damage_10k", "pickaxe.damage", 10000, getConfig().emeraldBonus * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.RAW_IRON).key("challenge_pick_value_5k") + .title(Localizer.dLocalize("advancement.challenge_pick_value_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_pick_value_5k.description")) + .model(CustomModel.get(Material.RAW_IRON, "advancement", "pickaxe", "challenge_pick_value_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.RAW_GOLD) + .key("challenge_pick_value_50k") + .title(Localizer.dLocalize("advancement.challenge_pick_value_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_pick_value_50k.description")) + .model(CustomModel.get(Material.RAW_GOLD, "advancement", "pickaxe", "challenge_pick_value_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_pick_value_5k", "pickaxe.blocks.value", 5000, getConfig().emeraldBonus); + registerMilestone("challenge_pick_value_50k", "pickaxe.blocks.value", 50000, getConfig().emeraldBonus * 2); + + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_ORE).key("challenge_pick_ores_500") + .title(Localizer.dLocalize("advancement.challenge_pick_ores_500.title")) + .description(Localizer.dLocalize("advancement.challenge_pick_ores_500.description")) + .model(CustomModel.get(Material.IRON_ORE, "advancement", "pickaxe", "challenge_pick_ores_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_ORE) + .key("challenge_pick_ores_5k") + .title(Localizer.dLocalize("advancement.challenge_pick_ores_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_pick_ores_5k.description")) + .model(CustomModel.get(Material.DIAMOND_ORE, "advancement", "pickaxe", "challenge_pick_ores_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_pick_ores_500", "pickaxe.ores", 500, getConfig().emeraldBonus); + registerMilestone("challenge_pick_ores_5k", "pickaxe.ores", 5000, getConfig().emeraldBonus * 2); + + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + Player p = e.getDamager() instanceof Player ? (Player) e.getDamager() : null; + if (!getConfig().getXpForAttackingWithTools || p == null) { + return; + } + + shouldReturnForPlayer(p, () -> { + if (checkValidEntity(e.getEntity().getType())) { + AdaptPlayer a = getPlayer(p); + ItemStack hand = p.getInventory().getItemInMainHand(); + if (isPickaxe(hand)) { + a.getData().addStat("pickaxe.swings", 1); + a.getData().addStat("pickaxe.damage", e.getDamage()); + handleCooldown(p, () -> xp(p, e.getEntity().getLocation(), getConfig().damageXPMultiplier * e.getDamage())); + } + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(p, () -> { + ItemStack mainHand = p.getInventory().getItemInMainHand(); + + if (isPickaxe(mainHand)) { + Material blockType = e.getBlock().getType(); + double blockValue = getValue(blockType); + AdaptPlayer adaptPlayer = getPlayer(p); + + adaptPlayer.getData().addStat("pickaxe.blocks.broken", 1); + adaptPlayer.getData().addStat("pickaxe.blocks.value", blockValue); + if (blockType.name().contains("_ORE")) { + adaptPlayer.getData().addStat("pickaxe.ores", 1); + } + + handleCooldown(p, () -> { + if (XpProvenance.breakXpMultiplier(e.getBlock()) <= 0) { + return; + } + if (mainHand.getEnchantments().containsKey(Enchantment.SILK_TOUCH)) { + xp(p, 5); + } else { + Location blockLocation = e.getBlock().getLocation().clone().add(0.5, 0.5, 0.5); + xp(p, blockLocation, blockXP(e.getBlock(), blockValue)); + } + }); + } + }); + } + + public double getValue(Material type) { + Config c = getConfig(); + double value = super.getValue(type) * c.blockValueMultiplier; + value += Math.min(c.maxHardnessBonus, type.getHardness()); + value += Math.min(c.maxBlastResistanceBonus, type.getBlastResistance()); + + value += switch (type) { + case COAL_ORE -> c.coalBonus; + case COPPER_ORE -> c.copperBonus; + case IRON_ORE -> c.ironBonus; + case GOLD_ORE -> c.goldBonus; + case LAPIS_ORE -> c.lapisBonus; + case DIAMOND_ORE -> c.diamondBonus; + case EMERALD_ORE -> c.emeraldBonus; + case NETHER_GOLD_ORE -> c.netherGoldBonus; + case NETHER_QUARTZ_ORE -> c.netherQuartzBonus; + case REDSTONE_ORE -> c.redstoneBonus; + case ANCIENT_DEBRIS -> c.debrisBonus; + case DEEPSLATE_COAL_ORE -> c.coalBonus * c.deepslateMultiplier; + case DEEPSLATE_COPPER_ORE -> c.copperBonus * c.deepslateMultiplier; + case DEEPSLATE_IRON_ORE -> c.ironBonus * c.deepslateMultiplier; + case DEEPSLATE_GOLD_ORE -> c.goldBonus * c.deepslateMultiplier; + case DEEPSLATE_LAPIS_ORE -> c.lapisBonus * c.deepslateMultiplier; + case DEEPSLATE_DIAMOND_ORE -> c.diamondBonus * c.deepslateMultiplier; + case DEEPSLATE_EMERALD_ORE -> c.emeraldBonus * c.deepslateMultiplier; + case DEEPSLATE_REDSTONE_ORE -> c.redstoneBonus * c.deepslateMultiplier; + default -> 0; + }; + + return value * 0.48; + } + + + private void handleCooldown(Player p, Runnable action) { + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + action.run(); + } + + @Override + public void onTick() { + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Debris Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double debrisBonus = 210; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&6"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Get Xp For Attacking With Tools for the Pickaxes skill.", impact = "True enables this behavior and false disables it.") + boolean getXpForAttackingWithTools = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage XPMultiplier for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageXPMultiplier = 6.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Block Value Multiplier for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double blockValueMultiplier = 0.125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Hardness Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxHardnessBonus = 9; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Max Blast Resistance Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double maxBlastResistanceBonus = 10; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Coal Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double coalBonus = 18; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Iron Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double ironBonus = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Redstone Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double redstoneBonus = 55; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Copper Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double copperBonus = 22; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Gold Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double goldBonus = 38; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Lapis Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lapisBonus = 75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Diamond Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double diamondBonus = 175; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Emerald Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double emeraldBonus = 210; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Nether Gold Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double netherGoldBonus = 105; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Nether Quartz Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double netherQuartzBonus = 125; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Deepslate Multiplier for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double deepslateMultiplier = 1.35; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillRanged.java b/src/main/java/art/arcane/adapt/content/skill/SkillRanged.java new file mode 100644 index 000000000..441714b86 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillRanged.java @@ -0,0 +1,288 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.content.adaptation.ranged.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Snowball; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public class SkillRanged extends SimpleSkill { + private final Map cooldowns; + + public SkillRanged() { + super("ranged", Localizer.dLocalize("skill.ranged.icon")); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("skill.ranged.description")); + setDisplayName(Localizer.dLocalize("skill.ranged.name")); + setColor(C.DARK_GREEN); + setInterval(3044); + registerAdaptation(new RangedForce()); + registerAdaptation(new RangedPiercing()); + registerAdaptation(new RangedArrowRecovery()); + registerAdaptation(new RangedLungeShot()); + registerAdaptation(new RangedWebBomb()); + registerAdaptation(new RangedTrajectorySight()); + registerAdaptation(new RangedFloaters()); + registerAdaptation(new RangedPinningShot()); + registerAdaptation(new RangedRicochetBolt()); + setIcon(Material.CROSSBOW); + cooldowns = new HashMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ARROW) + .key("challenge_ranged_100") + .title(Localizer.dLocalize("advancement.challenge_ranged_100.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_100.description")) + .model(CustomModel.get(Material.ARROW, "advancement", "ranged", "challenge_ranged_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_ranged_1k") + .title(Localizer.dLocalize("advancement.challenge_ranged_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_1k.description")) + .model(CustomModel.get(Material.SPECTRAL_ARROW, "advancement", "ranged", "challenge_ranged_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CROSSBOW) + .key("challenge_ranged_10k") + .title(Localizer.dLocalize("advancement.challenge_ranged_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_10k.description")) + .model(CustomModel.get(Material.CROSSBOW, "advancement", "ranged", "challenge_ranged_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_ranged_100", "ranged.shotsfired", 100, getConfig().challengeRangedReward); + registerMilestone("challenge_ranged_1k", "ranged.shotsfired", 1000, getConfig().challengeRangedReward * 2); + registerMilestone("challenge_ranged_10k", "ranged.shotsfired", 10000, getConfig().challengeRangedReward * 5); + + // Chain 2 - Ranged Damage + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BOW) + .key("challenge_ranged_dmg_1k") + .title(Localizer.dLocalize("advancement.challenge_ranged_dmg_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_dmg_1k.description")) + .model(CustomModel.get(Material.BOW, "advancement", "ranged", "challenge_ranged_dmg_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CROSSBOW) + .key("challenge_ranged_dmg_10k") + .title(Localizer.dLocalize("advancement.challenge_ranged_dmg_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_dmg_10k.description")) + .model(CustomModel.get(Material.CROSSBOW, "advancement", "ranged", "challenge_ranged_dmg_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_ranged_dmg_1k", "ranged.damage", 1000, getConfig().challengeRangedDmgReward); + registerMilestone("challenge_ranged_dmg_10k", "ranged.damage", 10000, getConfig().challengeRangedDmgReward * 3); + + // Chain 3 - Ranged Distance + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ARROW) + .key("challenge_ranged_dist_5k") + .title(Localizer.dLocalize("advancement.challenge_ranged_dist_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_dist_5k.description")) + .model(CustomModel.get(Material.ARROW, "advancement", "ranged", "challenge_ranged_dist_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SPECTRAL_ARROW) + .key("challenge_ranged_dist_50k") + .title(Localizer.dLocalize("advancement.challenge_ranged_dist_50k.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_dist_50k.description")) + .model(CustomModel.get(Material.SPECTRAL_ARROW, "advancement", "ranged", "challenge_ranged_dist_50k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_ranged_dist_5k", "ranged.distance", 5000, getConfig().challengeRangedDistReward); + registerMilestone("challenge_ranged_dist_50k", "ranged.distance", 50000, getConfig().challengeRangedDistReward * 3); + + // Chain 4 - Ranged Kills + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TIPPED_ARROW) + .key("challenge_ranged_kills_50") + .title(Localizer.dLocalize("advancement.challenge_ranged_kills_50.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_kills_50.description")) + .model(CustomModel.get(Material.TIPPED_ARROW, "advancement", "ranged", "challenge_ranged_kills_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TARGET) + .key("challenge_ranged_kills_500") + .title(Localizer.dLocalize("advancement.challenge_ranged_kills_500.title")) + .description(Localizer.dLocalize("advancement.challenge_ranged_kills_500.description")) + .model(CustomModel.get(Material.TARGET, "advancement", "ranged", "challenge_ranged_kills_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_ranged_kills_50", "ranged.kills", 50, getConfig().challengeRangedKillsReward); + registerMilestone("challenge_ranged_kills_500", "ranged.kills", 500, getConfig().challengeRangedKillsReward * 3); + + // Chain 5 - Longshots + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SPYGLASS) + .key("challenge_longshot_25") + .title(Localizer.dLocalize("advancement.challenge_longshot_25.title")) + .description(Localizer.dLocalize("advancement.challenge_longshot_25.description")) + .model(CustomModel.get(Material.SPYGLASS, "advancement", "ranged", "challenge_longshot_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_longshot_250") + .title(Localizer.dLocalize("advancement.challenge_longshot_250.title")) + .description(Localizer.dLocalize("advancement.challenge_longshot_250.description")) + .model(CustomModel.get(Material.ENDER_EYE, "advancement", "ranged", "challenge_longshot_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_longshot_25", "ranged.longshots", 25, getConfig().challengeRangedLongshotReward); + registerMilestone("challenge_longshot_250", "ranged.longshots", 250, getConfig().challengeRangedLongshotReward * 3); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(ProjectileLaunchEvent e) { + if (!(e.getEntity().getShooter() instanceof Player p)) { + return; + } + shouldReturnForPlayer(p, e, () -> { + if (e.getEntity() instanceof Snowball || e.getEntity().getType().name().toLowerCase(Locale.ROOT).contains("hook")) { + return; // Ignore snowballs and fishing hooks + } + + getPlayer(p).getData().addStat("ranged.shotsfired", 1); + getPlayer(p).getData().addStat("ranged.shotsfired." + e.getEntity().getType().name().toLowerCase(Locale.ROOT), 1); + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + xp(p, getConfig().shootXP); + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Projectile) || !(((Projectile) e.getDamager()).getShooter() instanceof Player p) || !checkValidEntity(e.getEntity().getType())) { + return; + } + shouldReturnForPlayer(p, e, () -> { + if (e.getEntity() instanceof Snowball || e.getEntity() instanceof FishHook) { + return; // Ignore snowballs and fishing hooks + } + if (e.getEntity().getLocation().getWorld().equals(p.getLocation().getWorld())) { + double distance = e.getEntity().getLocation().distance(p.getLocation()); + getPlayer(p).getData().addStat("ranged.distance", distance); + getPlayer(p).getData().addStat("ranged.distance." + e.getDamager().getType().name().toLowerCase(Locale.ROOT), distance); + if (distance > 30) { + getPlayer(p).getData().addStat("ranged.longshots", 1); + } + } + getPlayer(p).getData().addStat("ranged.damage", e.getDamage()); + getPlayer(p).getData().addStat("ranged.damage." + e.getDamager().getType().name().toLowerCase(Locale.ROOT), e.getDamage()); + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + xp(p, e.getEntity().getLocation(), (getConfig().hitDamageXPMultiplier * e.getDamage()) + (e.getEntity().getLocation().distance(p.getLocation()) * getConfig().hitDistanceXPMultiplier)); + + }); + } + + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + Player p = e.getEntity().getKiller(); + if (p == null) { + return; + } + shouldReturnForPlayer(p, () -> { + ItemStack hand = p.getInventory().getItemInMainHand(); + if (hand.getType() == Material.BOW || hand.getType() == Material.CROSSBOW) { + getPlayer(p).getData().addStat("ranged.kills", 1); + } + }); + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&2"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Shoot XP for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double shootXP = 5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hit Damage XPMultiplier for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hitDamageXPMultiplier = 1.75; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Hit Distance XPMultiplier for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double hitDistanceXPMultiplier = 1.2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeRangedReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Damage Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeRangedDmgReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Distance Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeRangedDistReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Kills Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeRangedKillsReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Longshot Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeRangedLongshotReward = 500; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillRift.java b/src/main/java/art/arcane/adapt/content/skill/SkillRift.java new file mode 100644 index 000000000..ffcbd9904 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillRift.java @@ -0,0 +1,328 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.content.adaptation.chronos.ChronosInstantRecall; +import art.arcane.adapt.content.adaptation.rift.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.reflect.registries.Attributes; +import art.arcane.adapt.util.reflect.registries.EntityTypes; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.math.M; +import lombok.NoArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; + +import java.util.UUID; + +public class SkillRift extends SimpleSkill { + private final KMap lasttp; + + public SkillRift() { + super("rift", Localizer.dLocalize("skill.rift.icon")); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("skill.rift.description")); + setDisplayName(Localizer.dLocalize("skill.rift.name")); + setColor(C.DARK_PURPLE); + setInterval(1154); + setIcon(Material.ENDER_EYE); + registerAdaptation(new RiftResist()); + registerAdaptation(new RiftAccess()); + registerAdaptation(new RiftEnderchest()); + registerAdaptation(new RiftGate()); + registerAdaptation(new RiftBlink()); + registerAdaptation(new RiftDescent()); + registerAdaptation(new RiftVisage()); + registerAdaptation(new RiftEnderTaglock()); + registerAdaptation(new RiftInflatedPocketDimension()); + registerAdaptation(new RiftVoidMagnet()); + lasttp = new KMap<>(); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_50") + .title(Localizer.dLocalize("advancement.challenge_rift_50.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_50.description")) + .model(CustomModel.get(Material.ENDER_PEARL, "advancement", "rift", "challenge_rift_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_rift_500") + .title(Localizer.dLocalize("advancement.challenge_rift_500.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_500.description")) + .model(CustomModel.get(Material.ENDER_EYE, "advancement", "rift", "challenge_rift_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.END_CRYSTAL) + .key("challenge_rift_5k") + .title(Localizer.dLocalize("advancement.challenge_rift_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_5k.description")) + .model(CustomModel.get(Material.END_CRYSTAL, "advancement", "rift", "challenge_rift_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_rift_50", "rift.teleports", 50, getConfig().challengeRiftReward); + registerMilestone("challenge_rift_500", "rift.teleports", 500, getConfig().challengeRiftReward * 2); + registerMilestone("challenge_rift_5k", "rift.teleports", 5000, getConfig().challengeRiftReward * 5); + + // Chain 2 - Ender Pearl Throws + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_pearls_50") + .title(Localizer.dLocalize("advancement.challenge_rift_pearls_50.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_pearls_50.description")) + .model(CustomModel.get(Material.ENDER_PEARL, "advancement", "rift", "challenge_rift_pearls_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENDER_EYE) + .key("challenge_rift_pearls_500") + .title(Localizer.dLocalize("advancement.challenge_rift_pearls_500.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_pearls_500.description")) + .model(CustomModel.get(Material.ENDER_EYE, "advancement", "rift", "challenge_rift_pearls_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_pearls_50", "rift.ender.pearls", 50, getConfig().challengeRiftReward); + registerMilestone("challenge_rift_pearls_500", "rift.ender.pearls", 500, getConfig().challengeRiftReward * 2); + + // Chain 3 - Enderman Damage + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ENDER_PEARL) + .key("challenge_rift_enderman_50") + .title(Localizer.dLocalize("advancement.challenge_rift_enderman_50.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_enderman_50.description")) + .model(CustomModel.get(Material.ENDER_PEARL, "advancement", "rift", "challenge_rift_enderman_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.END_STONE) + .key("challenge_rift_enderman_500") + .title(Localizer.dLocalize("advancement.challenge_rift_enderman_500.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_enderman_500.description")) + .model(CustomModel.get(Material.END_STONE, "advancement", "rift", "challenge_rift_enderman_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_enderman_50", "rift.enderman.kills", 50, getConfig().challengeRiftReward); + registerMilestone("challenge_rift_enderman_500", "rift.enderman.kills", 500, getConfig().challengeRiftReward * 2); + + // Chain 4 - Dragon Damage + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DRAGON_BREATH) + .key("challenge_rift_dragon_500") + .title(Localizer.dLocalize("advancement.challenge_rift_dragon_500.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_dragon_500.description")) + .model(CustomModel.get(Material.DRAGON_BREATH, "advancement", "rift", "challenge_rift_dragon_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DRAGON_HEAD) + .key("challenge_rift_dragon_5k") + .title(Localizer.dLocalize("advancement.challenge_rift_dragon_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_dragon_5k.description")) + .model(CustomModel.get(Material.DRAGON_HEAD, "advancement", "rift", "challenge_rift_dragon_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_dragon_500", "rift.dragon.damage", 500, getConfig().challengeRiftReward); + registerMilestone("challenge_rift_dragon_5k", "rift.dragon.damage", 5000, getConfig().challengeRiftReward * 2); + + // Chain 5 - End Crystal Destruction + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.END_CRYSTAL) + .key("challenge_rift_crystal_10") + .title(Localizer.dLocalize("advancement.challenge_rift_crystal_10.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_crystal_10.description")) + .model(CustomModel.get(Material.END_CRYSTAL, "advancement", "rift", "challenge_rift_crystal_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BEACON) + .key("challenge_rift_crystal_100") + .title(Localizer.dLocalize("advancement.challenge_rift_crystal_100.title")) + .description(Localizer.dLocalize("advancement.challenge_rift_crystal_100.description")) + .model(CustomModel.get(Material.BEACON, "advancement", "rift", "challenge_rift_crystal_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_rift_crystal_10", "rift.crystals.destroyed", 10, getConfig().challengeRiftReward); + registerMilestone("challenge_rift_crystal_100", "rift.crystals.destroyed", 100, getConfig().challengeRiftReward * 2); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerTeleportEvent e) { + Player p = e.getPlayer(); + if (ChronosInstantRecall.isRecallTeleportSuppressed(p)) { + return; + } + + shouldReturnForPlayer(e.getPlayer(), e, () -> { + getPlayer(p).getData().addStat("rift.teleports", 1); + if (!lasttp.containsKey(p.getUniqueId())) { + xpSilent(p, getConfig().teleportXP, "rift:teleport"); + lasttp.put(p.getUniqueId(), M.ms()); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(ProjectileLaunchEvent e) { + if (!(e.getEntity().getShooter() instanceof Player p)) { + return; + } + shouldReturnForPlayer(p, e, () -> { + if (e.getEntity() instanceof EnderPearl) { + xp(p, getConfig().throwEnderpearlXP, "rift:throw:ender-pearl"); + getPlayer(p).getData().addStat("rift.ender.pearls", 1); + } else if (e.getEntity() instanceof EnderSignal) { + xp(p, getConfig().throwEnderEyeXP, "rift:throw:ender-eye"); + } + }); + } + + private void handleEntityDamageByEntity(Entity entity, Player p, double damage) { + if (entity instanceof LivingEntity living) { + art.arcane.adapt.api.version.IAttribute attribute = Version.get().getAttribute(living, Attributes.GENERIC_MAX_HEALTH); + double baseHealth = attribute == null ? 1 : attribute.getBaseValue(); + double multiplier = switch (entity.getType()) { + case ENDERMAN -> getConfig().damageEndermanXPMultiplier; + case ENDERMITE -> getConfig().damageEndermiteXPMultiplier; + case ENDER_DRAGON -> getConfig().damageEnderdragonXPMultiplier; + default -> 0; + }; + double xp = multiplier * Math.min(damage, baseHealth); + String rewardKey = switch (entity.getType()) { + case ENDERMAN -> "rift:damage:enderman"; + case ENDERMITE -> "rift:damage:endermite"; + case ENDER_DRAGON -> "rift:damage:ender-dragon"; + default -> "rift:damage:other"; + }; + if (xp > 0) xp(p, xp, rewardKey); + if (entity.getType() == EntityType.ENDERMAN) { + getPlayer(p).getData().addStat("rift.enderman.kills", 1); + } else if (entity.getType() == EntityType.ENDER_DRAGON) { + getPlayer(p).getData().addStat("rift.dragon.damage", damage); + } + } else if (entity.getType() == EntityTypes.ENDER_CRYSTAL) { + xp(p, getConfig().damageEndCrystalXP, "rift:damage:end-crystal"); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p) { + shouldReturnForPlayer(p, e, () -> handleEntityDamageByEntity(e.getEntity(), p, e.getDamage())); + } else if (e.getDamager() instanceof Projectile j && j.getShooter() instanceof Player p) { + shouldReturnForPlayer(p, e, () -> handleEntityDamageByEntity(e.getEntity(), p, e.getDamage())); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + if (e.getEntity() instanceof EnderCrystal && e.getEntity().getKiller() != null) { + Player p = e.getEntity().getKiller(); + shouldReturnForPlayer(p, () -> { + xp(e.getEntity().getKiller(), getConfig().destroyEndCrystalXP, "rift:kill:end-crystal"); + getPlayer(p).getData().addStat("rift.crystals.destroyed", 1); + }); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerQuitEvent e) { + Player p = e.getPlayer(); + lasttp.remove(p.getUniqueId()); + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + for (UUID playerId : lasttp.k()) { + Player player = Bukkit.getPlayer(playerId); + if (player == null || !player.isOnline()) { + lasttp.remove(playerId); + continue; + } + + shouldReturnForPlayer(player, () -> { + if (M.ms() - lasttp.get(playerId) > getConfig().teleportXPCooldown) { + lasttp.remove(playerId); + } + }); + } + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&5"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Destroy End Crystal XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double destroyEndCrystalXP = 250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage End Crystal XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageEndCrystalXP = 110; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Enderman XPMultiplier for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageEndermanXPMultiplier = 4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Endermite XPMultiplier for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageEndermiteXPMultiplier = 2; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Enderdragon XPMultiplier for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageEnderdragonXPMultiplier = 8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Throw Enderpearl XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double throwEnderpearlXP = 65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Throw Ender Eye XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double throwEnderEyeXP = 30; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Teleport XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double teleportXP = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Teleport XPCooldown for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double teleportXPCooldown = 60000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Rift Reward for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeRiftReward = 500; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillSeaborne.java b/src/main/java/art/arcane/adapt/content/skill/SkillSeaborne.java new file mode 100644 index 000000000..84bb6339b --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillSeaborne.java @@ -0,0 +1,310 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.seaborrne.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.reflect.registries.Attributes; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Trident; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.player.PlayerFishEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillSeaborne extends SimpleSkill { + private final Map cooldowns; + + public SkillSeaborne() { + super("seaborne", Localizer.dLocalize("skill.seaborne.icon")); + registerConfiguration(Config.class); + setColor(C.BLUE); + setDescription(Localizer.dLocalize("skill.seaborne.description")); + setDisplayName(Localizer.dLocalize("skill.seaborne.name")); + setInterval(2120); + setIcon(Material.TRIDENT); + registerAdaptation(new SeaborneOxygen()); + registerAdaptation(new SeaborneSpeed()); + registerAdaptation(new SeaborneFishersFantasy()); + registerAdaptation(new SeaborneTurtlesVision()); + registerAdaptation(new SeaborneTurtlesMiningSpeed()); + registerAdaptation(new SeaborneTidecaller()); + registerAdaptation(new SeabornePressureDiver()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TURTLE_HELMET) + .key("challenge_swim_1nm") + .title(Localizer.dLocalize("advancement.challenge_swim_1nm.title")) + .description(Localizer.dLocalize("advancement.challenge_swim_1nm.description")) + .model(CustomModel.get(Material.TURTLE_HELMET, "advancement", "seaborne", "challenge_swim_1nm")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.HEART_OF_THE_SEA) + .key("challenge_swim_5k") + .title(Localizer.dLocalize("advancement.challenge_swim_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_swim_5k.description")) + .model(CustomModel.get(Material.HEART_OF_THE_SEA, "advancement", "seaborne", "challenge_swim_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TRIDENT) + .key("challenge_swim_20k") + .title(Localizer.dLocalize("advancement.challenge_swim_20k.title")) + .description(Localizer.dLocalize("advancement.challenge_swim_20k.description")) + .model(CustomModel.get(Material.TRIDENT, "advancement", "seaborne", "challenge_swim_20k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_swim_1nm", "move.swim", 1852, getConfig().challengeSwim1nmReward); + registerMilestone("challenge_swim_5k", "move.swim", 5000, getConfig().challengeSwim5kReward); + registerMilestone("challenge_swim_20k", "move.swim", 20000, getConfig().challengeSwim20kReward); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FISHING_ROD) + .key("challenge_fish_25") + .title(Localizer.dLocalize("advancement.challenge_fish_25.title")) + .description(Localizer.dLocalize("advancement.challenge_fish_25.description")) + .model(CustomModel.get(Material.FISHING_ROD, "advancement", "seaborne", "challenge_fish_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TROPICAL_FISH) + .key("challenge_fish_250") + .title(Localizer.dLocalize("advancement.challenge_fish_250.title")) + .description(Localizer.dLocalize("advancement.challenge_fish_250.description")) + .model(CustomModel.get(Material.TROPICAL_FISH, "advancement", "seaborne", "challenge_fish_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_fish_25", "seaborne.fish.caught", 25, getConfig().challengeSwim1nmReward); + registerMilestone("challenge_fish_250", "seaborne.fish.caught", 250, getConfig().challengeSwim1nmReward); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ROTTEN_FLESH) + .key("challenge_drowned_25") + .title(Localizer.dLocalize("advancement.challenge_drowned_25.title")) + .description(Localizer.dLocalize("advancement.challenge_drowned_25.description")) + .model(CustomModel.get(Material.ROTTEN_FLESH, "advancement", "seaborne", "challenge_drowned_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.TRIDENT) + .key("challenge_drowned_250") + .title(Localizer.dLocalize("advancement.challenge_drowned_250.title")) + .description(Localizer.dLocalize("advancement.challenge_drowned_250.description")) + .model(CustomModel.get(Material.TRIDENT, "advancement", "seaborne", "challenge_drowned_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_drowned_25", "seaborne.drowned.kills", 25, getConfig().challengeSwim1nmReward); + registerMilestone("challenge_drowned_250", "seaborne.drowned.kills", 250, getConfig().challengeSwim1nmReward); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.PRISMARINE_SHARD) + .key("challenge_guardian_10") + .title(Localizer.dLocalize("advancement.challenge_guardian_10.title")) + .description(Localizer.dLocalize("advancement.challenge_guardian_10.description")) + .model(CustomModel.get(Material.PRISMARINE_SHARD, "advancement", "seaborne", "challenge_guardian_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.SEA_LANTERN) + .key("challenge_guardian_100") + .title(Localizer.dLocalize("advancement.challenge_guardian_100.title")) + .description(Localizer.dLocalize("advancement.challenge_guardian_100.description")) + .model(CustomModel.get(Material.SEA_LANTERN, "advancement", "seaborne", "challenge_guardian_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_guardian_10", "seaborne.guardian.kills", 10, getConfig().challengeSwim1nmReward); + registerMilestone("challenge_guardian_100", "seaborne.guardian.kills", 100, getConfig().challengeSwim1nmReward); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.PRISMARINE) + .key("challenge_underwater_blocks_100") + .title(Localizer.dLocalize("advancement.challenge_underwater_blocks_100.title")) + .description(Localizer.dLocalize("advancement.challenge_underwater_blocks_100.description")) + .model(CustomModel.get(Material.PRISMARINE, "advancement", "seaborne", "challenge_underwater_blocks_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CONDUIT) + .key("challenge_underwater_blocks_1k") + .title(Localizer.dLocalize("advancement.challenge_underwater_blocks_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_underwater_blocks_1k.description")) + .model(CustomModel.get(Material.CONDUIT, "advancement", "seaborne", "challenge_underwater_blocks_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_underwater_blocks_100", "seaborne.underwater.blocks", 100, getConfig().challengeSwim1nmReward); + registerMilestone("challenge_underwater_blocks_1k", "seaborne.underwater.blocks", 1000, getConfig().challengeSwim1nmReward); + cooldowns = new HashMap<>(); + } + + private boolean isOnCooldown(Player p, long cooldown) { + Long lastCooldown = cooldowns.get(p.getUniqueId()); + return lastCooldown != null && lastCooldown + cooldown > System.currentTimeMillis(); + } + + private void setCooldown(Player p) { + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player i = adaptPlayer.getPlayer(); + shouldReturnForPlayer(i, () -> { + if ((i.isInWater() || i.isSwimming()) && i.getRemainingAir() < i.getMaximumAir()) { + Adapt.verbose("seaborne Tick"); + checkStatTrackers(adaptPlayer); + xpSilent(i, getConfig().swimXP, "seaborne:swim"); + } + }); + + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerFishEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (e.getState().equals(PlayerFishEvent.State.CAUGHT_FISH)) { + getPlayer(p).getData().addStat("seaborne.fish.caught", 1); + xp(p, 250); + } else if (e.getState().equals(PlayerFishEvent.State.CAUGHT_ENTITY)) { + xp(p, 10); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(BlockBreakEvent e) { + Player p = e.getPlayer(); + shouldReturnForPlayer(e.getPlayer(), e, () -> { + if (isOnCooldown(p, getConfig().seaPickleCooldown)) { + return; + } + setCooldown(p); + if (p.isSwimming() || p.isInWater()) { + getPlayer(p).getData().addStat("seaborne.underwater.blocks", 1); + } + if (e.getBlock().getType().equals(Material.SEA_PICKLE) && p.isSwimming() && p.getRemainingAir() < p.getMaximumAir()) { // BECAUSE I LIKE PICKLES + xpSilent(p, 10, "seaborne:sea-pickle"); + } else { + xpSilent(p, 3, "seaborne:underwater-block"); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + Player p = e.getEntity().getKiller(); + if (p == null || !p.getClass().getSimpleName().equals("CraftPlayer")) { + return; + } + shouldReturnForPlayer(p, () -> { + if (e.getEntityType() == EntityType.DROWNED) { + getPlayer(p).getData().addStat("seaborne.drowned.kills", 1); + } else if (e.getEntityType() == EntityType.GUARDIAN || e.getEntityType() == EntityType.ELDER_GUARDIAN) { + getPlayer(p).getData().addStat("seaborne.guardian.kills", 1); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof LivingEntity entity)) + return; + + if (e.getEntity().getType() == EntityType.DROWNED && e.getDamager() instanceof Player p) { + shouldReturnForPlayer(p, e, () -> { + if (isOnCooldown(p, getConfig().seaPickleCooldown)) { + return; + } + setCooldown(p); + xp(p, getConfig().damagedrownxpmultiplier * Math.min(e.getDamage(), getBaseHealth(entity))); + }); + } else if (e.getDamager().getType() == EntityType.TRIDENT) { + org.bukkit.projectiles.ProjectileSource shooter = ((Trident) e.getDamager()).getShooter(); + if (shooter instanceof Player p) { + shouldReturnForPlayer(p, e, () -> xp(p, getConfig().tridentxpmultiplier * Math.min(e.getDamage(), getBaseHealth(entity)))); + } + } else if (e.getDamager() instanceof Player p) { + if (p.getInventory().getItemInMainHand().getType().equals(Material.TRIDENT)) { + shouldReturnForPlayer(p, e, () -> xp(p, getConfig().tridentxpmultiplier * Math.min(e.getDamage(), getBaseHealth(entity)))); + } + } + } + + private double getBaseHealth(LivingEntity entity) { + art.arcane.adapt.api.version.IAttribute attribute = Version.get().getAttribute(entity, Attributes.GENERIC_MAX_HEALTH); + return attribute == null ? 0 : attribute.getBaseValue(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sea Pickle Cooldown for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public long seaPickleCooldown = 60000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Tridentxpmultiplier for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double tridentxpmultiplier = 4.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damagedrownxpmultiplier for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damagedrownxpmultiplier = 3; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&9"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Swim1nm Reward for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSwim1nmReward = 750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Swim5k Reward for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSwim5kReward = 1500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Swim20k Reward for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSwim20kReward = 3750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Swim XP for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double swimXP = 0.4; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillStealth.java b/src/main/java/art/arcane/adapt/content/skill/SkillStealth.java new file mode 100644 index 000000000..a86ac12cc --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillStealth.java @@ -0,0 +1,283 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.excavation.ExcavationGraveDigger; +import art.arcane.adapt.content.adaptation.stealth.*; +import art.arcane.adapt.content.adaptation.tragoul.TragoulSkeletalServant; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillStealth extends SimpleSkill { + private final Map cooldowns; + + public SkillStealth() { + super("stealth", Localizer.dLocalize("skill.stealth.icon")); + registerConfiguration(Config.class); + setColor(C.DARK_GRAY); + setInterval(1412); + setIcon(Material.WITHER_ROSE); + cooldowns = new HashMap<>(); + setDescription(Localizer.dLocalize("skill.stealth.description")); + setDisplayName(Localizer.dLocalize("skill.stealth.name")); + registerAdaptation(new StealthSpeed()); + registerAdaptation(new StealthSnatch()); + registerAdaptation(new StealthGhostArmor()); + registerAdaptation(new StealthSight()); + registerAdaptation(new StealthEnderVeil()); + registerAdaptation(new StealthSilentStep()); + registerAdaptation(new StealthShadowDecoy()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEATHER_LEGGINGS) + .key("challenge_sneak_1k") + .title(Localizer.dLocalize("advancement.challenge_sneak_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_sneak_1k.description")) + .model(CustomModel.get(Material.LEATHER_LEGGINGS, "advancement", "stealth", "challenge_sneak_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CHAINMAIL_LEGGINGS) + .key("challenge_sneak_5k") + .title(Localizer.dLocalize("advancement.challenge_sneak_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_sneak_5k.description")) + .model(CustomModel.get(Material.CHAINMAIL_LEGGINGS, "advancement", "stealth", "challenge_sneak_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_LEGGINGS) + .key("challenge_sneak_20k") + .title(Localizer.dLocalize("advancement.challenge_sneak_20k.title")) + .description(Localizer.dLocalize("advancement.challenge_sneak_20k.description")) + .model(CustomModel.get(Material.NETHERITE_LEGGINGS, "advancement", "stealth", "challenge_sneak_20k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_sneak_1k", "move.sneak", 1000, getConfig().challengeSneak1kReward); + registerMilestone("challenge_sneak_5k", "move.sneak", 5000, getConfig().challengeSneak5kReward); + registerMilestone("challenge_sneak_20k", "move.sneak", 20000, getConfig().challengeSneak20kReward); + + // Chain 2 - Stealth Damage While Sneaking + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.STONE_SWORD) + .key("challenge_stealth_dmg_500") + .title(Localizer.dLocalize("advancement.challenge_stealth_dmg_500.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_dmg_500.description")) + .model(CustomModel.get(Material.STONE_SWORD, "advancement", "stealth", "challenge_stealth_dmg_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_stealth_dmg_5k") + .title(Localizer.dLocalize("advancement.challenge_stealth_dmg_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_dmg_5k.description")) + .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "stealth", "challenge_stealth_dmg_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_stealth_dmg_500", "stealth.damage.sneaking", 500, getConfig().challengeStealthDmg500Reward); + registerMilestone("challenge_stealth_dmg_5k", "stealth.damage.sneaking", 5000, getConfig().challengeStealthDmg5kReward); + + // Chain 3 - Stealth Kills While Sneaking + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SKELETON_SKULL) + .key("challenge_stealth_kills_10") + .title(Localizer.dLocalize("advancement.challenge_stealth_kills_10.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_kills_10.description")) + .model(CustomModel.get(Material.SKELETON_SKULL, "advancement", "stealth", "challenge_stealth_kills_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.WITHER_ROSE) + .key("challenge_stealth_kills_100") + .title(Localizer.dLocalize("advancement.challenge_stealth_kills_100.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_kills_100.description")) + .model(CustomModel.get(Material.WITHER_ROSE, "advancement", "stealth", "challenge_stealth_kills_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_stealth_kills_10", "stealth.kills.sneaking", 10, getConfig().challengeStealthKills10Reward); + registerMilestone("challenge_stealth_kills_100", "stealth.kills.sneaking", 100, getConfig().challengeStealthKills100Reward); + + // Chain 4 - Stealth Time Spent Sneaking + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEATHER_BOOTS) + .key("challenge_stealth_time_1h") + .title(Localizer.dLocalize("advancement.challenge_stealth_time_1h.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_time_1h.description")) + .model(CustomModel.get(Material.LEATHER_BOOTS, "advancement", "stealth", "challenge_stealth_time_1h")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CHAINMAIL_BOOTS) + .key("challenge_stealth_time_10h") + .title(Localizer.dLocalize("advancement.challenge_stealth_time_10h.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_time_10h.description")) + .model(CustomModel.get(Material.CHAINMAIL_BOOTS, "advancement", "stealth", "challenge_stealth_time_10h")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_stealth_time_1h", "stealth.time", 3600, getConfig().challengeStealthTime1hReward); + registerMilestone("challenge_stealth_time_10h", "stealth.time", 36000, getConfig().challengeStealthTime10hReward); + + // Chain 5 - Stealth Arrows Fired While Sneaking + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BOW) + .key("challenge_stealth_arrows_50") + .title(Localizer.dLocalize("advancement.challenge_stealth_arrows_50.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_arrows_50.description")) + .model(CustomModel.get(Material.BOW, "advancement", "stealth", "challenge_stealth_arrows_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CROSSBOW) + .key("challenge_stealth_arrows_500") + .title(Localizer.dLocalize("advancement.challenge_stealth_arrows_500.title")) + .description(Localizer.dLocalize("advancement.challenge_stealth_arrows_500.description")) + .model(CustomModel.get(Material.CROSSBOW, "advancement", "stealth", "challenge_stealth_arrows_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_stealth_arrows_50", "stealth.arrows.sneaking", 50, getConfig().challengeStealthArrows50Reward); + registerMilestone("challenge_stealth_arrows_500", "stealth.arrows.sneaking", 500, getConfig().challengeStealthArrows500Reward); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p && p.isSneaking()) { + shouldReturnForPlayer(p, e, () -> { + getPlayer(p).getData().addStat("stealth.damage.sneaking", e.getDamage()); + xp(p, e.getEntity().getLocation(), e.getDamage() * getConfig().sneakCombatXPMultiplier); + }); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + if (e.getEntity().getKiller() == null) { + return; + } + + if (TragoulSkeletalServant.isServant(e.getEntity()) || ExcavationGraveDigger.isGraveMob(e.getEntity())) { + return; + } + + Player p = e.getEntity().getKiller(); + if (p.isSneaking()) { + shouldReturnForPlayer(p, () -> { + getPlayer(p).getData().addStat("stealth.kills.sneaking", 1); + xp(p, e.getEntity().getLocation(), getConfig().sneakKillXP); + }); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(ProjectileLaunchEvent e) { + if (!(e.getEntity().getShooter() instanceof Player p)) { + return; + } + if (p.isSneaking()) { + shouldReturnForPlayer(p, e, () -> { + getPlayer(p).getData().addStat("stealth.arrows.sneaking", 1); + }); + } + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { + Player i = adaptPlayer.getPlayer(); + shouldReturnForPlayer(i, () -> { + checkStatTrackers(adaptPlayer); + if (i.isSneaking() && !i.isSwimming() && !i.isSprinting() && !i.isFlying() && !i.isGliding() && (i.getGameMode().equals(GameMode.SURVIVAL) || i.getGameMode().equals(GameMode.ADVENTURE))) { + xpSilent(i, getConfig().sneakXP, "stealth:sneak"); + adaptPlayer.getData().addStat("stealth.time", 1); + } + }); + + } + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&8"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sneak1k Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSneak1kReward = 1750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sneak5k Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSneak5kReward = 3500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sneak20k Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSneak20kReward = 8750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Sneak XP for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sneakXP = 0.4; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP multiplier for dealing damage while sneaking.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sneakCombatXPMultiplier = 3.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP awarded for killing while sneaking.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double sneakKillXP = 15; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Dmg 500 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeStealthDmg500Reward = 1500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Dmg 5k Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeStealthDmg5kReward = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Kills 10 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeStealthKills10Reward = 1000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Kills 100 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeStealthKills100Reward = 5000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Time 1h Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeStealthTime1hReward = 2000; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Time 10h Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeStealthTime10hReward = 7500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Arrows 50 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeStealthArrows50Reward = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Arrows 500 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeStealthArrows500Reward = 5000; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillSwords.java b/src/main/java/art/arcane/adapt/content/skill/SkillSwords.java new file mode 100644 index 000000000..84cf9feee --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillSwords.java @@ -0,0 +1,263 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.sword.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillSwords extends SimpleSkill { + private final Map cooldowns; + + public SkillSwords() { + super("swords", Localizer.dLocalize("skill.swords.icon")); + registerConfiguration(Config.class); + setColor(C.YELLOW); + setDescription(Localizer.dLocalize("skill.swords.description")); + setDisplayName(Localizer.dLocalize("skill.swords.name")); + setInterval(2150); + setIcon(Material.DIAMOND_SWORD); + cooldowns = new HashMap<>(); + registerAdaptation(new SwordsMachete()); + registerAdaptation(new SwordsPoisonedBlade()); + registerAdaptation(new SwordsBloodyBlade()); + registerAdaptation(new SwordsDualWield()); + registerAdaptation(new SwordsExecutionersEdge()); + registerAdaptation(new SwordsRiposteWindow()); + registerAdaptation(new SwordsCrimsonCyclone()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.WOODEN_SWORD) + .key("challenge_sword_100") + .title(Localizer.dLocalize("advancement.challenge_sword_100.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_100.description")) + .model(CustomModel.get(Material.WOODEN_SWORD, "advancement", "swords", "challenge_sword_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_sword_1k") + .title(Localizer.dLocalize("advancement.challenge_sword_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_1k.description")) + .model(CustomModel.get(Material.IRON_SWORD, "advancement", "swords", "challenge_sword_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_sword_10k") + .title(Localizer.dLocalize("advancement.challenge_sword_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_10k.description")) + .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "swords", "challenge_sword_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_sword_100", "sword.hits", 100, getConfig().challengeSwordReward); + registerMilestone("challenge_sword_1k", "sword.hits", 1000, getConfig().challengeSwordReward * 2); + registerMilestone("challenge_sword_10k", "sword.hits", 10000, getConfig().challengeSwordReward * 5); + + // Chain 2 - sword.damage + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_SWORD) + .key("challenge_sword_dmg_1k") + .title(Localizer.dLocalize("advancement.challenge_sword_dmg_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_dmg_1k.description")) + .model(CustomModel.get(Material.GOLDEN_SWORD, "advancement", "swords", "challenge_sword_dmg_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_sword_dmg_10k") + .title(Localizer.dLocalize("advancement.challenge_sword_dmg_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_dmg_10k.description")) + .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "swords", "challenge_sword_dmg_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_sword_dmg_1k", "sword.damage", 1000, getConfig().challengeSwordDmgReward); + registerMilestone("challenge_sword_dmg_10k", "sword.damage", 10000, getConfig().challengeSwordDmgReward * 3); + + // Chain 3 - sword.kills + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.IRON_SWORD) + .key("challenge_sword_kills_50") + .title(Localizer.dLocalize("advancement.challenge_sword_kills_50.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_kills_50.description")) + .model(CustomModel.get(Material.IRON_SWORD, "advancement", "swords", "challenge_sword_kills_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_sword_kills_500") + .title(Localizer.dLocalize("advancement.challenge_sword_kills_500.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_kills_500.description")) + .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "swords", "challenge_sword_kills_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_sword_kills_50", "sword.kills", 50, getConfig().challengeSwordKillsReward); + registerMilestone("challenge_sword_kills_500", "sword.kills", 500, getConfig().challengeSwordKillsReward * 3); + + // Chain 4 - sword.critical + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_SWORD) + .key("challenge_sword_crit_50") + .title(Localizer.dLocalize("advancement.challenge_sword_crit_50.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_crit_50.description")) + .model(CustomModel.get(Material.GOLDEN_SWORD, "advancement", "swords", "challenge_sword_crit_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_sword_crit_500") + .title(Localizer.dLocalize("advancement.challenge_sword_crit_500.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_crit_500.description")) + .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "swords", "challenge_sword_crit_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_sword_crit_50", "sword.critical", 50, getConfig().challengeSwordCritReward); + registerMilestone("challenge_sword_crit_500", "sword.critical", 500, getConfig().challengeSwordCritReward * 3); + + // Chain 5 - sword.heavy.hits + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_sword_heavy_25") + .title(Localizer.dLocalize("advancement.challenge_sword_heavy_25.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_heavy_25.description")) + .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "swords", "challenge_sword_heavy_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHERITE_SWORD) + .key("challenge_sword_heavy_250") + .title(Localizer.dLocalize("advancement.challenge_sword_heavy_250.title")) + .description(Localizer.dLocalize("advancement.challenge_sword_heavy_250.description")) + .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "swords", "challenge_sword_heavy_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_sword_heavy_25", "sword.heavy.hits", 25, getConfig().challengeSwordHeavyReward); + registerMilestone("challenge_sword_heavy_250", "sword.heavy.hits", 250, getConfig().challengeSwordHeavyReward * 3); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Player p && checkValidEntity(e.getEntity().getType())) { + shouldReturnForPlayer(p, e, () -> { + AdaptPlayer a = getPlayer(p); + ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); + if (isSword(hand)) { + getPlayer(p).getData().addStat("sword.hits", 1); + getPlayer(p).getData().addStat("sword.damage", e.getDamage()); + if (p.getFallDistance() > 0 && !p.isOnGround()) { + getPlayer(p).getData().addStat("sword.critical", 1); + } + if (e.getDamage() > 8) { + getPlayer(p).getData().addStat("sword.heavy.hits", 1); + } + if (!isOnCooldown(p)) { + setCooldown(p); + xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().damageXPMultiplier * e.getDamage()); + } + } + }); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + if (e.getEntity().getKiller() == null) { + return; + } + Player p = e.getEntity().getKiller(); + shouldReturnForPlayer(p, () -> { + ItemStack hand = p.getInventory().getItemInMainHand(); + if (isSword(hand)) { + getPlayer(p).getData().addStat("sword.kills", 1); + } + }); + } + + private boolean isOnCooldown(Player p) { + Long cooldown = cooldowns.get(p.getUniqueId()); + return cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis(); + } + + private void setCooldown(Player p) { + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + } + + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&e"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage XPMultiplier for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageXPMultiplier = 4.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSwordReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Damage Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSwordDmgReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Kills Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSwordKillsReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Critical Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSwordCritReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Heavy Hits Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeSwordHeavyReward = 500; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillTaming.java b/src/main/java/art/arcane/adapt/content/skill/SkillTaming.java new file mode 100644 index 000000000..10c68cbdb --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillTaming.java @@ -0,0 +1,284 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.content.adaptation.excavation.ExcavationGraveDigger; +import art.arcane.adapt.content.adaptation.taming.*; +import art.arcane.adapt.content.adaptation.tragoul.TragoulSkeletalServant; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityBreedEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityTameEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillTaming extends SimpleSkill { + private final Map cooldowns; + + public SkillTaming() { + super("taming", Localizer.dLocalize("skill.taming.icon")); + registerConfiguration(Config.class); + setDescription(Localizer.dLocalize("skill.taming.description")); + setDisplayName(Localizer.dLocalize("skill.taming.name")); + setColor(C.GOLD); + setInterval(3480); + setIcon(Material.LEAD); + cooldowns = new HashMap<>(); + registerAdaptation(new TamingHealthBoost()); + registerAdaptation(new TamingDamage()); + registerAdaptation(new TamingHealthRegeneration()); + registerAdaptation(new TamingPackLeaderAura()); + registerAdaptation(new TamingBeastRecall()); + registerAdaptation(new TamingSharedPain()); + registerAdaptation(new TamingMountedTactics()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEAD) + .key("challenge_taming_10") + .title(Localizer.dLocalize("advancement.challenge_taming_10.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_10.description")) + .model(CustomModel.get(Material.LEAD, "advancement", "taming", "challenge_taming_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NAME_TAG) + .key("challenge_taming_50") + .title(Localizer.dLocalize("advancement.challenge_taming_50.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_50.description")) + .model(CustomModel.get(Material.NAME_TAG, "advancement", "taming", "challenge_taming_50")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GOLDEN_APPLE) + .key("challenge_taming_500") + .title(Localizer.dLocalize("advancement.challenge_taming_500.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_500.description")) + .model(CustomModel.get(Material.GOLDEN_APPLE, "advancement", "taming", "challenge_taming_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_pet_dmg_500") + .title(Localizer.dLocalize("advancement.challenge_pet_dmg_500.title")) + .description(Localizer.dLocalize("advancement.challenge_pet_dmg_500.description")) + .model(CustomModel.get(Material.BONE, "advancement", "taming", "challenge_pet_dmg_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.DIAMOND_SWORD) + .key("challenge_pet_dmg_5k") + .title(Localizer.dLocalize("advancement.challenge_pet_dmg_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_pet_dmg_5k.description")) + .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "taming", "challenge_pet_dmg_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.LEAD) + .key("challenge_tamed_10") + .title(Localizer.dLocalize("advancement.challenge_tamed_10.title")) + .description(Localizer.dLocalize("advancement.challenge_tamed_10.description")) + .model(CustomModel.get(Material.LEAD, "advancement", "taming", "challenge_tamed_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NAME_TAG) + .key("challenge_tamed_100") + .title(Localizer.dLocalize("advancement.challenge_tamed_100.title")) + .description(Localizer.dLocalize("advancement.challenge_tamed_100.description")) + .model(CustomModel.get(Material.NAME_TAG, "advancement", "taming", "challenge_tamed_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_pet_kills_25") + .title(Localizer.dLocalize("advancement.challenge_pet_kills_25.title")) + .description(Localizer.dLocalize("advancement.challenge_pet_kills_25.description")) + .model(CustomModel.get(Material.BONE, "advancement", "taming", "challenge_pet_kills_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.GOLDEN_APPLE) + .key("challenge_pet_kills_250") + .title(Localizer.dLocalize("advancement.challenge_pet_kills_250.title")) + .description(Localizer.dLocalize("advancement.challenge_pet_kills_250.description")) + .model(CustomModel.get(Material.GOLDEN_APPLE, "advancement", "taming", "challenge_pet_kills_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.GOLDEN_CARROT) + .key("challenge_taming_2500") + .title(Localizer.dLocalize("advancement.challenge_taming_2500.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_2500.description")) + .model(CustomModel.get(Material.GOLDEN_CARROT, "advancement", "taming", "challenge_taming_2500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ENCHANTED_GOLDEN_APPLE) + .key("challenge_taming_25k") + .title(Localizer.dLocalize("advancement.challenge_taming_25k.title")) + .description(Localizer.dLocalize("advancement.challenge_taming_25k.description")) + .model(CustomModel.get(Material.ENCHANTED_GOLDEN_APPLE, "advancement", "taming", "challenge_taming_25k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_taming_10", "taming.bred", 10, getConfig().challengeTamingReward); + registerMilestone("challenge_taming_50", "taming.bred", 50, getConfig().challengeTamingReward * 2); + registerMilestone("challenge_taming_500", "taming.bred", 500, getConfig().challengeTamingReward * 5); + registerMilestone("challenge_pet_dmg_500", "taming.pet.damage", 500, getConfig().challengePetDmgReward); + registerMilestone("challenge_pet_dmg_5k", "taming.pet.damage", 5000, getConfig().challengePetDmgReward * 5); + registerMilestone("challenge_tamed_10", "taming.tamed", 10, getConfig().challengeTamedReward); + registerMilestone("challenge_tamed_100", "taming.tamed", 100, getConfig().challengeTamedReward * 5); + registerMilestone("challenge_pet_kills_25", "taming.pet.kills", 25, getConfig().challengePetKillsReward); + registerMilestone("challenge_pet_kills_250", "taming.pet.kills", 250, getConfig().challengePetKillsReward * 5); + registerMilestone("challenge_taming_2500", "taming.bred", 2500, getConfig().challengeTamingReward * 10); + registerMilestone("challenge_taming_25k", "taming.bred", 25000, getConfig().challengeTamingReward * 25); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityBreedEvent e) { + for (Entity nearby : e.getEntity().getNearbyEntities(15, 15, 15)) { + if (!(nearby instanceof Player p)) { + continue; + } + shouldReturnForPlayer(p, e, () -> { + getPlayer(p).getData().addStat("taming.bred", 1); + if (!isOnCooldown(p)) { + setCooldown(p); + xp(p, getConfig().tameXpBase); + } + }); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (e.getDamager() instanceof Tameable tameable && tameable.isTamed() && tameable.getOwner() instanceof Player p) { + shouldReturnForPlayer(p, e, () -> { + getPlayer(p).getData().addStat("taming.pet.damage", e.getDamage()); + if (!isOnCooldown(p)) { + setCooldown(p); + xp(p, e.getEntity().getLocation(), e.getDamage() * getConfig().tameDamageXPMultiplier); + } + }); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityTameEvent e) { + if (e.getOwner() instanceof Player p) { + shouldReturnForPlayer(p, e, () -> { + getPlayer(p).getData().addStat("taming.tamed", 1); + xp(p, e.getEntity().getLocation(), getConfig().tameSuccessXP); + }); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + if (e.getEntity().getKiller() != null) { + return; + } + + if (TragoulSkeletalServant.isServant(e.getEntity()) || ExcavationGraveDigger.isGraveMob(e.getEntity())) { + return; + } + + if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent damageEvent) { + if (damageEvent.getDamager() instanceof Tameable tameable && tameable.isTamed() && tameable.getOwner() instanceof Player p) { + shouldReturnForPlayer(p, () -> { + getPlayer(p).getData().addStat("taming.pet.kills", 1); + xp(p, e.getEntity().getLocation(), getConfig().petKillXP); + }); + } + } + } + + private boolean isOnCooldown(Player p) { + Long cooldown = cooldowns.get(p.getUniqueId()); + return cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis(); + } + + private void setCooldown(Player p) { + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + } + + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&6"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Tame Xp Base for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double tameXpBase = 65; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Tame Damage XPMultiplier for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double tameDamageXPMultiplier = 8.0; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP awarded for successfully taming an animal.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double tameSuccessXP = 150; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP awarded when a pet kills a mob.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double petKillXP = 25; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Taming Reward for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeTamingReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Pet Damage Reward for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengePetDmgReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Tamed Reward for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeTamedReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Pet Kills Reward for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengePetKillsReward = 500; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillTragOul.java b/src/main/java/art/arcane/adapt/content/skill/SkillTragOul.java new file mode 100644 index 000000000..1a672de7a --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillTragOul.java @@ -0,0 +1,326 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.api.world.PlayerAdaptation; +import art.arcane.adapt.api.world.PlayerSkillLine; +import art.arcane.adapt.content.adaptation.tragoul.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.reflect.registries.Particles; +import de.slikey.effectlib.effect.CloudEffect; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.PlayerDeathEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillTragOul extends SimpleSkill { + private final Map cooldowns; + + public SkillTragOul() { + super("tragoul", Localizer.dLocalize("skill.tragoul.icon")); + registerConfiguration(Config.class); + setColor(C.AQUA); + setDescription(Localizer.dLocalize("skill.tragoul.description")); + setDisplayName(Localizer.dLocalize("skill.tragoul.name")); + setInterval(2755); + setIcon(Material.CRIMSON_ROOTS); + cooldowns = new HashMap<>(); + registerAdaptation(new TragoulThorns()); + registerAdaptation(new TragoulGlobe()); + registerAdaptation(new TragoulHealing()); + registerAdaptation(new TragoulLance()); + registerAdaptation(new TragoulBloodPact()); + registerAdaptation(new TragoulBoneHarvest()); + registerAdaptation(new TragoulCorpseExplosion()); + registerAdaptation(new TragoulSoulSiphon()); + registerAdaptation(new TragoulSkeletalServant()); + registerAdaptation(new TragoulMarrowArmor()); + registerAdaptation(new TragoulCurseOfFrailty()); + registerAdaptation(new TragoulDeathSense()); + registerAdaptation(new TragoulPlagueBearer()); + registerAdaptation(new TragoulLastRites()); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.CRIMSON_ROOTS) + .key("challenge_trag_1k") + .title(Localizer.dLocalize("advancement.challenge_trag_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_1k.description")) + .model(CustomModel.get(Material.CRIMSON_ROOTS, "advancement", "tragoul", "challenge_trag_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.CRIMSON_STEM) + .key("challenge_trag_10k") + .title(Localizer.dLocalize("advancement.challenge_trag_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_10k.description")) + .model(CustomModel.get(Material.CRIMSON_STEM, "advancement", "tragoul", "challenge_trag_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHER_STAR) + .key("challenge_trag_100k") + .title(Localizer.dLocalize("advancement.challenge_trag_100k.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_100k.description")) + .model(CustomModel.get(Material.NETHER_STAR, "advancement", "tragoul", "challenge_trag_100k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_trag_1k", "trag.damage", 1000, getConfig().challengeTragReward); + registerMilestone("challenge_trag_10k", "trag.damage", 10000, getConfig().challengeTragReward * 2); + registerMilestone("challenge_trag_100k", "trag.damage", 100000, getConfig().challengeTragReward * 5); + + // Chain 2 - Hits Received + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ROTTEN_FLESH) + .key("challenge_trag_hits_500") + .title(Localizer.dLocalize("advancement.challenge_trag_hits_500.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_hits_500.description")) + .model(CustomModel.get(Material.ROTTEN_FLESH, "advancement", "tragoul", "challenge_trag_hits_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BONE) + .key("challenge_trag_hits_5k") + .title(Localizer.dLocalize("advancement.challenge_trag_hits_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_hits_5k.description")) + .model(CustomModel.get(Material.BONE, "advancement", "tragoul", "challenge_trag_hits_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_trag_hits_500", "trag.hitsrecieved", 500, getConfig().challengeTragReward); + registerMilestone("challenge_trag_hits_5k", "trag.hitsrecieved", 5000, getConfig().challengeTragReward); + + // Chain 3 - Deaths + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SKELETON_SKULL) + .key("challenge_trag_deaths_10") + .title(Localizer.dLocalize("advancement.challenge_trag_deaths_10.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_deaths_10.description")) + .model(CustomModel.get(Material.SKELETON_SKULL, "advancement", "tragoul", "challenge_trag_deaths_10")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.WITHER_SKELETON_SKULL) + .key("challenge_trag_deaths_100") + .title(Localizer.dLocalize("advancement.challenge_trag_deaths_100.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_deaths_100.description")) + .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "tragoul", "challenge_trag_deaths_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_trag_deaths_10", "trag.deaths", 10, getConfig().challengeTragReward); + registerMilestone("challenge_trag_deaths_100", "trag.deaths", 100, getConfig().challengeTragReward); + + // Chain 4 - Fire Damage + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.BLAZE_POWDER) + .key("challenge_trag_fire_500") + .title(Localizer.dLocalize("advancement.challenge_trag_fire_500.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_fire_500.description")) + .model(CustomModel.get(Material.BLAZE_POWDER, "advancement", "tragoul", "challenge_trag_fire_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.MAGMA_CREAM) + .key("challenge_trag_fire_5k") + .title(Localizer.dLocalize("advancement.challenge_trag_fire_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_fire_5k.description")) + .model(CustomModel.get(Material.MAGMA_CREAM, "advancement", "tragoul", "challenge_trag_fire_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_trag_fire_500", "trag.fire.damage", 500, getConfig().challengeTragReward); + registerMilestone("challenge_trag_fire_5k", "trag.fire.damage", 5000, getConfig().challengeTragReward); + + // Chain 5 - Fall Damage + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FEATHER) + .key("challenge_trag_fall_500") + .title(Localizer.dLocalize("advancement.challenge_trag_fall_500.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_fall_500.description")) + .model(CustomModel.get(Material.FEATHER, "advancement", "tragoul", "challenge_trag_fall_500")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.HAY_BLOCK) + .key("challenge_trag_fall_5k") + .title(Localizer.dLocalize("advancement.challenge_trag_fall_5k.title")) + .description(Localizer.dLocalize("advancement.challenge_trag_fall_5k.description")) + .model(CustomModel.get(Material.HAY_BLOCK, "advancement", "tragoul", "challenge_trag_fall_5k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_trag_fall_500", "trag.fall.damage", 500, getConfig().challengeTragReward); + registerMilestone("challenge_trag_fall_5k", "trag.fall.damage", 5000, getConfig().challengeTragReward); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + shouldReturnForPlayer(p, e, () -> { + if (e.getEntity().isDead() + || e.getEntity().isInvulnerable() + || p.isInvulnerable() + || p.isBlocking() + || !checkValidEntity(e.getEntity().getType())) { + return; + } + AdaptPlayer a = getPlayer(p); + a.getData().addStat("trag.hitsrecieved", 1); + a.getData().addStat("trag.damage", e.getDamage()); + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + xp(a.getPlayer(), getConfig().damageReceivedXpMultiplier * e.getDamage()); + if (p.getHealth() - e.getFinalDamage() > 0 && p.getHealth() - e.getFinalDamage() <= 8) { + xp(a.getPlayer(), getConfig().lowHealthSurvivalXP); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(PlayerDeathEvent e) { + Player p = e.getEntity(); + shouldReturnForPlayer(p, () -> { + AdaptPlayer a = getPlayer(p); + a.getData().addStat("trag.deaths", 1); + if (AdaptConfig.get().isHardcoreResetOnPlayerDeath()) { + Adapt.info("Resetting " + p.getName() + "'s skills due to death"); + a.delete(p.getUniqueId()); + return; + } + if (getConfig().takeAwaySkillsOnDeath) { + if (areParticlesEnabled()) { + CloudEffect ce = new CloudEffect(Adapt.instance.adaptEffectManager); + ce.mainParticle = Particle.ASH; + ce.cloudParticle = Particles.REDSTONE; + ce.duration = 10000; + ce.iterations = 1000; + ce.setEntity(p); + ce.start(); + } + + if (!this.hasUsePermission(p, this)) { + return; + } + + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.ENTITY_BLAZE_DEATH, 1f, 1f); + + PlayerSkillLine tragoul = a.getData().getSkillLineNullable("tragoul"); + if (tragoul != null) { + double xp = tragoul.getXp(); + if (xp > getConfig().deathXpLoss) { + xp(p, getConfig().deathXpLoss); + } else { + tragoul.setXp(0); + } + tragoul.setLastXP(xp); + + for (PlayerAdaptation adapt : tragoul.getAdaptations().values()) { + adapt.setLevel(Math.max(adapt.getLevel() - 1, 0)); + } + + recalcTotalExp(p); + a.getData().pruneAdaptationsForPowerBudget(); + } + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageEvent e) { + if (!(e.getEntity() instanceof Player p)) { + return; + } + shouldReturnForPlayer(p, e, () -> { + EntityDamageEvent.DamageCause cause = e.getCause(); + if (cause == EntityDamageEvent.DamageCause.FALL) { + getPlayer(p).getData().addStat("trag.fall.damage", e.getDamage()); + } else if (cause == EntityDamageEvent.DamageCause.FIRE + || cause == EntityDamageEvent.DamageCause.FIRE_TICK + || cause == EntityDamageEvent.DamageCause.LAVA) { + getPlayer(p).getData().addStat("trag.fire.damage", e.getDamage()); + } + }); + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + checkStatTrackersForOnlinePlayers(); + } + + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Death Xp Loss for the Trag Oul skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + public double deathXpLoss = -250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Take Away Skills On Death for the Trag Oul skill.", impact = "True enables this behavior and false disables it.") + boolean takeAwaySkillsOnDeath = false; + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&b"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Trag Oul skill.", impact = "True enables this behavior and false disables it.") + boolean showParticles = true; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Trag Oul skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 450; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage Received Xp Multiplier for the Trag Oul skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageReceivedXpMultiplier = 4.8; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls XP bonus for surviving a hit below 4 hearts.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double lowHealthSurvivalXP = 28; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Trag Reward for the Trag Oul skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeTragReward = 500; + } +} diff --git a/src/main/java/art/arcane/adapt/content/skill/SkillUnarmed.java b/src/main/java/art/arcane/adapt/content/skill/SkillUnarmed.java new file mode 100644 index 000000000..a3edb6ec7 --- /dev/null +++ b/src/main/java/art/arcane/adapt/content/skill/SkillUnarmed.java @@ -0,0 +1,276 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.content.skill; + +import art.arcane.adapt.api.advancement.AdaptAdvancement; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import art.arcane.adapt.api.advancement.AdvancementVisibility; +import art.arcane.adapt.api.skill.SimpleSkill; +import art.arcane.adapt.api.world.AdaptPlayer; +import art.arcane.adapt.content.adaptation.unarmed.*; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.misc.CustomModel; +import lombok.NoArgsConstructor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SkillUnarmed extends SimpleSkill { + private final Map cooldowns; + + public SkillUnarmed() { + super("unarmed", Localizer.dLocalize("skill.unarmed.icon")); + registerConfiguration(Config.class); + cooldowns = new HashMap<>(); + setColor(C.YELLOW); + setDescription(Localizer.dLocalize("skill.unarmed.description")); + setDisplayName(Localizer.dLocalize("skill.unarmed.name")); + setInterval(2579); + registerAdaptation(new UnarmedSuckerPunch()); + registerAdaptation(new UnarmedPower()); + registerAdaptation(new UnarmedGlassCannon()); + registerAdaptation(new UnarmedBatteringCharge()); + registerAdaptation(new UnarmedComboChain()); + registerAdaptation(new UnarmedDisarm()); + registerAdaptation(new UnarmedFlurry()); + registerAdaptation(new UnarmedPressurePoint()); + registerAdaptation(new UnarmedShockwaveClap()); + registerAdaptation(new UnarmedIronFists()); + registerAdaptation(new UnarmedGrapple()); + registerAdaptation(new UnarmedSecondWind()); + registerAdaptation(new UnarmedMeditation()); + setIcon(Material.FIRE_CHARGE); + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FIRE_CHARGE) + .key("challenge_unarmed_100") + .title(Localizer.dLocalize("advancement.challenge_unarmed_100.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_100.description")) + .model(CustomModel.get(Material.FIRE_CHARGE, "advancement", "unarmed", "challenge_unarmed_100")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BLAZE_POWDER) + .key("challenge_unarmed_1k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_1k.description")) + .model(CustomModel.get(Material.BLAZE_POWDER, "advancement", "unarmed", "challenge_unarmed_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.NETHER_STAR) + .key("challenge_unarmed_10k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_10k.description")) + .model(CustomModel.get(Material.NETHER_STAR, "advancement", "unarmed", "challenge_unarmed_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()) + .build()); + registerMilestone("challenge_unarmed_100", "unarmed.hits", 100, getConfig().challengeUnarmedReward); + registerMilestone("challenge_unarmed_1k", "unarmed.hits", 1000, getConfig().challengeUnarmedReward * 2); + registerMilestone("challenge_unarmed_10k", "unarmed.hits", 10000, getConfig().challengeUnarmedReward * 5); + + // Chain 2 - Unarmed Damage + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.FIRE_CHARGE) + .key("challenge_unarmed_dmg_1k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_dmg_1k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_dmg_1k.description")) + .model(CustomModel.get(Material.FIRE_CHARGE, "advancement", "unarmed", "challenge_unarmed_dmg_1k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.BLAZE_ROD) + .key("challenge_unarmed_dmg_10k") + .title(Localizer.dLocalize("advancement.challenge_unarmed_dmg_10k.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_dmg_10k.description")) + .model(CustomModel.get(Material.BLAZE_ROD, "advancement", "unarmed", "challenge_unarmed_dmg_10k")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_dmg_1k", "unarmed.damage", 1000, getConfig().challengeUnarmedDmgReward); + registerMilestone("challenge_unarmed_dmg_10k", "unarmed.damage", 10000, getConfig().challengeUnarmedDmgReward * 3); + + // Chain 3 - Unarmed Kills + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.ROTTEN_FLESH) + .key("challenge_unarmed_kills_25") + .title(Localizer.dLocalize("advancement.challenge_unarmed_kills_25.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_kills_25.description")) + .model(CustomModel.get(Material.ROTTEN_FLESH, "advancement", "unarmed", "challenge_unarmed_kills_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.ZOMBIE_HEAD) + .key("challenge_unarmed_kills_250") + .title(Localizer.dLocalize("advancement.challenge_unarmed_kills_250.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_kills_250.description")) + .model(CustomModel.get(Material.ZOMBIE_HEAD, "advancement", "unarmed", "challenge_unarmed_kills_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_kills_25", "unarmed.kills", 25, getConfig().challengeUnarmedKillsReward); + registerMilestone("challenge_unarmed_kills_250", "unarmed.kills", 250, getConfig().challengeUnarmedKillsReward * 3); + + // Chain 4 - Unarmed Criticals + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.SUGAR) + .key("challenge_unarmed_crit_25") + .title(Localizer.dLocalize("advancement.challenge_unarmed_crit_25.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_crit_25.description")) + .model(CustomModel.get(Material.SUGAR, "advancement", "unarmed", "challenge_unarmed_crit_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.FERMENTED_SPIDER_EYE) + .key("challenge_unarmed_crit_250") + .title(Localizer.dLocalize("advancement.challenge_unarmed_crit_250.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_crit_250.description")) + .model(CustomModel.get(Material.FERMENTED_SPIDER_EYE, "advancement", "unarmed", "challenge_unarmed_crit_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_crit_25", "unarmed.critical", 25, getConfig().challengeUnarmedCritReward); + registerMilestone("challenge_unarmed_crit_250", "unarmed.critical", 250, getConfig().challengeUnarmedCritReward * 3); + + // Chain 5 - Unarmed Heavy Hits + registerAdvancement(AdaptAdvancement.builder() + .icon(Material.TNT) + .key("challenge_unarmed_heavy_25") + .title(Localizer.dLocalize("advancement.challenge_unarmed_heavy_25.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_heavy_25.description")) + .model(CustomModel.get(Material.TNT, "advancement", "unarmed", "challenge_unarmed_heavy_25")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .child(AdaptAdvancement.builder() + .icon(Material.END_CRYSTAL) + .key("challenge_unarmed_heavy_250") + .title(Localizer.dLocalize("advancement.challenge_unarmed_heavy_250.title")) + .description(Localizer.dLocalize("advancement.challenge_unarmed_heavy_250.description")) + .model(CustomModel.get(Material.END_CRYSTAL, "advancement", "unarmed", "challenge_unarmed_heavy_250")) + .frame(AdaptAdvancementFrame.CHALLENGE) + .visibility(AdvancementVisibility.PARENT_GRANTED) + .build()) + .build()); + registerMilestone("challenge_unarmed_heavy_25", "unarmed.heavy", 25, getConfig().challengeUnarmedHeavyReward); + registerMilestone("challenge_unarmed_heavy_250", "unarmed.heavy", 250, getConfig().challengeUnarmedHeavyReward * 3); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDamageByEntityEvent e) { + if (!(e.getDamager() instanceof Player p)) { + return; + } + + shouldReturnForPlayer(p, e, () -> { + if (e.getEntity().isDead() + || e.getEntity().isInvulnerable() + || p.isInvulnerable()) { + return; + } + + if (!checkValidEntity(e.getEntity().getType())) { + return; + } + + AdaptPlayer a = getPlayer(p); + ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); + + if (!isMelee(hand)) { + a.getData().addStat("unarmed.hits", 1); + a.getData().addStat("unarmed.damage", e.getDamage()); + if (p.getFallDistance() > 0 && !p.isOnGround()) { + a.getData().addStat("unarmed.critical", 1); + } + if (e.getDamage() > 6) { + a.getData().addStat("unarmed.heavy", 1); + } + Long cooldown = cooldowns.get(p.getUniqueId()); + if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) + return; + cooldowns.put(p.getUniqueId(), System.currentTimeMillis()); + xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().damageXPMultiplier * e.getDamage()); + } + }); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(EntityDeathEvent e) { + if (e.getEntity().getKiller() == null) { + return; + } + Player p = e.getEntity().getKiller(); + + shouldReturnForPlayer(p, () -> { + AdaptPlayer a = getPlayer(p); + ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); + + if (!isMelee(hand)) { + a.getData().addStat("unarmed.kills", 1); + } + }); + } + + @Override + public void onTick() { + if (!this.isEnabled()) { + return; + } + checkStatTrackersForOnlinePlayers(); + } + + @Override + public boolean isEnabled() { + return getConfig().enabled; + } + + @NoArgsConstructor + protected static class Config { + @art.arcane.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") + boolean enabled = true; + String skillColor = "&e"; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Damage XPMultiplier for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double damageXPMultiplier = 4.5; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + long cooldownDelay = 1250; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeUnarmedReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Damage Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeUnarmedDmgReward = 500; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Kills Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeUnarmedKillsReward = 750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Critical Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeUnarmedCritReward = 750; + @art.arcane.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Heavy Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") + double challengeUnarmedHeavyReward = 750; + } +} diff --git a/src/main/java/com/volmit/adapt/content/skill/package-info.java b/src/main/java/art/arcane/adapt/content/skill/package-info.java similarity index 96% rename from src/main/java/com/volmit/adapt/content/skill/package-info.java rename to src/main/java/art/arcane/adapt/content/skill/package-info.java index 8611716ae..4a29de841 100644 --- a/src/main/java/com/volmit/adapt/content/skill/package-info.java +++ b/src/main/java/art/arcane/adapt/content/skill/package-info.java @@ -16,4 +16,4 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.content.skill; \ No newline at end of file +package art.arcane.adapt.content.skill; \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/core/nms/container/BlockProperty.java b/src/main/java/art/arcane/adapt/core/nms/container/BlockProperty.java new file mode 100644 index 000000000..533499141 --- /dev/null +++ b/src/main/java/art/arcane/adapt/core/nms/container/BlockProperty.java @@ -0,0 +1,165 @@ +package art.arcane.adapt.core.nms.container; + +import art.arcane.volmlib.util.json.JSONArray; +import art.arcane.volmlib.util.json.JSONObject; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.function.Function; + +public class BlockProperty { + private static final Set> NATIVES = Set.of(Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Boolean.class, String.class); + private final String name; + private final Class type; + + private final Object defaultValue; + private final Set values; + private final Function nameFunction; + private final Function jsonFunction; + + public > BlockProperty( + String name, + Class type, + T defaultValue, + Collection values, + Function nameFunction + ) { + this.name = name; + this.type = type; + this.defaultValue = defaultValue; + this.values = Collections.unmodifiableSet(new TreeSet<>(values)); + this.nameFunction = (Function) (Object) nameFunction; + jsonFunction = NATIVES.contains(type) ? Function.identity() : this.nameFunction::apply; + } + + public static > BlockProperty ofEnum(Class type, String name, T defaultValue) { + return new BlockProperty( + name, + type, + defaultValue, + Arrays.asList(type.getEnumConstants()), + val -> val == null ? "null" : val.name() + ); + } + + public static BlockProperty ofFloat(String name, float defaultValue, float min, float max, boolean exclusiveMin, boolean exclusiveMax) { + return new BoundedDouble( + name, + defaultValue, + min, + max, + exclusiveMin, + exclusiveMax, + (f) -> String.format("%.2f", f) + ); + } + + public static BlockProperty ofBoolean(String name, boolean defaultValue) { + return new BlockProperty( + name, + Boolean.class, + defaultValue, + List.of(true, false), + (b) -> b ? "true" : "false" + ); + } + + @Override + public @NotNull String toString() { + return name + "=" + nameFunction.apply(defaultValue) + " [" + String.join(",", names()) + "]"; + } + + public String name() { + return name; + } + + public String defaultValue() { + return nameFunction.apply(defaultValue); + } + + public List names() { + return values.stream().map(nameFunction).toList(); + } + + public Object defaultValueAsJson() { + return jsonFunction.apply(defaultValue); + } + + public JSONArray valuesAsJson() { + return new JSONArray(values.stream().map(jsonFunction).toList()); + } + + public JSONObject buildJson() { + art.arcane.volmlib.util.json.JSONObject json = new JSONObject(); + json.put("type", jsonType()); + json.put("default", defaultValueAsJson()); + if (!values.isEmpty()) { + json.put("enum", valuesAsJson()); + } + return json; + } + + public String jsonType() { + if (type == Boolean.class) { + return "boolean"; + } + if (type == Byte.class || type == Short.class || type == Integer.class || type == Long.class) { + return "integer"; + } + if (type == Float.class || type == Double.class) { + return "number"; + } + return "string"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + art.arcane.adapt.core.nms.container.BlockProperty that = (BlockProperty) obj; + return Objects.equals(this.name, that.name) && + Objects.equals(this.values, that.values) && + Objects.equals(this.type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(name, values, type); + } + + private static class BoundedDouble extends BlockProperty { + private final double min; + private final double max; + private final boolean exclusiveMin; + private final boolean exclusiveMax; + + public BoundedDouble( + String name, + double defaultValue, + double min, + double max, + boolean exclusiveMin, + boolean exclusiveMax, + Function nameFunction + ) { + super(name, Double.class, defaultValue, List.of(), nameFunction); + this.min = min; + this.max = max; + this.exclusiveMin = exclusiveMin; + this.exclusiveMax = exclusiveMax; + } + + @Override + public JSONObject buildJson() { + return super.buildJson() + .put("minimum", min) + .put("maximum", max) + .put("exclusiveMinimum", exclusiveMin) + .put("exclusiveMaximum", exclusiveMax); + } + } +} diff --git a/src/main/java/art/arcane/adapt/nms/NMS.java b/src/main/java/art/arcane/adapt/nms/NMS.java new file mode 100644 index 000000000..b1f92109d --- /dev/null +++ b/src/main/java/art/arcane/adapt/nms/NMS.java @@ -0,0 +1,32 @@ +package art.arcane.adapt.nms; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.bukkit.util.io.BukkitObjectOutputStream; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; + +public class NMS { + + public static String serializeStack(ItemStack is) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (BukkitObjectOutputStream oos = new BukkitObjectOutputStream(out)) { + oos.writeObject(is); + return Base64.getUrlEncoder().encodeToString(out.toByteArray()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static ItemStack deserializeStack(String s) { + ByteArrayInputStream in = new ByteArrayInputStream(Base64.getUrlDecoder().decode(s)); + try (BukkitObjectInputStream ois = new BukkitObjectInputStream(in)) { + return (ItemStack) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/art/arcane/adapt/service/AdaptIntegrationService.java b/src/main/java/art/arcane/adapt/service/AdaptIntegrationService.java new file mode 100644 index 000000000..fd0bb8f7d --- /dev/null +++ b/src/main/java/art/arcane/adapt/service/AdaptIntegrationService.java @@ -0,0 +1,186 @@ +package art.arcane.adapt.service; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.protection.WorldPolicyLatencyTelemetry; +import art.arcane.adapt.api.telemetry.AbilityCheckTelemetry; +import art.arcane.adapt.util.common.plugin.AdaptService; +import art.arcane.volmlib.integration.*; +import org.bukkit.Bukkit; +import org.bukkit.plugin.ServicePriority; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class AdaptIntegrationService implements AdaptService, IntegrationServiceContract { + private static final Set SUPPORTED_PROTOCOLS = Set.of( + new IntegrationProtocolVersion(1, 0), + new IntegrationProtocolVersion(1, 1) + ); + private static final Set CAPABILITIES = Set.of( + "handshake", + "heartbeat", + "metrics", + "adapt-runtime-metrics" + ); + + private volatile IntegrationProtocolVersion negotiatedProtocol = new IntegrationProtocolVersion(1, 1); + + @Override + public void onEnable() { + Bukkit.getServicesManager().register(IntegrationServiceContract.class, this, Adapt.instance, ServicePriority.Normal); + Adapt.verbose("Integration provider registered for Adapt"); + } + + @Override + public void onDisable() { + Bukkit.getServicesManager().unregister(IntegrationServiceContract.class, this); + AbilityCheckTelemetry.clear(); + WorldPolicyLatencyTelemetry.clear(); + } + + @Override + public String pluginId() { + return "adapt"; + } + + @Override + public String pluginVersion() { + return Adapt.instance.getDescription().getVersion(); + } + + @Override + public Set supportedProtocols() { + return SUPPORTED_PROTOCOLS; + } + + @Override + public Set capabilities() { + return CAPABILITIES; + } + + @Override + public Set metricDescriptors() { + return IntegrationMetricSchema.descriptors().stream() + .filter(descriptor -> descriptor.key().startsWith("adapt.")) + .collect(java.util.stream.Collectors.toSet()); + } + + @Override + public IntegrationHandshakeResponse handshake(IntegrationHandshakeRequest request) { + long now = System.currentTimeMillis(); + if (request == null) { + return new IntegrationHandshakeResponse( + pluginId(), + pluginVersion(), + false, + null, + SUPPORTED_PROTOCOLS, + CAPABILITIES, + "missing request", + now + ); + } + + Optional negotiated = IntegrationProtocolNegotiator.negotiate( + SUPPORTED_PROTOCOLS, + request.supportedProtocols() + ); + if (negotiated.isEmpty()) { + return new IntegrationHandshakeResponse( + pluginId(), + pluginVersion(), + false, + null, + SUPPORTED_PROTOCOLS, + CAPABILITIES, + "no-common-protocol", + now + ); + } + + negotiatedProtocol = negotiated.get(); + return new IntegrationHandshakeResponse( + pluginId(), + pluginVersion(), + true, + negotiatedProtocol, + SUPPORTED_PROTOCOLS, + CAPABILITIES, + "ok", + now + ); + } + + @Override + public IntegrationHeartbeat heartbeat() { + long now = System.currentTimeMillis(); + return new IntegrationHeartbeat(negotiatedProtocol, true, now, "ok"); + } + + @Override + public Map sampleMetrics(Set metricKeys) { + Set requested = metricKeys == null || metricKeys.isEmpty() + ? IntegrationMetricSchema.adaptKeys() + : metricKeys; + long now = System.currentTimeMillis(); + Map out = new HashMap<>(); + + for (String key : requested) { + switch (key) { + case IntegrationMetricSchema.ADAPT_SESSION_LOAD -> + out.put(key, sampleSessionLoad(now)); + case IntegrationMetricSchema.ADAPT_ABILITY_OPS -> + out.put(key, sampleAbilityOps(now)); + case IntegrationMetricSchema.ADAPT_ABILITY_CHECK_OPS -> + out.put(key, sampleAbilityCheckOps(now)); + case IntegrationMetricSchema.ADAPT_ABILITY_CHECK_OPS_TICK -> + out.put(key, sampleAbilityCheckOpsTick(now)); + case IntegrationMetricSchema.ADAPT_WORLD_POLICY_LATENCY -> + out.put(key, sampleWorldPolicyLatency(now)); + default -> out.put(key, IntegrationMetricSample.unavailable( + IntegrationMetricSchema.descriptor(key), + "unsupported-key", + now + )); + } + } + + return out; + } + + private IntegrationMetricSample sampleSessionLoad(long now) { + IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.ADAPT_SESSION_LOAD); + if (Adapt.instance.getTicker() == null) { + return IntegrationMetricSample.unavailable(descriptor, "ticker-not-ready", now); + } + + double load = Adapt.instance.getTicker().getWindowLoadPercent(); + return IntegrationMetricSample.available(descriptor, load, now); + } + + private IntegrationMetricSample sampleAbilityOps(long now) { + IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.ADAPT_ABILITY_OPS); + long count = AbilityCheckTelemetry.successfulChecksPerMinute(now); + return IntegrationMetricSample.available(descriptor, count, now); + } + + private IntegrationMetricSample sampleAbilityCheckOps(long now) { + IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.ADAPT_ABILITY_CHECK_OPS); + long count = AbilityCheckTelemetry.checksPerMinute(now); + return IntegrationMetricSample.available(descriptor, count, now); + } + + private IntegrationMetricSample sampleAbilityCheckOpsTick(long now) { + IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.ADAPT_ABILITY_CHECK_OPS_TICK); + double count = AbilityCheckTelemetry.checksPerTick(now); + return IntegrationMetricSample.available(descriptor, count, now); + } + + private IntegrationMetricSample sampleWorldPolicyLatency(long now) { + IntegrationMetricDescriptor descriptor = IntegrationMetricSchema.descriptor(IntegrationMetricSchema.ADAPT_WORLD_POLICY_LATENCY); + double averageMs = WorldPolicyLatencyTelemetry.averageMillis(now); + return IntegrationMetricSample.available(descriptor, averageMs, now); + } +} diff --git a/src/main/java/art/arcane/adapt/service/CommandSVC.java b/src/main/java/art/arcane/adapt/service/CommandSVC.java new file mode 100644 index 000000000..dedc57e5a --- /dev/null +++ b/src/main/java/art/arcane/adapt/service/CommandSVC.java @@ -0,0 +1,237 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.adapt.service; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.command.CommandAdapt; +import art.arcane.adapt.util.cache.AtomicCache; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.plugin.AdaptService; +import art.arcane.adapt.util.common.plugin.VolmitSender; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.director.DirectorSystem; +import art.arcane.volmlib.util.director.compat.BukkitDirectorContext; +import art.arcane.volmlib.util.director.compat.DirectorEngineFactory; +import art.arcane.volmlib.util.director.context.DirectorContextRegistry; +import art.arcane.volmlib.util.director.runtime.*; +import art.arcane.volmlib.util.director.visual.DirectorVisualCommand; +import art.arcane.volmlib.util.math.RNG; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.command.*; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class CommandSVC implements AdaptService, CommandExecutor, TabCompleter, DirectorInvocationHook { + private static final String ROOT_COMMAND = "adapt"; + private static final String ROOT_PERMISSION = "adapt.main"; + + private final transient AtomicCache directorCache = new AtomicCache<>(); + private final transient AtomicCache helpCache = new AtomicCache<>(); + + @Override + public void onEnable() { + Adapt.verbose("Initializing Commands..."); + PluginCommand command = Adapt.instance.getCommand(ROOT_COMMAND); + if (command == null) { + Adapt.warn("Failed to find command '" + ROOT_COMMAND + "'"); + return; + } + + command.setExecutor(this); + command.setTabCompleter(this); + J.a(this::getDirector); + } + + @Override + public void onDisable() { + + } + + public DirectorRuntimeEngine getDirector() { + return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create( + new CommandAdapt(), + null, + buildDirectorContexts(), + this::dispatchDirector, + this, + DirectorSystem.handlers + )); + } + + private DirectorContextRegistry buildDirectorContexts() { + DirectorContextRegistry contexts = new DirectorContextRegistry(); + contexts.register(World.class, (invocation, map) -> { + if (invocation.getSender() instanceof BukkitDirectorSender sender && sender.sender() instanceof Player player) { + return player.getWorld(); + } + + return null; + }); + + return contexts; + } + + private void dispatchDirector(DirectorExecutionMode mode, Runnable runnable) { + if (mode == DirectorExecutionMode.SYNC) { + J.s(runnable); + } else { + runnable.run(); + } + } + + @Override + public void beforeInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) { + if (invocation.getSender() instanceof BukkitDirectorSender sender) { + BukkitDirectorContext.touch(sender.sender()); + } + } + + @Override + public void afterInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) { + BukkitDirectorContext.remove(); + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + if (!command.getName().equalsIgnoreCase(ROOT_COMMAND)) { + return List.of(); + } + + List v = runDirectorTab(sender, alias, args); + + if (sender instanceof Player p) { + SoundPlayer sp = SoundPlayer.of(p); + sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f)); + } + + return v; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!command.getName().equalsIgnoreCase(ROOT_COMMAND)) { + return false; + } + + Adapt.verbose("Received Command from %s: /%s".formatted(sender.getName(), label + String.join(" ", args))); + if (!sender.hasPermission(ROOT_PERMISSION)) { + sender.sendMessage("You lack the Permission '" + ROOT_PERMISSION + "'"); + return true; + } + + executeCommand(sender, label, args); + return true; + } + + private void executeCommand(CommandSender sender, String label, String[] args) { + if (sendHelpIfRequested(sender, args)) { + playSuccessSound(sender); + return; + } + + DirectorExecutionResult result = runDirector(sender, label, args); + + if (result.isSuccess()) { + playSuccessSound(sender); + return; + } + + playFailureSound(sender); + if (result.getMessage() == null || result.getMessage().trim().isEmpty()) { + sender.sendMessage(C.RED + "Unknown Adapt Command"); + } + } + + private boolean sendHelpIfRequested(CommandSender sender, String[] args) { + Optional request = DirectorVisualCommand.resolveHelp(getHelpRoot(), Arrays.asList(args)); + if (request.isEmpty()) { + return false; + } + + VolmitSender volmitSender = new VolmitSender(sender); + volmitSender.sendDirectorHelp(request.get().command(), request.get().page()); + return true; + } + + private DirectorVisualCommand getHelpRoot() { + return helpCache.aquireNastyPrint(() -> DirectorVisualCommand.createRoot(getDirector())); + } + + private DirectorExecutionResult runDirector(CommandSender sender, String label, String[] args) { + try { + return getDirector().execute(new DirectorInvocation(new BukkitDirectorSender(sender), label, Arrays.asList(args))); + } catch (Throwable e) { + Adapt.warn("Director command execution failed: " + e.getClass().getSimpleName() + " " + e.getMessage()); + return DirectorExecutionResult.notHandled(); + } + } + + private List runDirectorTab(CommandSender sender, String alias, String[] args) { + try { + return getDirector().tabComplete(new DirectorInvocation(new BukkitDirectorSender(sender), alias, Arrays.asList(args))); + } catch (Throwable e) { + Adapt.warn("Director tab completion failed: " + e.getClass().getSimpleName() + " " + e.getMessage()); + return List.of(); + } + } + + private void playFailureSound(CommandSender sender) { + if (sender instanceof Player player) { + SoundPlayer sp = SoundPlayer.of(player); + sp.play(player.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_DEPLETE, 0.77f, 0.25f); + sp.play(player.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.2f, 0.45f); + } + } + + private void playSuccessSound(CommandSender sender) { + if (sender instanceof Player player) { + SoundPlayer sp = SoundPlayer.of(player); + sp.play(player.getLocation(), Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 1.65f); + sp.play(player.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.125f, 2.99f); + } + } + + private record BukkitDirectorSender( + CommandSender sender) implements DirectorSender { + @Override + public String getName() { + return sender.getName(); + } + + @Override + public boolean isPlayer() { + return sender instanceof Player; + } + + @Override + public void sendMessage(String message) { + if (message != null && !message.trim().isEmpty()) { + sender.sendMessage(message); + } + } + } +} diff --git a/src/main/java/art/arcane/adapt/service/ConfigInputSVC.java b/src/main/java/art/arcane/adapt/service/ConfigInputSVC.java new file mode 100644 index 000000000..da5dbfc80 --- /dev/null +++ b/src/main/java/art/arcane/adapt/service/ConfigInputSVC.java @@ -0,0 +1,130 @@ +package art.arcane.adapt.service; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.content.gui.ConfigGui; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.plugin.AdaptService; +import art.arcane.adapt.util.common.scheduling.J; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class ConfigInputSVC implements AdaptService { + private static final long SESSION_TIMEOUT_MS = 45_000L; + + private final Map sessions = new ConcurrentHashMap<>(); + private int cleanupTaskId = -1; + + @Override + public void onEnable() { + cleanupTaskId = J.sr(this::cleanupExpiredSessions, 20); + } + + @Override + public void onDisable() { + if (cleanupTaskId != -1) { + J.csr(cleanupTaskId); + cleanupTaskId = -1; + } + sessions.clear(); + } + + public void beginSession(Player player, String valuePath, String returnSectionPath, int returnPage, Class targetType, String label) { + if (player == null) { + return; + } + + PendingInput pending = new PendingInput( + player.getUniqueId(), + valuePath, + returnSectionPath == null ? "" : returnSectionPath, + Math.max(0, returnPage), + targetType, + label == null ? valuePath : label, + System.currentTimeMillis() + SESSION_TIMEOUT_MS + ); + sessions.put(player.getUniqueId(), pending); + + ConfigGui.suppressClose(player); + player.closeInventory(); + Adapt.messagePlayer(player, C.AQUA + "Enter value for " + C.WHITE + pending.label()); + Adapt.messagePlayer(player, C.AQUA + "Path: " + C.WHITE + pending.valuePath()); + Adapt.messagePlayer(player, C.AQUA + "Expected type: " + C.WHITE + ConfigGui.typeName(targetType)); + Adapt.messagePlayer(player, C.GRAY + "Type " + C.WHITE + "cancel" + C.GRAY + " to abort."); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onAsyncChat(AsyncPlayerChatEvent event) { + Player player = event.getPlayer(); + PendingInput pending = sessions.get(player.getUniqueId()); + if (pending == null) { + return; + } + + event.setCancelled(true); + + if (pending.isExpired()) { + sessions.remove(player.getUniqueId()); + J.s(() -> { + Adapt.messagePlayer(player, C.RED + "Config input timed out."); + ConfigGui.open(player, pending.returnSectionPath(), pending.returnPage()); + }); + return; + } + + String message = event.getMessage() == null ? "" : event.getMessage(); + if (message.equalsIgnoreCase("cancel")) { + sessions.remove(player.getUniqueId()); + J.s(() -> { + Adapt.messagePlayer(player, C.YELLOW + "Config edit cancelled."); + ConfigGui.open(player, pending.returnSectionPath(), pending.returnPage()); + }); + return; + } + + ConfigGui.ParseResult parsed = ConfigGui.parseInputValue(pending.targetType(), message); + if (!parsed.success()) { + J.s(() -> { + Adapt.messagePlayer(player, C.RED + parsed.error()); + Adapt.messagePlayer(player, C.GRAY + "Try again or type " + C.WHITE + "cancel"); + }); + return; + } + + sessions.remove(player.getUniqueId()); + Object value = parsed.value(); + J.s(() -> { + ConfigGui.confirmAndApply(player, pending.returnSectionPath(), pending.returnPage(), pending.valuePath(), value); + }); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + sessions.remove(event.getPlayer().getUniqueId()); + } + + private void cleanupExpiredSessions() { + long now = System.currentTimeMillis(); + sessions.entrySet().removeIf(e -> e.getValue().expiresAt() <= now); + } + + private record PendingInput( + UUID playerId, + String valuePath, + String returnSectionPath, + int returnPage, + Class targetType, + String label, + long expiresAt + ) { + private boolean isExpired() { + return System.currentTimeMillis() >= expiresAt; + } + } +} diff --git a/src/main/java/art/arcane/adapt/service/HotloadSVC.java b/src/main/java/art/arcane/adapt/service/HotloadSVC.java new file mode 100644 index 000000000..bbf0ec391 --- /dev/null +++ b/src/main/java/art/arcane/adapt/service/HotloadSVC.java @@ -0,0 +1,502 @@ +package art.arcane.adapt.service; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.skill.SkillRegistry; +import art.arcane.adapt.api.tick.TickedObject; +import art.arcane.adapt.content.gui.ConfigGui; +import art.arcane.adapt.content.gui.SkillsGui; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.format.Localizer; +import art.arcane.adapt.util.common.io.Json; +import art.arcane.adapt.util.common.misc.CustomModel; +import art.arcane.adapt.util.common.plugin.AdaptService; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigFileSupport; +import art.arcane.adapt.util.project.config.ConfigRewriteReporter; +import art.arcane.volmlib.util.hotload.ConfigHotloadEngine; +import art.arcane.volmlib.util.inventorygui.UIWindow; +import art.arcane.volmlib.util.io.IO; +import com.google.gson.JsonElement; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +import java.io.File; +import java.nio.file.Files; +import java.util.*; + +import static art.arcane.adapt.util.director.context.AdaptationListingHandler.initializeAdaptationListings; + +public class HotloadSVC implements AdaptService { + private static final long WATCHER_POLL_MS = 500; + private static final int MAX_DIFF_MESSAGES_PER_FILE = 12; + + private TickedObject configTicker; + private File adaptConfigFile; + private File adaptConfigLegacyFile; + private File modelsFile; + private File modelsLegacyFile; + private File skillsFolder; + private File adaptationsFolder; + private final ConfigHotloadEngine hotloadEngine = new ConfigHotloadEngine( + this::isManagedConfigFile, + this::listKnownConfigFiles, + this::readFileContent, + this::normalizeContent + ); + + @Override + public void onEnable() { + adaptConfigFile = Adapt.instance.getDataFile("adapt", "adapt.toml"); + adaptConfigLegacyFile = Adapt.instance.getDataFile("adapt", "adapt.json"); + modelsFile = Adapt.instance.getDataFile("adapt", "models.toml"); + modelsLegacyFile = Adapt.instance.getDataFile("adapt", "models.json"); + skillsFolder = Adapt.instance.getDataFolder("adapt", "skills"); + adaptationsFolder = Adapt.instance.getDataFolder("adapt", "adaptations"); + hotloadEngine.configure( + WATCHER_POLL_MS, + List.of(adaptConfigFile, adaptConfigLegacyFile, modelsFile, modelsLegacyFile), + List.of(skillsFolder, adaptationsFolder) + ); + Adapt.info("Config hotload watcher enabled for all /adapt/*.json and /adapt/*.toml files."); + + configTicker = new TickedObject("config", "config-hotload-service", WATCHER_POLL_MS) { + @Override + public void onTick() { + pollConfigChanges(); + } + }; + } + + @Override + public void onDisable() { + if (configTicker != null) { + configTicker.unregister(); + configTicker = null; + } + hotloadEngine.clear(); + } + + private void pollConfigChanges() { + java.util.Set touched = hotloadEngine.pollTouchedFiles(); + if (touched.isEmpty()) { + return; + } + + boolean refreshedSomething = false; + for (File file : touched) { + refreshedSomething = processConfigChange(file) || refreshedSomething; + } + + if (refreshedSomething) { + refreshOpenAdaptGuis(); + } + } + + private boolean processConfigChange(File file) { + return hotloadEngine.processFileChange(file, this::applyConfigChange, delta -> { + if (isModelsConfigFile(file)) { + return; + } + + notifyOps(file, delta.before(), delta.after()); + }); + } + + private boolean applyConfigChange(File file) { + try { + if (isShadowedLegacyJson(file)) { + if (!isModelsConfigFile(file)) { + Adapt.verbose("Ignoring legacy json hotload because canonical toml exists: " + file.getPath()); + } + return false; + } + + if (isAdaptConfigFile(file)) { + boolean ok = AdaptConfig.reload(); + if (ok) { + refreshGlobalRuntimeSettings(); + } else { + Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid config."); + } + return ok; + } + + if (isSkillConfigFile(file)) { + return reloadSkillConfig(file); + } + + if (isAdaptationConfigFile(file)) { + return reloadAdaptationConfig(file); + } + + if (isModelsConfigFile(file)) { + return reloadModelsConfig(file); + } + + return validateAndCanonicalizeConfig(file); + } catch (Throwable e) { + Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid config: " + e.getMessage()); + return false; + } + } + + private boolean reloadSkillConfig(File file) { + String skillName = toConfigName(file.getName()); + if (skillName == null) { + return false; + } + + SkillRegistry registry = Adapt.instance.getAdaptServer().getSkillRegistry(); + boolean ok = registry.hotReloadSkillConfig(skillName); + if (ok) { + initializeAdaptationListings(); + } else { + Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid skill config."); + } + return ok; + } + + private boolean reloadAdaptationConfig(File file) { + String adaptationName = toConfigName(file.getName()); + if (adaptationName == null) { + return false; + } + + for (Skill skill : Adapt.instance.getAdaptServer().getSkillRegistry().getAllSkills()) { + for (Adaptation adaptation : skill.getAdaptations()) { + if (!adaptation.getName().equalsIgnoreCase(adaptationName)) { + continue; + } + + if (!(adaptation instanceof SimpleAdaptation simpleAdaptation)) { + return false; + } + + boolean ok = simpleAdaptation.reloadConfigFromDisk(false); + if (ok) { + Adapt.instance.getAdaptServer().getSkillRegistry().refreshRecipes(skill); + initializeAdaptationListings(); + } else { + Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid adaptation config."); + } + return ok; + } + } + + return validateAndCanonicalizeConfig(file); + } + + private boolean reloadModelsConfig(File file) { + return CustomModel.reloadFromDisk(true); + } + + private void refreshGlobalRuntimeSettings() { + Adapt.wordKey.clear(); + if (AdaptConfig.get().isAutoUpdateLanguage()) { + Localizer.updateLanguageFile(); + } + + if (AdaptConfig.get().isCustomModels()) { + CustomModel.reloadFromDisk(true); + } else { + CustomModel.clear(); + } + } + + private boolean validateAndCanonicalizeConfig(File file) { + if (file == null || !file.exists() || !file.isFile()) { + return true; + } + + try { + String raw = readFileContent(file); + JsonElement parsed = parseStructured(raw, file); + if (parsed == null) { + return false; + } + + if (ConfigFileSupport.isTomlFile(file)) { + return true; + } + + String canonical = Json.toJson(parsed, true); + if (!normalizeContent(raw).equals(normalizeContent(canonical))) { + ConfigRewriteReporter.reportRewrite(file, "hotload", raw, canonical); + IO.writeAll(file, canonical); + } + return true; + } catch (Throwable e) { + return false; + } + } + + private boolean isAdaptConfigFile(File file) { + return sameFile(file, adaptConfigFile) || sameFile(file, adaptConfigLegacyFile); + } + + private boolean isModelsConfigFile(File file) { + return sameFile(file, modelsFile) || sameFile(file, modelsLegacyFile); + } + + private boolean isSkillConfigFile(File file) { + return isDirectChild(skillsFolder, file) && ConfigFileSupport.isSupportedConfigFile(file); + } + + private boolean isAdaptationConfigFile(File file) { + return isDirectChild(adaptationsFolder, file) && ConfigFileSupport.isSupportedConfigFile(file); + } + + private boolean isManagedConfigFile(File file) { + return isAdaptConfigFile(file) + || isModelsConfigFile(file) + || isSkillConfigFile(file) + || isAdaptationConfigFile(file); + } + + private boolean isDirectChild(File parent, File child) { + if (parent == null || child == null) { + return false; + } + + File childParent = child.getParentFile(); + return childParent != null && sameFile(parent, childParent); + } + + private boolean sameFile(File a, File b) { + return a != null && b != null && a.getAbsoluteFile().equals(b.getAbsoluteFile()); + } + + private boolean isShadowedLegacyJson(File file) { + if (file == null || !file.getName().toLowerCase(Locale.ROOT).endsWith(".json")) { + return false; + } + + if (sameFile(file, adaptConfigLegacyFile) && adaptConfigFile != null && adaptConfigFile.exists()) { + return true; + } + if (sameFile(file, modelsLegacyFile) && modelsFile != null && modelsFile.exists()) { + return true; + } + if ((isSkillConfigFile(file) || isAdaptationConfigFile(file)) && ConfigFileSupport.toTomlFile(file).exists()) { + return true; + } + + return false; + } + + private String toConfigName(String fileName) { + return ConfigFileSupport.configNameFromFileName(fileName); + } + + private List listKnownConfigFiles() { + List files = new ArrayList<>(); + Map added = new HashMap<>(); + + addIfManaged(files, added, adaptConfigFile); + addIfManaged(files, added, adaptConfigLegacyFile); + addIfManaged(files, added, modelsFile); + addIfManaged(files, added, modelsLegacyFile); + + addDirectChildren(skillsFolder, files, added); + addDirectChildren(adaptationsFolder, files, added); + + return files; + } + + private void addDirectChildren(File folder, List out, Map added) { + if (folder == null || !folder.exists() || !folder.isDirectory()) { + return; + } + + File[] children = folder.listFiles(); + if (children == null || children.length == 0) { + return; + } + + for (File child : children) { + if (child == null || !child.isFile()) { + continue; + } + addIfManaged(out, added, child); + } + } + + private void addIfManaged(List out, Map added, File file) { + if (file == null || !isManagedConfigFile(file)) { + return; + } + + String path = file.getAbsolutePath(); + if (added.putIfAbsent(path, file) != null) { + return; + } + + out.add(file); + } + + private String readFileContent(File file) { + if (file == null || !file.exists() || !file.isFile()) { + return null; + } + + try { + return Files.readString(file.toPath()); + } catch (Throwable e) { + return null; + } + } + + private String normalizeContent(String text) { + if (text == null) { + return null; + } + return ConfigFileSupport.normalize(text); + } + + private JsonElement parseStructured(String raw, File file) { + if (raw == null || raw.isBlank()) { + return null; + } + + return ConfigFileSupport.parseToJsonElement(raw, file); + } + + private void notifyOps(File file, String before, String after) { + List diffs = ConfigHotloadEngine.computeStructuredDiff( + before, + after, + raw -> parseStructured(raw, null) + ); + if (diffs.isEmpty()) { + return; + } + + String relative = relativizeToDataFolder(file); + List messages = new ArrayList<>(); + int shown = Math.min(MAX_DIFF_MESSAGES_PER_FILE, diffs.size()); + for (int i = 0; i < shown; i++) { + ConfigHotloadEngine.DiffEntry diff = diffs.get(i); + messages.add(formatHotloadMessage(relative, diff.key(), diff.oldValue(), diff.newValue())); + } + + if (diffs.size() > shown) { + int remaining = diffs.size() - shown; + messages.add(formatHotloadMessage(relative, "...", "+" + remaining + " more", "truncated")); + } + + J.s(() -> { + for (Player player : Adapt.instance.getAdaptServer().getOnlinePlayerSnapshot()) { + if (!player.isOp()) { + continue; + } + + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 0.8f, 1.6f); + messages.forEach(player::sendMessage); + } + }); + } + + private String formatHotloadMessage(String file, String key, String oldValue, String newValue) { + return C.GRAY + "[" + C.DARK_RED + "Adapt" + C.GRAY + "]: " + + C.GREEN + "Adapt Hotloaded: " + + C.WHITE + "[" + file + "] " + + C.AQUA + "[" + key + "] " + + C.GRAY + "[" + formatValue(oldValue) + " -> " + formatValue(newValue) + "]"; + } + + private String formatValue(String value) { + return ConfigHotloadEngine.compactValue(value, 120); + } + + private String relativizeToDataFolder(File file) { + try { + return Adapt.instance.getDataFolder().toPath().relativize(file.toPath()).toString(); + } catch (Throwable e) { + return file.getName(); + } + } + + private void refreshOpenAdaptGuis() { + J.s(() -> { + Map open = new HashMap<>(Adapt.instance.getGuiLeftovers()); + for (Map.Entry entry : open.entrySet()) { + String playerKey = entry.getKey(); + UIWindow window = entry.getValue(); + if (window == null) { + continue; + } + + UUID uuid; + try { + uuid = UUID.fromString(playerKey); + } catch (Throwable ignored) { + continue; + } + + Player player = Bukkit.getPlayer(uuid); + if (player == null || !player.isOnline()) { + Adapt.instance.getGuiLeftovers().remove(playerKey); + continue; + } + + reopenFromTag(player, window.getTag()); + } + }); + } + + private void reopenFromTag(Player player, String tag) { + if (tag == null || tag.isBlank() || "/".equals(tag)) { + SkillsGui.open(player); + return; + } + + if (tag.startsWith("config/")) { + ConfigGui.reopenFromTag(player, tag); + return; + } + + if (!tag.startsWith("skill/")) { + SkillsGui.open(player); + return; + } + + String[] parts = tag.split("/"); + if (parts.length < 2) { + SkillsGui.open(player); + return; + } + + Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(parts[1]); + if (skill == null || !skill.isEnabled()) { + SkillsGui.open(player); + return; + } + + if (parts.length == 2) { + skill.openGui(player); + return; + } + + String adaptationName = parts[2]; + for (Adaptation adaptation : skill.getAdaptations()) { + if (!adaptation.getName().equalsIgnoreCase(adaptationName)) { + continue; + } + + if (adaptation.isEnabled()) { + adaptation.openGui(player); + } else { + skill.openGui(player); + } + return; + } + + skill.openGui(player); + } + +} diff --git a/src/main/java/art/arcane/adapt/util/cache/AtomicCache.java b/src/main/java/art/arcane/adapt/util/cache/AtomicCache.java new file mode 100644 index 000000000..9b102052e --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/cache/AtomicCache.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ + +package art.arcane.adapt.util.cache; + + +//import art.arcane.react.React; + +import art.arcane.adapt.Adapt; +import art.arcane.volmlib.util.function.NastySupplier; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +public class AtomicCache { + private transient final AtomicReference t; + private transient final AtomicBoolean set; + private transient final ReentrantLock lock; + private transient final boolean nullSupport; + + public AtomicCache() { + this(false); + } + + public AtomicCache(boolean nullSupport) { + set = nullSupport ? new AtomicBoolean() : null; + t = new AtomicReference<>(); + lock = new ReentrantLock(); + this.nullSupport = nullSupport; + } + + public void reset() { + t.set(null); + + if (nullSupport) { + set.set(false); + } + } + + public T aquireNasty(NastySupplier t) { + return aquire(() -> { + try { + return t.get(); + } catch (Throwable e) { + return null; + } + }); + } + + public T aquireNastyPrint(NastySupplier t) { + return aquire(() -> { + try { + return t.get(); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + }); + } + + public T aquire(Supplier t) { + if (this.t.get() != null) { + return this.t.get(); + } else if (nullSupport && set.get()) { + return null; + } + + lock.lock(); + + if (this.t.get() != null) { + lock.unlock(); + return this.t.get(); + } else if (nullSupport && set.get()) { + lock.unlock(); + return null; + } + + try { + this.t.set(t.get()); + + if (nullSupport) { + set.set(true); + } + } catch (Throwable e) { + Adapt.error("Atomic cache failure!"); + e.printStackTrace(); + } + + lock.unlock(); + + return this.t.get(); + } +} diff --git a/src/main/java/art/arcane/adapt/util/cache/Cache.java b/src/main/java/art/arcane/adapt/util/cache/Cache.java new file mode 100644 index 000000000..4b1e193a3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/cache/Cache.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ + +package art.arcane.adapt.util.cache; + +import art.arcane.volmlib.util.cache.CacheKey; +import org.bukkit.Chunk; + +public interface Cache { + static long key(Chunk chunk) { + return CacheKey.key(chunk); + } + + static long key(int x, int z) { + return CacheKey.key(x, z); + } + + static int keyX(long key) { + return CacheKey.keyX(key); + } + + static int keyZ(long key) { + return CacheKey.keyZ(key); + } + + static int to1D(int x, int y, int z, int w, int h) { + return CacheKey.to1D(x, y, z, w, h); + } + + static int[] to3D(int idx, int w, int h) { + return CacheKey.to3D(idx, w, h); + } + + int getId(); + + V get(int x, int z); +} diff --git a/src/main/java/art/arcane/adapt/util/common/collection/Dictionary.java b/src/main/java/art/arcane/adapt/util/common/collection/Dictionary.java new file mode 100644 index 000000000..d252242e1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/collection/Dictionary.java @@ -0,0 +1,412 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.collection; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class Dictionary { + //!\"#$%&[] + private static final Pattern wordsPattern = Pattern.compile("[A-Z][A-Z][A-Z][A-Z]*"); + private static final Pattern longCodesPattern = Pattern.compile("<[N-Z0-9!\\\\\"#$%\\[\\]& _][A-Z0-9!\\\\\"#$%\\[\\]& _]"); + private static final Pattern shortCodesPattern = Pattern.compile("<[A-M]"); + + private static final Comparator byLength = new Comparator() { + @Override + public int compare(String a, String b) { + return b.length() - a.length(); + } + }; + + + private static final String[] wordsAndCodes = { + "A", "THE", "B", "AND", "C", "FOR", "D", "YOU", "E", "NOT", "F", "ARE", + "G", "ALL", "H", "NEW", "I", "WAS", "J", "CAN", "K", "HAS", "L", "BUT", + "M", "OUR", + "NA", "THAT", "NB", "THIS", "NC", "WITH", "ND", "FROM", "NE", "YOUR", + "NF", "HAVE", "NG", "MORE", "NH", "WILL", "NI", "HOME", "NJ", "ABOUT", + "NK", "PAGE", "NL", "SEARCH", "NM", "FREE", "NN", "OTHER", "NO", "INFORMATION", + "NP", "TIME", "NQ", "THEY", "NR", "SITE", "NS", "WHAT", "NT", "WHICH", + "NU", "THEIR", "NV", "NEWS", "NW", "THERE", "NX", "ONLY", "NY", "WHEN", + "NZ", "CONTACT", "N0", "HERE", "N1", "BUSINESS", "N2", "ALSO", "N3", "HELP", + "N4", "VIEW", "N5", "ONLINE", "N6", "FIRST", "N7", "BEEN", "N8", "WOULD", + "N9", "WERE", "N!", "SERVICES", "N\"", "SOME", "N#", "THESE", "N$", "CLICK", + "N%", "LIKE", "N&", "SERVICE", "N[", "THAN", "N]", "FIND", "N ", "PRICE", + "N_", "DATE", "OA", "BACK", "OB", "PEOPLE", "OC", "LIST", "OD", "NAME", + "OE", "JUST", "OF", "OVER", "OG", "STATE", "OH", "YEAR", "OI", "INTO", + "OJ", "EMAIL", "OK", "HEALTH", "OL", "WORLD", "OM", "NEXT", "ON", "USED", + "OO", "WORK", "OP", "LAST", "OQ", "MOST", "OR", "PRODUCTS", "OS", "MUSIC", + "OT", "DATA", "OU", "MAKE", "OV", "THEM", "OW", "SHOULD", "OX", "PRODUCT", + "OY", "SYSTEM", "OZ", "POST", "O0", "CITY", "O1", "POLICY", "O2", "NUMBER", + "O3", "SUCH", "O4", "PLEASE", "O5", "AVAILABLE", "O6", "COPYRIGHT", "O7", "SUPPORT", + "O8", "MESSAGE", "O9", "AFTER", "O!", "BEST", "O\"", "SOFTWARE", "O#", "THEN", + "O$", "GOOD", "O%", "VIDEO", "O&", "WELL", "O[", "WHERE", "O]", "INFO", + "O ", "RIGHTS", "O_", "PUBLIC", "PA", "BOOKS", "PB", "HIGH", "PC", "SCHOOL", + "PD", "THROUGH", "PE", "EACH", "PF", "LINKS", "PG", "REVIEW", "PH", "YEARS", + "PI", "ORDER", "PJ", "VERY", "PK", "PRIVACY", "PL", "BOOK", "PM", "ITEMS", + "PN", "COMPANY", "PO", "READ", "PP", "GROUP", "PQ", "NEED", "PR", "MANY", + "PS", "USER", "PT", "SAID", "PU", "DOES", "PV", "UNDER", "PW", "GENERAL", + "PX", "RESEARCH", "PY", "UNIVERSITY", "PZ", "JANUARY", "P0", "MAIL", "P1", "FULL", + "P2", "REVIEWS", "P3", "PROGRAM", "P4", "LIFE", "P5", "KNOW", "P6", "GAMES", + "P7", "DAYS", "P8", "MANAGEMENT", "P9", "PART", "P!", "COULD", "P\"", "GREAT", + "P#", "UNITED", "P$", "HOTEL", "P%", "REAL", "P&", "ITEM", "P[", "INTERNATIONAL", + "P]", "CENTER", "P ", "EBAY", "P_", "MUST", "QA", "STORE", "QB", "TRAVEL", + "QC", "COMMENTS", "QD", "MADE", "QE", "DEVELOPMENT", "QF", "REPORT", "QG", "MEMBER", + "QH", "DETAILS", "QI", "LINE", "QJ", "TERMS", "QK", "BEFORE", "QL", "HOTELS", + "QM", "SEND", "QN", "RIGHT", "QO", "TYPE", "QP", "BECAUSE", "QQ", "LOCAL", + "QR", "THOSE", "QS", "USING", "QT", "RESULTS", "QU", "OFFICE", "QV", "EDUCATION", + "QW", "NATIONAL", "QX", "DESIGN", "QY", "TAKE", "QZ", "POSTED", "Q0", "INTERNET", + "Q1", "ADDRESS", "Q2", "COMMUNITY", "Q3", "WITHIN", "Q4", "STATES", "Q5", "AREA", + "Q6", "WANT", "Q7", "PHONE", "Q8", "SHIPPING", "Q9", "RESERVED", "Q!", "SUBJECT", + "Q\"", "BETWEEN", "Q#", "FORUM", "Q$", "FAMILY", "Q%", "LONG", "Q&", "BASED", + "Q[", "CODE", "Q]", "SHOW", "Q ", "EVEN", "Q_", "BLACK", "RA", "CHECK", + "RB", "SPECIAL", "RC", "PRICES", "RD", "WEBSITE", "RE", "INDEX", "RF", "BEING", + "RG", "WOMEN", "RH", "MUCH", "RI", "SIGN", "RJ", "FILE", "RK", "LINK", + "RL", "OPEN", "RM", "TODAY", "RN", "TECHNOLOGY", "RO", "SOUTH", "RP", "CASE", + "RQ", "PROJECT", "RR", "SAME", "RS", "PAGES", "RT", "VERSION", "RU", "SECTION", + "RV", "FOUND", "RW", "SPORTS", "RX", "HOUSE", "RY", "RELATED", "RZ", "SECURITY", + "R0", "BOTH", "R1", "COUNTY", "R2", "AMERICAN", "R3", "PHOTO", "R4", "GAME", + "R5", "MEMBERS", "R6", "POWER", "R7", "WHILE", "R8", "CARE", "R9", "NETWORK", + "R!", "DOWN", "R\"", "COMPUTER", "R#", "SYSTEMS", "R$", "THREE", "R%", "TOTAL", + "R&", "PLACE", "R[", "FOLLOWING", "R]", "DOWNLOAD", "R ", "WITHOUT", "R_", "ACCESS", + "SA", "THINK", "SB", "NORTH", "SC", "RESOURCES", "SD", "CURRENT", "SE", "POSTS", + "SF", "MEDIA", "SG", "CONTROL", "SH", "WATER", "SI", "HISTORY", "SJ", "PICTURES", + "SK", "SIZE", "SL", "PERSONAL", "SM", "SINCE", "SN", "INCLUDING", "SO", "GUIDE", + "SP", "SHOP", "SQ", "DIRECTORY", "SR", "BOARD", "SS", "LOCATION", "ST", "CHANGE", + "SU", "WHITE", "SV", "TEXT", "SW", "SMALL", "SX", "RATING", "SY", "RATE", + "SZ", "GOVERNMENT", "S0", "CHILDREN", "S1", "DURING", "S2", "RETURN", "S3", "STUDENTS", + "S4", "SHOPPING", "S5", "ACCOUNT", "S6", "TIMES", "S7", "SITES", "S8", "LEVEL", + "S9", "DIGITAL", "S!", "PROFILE", "S\"", "PREVIOUS", "S#", "FORM", "S$", "EVENTS", + "S%", "LOVE", "S&", "JOHN", "S[", "MAIN", "S]", "CALL", "S ", "HOURS", + "S_", "IMAGE", "TA", "DEPARTMENT", "TB", "TITLE", "TC", "DESCRIPTION", "TD", "INSURANCE", + "TE", "ANOTHER", "TF", "SHALL", "TG", "PROPERTY", "TH", "CLASS", "TI", "STILL", + "TJ", "MONEY", "TK", "QUALITY", "TL", "EVERY", "TM", "LISTING", "TN", "CONTENT", + "TO", "COUNTRY", "TP", "PRIVATE", "TQ", "LITTLE", "TR", "VISIT", "TS", "SAVE", + "TT", "TOOLS", "TU", "REPLY", "TV", "CUSTOMER", "TW", "DECEMBER", "TX", "COMPARE", + "TY", "MOVIES", "TZ", "INCLUDE", "T0", "COLLEGE", "T1", "VALUE", "T2", "ARTICLE", + "T3", "YORK", "T4", "CARD", "T5", "JOBS", "T6", "PROVIDE", "T7", "FOOD", + "T8", "SOURCE", "T9", "AUTHOR", "T!", "DIFFERENT", "T\"", "PRESS", "T#", "LEARN", + "T$", "SALE", "T%", "AROUND", "T&", "PRINT", "T[", "COURSE", "T]", "CANADA", + "T ", "PROCESS", "T_", "TEEN", "UA", "ROOM", "UB", "STOCK", "UC", "TRAINING", + "UD", "CREDIT", "UE", "POINT", "UF", "JOIN", "UG", "SCIENCE", "UH", "CATEGORIES", + "UI", "ADVANCED", "UJ", "WEST", "UK", "SALES", "UL", "LOOK", "UM", "ENGLISH", + "UN", "LEFT", "UO", "TEAM", "UP", "ESTATE", "UQ", "CONDITIONS", "UR", "SELECT", + "US", "WINDOWS", "UT", "PHOTOS", "UU", "THREAD", "UV", "WEEK", "UW", "CATEGORY", + "UX", "NOTE", "UY", "LIVE", "UZ", "LARGE", "U0", "GALLERY", "U1", "TABLE", + "U2", "REGISTER", "U3", "HOWEVER", "U4", "JUNE", "U5", "OCTOBER", "U6", "NOVEMBER", + "U7", "MARKET", "U8", "LIBRARY", "U9", "REALLY", "U!", "ACTION", "U\"", "START", + "U#", "SERIES", "U$", "MODEL", "U%", "FEATURES", "U&", "INDUSTRY", "U[", "PLAN", + "U]", "HUMAN", "U ", "PROVIDED", "U_", "REQUIRED", "VA", "SECOND", "VB", "ACCESSORIES", + "VC", "COST", "VD", "MOVIE", "VE", "FORUMS", "VF", "MARCH", "VG", "SEPTEMBER", + "VH", "BETTER", "VI", "QUESTIONS", "VJ", "JULY", "VK", "YAHOO", "VL", "GOING", + "VM", "MEDICAL", "VN", "TEST", "VO", "FRIEND", "VP", "COME", "VQ", "SERVER", + "VR", "STUDY", "VS", "APPLICATION", "VT", "CART", "VU", "STAFF", "VV", "ARTICLES", + "VW", "FEEDBACK", "VX", "AGAIN", "VY", "PLAY", "VZ", "LOOKING", "V0", "ISSUES", + "V1", "APRIL", "V2", "NEVER", "V3", "USERS", "V4", "COMPLETE", "V5", "STREET", + "V6", "TOPIC", "V7", "COMMENT", "V8", "FINANCIAL", "V9", "THINGS", "V!", "WORKING", + "V\"", "AGAINST", "V#", "STANDARD", "V$", "PERSON", "V%", "BELOW", "V&", "MOBILE", + "V[", "LESS", "V]", "BLOG", "V ", "PARTY", "V_", "PAYMENT", "WA", "EQUIPMENT", + "WB", "LOGIN", "WC", "STUDENT", "WD", "PROGRAMS", "WE", "OFFERS", "WF", "LEGAL", + "WG", "ABOVE", "WH", "RECENT", "WI", "PARK", "WJ", "STORES", "WK", "SIDE", + "WL", "PROBLEM", "WM", "GIVE", "WN", "MEMORY", "WO", "PERFORMANCE", "WP", "SOCIAL", + "WQ", "AUGUST", "WR", "QUOTE", "WS", "LANGUAGE", "WT", "STORY", "WU", "SELL", + "WV", "OPTIONS", "WW", "EXPERIENCE", "WX", "RATES", "WY", "CREATE", "WZ", "BODY", + "W0", "YOUNG", "W1", "AMERICA", "W2", "IMPORTANT", "W3", "FIELD", "W4", "EAST", + "W5", "PAPER", "W6", "SINGLE", "W7", "ACTIVITIES", "W8", "CLUB", "W9", "EXAMPLE", + "W!", "GIRLS", "W\"", "ADDITIONAL", "W#", "PASSWORD", "W$", "LATEST", "W%", "SOMETHING", + "W&", "ROAD", "W[", "GIFT", "W]", "QUESTION", "W ", "CHANGES", "W_", "NIGHT", + "XA", "HARD", "XB", "TEXAS", "XC", "FOUR", "XD", "POKER", "XE", "STATUS", + "XF", "BROWSE", "XG", "ISSUE", "XH", "RANGE", "XI", "BUILDING", "XJ", "SELLER", + "XK", "COURT", "XL", "FEBRUARY", "XM", "ALWAYS", "XN", "RESULT", "XO", "AUDIO", + "XP", "LIGHT", "XQ", "WRITE", "XR", "OFFER", "XS", "BLUE", "XT", "GROUPS", + "XU", "EASY", "XV", "GIVEN", "XW", "FILES", "XX", "EVENT", "XY", "RELEASE", + "XZ", "ANALYSIS", "X0", "REQUEST", "X1", "CHINA", "X2", "MAKING", "X3", "PICTURE", + "X4", "NEEDS", "X5", "POSSIBLE", "X6", "MIGHT", "X7", "PROFESSIONAL", "X8", "MONTH", + "X9", "MAJOR", "X!", "STAR", "X\"", "AREAS", "X#", "FUTURE", "X$", "SPACE", + "X%", "COMMITTEE", "X&", "HAND", "X[", "CARDS", "X]", "PROBLEMS", "X ", "LONDON", + "X_", "WASHINGTON", "YA", "MEETING", "YB", "BECOME", "YC", "INTEREST", "YD", "CHILD", + "YE", "KEEP", "YF", "ENTER", "YG", "CALIFORNIA", "YH", "PORN", "YI", "SHARE", + "YJ", "SIMILAR", "YK", "GARDEN", "YL", "SCHOOLS", "YM", "MILLION", "YN", "ADDED", + "YO", "REFERENCE", "YP", "COMPANIES", "YQ", "LISTED", "YR", "BABY", "YS", "LEARNING", + "YT", "ENERGY", "YU", "DELIVERY", "YV", "POPULAR", "YW", "TERM", "YX", "FILM", + "YY", "STORIES", "YZ", "COMPUTERS", "Y0", "JOURNAL", "Y1", "REPORTS", "Y2", "WELCOME", + "Y3", "CENTRAL", "Y4", "IMAGES", "Y5", "PRESIDENT", "Y6", "NOTICE", "Y7", "ORIGINAL", + "Y8", "HEAD", "Y9", "RADIO", "Y!", "UNTIL", "Y\"", "CELL", "Y#", "COLOR", + "Y$", "SELF", "Y%", "COUNCIL", "Y&", "AWAY", "Y[", "INCLUDES", "Y]", "TRACK", + "Y ", "AUSTRALIA", "Y_", "DISCUSSION", "ZA", "ARCHIVE", "ZB", "ONCE", "ZC", "OTHERS", + "ZD", "ENTERTAINMENT", "ZE", "AGREEMENT", "ZF", "FORMAT", "ZG", "LEAST", "ZH", "SOCIETY", + "ZI", "MONTHS", "ZJ", "SAFETY", "ZK", "FRIENDS", "ZL", "SURE", "ZM", "TRADE", + "ZN", "EDITION", "ZO", "CARS", "ZP", "MESSAGES", "ZQ", "MARKETING", "ZR", "TELL", + "ZS", "FURTHER", "ZT", "UPDATED", "ZU", "ASSOCIATION", "ZV", "ABLE", "ZW", "HAVING", + "ZX", "PROVIDES", "ZY", "DAVID", "ZZ", "ALREADY", "Z0", "GREEN", "Z1", "STUDIES", + "Z2", "CLOSE", "Z3", "COMMON", "Z4", "DRIVE", "Z5", "SPECIFIC", "Z6", "SEVERAL", + "Z7", "GOLD", "Z8", "LIVING", "Z9", "COLLECTION", "Z!", "CALLED", "Z\"", "SHORT", + "Z#", "ARTS", "Z$", "DISPLAY", "Z%", "LIMITED", "Z&", "POWERED", "Z[", "SOLUTIONS", + "Z]", "MEANS", "Z ", "DIRECTOR", "Z_", "DAILY", "0A", "BEACH", "0B", "PAST", + "0C", "NATURAL", "0D", "WHETHER", "0E", "ELECTRONICS", "0F", "FIVE", "0G", "UPON", + "0H", "PERIOD", "0I", "PLANNING", "0J", "DATABASE", "0K", "SAYS", "0L", "OFFICIAL", + "0M", "WEATHER", "0N", "LAND", "0O", "AVERAGE", "0P", "DONE", "0Q", "TECHNICAL", + "0R", "WINDOW", "0S", "FRANCE", "0T", "REGION", "0U", "ISLAND", "0V", "RECORD", + "0W", "DIRECT", "0X", "MICROSOFT", "0Y", "CONFERENCE", "0Z", "ENVIRONMENT", "00", "RECORDS", + "01", "DISTRICT", "02", "CALENDAR", "03", "COSTS", "04", "STYLE", "05", "FRONT", + "06", "STATEMENT", "07", "UPDATE", "08", "PARTS", "09", "EVER", "0!", "DOWNLOADS", + "0\"", "EARLY", "0#", "MILES", "0$", "SOUND", "0%", "RESOURCE", "0&", "PRESENT", + "0[", "APPLICATIONS", "0]", "EITHER", "0 ", "DOCUMENT", "0_", "WORD", "1A", "WORKS", + "1B", "MATERIAL", "1C", "BILL", "1D", "WRITTEN", "1E", "TALK", "1F", "FEDERAL", + "1G", "HOSTING", "1H", "RULES", "1I", "FINAL", "1J", "ADULT", "1K", "TICKETS", + "1L", "THING", "1M", "CENTRE", "1N", "REQUIREMENTS", "1O", "CHEAP", "1P", "NUDE", + "1Q", "KIDS", "1R", "FINANCE", "1S", "TRUE", "1T", "MINUTES", "1U", "ELSE", + "1V", "MARK", "1W", "THIRD", "1X", "ROCK", "1Y", "GIFTS", "1Z", "EUROPE", + "10", "READING", "11", "TOPICS", "12", "INDIVIDUAL", "13", "TIPS", "14", "PLUS", + "15", "AUTO", "16", "COVER", "17", "USUALLY", "18", "EDIT", "19", "TOGETHER", + "1!", "VIDEOS", "1\"", "PERCENT", "1#", "FAST", "1$", "FUNCTION", "1%", "FACT", + "1&", "UNIT", "1[", "GETTING", "1]", "GLOBAL", "1 ", "TECH", "1_", "MEET", + "2A", "ECONOMIC", "2B", "PLAYER", "2C", "PROJECTS", "2D", "LYRICS", "2E", "OFTEN", + "2F", "SUBSCRIBE", "2G", "SUBMIT", "2H", "GERMANY", "2I", "AMOUNT", "2J", "WATCH", + "2K", "INCLUDED", "2L", "FEEL", "2M", "THOUGH", "2N", "BANK", "2O", "RISK", + "2P", "THANKS", "2Q", "EVERYTHING", "2R", "DEALS", "2S", "VARIOUS", "2T", "WORDS", + "2U", "LINUX", "2V", "PRODUCTION", "2W", "COMMERCIAL", "2X", "JAMES", "2Y", "WEIGHT", + "2Z", "TOWN", "20", "HEART", "21", "ADVERTISING", "22", "RECEIVED", "23", "CHOOSE", + "24", "TREATMENT", "25", "NEWSLETTER", "26", "ARCHIVES", "27", "POINTS", "28", "KNOWLEDGE", + "29", "MAGAZINE", "2!", "ERROR", "2\"", "CAMERA", "2#", "GIRL", "2$", "CURRENTLY", + "2%", "CONSTRUCTION", "2&", "TOYS", "2[", "REGISTERED", "2]", "CLEAR", "2 ", "GOLF", + "2_", "RECEIVE", "3A", "DOMAIN", "3B", "METHODS", "3C", "CHAPTER", "3D", "MAKES", + "3E", "PROTECTION", "3F", "POLICIES", "3G", "LOAN", "3H", "WIDE", "3I", "BEAUTY", + "3J", "MANAGER", "3K", "INDIA", "3L", "POSITION", "3M", "TAKEN", "3N", "SORT", + "3O", "LISTINGS", "3P", "MODELS", "3Q", "MICHAEL", "3R", "KNOWN", "3S", "HALF", + "3T", "CASES", "3U", "STEP", "3V", "ENGINEERING", "3W", "FLORIDA", "3X", "SIMPLE", + "3Y", "QUICK", "3Z", "NONE", "30", "WIRELESS", "31", "LICENSE", "32", "PAUL", + "33", "FRIDAY", "34", "LAKE", "35", "WHOLE", "36", "ANNUAL", "37", "PUBLISHED", + "38", "LATER", "39", "BASIC", "3!", "SONY", "3\"", "SHOWS", "3#", "CORPORATE", + "3$", "GOOGLE", "3%", "CHURCH", "3&", "METHOD", "3[", "PURCHASE", "3]", "CUSTOMERS", + "3 ", "ACTIVE", "3_", "RESPONSE", "4A", "PRACTICE", "4B", "HARDWARE", "4C", "FIGURE", + "4D", "MATERIALS", "4E", "FIRE", "4F", "HOLIDAY", "4G", "CHAT", "4H", "ENOUGH", + "4I", "DESIGNED", "4J", "ALONG", "4K", "AMONG", "4L", "DEATH", "4M", "WRITING", + "4N", "SPEED", "4O", "HTML", "4P", "COUNTRIES", "4Q", "LOSS", "4R", "FACE", + "4S", "BRAND", "4T", "DISCOUNT", "4U", "HIGHER", "4V", "EFFECTS", "4W", "CREATED", + "4X", "REMEMBER", "4Y", "STANDARDS", "4Z", "YELLOW", "40", "POLITICAL", "41", "INCREASE", + "42", "ADVERTISE", "43", "KINGDOM", "44", "BASE", "45", "NEAR", "46", "ENVIRONMENTAL", + "47", "THOUGHT", "48", "STUFF", "49", "FRENCH", "4!", "STORAGE", "4\"", "JAPAN", + "4#", "DOING", "4$", "LOANS", "4%", "SHOES", "4&", "ENTRY", "4[", "STAY", + "4]", "NATURE", "4 ", "ORDERS", "4_", "AVAILABILITY", "5A", "AFRICA", "5B", "SUMMARY", + "5C", "TURN", "5D", "MEAN", "5E", "GROWTH", "5F", "NOTES", "5G", "AGENCY", + "5H", "KING", "5I", "MONDAY", "5J", "EUROPEAN", "5K", "ACTIVITY", "5L", "COPY", + "5M", "ALTHOUGH", "5N", "DRUG", "5O", "PICS", "5P", "WESTERN", "5Q", "INCOME", + "5R", "FORCE", "5S", "CASH", "5T", "EMPLOYMENT", "5U", "OVERALL", "5V", "RIVER", + "5W", "COMMISSION", "5X", "PACKAGE", "5Y", "CONTENTS", "5Z", "SEEN", "50", "PLAYERS", + "51", "ENGINE", "52", "PORT", "53", "ALBUM", "54", "REGIONAL", "55", "STOP", + "56", "SUPPLIES", "57", "STARTED", "58", "ADMINISTRATION", "59", "INSTITUTE", "5!", "VIEWS", + "5\"", "PLANS", "5#", "DOUBLE", "5$", "BUILD", "5%", "SCREEN", "5&", "EXCHANGE", + "5[", "TYPES", "5]", "SOON", "5 ", "SPONSORED", "5_", "LINES", "6A", "ELECTRONIC", + "6B", "CONTINUE", "6C", "ACROSS", "6D", "BENEFITS", "6E", "NEEDED", "6F", "SEASON", + "6G", "APPLY", "6H", "SOMEONE", "6I", "HELD", "6J", "ANYTHING", "6K", "PRINTER", + "6L", "CONDITION", "6M", "EFFECTIVE", "6N", "BELIEVE", "6O", "ORGANIZATION", "6P", "EFFECT", + "6Q", "ASKED", "6R", "MIND", "6S", "SUNDAY", "6T", "SELECTION", "6U", "CASINO", + "6V", "LOST", "6W", "TOUR", "6X", "MENU", "6Y", "VOLUME", "6Z", "CROSS", + "60", "ANYONE", "61", "MORTGAGE", "62", "HOPE", "63", "SILVER", "64", "CORPORATION", + "65", "WISH", "66", "INSIDE", "67", "SOLUTION", "68", "MATURE", "69", "ROLE", + "6!", "RATHER", "6\"", "WEEKS", "6#", "ADDITION", "6$", "CAME", "6%", "SUPPLY", + "6&", "NOTHING", "6[", "CERTAIN", "6]", "EXECUTIVE", "6 ", "RUNNING", "6_", "LOWER", + "7A", "NECESSARY", "7B", "UNION", "7C", "JEWELRY", "7D", "ACCORDING", "7E", "CLOTHING", + "7F", "PARTICULAR", "7G", "FINE", "7H", "NAMES", "7I", "ROBERT", "7J", "HOMEPAGE", + "7K", "HOUR", "7L", "SKILLS", "7M", "BUSH", "7N", "ISLANDS", "7O", "ADVICE", + "7P", "CAREER", "7Q", "MILITARY", "7R", "RENTAL", "7S", "DECISION", "7T", "LEAVE", + "7U", "BRITISH", "7V", "TEENS", "7W", "HUGE", "7X", "WOMAN", "7Y", "FACILITIES", + "7Z", "KIND", "70", "SELLERS", "71", "MIDDLE", "72", "MOVE", "73", "CABLE", + "74", "OPPORTUNITIES", "75", "TAKING", "76", "VALUES", "77", "DIVISION", "78", "COMING", + "79", "TUESDAY", "7!", "OBJECT", "7\"", "LESBIAN", "7#", "APPROPRIATE", "7$", "MACHINE", + "7%", "LOGO", "7&", "LENGTH", "7[", "ACTUALLY", "7]", "NICE", "7 ", "SCORE", + "7_", "STATISTICS", "8A", "CLIENT", "8B", "RETURNS", "8C", "CAPITAL", "8D", "FOLLOW", + "8E", "SAMPLE", "8F", "INVESTMENT", "8G", "SENT", "8H", "SHOWN", "8I", "SATURDAY", + "8J", "CHRISTMAS", "8K", "ENGLAND", "8L", "CULTURE", "8M", "BAND", "8N", "FLASH", + "8O", "LEAD", "8P", "GEORGE", "8Q", "CHOICE", "8R", "WENT", "8S", "STARTING", + "8T", "REGISTRATION", "8U", "THURSDAY", "8V", "COURSES", "8W", "CONSUMER", "8X", "AIRPORT", + "8Y", "FOREIGN", "8Z", "ARTIST", "80", "OUTSIDE", "81", "FURNITURE", "82", "LEVELS", + "83", "CHANNEL", "84", "LETTER", "85", "MODE", "86", "PHONES", "87", "IDEAS", + "88", "WEDNESDAY", "89", "STRUCTURE", "8!", "FUND", "8\"", "SUMMER", "8#", "ALLOW", + "8$", "DEGREE", "8%", "CONTRACT", "8&", "BUTTON", "8[", "RELEASES", "8]", "HOMES", + "8 ", "SUPER", "8_", "MALE", "9A", "MATTER", "9B", "CUSTOM", "9C", "VIRGINIA", + "9D", "ALMOST", "9E", "TOOK", "9F", "LOCATED", "9G", "MULTIPLE", "9H", "ASIAN", + "9I", "DISTRIBUTION", "9J", "EDITOR", "9K", "INDUSTRIAL", "9L", "CAUSE", "9M", "POTENTIAL", + "9N", "SONG", "9O", "CNET", "9P", "FOCUS", "9Q", "LATE", "9R", "FALL", + "9S", "FEATURED", "9T", "IDEA", "9U", "ROOMS", "9V", "FEMALE", "9W", "RESPONSIBLE", + "9X", "COMMUNICATIONS", "9Y", "ASSOCIATED", "9Z", "THOMAS", "90", "PRIMARY", "91", "CANCER", + "92", "NUMBERS", "93", "REASON", "94", "TOOL", "95", "BROWSER", "96", "SPRING", + "97", "FOUNDATION", "98", "ANSWER", "99", "VOICE", "9!", "FRIENDLY", "9\"", "SCHEDULE", + "9#", "DOCUMENTS", "9$", "COMMUNICATION", "9%", "PURPOSE", "9&", "FEATURE", "9[", "COMES", + "9]", "POLICE", "9 ", "EVERYONE", "9_", "INDEPENDENT", "!A", "APPROACH", "!B", "CAMERAS", + "!C", "BROWN", "!D", "PHYSICAL", "!E", "OPERATING", "!F", "HILL", "!G", "MAPS", + "!H", "MEDICINE", "!I", "DEAL", "!J", "HOLD", "!K", "RATINGS", "!L", "CHICAGO", + "!M", "FORMS", "!N", "GLASS", "!O", "HAPPY", "!P", "SMITH", "!Q", "WANTED", + "!R", "DEVELOPED", "!S", "THANK", "!T", "SAFE", "!U", "UNIQUE", "!V", "SURVEY", + "!W", "PRIOR", "!X", "TELEPHONE", "!Y", "SPORT", "!Z", "READY", "!0", "FEED", + "!1", "ANIMAL", "!2", "SOURCES", "!3", "MEXICO", "!4", "POPULATION", "!5", "REGULAR", + "!6", "SECURE", "!7", "NAVIGATION", "!8", "OPERATIONS", "!9", "THEREFORE", "!!", "SIMPLY", + "!\"", "EVIDENCE", "!#", "STATION", "!$", "CHRISTIAN", "!%", "ROUND", "!&", "PAYPAL", + "![", "FAVORITE", "!]", "UNDERSTAND", "! ", "OPTION", "!_", "MASTER", "\"A", "VALLEY", + "\"B", "RECENTLY", "\"C", "PROBABLY", "\"D", "RENTALS", "\"E", "BUILT", "\"F", "PUBLICATIONS", + "\"G", "BLOOD", "\"H", "WORLDWIDE", "\"I", "IMPROVE", "\"J", "CONNECTION", "\"K", "PUBLISHER", + "\"L", "HALL", "\"M", "LARGER", "\"N", "ANTI", "\"O", "NETWORKS", "\"P", "EARTH", + "\"Q", "PARENTS", "\"R", "NOKIA", "\"S", "IMPACT", "\"T", "TRANSFER", "\"U", "INTRODUCTION", + "\"V", "KITCHEN", "\"W", "STRONG", "\"X", "CAROLINA", "\"Y", "WEDDING", "\"Z", "PROPERTIES", + "\"0", "HOSPITAL", "\"1", "GROUND", "\"2", "OVERVIEW", "\"3", "SHIP", "\"4", "ACCOMMODATION", + "\"5", "OWNERS", "\"6", "DISEASE", "\"7", "EXCELLENT", "\"8", "PAID", "\"9", "ITALY", + "\"!", "PERFECT", "\"\"", "HAIR", "\"#", "OPPORTUNITY", "\"$", "CLASSIC", "\"%", "BASIS", + "\"&", "COMMAND", "\"[", "CITIES", "\"]", "WILLIAM", "\" ", "EXPRESS", "\"_", "ANAL", + "#A", "AWARD", "#B", "DISTANCE", "#C", "TREE", "#D", "PETER", "#E", "ASSESSMENT", + "#F", "ENSURE", "#G", "THUS", "#H", "WALL", "#I", "INVOLVED", "#J", "EXTRA", + "#K", "ESPECIALLY", "#L", "INTERFACE", "#M", "PUSSY", "#N", "PARTNERS", "#O", "BUDGET", + "#P", "RATED", "#Q", "GUIDES", "#R", "SUCCESS", "#S", "MAXIMUM", "#T", "OPERATION", + "#U", "EXISTING", "#V", "QUITE", "#W", "SELECTED", "#X", "AMAZON", "#Y", "PATIENTS", + "#Z", "RESTAURANTS", "#0", "BEAUTIFUL", "#1", "WARNING", "#2", "WINE", "#3", "LOCATIONS", + "#4", "HORSE", "#5", "VOTE", "#6", "FORWARD", "#7", "FLOWERS", "#8", "STARS", + "#9", "SIGNIFICANT", "#!", "LISTS", "#\"", "TECHNOLOGIES", "##", "OWNER", "#$", "RETAIL", + "#%", "ANIMALS", "#&", "USEFUL", "#[", "DIRECTLY", "#]", "MANUFACTURER", "# ", "WAYS", + "#_", "PROVIDING", "$A", "RULE", "$B", "HOUSING", "$C", "TAKES", "$D", "BRING", + "$E", "CATALOG", "$F", "SEARCHES", "$G", "TRYING", "$H", "MOTHER", "$I", "AUTHORITY", + "$J", "CONSIDERED", "$K", "TOLD", "$L", "TRAFFIC", "$M", "PROGRAMME", "$N", "JOINED", + "$O", "INPUT", "$P", "STRATEGY", "$Q", "FEET", "$R", "AGENT", "$S", "VALID", + "$T", "MODERN", "$U", "SENIOR", "$V", "IRELAND", "$W", "SEXY", "$X", "TEACHING", + "$Y", "DOOR", "$Z", "GRAND", "$0", "TESTING", "$1", "TRIAL", "$2", "CHARGE", + "$3", "UNITS", "$4", "INSTEAD", "$5", "CANADIAN", "$6", "COOL", "$7", "NORMAL", + "$8", "WROTE", "$9", "ENTERPRISE", "$!", "SHIPS", "$\"", "ENTIRE", "$#", "EDUCATIONAL", + "$$", "LEADING", "$%", "METAL", "$&", "POSITIVE", "$[", "FITNESS", "$]", "CHINESE", + "$ ", "OPINION", "$_", "ASIA", "%A", "FOOTBALL", "%B", "ABSTRACT", "%C", "USES", + "%D", "OUTPUT", "%E", "FUNDS", "%F", "GREATER", "%G", "LIKELY", "%H", "DEVELOP", + "%I", "EMPLOYEES", "%J", "ARTISTS", "%K", "ALTERNATIVE", "%L", "PROCESSING", "%M", "RESPONSIBILITY", + "%N", "RESOLUTION", "%O", "JAVA", "%P", "GUEST", "%Q", "SEEMS", "%R", "PUBLICATION", + "%S", "PASS", "%T", "RELATIONS", "%U", "TRUST", "%V", "CONTAINS", "%W", "SESSION", + "%X", "MULTI", "%Y", "PHOTOGRAPHY", "%Z", "REPUBLIC", "%0", "FEES", "%1", "COMPONENTS", + "%2", "VACATION", "%3", "CENTURY", "%4", "ACADEMIC", "%5", "ASSISTANCE", "%6", "COMPLETED", + "%7", "SKIN", "%8", "GRAPHICS", "%9", "INDIAN", "%!", "PREV", "%\"", "MARY", + "%#", "EXPECTED", "%$", "RING", "%%", "GRADE", "%&", "DATING", "%[", "PACIFIC", + "%]", "MOUNTAIN", "% ", "ORGANIZATIONS", "%_", "FILTER", "&A", "MAILING", "&B", "VEHICLE", + "&C", "LONGER", "&D", "CONSIDER", "&E", "NORTHERN", "&F", "BEHIND", "&G", "PANEL", + "&H", "FLOOR", "&I", "GERMAN", "&J", "BUYING", "&K", "MATCH", "&L", "PROPOSED", + "&M", "DEFAULT", "&N", "REQUIRE", "&O", "IRAQ", "&P", "BOYS", "&Q", "OUTDOOR", + "&R", "DEEP", "&S", "MORNING", "&T", "OTHERWISE", "&U", "ALLOWS", "&V", "REST", + "&W", "PROTEIN", "&X", "PLANT", "&Y", "REPORTED", "&Z", "TRANSPORTATION", "&0", "POOL", + "&1", "MINI", "&2", "POLITICS", "&3", "PARTNER", "&4", "DISCLAIMER", "&5", "AUTHORS", + "&6", "BOARDS", "&7", "FACULTY", "&8", "PARTIES", "&9", "FISH", "&!", "MEMBERSHIP", + "&\"", "MISSION", "&#", "STRING", "&$", "SENSE", "&%", "MODIFIED", "&&", "PACK", + "&[", "RELEASED", "&]", "STAGE", "& ", "INTERNAL", "&_", "GOODS", "[A", "RECOMMENDED", + "[B", "BORN", "[C", "UNLESS", "[D", "RICHARD", "[E", "DETAILED", "[F", "JAPANESE", + "[G", "RACE", "[H", "APPROVED", "[I", "BACKGROUND", "[J", "TARGET", "[K", "EXCEPT", + "[L", "CHARACTER", "[M", "MAINTENANCE", "[N", "ABILITY", "[O", "MAYBE", "[P", "FUNCTIONS", + "[Q", "MOVING", "[R", "BRANDS", "[S", "PLACES", "[T", "PRETTY", "[U", "TRADEMARKS", + "[V", "PHENTERMINE", "[W", "SPAIN", "[X", "SOUTHERN", "[Y", "YOURSELF", "[Z", "WINTER", + "[0", "RAPE", "[1", "BATTERY", "[2", "YOUTH", "[3", "PRESSURE", "[4", "SUBMITTED", + "[5", "BOSTON", "[6", "INCEST", "[7", "DEBT", "[8", "KEYWORDS", "[9", "MEDIUM", + "[!", "TELEVISION", "[\"", "INTERESTED", "[#", "CORE", "[$", "BREAK", "[%", "PURPOSES", + "[&", "THROUGHOUT", "[[", "SETS", "[]", "DANCE", "[ ", "WOOD", "[_", "ITSELF", + "]A", "DEFINED", "]B", "PAPERS", "]C", "PLAYING", "]D", "AWARDS", "]E", "STUDIO", + "]F", "READER", "]G", "VIRTUAL", "]H", "DEVICE", "]I", "ESTABLISHED", "]J", "ANSWERS", + "]K", "RENT", "]L", "REMOTE", "]M", "DARK", "]N", "PROGRAMMING", "]O", "EXTERNAL", + "]P", "APPLE", "]Q", "REGARDING", "]R", "INSTRUCTIONS", "]S", "OFFERED", "]T", "THEORY", + "]U", "ENJOY", "]V", "REMOVE", "]W", "SURFACE", "]X", "MINIMUM", "]Y", "VISUAL", + "]Z", "HOST", "]0", "VARIETY", "]1", "TEACHERS", "]2", "ISBN", "]3", "MARTIN", + "]4", "MANUAL", "]5", "BLOCK", "]6", "SUBJECTS", "]7", "AGENTS", "]8", "INCREASED", + "]9", "REPAIR", "]!", "FAIR", "]\"", "CIVIL", "]#", "STEEL", "]$", "UNDERSTANDING", + "]%", "SONGS", "]&", "FIXED", "][", "WRONG", "]]", "BEGINNING", "] ", "HANDS", + "]_", "ASSOCIATES", " A", "FINALLY", " B", "UPDATES", " C", "DESKTOP", " D", "CLASSES", + " E", "PARIS", " F", "OHIO", " G", "GETS", " H", "SECTOR", " I", "CAPACITY", + " J", "REQUIRES", " K", "JERSEY", " L", "FULLY", " M", "FATHER", " N", "ELECTRIC", + " O", "INSTRUMENTS", " P", "QUOTES", " Q", "OFFICER", " R", "DRIVER", " S", "BUSINESSES", + " T", "DEAD", " U", "RESPECT", " V", "UNKNOWN", " W", "SPECIFIED", " X", "RESTAURANT", + " Y", "MIKE", " Z", "TRIP", " 0", "WORTH", " 1", "PROCEDURES", " 2", "POOR", + " 3", "TEACHER", " 4", "EYES", " 5", "RELATIONSHIP", " 6", "WORKERS", " 7", "FARM", + " 8", "HTTP://", " 9", "GEORGIA", " !", "PEACE", " \"", "TRADITIONAL", " #", "CAMPUS", + " $", "SHOWING", " %", "CREATIVE", " &", "COAST", " [", "BENEFIT", " ]", "PROGRESS", + " ", "FUNDING", " _", "DEVICES", "_A", "LORD", "_B", "GRANT", "_C", "AGREE", + "_D", "FICTION", "_E", "HEAR", "_F", "SOMETIMES", "_G", "WATCHES", "_H", "CAREERS", + "_I", "BEYOND", "_J", "GOES", "_K", "FAMILIES", "_L", "MUSEUM", "_M", "THEMSELVES", + "_N", "TRANSPORT", "_O", "INTERESTING", "_P", "BLOGS", "_Q", "WIFE", "_R", "EVALUATION", + "_S", "ACCEPTED", "_T", "FORMER", "_U", "IMPLEMENTATION", "_V", "HITS", "_W", "ZONE", + "_X", "COMPLEX", "_Y", "GALLERIES", "_Z", "REFERENCES", "_0", "PRESENTED", "_1", "JACK", + "_2", "FLAT", "_3", "FLOW", "_4", "AGENCIES", "_5", "LITERATURE", "_6", "RESPECTIVE", + "_7", "PARENT", "_8", "SPANISH", "_9", "MICHIGAN", "_!", "COLUMBIA", "_\"", "SETTING", + "_#", "SCALE", "_$", "STAND", "_%", "ECONOMY", "_&", "HIGHEST", "_[", "HELPFUL", + "_]", "MONTHLY", "_ ", "CRITICAL", "__", "FRAME" + }; + private static final Map wordsToCodes = new HashMap(); + private static final Map codesToWords = new HashMap(); + + static { + for (int i = 0; i < wordsAndCodes.length; i = i + 2) { + String code = wordsAndCodes[i]; + String word = wordsAndCodes[i + 1]; + wordsToCodes.put(word, code); + codesToWords.put(code, word); + } + } + + public static String encode(String word) { + if (wordsToCodes.containsKey(word)) { + return "<" + wordsToCodes.get(word); + } + return word; + } + + public static String decode(String code1) { + if (!code1.startsWith("<")) { + return code1; + } + String code = code1.substring(1); + if (codesToWords.containsKey(code)) { + return codesToWords.get(code); + } + return code1; + } + + public static String lengthen(String result2) { + List shortCodesInString = wordsForPattern(shortCodesPattern, result2); + for (String w : shortCodesInString) { + String theWord = Dictionary.decode(w); + result2 = result2.replace(w, theWord); + } + List longCodesInString = wordsForPattern(longCodesPattern, result2); + for (String w : longCodesInString) { + String theWord = Dictionary.decode(w); + result2 = result2.replace(w, theWord); + } + return result2; + } + + public static String shorten(String str) { + List words = wordsForPattern(wordsPattern, str); + String result = str; + for (String w : words) { + String code = Dictionary.encode(w); + result = result.replace(w, code); + } + return result; + } + + public static List wordsForPattern(Pattern p, String str) { + Matcher m = p.matcher(str); + List words = new ArrayList(); + while (m.find()) { + words.add(m.group(0)); + } + + Collections.sort(words, byLength); + return words; + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/collection/KeyPair.java b/src/main/java/art/arcane/adapt/util/common/collection/KeyPair.java new file mode 100644 index 000000000..d37866759 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/collection/KeyPair.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.collection; + +/** + * Represents a keypair + * + * @param the key type + * @param the value type + * @author cyberpwn + */ +@SuppressWarnings("hiding") +public class KeyPair { + private K k; + private V v; + + /** + * Create a keypair + * + * @param k the key + * @param v the value + */ + public KeyPair(K k, V v) { + this.k = k; + this.v = v; + } + + public K getK() { + return k; + } + + public void setK(K k) { + this.k = k; + } + + public V getV() { + return v; + } + + public void setV(V v) { + this.v = v; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/format/AdventureCompat.java b/src/main/java/art/arcane/adapt/util/common/format/AdventureCompat.java new file mode 100644 index 000000000..f51924751 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/format/AdventureCompat.java @@ -0,0 +1,106 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.format; + +import art.arcane.adapt.Adapt; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class AdventureCompat { + private static final AtomicBoolean FALLBACK_NOTIFIED = new AtomicBoolean(false); + private static volatile boolean miniMessageCompatible = true; + + private AdventureCompat() { + } + + public static Component deserialize(String message) { + String source = normalize(message); + if (miniMessageCompatible) { + try { + return MiniMessage.miniMessage().deserialize(source); + } catch (Throwable e) { + markIncompatible(e); + } + } + + return LegacyComponentSerializer.legacySection().deserialize(C.translateAlternateColorCodes('&', stripTagsFallback(source))); + } + + public static Component deserializeNoProcessing(String message) { + String source = normalize(message); + if (miniMessageCompatible) { + try { + return MiniMessage.builder().postProcessor(c -> c).build().deserialize(source); + } catch (Throwable e) { + markIncompatible(e); + } + } + + return LegacyComponentSerializer.legacySection().deserialize(C.translateAlternateColorCodes('&', stripTagsFallback(source))); + } + + public static String stripTags(String message) { + String source = normalize(message); + if (miniMessageCompatible) { + try { + return MiniMessage.miniMessage().stripTags(source); + } catch (Throwable e) { + markIncompatible(e); + } + } + + return stripTagsFallback(source); + } + + public static String toLegacySection(String message) { + String source = normalize(message); + if (miniMessageCompatible) { + try { + return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(source)); + } catch (Throwable e) { + markIncompatible(e); + } + } + + return C.translateAlternateColorCodes('&', stripTagsFallback(source)); + } + + private static String normalize(String message) { + return message == null ? "" : message; + } + + private static String stripTagsFallback(String message) { + return message.replaceAll("<[^>]+>", ""); + } + + private static void markIncompatible(Throwable e) { + miniMessageCompatible = false; + if (FALLBACK_NOTIFIED.compareAndSet(false, true)) { + String reason = e == null ? "unknown" : e.getClass().getSimpleName(); + Adapt.warn("MiniMessage compatibility fallback enabled (" + reason + ")."); + if (e != null) { + Adapt.verbose("MiniMessage fallback reason: " + e.toString().toLowerCase(Locale.ROOT)); + } + } + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/format/C.java b/src/main/java/art/arcane/adapt/util/common/format/C.java new file mode 100644 index 000000000..67a48e810 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/format/C.java @@ -0,0 +1,701 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.format; + +import art.arcane.adapt.util.common.plugin.VolmitSender; +import art.arcane.volmlib.util.format.ColorFormatter; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import org.apache.commons.lang3.Validate; +import org.bukkit.ChatColor; +import org.bukkit.Color; +import org.bukkit.DyeColor; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Colors + * + * @author cyberpwn + */ +public enum C { + /** + * Represents black + */ + BLACK('0', 0x00) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BLACK; + } + }, + /** + * Represents dark blue + */ + DARK_BLUE('1', 0x1) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_BLUE; + } + }, + /** + * Represents dark green + */ + DARK_GREEN('2', 0x2) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_GREEN; + } + }, + /** + * Represents dark blue (aqua) + */ + DARK_AQUA('3', 0x3) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_AQUA; + } + }, + /** + * Represents dark red + */ + DARK_RED('4', 0x4) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_RED; + } + }, + /** + * Represents dark red + */ + ADAPT('4', 0x4) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_RED; + } + }, + /** + * Represents dark purple + */ + DARK_PURPLE('5', 0x5) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_PURPLE; + } + }, + /** + * Represents gold + */ + GOLD('6', 0x6) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GOLD; + } + }, + /** + * Represents gray + */ + GRAY('7', 0x7) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GRAY; + } + }, + /** + * Represents dark gray + */ + DARK_GRAY('8', 0x8) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.DARK_GRAY; + } + }, + /** + * Represents blue + */ + BLUE('9', 0x9) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BLUE; + } + }, + /** + * Represents green + */ + GREEN('a', 0xA) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.GREEN; + } + }, + /** + * Represents aqua + */ + AQUA('b', 0xB) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.AQUA; + } + }, + /** + * Represents red + */ + RED('c', 0xC) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RED; + } + }, + /** + * Represents light purple + */ + LIGHT_PURPLE('d', 0xD) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.LIGHT_PURPLE; + } + }, + /** + * Represents yellow + */ + YELLOW('e', 0xE) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.YELLOW; + } + }, + /** + * Represents white + */ + WHITE('f', 0xF) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.WHITE; + } + }, + /** + * Represents magical characters that change around randomly + */ + MAGIC("", 'k', 0x10, true) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.MAGIC; + } + }, + /** + * Makes the text bold. + */ + BOLD('l', 0x11, true) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.BOLD; + } + }, + /** + * Makes a line appear through the text. + */ + STRIKETHROUGH('m', 0x12, true) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.STRIKETHROUGH; + } + }, + /** + * Makes the text appear underlined. + */ + UNDERLINE("", 'n', 0x13, true) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.UNDERLINE; + } + }, + /** + * Makes the text italic. + */ + ITALIC('o', 0x14, true) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.ITALIC; + } + }, + + /** + * Resets all previous chat colors or formats. + */ + RESET('r', 0x15) { + @Override + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RESET; + } + }, + + + ; + /** + * The special character which prefixes all chat colour codes. Use this if you + * need to dynamically convert colour codes from your custom format. + */ + public static final char COLOR_CHAR = '\u00A7'; + public final static C[] COLORCYCLE = new C[]{C.GOLD, C.YELLOW, C.GREEN, C.AQUA, C.LIGHT_PURPLE, C.AQUA, C.GREEN, C.YELLOW, C.GOLD, C.RED}; + private final static C[] COLORS = new C[]{C.BLACK, C.DARK_BLUE, C.DARK_GREEN, C.DARK_AQUA, C.DARK_RED, C.DARK_PURPLE, C.GOLD, C.GRAY, C.DARK_GRAY, C.BLUE, C.GREEN, C.AQUA, C.RED, C.LIGHT_PURPLE, C.YELLOW, C.WHITE}; + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + private final static Map BY_ID = new HashMap<>(); + private final static Map BY_CHAR = new HashMap<>(); + private final static Map dyeChatMap = new HashMap<>(); + private final static Map chatHexMap = new HashMap<>(); + private final static Map dyeHexMap = new HashMap<>(); + + static { + chatHexMap.put(C.BLACK, "#000000"); + chatHexMap.put(C.DARK_BLUE, "#0000AA"); + chatHexMap.put(C.DARK_GREEN, "#00AA00"); + chatHexMap.put(C.DARK_AQUA, "#00AAAA"); + chatHexMap.put(C.DARK_RED, "#AA0000"); + chatHexMap.put(C.ADAPT, "#AA0000"); + chatHexMap.put(C.DARK_PURPLE, "#AA00AA"); + chatHexMap.put(C.GOLD, "#FFAA00"); + chatHexMap.put(C.GRAY, "#AAAAAA"); + chatHexMap.put(C.DARK_GRAY, "#555555"); + chatHexMap.put(C.BLUE, "#5555FF"); + chatHexMap.put(C.GREEN, "#55FF55"); + chatHexMap.put(C.AQUA, "#55FFFF"); + chatHexMap.put(C.RED, "#FF5555"); + chatHexMap.put(C.LIGHT_PURPLE, "#FF55FF"); + chatHexMap.put(C.YELLOW, "#FFFF55"); + chatHexMap.put(C.WHITE, "#FFFFFF"); + dyeChatMap.put(DyeColor.BLACK, C.DARK_GRAY); + dyeChatMap.put(DyeColor.BLUE, C.DARK_BLUE); + dyeChatMap.put(DyeColor.BROWN, C.GOLD); + dyeChatMap.put(DyeColor.CYAN, C.AQUA); + dyeChatMap.put(DyeColor.GRAY, C.GRAY); + dyeChatMap.put(DyeColor.GREEN, C.DARK_GREEN); + dyeChatMap.put(DyeColor.LIGHT_BLUE, C.BLUE); + dyeChatMap.put(DyeColor.LIME, C.GREEN); + dyeChatMap.put(DyeColor.MAGENTA, C.LIGHT_PURPLE); + dyeChatMap.put(DyeColor.ORANGE, C.GOLD); + dyeChatMap.put(DyeColor.PINK, C.LIGHT_PURPLE); + dyeChatMap.put(DyeColor.PURPLE, C.DARK_PURPLE); + dyeChatMap.put(DyeColor.RED, C.RED); + dyeChatMap.put(DyeColor.LIGHT_GRAY, C.GRAY); + dyeChatMap.put(DyeColor.WHITE, C.WHITE); + dyeChatMap.put(DyeColor.YELLOW, C.YELLOW); + dyeHexMap.put(DyeColor.BLACK, "#181414"); + dyeHexMap.put(DyeColor.BLUE, "#253193"); + dyeHexMap.put(DyeColor.BROWN, "#56331c"); + dyeHexMap.put(DyeColor.CYAN, "#267191"); + dyeHexMap.put(DyeColor.GRAY, "#414141"); + dyeHexMap.put(DyeColor.GREEN, "#364b18"); + dyeHexMap.put(DyeColor.LIGHT_BLUE, "#6387d2"); + dyeHexMap.put(DyeColor.LIME, "#39ba2e"); + dyeHexMap.put(DyeColor.MAGENTA, "#be49c9"); + dyeHexMap.put(DyeColor.ORANGE, "#ea7e35"); + dyeHexMap.put(DyeColor.PINK, "#d98199"); + dyeHexMap.put(DyeColor.PURPLE, "#7e34bf"); + dyeHexMap.put(DyeColor.RED, "#9e2b27"); + dyeHexMap.put(DyeColor.LIGHT_GRAY, "#a0a7a7"); + dyeHexMap.put(DyeColor.WHITE, "#a4a4a4"); + dyeHexMap.put(DyeColor.YELLOW, "#c2b51c"); + } + + static { + for (C color : values()) { + BY_ID.put(color.intCode, color); + BY_CHAR.put(color.code, color); + } + } + + private final int intCode; + private final char code; + private final String token; + private final boolean isFormat; + private final String toString; + + C(char code, int intCode) { + this("^", code, intCode, false); + } + + C(String token, char code, int intCode) { + this(token, code, intCode, false); + } + + C(char code, int intCode, boolean isFormat) { + this("^", code, intCode, isFormat); + } + + C(String token, char code, int intCode, boolean isFormat) { + this.code = code; + this.token = token.equalsIgnoreCase("^") ? "<" + name().toLowerCase(Locale.ROOT) + ">" : token; + this.intCode = intCode; + this.isFormat = isFormat; + this.toString = new String(new char[]{COLOR_CHAR, code}); + } + + public static float[] spin(float[] c, int shift) { + return new float[]{spin(c[0], shift), spinc(c[1], shift), spinc(c[2], shift)}; + } + + public static float[] spin(float[] c, int a, int b, int d) { + return new float[]{spin(c[0], a), spinc(c[1], b), spinc(c[2], d)}; + } + + public static float spin(float c, int shift) { + float g = ((((int) Math.floor(c * 360)) + shift) % 360) / 360F; + return g < 0 ? 1f - g : g; + } + + public static float spinc(float c, int shift) { + float g = ((((int) Math.floor(c * 255)) + shift)) / 255F; + return Math.max(0f, Math.min(g, 1f)); + } + + public static java.awt.Color spin(java.awt.Color c, int h, int s, int b) { + float[] hsb = java.awt.Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); + hsb = spin(hsb, h, s, b); + return java.awt.Color.getHSBColor(hsb[0], hsb[1], hsb[2]); + } + + public static String spinToHex(C color, int h, int s, int b) { + return "#" + Integer.toHexString(spin(color.awtColor(), h, s, b).getRGB()).substring(2); + } + + public static String aura(String s, int hrad, int srad, int vrad) { + return aura(s, hrad, srad, vrad, 0.3D); + } + + public static String aura(String s, int hrad, int srad, int vrad, double pulse) { + StringBuilder b = new StringBuilder(); + boolean c = false; + + for (char i : s.toCharArray()) { + if (c) { + c = false; + + C o = C.getByChar(i); + + if (o.isColor() && (hrad != 0 || srad != 0 || vrad != 0)) { + if (pulse > 0) { + b.append(VolmitSender.pulse(spinToHex(o, hrad, srad, vrad), spinToHex(o, -hrad, -srad, -vrad), pulse)); + } else { + b.append(""); + } + } else { + b.append(C.getByChar(i).token); + } + + continue; + } + + if (i == C.COLOR_CHAR) { + c = true; + continue; + } + + b.append(i); + } + + return b.toString(); + } + + public static String compress(String c) { + return BaseComponent.toLegacyText(TextComponent.fromLegacyText(c)); + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link org.bukkit.ChatColor} with the given code, or + * null if it doesn't exist + */ + public static C getByChar(char code) { + try { + C c = BY_CHAR.get(code); + return c == null ? C.WHITE : c; + } catch (Exception e) { + return C.WHITE; + } + } + + /** + * Gets the color represented by the specified color code + * + * @param code Code to check + * @return Associative {@link org.bukkit.ChatColor} with the given code, or + * null if it doesn't exist + */ + public static C getByChar(String code) { + try { + Validate.notNull(code, "Code cannot be null"); + Validate.isTrue(code.length() > 0, "Code must have at least one char"); + + return BY_CHAR.get(code.charAt(0)); + } catch (Exception e) { + return C.WHITE; + } + } + + /** + * Strips the given message of all color codes + * + * @param input String to strip of color + * @return A copy of the input string, without any coloring + */ + public static String stripColor(final String input) { + return ColorFormatter.stripColor(input); + } + + /** + * DyeColor to ChatColor + * + * @param dclr the dye color + * @return the color + */ + public static C dyeToChat(DyeColor dclr) { + if (dyeChatMap.containsKey(dclr)) { + return dyeChatMap.get(dclr); + } + + return C.MAGIC; + } + + public static DyeColor chatToDye(ChatColor color) { + for (Map.Entry entry : dyeChatMap.entrySet()) { + if (entry.getValue().toString().equals(color.toString())) { + return entry.getKey(); + } + } + + return DyeColor.BLACK; + } + + @SuppressWarnings("unlikely-arg-type") + public static String chatToHex(C clr) { + if (chatHexMap.containsKey(clr)) { + return chatHexMap.get(clr); + } + + return "#000000"; + } + + public static String dyeToHex(DyeColor clr) { + if (dyeHexMap.containsKey(clr)) { + return dyeHexMap.get(clr); + } + + return "#000000"; + } + + public static Color hexToColor(String hex) { + if (hex.startsWith("#")) { + hex = hex.substring(1); + } + + if (hex.contains("x")) { + hex = hex.substring(hex.indexOf("x")); + } + + if (hex.length() != 6 && hex.length() != 3) { + return null; + } + int sz = hex.length() / 3, mult = 1 << ((2 - sz) * 4), x = 0; + + for (int i = 0, z = 0; z < hex.length(); ++i, z += sz) { + x |= (mult * Integer.parseInt(hex.substring(z, z + sz), 16)) << (i * 8); + } + + return Color.fromBGR(x & 0xffffff); + } + + public static Color rgbToColor(String rgb) { + String[] parts = rgb.split("[^0-9]+"); + if (parts.length < 3) { + return null; + } + + int x = 0, i; + + for (i = 0; i < 3; ++i) { + x |= Integer.parseInt(parts[i]) << (i * 8); + } + + return Color.fromBGR(x & 0xffffff); + } + + public static String generateColorTable() { + StringBuilder str = new StringBuilder(); + + str.append(""); + + for (Map.Entry e : chatHexMap.entrySet()) { + str.append(String.format("" + "", e.getKey().name(), e.getValue())); + } + + str.append("
Chat ColorColor
%1$sTest String
"); + str.append(""); + for (Map.Entry e : dyeHexMap.entrySet()) { + str.append(String.format("" + "", e.getKey().name(), e.getValue())); + } + + str.append("
Dye ColorColor
%1$sTest String
"); + + return str.toString(); + } + + /** + * Translates a string using an alternate color code character into a string + * that uses the internal ChatColor.COLOR_CODE color code character. The + * alternate color code character will only be replaced if it is immediately + * followed by 0-9, A-F, a-f, K-O, k-o, R or r. + * + * @param altColorChar The alternate color code character to replace. Ex: + * {@literal &} + * @param textToTranslate Text containing the alternate color code character. + * @return Text containing the ChatColor.COLOR_CODE color code character. + */ + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { + return ColorFormatter.translateAlternateColorCodes(altColorChar, textToTranslate); + } + + public static C fromItemMeta(byte c) { + for (C i : C.values()) { + if (i.getItemMeta() == c) { + return i; + } + } + + return null; + } + + public static C randomColor() { + return COLORS[(int) (Math.random() * (COLORS.length - 1))]; + } + + /** + * Gets the ChatColors used at the end of the given input string. + * + * @param input Input string to retrieve the colors from. + * @return Any remaining ChatColors to pass onto the next line. + */ + public static String getLastColors(String input) { + return ColorFormatter.getLastColors(input); + } + + public net.md_5.bungee.api.ChatColor asBungee() { + return net.md_5.bungee.api.ChatColor.RESET; + } + + /** + * Gets the char value associated with this color + * + * @return A char value of this color code + */ + public char getChar() { + return code; + } + + @Override + public String toString() { + return intCode == -1 ? token : toString; + } + + /** + * get the dye color for the chatcolor + */ + public DyeColor dye() { + return chatToDye(chatColor()); + } + + public String hex() { + return chatToHex(this); + } + + public java.awt.Color awtColor() { + return java.awt.Color.decode(hex()); + } + + /** + * Checks if this code is a format code as opposed to a color code. + * + * @return whether this ChatColor is a format code + */ + public boolean isFormat() { + return isFormat; + } + + /** + * Checks if this code is a color code as opposed to a format code. + * + * @return whether this ChatColor is a color code + */ + public boolean isColor() { + return !isFormat && this != RESET; + } + + /** + * Get the ChatColor enum instance instead of C + */ + public ChatColor chatColor() { + return ChatColor.getByChar(code); + } + + public byte getMeta() { + return switch (this) { + case AQUA -> (byte) 11; + case BLACK -> (byte) 0; + case BLUE, DARK_AQUA -> (byte) 9; + case BOLD, UNDERLINE, STRIKETHROUGH, RESET, MAGIC, ITALIC -> (byte) -1; + case DARK_BLUE -> (byte) 1; + case DARK_GRAY -> (byte) 8; + case DARK_GREEN -> (byte) 2; + case DARK_PURPLE -> (byte) 5; + case DARK_RED -> (byte) 4; + case GOLD -> (byte) 6; + case GRAY -> (byte) 7; + case GREEN -> (byte) 10; + case LIGHT_PURPLE -> (byte) 13; + case RED -> (byte) 12; + case YELLOW -> (byte) 14; + default -> (byte) 15; + }; + } + + public byte getItemMeta() { + return switch (this) { + case AQUA, DARK_AQUA -> (byte) 9; + case BLUE -> (byte) 3; + case BOLD, UNDERLINE, RESET, STRIKETHROUGH, MAGIC, ITALIC -> (byte) -1; + case DARK_BLUE -> (byte) 11; + case DARK_GRAY -> (byte) 7; + case DARK_GREEN -> (byte) 13; + case DARK_PURPLE -> (byte) 10; + case DARK_RED, RED -> (byte) 14; + case GOLD, YELLOW -> (byte) 4; + case GRAY -> (byte) 8; + case GREEN -> (byte) 5; + case LIGHT_PURPLE -> (byte) 2; + case WHITE -> (byte) 0; + default -> (byte) 15; + }; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/format/HiddenStringUtils.java b/src/main/java/art/arcane/adapt/util/common/format/HiddenStringUtils.java new file mode 100644 index 000000000..24191f961 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/format/HiddenStringUtils.java @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.format; + +import org.bukkit.ChatColor; + +import java.nio.charset.StandardCharsets; + +public class HiddenStringUtils { + + // String constants. TODO Change them to something unique to avoid conflict with other plugins! + private static final String SEQUENCE_HEADER = "" + ChatColor.RESET + ChatColor.UNDERLINE + ChatColor.RESET; + private static final String SEQUENCE_FOOTER = "" + ChatColor.RESET + ChatColor.ITALIC + ChatColor.RESET; + + public static String encodeString(String hiddenString) { + return quote(stringToColors(hiddenString)); + } + + public static boolean hasHiddenString(String input) { + if (input == null) return false; + + return input.indexOf(SEQUENCE_HEADER) > -1 && input.indexOf(SEQUENCE_FOOTER) > -1; + } + + public static String extractHiddenString(String input) { + return colorsToString(extract(input)); + } + + + public static String replaceHiddenString(String input, String hiddenString) { + if (input == null) return null; + + int start = input.indexOf(SEQUENCE_HEADER); + int end = input.indexOf(SEQUENCE_FOOTER); + + if (start < 0 || end < 0) { + return null; + } + + return input.substring(0, start + SEQUENCE_HEADER.length()) + stringToColors(hiddenString) + input.substring(end); + } + + /** + * Internal stuff. + */ + private static String quote(String input) { + if (input == null) return null; + return SEQUENCE_HEADER + input + SEQUENCE_FOOTER; + } + + private static String extract(String input) { + if (input == null) return null; + + int start = input.indexOf(SEQUENCE_HEADER); + int end = input.indexOf(SEQUENCE_FOOTER); + + if (start < 0 || end < 0) { + return null; + } + + return input.substring(start + SEQUENCE_HEADER.length(), end); + } + + private static String stringToColors(String normal) { + if (normal == null) return null; + + byte[] bytes = normal.getBytes(StandardCharsets.UTF_8); + char[] chars = new char[bytes.length * 4]; + + for (int i = 0; i < bytes.length; i++) { + char[] hex = byteToHex(bytes[i]); + chars[i * 4] = ChatColor.COLOR_CHAR; + chars[i * 4 + 1] = hex[0]; + chars[i * 4 + 2] = ChatColor.COLOR_CHAR; + chars[i * 4 + 3] = hex[1]; + } + + return new String(chars); + } + + private static String colorsToString(String colors) { + if (colors == null) return null; + + colors = colors.toLowerCase().replace("" + ChatColor.COLOR_CHAR, ""); + + if (colors.length() % 2 != 0) { + colors = colors.substring(0, (colors.length() / 2) * 2); + } + + char[] chars = colors.toCharArray(); + byte[] bytes = new byte[chars.length / 2]; + + for (int i = 0; i < chars.length; i += 2) { + bytes[i / 2] = hexToByte(chars[i], chars[i + 1]); + } + + return new String(bytes, StandardCharsets.UTF_8); + } + + private static int hexToUnsignedInt(char c) { + if (c >= '0' && c <= '9') { + return c - 48; + } else if (c >= 'a' && c <= 'f') { + return c - 87; + } else { + throw new IllegalArgumentException("Invalid hex char: out of range"); + } + } + + private static char unsignedIntToHex(int i) { + if (i >= 0 && i <= 9) { + return (char) (i + 48); + } else if (i >= 10 && i <= 15) { + return (char) (i + 87); + } else { + throw new IllegalArgumentException("Invalid hex int: out of range"); + } + } + + private static byte hexToByte(char hex1, char hex0) { + return (byte) (((hexToUnsignedInt(hex1) << 4) | hexToUnsignedInt(hex0)) + Byte.MIN_VALUE); + } + + private static char[] byteToHex(byte b) { + int unsignedByte = (int) b - Byte.MIN_VALUE; + return new char[]{unsignedIntToHex((unsignedByte >> 4) & 0xf), unsignedIntToHex(unsignedByte & 0xf)}; + } + +} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/ING.java b/src/main/java/art/arcane/adapt/util/common/format/ING.java similarity index 89% rename from src/main/java/com/volmit/adapt/util/ING.java rename to src/main/java/art/arcane/adapt/util/common/format/ING.java index 5ed88895d..e6c27e9d6 100644 --- a/src/main/java/com/volmit/adapt/util/ING.java +++ b/src/main/java/art/arcane/adapt/util/common/format/ING.java @@ -16,10 +16,12 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.format; + +import art.arcane.volmlib.util.math.RNG; public class ING { - public ING(RNG rng) { + public ING(RNG rng) { - } + } } diff --git a/src/main/java/art/arcane/adapt/util/common/format/Localizer.java b/src/main/java/art/arcane/adapt/util/common/format/Localizer.java new file mode 100644 index 000000000..124f223a0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/format/Localizer.java @@ -0,0 +1,343 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.format; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.util.config.ConfigFileSupport; +import art.arcane.volmlib.util.io.IO; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import lombok.SneakyThrows; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.Objects; + +public class Localizer { + private static final Object LANGUAGE_CACHE_LOCK = new Object(); + private static final String LANGUAGE_COLOR_NOTE = String.join("\n", + "# Color Codes:", + "# - Legacy format: &0..&f, &k..&o, &r (example: \"&7Gray\")", + "# - Hex format: &#RRGGBB or &x&R&R&G&G&B&B (example: \"7FFAAMint\")", + "# - MiniMessage also works (example: \"<#55FFAA>Mint\")", + "" + ); + private static String cachedPrimaryLanguage; + private static JsonObject cachedPrimaryLanguageRoot; + private static String cachedFallbackLanguage; + private static JsonObject cachedFallbackLanguageRoot; + + @SneakyThrows + public static void updateLanguageFile() { + if (AdaptConfig.get().isAutoUpdateLanguage()) { + Adapt.verbose("Attempting to update Language File"); + File langFolder = new File(Adapt.instance.getDataFolder() + "/languages"); + if (!langFolder.exists()) { + langFolder.mkdirs(); + } + + Adapt.verbose("Updating Primary Language File: " + AdaptConfig.get().getLanguage()); + syncLanguageResource(langFolder, AdaptConfig.get().getLanguage()); + Adapt.verbose("Loaded Primary Language: " + AdaptConfig.get().getLanguage()); + + if (!Objects.equals(AdaptConfig.get().getLanguage(), AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing())) { + Adapt.verbose("Updating Fallback Language File: " + AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing()); + syncLanguageResource(langFolder, AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing()); + Adapt.verbose("Loaded Fallback: " + AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing()); + } + + migrateExistingLanguageFilesToToml(); + } else { + Adapt.error("Auto Update Language is disabled, Expect Errors."); + Adapt.error("Do not disable this unless you know what you are doing, and dont expect support."); + migrateExistingLanguageFilesToToml(); + } + + invalidateLanguageCache(); + } + + + @SneakyThrows + public static String dLocalize(String s1, String s2, String s3) { + return dLocalize(s1 + "." + s2 + "." + s3); + } + + @SneakyThrows + public static String dLocalize(String key, Object... params) { + String cacheKey = key; + if (!Adapt.wordKey.containsKey(cacheKey)) { + File langFolder = new File(Adapt.instance.getDataFolder(), "languages"); + String resolved = resolveLocalizedFromRoot(getPrimaryLanguageRoot(langFolder), key); + + if (resolved == null) { + updateLanguageFile(); + resolved = resolveLocalizedFromRoot(getPrimaryLanguageRoot(langFolder), key); + } + + if (resolved == null) { + Adapt.verbose("Your Language File is missing the following key: " + key); + Adapt.verbose("Loading English Language File FallBack"); + + resolved = resolveLocalizedFromRoot(getFallbackLanguageRoot(langFolder), key); + } + + if (resolved == null) { + Adapt.wordKey.put(cacheKey, key); + Adapt.error("Your Fallback Language File is missing the following key: " + key); + Adapt.verbose("New Assignement: " + key); + Adapt.error("Please report this to the developer!"); + } else { + Adapt.wordKey.put(cacheKey, resolved); + Adapt.verbose("Loaded Localization: " + resolved + " for key: " + key); + } + } + String s = applyParameters(Adapt.wordKey.get(cacheKey), params); + s = C.translateAlternateColorCodes('&', s); + if (AdaptConfig.get().isAutomaticGradients()) { + s = C.aura(s, -20, 7, 8, 0.36); + } + + return AdventureCompat.toLegacySection(s); + } + + private static void syncLanguageResource(File langFolder, String languageCode) throws Exception { + if (languageCode == null || languageCode.isBlank()) { + return; + } + + String tomlResourcePath = languageCode + ".toml"; + String jsonResourcePath = languageCode + ".json"; + + String resourcePath = tomlResourcePath; + InputStream in = Adapt.instance.getResource(tomlResourcePath); + if (in == null) { + resourcePath = jsonResourcePath; + in = Adapt.instance.getResource(jsonResourcePath); + } + + if (in == null) { + Adapt.warn("Missing bundled language resource: " + tomlResourcePath + " (and fallback " + jsonResourcePath + ")"); + return; + } + + try (InputStream stream = in) { + String raw = IO.readAll(stream); + JsonElement parsed = ConfigFileSupport.parseToJsonElement(raw, new File(resourcePath)); + if (parsed == null) { + Adapt.warn("Failed to parse bundled language resource: " + resourcePath); + return; + } + + File tomlTarget = new File(langFolder, languageCode + ".toml"); + Files.deleteIfExists(tomlTarget.toPath()); + String toml = ConfigFileSupport.serializeJsonElementToToml(parsed); + Files.writeString(tomlTarget.toPath(), applyLanguageHeader(toml)); + + File legacyJsonTarget = new File(langFolder, jsonResourcePath); + Files.deleteIfExists(legacyJsonTarget.toPath()); + } + } + + private static File resolveLanguageFile(File languageFolder, String languageCode) { + File toml = new File(languageFolder, languageCode + ".toml"); + if (toml.exists()) { + return toml; + } + + return new File(languageFolder, languageCode + ".json"); + } + + private static JsonObject loadLanguageRoot(File file) { + try { + if (file == null || !file.exists() || !file.isFile()) { + return null; + } + + String raw = Files.readString(file.toPath()); + JsonElement root = ConfigFileSupport.parseToJsonElement(raw, file); + if (root == null || !root.isJsonObject()) { + return null; + } + + return root.getAsJsonObject(); + } catch (Throwable ignored) { + return null; + } + } + + private static JsonObject getPrimaryLanguageRoot(File languageFolder) { + synchronized (LANGUAGE_CACHE_LOCK) { + String language = AdaptConfig.get().getLanguage(); + if (Objects.equals(language, cachedPrimaryLanguage) && cachedPrimaryLanguageRoot != null) { + return cachedPrimaryLanguageRoot; + } + + cachedPrimaryLanguage = language; + cachedPrimaryLanguageRoot = loadLanguageRoot(resolveLanguageFile(languageFolder, language)); + return cachedPrimaryLanguageRoot; + } + } + + private static JsonObject getFallbackLanguageRoot(File languageFolder) { + synchronized (LANGUAGE_CACHE_LOCK) { + String fallbackLanguage = AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing(); + if (Objects.equals(fallbackLanguage, cachedFallbackLanguage) && cachedFallbackLanguageRoot != null) { + return cachedFallbackLanguageRoot; + } + + cachedFallbackLanguage = fallbackLanguage; + cachedFallbackLanguageRoot = loadLanguageRoot(resolveLanguageFile(languageFolder, fallbackLanguage)); + return cachedFallbackLanguageRoot; + } + } + + private static String resolveLocalizedFromRoot(JsonObject root, String key) { + if (root == null) { + return null; + } + return resolveLocalizedElementValue(resolveLocalizedElement(root, key)); + } + + private static void invalidateLanguageCache() { + synchronized (LANGUAGE_CACHE_LOCK) { + cachedPrimaryLanguage = null; + cachedPrimaryLanguageRoot = null; + cachedFallbackLanguage = null; + cachedFallbackLanguageRoot = null; + } + Adapt.wordKey.clear(); + } + + private static JsonElement resolveLocalizedElement(JsonObject root, String key) { + JsonObject current = root; + JsonElement element = null; + + for (String path : key.split("\\.")) { + if (current == null || !current.has(path)) { + return null; + } + + element = current.get(path); + if (element == null || element.isJsonNull()) { + return null; + } + + if (element.isJsonObject()) { + current = element.getAsJsonObject(); + } else { + current = null; + } + } + + return element; + } + + private static String resolveLocalizedElementValue(JsonElement element) { + if (element == null || element.isJsonNull()) { + return null; + } + + if (element.isJsonPrimitive()) { + return element.getAsString(); + } + + if (element.isJsonArray()) { + StringBuilder result = new StringBuilder(); + for (JsonElement value : element.getAsJsonArray()) { + if (!value.isJsonPrimitive()) { + continue; + } + + if (result.length() > 0) { + result.append('\n'); + } + + result.append(value.getAsString()); + } + + return result.toString(); + } + + return null; + } + + private static String applyParameters(String value, Object... params) { + if (value == null || params == null || params.length == 0) { + return value; + } + + String result = value; + for (int i = 0; i < params.length; i++) { + result = result.replace("{" + i + "}", String.valueOf(params[i])); + } + + return result; + } + + private static void migrateExistingLanguageFilesToToml() { + try { + File languageFolder = new File(Adapt.instance.getDataFolder(), "languages"); + if (!languageFolder.exists() || !languageFolder.isDirectory()) { + return; + } + + File[] files = languageFolder.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); + if (files == null || files.length == 0) { + return; + } + + for (File jsonFile : files) { + if (jsonFile == null || !jsonFile.exists() || !jsonFile.isFile()) { + continue; + } + + File tomlFile = ConfigFileSupport.toTomlFile(jsonFile); + if (tomlFile.exists() && tomlFile.isFile()) { + Files.deleteIfExists(jsonFile.toPath()); + continue; + } + + String raw = Files.readString(jsonFile.toPath()); + JsonElement parsed = ConfigFileSupport.parseToJsonElement(raw, jsonFile); + if (parsed == null) { + continue; + } + + String toml = ConfigFileSupport.serializeJsonElementToToml(parsed); + Files.writeString(tomlFile.toPath(), applyLanguageHeader(toml)); + Adapt.info("Migrated legacy language file [" + jsonFile.getName() + "] -> [" + tomlFile.getName() + "]."); + Files.deleteIfExists(jsonFile.toPath()); + } + invalidateLanguageCache(); + } catch (Throwable e) { + Adapt.warn("Failed to migrate legacy language json files: " + e.getMessage()); + } + } + + private static String applyLanguageHeader(String body) { + String source = body == null ? "" : body; + String trimmed = source.stripLeading(); + if (trimmed.startsWith("# Color Codes:")) { + return source; + } + return LANGUAGE_COLOR_NOTE + source; + } +} diff --git a/src/main/java/com/volmit/adapt/util/CallbackCV.java b/src/main/java/art/arcane/adapt/util/common/function/CallbackCV.java similarity index 93% rename from src/main/java/com/volmit/adapt/util/CallbackCV.java rename to src/main/java/art/arcane/adapt/util/common/function/CallbackCV.java index 516a956fa..65afb8661 100644 --- a/src/main/java/com/volmit/adapt/util/CallbackCV.java +++ b/src/main/java/art/arcane/adapt/util/common/function/CallbackCV.java @@ -16,8 +16,8 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.function; public interface CallbackCV { - void run(T t); + void run(T t); } diff --git a/src/main/java/art/arcane/adapt/util/common/function/Consumer2.java b/src/main/java/art/arcane/adapt/util/common/function/Consumer2.java new file mode 100644 index 000000000..549c9f8d1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Consumer2.java @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + + +@SuppressWarnings("hiding") +@FunctionalInterface +public interface Consumer2 { + void accept(A a, B b); +} diff --git a/src/main/java/art/arcane/adapt/util/common/function/Consumer4.java b/src/main/java/art/arcane/adapt/util/common/function/Consumer4.java new file mode 100644 index 000000000..4d89668a8 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Consumer4.java @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + + +@SuppressWarnings("hiding") +@FunctionalInterface +public interface Consumer4 { + void accept(A a, B b, C c, D d); +} diff --git a/src/main/java/art/arcane/adapt/util/common/function/Consumer5.java b/src/main/java/art/arcane/adapt/util/common/function/Consumer5.java new file mode 100644 index 000000000..b137cb0a7 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Consumer5.java @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + + +@SuppressWarnings("hiding") +@FunctionalInterface +public interface Consumer5 { + void accept(A a, B b, C c, D d, E e); +} diff --git a/src/main/java/art/arcane/adapt/util/common/function/Consumer6.java b/src/main/java/art/arcane/adapt/util/common/function/Consumer6.java new file mode 100644 index 000000000..f56bd9cb5 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Consumer6.java @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + + +@SuppressWarnings("hiding") +@FunctionalInterface +public interface Consumer6 { + void accept(A a, B b, C c, D d, E e, F f); +} diff --git a/src/main/java/com/volmit/adapt/util/Consumer7.java b/src/main/java/art/arcane/adapt/util/common/function/Consumer7.java similarity index 91% rename from src/main/java/com/volmit/adapt/util/Consumer7.java rename to src/main/java/art/arcane/adapt/util/common/function/Consumer7.java index 68ee5ebef..5efce4f97 100644 --- a/src/main/java/com/volmit/adapt/util/Consumer7.java +++ b/src/main/java/art/arcane/adapt/util/common/function/Consumer7.java @@ -16,10 +16,11 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.function; + @SuppressWarnings("hiding") @FunctionalInterface public interface Consumer7 { - void accept(A a, B b, C c, D d, E e, F f, G g); + void accept(A a, B b, C c, D d, E e, F f, G g); } diff --git a/src/main/java/art/arcane/adapt/util/common/function/Consumer8.java b/src/main/java/art/arcane/adapt/util/common/function/Consumer8.java new file mode 100644 index 000000000..c9a11cf72 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Consumer8.java @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + + +@SuppressWarnings("hiding") +@FunctionalInterface +public interface Consumer8 { + void accept(A a, B b, C c, D d, E e, F f, G g, H h); +} diff --git a/src/main/java/art/arcane/adapt/util/common/function/Function2.java b/src/main/java/art/arcane/adapt/util/common/function/Function2.java new file mode 100644 index 000000000..790cde9c7 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Function2.java @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + + +@SuppressWarnings("hiding") +@FunctionalInterface +public interface Function2 { + R apply(A a, B b); +} diff --git a/src/main/java/art/arcane/adapt/util/common/function/Function4.java b/src/main/java/art/arcane/adapt/util/common/function/Function4.java new file mode 100644 index 000000000..f5d932621 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Function4.java @@ -0,0 +1,26 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + + +@SuppressWarnings("hiding") +@FunctionalInterface +public interface Function4 { + R apply(A a, B b, C c, D d); +} diff --git a/src/main/java/art/arcane/adapt/util/common/function/NoiseInjector.java b/src/main/java/art/arcane/adapt/util/common/function/NoiseInjector.java new file mode 100644 index 000000000..6aa96c4d0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/NoiseInjector.java @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + +@FunctionalInterface +public interface NoiseInjector { + double[] combine(double src, double value); +} diff --git a/src/main/java/art/arcane/adapt/util/common/function/NoiseProvider.java b/src/main/java/art/arcane/adapt/util/common/function/NoiseProvider.java new file mode 100644 index 000000000..e1bcb7beb --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/NoiseProvider.java @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + +@FunctionalInterface +public interface NoiseProvider { + double noise(double x, double z); +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/function/Supplier2.java b/src/main/java/art/arcane/adapt/util/common/function/Supplier2.java new file mode 100644 index 000000000..2ff132dff --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Supplier2.java @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + +public interface Supplier2 { + void get(T t, TT tt); +} diff --git a/src/main/java/art/arcane/adapt/util/common/function/Supplier3.java b/src/main/java/art/arcane/adapt/util/common/function/Supplier3.java new file mode 100644 index 000000000..c25ad1084 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/function/Supplier3.java @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.function; + +public interface Supplier3 { + void get(T t, TT tt, TTT ttt); +} diff --git a/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiConfirm.java b/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiConfirm.java new file mode 100644 index 000000000..9342f36ad --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiConfirm.java @@ -0,0 +1,66 @@ +package art.arcane.adapt.util.common.inventorygui; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.data.MaterialBlock; +import art.arcane.volmlib.util.inventorygui.UIElement; +import art.arcane.volmlib.util.inventorygui.UIWindow; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +public final class GuiConfirm { + private GuiConfirm() { + } + + public static void open( + Player player, + String title, + String message, + Runnable onConfirm, + Runnable onCancel + ) { + if (player == null) { + return; + } + + if (!J.isPrimaryThread()) { + J.runEntity(player, () -> open(player, title, message, onConfirm, onCancel)); + return; + } + + UIWindow w = new UIWindow(Adapt.instance, player); + GuiTheme.apply(w, "confirm"); + w.setViewportHeight(3); + + w.setElement(0, 0, new UIElement("confirm-msg") + .setMaterial(new MaterialBlock(Material.PAPER)) + .setName(C.WHITE + (title == null ? "Confirm" : title)) + .addLore(C.GRAY + (message == null ? "Apply this change?" : message))); + + w.setElement(-2, 1, new UIElement("confirm-yes") + .setMaterial(new MaterialBlock(Material.LIME_STAINED_GLASS_PANE)) + .setName(C.GREEN + "Confirm") + .onLeftClick((e) -> { + w.close(); + if (onConfirm != null) { + onConfirm.run(); + } + })); + + w.setElement(2, 1, new UIElement("confirm-no") + .setMaterial(new MaterialBlock(Material.RED_STAINED_GLASS_PANE)) + .setName(C.RED + "Cancel") + .onLeftClick((e) -> { + w.close(); + if (onCancel != null) { + onCancel.run(); + } + })); + + w.setTitle(C.GRAY + "Confirm"); + w.onClosed((window) -> Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString())); + w.open(); + Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiEffects.java b/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiEffects.java new file mode 100644 index 000000000..6cfee1917 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiEffects.java @@ -0,0 +1,27 @@ +package art.arcane.adapt.util.common.inventorygui; + +import art.arcane.volmlib.util.inventorygui.Element; +import art.arcane.volmlib.util.inventorygui.Window; + +import java.util.List; + +public final class GuiEffects { + private GuiEffects() { + } + + public static void applyReveal(Window window, List placements) { + if (window == null || placements == null || placements.isEmpty()) { + return; + } + + for (Placement placement : placements) { + if (placement == null || placement.element() == null) { + continue; + } + window.setElement(placement.position(), placement.row(), placement.element()); + } + } + + public record Placement(int position, int row, Element element) { + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiLayout.java b/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiLayout.java new file mode 100644 index 000000000..f89b54689 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiLayout.java @@ -0,0 +1,66 @@ +package art.arcane.adapt.util.common.inventorygui; + +public final class GuiLayout { + public static final int WIDTH = 9; + public static final int MAX_ROWS = 6; + + private GuiLayout() { + } + + public static PagePlan plan(int totalItems, boolean reserveNavigationRow) { + int items = Math.max(0, totalItems); + + boolean navigation = reserveNavigationRow; + int maxContentRows = MAX_ROWS - (navigation ? 1 : 0); + if (maxContentRows < 1) { + maxContentRows = 1; + } + + if (items > maxContentRows * WIDTH) { + navigation = true; + maxContentRows = MAX_ROWS - 1; + } + + int contentRows; + if (items <= 0) { + contentRows = 1; + } else if (items > maxContentRows * WIDTH) { + contentRows = maxContentRows; + } else { + contentRows = (int) Math.ceil(items / (double) WIDTH); + } + + contentRows = Math.max(1, Math.min(maxContentRows, contentRows)); + int rows = contentRows + (navigation ? 1 : 0); + rows = Math.max(1, Math.min(MAX_ROWS, rows)); + + int itemsPerPage = contentRows * WIDTH; + itemsPerPage = Math.max(WIDTH, itemsPerPage); + int pages = Math.max(1, (int) Math.ceil(items / (double) itemsPerPage)); + + return new PagePlan(rows, contentRows, navigation, itemsPerPage, pages); + } + + public static int clampPage(int page, int pageCount) { + if (pageCount <= 0) { + return 0; + } + return Math.max(0, Math.min(pageCount - 1, page)); + } + + public static int centeredPosition(int indexInRow, int rowCount) { + int count = Math.max(1, Math.min(WIDTH, rowCount)); + int index = Math.max(0, Math.min(count - 1, indexInRow)); + int start = -(count / 2); + return start + index; + } + + public record PagePlan( + int rows, + int contentRows, + boolean hasNavigationRow, + int itemsPerPage, + int pageCount + ) { + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiTheme.java b/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiTheme.java new file mode 100644 index 000000000..1a50fa465 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/inventorygui/GuiTheme.java @@ -0,0 +1,40 @@ +package art.arcane.adapt.util.common.inventorygui; + +import art.arcane.volmlib.util.data.MaterialBlock; +import art.arcane.volmlib.util.inventorygui.UIElement; +import art.arcane.volmlib.util.inventorygui.Window; +import art.arcane.volmlib.util.inventorygui.WindowResolution; +import org.bukkit.Material; + +public final class GuiTheme { + private GuiTheme() { + } + + public static void apply(Window window, String tag) { + if (window == null) { + return; + } + + window.setResolution(WindowResolution.W9_H6); + window.setViewportHeight(WindowResolution.W9_H6.getMaxHeight()); + if (tag != null) { + window.setTag(tag); + } + + window.setDecorator((w, position, row) -> new UIElement("bg-" + row + "-" + position) + .setName(" ") + .setMaterial(new MaterialBlock(background(row, position)))); + } + + public static Material background(int row, int position) { + if (row == 0) { + return position % 2 == 0 ? Material.GRAY_STAINED_GLASS_PANE : Material.LIGHT_GRAY_STAINED_GLASS_PANE; + } + + if (row == 1) { + return Material.BLACK_STAINED_GLASS_PANE; + } + + return position % 2 == 0 ? Material.BLACK_STAINED_GLASS_PANE : Material.GRAY_STAINED_GLASS_PANE; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/inventorygui/Inventories.java b/src/main/java/art/arcane/adapt/util/common/inventorygui/Inventories.java new file mode 100644 index 000000000..caf46093a --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/inventorygui/Inventories.java @@ -0,0 +1,106 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.inventorygui; + +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +/** + * ItemStack & Inventory utilities + * + * @author cyberpwn + */ +public class Inventories { + /** + * Does the inventory have any space (empty slots) + * + * @param i the inventory + * @return true if it has at least one slot empty + */ + public static boolean hasSpace(Inventory i) { + return new PhantomInventory(i).hasSpace(); + } + + /** + * Does the inventory have a given amount of empty space (or more) + * + * @param i the inventory + * @param slots the slots needed empty + * @return true if it has more than or enough empty slots + */ + public static boolean hasSpace(Inventory i, int slots) { + int ex = 0; + + ItemStack[] vv = i.getContents(); + + for (ItemStack itemStack : vv) { + if (itemStack == null || itemStack.getType().equals(Material.AIR)) { + ex++; + } + } + + return ex >= slots; + } + + /** + * Get the ACTUAL contents in this inventory. Meaning no elements in the list + * are null, or just plain air + * + * @param i the inventory + * @return the ACTUAL contents + */ + public static List getActualContents(Inventory i) { + List actualItems = new ArrayList<>(); + + for (ItemStack j : i.getContents()) { + if (Items.is(j)) { + actualItems.add(j); + } + } + + return actualItems; + } + + /** + * Does the inventory have space for the given item + * + * @param i the inventory + * @param item the item + * @return returns true if either there is enough empty slots to fill it (amt + * / maxStackSize) OR the item can be merged with an existing item, else + * false. + */ + public static boolean hasSpace(Inventory i, ItemStack item) { + if (hasSpace(i, item.getAmount() / item.getMaxStackSize())) { + return true; + } else { + for (ItemStack j : getActualContents(i)) { + if (Items.isMergable(j, item)) { + return true; + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/inventorygui/Items.java b/src/main/java/art/arcane/adapt/util/common/inventorygui/Items.java new file mode 100644 index 000000000..f107fc49c --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/inventorygui/Items.java @@ -0,0 +1,358 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.inventorygui; + +import art.arcane.adapt.util.common.math.MaterialBlock; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; + +/** + * Itemstack utilities + * + * @author cyberpwn + */ +public class Items { + /** + * Is the item an item (not null or air) + * + * @param is the item + * @return true if it is + */ + public static boolean is(ItemStack is) { + return is != null && !is.getType().equals(Material.AIR); + } + + /** + * Is the item a certain material + * + * @param is the item + * @param material the material + * @return true if it is + */ + public static boolean is(ItemStack is, Material material) { + return is(is) && is.getType().equals(material); + } + + /** + * Is the item a certain material and metadata + * + * @param is the item + * @param mb the materialblock + * @return true if it is + */ + @SuppressWarnings("deprecation") + public static boolean is(ItemStack is, MaterialBlock mb) { + return is(is, mb.getMaterial()) && is.getData().getData() == mb.getData(); + } + + /** + * Is the item a given material and data + * + * @param is the item + * @param material the material + * @param data the data + * @return true if it is + */ + public static boolean is(ItemStack is, Material material, byte data) { + return is(is, new MaterialBlock(material, data)); + } + + /** + * Is the item a given material and data + * + * @param is the item + * @param material the material + * @param data the data + * @return true if it is + */ + public static boolean is(ItemStack is, Material material, int data) { + return is(is, material, (byte) data); + } + + /** + * Does the item have meta + * + * @param is the item + * @return true if it does + */ + public static boolean hasMeta(ItemStack is) { + return is(is) && is.hasItemMeta(); + } + + /** + * Does the item have a custom name + * + * @param is the item + * @return true if it has a name + */ + public static boolean hasName(ItemStack is) { + return hasMeta(is) && is.getItemMeta().hasDisplayName(); + } + + /** + * Does the item have any lore? + * + * @param is the item + * @return true if it does + */ + public static boolean hasLore(ItemStack is) { + return hasMeta(is) && is.getItemMeta().hasLore(); + } + + /** + * Does the item have the given name (color matters) + * + * @param is the item + * @param name the name + * @return true if it has the name + */ + public static boolean hasName(ItemStack is, String name) { + return hasName(is) && is.getItemMeta().getDisplayName().equals(name); + } + + + /** + * Does the item have the given enchantment + * + * @param is the item + * @param e the enchantment + * @return true if it does + */ + public static boolean hasEnchantment(ItemStack is, Enchantment e) { + return is(is) && is.getEnchantments().containsKey(e); + } + + /** + * Does the item have the enchantment at the given level + * + * @param is the item + * @param e the enchantment + * @param level the level + * @return true if it does + */ + public static boolean hasEnchantment(ItemStack is, Enchantment e, int level) { + if (!is(is)) { + return false; + } + + return hasEnchantment(is, e) && is.getEnchantmentLevel(e) == level; + } + + /** + * Does the item have any enchantments + * + * @param is the item + * @return true if it does + */ + public static boolean hasEnchantments(ItemStack is) { + if (!is(is)) { + return false; + } + + return !is.getEnchantments().isEmpty(); + } + + /** + * Get a materialblock representation of this item + * + * @param is the item + * @return the materialblock or null if the item is null + */ + @SuppressWarnings("deprecation") + public static MaterialBlock toMaterialBlock(ItemStack is) { + if (is != null) { + return new MaterialBlock(is.getType(), is.getData().getData()); + } + + return null; + } + + /** + * Should the itemstack be broken? + * + * @param is the itemStack + * @return true if it should be broken + */ + public static boolean isBroken(ItemStack is) { + return is(is) && getMaxDurability(is) == getDurability(is) && hasDurability(is); + } + + /** + * Does this item have durability + * + * @param is the item + * @return true if it does + */ + public static boolean hasDurability(ItemStack is) { + return is(is) && getMaxDurability(is) > 0; + } + + /** + * Get the durability percent + * + * @param is the itemstack + * @return the percent + */ + public static double getDurabilityPercent(ItemStack is) { + if (!is(is)) { + return 0.0; + } + + if (getMaxDurability(is) == 0) { + return 1.0; + } + + return 1.0 - ((double) getDurability(is) / (double) getMaxDurability(is)); + } + + /** + * Set the durability percent + * + * @param is the itemStack + * @param pc the percent + */ + public static void setDurabilityPercent(ItemStack is, double pc) { + if (!is(is)) { + return; + } + + pc = (pc > 1.0 ? 1.0 : (pc < 0.0 ? 0.0 : pc)); + + if (getDurability(is) == 0) { + return; + } + + setDurability(is, (int) ((double) getMaxDurability(is) * (1.0 - pc))); + } + + /** + * Get the max durability + * + * @param is the item + * @return the item type's max durability + */ + public static short getMaxDurability(ItemStack is) { + if (!is(is)) { + return 0; + } + + return is.getType().getMaxDurability(); + } + + /** + * Get the durability + * + * @param is the item + * @return the item durability + */ + public static short getDurability(ItemStack is) { + if (!is(is)) { + return 0; + } + + return is.getDurability(); + } + + /** + * Set the durability (if higher than max, it will be set to the max) + * + * @param is the item + * @param dmg the durability + */ + public static void setDurability(ItemStack is, short dmg) { + if (!is(is)) { + return; + } + + is.setDurability(dmg > getMaxDurability(is) ? getMaxDurability(is) : dmg); + } + + /** + * Set the durability (if higher than max, it will be set to the max) + * + * @param is the item + * @param dmg the durability + */ + public static void setDurability(ItemStack is, int dmg) { + if (!is(is)) { + return; + } + + setDurability(is, (short) dmg); + } + + /** + * Damage an itemstack + * + * @param is the item + * @param amt the amount to damage + */ + public static void damage(ItemStack is, int amt) { + if (!is(is)) { + return; + } + + setDurability(is, getDurability(is) + amt); + } + + /** + * Can the item a be stacked onto the item b (following max stack size) + * + * @param a the item a + * @param b the item b + * @return true if they can be merged + */ + @SuppressWarnings("deprecation") + public static boolean isMergable(ItemStack a, ItemStack b) { + if (is(a) && is(b)) { + if (!a.getType().equals(b.getType())) { + return false; + } + + if (a.getData().getData() != b.getData().getData()) { + return false; + } + + if (a.hasItemMeta() != b.hasItemMeta()) { + return false; + } + + if (a.getDurability() != b.getDurability()) { + return false; + } + + if (a.hasItemMeta()) { + if (!a.getItemMeta().getDisplayName().equals(b.getItemMeta().getDisplayName())) { + return false; + } + + if (!new ArrayList(a.getItemMeta().getLore()).equals(new ArrayList<>(b.getItemMeta().getLore()))) { + return false; + } + } + + return a.getMaxStackSize() >= a.getAmount() + b.getAmount(); + } + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/inventorygui/PhantomInventory.java b/src/main/java/art/arcane/adapt/util/common/inventorygui/PhantomInventory.java new file mode 100644 index 000000000..1a34f7b6c --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/inventorygui/PhantomInventory.java @@ -0,0 +1,232 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.inventorygui; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; + +public class PhantomInventory implements PhantomInventoryWrapper { + protected Inventory i; + + public PhantomInventory(Inventory i) { + this.i = i; + } + + @Override + public HashMap addItem(ItemStack... arg0) throws IllegalArgumentException { + return i.addItem(arg0); + } + + @Override + public int close() { + return i.close(); + } + + @Override + public HashMap all(Material arg0) throws IllegalArgumentException { + return i.all(arg0); + } + + @Override + public HashMap all(ItemStack arg0) { + return i.all(arg0); + } + + @Override + public void clear() { + i.clear(); + } + + @Override + public void clear(int arg0) { + i.clear(arg0); + } + + @Override + public boolean contains(Material arg0) throws IllegalArgumentException { + return i.contains(arg0); + } + + @Override + public boolean contains(ItemStack arg0) { + return i.contains(arg0); + } + + @Override + public boolean contains(Material arg0, int arg1) throws IllegalArgumentException { + return i.contains(arg0, arg1); + } + + @Override + public boolean contains(ItemStack arg0, int arg1) { + return i.contains(arg0, arg1); + } + + @Override + public boolean containsAtLeast(ItemStack arg0, int arg1) { + return i.containsAtLeast(arg0, arg1); + } + + @Override + public int first(Material arg0) throws IllegalArgumentException { + return i.first(arg0); + } + + @Override + public int first(ItemStack arg0) { + return i.first(arg0); + } + + @Override + public int firstEmpty() { + return i.firstEmpty(); + } + + @Override + public boolean isEmpty() { + return i.isEmpty(); + } + + @Override + public ItemStack[] getContents() { + return i.getContents(); + } + + @Override + public void setContents(ItemStack[] arg0) throws IllegalArgumentException { + i.setContents(arg0); + } + + @Override + public InventoryHolder getHolder() { + return i.getHolder(); + } + + @Override + public InventoryHolder getHolder(boolean useSnapshot) { + return i.getHolder(useSnapshot); + } + + @Override + public ItemStack getItem(int arg0) { + return i.getItem(arg0); + } + + @Override + public int getMaxStackSize() { + return i.getMaxStackSize(); + } + + @Override + public void setMaxStackSize(int arg0) { + i.setMaxStackSize(arg0); + } + + @Override + public int getSize() { + return i.getSize(); + } + + @Override + public InventoryType getType() { + return i.getType(); + } + + @Override + public List getViewers() { + return i.getViewers(); + } + + @Override + public ListIterator iterator() { + return i.iterator(); + } + + @Override + public ListIterator iterator(int arg0) { + return i.iterator(arg0); + } + + @Override + public void remove(Material arg0) throws IllegalArgumentException { + i.remove(arg0); + } + + @Override + public void remove(ItemStack arg0) { + i.remove(arg0); + } + + @Override + public HashMap removeItem(ItemStack... arg0) throws IllegalArgumentException { + return i.removeItem(arg0); + } + + @Override + public HashMap removeItemAnySlot(ItemStack... arg0) throws IllegalArgumentException { + return i.removeItemAnySlot(arg0); + } + + @Override + public void setItem(int arg0, ItemStack arg1) { + i.setItem(arg0, arg1); + } + + @Override + public boolean hasSpace() { + return firstEmpty() != -1; + } + + @Override + public int getSlotsLeft() { + int x = 0; + + for (ItemStack i : getContents()) { + if (i == null || i.getType().equals(Material.AIR)) { + x++; + } + } + + return x; + } + + @Override + public Location getLocation() { + return i.getLocation(); + } + + @Override + public ItemStack[] getStorageContents() { + return i.getStorageContents(); + } + + @Override + public void setStorageContents(ItemStack[] arg0) throws IllegalArgumentException { + i.setStorageContents(arg0); + } +} diff --git a/src/main/java/com/volmit/adapt/util/PhantomInventoryWrapper.java b/src/main/java/art/arcane/adapt/util/common/inventorygui/PhantomInventoryWrapper.java similarity index 77% rename from src/main/java/com/volmit/adapt/util/PhantomInventoryWrapper.java rename to src/main/java/art/arcane/adapt/util/common/inventorygui/PhantomInventoryWrapper.java index 07c938cf8..928e82ceb 100644 --- a/src/main/java/com/volmit/adapt/util/PhantomInventoryWrapper.java +++ b/src/main/java/art/arcane/adapt/util/common/inventorygui/PhantomInventoryWrapper.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.inventorygui; import org.bukkit.inventory.Inventory; @@ -26,17 +26,17 @@ * @author cyberpwn */ public interface PhantomInventoryWrapper extends Inventory { - /** - * Does the given inventory have any space? - * - * @return true if it has space for at least one more slot. - */ - boolean hasSpace(); + /** + * Does the given inventory have any space? + * + * @return true if it has space for at least one more slot. + */ + boolean hasSpace(); - /** - * Get how many air slots exist (empty slots) - * - * @return the number of slots empty (0 if full) - */ - int getSlotsLeft(); + /** + * Get how many air slots exist (empty slots) + * + * @return the number of slots empty (0 if full) + */ + int getSlotsLeft(); } \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/io/BukkitGson.java b/src/main/java/art/arcane/adapt/util/common/io/BukkitGson.java new file mode 100644 index 000000000..18e6252a1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/io/BukkitGson.java @@ -0,0 +1,102 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.io; + +import art.arcane.volmlib.util.format.Form; +import com.google.gson.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; + +public class BukkitGson { + public static final Gson gson = new GsonBuilder() + .registerTypeAdapter(World.class, (JsonSerializer) (world, type, s) -> s.serialize(world.getName())) + .registerTypeAdapter(World.class, (JsonDeserializer) (j, type, d) -> Bukkit.getWorld(j.getAsString())) + .registerTypeAdapter(BlockData.class, (JsonSerializer) (data, type, s) -> new JsonPrimitive(data.getAsString(true))) + .registerTypeAdapter(BlockData.class, (JsonDeserializer) (j, type, d) -> Bukkit.createBlockData(j.getAsString())) + .registerTypeAdapter(Location.class, (JsonSerializer) (data, type, s) -> { + JsonArray a = new JsonArray(); + a.add(data.getWorld().getName()); + a.add(truncate(data.getX(), 1)); + a.add(truncate(data.getY(), 1)); + a.add(truncate(data.getZ(), 1)); + a.add((int) data.getYaw()); + a.add((int) data.getPitch()); + return a; + }) + .registerTypeAdapter(Location.class, (JsonDeserializer) (j, type, d) -> { + JsonArray a = j.getAsJsonArray(); + return new Location(Bukkit.getWorld(a.get(0).getAsString()), a.get(1).getAsDouble(), a.get(2).getAsDouble(), a.get(3).getAsDouble(), a.get(4).getAsFloat(), a.get(5).getAsFloat()); + }) + .registerTypeAdapter(Block.class, (JsonSerializer) (data, type, s) -> { + JsonArray a = new JsonArray(); + a.add(data.getWorld().getName()); + a.add(data.getX()); + a.add(data.getY()); + a.add(data.getZ()); + return a; + }) + .registerTypeAdapter(Block.class, (JsonDeserializer) (j, type, d) -> { + JsonArray a = j.getAsJsonArray(); + return new Location(Bukkit.getWorld(a.get(0).getAsString()), a.get(1).getAsInt(), a.get(2).getAsInt(), a.get(3).getAsInt()).getBlock(); + }) + .registerTypeAdapter(BlockVector.class, (JsonSerializer) (data, type, s) -> { + JsonArray a = new JsonArray(); + a.add(data.getBlockX()); + a.add(data.getBlockY()); + a.add(data.getBlockZ()); + return a; + }) + .registerTypeAdapter(BlockVector.class, (JsonDeserializer) (j, type, d) -> { + JsonArray a = j.getAsJsonArray(); + return new BlockVector(a.get(0).getAsInt(), a.get(1).getAsInt(), a.get(2).getAsInt()); + }) + .registerTypeAdapter(Vector.class, (JsonSerializer) (data, type, s) -> { + JsonArray a = new JsonArray(); + a.add(truncate(data.getX(), 1)); + a.add(truncate(data.getY(), 1)); + a.add(truncate(data.getZ(), 1)); + return a; + }) + .registerTypeAdapter(Vector.class, (JsonDeserializer) (j, type, d) -> { + JsonArray a = j.getAsJsonArray(); + return new BlockVector(a.get(0).getAsDouble(), a.get(1).getAsDouble(), a.get(2).getAsDouble()); + }) + .create(); + + private static double truncate(double d, int p) { + if ((int) d == d) { + return d; + } + + return Double.parseDouble(Form.f(d, p)); + } + + private static double truncate(float d, int p) { + if ((int) d == d) { + return d; + } + + return Float.parseFloat(Form.f(d, p)); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/io/Json.java b/src/main/java/art/arcane/adapt/util/common/io/Json.java new file mode 100644 index 000000000..247238109 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/io/Json.java @@ -0,0 +1,38 @@ +package art.arcane.adapt.util.common.io; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import lombok.NonNull; + +import java.lang.reflect.Type; + +public class Json { + public static final Gson NORMAL = create(false); + public static final Gson PRETTY = create(true); + + @NonNull + public static String toJson(@NonNull Object src, boolean pretty) { + return toJson(src, src.getClass(), pretty); + } + + @NonNull + public static String toJson(@NonNull Object src, @NonNull Type type, boolean pretty) { + return (pretty ? PRETTY : NORMAL).toJson(src, type); + } + + public static T fromJson(@NonNull String json, @NonNull Class type) { + return NORMAL.fromJson(json, type); + } + + public static T fromJson(@NonNull String json, @NonNull Type type) { + return NORMAL.fromJson(json, type); + } + + private static Gson create(boolean pretty) { + com.google.gson.GsonBuilder builder = new GsonBuilder() + .setLenient() + .disableHtmlEscaping(); + if (pretty) builder.setPrettyPrinting(); + return builder.create(); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/io/LZString.java b/src/main/java/art/arcane/adapt/util/common/io/LZString.java new file mode 100644 index 000000000..a565a4078 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/io/LZString.java @@ -0,0 +1,556 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.io; + +import java.util.*; + +public class LZString { + + private static final char[] keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(); + private static final char[] keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".toCharArray(); + private static final Map> baseReverseDic = new HashMap>(); + + private static char getBaseValue(char[] alphabet, Character character) { + Map map = baseReverseDic.get(alphabet); + if (map == null) { + map = new HashMap(); + baseReverseDic.put(alphabet, map); + for (int i = 0; i < alphabet.length; i++) { + map.put(alphabet[i], i); + } + } + return (char) map.get(character).intValue(); + } + + public static String compressToBase64(String input) { + if (input == null) + return ""; + String res = LZString._compress(input, 6, new CompressFunctionWrapper() { + @Override + public char doFunc(int a) { + return keyStrBase64[a]; + } + }); + switch (res.length() % 4) { // To produce valid Base64 + default: // When could this happen ? + case 0: + return res; + case 1: + return res + "==="; + case 2: + return res + "=="; + case 3: + return res + "="; + } + } + + public static String decompressFromBase64(final String inputStr) { + if (inputStr == null) + return ""; + if (inputStr.equals("")) + return null; + return LZString._decompress(inputStr.length(), 32, new DecompressFunctionWrapper() { + @Override + public char doFunc(int index) { + return getBaseValue(keyStrBase64, inputStr.charAt(index)); + } + }); + } + + public static String compressToUTF16(String input) { + if (input == null) + return ""; + return LZString._compress(input, 15, new CompressFunctionWrapper() { + @Override + public char doFunc(int a) { + return fc(a + 32); + } + }) + " "; + } + + public static String decompressFromUTF16(final String compressedStr) { + if (compressedStr == null) + return ""; + if (compressedStr.isEmpty()) + return null; + return LZString._decompress(compressedStr.length(), 16384, new DecompressFunctionWrapper() { + @Override + public char doFunc(int index) { + return (char) (compressedStr.charAt(index) - 32); + } + }); + } + + //TODO: java has no Uint8Array type, what can we do? + + public static String compressToEncodedURIComponent(String input) { + if (input == null) + return ""; + return LZString._compress(input, 6, new CompressFunctionWrapper() { + @Override + public char doFunc(int a) { + return keyStrUriSafe[a]; + } + }); + } + + public static String decompressFromEncodedURIComponent(String inputStr) { + if (inputStr == null) return ""; + if (inputStr.isEmpty()) return null; + final String urlEncodedInputStr = inputStr.replace(' ', '+'); + return LZString._decompress(urlEncodedInputStr.length(), 32, new DecompressFunctionWrapper() { + @Override + public char doFunc(int index) { + return getBaseValue(keyStrUriSafe, urlEncodedInputStr.charAt(index)); + } + }); + } + + public static String compress(String uncompressed) { + return LZString._compress(uncompressed, 16, new CompressFunctionWrapper() { + @Override + public char doFunc(int a) { + return fc(a); + } + }); + } + + private static String _compress(String uncompressedStr, int bitsPerChar, CompressFunctionWrapper getCharFromInt) { + if (uncompressedStr == null) return ""; + int i, value; + Map context_dictionary = new HashMap(); + Set context_dictionaryToCreate = new HashSet(); + String context_c = ""; + String context_wc = ""; + String context_w = ""; + int context_enlargeIn = 2; // Compensate for the first entry which should not count + int context_dictSize = 3; + int context_numBits = 2; + StringBuilder context_data = new StringBuilder(uncompressedStr.length() / 3); + int context_data_val = 0; + int context_data_position = 0; + int ii; + + for (ii = 0; ii < uncompressedStr.length(); ii += 1) { + context_c = String.valueOf(uncompressedStr.charAt(ii)); + if (!context_dictionary.containsKey(context_c)) { + context_dictionary.put(context_c, context_dictSize++); + context_dictionaryToCreate.add(context_c); + } + + context_wc = context_w + context_c; + if (context_dictionary.containsKey(context_wc)) { + context_w = context_wc; + } else { + if (context_dictionaryToCreate.contains(context_w)) { + if (context_w.charAt(0) < 256) { + for (i = 0; i < context_numBits; i++) { + context_data_val = (context_data_val << 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + } + value = context_w.charAt(0); + for (i = 0; i < 8; i++) { + context_data_val = (context_data_val << 1) | (value & 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = value >> 1; + } + } else { + value = 1; + for (i = 0; i < context_numBits; i++) { + context_data_val = (context_data_val << 1) | value; + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = 0; + } + value = context_w.charAt(0); + for (i = 0; i < 16; i++) { + context_data_val = (context_data_val << 1) | (value & 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = value >> 1; + } + } + context_enlargeIn--; + if (context_enlargeIn == 0) { + context_enlargeIn = powerOf2(context_numBits); + context_numBits++; + } + context_dictionaryToCreate.remove(context_w); + } else { + value = context_dictionary.get(context_w); + for (i = 0; i < context_numBits; i++) { + context_data_val = (context_data_val << 1) | (value & 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = value >> 1; + } + + } + context_enlargeIn--; + if (context_enlargeIn == 0) { + context_enlargeIn = powerOf2(context_numBits); + context_numBits++; + } + // Add wc to the dictionary. + context_dictionary.put(context_wc, context_dictSize++); + context_w = context_c; + } + } + + // Output the code for w. + if (!context_w.isEmpty()) { + if (context_dictionaryToCreate.contains(context_w)) { + if (context_w.charAt(0) < 256) { + for (i = 0; i < context_numBits; i++) { + context_data_val = (context_data_val << 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + } + value = context_w.charAt(0); + for (i = 0; i < 8; i++) { + context_data_val = (context_data_val << 1) | (value & 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = value >> 1; + } + } else { + value = 1; + for (i = 0; i < context_numBits; i++) { + context_data_val = (context_data_val << 1) | value; + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = 0; + } + value = context_w.charAt(0); + for (i = 0; i < 16; i++) { + context_data_val = (context_data_val << 1) | (value & 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = value >> 1; + } + } + context_enlargeIn--; + if (context_enlargeIn == 0) { + context_enlargeIn = powerOf2(context_numBits); + context_numBits++; + } + context_dictionaryToCreate.remove(context_w); + } else { + value = context_dictionary.get(context_w); + for (i = 0; i < context_numBits; i++) { + context_data_val = (context_data_val << 1) | (value & 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = value >> 1; + } + + } + context_enlargeIn--; + if (context_enlargeIn == 0) { + context_enlargeIn = powerOf2(context_numBits); + context_numBits++; + } + } + + // Mark the end of the stream + value = 2; + for (i = 0; i < context_numBits; i++) { + context_data_val = (context_data_val << 1) | (value & 1); + if (context_data_position == bitsPerChar - 1) { + context_data_position = 0; + context_data.append(getCharFromInt.doFunc(context_data_val)); + context_data_val = 0; + } else { + context_data_position++; + } + value = value >> 1; + } + + // Flush the last char + while (true) { + context_data_val = (context_data_val << 1); + if (context_data_position == bitsPerChar - 1) { + context_data.append(getCharFromInt.doFunc(context_data_val)); + break; + } else + context_data_position++; + } + return context_data.toString(); + } + + public static String f(int i) { + return String.valueOf((char) i); + } + + public static char fc(int i) { + return (char) i; + } + + public static String decompress(final String compressed) { + if (compressed == null) + return ""; + if (compressed.isEmpty()) + return null; + return LZString._decompress(compressed.length(), 32768, new DecompressFunctionWrapper() { + @Override + public char doFunc(int i) { + return compressed.charAt(i); + } + }); + } + + private static String _decompress(int length, int resetValue, DecompressFunctionWrapper getNextValue) { + List dictionary = new ArrayList(); + // TODO: is next an unused variable in original lz-string? + @SuppressWarnings("unused") + int next; + int enlargeIn = 4; + int dictSize = 4; + int numBits = 3; + String entry = ""; + StringBuilder result = new StringBuilder(); + String w; + int bits, resb; + int maxpower, power; + String c = null; + DecData data = new DecData(); + data.val = getNextValue.doFunc(0); + data.position = resetValue; + data.index = 1; + + for (int i = 0; i < 3; i += 1) { + dictionary.add(i, f(i)); + } + + bits = 0; + maxpower = powerOf2(2); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue.doFunc(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + + switch (next = bits) { + case 0: + bits = 0; + maxpower = powerOf2(8); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue.doFunc(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + c = f(bits); + break; + case 1: + bits = 0; + maxpower = powerOf2(16); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue.doFunc(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + c = f(bits); + break; + case 2: + return ""; + } + dictionary.add(3, c); + w = c; + result.append(w); + while (true) { + if (data.index > length) { + return ""; + } + + bits = 0; + maxpower = powerOf2(numBits); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue.doFunc(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + // TODO: very strange here, c above is as char/string, here further is a int, rename "c" in the switch as "cc" + int cc; + switch (cc = bits) { + case 0: + bits = 0; + maxpower = powerOf2(8); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue.doFunc(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + + dictionary.add(dictSize++, f(bits)); + cc = dictSize - 1; + enlargeIn--; + break; + case 1: + bits = 0; + maxpower = powerOf2(16); + power = 1; + while (power != maxpower) { + resb = data.val & data.position; + data.position >>= 1; + if (data.position == 0) { + data.position = resetValue; + data.val = getNextValue.doFunc(data.index++); + } + bits |= (resb > 0 ? 1 : 0) * power; + power <<= 1; + } + dictionary.add(dictSize++, f(bits)); + cc = dictSize - 1; + enlargeIn--; + break; + case 2: + return result.toString(); + } + + if (enlargeIn == 0) { + enlargeIn = powerOf2(numBits); + numBits++; + } + + if (cc < dictionary.size() && dictionary.get(cc) != null) { + entry = dictionary.get(cc); + } else { + if (cc == dictSize) { + entry = w + w.charAt(0); + } else { + return null; + } + } + result.append(entry); + + // Add w+entry[0] to the dictionary. + dictionary.add(dictSize++, w + entry.charAt(0)); + enlargeIn--; + + w = entry; + + if (enlargeIn == 0) { + enlargeIn = powerOf2(numBits); + numBits++; + } + + } + + } + + private static int powerOf2(int power) { + return 1 << power; + } + + private static abstract class CompressFunctionWrapper { + public abstract char doFunc(int i); + } + + private static abstract class DecompressFunctionWrapper { + public abstract char doFunc(int i); + } + + protected static class DecData { + public char val; + public int position; + public int index; + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/io/PersistentJson.java b/src/main/java/art/arcane/adapt/util/common/io/PersistentJson.java new file mode 100644 index 000000000..d1cc8663a --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/io/PersistentJson.java @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.io; + +import art.arcane.adapt.Adapt; +import org.bukkit.NamespacedKey; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +public class PersistentJson { + + public static void write(PersistentDataContainer c, String key, Object data) { + c.set(new NamespacedKey(Adapt.instance, key), PersistentDataType.STRING, Json.toJson(data, false)); + } + + private static T fromJSON(PersistentDataContainer c, String key, Class type) { + String s = c.get(new NamespacedKey(Adapt.instance, key), PersistentDataType.STRING); + + if (s == null) { + return Json.fromJson(s, type); + } + + return null; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/io/ReactiveFolder.java b/src/main/java/art/arcane/adapt/util/common/io/ReactiveFolder.java new file mode 100644 index 000000000..4bb16fc13 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/io/ReactiveFolder.java @@ -0,0 +1,79 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.io; + +import art.arcane.amulet.io.FolderWatcher; +import art.arcane.volmlib.util.function.Consumer3; + +import java.io.File; +import java.util.List; + +public class ReactiveFolder { + private final File folder; + private final Consumer3, List, List> hotload; + private FolderWatcher fw; + + public ReactiveFolder(File folder, Consumer3, List, List> hotload) { + this.folder = folder; + this.hotload = hotload; + this.fw = new FolderWatcher(folder); + fw.checkModified(); + } + + public void checkIgnore() { + fw = new FolderWatcher(folder); + } + + public void check() { + boolean modified = false; + + if (fw.checkModified()) { + for (File i : fw.getCreated()) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json")) { + modified = true; + break; + } + } + + if (!modified) { + for (File i : fw.getChanged()) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json")) { + modified = true; + break; + } + } + } + + if (!modified) { + for (File i : fw.getDeleted()) { + if (i.getName().endsWith(".iob") || i.getName().endsWith(".json")) { + modified = true; + break; + } + } + } + } + + if (modified) { + hotload.accept(fw.getCreated(), fw.getChanged(), fw.getDeleted()); + } + + fw.checkModified(); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/io/SQLManager.java b/src/main/java/art/arcane/adapt/util/common/io/SQLManager.java new file mode 100644 index 000000000..f791ef0c8 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/io/SQLManager.java @@ -0,0 +1,136 @@ +package art.arcane.adapt.util.common.io; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; + +import java.sql.*; +import java.util.UUID; + +public class SQLManager { + + private static final String TABLE_NAME = "ADAPT_DATA"; + private static final String CREATE_TABLE_QUERY = "CREATE TABLE " + TABLE_NAME + " (UUID char(36) NOT NULL UNIQUE, DATA MEDIUMTEXT NOT NULL)"; + private static final String UPDATE_QUERY = "INSERT INTO " + TABLE_NAME + " (UUID, DATA) VALUES(?, ?) ON DUPLICATE KEY UPDATE DATA=?"; + private static final String FETCH_QUERY = "SELECT DATA FROM " + TABLE_NAME + " WHERE UUID=?"; + private static final String DELETE_QUERY = "DELETE FROM " + TABLE_NAME + " WHERE UUID=?"; + + private Connection connection; + + public synchronized void establishConnection() { + if (connection != null) { + closeConnection(); + } + + AdaptConfig config = AdaptConfig.get(); + try { + connection = DriverManager.getConnection(assembleUrl(config), config.getSql().getUsername(), config.getSql().getPassword()); + int verifySeconds = Math.max(1, Math.min(10, config.getSqlSecondsCheckverify())); + if (!connection.isValid(verifySeconds)) { + throw new SQLException("Connection timed out"); + } else { + setupDatabase(); + } + } catch (SQLException e) { + handleSQLException("Failed to establish a connection to the SQL server!", e); + connection = null; + } + } + + private void setupDatabase() throws SQLException { + if (!connection.getMetaData().getTables(null, null, TABLE_NAME, new String[]{"TABLE"}).next()) { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate(CREATE_TABLE_QUERY); + } + } + } + + public synchronized void closeConnection() { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + handleSQLException("Failed to close the connection to the SQL server!", e); + } + connection = null; + } + } + + public synchronized void updateData(UUID uuid, String data) { + executeWithRetry(conn -> { + try (PreparedStatement statement = conn.prepareStatement(UPDATE_QUERY)) { + statement.setString(1, uuid.toString()); + statement.setString(2, data); + statement.setString(3, data); + statement.executeUpdate(); + } + }, "Failed to write data to the SQL server!"); + } + + public synchronized void delete(UUID uuid) { + executeWithRetry(conn -> { + try (PreparedStatement statement = conn.prepareStatement(DELETE_QUERY)) { + statement.setString(1, uuid.toString()); + statement.executeUpdate(); + } + }, "Failed to delete data from the SQL server!"); + } + + public synchronized String fetchData(UUID uuid) { + try { + checkAndReestablishConnection(); + try (PreparedStatement statement = connection.prepareStatement(FETCH_QUERY)) { + statement.setString(1, uuid.toString()); + try (ResultSet set = statement.executeQuery()) { + if (!set.next()) { + return null; + } + return set.getString("DATA"); + } + } + } catch (SQLException e) { + handleSQLException("Failed to read data from the SQL server!", e); + return null; + } + } + + private void executeWithRetry(SqlAction action, String errorMessage) { + try { + checkAndReestablishConnection(); + action.run(connection); + } catch (SQLException e) { + handleSQLException(errorMessage, e); + } + } + + private void checkAndReestablishConnection() throws SQLException { + if (connection == null || !connection.isValid(AdaptConfig.get().getSqlSecondsCheckverify())) { // 30 sec by default + establishConnection(); + } + if (connection == null) { + throw new SQLException("No active SQL connection"); + } + } + + private void handleSQLException(String message, SQLException e) { + Adapt.error(message); + Adapt.error("\t" + e.getClass().getSimpleName() + (e.getMessage() != null ? ": " + e.getMessage() : "")); + } + + private String assembleUrl(AdaptConfig config) { + long connectTimeout = Math.max(1000L, config.getSql().getConnectionTimeout()); + long socketTimeout = Math.max(connectTimeout, connectTimeout * 2L); + return String.format( + "jdbc:mysql://%s:%d/%s?connectTimeout=%d&socketTimeout=%d", + config.getSql().getHost(), + config.getSql().getPort(), + config.getSql().getDatabase(), + connectTimeout, + socketTimeout + ); + } + + @FunctionalInterface + interface SqlAction { + void run(Connection connection) throws SQLException; + } +} diff --git a/src/main/java/com/volmit/adapt/util/ShittyGsonDataClass.java b/src/main/java/art/arcane/adapt/util/common/io/ShittyGsonDataClass.java similarity index 81% rename from src/main/java/com/volmit/adapt/util/ShittyGsonDataClass.java rename to src/main/java/art/arcane/adapt/util/common/io/ShittyGsonDataClass.java index 4c9b4e734..2eb3e192b 100644 --- a/src/main/java/com/volmit/adapt/util/ShittyGsonDataClass.java +++ b/src/main/java/art/arcane/adapt/util/common/io/ShittyGsonDataClass.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.io; import lombok.Getter; import lombok.Setter; @@ -25,18 +25,18 @@ @Setter public class ShittyGsonDataClass { + @Getter + @Setter + public class Snippets { + @Getter @Setter - public class Snippets { - - @Getter - @Setter - public class SkillsGUI { - private String Level; - private String Knowledge; - private String PowerUsed; - } + public class SkillsGUI { + private String Level; + private String Knowledge; + private String PowerUsed; } + } } \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/ArrayType.java b/src/main/java/art/arcane/adapt/util/common/math/ArrayType.java similarity index 93% rename from src/main/java/com/volmit/adapt/util/ArrayType.java rename to src/main/java/art/arcane/adapt/util/common/math/ArrayType.java index 466061aac..7b542d96e 100644 --- a/src/main/java/com/volmit/adapt/util/ArrayType.java +++ b/src/main/java/art/arcane/adapt/util/common/math/ArrayType.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.math; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -27,7 +27,7 @@ @Retention(RUNTIME) @Target({PARAMETER, TYPE, FIELD}) public @interface ArrayType { - Class type(); + Class type(); - int min() default 0; + int min() default 0; } diff --git a/src/main/java/com/volmit/adapt/util/CarveResult.java b/src/main/java/art/arcane/adapt/util/common/math/CarveResult.java similarity index 86% rename from src/main/java/com/volmit/adapt/util/CarveResult.java rename to src/main/java/art/arcane/adapt/util/common/math/CarveResult.java index b3f9fd9cb..fc1c1ff1f 100644 --- a/src/main/java/com/volmit/adapt/util/CarveResult.java +++ b/src/main/java/art/arcane/adapt/util/common/math/CarveResult.java @@ -16,16 +16,16 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.math; import lombok.Value; @Value public class CarveResult { - private final int surface; - private final int ceiling; + private final int surface; + private final int ceiling; - public int getHeight() { - return ceiling - surface; - } + public int getHeight() { + return ceiling - surface; + } } diff --git a/src/main/java/com/volmit/adapt/util/CaveResult.java b/src/main/java/art/arcane/adapt/util/common/math/CaveResult.java similarity index 78% rename from src/main/java/com/volmit/adapt/util/CaveResult.java rename to src/main/java/art/arcane/adapt/util/common/math/CaveResult.java index d93dbf85f..c54aa811c 100644 --- a/src/main/java/com/volmit/adapt/util/CaveResult.java +++ b/src/main/java/art/arcane/adapt/util/common/math/CaveResult.java @@ -16,21 +16,21 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.math; import lombok.Data; @Data public class CaveResult { - private int floor; - private int ceiling; + private int floor; + private int ceiling; - public CaveResult(int floor, int ceiling) { - this.floor = floor; - this.ceiling = ceiling; - } + public CaveResult(int floor, int ceiling) { + this.floor = floor; + this.ceiling = ceiling; + } - public boolean isWithin(int v) { - return v > floor || v < ceiling; - } + public boolean isWithin(int v) { + return v > floor || v < ceiling; + } } diff --git a/src/main/java/art/arcane/adapt/util/common/math/ChunkPosition.java b/src/main/java/art/arcane/adapt/util/common/math/ChunkPosition.java new file mode 100644 index 000000000..576525e03 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/ChunkPosition.java @@ -0,0 +1,66 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +public class ChunkPosition { + private int x; + private int z; + + public ChunkPosition(int x, int z) { + this.x = x; + this.z = z; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getZ() { + return z; + } + + public void setZ(int z) { + this.z = z; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + z; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ChunkPosition)) { + return false; + } + ChunkPosition other = (ChunkPosition) obj; + return x == other.x && z == other.z; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/DataPalette.java b/src/main/java/art/arcane/adapt/util/common/math/DataPalette.java new file mode 100644 index 000000000..699631b24 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/DataPalette.java @@ -0,0 +1,126 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import art.arcane.adapt.util.common.nbt.NibbleArray; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public abstract class DataPalette implements Writable { + private static final int DEFAULT_BITS_PER_BLOCK = 4; + private static final int CAPACITY = 4096; + private int bpb; + private NibbleArray data; + private List palette; + + public DataPalette(T defaultValue) { + palette = new ArrayList<>(); + bpb = DEFAULT_BITS_PER_BLOCK; + data = new NibbleArray(bpb, CAPACITY); + data.setAll(Byte.MIN_VALUE); + getPaletteId(defaultValue); + } + + public abstract T readType(DataInputStream i) throws IOException; + + public abstract void writeType(T t, DataOutputStream o) throws IOException; + + @Override + public void write(DataOutputStream o) throws IOException { + o.writeByte(bpb + Byte.MIN_VALUE); + o.writeByte(palette.size() + Byte.MIN_VALUE); + + for (T i : palette) { + writeType(i, o); + } + + data.write(o); + } + + @Override + public void read(DataInputStream i) throws IOException { + bpb = i.readByte() - Byte.MIN_VALUE; + palette = new ArrayList<>(); + int v = i.readByte() - Byte.MIN_VALUE; + + for (int j = 0; j < v; j++) { + palette.add(readType(i)); + } + + data = new NibbleArray(CAPACITY, i); + } + + private final void expand() { + if (bpb < 8) { + changeBitsPerBlock(bpb + 1); + } else { + throw new IndexOutOfBoundsException("The Data Palette can only handle at most 256 block types per 16x16x16 region. We cannot use more than 8 bits per block!"); + } + } + + public final void optimize() { + int targetBits = bpb; + int needed = palette.size(); + + for (int i = 1; i < bpb; i++) { + if (Math.pow(2, i) > needed) { + targetBits = i; + break; + } + } + + changeBitsPerBlock(targetBits); + } + + private final void changeBitsPerBlock(int bits) { + bpb = bits; + data = new NibbleArray(bpb, CAPACITY, data); + } + + public final void set(int x, int y, int z, T d) { + data.set(getCoordinateIndex(x, y, z), getPaletteId(d)); + } + + public final T get(int x, int y, int z) { + return palette.get(data.get(getCoordinateIndex(x, y, z))); + } + + private final int getPaletteId(T d) { + int index = palette.indexOf(d); + + if (index == -1) { + index = palette.size(); + palette.add(d); + + if (palette.size() > Math.pow(2, bpb)) { + expand(); + } + } + + return index + Byte.MIN_VALUE; + } + + private final int getCoordinateIndex(int x, int y, int z) { + return y << 8 | z << 4 | x; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/Dimension.java b/src/main/java/art/arcane/adapt/util/common/math/Dimension.java new file mode 100644 index 000000000..6ad1182c3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/Dimension.java @@ -0,0 +1,89 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +/** + * Dimensions + * + * @author cyberpwn + */ +public class Dimension { + private final int width; + private final int height; + private final int depth; + + /** + * Make a dimension + * + * @param width width of this (X) + * @param height the height (Y) + * @param depth the depth (Z) + */ + public Dimension(int width, int height, int depth) { + this.width = width; + this.height = height; + this.depth = depth; + } + + /** + * Make a dimension + * + * @param width width of this (X) + * @param height the height (Y) + */ + public Dimension(int width, int height) { + this.width = width; + this.height = height; + this.depth = 0; + } + + /** + * Get the direction of the flat part of this dimension (null if no thin + * face) + * + * @return the direction of the flat pane or null + */ + public DimensionFace getPane() { + if (width == 1) { + return DimensionFace.X; + } + + if (height == 1) { + return DimensionFace.Y; + } + + if (depth == 1) { + return DimensionFace.Z; + } + + return null; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getDepth() { + return depth; + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/math/DimensionFace.java b/src/main/java/art/arcane/adapt/util/common/math/DimensionFace.java new file mode 100644 index 000000000..5dea09935 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/DimensionFace.java @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +/** + * Represents a dimension (coordinates not worlds) + * + * @author cyberpwn + */ +public enum DimensionFace { + /** + * The X dimension (width) + */ + X, + + /** + * The Y dimension (height) + */ + Y, + + /** + * The Z dimension (depth) + */ + Z +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/math/Direction.java b/src/main/java/art/arcane/adapt/util/common/math/Direction.java new file mode 100644 index 000000000..e72805447 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/Direction.java @@ -0,0 +1,440 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import art.arcane.volmlib.util.collection.GBiset; +import art.arcane.volmlib.util.data.Cuboid.CuboidDirection; +import art.arcane.volmlib.util.math.DOP; +import org.bukkit.Axis; +import org.bukkit.block.BlockFace; +import org.bukkit.util.Vector; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Directions + * + * @author cyberpwn + */ +public enum Direction { + U(0, 1, 0, CuboidDirection.Up), + D(0, -1, 0, CuboidDirection.Down), + N(0, 0, -1, CuboidDirection.North), + S(0, 0, 1, CuboidDirection.South), + E(1, 0, 0, CuboidDirection.East), + W(-1, 0, 0, CuboidDirection.West); + + private static Map, DOP> permute = null; + + private final int x; + private final int y; + private final int z; + private final CuboidDirection f; + + Direction(int x, int y, int z, CuboidDirection f) { + this.x = x; + this.y = y; + this.z = z; + this.f = f; + } + + public static Direction getDirection(BlockFace f) { + switch (f) { + case DOWN: + return D; + case EAST: + return E; + case EAST_NORTH_EAST: + return E; + case EAST_SOUTH_EAST: + return E; + case NORTH: + return N; + case NORTH_EAST: + return N; + case NORTH_NORTH_EAST: + return N; + case NORTH_NORTH_WEST: + return N; + case NORTH_WEST: + return N; + case SELF: + return U; + case SOUTH: + return S; + case SOUTH_EAST: + return S; + case SOUTH_SOUTH_EAST: + return S; + case SOUTH_SOUTH_WEST: + return S; + case SOUTH_WEST: + return S; + case UP: + return U; + case WEST: + return W; + case WEST_NORTH_WEST: + return W; + case WEST_SOUTH_WEST: + return W; + } + + return D; + } + + public static Direction closest(Vector v) { + double m = Double.MAX_VALUE; + Direction s = null; + + for (Direction i : values()) { + Vector x = i.toVector(); + double g = x.dot(v); + + if (g < m) { + m = g; + s = i; + } + } + + return s; + } + + public static Direction closest(Vector v, Direction... d) { + double m = Double.MAX_VALUE; + Direction s = null; + + for (Direction i : d) { + Vector x = i.toVector(); + double g = x.distance(v); + + if (g < m) { + m = g; + s = i; + } + } + + return s; + } + + public static Direction closest(Vector v, List d) { + double m = Double.MAX_VALUE; + Direction s = null; + + for (Direction i : d) { + Vector x = i.toVector(); + double g = x.distance(v); + + if (g < m) { + m = g; + s = i; + } + } + + return s; + } + + public static List news() { + return Arrays.asList(N, E, W, S); + } + + public static Direction getDirection(Vector v) { + Vector k = VectorMath.triNormalize(v.clone().normalize()); + + for (Direction i : udnews()) { + if (i.x == k.getBlockX() && i.y == k.getBlockY() && i.z == k.getBlockZ()) { + return i; + } + } + + return Direction.N; + } + + public static List udnews() { + return Arrays.asList(U, D, N, E, W, S); + } + + /** + * Get the directional value from the given byte from common directional + * blocks (MUST BE BETWEEN 0 and 5 INCLUSIVE) + * + * @param b the byte + * @return the direction or null if the byte is outside of the inclusive range + * 0-5 + */ + public static Direction fromByte(byte b) { + if (b > 5 || b < 0) { + return null; + } + + if (b == 0) { + return D; + } else if (b == 1) { + return U; + } else if (b == 2) { + return N; + } else if (b == 3) { + return S; + } else if (b == 4) { + return W; + } else { + return E; + } + } + + public static void calculatePermutations() { + if (permute != null) { + return; + } + + permute = new HashMap<>(); + + for (Direction i : udnews()) { + for (Direction j : udnews()) { + GBiset b = new GBiset(i, j); + + if (i.equals(j)) { + permute.put(b, new DOP("DIRECT") { + @Override + public Vector op(Vector v) { + return v; + } + }); + } else if (i.reverse().equals(j)) { + if (i.isVertical()) { + permute.put(b, new DOP("R180CCZ") { + @Override + public Vector op(Vector v) { + return VectorMath.rotate90CCZ(VectorMath.rotate90CCZ(v)); + } + }); + } else { + permute.put(b, new DOP("R180CCY") { + @Override + public Vector op(Vector v) { + return VectorMath.rotate90CCY(VectorMath.rotate90CCY(v)); + } + }); + } + } else if (getDirection(VectorMath.rotate90CX(i.toVector())).equals(j)) { + permute.put(b, new DOP("R90CX") { + @Override + public Vector op(Vector v) { + return VectorMath.rotate90CX(v); + } + }); + } else if (getDirection(VectorMath.rotate90CCX(i.toVector())).equals(j)) { + permute.put(b, new DOP("R90CCX") { + @Override + public Vector op(Vector v) { + return VectorMath.rotate90CCX(v); + } + }); + } else if (getDirection(VectorMath.rotate90CY(i.toVector())).equals(j)) { + permute.put(b, new DOP("R90CY") { + @Override + public Vector op(Vector v) { + return VectorMath.rotate90CY(v); + } + }); + } else if (getDirection(VectorMath.rotate90CCY(i.toVector())).equals(j)) { + permute.put(b, new DOP("R90CCY") { + @Override + public Vector op(Vector v) { + return VectorMath.rotate90CCY(v); + } + }); + } else if (getDirection(VectorMath.rotate90CZ(i.toVector())).equals(j)) { + permute.put(b, new DOP("R90CZ") { + @Override + public Vector op(Vector v) { + return VectorMath.rotate90CZ(v); + } + }); + } else if (getDirection(VectorMath.rotate90CCZ(i.toVector())).equals(j)) { + permute.put(b, new DOP("R90CCZ") { + @Override + public Vector op(Vector v) { + return VectorMath.rotate90CCZ(v); + } + }); + } else { + permute.put(b, new DOP("FAIL") { + @Override + public Vector op(Vector v) { + return v; + } + }); + } + } + } + } + + @Override + public String toString() { + switch (this) { + case D: + return "Down"; + case E: + return "East"; + case N: + return "North"; + case S: + return "South"; + case U: + return "Up"; + case W: + return "West"; + } + + return "?"; + } + + public boolean isVertical() { + return equals(D) || equals(U); + } + + public Vector toVector() { + return new Vector(x, y, z); + } + + public boolean isCrooked(Direction to) { + if (equals(to.reverse())) { + return false; + } + + return !equals(to); + } + + public Vector angle(Vector initial, Direction d) { + calculatePermutations(); + + for (GBiset i : permute.keySet()) { + if (i.getA().equals(this) && i.getB().equals(d)) { + return permute.get(i).op(initial); + } + } + + return initial; + } + + public Direction reverse() { + switch (this) { + case D: + return U; + case E: + return W; + case N: + return S; + case S: + return N; + case U: + return D; + case W: + return E; + default: + break; + } + + return null; + } + + public int x() { + return x; + } + + public int y() { + return y; + } + + public int z() { + return z; + } + + public CuboidDirection f() { + return f; + } + + /** + * Get the byte value represented in some directional blocks + * + * @return the byte value + */ + public byte byteValue() { + switch (this) { + case D: + return 0; + case E: + return 5; + case N: + return 2; + case S: + return 3; + case U: + return 1; + case W: + return 4; + default: + break; + } + + return -1; + } + + public BlockFace getFace() { + switch (this) { + case D: + return BlockFace.DOWN; + case E: + return BlockFace.EAST; + case N: + return BlockFace.NORTH; + case S: + return BlockFace.SOUTH; + case U: + return BlockFace.UP; + case W: + return BlockFace.WEST; + } + + return null; + } + + public Axis getAxis() { + switch (this) { + case D: + return Axis.Y; + case E: + return Axis.X; + case N: + return Axis.Z; + case S: + return Axis.Z; + case U: + return Axis.Y; + case W: + return Axis.X; + } + + return null; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/DoubleArrayUtils.java b/src/main/java/art/arcane/adapt/util/common/math/DoubleArrayUtils.java new file mode 100644 index 000000000..1c45cc14a --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/DoubleArrayUtils.java @@ -0,0 +1,50 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + + +import com.google.common.util.concurrent.AtomicDoubleArray; + +import java.util.Arrays; + +public class DoubleArrayUtils { + public static void shiftRight(double[] values, double push) { + for (int index = values.length - 2; index >= 0; index--) { + values[index + 1] = values[index]; + } + + values[0] = push; + } + + public static void wrapRight(double[] values) { + double last = values[values.length - 1]; + shiftRight(values, last); + } + + public static void fill(double[] values, double value) { + Arrays.fill(values, value); + } + + public static void fill(AtomicDoubleArray values, double value) { + for (int i = 0; i < values.length(); i++) { + values.set(i, value); + } + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/HeightMap.java b/src/main/java/art/arcane/adapt/util/common/math/HeightMap.java new file mode 100644 index 000000000..8cb2d9482 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/HeightMap.java @@ -0,0 +1,38 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import java.util.Arrays; + +public class HeightMap { + private final byte[] height; + + public HeightMap() { + height = new byte[256]; + Arrays.fill(height, Byte.MIN_VALUE); + } + + public void setHeight(int x, int z, int h) { + height[x * 16 + z] = (byte) (h + Byte.MIN_VALUE); + } + + public int getHeight(int x, int z) { + return height[x * 16 + z] - Byte.MIN_VALUE; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/IObjectPlacer.java b/src/main/java/art/arcane/adapt/util/common/math/IObjectPlacer.java new file mode 100644 index 000000000..ce0d7c65b --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/IObjectPlacer.java @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import org.bukkit.block.data.BlockData; + +public interface IObjectPlacer { + int getHighest(int x, int z); + + int getHighest(int x, int z, boolean ignoreFluid); + + void set(int x, int y, int z, BlockData d); + + BlockData get(int x, int y, int z); + + boolean isPreventingDecay(); + + boolean isSolid(int x, int y, int z); + + boolean isUnderwater(int x, int z); + + int getFluidHeight(); + + boolean isDebugSmartBore(); +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/IPostBlockAccess.java b/src/main/java/art/arcane/adapt/util/common/math/IPostBlockAccess.java new file mode 100644 index 000000000..cbced2c3f --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/IPostBlockAccess.java @@ -0,0 +1,38 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import org.bukkit.block.data.BlockData; +import org.bukkit.generator.ChunkGenerator.ChunkData; + +import java.util.List; + +public interface IPostBlockAccess { + BlockData getPostBlock(int x, int y, int z, int currentPostX, int currentPostZ, ChunkData currentData); + + void setPostBlock(int x, int y, int z, BlockData d, int currentPostX, int currentPostZ, ChunkData currentData); + + int highestTerrainOrFluidBlock(int x, int z); + + int highestTerrainBlock(int x, int z); + + void updateHeight(int x, int z, int h); + + List caveFloors(int x, int z); +} diff --git a/src/main/java/com/volmit/adapt/util/IRare.java b/src/main/java/art/arcane/adapt/util/common/math/IRare.java similarity index 86% rename from src/main/java/com/volmit/adapt/util/IRare.java rename to src/main/java/art/arcane/adapt/util/common/math/IRare.java index 12dc3fae1..5a7f373f6 100644 --- a/src/main/java/com/volmit/adapt/util/IRare.java +++ b/src/main/java/art/arcane/adapt/util/common/math/IRare.java @@ -16,12 +16,12 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.math; public interface IRare { - static int get(Object v) { - return v instanceof IRare ? ((IRare) v).getRarity() : 1; - } + static int get(Object v) { + return v instanceof IRare ? ((IRare) v).getRarity() : 1; + } - int getRarity(); + int getRarity(); } diff --git a/src/main/java/com/volmit/adapt/util/InterpolationType.java b/src/main/java/art/arcane/adapt/util/common/math/InterpolationType.java similarity index 90% rename from src/main/java/com/volmit/adapt/util/InterpolationType.java rename to src/main/java/art/arcane/adapt/util/common/math/InterpolationType.java index 18c659e5f..5899caf0b 100644 --- a/src/main/java/com/volmit/adapt/util/InterpolationType.java +++ b/src/main/java/art/arcane/adapt/util/common/math/InterpolationType.java @@ -16,12 +16,12 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.math; public enum InterpolationType { - LINEAR, - PARAMETRIC_2, - PARAMETRIC_4, - BEZIER, - NONE + LINEAR, + PARAMETRIC_2, + PARAMETRIC_4, + BEZIER, + NONE } \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/math/MaterialBlock.java b/src/main/java/art/arcane/adapt/util/common/math/MaterialBlock.java new file mode 100644 index 000000000..ec876e2fa --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/MaterialBlock.java @@ -0,0 +1,126 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; + +/** + * Material blocks + * + * @author cyberpwn + */ +@SuppressWarnings("deprecation") +public class MaterialBlock { + private Material material; + private Byte data; + + /** + * Create a materialblock + * + * @param material the material + * @param data the data + */ + public MaterialBlock(Material material, Byte data) { + this.material = material; + this.data = data; + } + + public MaterialBlock(Material material) { + this.material = material; + data = 0; + } + + public MaterialBlock(Location location) { + this(location.getBlock()); + } + + public MaterialBlock(BlockState state) { + material = state.getType(); + data = state.getData().getData(); + } + + public MaterialBlock(Block block) { + material = block.getType(); + data = block.getData(); + } + + public MaterialBlock() { + material = Material.AIR; + data = 0; + } + + public Material getMaterial() { + return material; + } + + public void setMaterial(Material material) { + this.material = material; + } + + public Byte getData() { + return data; + } + + public void setData(Byte data) { + this.data = data; + } + + @Override + public String toString() { + if (getData() == 0) { + return getMaterial().toString(); + } + + return getMaterial().toString() + ":" + getData(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((data == null) ? 0 : data.hashCode()); + result = prime * result + ((material == null) ? 0 : material.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MaterialBlock other = (MaterialBlock) obj; + if (data == null) { + if (other.data != null) { + return false; + } + } else if (!data.equals(other.data)) { + return false; + } + return material == other.material; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/MathHelper.java b/src/main/java/art/arcane/adapt/util/common/math/MathHelper.java new file mode 100644 index 000000000..a6a251b4b --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/MathHelper.java @@ -0,0 +1,493 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import art.arcane.volmlib.util.math.BlockPosition; + +import java.util.Random; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.IntPredicate; + +public class MathHelper { + public static final float a = MathHelper.c(2.0f); + private static final float[] b = (float[]) a((Object) new float[65536], var0 -> + { + for (int var1 = 0; var1 < ((float[]) var0).length; ++var1) { + ((float[]) var0)[var1] = (float) Math.sin((double) var1 * 3.141592653589793 * 2.0 / 65536.0); + } + }); + private static final Random c = new Random(); + private static final int[] d = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + private static final double e = Double.longBitsToDouble(4805340802404319232L); + private static final double[] f = new double[257]; + private static final double[] g = new double[257]; + + static { + for (int var02 = 0; var02 < 257; ++var02) { + double var1 = (double) var02 / 256.0; + double var3 = Math.asin(var1); + MathHelper.g[var02] = Math.cos(var3); + MathHelper.f[var02] = var3; + } + } + + public static T a(T var0, Consumer var1) { + var1.accept(var0); + return var0; + } + + public static float sin(float var0) { + return b[(int) (var0 * 10430.378f) & 65535]; + } + + public static float cos(float var0) { + return b[(int) (var0 * 10430.378f + 16384.0f) & 65535]; + } + + public static float c(float var0) { + return (float) Math.sqrt(var0); + } + + public static float sqrt(double var0) { + return (float) Math.sqrt(var0); + } + + public static int d(float var0) { + int var1 = (int) var0; + return var0 < (float) var1 ? var1 - 1 : var1; + } + + public static int floor(double var0) { + int var2 = (int) var0; + return var0 < (double) var2 ? var2 - 1 : var2; + } + + public static long d(double var0) { + long var2 = (long) var0; + return var0 < (double) var2 ? var2 - 1L : var2; + } + + public static float e(float var0) { + return Math.abs(var0); + } + + public static int a(int var0) { + return Math.abs(var0); + } + + public static int f(float var0) { + int var1 = (int) var0; + return var0 > (float) var1 ? var1 + 1 : var1; + } + + public static int f(double var0) { + int var2 = (int) var0; + return var0 > (double) var2 ? var2 + 1 : var2; + } + + public static int clamp(int var0, int var1, int var2) { + if (var0 < var1) { + return var1; + } + if (var0 > var2) { + return var2; + } + return var0; + } + + public static float a(float var0, float var1, float var2) { + if (var0 < var1) { + return var1; + } + if (var0 > var2) { + return var2; + } + return var0; + } + + public static double a(double var0, double var2, double var4) { + if (var0 < var2) { + return var2; + } + if (var0 > var4) { + return var4; + } + return var0; + } + + public static double b(double var0, double var2, double var4) { + if (var4 < 0.0) { + return var0; + } + if (var4 > 1.0) { + return var2; + } + return MathHelper.d(var4, var0, var2); + } + + public static double a(double var0, double var2) { + if (var0 < 0.0) { + var0 = -var0; + } + if (var2 < 0.0) { + var2 = -var2; + } + return var0 > var2 ? var0 : var2; + } + + public static int a(int var0, int var1) { + return Math.floorDiv(var0, var1); + } + + public static int nextInt(Random var0, int var1, int var2) { + if (var1 >= var2) { + return var1; + } + return var0.nextInt(var2 - var1 + 1) + var1; + } + + public static float a(Random var0, float var1, float var2) { + if (var1 >= var2) { + return var1; + } + return var0.nextFloat() * (var2 - var1) + var1; + } + + public static double a(Random var0, double var1, double var3) { + if (var1 >= var3) { + return var1; + } + return var0.nextDouble() * (var3 - var1) + var1; + } + + public static double a(long[] var0) { + long var1 = 0L; + for (long var6 : var0) { + var1 += var6; + } + return (double) var1 / (double) var0.length; + } + + public static boolean b(double var0, double var2) { + return Math.abs(var2 - var0) < 9.999999747378752E-6; + } + + public static int b(int var0, int var1) { + return Math.floorMod(var0, var1); + } + + public static float g(float var0) { + float var1 = var0 % 360.0f; + if (var1 >= 180.0f) { + var1 -= 360.0f; + } + if (var1 < -180.0f) { + var1 += 360.0f; + } + return var1; + } + + public static double g(double var0) { + double var2 = var0 % 360.0; + if (var2 >= 180.0) { + var2 -= 360.0; + } + if (var2 < -180.0) { + var2 += 360.0; + } + return var2; + } + + public static float c(float var0, float var1) { + return MathHelper.g(var1 - var0); + } + + public static float d(float var0, float var1) { + return MathHelper.e(MathHelper.c(var0, var1)); + } + + public static float b(float var0, float var1, float var2) { + float var3 = MathHelper.c(var0, var1); + float var4 = MathHelper.a(var3, -var2, var2); + return var1 - var4; + } + + public static float c(float var0, float var1, float var2) { + var2 = MathHelper.e(var2); + if (var0 < var1) { + return MathHelper.a(var0 + var2, var0, var1); + } + return MathHelper.a(var0 - var2, var1, var0); + } + + public static float d(float var0, float var1, float var2) { + float var3 = MathHelper.c(var0, var1); + return MathHelper.c(var0, var0 + var3, var2); + } + + public static int c(int var0) { + int var1 = var0 - 1; + var1 |= var1 >> 1; + var1 |= var1 >> 2; + var1 |= var1 >> 4; + var1 |= var1 >> 8; + var1 |= var1 >> 16; + return var1 + 1; + } + + public static boolean d(int var0) { + return var0 != 0 && (var0 & var0 - 1) == 0; + } + + public static int e(int var0) { + var0 = MathHelper.d(var0) ? var0 : MathHelper.c(var0); + return d[(int) ((long) var0 * 125613361L >> 27) & 31]; + } + + public static int f(int var0) { + return MathHelper.e(var0) - (MathHelper.d(var0) ? 0 : 1); + } + + public static int c(int var0, int var1) { + int var2; + if (var1 == 0) { + return 0; + } + if (var0 == 0) { + return var1; + } + if (var0 < 0) { + var1 *= -1; + } + if ((var2 = var0 % var1) == 0) { + return var0; + } + return var0 + var1 - var2; + } + + public static float h(float var0) { + return var0 - (float) MathHelper.d(var0); + } + + public static double h(double var0) { + return var0 - (double) MathHelper.d(var0); + } + + public static long a(BlockPosition var0) { + return c(var0.getX(), var0.getY(), var0.getZ()); + } + + public static long c(int var0, int var1, int var2) { + long var3 = (long) (var0 * 3129871) ^ (long) var2 * 116129781L ^ (long) var1; + var3 = var3 * var3 * 42317861L + var3 * 11L; + return var3 >> 16; + } + + public static UUID a(Random var0) { + long var1 = var0.nextLong() & -61441L | 16384L; + long var3 = var0.nextLong() & 0x3FFFFFFFFFFFFFFFL | Long.MIN_VALUE; + return new UUID(var1, var3); + } + + public static UUID a() { + return MathHelper.a(c); + } + + public static double c(double var0, double var2, double var4) { + return (var0 - var2) / (var4 - var2); + } + + public static double d(double var0, double var2) { + double var9; + boolean var6; + boolean var7; + boolean var8; + double var4 = var2 * var2 + var0 * var0; + if (Double.isNaN(var4)) { + return Double.NaN; + } + @SuppressWarnings("unused") + boolean bl = var6 = var0 < 0.0; + if (var6) { + var0 = -var0; + } + @SuppressWarnings("unused") + boolean bl2 = var7 = var2 < 0.0; + if (var7) { + var2 = -var2; + } + @SuppressWarnings("unused") + boolean bl3 = var8 = var0 > var2; + if (var8) { + var9 = var2; + var2 = var0; + var0 = var9; + } + var9 = MathHelper.i(var4); + double var11 = e + (var0 *= var9); + int var13 = (int) Double.doubleToRawLongBits(var11); + double var14 = f[var13]; + double var16 = g[var13]; + double var18 = var11 - e; + double var20 = var0 * var16 - (var2 *= var9) * var18; + double var22 = (6.0 + var20 * var20) * var20 * 0.16666666666666666; + double var24 = var14 + var22; + if (var8) { + var24 = 1.5707963267948966 - var24; + } + if (var7) { + var24 = 3.141592653589793 - var24; + } + if (var6) { + var24 = -var24; + } + return var24; + } + + public static double i(double var0) { + double var2 = 0.5 * var0; + long var4 = Double.doubleToRawLongBits(var0); + var4 = 6910469410427058090L - (var4 >> 1); + var0 = Double.longBitsToDouble(var4); + var0 *= 1.5 - var2 * var0 * var0; + return var0; + } + + public static int f(float var0, float var1, float var2) { + float var9; + float var8; + float var10; + int var3 = (int) (var0 * 6.0f) % 6; + float var4 = var0 * 6.0f - (float) var3; + float var5 = var2 * (1.0f - var1); + float var6 = var2 * (1.0f - var4 * var1); + float var7 = var2 * (1.0f - (1.0f - var4) * var1); + switch (var3) { + case 0: { + var8 = var2; + var9 = var7; + var10 = var5; + break; + } + case 1: { + var8 = var6; + var9 = var2; + var10 = var5; + break; + } + case 2: { + var8 = var5; + var9 = var2; + var10 = var7; + break; + } + case 3: { + var8 = var5; + var9 = var6; + var10 = var2; + break; + } + case 4: { + var8 = var7; + var9 = var5; + var10 = var2; + break; + } + case 5: { + var8 = var2; + var9 = var5; + var10 = var6; + break; + } + default: { + throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + var0 + ", " + var1 + ", " + var2); + } + } + int var11 = MathHelper.clamp((int) (var8 * 255.0f), 0, 255); + int var12 = MathHelper.clamp((int) (var9 * 255.0f), 0, 255); + int var13 = MathHelper.clamp((int) (var10 * 255.0f), 0, 255); + return var11 << 16 | var12 << 8 | var13; + } + + public static int g(int var0) { + var0 ^= var0 >>> 16; + var0 *= -2048144789; + var0 ^= var0 >>> 13; + var0 *= -1028477387; + var0 ^= var0 >>> 16; + return var0; + } + + public static int a(int var0, int var1, IntPredicate var2) { + int var3 = var1 - var0; + while (var3 > 0) { + int var4 = var3 / 2; + int var5 = var0 + var4; + if (var2.test(var5)) { + var3 = var4; + continue; + } + var0 = var5 + 1; + var3 -= var4 + 1; + } + return var0; + } + + public static float g(float var0, float var1, float var2) { + return var1 + var0 * (var2 - var1); + } + + public static double d(double var0, double var2, double var4) { + return var2 + var0 * (var4 - var2); + } + + public static double a(double var0, double var2, double var4, double var6, double var8, double var10) { + return MathHelper.d(var2, MathHelper.d(var0, var4, var6), MathHelper.d(var0, var8, var10)); + } + + public static double a(double var0, double var2, double var4, double var6, double var8, double var10, double var12, double var14, double var16, double var18, double var20) { + return MathHelper.d(var4, MathHelper.a(var0, var2, var6, var8, var10, var12), MathHelper.a(var0, var2, var14, var16, var18, var20)); + } + + public static double j(double var0) { + return var0 * var0 * var0 * (var0 * (var0 * 6.0 - 15.0) + 10.0); + } + + public static int k(double var0) { + if (var0 == 0.0) { + return 0; + } + return var0 > 0.0 ? 1 : -1; + } + + public static float j(float var0, float var1, float var2) { + float var3; + for (var3 = var1 - var0; var3 < -180.0f; var3 += 360.0f) { + } + while (var3 >= 180.0f) { + var3 -= 360.0f; + } + return var0 + var2 * var3; + } + + public static float k(float var0) { + return var0 * var0; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/Sphere.java b/src/main/java/art/arcane/adapt/util/common/math/Sphere.java new file mode 100644 index 000000000..e761b1b5c --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/Sphere.java @@ -0,0 +1,50 @@ +package art.arcane.adapt.util.common.math; + +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.math.BlockPosition; + +import java.util.Iterator; + +public class Sphere implements Iterator, Cloneable { + private final KList blocks; + private int i = 0; + + public Sphere(int radius) { + int dist = radius * radius * radius; + + blocks = new KList<>(); + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + for (int y = -radius; y <= radius; y++) { + if (x * x + z * z + y * y > dist) + continue; + + blocks.add(new BlockPosition(x, y, z)); + } + } + } + } + + private Sphere(KList blocks) { + this.blocks = blocks.copy(); + } + + public void reset() { + i = 0; + } + + @Override + public boolean hasNext() { + return i < blocks.size(); + } + + @Override + public BlockPosition next() { + return blocks.get(i++); + } + + @Override + public Sphere clone() { + return new Sphere(blocks); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/VectorMath.java b/src/main/java/art/arcane/adapt/util/common/math/VectorMath.java new file mode 100644 index 000000000..4845de80c --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/VectorMath.java @@ -0,0 +1,623 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import art.arcane.volmlib.util.collection.GListAdapter; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.math.CDou; +import org.bukkit.Axis; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; + +/** + * Vector utilities + * + * @author cyberpwn + */ +public class VectorMath { + public static Vector scaleStatic(Axis x, Vector v, double amt) { + switch (x) { + case X: + return scaleX(v, amt); + case Y: + return scaleY(v, amt); + case Z: + return scaleZ(v, amt); + } + + return null; + } + + public static Vector scaleX(Vector v, double amt) { + double x = v.getX(); + double y = v.getY(); + double z = v.getZ(); + double rx = x == 0 ? 1 : amt / x; + + return new Vector(x * rx, y * rx, z * rx); + } + + public static Vector scaleY(Vector v, double amt) { + double x = v.getX(); + double y = v.getY(); + double z = v.getZ(); + double rx = y == 0 ? 1 : amt / y; + + return new Vector(x * rx, y * rx, z * rx); + } + + public static Vector scaleZ(Vector v, double amt) { + double x = v.getX(); + double y = v.getY(); + double z = v.getZ(); + double rx = z == 0 ? 1 : amt / z; + + return new Vector(x * rx, y * rx, z * rx); + } + + public static Vector reverseXZ(Vector v) { + v.setX(-v.getX()); + v.setZ(-v.getZ()); + return v; + } + + public static boolean isLookingNear(Location a, Location b, double maxOff) { + Vector perfect = VectorMath.direction(a, b); + Vector actual = a.getDirection(); + + return perfect.distance(actual) <= maxOff; + } + + public static Vector rotate(Direction current, Direction to, Vector v) { + if (current.equals(to)) { + return v; + } else if (current.equals(to.reverse())) { + if (current.isVertical()) { + return new Vector(v.getX(), -v.getY(), v.getZ()); + } else { + return new Vector(-v.getX(), v.getY(), -v.getZ()); + } + } else { + Vector c = current.toVector().clone().add(to.toVector()); + + if (c.getX() == 0) { + if (c.getY() != c.getZ()) { + return rotate90CX(v); + } + + return rotate90CCX(v); + } else if (c.getY() == 0) { + if (c.getX() != c.getZ()) { + return rotate90CY(v); + } + + return rotate90CCY(v); + } else if (c.getZ() == 0) { + if (c.getX() != c.getY()) { + return rotate90CZ(v); + } + + return rotate90CCZ(v); + } + } + + return v; + } + + // Y X 0 0 + // X Z 0 0 + + // 0 X Y 0 + // 0 Z X 0 + + public static Vector rotate(Direction current, Direction to, Vector v, int w, int h, int d) { + if (current.equals(to)) { + return v; + } else if (current.equals(to.reverse())) { + if (current.isVertical()) { + return new Vector(v.getX(), -v.getY() + h, v.getZ()); + } else { + return new Vector(-v.getX() + w, v.getY(), -v.getZ() + d); + } + } else { + Vector c = current.toVector().clone().add(to.toVector()); + + if (c.getX() == 0) { + if (c.getY() != c.getZ()) { + return rotate90CX(v, d); + } + + return rotate90CCX(v, h); + } else if (c.getY() == 0) { + if (c.getX() != c.getZ()) { + return rotate90CY(v, d); + } + + return rotate90CCY(v, w); + } else if (c.getZ() == 0) { + if (c.getX() != c.getY()) { + return rotate90CZ(v, w); + } + + return rotate90CCZ(v, h); + } + } + + return v; + } + + public static Vector rotate90CX(Vector v) { + return new Vector(v.getX(), -v.getZ(), v.getY()); + } + + public static Vector rotate90CCX(Vector v) { + return new Vector(v.getX(), v.getZ(), -v.getY()); + } + + public static Vector rotate90CY(Vector v) { + return new Vector(-v.getZ(), v.getY(), v.getX()); + } + + public static Vector rotate90CCY(Vector v) { + return new Vector(v.getZ(), v.getY(), -v.getX()); + } + + public static Vector rotate90CZ(Vector v) { + return new Vector(v.getY(), -v.getX(), v.getZ()); + } + + public static Vector rotate90CCZ(Vector v) { + return new Vector(-v.getY(), v.getX(), v.getZ()); + } + + public static Vector rotate90CX(Vector v, int s) { + return new Vector(v.getX(), -v.getZ() + s, v.getY()); + } + + public static Vector rotate90CCX(Vector v, int s) { + return new Vector(v.getX(), v.getZ(), -v.getY() + s); + } + + public static Vector rotate90CY(Vector v, int s) { + return new Vector(-v.getZ() + s, v.getY(), v.getX()); + } + + public static Vector rotate90CCY(Vector v, int s) { + return new Vector(v.getZ(), v.getY(), -v.getX() + s); + } + + public static Vector rotate90CZ(Vector v, int s) { + return new Vector(v.getY(), -v.getX() + s, v.getZ()); + } + + public static Vector rotate90CCZ(Vector v, int s) { + return new Vector(-v.getY() + s, v.getX(), v.getZ()); + } + + public static Vector getAxis(Direction current, Direction to) { + if (current.equals(Direction.U) || current.equals(Direction.D)) { + if (to.equals(Direction.U) || to.equals(Direction.D)) { + return new Vector(1, 0, 0); + } else { + if (current.equals(Direction.N) || current.equals(Direction.S)) { + return Direction.E.toVector(); + } else { + return Direction.S.toVector(); + } + } + } + + return new Vector(0, 1, 0); + } + + private static double round(double value, int precision) { + return Double.valueOf(Form.f(value, precision)); + } + + public static Vector clip(Vector v, int decimals) { + v.setX(round(v.getX(), decimals)); + v.setY(round(v.getY(), decimals)); + v.setZ(round(v.getZ(), decimals)); + return v; + } + + public static Vector rotateVectorCC(Vector vec, Vector axis, double deg) { + double theta = Math.toRadians(deg); + double x, y, z; + double u, v, w; + x = vec.getX(); + y = vec.getY(); + z = vec.getZ(); + u = axis.getX(); + v = axis.getY(); + w = axis.getZ(); + double xPrime = u * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + x * Math.cos(theta) + (-w * y + v * z) * Math.sin(theta); + double yPrime = v * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + y * Math.cos(theta) + (w * x - u * z) * Math.sin(theta); + double zPrime = w * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + z * Math.cos(theta) + (-v * x + u * y) * Math.sin(theta); + + return clip(new Vector(xPrime, yPrime, zPrime), 4); + } + + /** + * Get all SIMPLE block faces from a more specific block face (SOUTH_EAST) = + * (south, east) + * + * @param f the block face + * @return multiple faces, or one if the face is already simple + */ + public static List split(BlockFace f) { + List faces = new ArrayList<>(); + + switch (f) { + case DOWN: + faces.add(BlockFace.DOWN); + break; + case EAST: + faces.add(BlockFace.EAST); + break; + case EAST_NORTH_EAST: + faces.add(BlockFace.EAST); + faces.add(BlockFace.EAST); + faces.add(BlockFace.NORTH); + break; + case EAST_SOUTH_EAST: + faces.add(BlockFace.EAST); + faces.add(BlockFace.EAST); + faces.add(BlockFace.SOUTH); + break; + case NORTH: + faces.add(BlockFace.NORTH); + break; + case NORTH_EAST: + faces.add(BlockFace.NORTH); + faces.add(BlockFace.EAST); + break; + case NORTH_NORTH_EAST: + faces.add(BlockFace.NORTH); + faces.add(BlockFace.NORTH); + faces.add(BlockFace.EAST); + break; + case NORTH_NORTH_WEST: + faces.add(BlockFace.NORTH); + faces.add(BlockFace.NORTH); + faces.add(BlockFace.WEST); + break; + case NORTH_WEST: + faces.add(BlockFace.NORTH); + faces.add(BlockFace.WEST); + break; + case SELF: + faces.add(BlockFace.SELF); + break; + case SOUTH: + faces.add(BlockFace.SOUTH); + break; + case SOUTH_EAST: + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.EAST); + break; + case SOUTH_SOUTH_EAST: + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.EAST); + break; + case SOUTH_SOUTH_WEST: + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.WEST); + break; + case SOUTH_WEST: + faces.add(BlockFace.SOUTH); + faces.add(BlockFace.WEST); + break; + case UP: + faces.add(BlockFace.UP); + break; + case WEST: + faces.add(BlockFace.WEST); + break; + case WEST_NORTH_WEST: + faces.add(BlockFace.WEST); + faces.add(BlockFace.WEST); + faces.add(BlockFace.NORTH); + break; + case WEST_SOUTH_WEST: + faces.add(BlockFace.WEST); + faces.add(BlockFace.WEST); + faces.add(BlockFace.SOUTH); + break; + default: + break; + } + + return faces; + } + + /** + * Get a normalized vector going from a location to another + * + * @param from from here + * @param to to here + * @return the normalized vector direction + */ + public static Vector direction(Location from, Location to) { + return to.clone().subtract(from.clone()).toVector().normalize(); + } + + public static Vector directionNoNormal(Location from, Location to) { + return to.clone().subtract(from.clone()).toVector(); + } + + /** + * Get the vector direction from the yaw and pitch + * + * @param yaw the yaw + * @param pitch the pitch + * @return the vector + */ + public static Vector toVector(float yaw, float pitch) { + return new Vector(Math.cos(pitch) * Math.cos(yaw), Math.sin(pitch), Math.cos(pitch) * Math.sin(-yaw)); + } + + /** + * Add an impulse (force) to an entity + * + * @param e the entity + * @param v the vector + */ + public static void impulse(Entity e, Vector v) { + impulse(e, v, 1.0); + } + + /** + * Add an impulse (force) on an entity + * + * @param e the entity + * @param v the vector + * @param effectiveness the effectiveness + */ + public static void impulse(Entity e, Vector v, double effectiveness) { + Vector vx = e.getVelocity(); + vx.add(v.clone().multiply(effectiveness)); + e.setVelocity(vx); + } + + /** + * Reverse a direction + * + * @param v the direction + * @return the reversed direction + */ + public static Vector reverse(Vector v) { + if (v.getX() != 0) { + v.setX(-v.getX()); + } + + if (v.getY() != 0) { + v.setY(-v.getY()); + } + + if (v.getZ() != 0) { + v.setZ(-v.getZ()); + } + + return v; + } + + /** + * Get a speed value from a vector (velocity) + * + * @param v the vector + * @return the speed + */ + public static double getSpeed(Vector v) { + Vector vi = new Vector(0, 0, 0); + Vector vt = new Vector(0, 0, 0).add(v); + + return vi.distance(vt); + } + + /** + * Shift all vectors based on the given vector + * + * @param vector the vector direction to shift the vectors + * @param vectors the vectors to be shifted + * @return the shifted vectors + */ + public static List shift(Vector vector, List vectors) { + return new ArrayList<>(new GListAdapter() { + @Override + public Vector onAdapt(Vector from) { + return from.add(vector); + } + }.adapt(vectors)); + } + + /** + * Attempt to get the blockFace for the vector (will be tri-normalized) + * + * @param v the vector + * @return the block face or null + */ + public static BlockFace getBlockFace(Vector v) { + Vector p = triNormalize(v); + + for (BlockFace i : BlockFace.values()) { + if (p.getX() == i.getModX() && p.getY() == i.getModY() && p.getZ() == i.getModZ()) { + return i; + } + } + + for (BlockFace i : BlockFace.values()) { + if (p.getX() == i.getModX() && p.getZ() == i.getModZ()) { + return i; + } + } + + for (BlockFace i : BlockFace.values()) { + if (p.getY() == i.getModY() && p.getZ() == i.getModZ()) { + return i; + } + } + + for (BlockFace i : BlockFace.values()) { + if (p.getX() == i.getModX() || p.getY() == i.getModY()) { + return i; + } + } + + for (BlockFace i : BlockFace.values()) { + if (p.getX() == i.getModX() || p.getY() == i.getModY() || p.getZ() == i.getModZ()) { + return i; + } + } + + return null; + } + + /** + * Angle the vector in a self relative direction + * + * @param v the initial direction + * @param amt the amount to shift in the direction + * @return the shifted direction + */ + public static Vector angleLeft(Vector v, float amt) { + Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + l.setDirection(v); + float y = l.getYaw(); + float p = l.getPitch(); + CDou cy = new CDou(360); + CDou cp = new CDou(180); + cy.set(y); + cp.set(p); + cy.sub(amt); + l.setYaw((float) cy.get()); + l.setPitch((float) cp.get()); + + return l.getDirection(); + } + + /** + * Angle the vector in a self relative direction + * + * @param v the initial direction + * @param amt the amount to shift in the direction + * @return the shifted direction + */ + public static Vector angleRight(Vector v, float amt) { + Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + l.setDirection(v); + float y = l.getYaw(); + float p = l.getPitch(); + CDou cy = new CDou(360); + CDou cp = new CDou(180); + cy.set(y); + cp.set(p); + cy.add(amt); + l.setYaw((float) cy.get()); + l.setPitch((float) cp.get()); + + return l.getDirection(); + } + + /** + * Angle the vector in a self relative direction + * + * @param v the initial direction + * @param amt the amount to shift in the direction + * @return the shifted direction + */ + public static Vector angleUp(Vector v, float amt) { + Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + l.setDirection(v); + float y = l.getYaw(); + float p = l.getPitch(); + CDou cy = new CDou(360); + cy.set(y); + l.setYaw((float) cy.get()); + l.setPitch(Math.max(-90, p - amt)); + + return l.getDirection(); + } + + /** + * Angle the vector in a self relative direction + * + * @param v the initial direction + * @param amt the amount to shift in the direction + * @return the shifted direction + */ + public static Vector angleDown(Vector v, float amt) { + Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); + l.setDirection(v); + float y = l.getYaw(); + float p = l.getPitch(); + CDou cy = new CDou(360); + cy.set(y); + l.setYaw((float) cy.get()); + l.setPitch(Math.min(90, p + amt)); + + return l.getDirection(); + } + + /** + * (clone) Force normalize the vector into three points, 1, 0, or -1. If the + * value is > 0.333 (1) if the value is less than -0.333 (-1) else 0 + * + * @param direction the direction + * @return the vector + */ + public static Vector triNormalize(Vector direction) { + Vector v = direction.clone(); + v.normalize(); + + if (v.getX() > 0.333) { + v.setX(1); + } else if (v.getX() < -0.333) { + v.setX(-1); + } else { + v.setX(0); + } + + if (v.getY() > 0.333) { + v.setY(1); + } else if (v.getY() < -0.333) { + v.setY(-1); + } else { + v.setY(0); + } + + if (v.getZ() > 0.333) { + v.setZ(1); + } else if (v.getZ() < -0.333) { + v.setZ(-1); + } else { + v.setZ(0); + } + + return v; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/VelocitySpeed.java b/src/main/java/art/arcane/adapt/util/common/math/VelocitySpeed.java new file mode 100644 index 000000000..25817e64c --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/VelocitySpeed.java @@ -0,0 +1,130 @@ +package art.arcane.adapt.util.common.math; + +import org.bukkit.Input; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +public final class VelocitySpeed { + public static final double EPSILON = 1.0E-6; + + private VelocitySpeed() { + } + + public static InputSnapshot readInput(Player p, double fallbackThresholdSquared) { + try { + Input input = p.getCurrentInput(); + if (input != null) { + InputSnapshot snapshot = new InputSnapshot(input.isForward(), input.isBackward(), input.isLeft(), input.isRight()); + if (snapshot.hasHorizontal()) { + return snapshot; + } + } + } catch (NoSuchMethodError ignored) { + // Fallback path for runtimes without Player#getCurrentInput. + } + + Vector horizontal = horizontalOnly(p.getVelocity()); + if (horizontal.lengthSquared() <= Math.max(0, fallbackThresholdSquared)) { + return InputSnapshot.NONE; + } + + Vector movementDirection = horizontal.normalize(); + Vector look = p.getLocation().getDirection().setY(0); + if (look.lengthSquared() <= EPSILON) { + return InputSnapshot.NONE; + } + + look.normalize(); + Vector right = new Vector(-look.getZ(), 0, look.getX()); + double forwardDot = movementDirection.dot(look); + double sideDot = movementDirection.dot(right); + return new InputSnapshot(forwardDot > 0.2, forwardDot < -0.2, sideDot < -0.2, sideDot > 0.2); + } + + public static Vector resolveHorizontalDirection(Player p, InputSnapshot input) { + Vector look = p.getLocation().getDirection().setY(0); + if (look.lengthSquared() <= EPSILON) { + return new Vector(); + } + + look.normalize(); + Vector right = new Vector(-look.getZ(), 0, look.getX()); + Vector direction = new Vector(); + if (input.forward()) { + direction.add(look); + } + if (input.backward()) { + direction.subtract(look); + } + if (input.right()) { + direction.add(right); + } + if (input.left()) { + direction.subtract(right); + } + + if (direction.lengthSquared() <= EPSILON) { + return direction; + } + + return direction.normalize(); + } + + public static Vector horizontalOnly(Vector velocity) { + return new Vector(velocity.getX(), 0, velocity.getZ()); + } + + public static Vector moveTowards(Vector current, Vector target, double maxDelta) { + if (maxDelta <= 0) { + return current.clone(); + } + + Vector delta = target.clone().subtract(current); + double distance = delta.length(); + if (distance <= EPSILON || distance <= maxDelta) { + return target.clone(); + } + + return current.clone().add(delta.multiply(maxDelta / distance)); + } + + public static Vector clampHorizontal(Vector horizontal, double maxSpeed) { + double clamped = Math.max(0, maxSpeed); + if (clamped <= 0) { + return new Vector(); + } + + if (horizontal.lengthSquared() <= clamped * clamped) { + return horizontal; + } + + return horizontal.normalize().multiply(clamped); + } + + public static void setHorizontalVelocity(Player p, Vector horizontal) { + Vector velocity = p.getVelocity(); + p.setVelocity(new Vector(horizontal.getX(), velocity.getY(), horizontal.getZ())); + } + + public static void hardStopHorizontal(Player p) { + Vector velocity = p.getVelocity(); + p.setVelocity(new Vector(0, velocity.getY(), 0)); + } + + public static double speedAmplifierScalar(int amplifier) { + return 1.0 + (Math.max(0, amplifier) + 1) * 0.2; + } + + public record InputSnapshot(boolean forward, boolean backward, boolean left, + boolean right) { + public static final InputSnapshot NONE = new InputSnapshot(false, false, false, false); + + public boolean hasHorizontal() { + return forward || backward || left || right; + } + + public boolean isForwardOnly() { + return forward && !backward && !left && !right; + } + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/math/Writable.java b/src/main/java/art/arcane/adapt/util/common/math/Writable.java new file mode 100644 index 000000000..92dbd4f8b --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/math/Writable.java @@ -0,0 +1,29 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.math; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public interface Writable { + void write(DataOutputStream o) throws IOException; + + void read(DataInputStream i) throws IOException; +} diff --git a/src/main/java/art/arcane/adapt/util/common/misc/AdvancementUtils.java b/src/main/java/art/arcane/adapt/util/common/misc/AdvancementUtils.java new file mode 100644 index 000000000..56fe8013a --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/AdvancementUtils.java @@ -0,0 +1,85 @@ +package art.arcane.adapt.util.common.misc; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.advancement.AdaptAdvancementFrame; +import com.fren_gor.ultimateAdvancementAPI.UltimateAdvancementAPI; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.MinecraftKeyWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementDisplayWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementFrameTypeWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.packets.PacketPlayOutAdvancementsWrapper; +import com.google.common.base.Preconditions; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class AdvancementUtils { + + private static volatile boolean unavailable; + private static volatile boolean warned; + + /** + * Displays a custom toast to a player. + * + * @param player A player to show the toast. + * @param icon The displayed item of the toast. + * @param title The displayed title of the toast. + * @param frame The frame type of the toast. + * @see UltimateAdvancementAPI#displayCustomToast(Player, ItemStack, String, + * com.fren_gor.ultimateAdvancementAPI.advancement.display.AdvancementFrameType) + */ + public static void displayToast(@NotNull Player player, @NotNull ItemStack icon, @NotNull String title, @NotNull String description, @NotNull AdaptAdvancementFrame frame) { + if (unavailable) { + return; + } + + Preconditions.checkNotNull(player, "Player is null."); + Preconditions.checkNotNull(icon, "Icon is null."); + Preconditions.checkNotNull(title, "Title is null."); + Preconditions.checkNotNull(frame, "AdvancementFrameType is null."); + Preconditions.checkArgument(icon.getType() != Material.AIR, "ItemStack is air."); + + try { + MinecraftKeyWrapper rootKey = MinecraftKeyWrapper.craft("com.fren_gor", "root"); + MinecraftKeyWrapper notificationKey = MinecraftKeyWrapper.craft("com.fren_gor", "notification"); + AdvancementDisplayWrapper rootDisplay = AdvancementDisplayWrapper.craft( + new ItemStack(Material.GRASS_BLOCK), + "§f§lNotifications§1§2§3§4§5§6§7§8§9§0", + "§7Notification page.\n§7Close and reopen advancements to hide.", + AdvancementFrameTypeWrapper.TASK, + 0, + 0, + "textures/block/stone.png" + ); + AdvancementWrapper root = AdvancementWrapper.craftRootAdvancement(rootKey, rootDisplay, 1); + AdvancementDisplayWrapper display = AdvancementDisplayWrapper.craft(icon, title, description, frame.toUaaFrame().getNMSWrapper(), 1, 0, true, false, false); + AdvancementWrapper notification = AdvancementWrapper.craftBaseAdvancement(notificationKey, root, display, 1); + PacketPlayOutAdvancementsWrapper.craftSendPacket(Map.of( + root, 1, + notification, 1 + )).sendTo(player); + PacketPlayOutAdvancementsWrapper.craftRemovePacket(Set.of(rootKey, notificationKey)).sendTo(player); + } catch (Throwable e) { + unavailable = true; + warnOnce(e); + } + } + + private static void warnOnce(Throwable throwable) { + if (warned) { + return; + } + + warned = true; + Throwable root = throwable; + while (root.getCause() != null && root.getCause() != root) { + root = root.getCause(); + } + Adapt.warn("Advancement notifications are unavailable: " + Objects.toString(root.getMessage(), root.getClass().getSimpleName())); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/misc/Area.java b/src/main/java/art/arcane/adapt/util/common/misc/Area.java new file mode 100644 index 000000000..5d516acd9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/Area.java @@ -0,0 +1,255 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.misc; + +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.data.Cuboid; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; + +/** + * Used to Create an instance of a spherical area based on a central location + * Great for efficiently checking if an entity is within a spherical area. + * + * @author cyberpwn + */ +public class Area { + private Location location; + private Double radius; + + /** + * Used to instantiate a new "area" in which you can check if entities are + * within this area. + * + * @param location The center location of the area + * @param radius The radius used as a double. + */ + public Area(Location location, Double radius) { + this.location = location; + this.radius = radius; + } + + /** + * Used to instantiate a new "area" in which you can check if entities are + * within this area. + * + * @param location The center location of the area + * @param radius The radius used as an int. + */ + public Area(Location location, Integer radius) { + this.location = location; + this.radius = (double) radius; + } + + public static boolean within(Location center, Location target, double rad) { + return new Area(center, rad).isWithin(target); + } + + public Cuboid toCuboid() { + return new Cuboid(location.clone().add(radius, radius, radius), location.clone().subtract(radius, radius, radius)); + } + + /** + * Calculate the ESTIMATED distance from the center of this + * area, to the given location WARNING: This uses newton's method, be + * careful on how accurate you need this. As it is meant for FAST calculations + * with minimal load. + * + * @param location The given location to calculate a distance from the + * center. + * @return Returns the distance of location from the center. + */ + public Double distance(Location location) { + double c = this.location.distanceSquared(location); + double t = c; + + for (int i = 0; i < 3; i++) { + t = (c / t + t) / 2.0; + } + + return t; + } + + /** + * Calculate the EXACT distance from the center of this area, + * to the given location WARNING: This uses the sqrt function, be + * careful on how heavy you call this. + * + * @param location The given location to calculate a distance from the + * center. + * @return Returns the distance of location from the center. + */ + public Double slowDistance(Location location) { + return this.location.distance(location); + } + + /** + * Check to see weather a location is within the area + * + * @param location The location to measure from the center. + * @return Returns True if within; False if not. + */ + public boolean isWithin(Location location) { + return this.location.distance(location) <= (radius * radius); + } + + /** + * But does it have any entities? + */ + public boolean hasEntities() { + return getNearbyEntities().length > 0; + } + + /** + * Get all nearby entities matching the given entity type + * + * @param type the entity type + * @return the nearby entities matching the given type + */ + public Entity[] getNearbyEntities(EntityType type) { + KList e = new KList<>(getNearbyEntities()); + + for (Entity i : e.copy()) { + if (!i.getType().equals(type)) { + e.remove(i); + } + } + + return e.toArray(new Entity[e.size()]); + } + + /** + * Get nearby entities which match the following class + * + * @param entityClass the entity class + * @return the nearby entities assignable from the given class + */ + public Entity[] getNearbyEntities(Class entityClass) { + KList e = new KList<>(getNearbyEntities()); + + for (Entity i : e.copy()) { + if (!i.getClass().isAssignableFrom(entityClass)) { + e.remove(i); + } + } + + return e.toArray(new Entity[0]); + } + + /** + * Get ALL entities within the area. NOTE: This is EVERY entity, not + * just LivingEntities. Drops, Particles, Mobs, Players, Everything + * + * @return Returns an Entity[] array of all entities within the given area. + */ + public Entity[] getNearbyEntities() { + try { + int chunkRadius = (int) (radius < 16 ? 1 : (radius - (radius % 16)) / 16); + HashSet radiusEntities = new HashSet(); + + for (int chX = 0 - chunkRadius; chX <= chunkRadius; chX++) { + for (int chZ = 0 - chunkRadius; chZ <= chunkRadius; chZ++) { + int x = (int) location.getX(), y = (int) location.getY(), z = (int) location.getZ(); + + for (Entity e : new Location(location.getWorld(), x + (chX * 16), y, z + (chZ * 16)).getChunk().getEntities()) { + if (e.getLocation().distanceSquared(location) <= radius * radius && e.getLocation().getBlock() != location.getBlock()) { + radiusEntities.add(e); + } + } + } + } + + return radiusEntities.toArray(new Entity[radiusEntities.size()]); + } catch (Exception e) { + return new ArrayList().toArray(new Entity[0]); + } + } + + /** + * Get all players within the area. + * + * @return Returns an Player[] array of all players within the given area. + */ + public Player[] getNearbyPlayers() { + List px = new ArrayList<>(); + + for (Entity i : getNearbyEntities()) { + if (i.getType().equals(EntityType.PLAYER)) { + px.add((Player) i); + } + } + + return px.toArray(new Player[0]); + } + + /** + * Get the defined center location + * + * @return Returns the center location of the area + */ + public Location getLocation() { + return location; + } + + /** + * Set the defined center location + * + * @param location The new location to be set + */ + public void setLocation(Location location) { + this.location = location; + } + + /** + * Gets the area's radius + * + * @return Returns the area's radius + */ + public Double getRadius() { + return radius; + } + + /** + * Set the area's radius + * + * @param radius The new radius to be set + */ + public void setRadius(Double radius) { + this.radius = radius; + } + + /** + * Pick a random location in this radius + */ + public Location random() { + Random r = new Random(); + double x = radius * ((r.nextDouble() - 0.5) * 2); + double y = radius * ((r.nextDouble() - 0.5) * 2); + double z = radius * ((r.nextDouble() - 0.5) * 2); + + return location.clone().add(x, y, z); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/misc/Chunker.java b/src/main/java/art/arcane/adapt/util/common/misc/Chunker.java new file mode 100644 index 000000000..e3939d8d8 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/Chunker.java @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.misc; + +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.scheduling.Callback; +import art.arcane.volmlib.util.scheduling.ChronoLatch; +import art.arcane.volmlib.util.scheduling.Contained; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class Chunker { + private final List q; + private ExecutorService executor; + private int threads; + private int workload; + + public Chunker(List q) { + this.q = q; + } + + public Chunker threads(int threads) { + this.threads = threads; + return this; + } + + public Chunker workload(int workload) { + this.workload = workload; + return this; + } + + public void execute(Consumer consumer, Callback progress, int progressInterval) { + ChronoLatch cl = new ChronoLatch(progressInterval); + Contained consumed = new Contained(0); + executor = Executors.newFixedThreadPool(threads); + int length = q.size(); + int remaining = length; + + while (remaining > 0) { + int at = remaining; + remaining -= (remaining > workload ? workload : remaining); + int to = remaining; + + executor.submit(() -> + { + J.dofor(at, (i) -> i >= to, -1, (i) -> J.attempt(() -> consumer.accept(q.get(i)))); + consumed.mod((c) -> c += workload); + J.doif(() -> progress != null && cl.flip(), () -> progress.run((double) consumed.get() / (double) length)); + }); + } + + executor.shutdown(); + J.attempt(() -> executor.awaitTermination(100, TimeUnit.HOURS)); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/misc/CustomModel.java b/src/main/java/art/arcane/adapt/util/common/misc/CustomModel.java new file mode 100644 index 000000000..b653a6403 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/CustomModel.java @@ -0,0 +1,233 @@ +package art.arcane.adapt.util.common.misc; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.version.Version; +import art.arcane.adapt.util.common.io.Json; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.config.ConfigFileSupport; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.io.IO; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + +import static art.arcane.adapt.Adapt.instance; + +public record CustomModel(Material material, int model, + NamespacedKey modelKey) { + public static final NamespacedKey EMPTY_KEY = NamespacedKey.minecraft("empty"); + private static volatile UpdateChecker updateChecker = null; + + private static UpdateChecker checker() { + UpdateChecker current = updateChecker; + if (current == null) { + synchronized (CustomModel.class) { + current = updateChecker; + if (current == null) { + current = new UpdateChecker(); + updateChecker = current; + } + } + } + + return current; + } + + public static CustomModel get(Material fallback, String... path) { + if (!AdaptConfig.get().isCustomModels()) + return new CustomModel(fallback, 0, null); + + return checker().get(fallback, path); + } + + public static void clear() { + updateChecker = null; + } + + public static boolean reloadFromDisk() { + return reloadFromDisk(false); + } + + public static boolean reloadFromDisk(boolean quiet) { + return checker().reloadFromDisk(quiet); + } + + public ItemStack toItemStack() { + return toItemStack(new ItemStack(material)); + } + + public ItemStack toItemStack(ItemStack itemStack) { + org.bukkit.inventory.meta.ItemMeta meta = itemStack.getItemMeta(); + if (meta == null || model == 0) + return itemStack; + + Version.get().applyModel(this, meta); + itemStack.setItemMeta(meta); + return itemStack; + } + + private static class UpdateChecker { + private final Object lock = new Object(); + private final AtomicBoolean writeQueued = new AtomicBoolean(false); + private final File modelsFile; + private final File legacyModelsFile; + private final KMap cache = new KMap<>(); + private JsonObject json = new JsonObject(); + + public UpdateChecker() { + modelsFile = instance.getDataFile("adapt", "models.toml"); + legacyModelsFile = instance.getDataFile("adapt", "models.json"); + + try { + readFile(); + } catch (IOException e) { + Adapt.error("Failed to read models.toml"); + e.printStackTrace(); + } + } + + public boolean reloadFromDisk(boolean quiet) { + synchronized (lock) { + try { + readFile(); + cache.clear(); + return true; + } catch (IOException e) { + if (!quiet) { + Adapt.error("Failed to read models.toml"); + e.printStackTrace(); + } + return false; + } + } + } + + public CustomModel get(Material fallback, String... path) { + String key = String.join("", path); + CustomModel cached = cache.get(key); + if (cached != null) { + return cached; + } + + CustomModel resolved = resolve(fallback, path); + CustomModel raced = cache.putIfAbsent(key, resolved); + return raced != null ? raced : resolved; + } + + private CustomModel resolve(Material fallback, String... path) { + synchronized (lock) { + JsonObject node = this.json; + for (String s : path) { + if (!node.has(s)) { + return set(new CustomModel(fallback, 0, EMPTY_KEY), path); + } + + JsonElement v = node.get(s); + if (!v.isJsonObject()) { + Adapt.warn("Invalid json at path: " + String.join(".", path)); + return new CustomModel(fallback, 0, EMPTY_KEY); + } + node = v.getAsJsonObject(); + } + + return new CustomModel( + node.has("material") ? Material.valueOf(node.get("material").getAsString()) : fallback, + node.has("model") ? node.get("model").getAsInt() : 0, + node.has("modelKey") ? NamespacedKey.fromString(node.get("modelKey").getAsString()) : EMPTY_KEY + ); + } + } + + public CustomModel set(CustomModel data, String... path) { + synchronized (lock) { + JsonObject node = this.json; + for (String s : path) { + if (!node.has(s)) + node.add(s, new JsonObject()); + + JsonElement v = node.get(s); + if (!v.isJsonObject()) { + v = new JsonObject(); + node.add(s, v); + } + node = v.getAsJsonObject(); + } + + node.addProperty("material", data.material.name()); + node.addProperty("model", data.model); + node.addProperty("modelKey", (data.modelKey == null ? EMPTY_KEY : data.modelKey).toString()); + } + + scheduleWrite(); + return data; + } + + private void scheduleWrite() { + if (!writeQueued.compareAndSet(false, true)) { + return; + } + + J.a(() -> { + writeQueued.set(false); + try { + writeFile(); + } catch (IOException e) { + Adapt.error("Failed to write models.toml"); + e.printStackTrace(); + } + }); + } + + public void readFile() throws IOException { + synchronized (lock) { + if (modelsFile.exists()) { + String raw = IO.readAll(modelsFile); + if (raw == null || raw.isBlank()) { + json = new JsonObject(); + ConfigFileSupport.deleteLegacyFileIfMigrated(modelsFile, legacyModelsFile, "models-config"); + return; + } + + JsonElement parsed = ConfigFileSupport.parseToJsonElement(raw, modelsFile); + if (parsed == null || !parsed.isJsonObject()) { + throw new IOException("Invalid models.toml"); + } + + json = parsed.getAsJsonObject(); + ConfigFileSupport.deleteLegacyFileIfMigrated(modelsFile, legacyModelsFile, "models-config"); + return; + } + + if (legacyModelsFile.exists()) { + String legacyRaw = IO.readAll(legacyModelsFile); + JsonObject legacy = Json.fromJson(legacyRaw, JsonObject.class); + if (legacy == null) { + throw new IOException("Invalid models.json"); + } + json = legacy; + IO.writeAll(modelsFile, ConfigFileSupport.serializeJsonElementToToml(legacy)); + Adapt.info("Migrated legacy config [adapt/models.json] -> [adapt/models.toml]."); + ConfigFileSupport.deleteLegacyFileIfMigrated(modelsFile, legacyModelsFile, "models-config"); + return; + } + + json = new JsonObject(); + IO.writeAll(modelsFile, ConfigFileSupport.serializeJsonElementToToml(json)); + ConfigFileSupport.recordMissingConfigCreated(); + } + } + + public void writeFile() throws IOException { + synchronized (lock) { + IO.writeAll(modelsFile, ConfigFileSupport.serializeJsonElementToToml(json)); + } + } + } +} diff --git a/src/main/java/com/volmit/adapt/util/DependsOn.java b/src/main/java/art/arcane/adapt/util/common/misc/DependsOn.java similarity index 95% rename from src/main/java/com/volmit/adapt/util/DependsOn.java rename to src/main/java/art/arcane/adapt/util/common/misc/DependsOn.java index 1843e0be0..6d1250d4a 100644 --- a/src/main/java/com/volmit/adapt/util/DependsOn.java +++ b/src/main/java/art/arcane/adapt/util/common/misc/DependsOn.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.misc; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -27,5 +27,5 @@ @Retention(RUNTIME) @Target({FIELD}) public @interface DependsOn { - String[] value(); + String[] value(); } diff --git a/src/main/java/art/arcane/adapt/util/common/misc/DirtyString.java b/src/main/java/art/arcane/adapt/util/common/misc/DirtyString.java new file mode 100644 index 000000000..902d46dda --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/DirtyString.java @@ -0,0 +1,51 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.misc; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.format.HiddenStringUtils; +import art.arcane.adapt.util.common.io.Json; +import org.bukkit.ChatColor; + +public class DirtyString { + + public static String write(Object data) { + return write(Json.toJson(data, false)); + } + + public static T fromJson(String data, Class t) { + return Json.fromJson(read(data), t); + } + + public static boolean has(String data) { + if (!HiddenStringUtils.hasHiddenString(data)) { + Adapt.info("Not has in " + data.replaceAll("\\Q" + ChatColor.COLOR_CHAR + "\\E", "&")); + } + return HiddenStringUtils.hasHiddenString(data); + } + + public static String write(String data) { + return HiddenStringUtils.encodeString(data); + } + + public static String read(String data) { + return HiddenStringUtils.extractHiddenString(data); + } +} + diff --git a/src/main/java/com/volmit/adapt/util/DontObfuscate.java b/src/main/java/art/arcane/adapt/util/common/misc/DontObfuscate.java similarity index 96% rename from src/main/java/com/volmit/adapt/util/DontObfuscate.java rename to src/main/java/art/arcane/adapt/util/common/misc/DontObfuscate.java index 6f5305119..0a6150ca9 100644 --- a/src/main/java/com/volmit/adapt/util/DontObfuscate.java +++ b/src/main/java/art/arcane/adapt/util/common/misc/DontObfuscate.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.misc; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/src/main/java/art/arcane/adapt/util/common/misc/Impulse.java b/src/main/java/art/arcane/adapt/util/common/misc/Impulse.java new file mode 100644 index 000000000..bc0780f1e --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/Impulse.java @@ -0,0 +1,121 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.misc; + +import art.arcane.adapt.util.common.math.VectorMath; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +public class Impulse { + private final List ignore; + private double radius; + private double forceMax; + private double forceMin; + private double damageMin; + private double damageMax; + private Predicate filter; + + public Impulse(double radius) { + ignore = new ArrayList<>(); + this.radius = radius; + this.forceMax = 1; + this.forceMin = 0; + this.damageMax = 1; + this.damageMin = 0; + } + + public Impulse radius(double radius) { + this.radius = radius; + return this; + } + + public Impulse force(double force) { + this.forceMax = force; + return this; + } + + public Impulse force(double forceMax, double forceMin) { + this.forceMax = forceMax; + this.forceMin = forceMin; + return this; + } + + public Impulse damage(double damage) { + this.damageMax = damage; + return this; + } + + public Impulse damage(double damageMax, double damageMin) { + this.damageMax = damageMax; + this.damageMin = damageMin; + return this; + } + + public Impulse filter(Predicate filter) { + this.filter = filter; + return this; + } + + public void punch(Location at) { + Area a = new Area(at, radius); + + for (Entity i : a.getNearbyEntities()) { + if (ignore.contains(i)) { + continue; + } + + if (filter != null && !filter.test(i)) { + continue; + } + + Vector force = VectorMath.direction(at, i.getLocation()); + double damage = 0; + double distance = i.getLocation().distance(at); + + if (forceMin < forceMax) { + force.clone().multiply(((1D - (distance / radius)) * (forceMax - forceMin)) + forceMin); + } + + if (damageMin < damageMax) { + damage = ((1D - (distance / radius)) * (damageMax - damageMin)) + damageMin; + } + + try { + if (i instanceof LivingEntity && damage > 0) { + ((LivingEntity) i).damage(damage); + } + + i.setVelocity(i.getVelocity().add(force)); + } catch (Exception e) { + + } + } + } + + public Impulse ignore(Entity player) { + ignore.add(player); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/Info.java b/src/main/java/art/arcane/adapt/util/common/misc/Info.java similarity index 88% rename from src/main/java/com/volmit/adapt/util/Info.java rename to src/main/java/art/arcane/adapt/util/common/misc/Info.java index b63f1a57e..7c3e5e862 100644 --- a/src/main/java/com/volmit/adapt/util/Info.java +++ b/src/main/java/art/arcane/adapt/util/common/misc/Info.java @@ -16,12 +16,12 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.misc; import org.bukkit.Bukkit; public class Info { - public static String getPortIP() { - return Bukkit.getPort() + Bukkit.getIp(); - } + public static String getPortIP() { + return Bukkit.getPort() + Bukkit.getIp(); + } } diff --git a/src/main/java/com/volmit/adapt/util/MaxNumber.java b/src/main/java/art/arcane/adapt/util/common/misc/MaxNumber.java similarity index 95% rename from src/main/java/com/volmit/adapt/util/MaxNumber.java rename to src/main/java/art/arcane/adapt/util/common/misc/MaxNumber.java index 229056aec..63deed4a8 100644 --- a/src/main/java/com/volmit/adapt/util/MaxNumber.java +++ b/src/main/java/art/arcane/adapt/util/common/misc/MaxNumber.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.misc; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -27,5 +27,5 @@ @Retention(RUNTIME) @Target({FIELD}) public @interface MaxNumber { - double value(); + double value(); } diff --git a/src/main/java/com/volmit/adapt/util/MinNumber.java b/src/main/java/art/arcane/adapt/util/common/misc/MinNumber.java similarity index 95% rename from src/main/java/com/volmit/adapt/util/MinNumber.java rename to src/main/java/art/arcane/adapt/util/common/misc/MinNumber.java index 67259120a..6a116651b 100644 --- a/src/main/java/com/volmit/adapt/util/MinNumber.java +++ b/src/main/java/art/arcane/adapt/util/common/misc/MinNumber.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.misc; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -27,5 +27,5 @@ @Retention(RUNTIME) @Target({FIELD}) public @interface MinNumber { - double value(); + double value(); } diff --git a/src/main/java/art/arcane/adapt/util/common/misc/NMSVersion.java b/src/main/java/art/arcane/adapt/util/common/misc/NMSVersion.java new file mode 100644 index 000000000..d9f40017f --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/NMSVersion.java @@ -0,0 +1,138 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.misc; + +import java.util.ArrayList; +import java.util.List; + +public enum NMSVersion { + R1_16, + R1_15, + R1_14, + R1_13, + R1_13_1, + R1_12, + R1_11, + R1_10, + R1_9_4, + R1_9_2, + R1_8; + + public static NMSVersion getMinimum() { + return values()[values().length - 1]; + } + + public static NMSVersion getMaximum() { + return values()[0]; + } + + public static NMSVersion current() { + if (tryVersion("1_8_R3")) { + return R1_8; + } + + if (tryVersion("1_9_R1")) { + return R1_9_2; + } + + if (tryVersion("1_9_R2")) { + return R1_9_4; + } + + if (tryVersion("1_10_R1")) { + return R1_10; + } + + if (tryVersion("1_11_R1")) { + return R1_11; + } + + if (tryVersion("1_12_R1")) { + return R1_12; + } + + if (tryVersion("1_13_R1")) { + return R1_13; + } + + if (tryVersion("1_13_R2")) { + return R1_13_1; + } + + if (tryVersion("1_14_R1")) { + return R1_14; + } + + if (tryVersion("1_15_R1")) { + return R1_15; + } + + if (tryVersion("1_16_R1")) { + return R1_16; + } + return null; + } + + private static boolean tryVersion(String v) { + try { + Class.forName("org.bukkit.craftbukkit.v" + v + ".CraftWorld"); + return true; + } catch (Throwable e) { + + } + + return false; + } + + public List getAboveInclusive() { + List n = new ArrayList<>(); + + for (NMSVersion i : values()) { + if (i.ordinal() >= ordinal()) { + n.add(i); + } + } + + return n; + } + + public List betweenInclusive(NMSVersion other) { + List n = new ArrayList<>(); + + for (NMSVersion i : values()) { + if (i.ordinal() <= Math.max(other.ordinal(), ordinal()) && i.ordinal() >= Math.min(ordinal(), other.ordinal())) { + n.add(i); + } + } + + return n; + } + + public List getBelowInclusive() { + List n = new ArrayList<>(); + + for (NMSVersion i : values()) { + if (i.ordinal() <= ordinal()) { + n.add(i); + } + } + + return n; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/misc/Shrinkwrap.java b/src/main/java/art/arcane/adapt/util/common/misc/Shrinkwrap.java new file mode 100644 index 000000000..40e70a622 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/Shrinkwrap.java @@ -0,0 +1,39 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.misc; + +public class Shrinkwrap { + private T t; + + public Shrinkwrap(T t) { + set(t); + } + + public Shrinkwrap() { + this(null); + } + + public T get() { + return t; + } + + public void set(T t) { + this.t = t; + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/misc/SoundPlayer.java b/src/main/java/art/arcane/adapt/util/common/misc/SoundPlayer.java new file mode 100644 index 000000000..ccd36272c --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/misc/SoundPlayer.java @@ -0,0 +1,63 @@ +package art.arcane.adapt.util.common.misc; + +import art.arcane.adapt.AdaptConfig; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class SoundPlayer { + + private final Collection players; + + public static SoundPlayer of(Collection players) { + return new SoundPlayer(players); + } + + public static SoundPlayer of(Player player) { + return new SoundPlayer(List.of(player)); + } + + public static SoundPlayer of(World world) { + return new SoundPlayer(world.getPlayers()); + } + + public void play(@NotNull Location location, @NotNull Sound sound) { + play(location, sound, 1.0f, 1.0f); + } + + public void play(@NotNull Entity entity, @NotNull Sound sound, float volume, float pitch) { + play(entity.getLocation(), sound, volume, pitch); + } + + public void play(@NotNull Location location, @NotNull Sound sound, float volume, float pitch) { + if (!areSoundsEnabled()) { + return; + } + players.forEach(player -> player.playSound(location, sound, volume, pitch)); + //J.s(() -> Objects.requireNonNull(location.getWorld()).playSound(location, sound, volume, pitch)); + } + + public void play(@NotNull Location location, @NotNull Sound sound, SoundCategory category, float volume, float pitch) { + if (!areSoundsEnabled()) { + return; + } + players.forEach(player -> player.playSound(location, sound, category, volume, pitch)); + //J.s(() -> Objects.requireNonNull(location.getWorld()).playSound(location, sound, volume, pitch)); + } + + private boolean areSoundsEnabled() { + AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); + return effects == null || effects.isSoundsEnabled(); + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/ByteArrayTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/ByteArrayTag.java new file mode 100644 index 000000000..e629c40c2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/ByteArrayTag.java @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * The TAG_Byte_Array tag. + * + * @author Graham Edgecombe + */ +public final class ByteArrayTag extends Tag { + + /** + * The value. + */ + private final byte[] value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public ByteArrayTag(String name, byte[] value) { + super(name); + this.value = value; + } + + @Override + public byte[] getValue() { + return value; + } + + @Override + public String toString() { + StringBuilder hex = new StringBuilder(); + for (byte b : value) { + String hexDigits = Integer.toHexString(b).toUpperCase(); + if (hexDigits.length() == 1) { + hex.append("0"); + } + hex.append(hexDigits).append(" "); + } + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Byte_Array" + append + ": " + hex; + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/ByteTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/ByteTag.java new file mode 100644 index 000000000..1f63051bd --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/ByteTag.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * The TAG_Byte tag. + * + * @author Graham Edgecombe + */ +public final class ByteTag extends Tag { + + /** + * The value. + */ + private final byte value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public ByteTag(String name, byte value) { + super(name); + this.value = value; + } + + @Override + public Byte getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Byte" + append + ": " + value; + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/CompoundTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/CompoundTag.java new file mode 100644 index 000000000..b4d0bd1e9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/CompoundTag.java @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +import java.util.Map; + +/** + * The TAG_Compound tag. + * + * @author Graham Edgecombe + */ +public final class CompoundTag extends Tag { + + /** + * The value. + */ + private final Map value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public CompoundTag(String name, Map value) { + super(name); + this.value = value; + } + + @Override + public Map getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + StringBuilder bldr = new StringBuilder(); + bldr.append("TAG_Compound" + append + ": " + value.size() + " entries\r\n{\r\n"); + for (Map.Entry entry : value.entrySet()) { + bldr.append(" " + entry.getValue().toString().replaceAll("\r\n", "\r\n ") + "\r\n"); + } + bldr.append("}"); + return bldr.toString(); + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/DoubleTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/DoubleTag.java new file mode 100644 index 000000000..689891a13 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/DoubleTag.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * The TAG_Double tag. + * + * @author Graham Edgecombe + */ +public final class DoubleTag extends Tag { + + /** + * The value. + */ + private final double value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public DoubleTag(String name, double value) { + super(name); + this.value = value; + } + + @Override + public Double getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Double" + append + ": " + value; + } + +} diff --git a/src/main/java/com/volmit/adapt/util/EndTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/EndTag.java similarity index 80% rename from src/main/java/com/volmit/adapt/util/EndTag.java rename to src/main/java/art/arcane/adapt/util/common/nbt/EndTag.java index 6117cf184..62e498ec3 100644 --- a/src/main/java/com/volmit/adapt/util/EndTag.java +++ b/src/main/java/art/arcane/adapt/util/common/nbt/EndTag.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.nbt; /** * The TAG_End tag. @@ -25,21 +25,21 @@ */ public final class EndTag extends Tag { - /** - * Creates the tag. - */ - public EndTag() { - super(""); - } + /** + * Creates the tag. + */ + public EndTag() { + super(""); + } - @Override - public Object getValue() { - return null; - } + @Override + public Object getValue() { + return null; + } - @Override - public String toString() { - return "TAG_End"; - } + @Override + public String toString() { + return "TAG_End"; + } } diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/FloatTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/FloatTag.java new file mode 100644 index 000000000..62dae6cd1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/FloatTag.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * The TAG_Float tag. + * + * @author Graham Edgecombe + */ +public final class FloatTag extends Tag { + + /** + * The value. + */ + private final float value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public FloatTag(String name, float value) { + super(name); + this.value = value; + } + + @Override + public Float getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Float" + append + ": " + value; + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/IntArrayTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/IntArrayTag.java new file mode 100644 index 000000000..681ddbefc --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/IntArrayTag.java @@ -0,0 +1,61 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +import java.util.Arrays; + +/** + * The TAG_Int_Array tag. + * + * @author Neil Wightman + */ +public final class IntArrayTag extends Tag { + + /** + * The value. + */ + private final int[] value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public IntArrayTag(String name, int[] value) { + super(name); + this.value = value; + } + + @Override + public int[] getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Int_Array" + append + ": " + Arrays.toString(value); + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/IntTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/IntTag.java new file mode 100644 index 000000000..4d4539a74 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/IntTag.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * The TAG_Int tag. + * + * @author Graham Edgecombe + */ +public final class IntTag extends Tag { + + /** + * The value. + */ + private final int value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public IntTag(String name, int value) { + super(name); + this.value = value; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Int" + append + ": " + value; + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/ListTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/ListTag.java new file mode 100644 index 000000000..8d5aff06d --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/ListTag.java @@ -0,0 +1,84 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +import java.util.Collections; +import java.util.List; + +/** + * The TAG_List tag. + * + * @author Graham Edgecombe + */ +public final class ListTag extends Tag { + + /** + * The type. + */ + private final Class type; + + /** + * The value. + */ + private final List value; + + /** + * Creates the tag. + * + * @param name The name. + * @param type The type of item in the list. + * @param value The value. + */ + public ListTag(String name, Class type, List value) { + super(name); + this.type = type; + this.value = Collections.unmodifiableList(value); + } + + /** + * Gets the type of item in this list. + * + * @return The type of item in this list. + */ + public Class getType() { + return type; + } + + @Override + public List getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + StringBuilder bldr = new StringBuilder(); + bldr.append("TAG_List" + append + ": " + value.size() + " entries of type " + NBTUtils.getTypeName(type) + "\r\n{\r\n"); + for (Tag t : value) { + bldr.append(" " + t.toString().replaceAll("\r\n", "\r\n ") + "\r\n"); + } + bldr.append("}"); + return bldr.toString(); + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/LongTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/LongTag.java new file mode 100644 index 000000000..dc17c2a43 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/LongTag.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * The TAG_Long tag. + * + * @author Graham Edgecombe + */ +public final class LongTag extends Tag { + + /** + * The value. + */ + private final long value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public LongTag(String name, long value) { + super(name); + this.value = value; + } + + @Override + public Long getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Long" + append + ": " + value; + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/NBTConstants.java b/src/main/java/art/arcane/adapt/util/common/nbt/NBTConstants.java new file mode 100644 index 000000000..7d2526586 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/NBTConstants.java @@ -0,0 +1,63 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * Changes : Neil Wightman - Support 19133 Tag_Int_Array tag + */ + +/** + * A class which holds constant values. + * + * @author Graham Edgecombe + */ +public final class NBTConstants { + + /** + * The character set used by NBT (UTF-8). + */ + public static final Charset CHARSET = StandardCharsets.UTF_8; + + /** + * Tag type constants. + */ + public static final int TYPE_END = 0, + TYPE_BYTE = 1, + TYPE_SHORT = 2, + TYPE_INT = 3, + TYPE_LONG = 4, + TYPE_FLOAT = 5, + TYPE_DOUBLE = 6, + TYPE_BYTE_ARRAY = 7, + TYPE_STRING = 8, + TYPE_LIST = 9, + TYPE_COMPOUND = 10, + TYPE_INT_ARRAY = 11; + + /** + * Default private constructor. + */ + private NBTConstants() { + + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/NBTInputStream.java b/src/main/java/art/arcane/adapt/util/common/nbt/NBTInputStream.java new file mode 100644 index 000000000..d79d63408 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/NBTInputStream.java @@ -0,0 +1,190 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +import java.io.Closeable; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +/** + * Changes : Neil Wightman - Support 19133 Tag_Int_Array tag + */ + +/** + *

+ * This class reads NBT, or + * Named Binary Tag streams, and produces an object graph of subclasses of the Tag + * object.

+ * + *

+ * The NBT format was created by Markus Persson, and the specification may be found at + * http://www.minecraft.net/docs/NBT.txt.

+ * + * @author Graham Edgecombe + */ +public final class NBTInputStream implements Closeable { + + /** + * The data input stream. + */ + private final DataInputStream is; + + /** + * Create a new NBTInputStream, which will source its data from the specified input stream. + * + * @param is The output stream + */ + public NBTInputStream(DataInputStream is) { + this.is = is; + } + + /** + * Creates a new NBTInputStream, which will source its data from the specified input stream. + * The stream will be decompressed using GZIP. + * + * @param is The input stream. + * @throws IOException if an I/O error occurs. + */ + public NBTInputStream(InputStream is) throws IOException { + this.is = new DataInputStream(new GZIPInputStream(is)); + } + + /** + * Reads an NBT tag from the stream. + * + * @return The tag that was read. + * @throws IOException if an I/O error occurs. + */ + public Tag readTag() throws IOException { + return readTag(0); + } + + /** + * Reads an NBT from the stream. + * + * @param depth The depth of this tag. + * @return The tag that was read. + * @throws IOException if an I/O error occurs. + */ + private Tag readTag(int depth) throws IOException { + int type = is.readByte() & 0xFF; + + String name; + if (type != NBTConstants.TYPE_END) { + int nameLength = is.readShort() & 0xFFFF; + byte[] nameBytes = new byte[nameLength]; + is.readFully(nameBytes); + name = new String(nameBytes, NBTConstants.CHARSET); + } else { + name = ""; + } + + return readTagPayload(type, name, depth); + } + + /** + * Reads the payload of a tag, given the name and type. + * + * @param type The type. + * @param name The name. + * @param depth The depth. + * @return The tag. + * @throws IOException if an I/O error occurs. + */ + private Tag readTagPayload(int type, String name, int depth) throws IOException { + switch (type) { + case NBTConstants.TYPE_END: + if (depth == 0) { + throw new IOException("TAG_End found without a TAG_Compound/TAG_List tag preceding it."); + } else { + return new EndTag(); + } + case NBTConstants.TYPE_BYTE: + return new ByteTag(name, is.readByte()); + case NBTConstants.TYPE_SHORT: + return new ShortTag(name, is.readShort()); + case NBTConstants.TYPE_INT: + return new IntTag(name, is.readInt()); + case NBTConstants.TYPE_LONG: + return new LongTag(name, is.readLong()); + case NBTConstants.TYPE_FLOAT: + return new FloatTag(name, is.readFloat()); + case NBTConstants.TYPE_DOUBLE: + return new DoubleTag(name, is.readDouble()); + case NBTConstants.TYPE_BYTE_ARRAY: + int length = is.readInt(); + byte[] bytes = new byte[length]; + is.readFully(bytes); + return new ByteArrayTag(name, bytes); + case NBTConstants.TYPE_STRING: + length = is.readShort(); + bytes = new byte[length]; + is.readFully(bytes); + return new StringTag(name, new String(bytes, NBTConstants.CHARSET)); + case NBTConstants.TYPE_LIST: + int childType = is.readByte(); + length = is.readInt(); + + List tagList = new ArrayList(); + for (int i = 0; i < length; i++) { + Tag tag = readTagPayload(childType, "", depth + 1); + if (tag instanceof EndTag) { + throw new IOException("TAG_End not permitted in a list."); + } + tagList.add(tag); + } + + return new ListTag(name, NBTUtils.getTypeClass(childType), tagList); + case NBTConstants.TYPE_COMPOUND: + Map tagMap = new HashMap(); + while (true) { + Tag tag = readTag(depth + 1); + if (tag instanceof EndTag) { + break; + } else { + tagMap.put(tag.getName(), tag); + } + } + + return new CompoundTag(name, tagMap); + case NBTConstants.TYPE_INT_ARRAY: + length = is.readInt(); + int[] value = new int[length]; + for (int i = 0; i < length; i++) { + value[i] = is.readInt(); + } + return new IntArrayTag(name, value); + default: + throw new IOException("Invalid tag type: " + type + "."); + } + } + + @Override + public void close() throws IOException { + is.close(); + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/NBTOutputStream.java b/src/main/java/art/arcane/adapt/util/common/nbt/NBTOutputStream.java new file mode 100644 index 000000000..9cd19e745 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/NBTOutputStream.java @@ -0,0 +1,290 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +import java.io.Closeable; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.zip.GZIPOutputStream; +/** + * Changes : Neil Wightman - Support 19133 Tag_Int_Array tag + */ + +/** + *

+ * This class writes NBT, or + * Named Binary Tag Tag objects to an underlying + * OutputStream.

+ * + *

+ * The NBT format was created by Markus Persson, and the specification may be + * found at + * http://www.minecraft.net/docs/NBT.txt.

+ * + * @author Graham Edgecombe + */ +public final class NBTOutputStream implements Closeable { + + /** + * The output stream. + */ + private final DataOutputStream os; + + /** + * Create a new NBTOutputStream, which will write data to the + * specified underlying output stream. + * + * @param os The output stream + */ + public NBTOutputStream(DataOutputStream os) { + this.os = os; + } + + /** + * Creates a new NBTOutputStream, which will write data to the + * specified underlying output stream. the stream will be compressed using + * GZIP. + * + * @param os The output stream. + * @throws IOException if an I/O error occurs. + */ + public NBTOutputStream(OutputStream os) throws IOException { + this.os = new DataOutputStream(new GZIPOutputStream(os)); + } + + /** + * Writes a tag. + * + * @param tag The tag to write. + * @throws IOException if an I/O error occurs. + */ + public void writeTag(Tag tag) throws IOException { + int type = NBTUtils.getTypeCode(tag.getClass()); + String name = tag.getName(); + byte[] nameBytes = name.getBytes(NBTConstants.CHARSET); + + os.writeByte(type); + os.writeShort(nameBytes.length); + os.write(nameBytes); + + if (type == NBTConstants.TYPE_END) { + throw new IOException("Named TAG_End not permitted."); + } + + writeTagPayload(tag); + } + + /** + * Writes tag payload. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeTagPayload(Tag tag) throws IOException { + int type = NBTUtils.getTypeCode(tag.getClass()); + switch (type) { + case NBTConstants.TYPE_END: + writeEndTagPayload((EndTag) tag); + break; + case NBTConstants.TYPE_BYTE: + writeByteTagPayload((ByteTag) tag); + break; + case NBTConstants.TYPE_SHORT: + writeShortTagPayload((ShortTag) tag); + break; + case NBTConstants.TYPE_INT: + writeIntTagPayload((IntTag) tag); + break; + case NBTConstants.TYPE_LONG: + writeLongTagPayload((LongTag) tag); + break; + case NBTConstants.TYPE_FLOAT: + writeFloatTagPayload((FloatTag) tag); + break; + case NBTConstants.TYPE_DOUBLE: + writeDoubleTagPayload((DoubleTag) tag); + break; + case NBTConstants.TYPE_BYTE_ARRAY: + writeByteArrayTagPayload((ByteArrayTag) tag); + break; + case NBTConstants.TYPE_STRING: + writeStringTagPayload((StringTag) tag); + break; + case NBTConstants.TYPE_LIST: + writeListTagPayload((ListTag) tag); + break; + case NBTConstants.TYPE_COMPOUND: + writeCompoundTagPayload((CompoundTag) tag); + break; + case NBTConstants.TYPE_INT_ARRAY: + writeIntArrayTagPayload((IntArrayTag) tag); + break; + default: + throw new IOException("Invalid tag type: " + type + "."); + } + } + + /** + * Writes a TAG_Byte tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeByteTagPayload(ByteTag tag) throws IOException { + os.writeByte(tag.getValue()); + } + + /** + * Writes a TAG_Byte_Array tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeByteArrayTagPayload(ByteArrayTag tag) throws IOException { + byte[] bytes = tag.getValue(); + os.writeInt(bytes.length); + os.write(bytes); + } + + + /** + * Writes a TAG_Compound tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeCompoundTagPayload(CompoundTag tag) throws IOException { + for (Tag childTag : tag.getValue().values()) { + writeTag(childTag); + } + os.writeByte((byte) 0); // end tag - better way? + } + + /** + * Writes a TAG_List tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeListTagPayload(ListTag tag) throws IOException { + Class clazz = tag.getType(); + List tags = tag.getValue(); + int size = tags.size(); + + os.writeByte(NBTUtils.getTypeCode(clazz)); + os.writeInt(size); + for (int i = 0; i < size; i++) { + writeTagPayload(tags.get(i)); + } + } + + /** + * Writes a TAG_String tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeStringTagPayload(StringTag tag) throws IOException { + byte[] bytes = tag.getValue().getBytes(NBTConstants.CHARSET); + os.writeShort(bytes.length); + os.write(bytes); + } + + /** + * Writes a TAG_Double tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeDoubleTagPayload(DoubleTag tag) throws IOException { + os.writeDouble(tag.getValue()); + } + + /** + * Writes a TAG_Float tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeFloatTagPayload(FloatTag tag) throws IOException { + os.writeFloat(tag.getValue()); + } + + /** + * Writes a TAG_Long tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeLongTagPayload(LongTag tag) throws IOException { + os.writeLong(tag.getValue()); + } + + /** + * Writes a TAG_Int tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeIntTagPayload(IntTag tag) throws IOException { + os.writeInt(tag.getValue()); + } + + /** + * Writes a TAG_Short tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeShortTagPayload(ShortTag tag) throws IOException { + os.writeShort(tag.getValue()); + } + + /** + * Writes a TAG_Empty tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeEndTagPayload(EndTag tag) { + /* empty */ + } + + /** + * Writes a TAG_Int_Array tag. + * + * @param tag The tag. + * @throws IOException if an I/O error occurs. + */ + private void writeIntArrayTagPayload(IntArrayTag tag) throws IOException { + final int[] values = tag.getValue(); + os.writeInt(values.length); + for (final int value : values) { + os.writeInt(value); + } + } + + @Override + public void close() throws IOException { + os.close(); + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/NBTUtils.java b/src/main/java/art/arcane/adapt/util/common/nbt/NBTUtils.java new file mode 100644 index 000000000..edb346b95 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/NBTUtils.java @@ -0,0 +1,151 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * Changes : Neil Wightman - Support 19133 Tag_Int_Array tag + */ + +/** + * A class which contains NBT-related utility methods. This currently supports + * reading 19133 but only writing 19132. + * + * @author Graham Edgecombe + */ +public final class NBTUtils { + + /** + * Default private constructor. + */ + private NBTUtils() { + + } + + /** + * Gets the type name of a tag. + * + * @param clazz The tag class. + * @return The type name. + */ + public static String getTypeName(Class clazz) { + if (clazz.equals(ByteArrayTag.class)) { + return "TAG_Byte_Array"; + } else if (clazz.equals(ByteTag.class)) { + return "TAG_Byte"; + } else if (clazz.equals(CompoundTag.class)) { + return "TAG_Compound"; + } else if (clazz.equals(DoubleTag.class)) { + return "TAG_Double"; + } else if (clazz.equals(EndTag.class)) { + return "TAG_End"; + } else if (clazz.equals(FloatTag.class)) { + return "TAG_Float"; + } else if (clazz.equals(IntTag.class)) { + return "TAG_Int"; + } else if (clazz.equals(ListTag.class)) { + return "TAG_List"; + } else if (clazz.equals(LongTag.class)) { + return "TAG_Long"; + } else if (clazz.equals(ShortTag.class)) { + return "TAG_Short"; + } else if (clazz.equals(StringTag.class)) { + return "TAG_String"; + } else if (clazz.equals(IntArrayTag.class)) { + return "TAG_Int_Array"; + } else { + throw new IllegalArgumentException("Invalid tag classs (" + clazz.getName() + ")."); + } + } + + /** + * Gets the type code of a tag class. + * + * @param clazz The tag class. + * @return The type code. + * @throws IllegalArgumentException if the tag class is invalid. + */ + public static int getTypeCode(Class clazz) { + if (clazz.equals(ByteArrayTag.class)) { + return NBTConstants.TYPE_BYTE_ARRAY; + } else if (clazz.equals(ByteTag.class)) { + return NBTConstants.TYPE_BYTE; + } else if (clazz.equals(CompoundTag.class)) { + return NBTConstants.TYPE_COMPOUND; + } else if (clazz.equals(DoubleTag.class)) { + return NBTConstants.TYPE_DOUBLE; + } else if (clazz.equals(EndTag.class)) { + return NBTConstants.TYPE_END; + } else if (clazz.equals(FloatTag.class)) { + return NBTConstants.TYPE_FLOAT; + } else if (clazz.equals(IntTag.class)) { + return NBTConstants.TYPE_INT; + } else if (clazz.equals(ListTag.class)) { + return NBTConstants.TYPE_LIST; + } else if (clazz.equals(LongTag.class)) { + return NBTConstants.TYPE_LONG; + } else if (clazz.equals(ShortTag.class)) { + return NBTConstants.TYPE_SHORT; + } else if (clazz.equals(StringTag.class)) { + return NBTConstants.TYPE_STRING; + } else if (clazz.equals(IntArrayTag.class)) { + return NBTConstants.TYPE_INT_ARRAY; + } else { + throw new IllegalArgumentException("Invalid tag classs (" + clazz.getName() + ")."); + } + } + + /** + * Gets the class of a type of tag. + * + * @param type The type. + * @return The class. + * @throws IllegalArgumentException if the tag type is invalid. + */ + public static Class getTypeClass(int type) { + switch (type) { + case NBTConstants.TYPE_END: + return EndTag.class; + case NBTConstants.TYPE_BYTE: + return ByteTag.class; + case NBTConstants.TYPE_SHORT: + return ShortTag.class; + case NBTConstants.TYPE_INT: + return IntTag.class; + case NBTConstants.TYPE_LONG: + return LongTag.class; + case NBTConstants.TYPE_FLOAT: + return FloatTag.class; + case NBTConstants.TYPE_DOUBLE: + return DoubleTag.class; + case NBTConstants.TYPE_BYTE_ARRAY: + return ByteArrayTag.class; + case NBTConstants.TYPE_STRING: + return StringTag.class; + case NBTConstants.TYPE_LIST: + return ListTag.class; + case NBTConstants.TYPE_COMPOUND: + return CompoundTag.class; + case NBTConstants.TYPE_INT_ARRAY: + return IntArrayTag.class; + default: + throw new IllegalArgumentException("Invalid tag type : " + type + "."); + } + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/NibbleArray.java b/src/main/java/art/arcane/adapt/util/common/nbt/NibbleArray.java new file mode 100644 index 000000000..14579c303 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/NibbleArray.java @@ -0,0 +1,197 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +import art.arcane.adapt.util.common.math.Writable; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.StringJoiner; + +public class NibbleArray implements Writable { + private static final int[] MASKS = new int[8]; + + static { + for (int i = 0; i < MASKS.length; i++) { + MASKS[i] = maskFor(i); + } + } + + private final int size; + private final Object lock = new Object(); + private byte[] data; + private int depth; + private byte mask; + private transient int bitIndex, byteIndex, bitInByte; + + public NibbleArray(int capacity, DataInputStream in) throws IOException { + size = capacity; + read(in); + } + + public NibbleArray(int nibbleDepth, int capacity) { + if (nibbleDepth > 8 || nibbleDepth < 1) { + throw new IllegalArgumentException(); + } + + int neededBits = nibbleDepth * capacity; + + size = capacity; + depth = nibbleDepth; + data = new byte[(neededBits + neededBits % 8) / 8]; + mask = (byte) maskFor(nibbleDepth); + } + + public NibbleArray(int nibbleDepth, int capacity, NibbleArray existing) { + if (nibbleDepth > 8 || nibbleDepth < 1) { + throw new IllegalArgumentException(); + } + + int neededBits = nibbleDepth * capacity; + size = capacity; + depth = nibbleDepth; + data = new byte[(neededBits + neededBits % 8) / 8]; + mask = (byte) maskFor(nibbleDepth); + + for (int i = 0; i < Math.min(size, existing.size()); i++) { + set(i, existing.get(i)); + } + } + + public static int maskFor(int amountOfBits) { + return powerOfTwo(amountOfBits) - 1; + } + + public static int powerOfTwo(int power) { + int result = 1; + + for (int i = 0; i < power; i++) { + result *= 2; + } + + return result; + } + + public static String binaryString(byte b, ByteOrder byteOrder) { + String str = String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(' ', '0'); + + return byteOrder.equals(ByteOrder.BIG_ENDIAN) ? str : reverse(str); + } + + public static String reverse(String str) { + return new StringBuilder(str).reverse().toString(); + } + + @Override + public void write(DataOutputStream o) throws IOException { + o.writeByte(depth + Byte.MIN_VALUE); + o.write(data); + } + + @Override + public void read(DataInputStream i) throws IOException { + depth = i.readByte() - Byte.MIN_VALUE; + int neededBits = depth * size; + data = new byte[(neededBits + neededBits % 8) / 8]; + mask = (byte) maskFor(depth); + i.read(data); + } + + public int size() { + return size; + } + + public byte get(int index) { + synchronized (lock) { + bitIndex = index * depth; + byteIndex = bitIndex >> 3; + bitInByte = bitIndex & 7; + int value = data[byteIndex] >> bitInByte; + + if (bitInByte + depth > 8) { + value |= data[byteIndex + 1] << bitInByte; + } + + return (byte) (value & mask); + } + } + + public byte getAsync(int index) { + int bitIndex = index * depth; + int byteIndex = bitIndex >> 3; + int bitInByte = bitIndex & 7; + int value = data[byteIndex] >> bitInByte; + + if (bitInByte + depth > 8) { + value |= data[byteIndex + 1] << bitInByte; + } + + return (byte) (value & mask); + } + + public void set(int index, int nibble) { + set(index, (byte) nibble); + } + + public void set(int index, byte nybble) { + synchronized (lock) { + bitIndex = index * depth; + byteIndex = bitIndex >> 3; + bitInByte = bitIndex & 7; + data[byteIndex] = (byte) (((~(data[byteIndex] & (mask << bitInByte)) & data[byteIndex]) | ((nybble & mask) << bitInByte)) & 0xff); + + if (bitInByte + depth > 8) { + data[byteIndex + 1] = (byte) (((~(data[byteIndex + 1] & MASKS[bitInByte + depth - 8]) & data[byteIndex + 1]) | ((nybble & mask) >> (8 - bitInByte))) & 0xff); + } + } + } + + public String toBitsString() { + return toBitsString(ByteOrder.BIG_ENDIAN); + } + + public String toBitsString(ByteOrder byteOrder) { + StringJoiner joiner = new StringJoiner(" "); + + for (int i = 0; i < data.length; i++) { + joiner.add(binaryString(data[i], byteOrder)); + } + + return joiner.toString(); + } + + public void clear() { + Arrays.fill(data, (byte) 0); + } + + public void setAll(byte nibble) { + for (int i = 0; i < size; i++) { + set(i, nibble); + } + } + + public void setAll(int nibble) { + for (int i = 0; i < size; i++) { + set(i, (byte) nibble); + } + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/ShortTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/ShortTag.java new file mode 100644 index 000000000..e1a428be2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/ShortTag.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * The TAG_Short tag. + * + * @author Graham Edgecombe + */ +public final class ShortTag extends Tag { + + /** + * The value. + */ + private final short value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public ShortTag(String name, short value) { + super(name); + this.value = value; + } + + @Override + public Short getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_Short" + append + ": " + value; + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/StringTag.java b/src/main/java/art/arcane/adapt/util/common/nbt/StringTag.java new file mode 100644 index 000000000..4bb1cc73f --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/StringTag.java @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * The TAG_String tag. + * + * @author Graham Edgecombe + */ +public final class StringTag extends Tag { + + /** + * The value. + */ + private final String value; + + /** + * Creates the tag. + * + * @param name The name. + * @param value The value. + */ + public StringTag(String name, String value) { + super(name); + this.value = value; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String toString() { + String name = getName(); + String append = ""; + if (name != null && !name.equals("")) { + append = "(\"" + this.getName() + "\")"; + } + return "TAG_String" + append + ": " + value; + } + +} diff --git a/src/main/java/art/arcane/adapt/util/common/nbt/Tag.java b/src/main/java/art/arcane/adapt/util/common/nbt/Tag.java new file mode 100644 index 000000000..6a6e3d278 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/nbt/Tag.java @@ -0,0 +1,58 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.nbt; + +/** + * Represents a single NBT tag. + * + * @author Graham Edgecombe + */ +public abstract class Tag { + + /** + * The name of this tag. + */ + private final String name; + + /** + * Creates the tag with the specified name. + * + * @param name The name. + */ + public Tag(String name) { + this.name = name; + } + + /** + * Gets the name of this tag. + * + * @return The name of this tag. + */ + public final String getName() { + return name; + } + + /** + * Gets the value of this tag. + * + * @return The value of this tag. + */ + public abstract Object getValue(); + +} diff --git a/src/main/java/art/arcane/adapt/util/common/parallel/BurstExecutor.java b/src/main/java/art/arcane/adapt/util/common/parallel/BurstExecutor.java new file mode 100644 index 000000000..f9aab0f77 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/parallel/BurstExecutor.java @@ -0,0 +1,9 @@ +package art.arcane.adapt.util.common.parallel; + +import java.util.concurrent.ExecutorService; + +public class BurstExecutor extends art.arcane.volmlib.util.parallel.BurstExecutorSupport { + public BurstExecutor(ExecutorService executor, int burstSizeEstimate) { + super(executor, burstSizeEstimate, Throwable::printStackTrace); + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/parallel/MultiBurst.java b/src/main/java/art/arcane/adapt/util/common/parallel/MultiBurst.java new file mode 100644 index 000000000..c11f9ea17 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/parallel/MultiBurst.java @@ -0,0 +1,40 @@ +package art.arcane.adapt.util.common.parallel; + +import art.arcane.adapt.Adapt; + +import java.util.concurrent.ExecutorService; +import java.util.function.IntSupplier; + +public class MultiBurst extends art.arcane.volmlib.util.parallel.MultiBurstSupport { + private static final long TIMEOUT = Long.getLong("adapt.burst.timeout", 15000); + public static MultiBurst burst = new MultiBurst(Runtime.getRuntime().availableProcessors()); + + public MultiBurst(int tc) { + this(() -> tc); + } + + public MultiBurst(IntSupplier parallelism) { + super("Adapt Workgroup", Thread.MAX_PRIORITY, parallelism, i -> Math.max(i, 1), System::currentTimeMillis, + e -> { + Adapt.info("Exception encountered in burst thread"); + e.printStackTrace(); + }, + Adapt::info, + Adapt::warn, + TIMEOUT); + } + + public ExecutorService getService() { + return service(); + } + + @Override + public BurstExecutor burst(int estimate) { + return new BurstExecutor(service(), estimate); + } + + @Override + public BurstExecutor burst() { + return burst(16); + } +} diff --git a/src/main/java/com/volmit/adapt/util/AdaptService.java b/src/main/java/art/arcane/adapt/util/common/plugin/AdaptService.java similarity index 81% rename from src/main/java/com/volmit/adapt/util/AdaptService.java rename to src/main/java/art/arcane/adapt/util/common/plugin/AdaptService.java index 8a7932457..59eccd8bb 100644 --- a/src/main/java/com/volmit/adapt/util/AdaptService.java +++ b/src/main/java/art/arcane/adapt/util/common/plugin/AdaptService.java @@ -16,17 +16,17 @@ * along with this program. If not, see . */ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.plugin; -import com.volmit.adapt.Adapt; +import art.arcane.adapt.Adapt; import org.bukkit.event.Listener; public interface AdaptService extends Listener { - void onEnable(); + void onEnable(); - void onDisable(); + void onDisable(); - default void postShutdown(Runnable r) { - Adapt.instance.postShutdown(r); - } + default void postShutdown(Runnable r) { + Adapt.instance.postShutdown(r); + } } diff --git a/src/main/java/com/volmit/adapt/util/IActivator.java b/src/main/java/art/arcane/adapt/util/common/plugin/IActivator.java similarity index 95% rename from src/main/java/com/volmit/adapt/util/IActivator.java rename to src/main/java/art/arcane/adapt/util/common/plugin/IActivator.java index 4263bed91..a8b920798 100644 --- a/src/main/java/com/volmit/adapt/util/IActivator.java +++ b/src/main/java/art/arcane/adapt/util/common/plugin/IActivator.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.plugin; public interface IActivator { diff --git a/src/main/java/com/volmit/adapt/util/Instance.java b/src/main/java/art/arcane/adapt/util/common/plugin/Instance.java similarity index 96% rename from src/main/java/com/volmit/adapt/util/Instance.java rename to src/main/java/art/arcane/adapt/util/common/plugin/Instance.java index 5689aa2e9..4d970adbf 100644 --- a/src/main/java/com/volmit/adapt/util/Instance.java +++ b/src/main/java/art/arcane/adapt/util/common/plugin/Instance.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.plugin; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/src/main/java/art/arcane/adapt/util/common/plugin/Metrics.java b/src/main/java/art/arcane/adapt/util/common/plugin/Metrics.java new file mode 100644 index 000000000..f0bc127c9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/plugin/Metrics.java @@ -0,0 +1,883 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.plugin; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.scheduling.J; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; + +public class Metrics { + + private final Plugin plugin; + + private final MetricsBase metricsBase; + + /** + * Creates a new Metrics instance. + * + * @param plugin Your plugin instance. + * @param serviceId The id of the service. It can be found at What is my + * plugin id? + */ + public Metrics(JavaPlugin plugin, int serviceId) { + this.plugin = plugin; + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", true); + config.addDefault("serverUuid", UUID.randomUUID().toString()); + config.addDefault("logFailedRequests", false); + config.addDefault("logSentData", false); + config.addDefault("logResponseStatusText", false); + // Inform the server owners about bStats + config + .options() + .header( + "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" + + "many people use their plugin and their total player count. It's recommended to keep bStats\n" + + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" + + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" + + "anonymous.") + .copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ex) { + Adapt.verbose("Failed to save bStats config file: " + + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + } + // Load the data + boolean enabled = config.getBoolean("enabled", true); + String serverUUID = config.getString("serverUuid"); + boolean logErrors = config.getBoolean("logFailedRequests", false); + boolean logSentData = config.getBoolean("logSentData", false); + boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); + metricsBase = + new MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + submitDataTask -> J.s(submitDataTask), + plugin::isEnabled, + (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), + (message) -> this.plugin.getLogger().log(Level.INFO, message), + logErrors, + logSentData, + logResponseStatusText); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + metricsBase.addCustomChart(chart); + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", getPlayerAmount()); + builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); + builder.appendField("bukkitVersion", Bukkit.getVersion()); + builder.appendField("bukkitName", Bukkit.getName()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField("pluginVersion", plugin.getDescription().getVersion()); + } + + private int getPlayerAmount() { + try { + // Around MC 1.8 the return type was changed from an array to a collection, + // This fixes java.lang.NoSuchMethodError: + // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + return onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + // Just use the new method if the reflection failed + return Bukkit.getOnlinePlayers().size(); + } + } + + public static class MetricsBase { + + /** + * The version of the Metrics class. + */ + public static final String METRICS_VERSION = "2.2.1"; + + private static final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is + * enabled. + * @param appendPlatformDataConsumer A consumer that receives a + * {@code JsonObjectBuilder} and appends + * all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a + * {@code JsonObjectBuilder} and appends + * all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with + * the submit task. This can be used to + * delegate the data collection to a + * another thread to prevent errors + * caused by concurrency. Can be + * {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is + * still enabled. + * @param errorLogger A consumer that accepts log message + * and an error. + * @param infoLogger A consumer that accepts info log + * messages. + * @param logErrors Whether or not errors should be + * logged. + * @param logSentData Whether or not the sent data should be + * logged. + * @param logResponseStatusText Whether or not the response status + * text should be logged. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText) { + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + checkRelocation(); + if (enabled) { + startSubmitting(); + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution + // of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial + // and second delay. + // WARNING: You must not modify and part of this Metrics class, including the submit delay or + // frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** + * Checks that the class was properly relocated. + */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little + // "trick" ... :D + final String defaultPackage = + new String(new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong package + // names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[]{entry.getValue()}); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, + * it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Escapes the given string like stated in + * https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and + * '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not + * as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the + * {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like + * {@link JsonObjectBuilder#appendField(String, JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } +} diff --git a/src/main/java/com/volmit/adapt/util/Permission.java b/src/main/java/art/arcane/adapt/util/common/plugin/Permission.java similarity index 96% rename from src/main/java/com/volmit/adapt/util/Permission.java rename to src/main/java/art/arcane/adapt/util/common/plugin/Permission.java index e087bddb6..3102f740e 100644 --- a/src/main/java/com/volmit/adapt/util/Permission.java +++ b/src/main/java/art/arcane/adapt/util/common/plugin/Permission.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.common.plugin; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/src/main/java/art/arcane/adapt/util/common/plugin/VolmitPlugin.java b/src/main/java/art/arcane/adapt/util/common/plugin/VolmitPlugin.java new file mode 100644 index 000000000..94bf83e81 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/plugin/VolmitPlugin.java @@ -0,0 +1,312 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.plugin; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.AdaptationEventRegistrar; +import art.arcane.adapt.api.skill.SkillEventRegistrar; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.project.command.Control; +import art.arcane.adapt.util.project.command.IController; +import art.arcane.adapt.util.reflect.events.ReflectiveEvents; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.io.IO; +import art.arcane.volmlib.util.math.M; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.List; + +public abstract class VolmitPlugin extends JavaPlugin implements Listener { + public static boolean bad = false; + private int controllerTickerTaskId = -1; + private KMap controllers; + private List cachedControllers; + private KMap, IController> cachedClassControllers; + + public void l(Object l) { + Adapt.info("[" + getName() + "]: " + l); + } + + public void w(Object l) { + Adapt.warn("[" + getName() + "]: " + l); + } + + public void f(Object l) { + Adapt.error("[" + getName() + "]: " + l); + } + + public void v(Object l) { + Adapt.verbose("[" + getName() + "]: " + l); + } + + public void onEnable() { + registerInstance(); + registerControllers(); + controllerTickerTaskId = J.sr(this::tickControllers, 1); + J.a(this::outputInfo); + registerListener(this); + start(); + } + + public void unregisterAll() { + try { + stopControllers(); + unregisterListeners(); + unregisterInstance(); + } catch (Exception e) { + Adapt.error("Adapt: Failed to unregister all, You have a plugin that is not unloading properly. This is a bug in that plugin. Please report it to the developer. This is on shutdown however, so it's not a big deal."); + Adapt.error("Adapt: This is not a bug in Adapt. This is a bug in another plugin. Adapt is unloading ALL Command Nodes with Adapt ID's, If another plugin is unloading all or some of these nodes, it will cause this error."); + } + } + + private void outputInfo() { + try { + IO.delete(getDataFolder("info")); + getDataFolder("info").mkdirs(); + outputPluginInfo(); + } catch (Throwable ex) { + Adapt.verbose("Failed to output plugin info: " + ex.getClass().getSimpleName() + + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); + } + } + + + private void outputPluginInfo() throws IOException { + FileConfiguration fc = new YamlConfiguration(); + fc.set("version", getDescription().getVersion()); + fc.set("name", getDescription().getName()); + fc.save(getDataFile("info", "plugin.yml")); + } + + + @Override + public void onDisable() { + stop(); + if (controllerTickerTaskId != -1) { + J.csr(controllerTickerTaskId); + controllerTickerTaskId = -1; + } + J.cancelPluginTasks(); + unregisterListener(this); + unregisterAll(); + } + + private void tickControllers() { + if (bad) { + return; + } + + for (IController i : getControllers()) { + tickController(i); + } + } + + private void tickController(IController i) { + if (bad) { + return; + } + + if (i.getTickInterval() < 0) { + return; + } + + M.tick++; + if (M.interval(i.getTickInterval())) { + try { + i.tick(); + } catch (Throwable e) { + w("Failed to tick controller " + i.getName()); + e.printStackTrace(); + } + } + } + + public List getControllers() { + return cachedControllers; + } + + private void registerControllers() { + if (bad) { + return; + } + controllers = new KMap<>(); + cachedClassControllers = new KMap<>(); + + for (Field i : getClass().getDeclaredFields()) { + if (i.isAnnotationPresent(Control.class)) { + try { + i.setAccessible(true); + IController pc = (IController) i.getType().getConstructor().newInstance(); + registerController(pc); + i.set(this, pc); + v("Registered " + pc.getName() + " (" + i.getName() + ")"); + } catch (IllegalArgumentException | IllegalAccessException | + InstantiationException | + InvocationTargetException | NoSuchMethodException | + SecurityException e) { + w("Failed to register controller (field " + i.getName() + ")"); + e.printStackTrace(); + } + } + } + + cachedControllers = controllers.v(); + } + + public IController getController(Class c) { + return cachedClassControllers.get(c); + } + + private void registerController(IController pc) { + if (bad) { + return; + } + controllers.put(pc.getName(), pc); + cachedClassControllers.put(pc.getClass(), pc); + registerListener(pc); + + try { + pc.start(); + v("Started " + pc.getName()); + } catch (Throwable e) { + w("Failed to start controller " + pc.getName()); + e.printStackTrace(); + } + } + + private void registerInstance() { + if (bad) { + return; + } + for (Field i : getClass().getDeclaredFields()) { + if (i.isAnnotationPresent(Instance.class)) { + try { + i.setAccessible(true); + i.set(Modifier.isStatic(i.getModifiers()) ? null : this, this); + v("Registered Instance " + i.getName()); + } catch (IllegalArgumentException | IllegalAccessException | + SecurityException e) { + w("Failed to register instance (field " + i.getName() + ")"); + e.printStackTrace(); + } + } + } + } + + private void unregisterInstance() { + if (bad) { + return; + } + for (Field i : getClass().getDeclaredFields()) { + if (i.isAnnotationPresent(Instance.class)) { + try { + i.setAccessible(true); + i.set(Modifier.isStatic(i.getModifiers()) ? null : this, null); + v("Unregistered Instance " + i.getName()); + } catch (IllegalArgumentException | IllegalAccessException | + SecurityException e) { + w("Failed to unregister instance (field " + i.getName() + ")"); + e.printStackTrace(); + } + } + } + } + + + public String getTag() { + if (bad) { + return ""; + } + return getTag(""); + } + + public void registerListener(Listener l) { + if (bad) { + return; + } + if (!SkillEventRegistrar.register(this, l) && !AdaptationEventRegistrar.register(this, l)) { + Bukkit.getPluginManager().registerEvents(l, this); + } + ReflectiveEvents.register(l); + } + + public void unregisterListener(Listener l) { + if (bad) { + return; + } + HandlerList.unregisterAll(l); + } + + public void unregisterListeners() { + if (bad) { + return; + } + HandlerList.unregisterAll((Listener) this); + } + + + private void stopControllers() { + if (bad) { + return; + } + for (IController i : controllers.v()) { + try { + unregisterListener(i); + i.stop(); + v("Stopped " + i.getName()); + } catch (Throwable e) { + w("Failed to stop controller " + i.getName()); + e.printStackTrace(); + } + } + } + + public File getDataFile(String... strings) { + File f = new File(getDataFolder(), String.join(File.separator, strings)); + f.getParentFile().mkdirs(); + return f; + } + + public File getDataFolder(String... strings) { + if (strings.length == 0) { + return super.getDataFolder(); + } + + File f = new File(getDataFolder(), String.join(File.separator, strings)); + f.mkdirs(); + + return f; + } + + public abstract void start(); + + public abstract void stop(); + + public abstract String getTag(String subTag); +} diff --git a/src/main/java/art/arcane/adapt/util/common/plugin/VolmitSender.java b/src/main/java/art/arcane/adapt/util/common/plugin/VolmitSender.java new file mode 100644 index 000000000..eef5ee160 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/plugin/VolmitSender.java @@ -0,0 +1,628 @@ +/* + * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ + +package art.arcane.adapt.util.common.plugin; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.format.AdventureCompat; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.adapt.util.project.command.CommandDummy; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.director.visual.DirectorVisualCommand; +import art.arcane.volmlib.util.director.visual.DirectorVisualCommand.DirectorVisualParameter; +import art.arcane.volmlib.util.format.Form; +import art.arcane.volmlib.util.math.RNG; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.title.Title; +import org.bukkit.Server; +import org.bukkit.Sound; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Represents a volume sender. A command sender with extra crap in its + * + * @author cyberpwn + */ +public class VolmitSender implements CommandSender { + @Getter + private static final KMap helpCache = new KMap<>(); + private final CommandSender s; + public boolean useConsoleCustomColors = true; + public boolean useCustomColorsIngame = true; + public int spinh = -20; + public int spins = 7; + public int spinb = 8; + private String tag; + @Getter + @Setter + private String command; + + /** + * Wrap a command sender + * + * @param s the command sender + */ + public VolmitSender(CommandSender s) { + tag = ""; + this.s = s; + } + + public VolmitSender(CommandSender s, String tag) { + this.tag = tag; + this.s = s; + } + + public static long getTick() { + return art.arcane.volmlib.util.math.M.ms() / 16; + } + + public static String pulse(String colorA, String colorB, double speed) { + return ""; + } + + public static String pulse(double speed) { + return Form.f(invertSpread((((getTick() * 15D * speed) % 1000D) / 1000D)), 3).replaceAll("\\Q,\\E", ".").replaceAll("\\Q?\\E", "-"); + } + + public static double invertSpread(double v) { + return ((1D - v) * 2D) - 1D; + } + + public static List paginate(List all, int linesPerPage, int page, AtomicBoolean hasNext) { + int totalPages = (int) Math.ceil((double) all.size() / linesPerPage); + page = page < 0 ? 0 : page >= totalPages ? totalPages - 1 : page; + hasNext.set(page < totalPages - 1); + List d = new ArrayList<>(); + + for (int i = linesPerPage * page; i < Math.min(all.size(), linesPerPage * (page + 1)); i++) { + d.add(all.get(i)); + } + + return d; + } + + /** + * Get the command tag + * + * @return the command tag + */ + public String getTag() { + return tag; + } + + /** + * Set a command tag (prefix for sendMessage) + * + * @param tag the tag + */ + public void setTag(String tag) { + this.tag = tag; + } + + /** + * Is this sender a player? + * + * @return true if it is + */ + public boolean isPlayer() { + return getS() instanceof Player; + } + + /** + * Is this sender a console? + * + * @return true if it is + */ + public boolean isConsole() { + return getS() instanceof ConsoleCommandSender; + } + + /** + * Force cast to player (be sure to check first) + * + * @return a casted player + */ + public Player player() { + return (Player) getS(); + } + + /** + * Get the origin sender this object is wrapping + * + * @return the command sender + */ + public CommandSender getS() { + return s; + } + + @Override + public boolean isPermissionSet(String name) { + return s.isPermissionSet(name); + } + + @Override + public boolean isPermissionSet(Permission perm) { + return s.isPermissionSet(perm); + } + + @Override + public boolean hasPermission(String name) { + return s.hasPermission(name); + } + + @Override + public boolean hasPermission(Permission perm) { + return s.hasPermission(perm); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + return s.addAttachment(plugin, name, value); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + return s.addAttachment(plugin); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + return s.addAttachment(plugin, name, value, ticks); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + return s.addAttachment(plugin, ticks); + } + + @Override + public void removeAttachment(PermissionAttachment attachment) { + s.removeAttachment(attachment); + } + + @Override + public void recalculatePermissions() { + s.recalculatePermissions(); + } + + @Override + public Set getEffectivePermissions() { + return s.getEffectivePermissions(); + } + + @Override + public boolean isOp() { + return s.isOp(); + } + + @Override + public void setOp(boolean value) { + s.setOp(value); + } + + public void hr() { + s.sendMessage("========================================================"); + } + + public void sendTitle(String title, String subtitle, int i, int s, int o) { + Adapt.audiences.player(player()).showTitle(Title.title( + createComponent(title), + createComponent(subtitle), + Title.Times.times(Duration.ofMillis(i), Duration.ofMillis(s), Duration.ofMillis(o)))); + } + + public void sendProgress(double percent, String thing) { + //noinspection IfStatementWithIdenticalBranches + if (percent < 0) { + int l = 44; + int g = (int) (1D * l); + sendTitle(C.ADAPT + thing + " ", 0, 500, 250); + sendActionNoProcessing("" + "" + pulse("#ff5c5c", "#4d0000", 1D) + " " + Form.repeat(" ", g) + "" + Form.repeat(" ", l - g)); + } else { + int l = 44; + int g = (int) (percent * l); + sendTitle(C.ADAPT + thing + " " + C.BLUE + "" + Form.pc(percent, 0), 0, 500, 250); + sendActionNoProcessing("" + "" + pulse("#ff5c5c", "#4d0000", 1D) + " " + Form.repeat(" ", g) + "" + Form.repeat(" ", l - g)); + } + } + + public void sendAction(String action) { + Adapt.audiences.player(player()).sendActionBar(createNoPrefixComponent(action)); + } + + public void sendActionNoProcessing(String action) { + Adapt.audiences.player(player()).sendActionBar(createNoPrefixComponentNoProcessing(action)); + } + + public void sendTitle(String subtitle, int i, int s, int o) { + Adapt.audiences.player(player()).showTitle(Title.title( + createNoPrefixComponent(" "), + createNoPrefixComponent(subtitle), + Title.Times.times(Duration.ofMillis(i), Duration.ofMillis(s), Duration.ofMillis(o)))); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean canUseCustomColors(VolmitSender volmitSender) { + return volmitSender.isPlayer() ? useCustomColorsIngame : useConsoleCustomColors; + } + + private Component createNoPrefixComponent(String message) { + if (!canUseCustomColors(this)) { + String t = C.translateAlternateColorCodes('&', AdventureCompat.stripTags(message)); + return AdventureCompat.deserialize(t); + } + + String t = C.translateAlternateColorCodes('&', message); + String a = C.aura(t, spinh, spins, spinb, 0.36); + return AdventureCompat.deserialize(a); + } + + private Component createNoPrefixComponentNoProcessing(String message) { + return AdventureCompat.deserializeNoProcessing(message); + } + + private Component createComponent(String message) { + return AdventureCompat.deserialize(createMiniMessage(message)); + } + + private String createMiniMessage(String message) { + if (!canUseCustomColors(this)) { + String t = C.translateAlternateColorCodes('&', AdventureCompat.stripTags(getTag() + message)); + return t; + } + + String t = C.translateAlternateColorCodes('&', getTag() + message); + return C.aura(t, spinh, spins, spinb); + } + + private Component createComponentRaw(String message) { + return AdventureCompat.deserialize(createMiniMessageRaw(message)); + } + + private String createMiniMessageRaw(String message) { + if (!canUseCustomColors(this)) { + String t = C.translateAlternateColorCodes('&', AdventureCompat.stripTags(getTag() + message)); + return t; + } + + String t = C.translateAlternateColorCodes('&', getTag() + message); + return t; + } + + private boolean deliverRichMessage(String miniMessage) { + try { + s.getClass().getMethod("sendRichMessage", String.class).invoke(s, miniMessage); + return true; + } catch (Throwable ignored) { + return false; + } + } + + public void showWaiting(String passive, CompletableFuture f) { + AtomicInteger v = new AtomicInteger(); + v.set(J.sr(() -> { + if (f.isDone()) { + J.csr(v.get()); + sendAction(" "); + return; + } + + sendProgress(-1, passive); + }, 1)); + J.a(() -> { + try { + f.get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + }); + + } + + @Override + public void sendMessage(String message) { + if (s instanceof CommandDummy) { + return; + } + + if ((!useCustomColorsIngame && s instanceof Player) || !useConsoleCustomColors) { + s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message)); + return; + } + + if (message.contains("")) { + s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message.replaceAll("\\Q\\E", ""))); + return; + } + + if (deliverRichMessage(createMiniMessage(message))) { + return; + } + + s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message)); + } + + public void sendMessageBasic(String message) { + s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message)); + } + + public void sendMessageRaw(String message) { + if (s instanceof CommandDummy) { + return; + } + + if ((!useCustomColorsIngame && s instanceof Player) || !useConsoleCustomColors) { + s.sendMessage(C.translateAlternateColorCodes('&', message)); + return; + } + + if (message.contains("")) { + s.sendMessage(message.replaceAll("\\Q\\E", "")); + return; + } + + if (deliverRichMessage(createMiniMessageRaw(message))) { + return; + } + + s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message)); + } + + @Override + public void sendMessage(String[] messages) { + for (String str : messages) + sendMessage(str); + } + + @Override + public void sendMessage(UUID uuid, String message) { + sendMessage(message); + } + + @Override + public void sendMessage(UUID uuid, String[] messages) { + sendMessage(messages); + } + + @Override + public Server getServer() { + return s.getServer(); + } + + @Override + public String getName() { + return s.getName(); + } + + @Override + public Component name() { + return Component.text(getName()); + } + + @Override + public Spigot spigot() { + return s.spigot(); + } + + private String pickRandoms(int max, DirectorVisualCommand i) { + KList m = new KList<>(); + for (int ix = 0; ix < max; ix++) { + m.add((i.isNode() + ? (i.getNode().getParameters().isNotEmpty()) + ? "<#ffd1d1>✦ <#ff6b6b>" + + i.getParentPath() + + " <#ff8a8a>" + + i.getName() + " " + + i.getNode().getParameters().shuffleCopy(RNG.r).kConvert((f) + -> (f.isRequired() || RNG.r.b(0.5) + ? "<#f2e15e>" + f.getNames().getRandom() + "=" + + "<#ff9ea0>" + f.example() + : "")) + .toString(" ") + : "" + : "")); + } + + return m.removeDuplicates().kConvert((iff) -> iff.replaceAll("\\Q \\E", " ")).toString("\n"); + } + + public void sendHeader(String name, int overrideLength) { + int len = overrideLength; + int h = name.length() + 2; + String s = Form.repeat(" ", len - h - 4); + String si = Form.repeat("(", 3); + String so = Form.repeat(")", 3); + String sf = "["; + String se = "]"; + + if (name.trim().isEmpty()) { + sendMessageRaw("" + sf + s + "" + s + se); + } else { + sendMessageRaw("" + sf + s + si + " " + name + " " + so + s + se); + } + } + + public void sendHeader(String name) { + sendHeader(name, 40); + } + + public void sendDirectorHelp(DirectorVisualCommand v) { + sendDirectorHelp(v, 0); + } + + public void sendDirectorHelp(DirectorVisualCommand v, int page) { + if (!isPlayer()) { + for (DirectorVisualCommand i : v.getNodes()) { + sendDirectorHelpNode(i); + } + + return; + } + + sendMessageRaw("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); + + if (v.getNodes().isNotEmpty()) { + sendHeader(v.getPath() + (page > 0 ? (" {" + (page + 1) + "}") : "")); + if (isPlayer() && v.getParent() != null) { + sendMessageRaw("Click to go back to <#ff8a8a>" + Form.capitalize(v.getParent().getName()) + " Help" + "'><#ff6b6b>〈 Back"); + } + + AtomicBoolean next = new AtomicBoolean(false); + for (DirectorVisualCommand i : paginate(v.getNodes(), 17, page, next)) { + sendDirectorHelpNode(i); + } + + String s = ""; + int l = 75 - (page > 0 ? 10 : 0) - (next.get() ? 10 : 0); + + if (page > 0) { + s += "Click to go back to page " + page + "'>〈 Page " + page + " "; + } + + s += "" + Form.repeat(" ", l) + ""; + + if (next.get()) { + s += " Click to go to back to page " + (page + 2) + "'>Page " + (page + 2) + " ❭"; + } + + sendMessageRaw(s); + } else { + sendMessage(C.RED + "There are no subcommands in this group! Contact support, this is a command design issue!"); + } + } + + public void sendDirectorHelpNode(DirectorVisualCommand i) { + if (isPlayer() || s instanceof CommandDummy) { + sendMessageRaw(helpCache.computeIfAbsent(i.getPath(), (k) -> { + String newline = "\n"; + + String realText = i.getPath() + " >" + "<#ffe6e6>⇀ " + i.getName(); + String hoverTitle = i.getNames().copy().reverse().kConvert((f) -> "<#ff5c5c>" + f).toString(", "); + String description = "<#ff9ea0>✎ <#ffd1d1>" + i.getDescription(); + String usage = "<#FF0000>✒ <#8b1a1a>"; + + String onClick; + if (i.isNode()) { + if (i.getNode().getParameters().isEmpty()) { + usage += "There are no parameters. Click to type command."; + onClick = "suggest_command"; + } else { + usage += "Hover over all of the parameters to learn more."; + onClick = "suggest_command"; + } + } else { + usage += "This is a command category. Click to run."; + onClick = "run_command"; + } + + String suggestion = ""; + String suggestions = ""; + if (i.isNode() && i.getNode().getParameters().isNotEmpty()) { + suggestion += newline + "<#ffd1d1>✦ <#ff6b6b>" + i.getParentPath() + " <#ff8a8a>" + i.getName() + " " + + i.getNode().getParameters().kConvert((f) -> "<#ff9ea0>" + f.example()).toString(" "); + suggestions += newline + "" + pickRandoms(Math.min(i.getNode().getParameters().size() + 1, 5), i); + } + + StringBuilder nodes = new StringBuilder(); + if (i.isNode()) { + for (DirectorVisualParameter p : i.getNode().getParameters()) { + String nTitle = "" + p.getName(); + String nHoverTitle = p.getNames().kConvert((ff) -> "<#ff9900>" + ff).toString(", "); + String nDescription = "<#ff9ea0>✎ <#ffd1d1>" + p.getDescription(); + String nUsage; + String fullTitle; + Adapt.debug("Contextual: " + p.isContextual() + " / player: " + isPlayer()); + if (p.isContextual() && (isPlayer() || s instanceof CommandDummy)) { + fullTitle = "<#ffcc66>[" + nTitle + "<#ffcc66>] "; + nUsage = "<#ff9900>➱ <#ffcc66>The value may be derived from environment context."; + } else if (p.isRequired()) { + fullTitle = "[" + nTitle + "] "; + nUsage = "<#ff6b6b>⚠ <#ffd1d1>This parameter is required."; + } else if (p.hasDefault()) { + fullTitle = "<#F7F7F7>⊰" + nTitle + "<#F7F7F7>⊱"; + nUsage = "<#ff8a8a>✔ <#ffd1d1>Defaults to \"" + p.getParam().defaultValue() + "\" if undefined."; + } else { + fullTitle = "<#F7F7F7>⊰" + nTitle + "<#F7F7F7>⊱"; + nUsage = "<#ff6b6b>✔ <#ffd1d1>This parameter is optional."; + } + String type = "<#ff6b6b>✢ <#ff9ea0>This parameter is of type " + p.getType().getSimpleName() + "."; + + nodes + .append("") + .append(fullTitle) + .append(""); + } + } else { + nodes = new StringBuilder(" - Category of Commands"); + } + + return "" + + "" + + "" + + " " + + nodes; + })); + } else { + sendMessage(i.getPath()); + } + } + + public void playSound(Sound sound, float volume, float pitch) { + if (isPlayer()) { + player().playSound(player().getLocation(), sound, volume, pitch); + } + } +} diff --git a/src/main/java/art/arcane/adapt/util/common/scheduling/J.java b/src/main/java/art/arcane/adapt/util/common/scheduling/J.java new file mode 100644 index 000000000..0df13d297 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/common/scheduling/J.java @@ -0,0 +1,218 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.common.scheduling; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.parallel.MultiBurst; +import art.arcane.volmlib.util.function.NastyFunction; +import art.arcane.volmlib.util.function.NastyFuture; +import art.arcane.volmlib.util.function.NastyRunnable; +import art.arcane.volmlib.util.math.FinalInteger; +import art.arcane.volmlib.util.scheduling.*; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.player.PlayerTeleportEvent; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class J { + private static final SchedulerRuntime RUNTIME = new SchedulerRuntime( + () -> Adapt.instance, + J::a, + Adapt::verbose, + Adapt::warn, + Throwable::printStackTrace + ); + + static { + SchedulerBridge.setSyncScheduler(J::s); + SchedulerBridge.setDelayedSyncScheduler(J::s); + SchedulerBridge.setAsyncScheduler(J::a); + SchedulerBridge.setDelayedAsyncScheduler(J::a); + SchedulerBridge.setSyncRepeatingScheduler(J::sr); + SchedulerBridge.setAsyncRepeatingScheduler(J::ar); + SchedulerBridge.setCancelScheduler(J::car); + SchedulerBridge.setErrorHandler(Throwable::printStackTrace); + SchedulerBridge.setInfoLogger(Adapt::info); + } + + public static void dofor(int a, Function c, int ch, Consumer d) { + JSupport.dofor(a, c, ch, d); + } + + public static boolean doif(Supplier c, Runnable g) { + return JSupport.doif(c, g, null); + } + + public static void a(Runnable a) { + MultiBurst.burst.lazy(a); + } + + public static Future a(Callable a) { + return MultiBurst.burst.getService().submit(a); + } + + public static void attemptAsync(NastyRunnable r) { + JSupport.attemptAsync(r::run, J::a); + } + + public static R attemptResult(NastyFuture r, R onError) { + return JSupport.attemptResult(r::run, onError, Throwable::printStackTrace); + } + + public static R attemptFunction(NastyFunction r, T param, R onError) { + return JSupport.attemptFunction(r::run, param, onError, e -> Adapt.verbose("Failed to run function: " + e.getMessage())); + } + + public static boolean sleep(long ms) { + return JSupport.sleep(ms); + } + + public static boolean attempt(NastyRunnable r) { + return JSupport.attempt(r::run); + } + + public static Throwable attemptCatch(NastyRunnable r) { + return JSupport.attemptCatch(r::run); + } + + public static T attempt(Supplier t, T i) { + return JSupport.attempt(t::get, i, null); + } + + /** + * Dont call this unless you know what you are doing! + */ + public static void executeAfterStartupQueue() { + RUNTIME.executeAfterStartupQueue(J::s); + } + + public static void ass(Runnable r) { + RUNTIME.enqueueAfterStartupSync(r, J::s); + } + + public static void asa(Runnable r) { + RUNTIME.enqueueAfterStartupAsync(r); + } + + public static boolean isPrimaryThread() { + return FoliaScheduler.isPrimaryThread(); + } + + public static boolean isFoliaThreading() { + return RUNTIME.isFoliaThreading(); + } + + public static boolean isOwnedByCurrentRegion(Entity entity) { + return RUNTIME.isOwnedByCurrentRegion(entity); + } + + public static boolean runEntity(Entity entity, Runnable runnable) { + return RUNTIME.runEntity(entity, runnable); + } + + public static boolean runEntity(Entity entity, Runnable runnable, int delayTicks) { + return RUNTIME.runEntity(entity, runnable, delayTicks); + } + + public static boolean teleport(Entity entity, Location location) { + return teleport(entity, location, null); + } + + public static boolean teleport(Entity entity, Location location, PlayerTeleportEvent.TeleportCause cause) { + return RUNTIME.teleport(entity, location, cause); + } + + public static boolean runAt(Location location, Runnable runnable) { + return RUNTIME.runAt(location, runnable); + } + + public static boolean runAt(Location location, Runnable runnable, int delayTicks) { + return RUNTIME.runAt(location, runnable, delayTicks); + } + + public static void cancelPluginTasks() { + RUNTIME.cancelPluginTasks(); + } + + public static void s(Runnable r) { + RUNTIME.s(r); + } + + public static void s(Runnable r, int delay) { + RUNTIME.s(r, delay); + } + + public static void csr(int id) { + RUNTIME.csr(id); + } + + public static int sr(Runnable r, int interval) { + return RUNTIME.sr(r, interval); + } + + public static void sr(Runnable r, int interval, int intervals) { + FinalInteger fi = new FinalInteger(0); + + new SR(interval) { + @Override + public void run() { + fi.add(1); + r.run(); + + if (fi.get() >= intervals) { + cancel(); + } + } + }; + } + + public static void a(Runnable r, int delay) { + RUNTIME.a(r, delay); + } + + public static void car(int id) { + RUNTIME.car(id); + } + + public static int ar(Runnable r, int interval) { + return RUNTIME.ar(r, interval); + } + + public static void ar(Runnable r, int interval, int intervals) { + FinalInteger fi = new FinalInteger(0); + + new AR(interval) { + @Override + public void run() { + fi.add(1); + r.run(); + + if (fi.get() >= intervals) { + cancel(); + } + } + }; + } + +} diff --git a/src/main/java/art/arcane/adapt/util/data/Metadata.java b/src/main/java/art/arcane/adapt/util/data/Metadata.java new file mode 100644 index 000000000..8c7b2e6a6 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/data/Metadata.java @@ -0,0 +1,41 @@ +package art.arcane.adapt.util.data; + +import art.arcane.adapt.Adapt; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.Metadatable; + +import static java.util.UUID.randomUUID; + +public class Metadata { + public static final Metadata VEIN_MINED = new Metadata("vein-mined", false); + private static final String UUID = randomUUID().toString(); + private final String key; + private final boolean defaultValue; + + private Metadata(String prefix, boolean defaultValue) { + this.key = UUID + prefix; + this.defaultValue = defaultValue; + } + + public boolean has(Metadatable metadatable) { + return metadatable.hasMetadata(key); + } + + public boolean get(Metadatable metadatable) { + return metadatable.hasMetadata(key) ? metadatable.getMetadata(key).get(0).asBoolean() : defaultValue; + } + + public void set(Metadatable metadatable, boolean value) { + if (value != defaultValue) + metadatable.setMetadata(key, new FixedMetadataValue(Adapt.instance, value)); + else metadatable.removeMetadata(key, Adapt.instance); + } + + public void add(Metadatable metadatable) { + set(metadatable, !defaultValue); + } + + public void remove(Metadatable metadatable) { + set(metadatable, defaultValue); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/DirectorSystem.java b/src/main/java/art/arcane/adapt/util/director/DirectorSystem.java new file mode 100644 index 000000000..71efbb622 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/DirectorSystem.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ + +package art.arcane.adapt.util.director; + +import art.arcane.adapt.Adapt; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.DirectorSystemSupport; + +public final class DirectorSystem { + public static final KList> handlers = Adapt.initialize("art.arcane.adapt.util.director.handlers", null).convert((i) -> (DirectorParameterHandler) i); + + private DirectorSystem() { + } + + /** + * Get the handler for the specified type + * + * @param type The type to handle + * @return The corresponding {@link DirectorParameterHandler}, or null + */ + public static DirectorParameterHandler getHandler(Class type) { + DirectorParameterHandler handler = DirectorSystemSupport.getHandler(handlers, type, (h, t) -> h.supports(t)); + if (handler != null) { + return handler; + } + + Adapt.error("Unhandled type in Director Parameter: " + type.getName() + ". This is bad!"); + return null; + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/context/AdaptationListingHandler.java b/src/main/java/art/arcane/adapt/util/director/context/AdaptationListingHandler.java new file mode 100644 index 000000000..409b334b7 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/context/AdaptationListingHandler.java @@ -0,0 +1,150 @@ +package art.arcane.adapt.util.director.context; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.api.adaptation.Adaptation; +import art.arcane.adapt.api.skill.Skill; +import art.arcane.adapt.api.skill.SkillRegistry; +import art.arcane.volmlib.util.collection.KList; + +public class AdaptationListingHandler { + + private static KList adaptationLists; + private static KList adaptationSkillLists; + private static KList adaptationProviders; + private static KList skillProviders; + + + public static void initializeAdaptationListings() { + adaptationLists = new KList<>(); + adaptationSkillLists = new KList<>(); + adaptationProviders = new KList<>(); + skillProviders = new KList<>(); + + getAdaptionListings(); + getAdaptionSkillListings(); + getAdaptationProviders(); + getSkillProvider(); + } + + public static KList getAdaptionListings() { + if (adaptationLists.isNotEmpty()) return adaptationLists; + + AdaptationList main = new AdaptationList("[Main]"); + adaptationLists.add(main); + + for (Skill skill : SkillRegistry.skills.sortV()) { + if (!skill.isEnabled()) { + continue; + } + AdaptationList skillList = new AdaptationList("[Skill]-" + skill.getName()); + adaptationLists.add(skillList); + + for (Adaptation adaptation : skill.getAdaptations()) { + if (!adaptation.isEnabled()) { + continue; + } + AdaptationList adaptationList = new AdaptationList("[Adaptation]-" + adaptation.getName()); + adaptationLists.add(adaptationList); + } + } + return adaptationLists; + } + + public static KList getAdaptionSkillListings() { + if (adaptationSkillLists.isNotEmpty()) return adaptationSkillLists; + + AdaptationSkillList t1 = new AdaptationSkillList("[all]"); + adaptationSkillLists.add(t1); + AdaptationSkillList t2 = new AdaptationSkillList("[random]"); + adaptationSkillLists.add(t2); + + for (Skill skill : allSkills()) { + AdaptationSkillList t3 = new AdaptationSkillList(skill.getName()); + adaptationSkillLists.add(t3); + } + + adaptationSkillLists.removeDuplicates(); + return adaptationSkillLists; + } + + private static KList> allSkills() { + if (Adapt.instance != null + && Adapt.instance.getAdaptServer() != null + && Adapt.instance.getAdaptServer().getSkillRegistry() != null) { + return new KList<>(Adapt.instance.getAdaptServer().getSkillRegistry().getAllSkills()); + } + + return SkillRegistry.skills.sortV(); + } + + public static KList getAdaptationProviders() { + if (adaptationProviders.isNotEmpty()) return adaptationProviders; + + for (Skill skill : SkillRegistry.skills.sortV()) { + if (!skill.isEnabled()) { + continue; + } + for (Adaptation adaptation : skill.getAdaptations()) { + if (!adaptation.isEnabled()) { + continue; + } + AdaptationProvider suggestion = new AdaptationProvider(skill.getName() + ":" + adaptation.getName()); + adaptationProviders.add(suggestion); + } + } + return adaptationProviders; + } + + public static KList getSkillProvider() { + if (skillProviders.isNotEmpty()) return skillProviders; + + for (Skill skill : SkillRegistry.skills.sortV()) { + if (!skill.isEnabled()) { + continue; + } + SkillProvider t1 = new SkillProvider(skill.getName()); + skillProviders.add(t1); + } + return skillProviders; + } + + public record AdaptationList(String name) { + public boolean startsWith(String prefix) { + return name.startsWith(prefix); + } + + public boolean equals(String prefix) { + return name.equalsIgnoreCase(prefix); + } + } + + public record AdaptationSkillList(String name) { + public boolean startsWith(String prefix) { + return name.startsWith(prefix); + } + + public boolean equals(String prefix) { + return name.equalsIgnoreCase(prefix); + } + } + + public record AdaptationProvider(String name) { + public boolean startsWith(String prefix) { + return name.startsWith(prefix); + } + + public boolean equals(String prefix) { + return name.equalsIgnoreCase(prefix); + } + } + + public record SkillProvider(String name) { + public boolean startsWith(String prefix) { + return name.startsWith(prefix); + } + + public boolean equals(String prefix) { + return name.equalsIgnoreCase(prefix); + } + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationListHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationListHandler.java new file mode 100644 index 000000000..4eeca495e --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationListHandler.java @@ -0,0 +1,28 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.adapt.util.director.context.AdaptationListingHandler; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.exceptions.DirectorParsingException; + +public class AdaptationListHandler implements DirectorParameterHandler { + @Override + public KList getPossibilities() { + return AdaptationListingHandler.getAdaptionListings(); + } + + @Override + public String toString(AdaptationListingHandler.AdaptationList adaptationList) { + return adaptationList.name(); + } + + @Override + public AdaptationListingHandler.AdaptationList parse(String in, boolean force) throws DirectorParsingException { + return new AdaptationListingHandler.AdaptationList(in); + } + + @Override + public boolean supports(Class type) { + return type.equals(AdaptationListingHandler.AdaptationList.class); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationProviderHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationProviderHandler.java new file mode 100644 index 000000000..417267748 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationProviderHandler.java @@ -0,0 +1,29 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.adapt.util.director.context.AdaptationListingHandler; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.exceptions.DirectorParsingException; + +public class AdaptationProviderHandler implements DirectorParameterHandler { + + @Override + public KList getPossibilities() { + return AdaptationListingHandler.getAdaptationProviders(); + } + + @Override + public String toString(AdaptationListingHandler.AdaptationProvider adaptationProvider) { + return adaptationProvider.name(); + } + + @Override + public AdaptationListingHandler.AdaptationProvider parse(String in, boolean force) throws DirectorParsingException { + return new AdaptationListingHandler.AdaptationProvider(in); + } + + @Override + public boolean supports(Class type) { + return type.equals(AdaptationListingHandler.AdaptationProvider.class); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationSkillListHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationSkillListHandler.java new file mode 100644 index 000000000..11014cf34 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/AdaptationSkillListHandler.java @@ -0,0 +1,28 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.adapt.util.director.context.AdaptationListingHandler; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.exceptions.DirectorParsingException; + +public class AdaptationSkillListHandler implements DirectorParameterHandler { + @Override + public KList getPossibilities() { + return AdaptationListingHandler.getAdaptionSkillListings(); + } + + @Override + public String toString(AdaptationListingHandler.AdaptationSkillList adaptationSkillList) { + return adaptationSkillList.name(); + } + + @Override + public AdaptationListingHandler.AdaptationSkillList parse(String in, boolean force) throws DirectorParsingException { + return new AdaptationListingHandler.AdaptationSkillList(in); + } + + @Override + public boolean supports(Class type) { + return type.equals(AdaptationListingHandler.AdaptationSkillList.class); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/BlockVectorHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/BlockVectorHandler.java new file mode 100644 index 000000000..ea1a2e501 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/BlockVectorHandler.java @@ -0,0 +1,39 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.adapt.util.director.DirectorSystem; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.compat.BukkitDirectorContext; +import art.arcane.volmlib.util.director.handlers.base.BlockVectorHandlerBase; +import art.arcane.volmlib.util.format.Form; +import org.bukkit.FluidCollisionMode; +import org.bukkit.entity.Player; +import org.bukkit.util.BlockVector; + +import java.util.List; + +public class BlockVectorHandler extends BlockVectorHandlerBase implements DirectorParameterHandler { + @Override + protected boolean isSenderPlayer() { + return BukkitDirectorContext.isPlayer(); + } + + @Override + protected BlockVector getSenderBlockVector() { + return BukkitDirectorContext.player().getLocation().toVector().toBlockVector(); + } + + @Override + protected BlockVector getLookBlockVector() { + return BukkitDirectorContext.player().getTargetBlockExact(256, FluidCollisionMode.NEVER).getLocation().toVector().toBlockVector(); + } + + @Override + protected List playerPossibilities(String query) { + return DirectorSystem.getHandler(Player.class).getPossibilities(query); + } + + @Override + protected String format(double value) { + return Form.f(value, 2); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/BooleanHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/BooleanHandler.java new file mode 100644 index 000000000..2890aa598 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/BooleanHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.BooleanHandlerBase; + +public class BooleanHandler extends BooleanHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/ByteHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/ByteHandler.java new file mode 100644 index 000000000..643d0e7d9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/ByteHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.ByteHandlerBase; + +public class ByteHandler extends ByteHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/DoubleHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/DoubleHandler.java new file mode 100644 index 000000000..f6606c97f --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/DoubleHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.DoubleHandlerBase; + +public class DoubleHandler extends DoubleHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/FloatHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/FloatHandler.java new file mode 100644 index 000000000..5ae9079c4 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/FloatHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.FloatHandlerBase; + +public class FloatHandler extends FloatHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/IntegerHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/IntegerHandler.java new file mode 100644 index 000000000..746cc8881 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/IntegerHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.IntegerHandlerBase; + +public class IntegerHandler extends IntegerHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/LongHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/LongHandler.java new file mode 100644 index 000000000..add7d7017 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/LongHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.LongHandlerBase; + +public class LongHandler extends LongHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/OptionalWorldHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/OptionalWorldHandler.java new file mode 100644 index 000000000..85c3f5cb2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/OptionalWorldHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ + +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.OptionalWorldHandlerBase; + +public class OptionalWorldHandler extends OptionalWorldHandlerBase implements DirectorParameterHandler { + @Override + protected String excludedPrefix() { + return "adapt/"; + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/ParticleHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/ParticleHandler.java new file mode 100644 index 000000000..1394b80f4 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/ParticleHandler.java @@ -0,0 +1,32 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.exceptions.DirectorParsingException; +import org.bukkit.Particle; + +public class ParticleHandler implements DirectorParameterHandler { + @Override + public KList getPossibilities() { + return new KList<>(Particle.values()); + } + + @Override + public String toString(Particle particle) { + return particle.name(); + } + + @Override + public Particle parse(String in, boolean force) throws DirectorParsingException { + try { + return Particle.valueOf(in); + } catch (IllegalArgumentException e) { + throw new DirectorParsingException("Invalid particle: " + in); + } + } + + @Override + public boolean supports(Class type) { + return type.equals(Particle.class); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/PlayerHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/PlayerHandler.java new file mode 100644 index 000000000..cf8f37a24 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/PlayerHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ + +package art.arcane.adapt.util.director.handlers; + +import art.arcane.adapt.Adapt; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.PlayerHandlerBase; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +public class PlayerHandler extends PlayerHandlerBase implements DirectorParameterHandler { + @Override + protected List playerOptions() { + if (Adapt.instance != null && Adapt.instance.getAdaptServer() != null) { + return new ArrayList<>(Adapt.instance.getAdaptServer().getOnlinePlayerSnapshot()); + } + return new ArrayList<>(Bukkit.getOnlinePlayers()); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/ShortHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/ShortHandler.java new file mode 100644 index 000000000..c6bbc54c4 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/ShortHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.ShortHandlerBase; + +public class ShortHandler extends ShortHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/SkillProviderHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/SkillProviderHandler.java new file mode 100644 index 000000000..724a15766 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/SkillProviderHandler.java @@ -0,0 +1,28 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.adapt.util.director.context.AdaptationListingHandler; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.exceptions.DirectorParsingException; + +public class SkillProviderHandler implements DirectorParameterHandler { + @Override + public KList getPossibilities() { + return AdaptationListingHandler.getSkillProvider(); + } + + @Override + public String toString(AdaptationListingHandler.SkillProvider skillProvider) { + return skillProvider.name(); + } + + @Override + public AdaptationListingHandler.SkillProvider parse(String in, boolean force) throws DirectorParsingException { + return new AdaptationListingHandler.SkillProvider(in); + } + + @Override + public boolean supports(Class type) { + return type.equals(AdaptationListingHandler.SkillProvider.class); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/SoundHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/SoundHandler.java new file mode 100644 index 000000000..cc5a47816 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/SoundHandler.java @@ -0,0 +1,32 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.exceptions.DirectorParsingException; +import org.bukkit.Sound; + +public class SoundHandler implements DirectorParameterHandler { + @Override + public KList getPossibilities() { + return new KList<>(Sound.values()); + } + + @Override + public String toString(Sound sound) { + return sound.name(); + } + + @Override + public Sound parse(String in, boolean force) throws DirectorParsingException { + try { + return Sound.valueOf(in); + } catch (IllegalArgumentException e) { + throw new DirectorParsingException("Invalid sound: " + in); + } + } + + @Override + public boolean supports(Class type) { + return type.equals(Sound.class); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/StringHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/StringHandler.java new file mode 100644 index 000000000..483788555 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/StringHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.StringHandlerBase; + +public class StringHandler extends StringHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/VectorHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/VectorHandler.java new file mode 100644 index 000000000..acc1859f5 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/VectorHandler.java @@ -0,0 +1,39 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.adapt.util.director.DirectorSystem; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.compat.BukkitDirectorContext; +import art.arcane.volmlib.util.director.handlers.base.VectorHandlerBase; +import art.arcane.volmlib.util.format.Form; +import org.bukkit.FluidCollisionMode; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.List; + +public class VectorHandler extends VectorHandlerBase implements DirectorParameterHandler { + @Override + protected boolean isSenderPlayer() { + return BukkitDirectorContext.isPlayer(); + } + + @Override + protected Vector getSenderVector() { + return BukkitDirectorContext.player().getLocation().toVector(); + } + + @Override + protected Vector getLookVector() { + return BukkitDirectorContext.player().getTargetBlockExact(256, FluidCollisionMode.NEVER).getLocation().toVector(); + } + + @Override + protected List playerPossibilities(String query) { + return DirectorSystem.getHandler(Player.class).getPossibilities(query); + } + + @Override + protected String format(double value) { + return Form.f(value, 2); + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/handlers/WorldHandler.java b/src/main/java/art/arcane/adapt/util/director/handlers/WorldHandler.java new file mode 100644 index 000000000..777a07cd0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/handlers/WorldHandler.java @@ -0,0 +1,12 @@ +package art.arcane.adapt.util.director.handlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.WorldHandlerBase; +import org.bukkit.World; + +public class WorldHandler extends WorldHandlerBase implements DirectorParameterHandler { + @Override + protected String excludedPrefix() { + return "adapt/"; + } +} diff --git a/src/main/java/art/arcane/adapt/util/director/specialhandlers/DummyHandler.java b/src/main/java/art/arcane/adapt/util/director/specialhandlers/DummyHandler.java new file mode 100644 index 000000000..a08d16fb1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/specialhandlers/DummyHandler.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.director.specialhandlers; + +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.handlers.base.DummyHandlerBase; + +public class DummyHandler extends DummyHandlerBase implements DirectorParameterHandler { +} diff --git a/src/main/java/art/arcane/adapt/util/director/specialhandlers/NullablePlayerHandler.java b/src/main/java/art/arcane/adapt/util/director/specialhandlers/NullablePlayerHandler.java new file mode 100644 index 000000000..d29a3c667 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/director/specialhandlers/NullablePlayerHandler.java @@ -0,0 +1,22 @@ +package art.arcane.adapt.util.director.specialhandlers; + +import art.arcane.adapt.util.director.handlers.PlayerHandler; +import art.arcane.volmlib.util.director.DirectorParameterHandler; +import art.arcane.volmlib.util.director.exceptions.DirectorParsingException; +import org.bukkit.entity.Player; + +public class NullablePlayerHandler extends PlayerHandler implements DirectorParameterHandler { + @Override + public Player parse(String in, boolean force) throws DirectorParsingException { + if (in == null) { + return null; + } + + String value = in.trim(); + if (value.isEmpty() || value.equals("---") || value.equalsIgnoreCase("null")) { + return null; + } + + return super.parse(value, force); + } +} diff --git a/src/main/java/com/volmit/adapt/util/Command.java b/src/main/java/art/arcane/adapt/util/project/command/Command.java similarity index 94% rename from src/main/java/com/volmit/adapt/util/Command.java rename to src/main/java/art/arcane/adapt/util/project/command/Command.java index a5b0c035e..67baa14eb 100644 --- a/src/main/java/com/volmit/adapt/util/Command.java +++ b/src/main/java/art/arcane/adapt/util/project/command/Command.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.project.command; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -27,5 +27,5 @@ @Retention(RUNTIME) @Target(FIELD) public @interface Command { - String value() default ""; + String value() default ""; } diff --git a/src/main/java/art/arcane/adapt/util/project/command/CommandDummy.java b/src/main/java/art/arcane/adapt/util/project/command/CommandDummy.java new file mode 100644 index 000000000..82ccbf7b2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/CommandDummy.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + */ + +package art.arcane.adapt.util.project.command; + +import net.kyori.adventure.text.Component; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.UUID; + +public class CommandDummy implements CommandSender { + @Override + public void sendMessage(@NotNull String message) { + + } + + @Override + public void sendMessage(@NotNull String... messages) { + + } + + @Override + public void sendMessage(@Nullable UUID sender, @NotNull String message) { + + } + + @Override + public void sendMessage(@Nullable UUID sender, @NotNull String... messages) { + + } + + @NotNull + @Override + public Server getServer() { + return null; + } + + @NotNull + @Override + public String getName() { + return null; + } + + @NotNull + @Override + public Component name() { + return Component.text(""); + } + + @NotNull + @Override + public Spigot spigot() { + return null; + } + + @Override + public boolean isPermissionSet(@NotNull String name) { + return false; + } + + @Override + public boolean isPermissionSet(@NotNull Permission perm) { + return false; + } + + @Override + public boolean hasPermission(@NotNull String name) { + return false; + } + + @Override + public boolean hasPermission(@NotNull Permission perm) { + return false; + } + + @NotNull + @Override + public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) { + return null; + } + + @NotNull + @Override + public PermissionAttachment addAttachment(@NotNull Plugin plugin) { + return null; + } + + @Nullable + @Override + public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) { + return null; + } + + @Nullable + @Override + public PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) { + return null; + } + + @Override + public void removeAttachment(@NotNull PermissionAttachment attachment) { + + } + + @Override + public void recalculatePermissions() { + + } + + @NotNull + @Override + public Set getEffectivePermissions() { + return null; + } + + @Override + public boolean isOp() { + return false; + } + + @Override + public void setOp(boolean value) { + + } +} diff --git a/src/main/java/com/volmit/adapt/util/Control.java b/src/main/java/art/arcane/adapt/util/project/command/Control.java similarity index 96% rename from src/main/java/com/volmit/adapt/util/Control.java rename to src/main/java/art/arcane/adapt/util/project/command/Control.java index 13f8db378..cdc15dc2d 100644 --- a/src/main/java/com/volmit/adapt/util/Control.java +++ b/src/main/java/art/arcane/adapt/util/project/command/Control.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.project.command; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/src/main/java/art/arcane/adapt/util/project/command/FCommand.java b/src/main/java/art/arcane/adapt/util/project/command/FCommand.java new file mode 100644 index 000000000..1d49177d6 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/FCommand.java @@ -0,0 +1,5 @@ +package art.arcane.adapt.util.command; + +public interface FCommand { + +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/FConst.java b/src/main/java/art/arcane/adapt/util/project/command/FConst.java new file mode 100644 index 000000000..f3bf32d71 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/FConst.java @@ -0,0 +1,68 @@ +package art.arcane.adapt.util.command; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Sound; + +import java.awt.*; + +public class FConst { + public static final Color COLOR_ERROR = new Color(255, 0, 0); + public static final Color COLOR_SUCCESS = new Color(0, 255, 0); + public static final Color COLOR_WARNING = new Color(255, 255, 0); + public static final Color COLOR_INFO = new Color(255, 255, 255); + public static final Feedback TELEPORT = Feedback.builder() + .sound(SoundFeedback.builder() + .sound(Sound.ENTITY_ENDER_EYE_LAUNCH) + .volume(0.7f) + .pitch(1.25f) + .build()) + .build(); + + public static Feedback error(String message, Object... args) { + return Feedback.builder() + .message(errorText(message, args)) + .sound(SoundFeedback.builder().sound(Sound.BLOCK_DEEPSLATE_BREAK).pitch(0.5f).volume(1f).build()) + .build(); + } + + public static Feedback success(String message, Object... args) { + return Feedback.builder() + .message(successText(message, args)) + .sound(SoundFeedback.builder().sound(Sound.BLOCK_AMETHYST_BLOCK_PLACE).pitch(1.5f).volume(1f).build()) + .sound(SoundFeedback.builder().sound(Sound.ITEM_ARMOR_EQUIP_ELYTRA).pitch(1.1f).volume(1f).build()) + .build(); + } + + public static Feedback warning(String message, Object... args) { + return Feedback.builder() + .message(warningText(message, args)) + .sound(SoundFeedback.builder().sound(Sound.ITEM_ARMOR_EQUIP_CHAIN).pitch(0.6f).volume(1f).build()) + .build(); + } + + public static Feedback info(String message, Object... args) { + return Feedback.builder() + .message(infoText(message, args)) + .sound(SoundFeedback.builder().sound(Sound.ITEM_ARMOR_EQUIP_LEATHER).pitch(1.1f).volume(1f).build()) + .build(); + } + + public static TextComponent errorText(String message, Object... args) { + return Component.text(message.formatted(args)).color(TextColor.color(FConst.COLOR_ERROR.getRGB())); + } + + public static TextComponent successText(String message, Object... args) { + return Component.text(message.formatted(args)).color(TextColor.color(FConst.COLOR_SUCCESS.getRGB())); + } + + public static TextComponent warningText(String message, Object... args) { + return Component.text(message.formatted(args)).color(TextColor.color(FConst.COLOR_WARNING.getRGB())); + } + + public static TextComponent infoText(String message, Object... args) { + return Component.text(message.formatted(args)).color(TextColor.color(FConst.COLOR_INFO.getRGB())); + } + +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/FService.java b/src/main/java/art/arcane/adapt/util/project/command/FService.java new file mode 100644 index 000000000..6d91ec17a --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/FService.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.command; + +public interface FService { + void start(); + + void stop(); +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/Feedback.java b/src/main/java/art/arcane/adapt/util/project/command/Feedback.java new file mode 100644 index 000000000..7bcd310a3 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/Feedback.java @@ -0,0 +1,42 @@ +package art.arcane.adapt.util.command; + +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.plugin.VolmitSender; +import lombok.Builder; +import lombok.Data; +import lombok.Singular; +import lombok.experimental.Accessors; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +@Builder +@Data +@Accessors(chain = true, fluent = true) +public class Feedback { + @Singular + private List sounds; + @Singular + private List messages; + + public void send(CommandSender serverOrPlayer) { + if (serverOrPlayer instanceof Player p) { + for (SoundFeedback i : sounds) { + i.play(p); + } + } + + LegacyComponentSerializer serializer = LegacyComponentSerializer.legacySection(); + String prefix = C.GRAY + "[" + C.ADAPT + "Adapt" + C.GRAY + "] "; + for (TextComponent i : messages) { + serverOrPlayer.sendMessage(prefix + serializer.serialize(i)); + } + } + + public void send(VolmitSender sender) { + send(sender.getS()); + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/ICommand.java b/src/main/java/art/arcane/adapt/util/project/command/ICommand.java new file mode 100644 index 000000000..27bdf6d58 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/ICommand.java @@ -0,0 +1,70 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.project.command; + +import java.util.List; + +/** + * Represents a pawn command + * + * @author cyberpwn + */ +public interface ICommand { + List getRequiredPermissions(); + + /** + * Get the name of this command (node) + * + * @return the node + */ + String getNode(); + + /** + * Get all (realized) nodes of this command + * + * @return the nodes + */ + List getNodes(); + + /** + * Get all (every) node in this command + * + * @return all nodes + */ + List getAllNodes(); + + /** + * Add a node to this command + * + * @param node the node + */ + void addNode(String node); + + /** + * Handle a command. If this is a subcommand, parameters after the subcommand + * will be adapted in args for you + * + * @param sender the volume sender (pre-tagged) + * @param args the arguments after this command node + * @return return true to mark it as handled + */ + boolean handle(MortarSender sender, String[] args); + + List handleTab(MortarSender sender, String[] args); +} diff --git a/src/main/java/com/volmit/adapt/util/IController.java b/src/main/java/art/arcane/adapt/util/project/command/IController.java similarity index 82% rename from src/main/java/com/volmit/adapt/util/IController.java rename to src/main/java/art/arcane/adapt/util/project/command/IController.java index d06d23ee8..4ffc74fd3 100644 --- a/src/main/java/com/volmit/adapt/util/IController.java +++ b/src/main/java/art/arcane/adapt/util/project/command/IController.java @@ -16,26 +16,26 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.project.command; import org.bukkit.event.Listener; public interface IController extends Listener { - String getName(); + String getName(); - void start(); + void start(); - void stop(); + void stop(); - void tick(); + void tick(); - int getTickInterval(); + int getTickInterval(); - void l(Object l); + void l(Object l); - void w(Object l); + void w(Object l); - void f(Object l); + void f(Object l); - void v(Object l); + void v(Object l); } diff --git a/src/main/java/art/arcane/adapt/util/project/command/MortarCommand.java b/src/main/java/art/arcane/adapt/util/project/command/MortarCommand.java new file mode 100644 index 000000000..32ab35a37 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/MortarCommand.java @@ -0,0 +1,222 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.project.command; + +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.volmlib.util.collection.KList; +import org.bukkit.Sound; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a pawn command + * + * @author cyberpwn + */ +public abstract class MortarCommand implements ICommand { + private final KList children; + private final KList nodes; + private final KList requiredPermissions; + private final String node; + private String category; + private String description; + + /** + * Override this with a super constructor as most commands shouldn't change + * these parameters + * + * @param node the node (primary node) i.e. volume + * @param nodes the aliases. i.e. v, vol, bile + */ + public MortarCommand(String node, String... nodes) { + category = ""; + this.node = node; + this.nodes = new KList<>(); + this.nodes.add(nodes); + requiredPermissions = new KList<>(); + children = buildChildren(); + description = "No Description"; + } + + @Override + public List handleTab(MortarSender sender, String[] args) { + List v = new ArrayList<>(); + if (args.length == 0) { + for (MortarCommand i : getChildren()) { + v.add(i.getNode()); + } + } + + addTabOptions(sender, args, v); + + if (v.isEmpty()) { + return null; + } + + if (sender.isPlayer()) { + SoundPlayer spw = SoundPlayer.of(sender.player().getWorld()); + spw.play(sender.player().getLocation(), Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.25f, 1.7f); + } + + return v; + } + + public abstract void addTabOptions(MortarSender sender, String[] args, List list); + + public void printHelp(MortarSender sender) { + boolean b = false; + + for (MortarCommand i : getChildren()) { + for (String j : i.getRequiredPermissions()) { + if (!sender.hasPermission(j)) { + continue; + } + } + + b = true; + + sender.sendMessage(C.GREEN + i.getNode() + " " + C.WHITE + i.getArgsUsage() + C.GRAY + " - " + i.getDescription()); + } + + if (!b) { + sender.sendMessage("There are either no sub-commands or you do not have permission to use them."); + } + + if (sender.isPlayer()) { + SoundPlayer spw = SoundPlayer.of(sender.player().getWorld()); + spw.play(sender.player().getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.28f, 1.4f); + spw.play(sender.player().getLocation(), Sound.ITEM_AXE_STRIP, 0.35f, 1.7f); + } + } + + protected abstract String getArgsUsage(); + + public String getDescription() { + return description; + } + + protected void setDescription(String description) { + this.description = description; + } + + protected void requiresPermission(MortarPermission node) { + if (node == null) { + return; + } + + requiresPermission(node.toString()); + } + + protected void requiresPermission(String node) { + if (node == null) { + return; + } + + requiredPermissions.add(node); + } + + public void rejectAny(int past, MortarSender sender, String[] a) { + if (a.length > past) { + int p = past; + + String m = ""; + + for (String i : a) { + p--; + if (p < 0) { + m += i + ", "; + } + } + + if (!m.trim().isEmpty()) { + sender.sendMessage("Parameters Ignored: " + m); + } + } + } + + @Override + public String getNode() { + return node; + } + + @Override + public KList getNodes() { + return nodes; + } + + @Override + public KList getAllNodes() { + return getNodes().copy().qadd(getNode()); + } + + @Override + public void addNode(String node) { + getNodes().add(node); + } + + public List getChildren() { + return children; + } + + private KList buildChildren() { + KList p = new KList<>(); + + for (Field i : getClass().getDeclaredFields()) { + if (i.isAnnotationPresent(Command.class)) { + try { + i.setAccessible(true); + MortarCommand pc = (MortarCommand) i.getType().getConstructor().newInstance(); + Command c = i.getAnnotation(Command.class); + + if (!c.value().trim().isEmpty()) { + pc.setCategory(c.value().trim()); + } else { + pc.setCategory(getCategory()); + } + + p.add(pc); + } catch (IllegalArgumentException | IllegalAccessException | + InstantiationException | + InvocationTargetException | NoSuchMethodException | + SecurityException e) { + e.printStackTrace(); + } + } + } + + return p; + } + + @Override + public List getRequiredPermissions() { + return requiredPermissions; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/MortarPermission.java b/src/main/java/art/arcane/adapt/util/project/command/MortarPermission.java new file mode 100644 index 000000000..791f796fb --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/MortarPermission.java @@ -0,0 +1,101 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.project.command; + +import art.arcane.adapt.util.common.plugin.Permission; +import org.bukkit.command.CommandSender; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +public abstract class MortarPermission { + private MortarPermission parent; + + public MortarPermission() { + for (Field i : getClass().getDeclaredFields()) { + if (i.isAnnotationPresent(Permission.class)) { + try { + MortarPermission px = (MortarPermission) i.getType().getConstructor().newInstance(); + px.setParent(this); + i.set(Modifier.isStatic(i.getModifiers()) ? null : this, px); + } catch (IllegalArgumentException | IllegalAccessException | + InstantiationException | + InvocationTargetException | NoSuchMethodException | + SecurityException e) { + e.printStackTrace(); + } + } + } + } + + public List getChildren() { + List p = new ArrayList<>(); + + for (Field i : getClass().getDeclaredFields()) { + if (i.isAnnotationPresent(Permission.class)) { + try { + p.add((MortarPermission) i.get(Modifier.isStatic(i.getModifiers()) ? null : this)); + } catch (IllegalArgumentException | IllegalAccessException | + SecurityException e) { + e.printStackTrace(); + } + } + } + + return p; + } + + public String getFullNode() { + if (hasParent()) { + return getParent().getFullNode() + "." + getNode(); + } + + return getNode(); + } + + protected abstract String getNode(); + + public abstract String getDescription(); + + public abstract boolean isDefault(); + + @Override + public String toString() { + return getFullNode(); + } + + public boolean hasParent() { + return getParent() != null; + } + + public MortarPermission getParent() { + return parent; + } + + public void setParent(MortarPermission parent) { + this.parent = parent; + } + + public boolean has(CommandSender sender) { + return sender.hasPermission(getFullNode()); + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/MortarSender.java b/src/main/java/art/arcane/adapt/util/project/command/MortarSender.java new file mode 100644 index 000000000..724b23a3b --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/MortarSender.java @@ -0,0 +1,220 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.project.command; + +import art.arcane.adapt.util.common.format.C; +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.UUID; + +/** + * Represents a volume sender. A command sender with extra crap in it + * + * @author cyberpwn + */ +public class MortarSender implements CommandSender { + private final CommandSender s; + private String tag; + + @Getter + @Setter + private String command; + + /** + * Wrap a command sender + * + * @param s the command sender + */ + public MortarSender(CommandSender s) { + tag = ""; + this.s = s; + } + + public MortarSender(CommandSender s, String tag) { + this.tag = tag; + this.s = s; + } + + /** + * Get the command tag + * + * @return the command tag + */ + public String getTag() { + return tag; + } + + /** + * Set a command tag (prefix for sendMessage) + * + * @param tag the tag + */ + public void setTag(String tag) { + this.tag = tag; + } + + /** + * Is this sender a player? + * + * @return true if it is + */ + public boolean isPlayer() { + return getS() instanceof Player; + } + + /** + * Force cast to player (be sure to check first) + * + * @return a casted player + */ + public Player player() { + return (Player) getS(); + } + + /** + * Get the origin sender this object is wrapping + * + * @return the command sender + */ + public CommandSender getS() { + return s; + } + + @Override + public boolean isPermissionSet(String name) { + return s.isPermissionSet(name); + } + + @Override + public boolean isPermissionSet(Permission perm) { + return s.isPermissionSet(perm); + } + + @Override + public boolean hasPermission(String name) { + return s.hasPermission(name); + } + + @Override + public boolean hasPermission(Permission perm) { + return s.hasPermission(perm); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + return s.addAttachment(plugin, name, value); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + return s.addAttachment(plugin); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + return s.addAttachment(plugin, name, value, ticks); + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + return s.addAttachment(plugin, ticks); + } + + @Override + public void removeAttachment(PermissionAttachment attachment) { + s.removeAttachment(attachment); + } + + @Override + public void recalculatePermissions() { + s.recalculatePermissions(); + } + + @Override + public Set getEffectivePermissions() { + return s.getEffectivePermissions(); + } + + @Override + public boolean isOp() { + return s.isOp(); + } + + @Override + public void setOp(boolean value) { + s.setOp(value); + } + + public void hr() { + s.sendMessage("========================================================"); + } + + @Override + public void sendMessage(String message) { + s.sendMessage(C.translateAlternateColorCodes('&', getTag()) + message); + } + + @Override + public void sendMessage(String[] messages) { + for (String str : messages) + s.sendMessage(C.translateAlternateColorCodes('&', getTag() + str)); + } + + @Override + public void sendMessage(@Nullable UUID sender, @NotNull String message) { + s.sendMessage(sender, message); + } + + @Override + public void sendMessage(@Nullable UUID sender, @NotNull String... messages) { + s.sendMessage(sender, messages); + } + + @Override + public Server getServer() { + return s.getServer(); + } + + @Override + public String getName() { + return s.getName(); + } + + @Override + public Component name() { + return Component.text(getName()); + } + + @Override + public Spigot spigot() { + return s.spigot(); + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/RouterCommand.java b/src/main/java/art/arcane/adapt/util/project/command/RouterCommand.java new file mode 100644 index 000000000..38a78a542 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/RouterCommand.java @@ -0,0 +1,62 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.project.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +/** + * Assistive command router + * + * @author cyberpwn + */ +public class RouterCommand extends org.bukkit.command.Command { + private final CommandExecutor ex; + private String usage; + + /** + * The router command routes commands to bukkit executors + * + * @param realCommand the real command + * @param ex the executor + */ + public RouterCommand(ICommand realCommand, CommandExecutor ex) { + super(realCommand.getNode().toLowerCase()); + setAliases(realCommand.getNodes()); + + this.ex = ex; + } + + @Override + public Command setUsage(String u) { + this.usage = u; + return this; + } + + @Override + public String getUsage() { + return usage; + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + return ex.onCommand(sender, this, commandLabel, args); + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/SoundFeedback.java b/src/main/java/art/arcane/adapt/util/project/command/SoundFeedback.java new file mode 100644 index 000000000..86418762e --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/SoundFeedback.java @@ -0,0 +1,22 @@ +package art.arcane.adapt.util.command; + +import lombok.Builder; +import lombok.Data; +import lombok.experimental.Accessors; +import org.bukkit.Sound; +import org.bukkit.entity.Player; + +@Builder +@Data +@Accessors(chain = true, fluent = true) +public class SoundFeedback { + private Sound sound; + @Builder.Default + private float volume = 1f; + @Builder.Default + private float pitch = 1f; + + public void play(Player p) { + p.playSound(p.getLocation(), sound, volume, pitch); + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/command/VirtualCommand.java b/src/main/java/art/arcane/adapt/util/project/command/VirtualCommand.java new file mode 100644 index 000000000..481bcaec1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/command/VirtualCommand.java @@ -0,0 +1,193 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.project.command; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.format.C; +import art.arcane.adapt.util.common.misc.SoundPlayer; +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.collection.KMap; +import art.arcane.volmlib.util.reflect.V; +import org.bukkit.Sound; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; + +/** + * Represents a virtual command. A chain of iterative processing through + * subcommands. + * + * @author cyberpwn + */ +public class VirtualCommand { + private final ICommand command; + private final String tag; + + private final KMap, VirtualCommand> children; + + public VirtualCommand(ICommand command) { + this(command, ""); + } + + public VirtualCommand(ICommand command, String tag) { + this.command = command; + children = new KMap<>(); + this.tag = tag; + + for (Field i : command.getClass().getDeclaredFields()) { + if (i.isAnnotationPresent(Command.class)) { + try { + Command cc = i.getAnnotation(Command.class); + ICommand cmd = (ICommand) i.getType().getConstructor().newInstance(); + new V(command, true, true).set(i.getName(), cmd); + children.put(cmd.getAllNodes(), new VirtualCommand(cmd, cc.value().trim().isEmpty() ? tag : cc.value().trim())); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + public String getTag() { + return tag; + } + + public ICommand getCommand() { + return command; + } + + public Map, VirtualCommand> getChildren() { + return children; + } + + public boolean hit(CommandSender sender, KList chain) { + return hit(sender, chain, null); + } + + public boolean hit(CommandSender sender, KList chain, String label) { + MortarSender vs = new MortarSender(sender); + vs.setTag(tag); + + if (label != null) { + vs.setCommand(label); + } + + if (chain.isEmpty()) { + if (!checkPermissions(sender, command)) { + return true; + } + + return command.handle(vs, new String[0]); + } + + String nl = chain.get(0); + + for (List i : children.k()) { + for (String j : i) { + if (j.equalsIgnoreCase(nl)) { + vs.setCommand(chain.get(0)); + VirtualCommand cmd = children.get(i); + KList c = chain.copy(); + c.remove(0); + if (cmd.hit(sender, c, vs.getCommand())) { + if (vs.isPlayer()) { + SoundPlayer spw = SoundPlayer.of(vs.player().getWorld()); + spw.play(vs.player().getLocation(), Sound.ITEM_AXE_STRIP, 0.35f, 1.8f); + } + + return true; + } + } + } + } + + if (!checkPermissions(sender, command)) { + return true; + } + + return command.handle(vs, chain.toArray(new String[chain.size()])); + } + + public List hitTab(CommandSender sender, KList chain, String label) { + MortarSender vs = new MortarSender(sender); + vs.setTag(tag); + + if (label != null) + vs.setCommand(label); + + if (chain.isEmpty()) { + if (!checkPermissions(sender, command)) { + return null; + } + + return command.handleTab(vs, new String[0]); + } + + String nl = chain.get(0); + + for (List i : children.k()) { + for (String j : i) { + if (j.equalsIgnoreCase(nl)) { + vs.setCommand(chain.get(0)); + VirtualCommand cmd = children.get(i); + KList c = chain.copy(); + c.remove(0); + List v = cmd.hitTab(sender, c, vs.getCommand()); + if (v != null) { + return v; + } + } + } + } + + if (!checkPermissions(sender, command)) { + return null; + } + + return command.handleTab(vs, chain.toArray(new String[chain.size()])); + } + + private boolean checkPermissions(CommandSender sender, ICommand command2) { + boolean failed = false; + + for (String i : command.getRequiredPermissions()) { + if (!sender.hasPermission(i)) { + failed = true; + Player player = sender.getServer().getPlayer(sender.getName()); + if (player != null) { + J.runEntity(player, () -> Adapt.messagePlayer(player, "- " + C.WHITE + i)); + } + } + } + + if (failed) { + Player player = sender.getServer().getPlayer(sender.getName()); + if (player != null) { + Adapt.messagePlayer(player, "Insufficient Permissions"); + } + return false; + } + + return true; + } +} diff --git a/src/main/java/com/volmit/adapt/util/config/ConfigAdvanced.java b/src/main/java/art/arcane/adapt/util/project/config/ConfigAdvanced.java similarity index 87% rename from src/main/java/com/volmit/adapt/util/config/ConfigAdvanced.java rename to src/main/java/art/arcane/adapt/util/project/config/ConfigAdvanced.java index 0edf82468..ccccb08e1 100644 --- a/src/main/java/com/volmit/adapt/util/config/ConfigAdvanced.java +++ b/src/main/java/art/arcane/adapt/util/project/config/ConfigAdvanced.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.config; +package art.arcane.adapt.util.config; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/src/main/java/com/volmit/adapt/util/config/ConfigDescription.java b/src/main/java/art/arcane/adapt/util/project/config/ConfigDescription.java similarity index 82% rename from src/main/java/com/volmit/adapt/util/config/ConfigDescription.java rename to src/main/java/art/arcane/adapt/util/project/config/ConfigDescription.java index 0b8ef4917..226063549 100644 --- a/src/main/java/com/volmit/adapt/util/config/ConfigDescription.java +++ b/src/main/java/art/arcane/adapt/util/project/config/ConfigDescription.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.config; +package art.arcane.adapt.util.config; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -9,5 +9,5 @@ @Retention(RUNTIME) @Target(TYPE) public @interface ConfigDescription { - String value(); + String value(); } diff --git a/src/main/java/art/arcane/adapt/util/project/config/ConfigDoc.java b/src/main/java/art/arcane/adapt/util/project/config/ConfigDoc.java new file mode 100644 index 000000000..4f04f360d --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/config/ConfigDoc.java @@ -0,0 +1,15 @@ +package art.arcane.adapt.util.config; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) +@Target(FIELD) +public @interface ConfigDoc { + String value(); + + String impact() default ""; +} diff --git a/src/main/java/art/arcane/adapt/util/project/config/ConfigDocumentation.java b/src/main/java/art/arcane/adapt/util/project/config/ConfigDocumentation.java new file mode 100644 index 000000000..9d470fa63 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/config/ConfigDocumentation.java @@ -0,0 +1,308 @@ +package art.arcane.adapt.util.config; + +import java.lang.reflect.Field; +import java.util.*; + +public final class ConfigDocumentation { + private static final Map SUMMARY_BY_KEY = Map.ofEntries( + Map.entry("enabled", "Enables or disables this feature."), + Map.entry("permanent", "Keeps this adaptation permanently active once learned."), + Map.entry("baseCost", "Base knowledge cost used when learning this adaptation."), + Map.entry("initialCost", "Knowledge cost required to purchase level 1."), + Map.entry("costFactor", "Scaling factor applied to higher adaptation levels."), + Map.entry("maxLevel", "Maximum level a player can reach for this adaptation."), + Map.entry("setInterval", "Tick interval used by this logic."), + Map.entry("minXp", "Minimum xp threshold required for this skill logic."), + Map.entry("language", "Primary language file used for localizations."), + Map.entry("fallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing", "Fallback language used when a localization key is missing."), + Map.entry("autoUpdateLanguage", "When enabled, language files are refreshed from plugin resources."), + Map.entry("autoUpdateCheck", "Checks for plugin updates during startup."), + Map.entry("metrics", "Sends anonymous bStats usage metrics."), + Map.entry("xpInCreative", "Allows skill xp gain while players are in creative or spectator."), + Map.entry("allowAdaptationsInCreative", "Allows using adaptations in creative mode."), + Map.entry("blacklistedWorlds", "World folder names where Adapt logic is disabled."), + Map.entry("experienceMaxLevel", "Global maximum level cap for skill progression."), + Map.entry("adaptActivatorBlock", "Block type players right-click to open the skills UI."), + Map.entry("adaptActivatorBlockName", "Display name used in UI text for the activator block."), + Map.entry("customModels", "Enables custom model lookups from the models config."), + Map.entry("advancements", "Enables Adapt advancement registration and grant flow."), + Map.entry("loginBonus", "Grants the configured login bonus message/rewards."), + Map.entry("welcomeMessage", "Shows the Adapt welcome message when players join."), + Map.entry("useSql", "Uses SQL as the player data backend."), + Map.entry("useRedis", "Enables Redis synchronization when SQL is active.") + ); + + private static final Map IMPACT_BY_KEY = Map.ofEntries( + Map.entry("enabled", "Set to false to disable behavior without uninstalling files."), + Map.entry("permanent", "True removes the normal learn/unlearn flow and treats it as always learned."), + Map.entry("baseCost", "Higher values make each level cost more knowledge."), + Map.entry("initialCost", "Higher values make unlocking the first level more expensive."), + Map.entry("costFactor", "Higher values increase level-to-level cost growth."), + Map.entry("maxLevel", "Higher values allow more levels; lower values cap progression sooner."), + Map.entry("setInterval", "Lower values run logic more often; higher values run it less often."), + Map.entry("minXp", "Higher values delay when this skill starts applying."), + Map.entry("metrics", "Set to false to opt out of bStats telemetry."), + Map.entry("xpInCreative", "Set to true if you want creative/spectator players to gain xp."), + Map.entry("allowAdaptationsInCreative", "Set to true to let creative players trigger adaptations."), + Map.entry("experienceMaxLevel", "Higher values raise the hard cap for skill leveling."), + Map.entry("customModels", "Set to false to disable all custom model assignments."), + Map.entry("advancements", "Set to false to disable advancement creation and toast notifications."), + Map.entry("useSql", "Switching this changes where player data is loaded/saved."), + Map.entry("useRedis", "Requires SQL support and Redis credentials to synchronize across servers.") + ); + private static final Set ALWAYS_VISIBLE_KEYS = Set.of( + "enabled", + "permanent", + "baseCost", + "initialCost", + "costFactor", + "maxLevel", + "minXp", + "showParticles", + "showSounds" + ); + + private ConfigDocumentation() { + } + + public static List buildFieldComments(String sourceTag, String path, Field field, Object value) { + List lines = new ArrayList<>(); + ConfigDoc annotation = field.getAnnotation(ConfigDoc.class); + String key = field.getName(); + String summary; + String impact; + + if (annotation != null) { + summary = annotation.value().strip(); + impact = annotation.impact().strip(); + if (isGenericSummary(summary)) { + summary = defaultSummary(sourceTag, path, field); + } + if (impact.isBlank() || isGenericImpact(impact)) { + impact = defaultImpact(field, value); + } + } else { + summary = SUMMARY_BY_KEY.getOrDefault(key, defaultSummary(sourceTag, path, field)); + impact = IMPACT_BY_KEY.getOrDefault(key, defaultImpact(field, value)); + } + + if (summary != null && !summary.isBlank()) { + lines.add(summary); + } + if (!impact.isBlank()) { + lines.add("Effect: " + impact); + } + return lines; + } + + public static boolean shouldExposeField(String sourceTag, String path, Field field, Object value) { + if (field == null) { + return false; + } + if (field.getAnnotation(ConfigAdvanced.class) != null) { + return false; + } + + String key = field.getName(); + if (ALWAYS_VISIBLE_KEYS.contains(key)) { + return true; + } + + String lowered = key.toLowerCase(Locale.ROOT); + Class type = field.getType(); + boolean isBoolean = type == boolean.class || type == Boolean.class; + + // Hide challenge reward tuning; these are rarely gameplay-critical knobs. + if (lowered.startsWith("challenge") && lowered.contains("reward")) { + return false; + } + + // Internal update cadence knobs are advanced and should stay out of default configs. + if (lowered.equals("setinterval") || lowered.equals("statintervalms")) { + return false; + } + + // Hide over-granular audiovisual tuning by default. + if (lowered.contains("pitch") || lowered.contains("volume")) { + return false; + } + if (lowered.contains("sound") && !isBoolean) { + return false; + } + if (lowered.contains("particlesize") || lowered.contains("particlecount") || lowered.contains("particleevery")) { + return false; + } + if (lowered.contains("xoffset") || lowered.contains("yoffset") || lowered.contains("zoffset")) { + return false; + } + + // Hide fallback/anti-edge tuning that is mostly diagnostic. + if (lowered.contains("fallback") || lowered.contains("variance") || lowered.contains("curveexponent")) { + return false; + } + + return true; + } + + public static List buildSectionComments(String sourceTag, String path) { + if (path == null || path.isBlank()) { + return List.of(); + } + + String leaf = path; + int idx = leaf.lastIndexOf('.'); + if (idx >= 0 && idx + 1 < leaf.length()) { + leaf = leaf.substring(idx + 1); + } + + String humanLeaf = humanize(leaf); + if (sourceTag != null && sourceTag.startsWith("skill:")) { + return List.of("Settings for the " + sourceTag.substring("skill:".length()) + " skill " + humanLeaf + " section."); + } + if (sourceTag != null && sourceTag.startsWith("adaptation:")) { + return List.of("Settings for the " + sourceTag.substring("adaptation:".length()) + " adaptation " + humanLeaf + " section."); + } + + return List.of("Settings for " + humanLeaf + "."); + } + + private static String defaultSummary(String sourceTag, String path, Field field) { + String key = field.getName(); + String lower = key.toLowerCase(Locale.ROOT); + String subject = subject(sourceTag, path); + if (lower.contains("cooldown")) { + return "Cooldown between " + subject + " activations."; + } + if (lower.contains("chance")) { + return "Chance for " + subject + " to trigger."; + } + if (lower.contains("xp")) { + return "XP gain tuning for " + subject + "."; + } + if (lower.contains("multiplier") || lower.contains("factor") || lower.contains("scalar")) { + return "Scaling applied to " + subject + "."; + } + if (lower.contains("duration") || lower.contains("ticks") || lower.contains("millis") || lower.endsWith("ms")) { + return "Duration or timing used by " + subject + "."; + } + if (lower.contains("radius") || lower.contains("range") || lower.contains("distance")) { + return "Distance/area limit used by " + subject + "."; + } + if (lower.startsWith("min") || lower.contains("threshold")) { + return "Minimum threshold required for " + subject + "."; + } + if (lower.startsWith("max") || lower.contains("cap")) { + return "Maximum cap applied to " + subject + "."; + } + + String label = humanize(field.getName()); + if (sourceTag != null && sourceTag.startsWith("skill:")) { + return "Controls " + label + " for the " + sourceTag.substring("skill:".length()) + " skill."; + } + if (sourceTag != null && sourceTag.startsWith("adaptation:")) { + return "Controls " + label + " for the " + sourceTag.substring("adaptation:".length()) + " adaptation."; + } + if (path != null && !path.isBlank()) { + return "Controls " + label + " in the " + path + " section."; + } + return "Controls " + label + "."; + } + + private static String defaultImpact(Field field, Object value) { + Class type = field.getType(); + String lower = field.getName().toLowerCase(Locale.ROOT); + if (type == boolean.class || type == Boolean.class) { + return "True enables this behavior and false disables it."; + } + if (lower.contains("chance")) { + return "Use values near 0.0-1.0; higher values trigger more often."; + } + if (lower.contains("cooldown")) { + return "Higher values increase time between activations; lower values allow more frequent triggers."; + } + if (lower.contains("xp")) { + return "Higher values grant more progression; lower values slow progression."; + } + if (lower.contains("multiplier") || lower.contains("factor") || lower.contains("scalar")) { + return "Higher values scale the effect more strongly; lower values scale it down."; + } + if (lower.contains("duration") || lower.contains("ticks") || lower.contains("millis") || lower.endsWith("ms")) { + return "Higher values make the effect last longer; lower values shorten it."; + } + if (lower.contains("radius") || lower.contains("range") || lower.contains("distance")) { + return "Higher values affect a wider area; lower values keep the effect tighter."; + } + if (lower.startsWith("min") || lower.contains("threshold")) { + return "Higher values make activation stricter; lower values make it easier to trigger."; + } + if (lower.startsWith("max") || lower.contains("cap")) { + return "Higher values raise the upper limit; lower values clamp the effect sooner."; + } + if (Number.class.isAssignableFrom(type) || type.isPrimitive() && type != boolean.class && type != char.class) { + return "Higher values increase intensity or limits; lower values reduce them."; + } + if (type.isEnum()) { + return "Changing this selects a different operating mode."; + } + if (type == String.class || type == char.class || type == Character.class) { + return "Changing this alters the identifier or text used by the feature."; + } + if (value instanceof List) { + return "Add or remove entries to control which values are included."; + } + if (value instanceof Map) { + return "Edit entries to control per-key overrides for this feature."; + } + return ""; + } + + private static boolean isGenericSummary(String summary) { + if (summary == null || summary.isBlank()) { + return true; + } + + String lower = summary.toLowerCase(Locale.ROOT).trim(); + return lower.startsWith("controls ") || lower.equals("no description provided"); + } + + private static boolean isGenericImpact(String impact) { + if (impact == null || impact.isBlank()) { + return true; + } + + String lower = impact.toLowerCase(Locale.ROOT); + return lower.contains("higher values usually increase intensity, limits, or frequency; lower values reduce it.") + || lower.contains("true enables this behavior and false disables it."); + } + + private static String subject(String sourceTag, String path) { + if (sourceTag != null && sourceTag.startsWith("skill:")) { + return "the " + sourceTag.substring("skill:".length()) + " skill"; + } + if (sourceTag != null && sourceTag.startsWith("adaptation:")) { + return "the " + sourceTag.substring("adaptation:".length()) + " adaptation"; + } + if (path != null && !path.isBlank()) { + return "the " + path + " section"; + } + return "this feature"; + } + + private static String humanize(String key) { + if (key == null || key.isBlank()) { + return "this setting"; + } + + String spaced = key + .replace('_', ' ') + .replace('-', ' ') + .replaceAll("([a-z])([A-Z])", "$1 $2") + .trim(); + if (spaced.isBlank()) { + return key; + } + + String lower = spaced.toLowerCase(Locale.ROOT); + return Character.toUpperCase(lower.charAt(0)) + lower.substring(1); + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/config/ConfigFileSupport.java b/src/main/java/art/arcane/adapt/util/project/config/ConfigFileSupport.java new file mode 100644 index 000000000..b6ec19ea2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/config/ConfigFileSupport.java @@ -0,0 +1,281 @@ +package art.arcane.adapt.util.config; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.io.Json; +import art.arcane.adapt.util.project.config.ConfigRewriteReporter; +import art.arcane.volmlib.util.io.IO; +import com.google.gson.JsonElement; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicInteger; + +public final class ConfigFileSupport { + private static final long MAX_CONFIG_BYTES_DEFAULT = 2L * 1024L * 1024L; + private static final long MAX_CONFIG_BYTES_SKILL_OR_ADAPTATION = 256L * 1024L; + private static final AtomicInteger CREATED_MISSING_CONFIGS = new AtomicInteger(); + + private ConfigFileSupport() { + } + + public static T load( + File canonicalFile, + File legacyFile, + Class type, + T fallback, + boolean overwriteOnReadFailure, + String sourceTag, + String createdMessage + ) throws IOException { + long maxConfigBytes = maxConfigBytesForSourceTag(sourceTag); + boolean canonicalizeExisting = shouldCanonicalizeExisting(sourceTag, overwriteOnReadFailure); + if (canonicalFile != null && canonicalFile.exists()) { + try { + if (canonicalFile.length() > maxConfigBytes) { + throw new IOException("Config file is too large (" + canonicalFile.length() + " bytes)"); + } + String raw = IO.readAll(canonicalFile); + T loaded = deserialize(raw, canonicalFile, type); + if (loaded == null) { + throw new IOException("Config parser returned null."); + } + + if (canonicalizeExisting) { + String canonical = serialize(loaded, canonicalFile, sourceTag); + if (!normalize(canonical).equals(normalize(raw))) { + ConfigRewriteReporter.reportRewrite(canonicalFile, sourceTag, raw, canonical); + IO.writeAll(canonicalFile, canonical); + } + } + deleteLegacyFileIfMigrated(canonicalFile, legacyFile, sourceTag); + return loaded; + } catch (Throwable e) { + if (overwriteOnReadFailure) { + ConfigRewriteReporter.reportFallbackRewrite(canonicalFile, sourceTag, reason("invalid config", e)); + IO.writeAll(canonicalFile, serialize(fallback, canonicalFile, sourceTag)); + return fallback; + } + + throw new IOException("Invalid config", e); + } + } + + if (legacyFile != null && legacyFile.exists()) { + try { + if (legacyFile.length() > maxConfigBytes) { + throw new IOException("Legacy config file is too large (" + legacyFile.length() + " bytes)"); + } + String raw = IO.readAll(legacyFile); + T loaded = deserialize(raw, legacyFile, type); + if (loaded == null) { + throw new IOException("Config parser returned null."); + } + + IO.writeAll(canonicalFile, serialize(loaded, canonicalFile, sourceTag)); + Adapt.info("Migrated legacy config [" + legacyPath(legacyFile) + "] -> [" + legacyPath(canonicalFile) + "]."); + deleteLegacyFileIfMigrated(canonicalFile, legacyFile, sourceTag); + return loaded; + } catch (Throwable e) { + if (overwriteOnReadFailure) { + ConfigRewriteReporter.reportFallbackRewrite(canonicalFile, sourceTag, reason("invalid legacy config", e)); + IO.writeAll(canonicalFile, serialize(fallback, canonicalFile, sourceTag)); + return fallback; + } + + throw new IOException("Invalid legacy config", e); + } + } + + IO.writeAll(canonicalFile, serialize(fallback, canonicalFile, sourceTag)); + recordMissingConfigCreated(); + return fallback; + } + + public static void recordMissingConfigCreated() { + CREATED_MISSING_CONFIGS.incrementAndGet(); + } + + public static void flushCreatedConfigSummary() { + int created = CREATED_MISSING_CONFIGS.getAndSet(0); + if (created <= 0) { + return; + } + + Adapt.info("Created " + created + " missing config " + (created == 1 ? "entry" : "entries") + " from defaults."); + } + + public static String normalize(String text) { + if (text == null) { + return ""; + } + return text.replace("\r\n", "\n").stripTrailing(); + } + + public static File toTomlFile(File file) { + if (file == null) { + return null; + } + return replaceExtension(file, ".toml"); + } + + public static File toJsonFile(File file) { + if (file == null) { + return null; + } + return replaceExtension(file, ".json"); + } + + public static File replaceExtension(File file, String extension) { + String name = file.getName(); + int idx = name.lastIndexOf('.'); + String base = idx >= 0 ? name.substring(0, idx) : name; + return new File(file.getParentFile(), base + extension); + } + + public static boolean isTomlFile(File file) { + return file != null && file.getName().toLowerCase(Locale.ROOT).endsWith(".toml"); + } + + public static boolean isJsonFile(File file) { + if (file == null) { + return false; + } + String name = file.getName().toLowerCase(Locale.ROOT); + return name.endsWith(".json") || name.endsWith(".yml") || name.endsWith(".yaml"); + } + + public static boolean isSupportedConfigFile(File file) { + return isTomlFile(file) || isJsonFile(file); + } + + public static String configNameFromFileName(String fileName) { + if (fileName == null) { + return null; + } + String lower = fileName.toLowerCase(Locale.ROOT); + if (lower.endsWith(".toml")) { + return fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); + } + if (lower.endsWith(".json")) { + return fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); + } + if (lower.endsWith(".yml")) { + return fileName.substring(0, fileName.length() - 4).toLowerCase(Locale.ROOT); + } + if (lower.endsWith(".yaml")) { + return fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); + } + return null; + } + + public static JsonElement parseToJsonElement(String raw, File file) { + if (raw == null || raw.isBlank()) { + return null; + } + + try { + if (isTomlFile(file)) { + return TomlCodec.toJsonElement(raw); + } + + return Json.fromJson(raw, JsonElement.class); + } catch (Throwable ignored) { + } + + try { + return TomlCodec.toJsonElement(raw); + } catch (Throwable ignored) { + return null; + } + } + + public static String serializeJsonElementToToml(JsonElement element) { + return TomlCodec.toToml(element); + } + + public static boolean deleteLegacyFileIfMigrated(File canonicalFile, File legacyFile, String sourceTag) { + if (canonicalFile == null || legacyFile == null) { + return false; + } + if (!canonicalFile.exists() || !canonicalFile.isFile() || !legacyFile.exists() || !legacyFile.isFile()) { + return false; + } + if (canonicalFile.getAbsoluteFile().equals(legacyFile.getAbsoluteFile())) { + return false; + } + + try { + boolean deleted = Files.deleteIfExists(legacyFile.toPath()); + if (deleted) { + Adapt.verbose("Deleted migrated legacy config [" + legacyPath(legacyFile) + "] for [" + (sourceTag == null ? "config" : sourceTag) + "]."); + } + return deleted; + } catch (Throwable e) { + Adapt.warn("Failed to delete migrated legacy config [" + legacyPath(legacyFile) + "]: " + e.getMessage()); + return false; + } + } + + private static T deserialize(String raw, File sourceFile, Class type) throws IOException { + try { + if (isTomlFile(sourceFile)) { + return TomlCodec.fromToml(raw, type); + } + return Json.fromJson(raw, type); + } catch (Throwable e) { + throw new IOException("Failed to parse " + sourceFile.getName(), e); + } + } + + private static String serialize(Object loaded, File targetFile, String sourceTag) { + if (isTomlFile(targetFile)) { + return TomlCodec.toToml(loaded, sourceTag); + } + return Json.toJson(loaded, true); + } + + private static long maxConfigBytesForSourceTag(String sourceTag) { + if (sourceTag != null && (sourceTag.startsWith("skill:") || sourceTag.startsWith("adaptation:"))) { + return MAX_CONFIG_BYTES_SKILL_OR_ADAPTATION; + } + return MAX_CONFIG_BYTES_DEFAULT; + } + + private static boolean shouldCanonicalizeExisting(String sourceTag, boolean overwriteOnReadFailure) { + if (sourceTag == null) { + return true; + } + + // During initial startup of skill/adaptation content we prioritize fast parse/load. + // Canonical rewrites still occur via explicit canonicalization/hotload paths. + if (overwriteOnReadFailure && (sourceTag.startsWith("skill:") || sourceTag.startsWith("adaptation:"))) { + return false; + } + + return true; + } + + private static String reason(String prefix, Throwable error) { + if (error == null || error.getMessage() == null || error.getMessage().isBlank()) { + return prefix; + } + return prefix + ": " + error.getMessage(); + } + + private static String legacyPath(File file) { + if (file == null) { + return ""; + } + try { + File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); + if (dataFolder == null) { + return file.getPath(); + } + return dataFolder.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '/'); + } catch (Throwable ignored) { + return file.getPath(); + } + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/config/ConfigMigrationManager.java b/src/main/java/art/arcane/adapt/util/project/config/ConfigMigrationManager.java new file mode 100644 index 000000000..2c58d36d2 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/config/ConfigMigrationManager.java @@ -0,0 +1,216 @@ +package art.arcane.adapt.util.config; + +import art.arcane.adapt.Adapt; +import art.arcane.volmlib.util.io.IO; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public final class ConfigMigrationManager { + private static final Object LOCK = new Object(); + private static final DateTimeFormatter TS = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"); + private static volatile boolean backupAttempted = false; + + private ConfigMigrationManager() { + } + + public static void backupLegacyJsonConfigsOnce() { + synchronized (LOCK) { + if (backupAttempted) { + return; + } + backupAttempted = true; + + File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); + if (dataFolder == null || !dataFolder.exists()) { + return; + } + + File marker = Adapt.instance.getDataFile("adapt", "migrations", ".legacy-json-backed-up"); + if (marker.exists()) { + return; + } + + List legacyJson = collectLegacyJsonFiles(dataFolder); + if (legacyJson.isEmpty()) { + return; + } + + boolean migrationNeeded = legacyJson.stream() + .map(ConfigFileSupport::toTomlFile) + .anyMatch(file -> file != null && !file.exists()); + if (!migrationNeeded) { + return; + } + + File backupDir = Adapt.instance.getDataFolder("adapt", "migrations", "backups"); + String timestamp = LocalDateTime.now().format(TS); + File zip = new File(backupDir, timestamp + "-pre-toml-migration.zip"); + + try { + zipLegacyFiles(dataFolder, legacyJson, zip); + IO.writeAll(marker, "backup=" + zip.getName() + "\ncreated=" + timestamp + "\n"); + Adapt.warn("Created legacy config backup before TOML migration: " + zip.getPath()); + } catch (Throwable e) { + Adapt.warn("Failed to create legacy config backup zip: " + e.getMessage()); + } + } + } + + public static int deleteMigratedLegacyJsonFiles() { + synchronized (LOCK) { + File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); + if (dataFolder == null || !dataFolder.exists()) { + return 0; + } + + int deleted = 0; + for (File legacyJson : collectLegacyJsonFiles(dataFolder)) { + if (legacyJson == null || !legacyJson.exists() || !legacyJson.isFile()) { + continue; + } + + File canonicalToml = ConfigFileSupport.toTomlFile(legacyJson); + if (canonicalToml == null || !canonicalToml.exists() || !canonicalToml.isFile()) { + continue; + } + + try { + if (Files.deleteIfExists(legacyJson.toPath())) { + deleted++; + } + } catch (Throwable e) { + Adapt.warn("Failed to delete migrated legacy config [" + legacyPath(dataFolder, legacyJson) + "]: " + e.getMessage()); + } + } + + return deleted; + } + } + + public static boolean hasLegacySkillOrAdaptationJsonFiles() { + synchronized (LOCK) { + File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); + if (dataFolder == null || !dataFolder.exists()) { + return false; + } + + File adaptRoot = new File(dataFolder, "adapt"); + return hasAnyJson(new File(adaptRoot, "skills")) || hasAnyJson(new File(adaptRoot, "adaptations")); + } + } + + private static List collectLegacyJsonFiles(File dataFolder) { + List files = new ArrayList<>(); + addScopedJsonFiles(new File(dataFolder, "adapt"), files); + addScopedJsonFiles(new File(dataFolder, "languages"), files); + return files; + } + + private static void addScopedJsonFiles(File root, List out) { + if (root == null || !root.exists() || !root.isDirectory()) { + return; + } + + ArrayDeque queue = new ArrayDeque<>(); + queue.add(root); + while (!queue.isEmpty()) { + File next = queue.removeFirst(); + File[] children = next.listFiles(); + if (children == null || children.length == 0) { + continue; + } + + for (File child : children) { + if (child == null) { + continue; + } + if (child.isDirectory()) { + queue.add(child); + continue; + } + + if (child.getName().toLowerCase(Locale.ROOT).endsWith(".json")) { + out.add(child); + } + } + } + } + + private static boolean hasAnyJson(File root) { + if (root == null || !root.exists() || !root.isDirectory()) { + return false; + } + + ArrayDeque queue = new ArrayDeque<>(); + queue.add(root); + while (!queue.isEmpty()) { + File next = queue.removeFirst(); + File[] children = next.listFiles(); + if (children == null || children.length == 0) { + continue; + } + + for (File child : children) { + if (child == null) { + continue; + } + if (child.isDirectory()) { + queue.add(child); + continue; + } + if (child.getName().toLowerCase(Locale.ROOT).endsWith(".json")) { + return true; + } + } + } + + return false; + } + + private static void zipLegacyFiles(File dataFolder, List files, File zipFile) throws Exception { + zipFile.getParentFile().mkdirs(); + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile), StandardCharsets.UTF_8)) { + byte[] buffer = new byte[8192]; + for (File file : files) { + if (file == null || !file.exists() || !file.isFile()) { + continue; + } + + String relative = dataFolder.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '/'); + out.putNextEntry(new ZipEntry(relative)); + try (FileInputStream in = new FileInputStream(file)) { + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + } + out.closeEntry(); + } + } + } + + private static String legacyPath(File dataFolder, File file) { + if (file == null) { + return ""; + } + try { + if (dataFolder != null && dataFolder.exists()) { + return dataFolder.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '/'); + } + } catch (Throwable ignored) { + } + return file.getPath(); + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/config/ConfigRewriteReporter.java b/src/main/java/art/arcane/adapt/util/project/config/ConfigRewriteReporter.java new file mode 100644 index 000000000..83401a32d --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/config/ConfigRewriteReporter.java @@ -0,0 +1,220 @@ +package art.arcane.adapt.util.project.config; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.config.ConfigFileSupport; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.File; +import java.util.*; + +public class ConfigRewriteReporter { + private static final int MAX_KEYS_PER_CATEGORY = 8; + private static final String MISSING = ""; + private static final String REMOVED = ""; + + public static void reportRewrite(File file, String source, String beforeRaw, String afterRaw) { + String before = normalize(beforeRaw); + String after = normalize(afterRaw); + if (Objects.equals(before, after)) { + return; + } + + List changes = computeDiff(before, after); + String path = relativize(file); + String sourceTag = source == null || source.isBlank() ? "config" : source; + + if (changes.isEmpty()) { + Adapt.info("Canonicalized " + sourceTag + " [" + path + "] (format/order only)."); + return; + } + + int removed = 0; + int added = 0; + int changed = 0; + List removedKeys = new ArrayList<>(); + List addedKeys = new ArrayList<>(); + List changedKeys = new ArrayList<>(); + for (Change changeEntry : changes) { + if (changeEntry.type == ChangeType.REMOVED) { + removed++; + removedKeys.add(changeEntry.key); + } else if (changeEntry.type == ChangeType.ADDED) { + added++; + addedKeys.add(changeEntry.key); + } else { + changed++; + changedKeys.add(changeEntry.key); + } + } + + Adapt.warn("Canonicalized " + sourceTag + " [" + path + "] with schema changes (removed=" + removed + ", added=" + added + ", changed=" + changed + ")."); + if (!removedKeys.isEmpty()) { + Adapt.warn(" - removed keys: " + summarizeKeys(removedKeys)); + } + if (!addedKeys.isEmpty()) { + Adapt.info(" - added keys: " + summarizeKeys(addedKeys)); + } + if (!changedKeys.isEmpty()) { + Adapt.info(" - changed keys: " + summarizeKeys(changedKeys)); + } + } + + public static void reportFallbackRewrite(File file, String source, String reason) { + String path = relativize(file); + String sourceTag = source == null || source.isBlank() ? "config" : source; + String reasonText = reason == null || reason.isBlank() ? "invalid/unsupported content" : reason; + Adapt.warn("Rewrote " + sourceTag + " [" + path + "] using fallback defaults (" + reasonText + ")."); + } + + private static List computeDiff(String before, String after) { + Map left = flattenForDiff(before); + Map right = flattenForDiff(after); + Set keys = new HashSet<>(left.keySet()); + keys.addAll(right.keySet()); + + List ordered = new ArrayList<>(keys); + ordered.sort(String::compareTo); + + List out = new ArrayList<>(); + for (String key : ordered) { + boolean inLeft = left.containsKey(key); + boolean inRight = right.containsKey(key); + String oldValue = inLeft ? left.get(key) : MISSING; + String newValue = inRight ? right.get(key) : REMOVED; + if (Objects.equals(oldValue, newValue)) { + continue; + } + + ChangeType type; + if (inLeft && !inRight) { + type = ChangeType.REMOVED; + } else if (!inLeft && inRight) { + type = ChangeType.ADDED; + } else { + type = ChangeType.CHANGED; + } + + out.add(new Change(type, key)); + } + + return out; + } + + private static Map flattenForDiff(String raw) { + JsonElement element = parseStructured(raw); + if (element == null) { + Map fallback = new HashMap<>(); + if (raw != null && !raw.isBlank()) { + fallback.put("$", normalize(raw)); + } + return fallback; + } + + Map out = new HashMap<>(); + flattenJson("$", element, out); + return out; + } + + private static void flattenJson(String path, JsonElement element, Map out) { + if (element == null || element.isJsonNull()) { + out.put(path, "null"); + return; + } + + if (element.isJsonPrimitive()) { + out.put(path, element.toString()); + return; + } + + if (element.isJsonArray()) { + if (element.getAsJsonArray().size() == 0) { + out.put(path, "[]"); + return; + } + + for (int i = 0; i < element.getAsJsonArray().size(); i++) { + flattenJson(path + "[" + i + "]", element.getAsJsonArray().get(i), out); + } + return; + } + + JsonObject object = element.getAsJsonObject(); + if (object.entrySet().isEmpty()) { + out.put(path, "{}"); + return; + } + + for (Map.Entry entry : object.entrySet()) { + flattenJson(path + "." + entry.getKey(), entry.getValue(), out); + } + } + + private static JsonElement parseStructured(String raw) { + if (raw == null || raw.isBlank()) { + return null; + } + + return ConfigFileSupport.parseToJsonElement(raw, null); + } + + private static String summarizeKeys(List keys) { + if (keys == null || keys.isEmpty()) { + return "(none)"; + } + + int shown = Math.min(MAX_KEYS_PER_CATEGORY, keys.size()); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < shown; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(keys.get(i)); + } + + if (keys.size() > shown) { + sb.append(" (+").append(keys.size() - shown).append(" more)"); + } + + return sb.toString(); + } + + private static String normalize(String json) { + if (json == null) { + return null; + } + return ConfigFileSupport.normalize(json); + } + + private static String relativize(File file) { + if (file == null) { + return ""; + } + + try { + File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); + if (dataFolder == null) { + return file.getPath(); + } + return dataFolder.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '/'); + } catch (Throwable ignored) { + return file.getPath(); + } + } + + private enum ChangeType { + REMOVED, + ADDED, + CHANGED + } + + private static class Change { + private final ChangeType type; + private final String key; + + private Change(ChangeType type, String key) { + this.type = type; + this.key = key; + } + } +} diff --git a/src/main/java/com/volmit/adapt/util/Denv.java b/src/main/java/art/arcane/adapt/util/project/config/Denv.java similarity index 95% rename from src/main/java/com/volmit/adapt/util/Denv.java rename to src/main/java/art/arcane/adapt/util/project/config/Denv.java index 946730a6b..8cb747eff 100644 --- a/src/main/java/com/volmit/adapt/util/Denv.java +++ b/src/main/java/art/arcane/adapt/util/project/config/Denv.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.project.config; public class Denv { diff --git a/src/main/java/com/volmit/adapt/util/Desc.java b/src/main/java/art/arcane/adapt/util/project/config/Desc.java similarity index 95% rename from src/main/java/com/volmit/adapt/util/Desc.java rename to src/main/java/art/arcane/adapt/util/project/config/Desc.java index 2d8dc6b20..946cde605 100644 --- a/src/main/java/com/volmit/adapt/util/Desc.java +++ b/src/main/java/art/arcane/adapt/util/project/config/Desc.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.project.config; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -27,5 +27,5 @@ @Retention(RUNTIME) @Target({PARAMETER, TYPE, FIELD}) public @interface Desc { - String value(); + String value(); } diff --git a/src/main/java/com/volmit/adapt/util/Required.java b/src/main/java/art/arcane/adapt/util/project/config/Required.java similarity index 96% rename from src/main/java/com/volmit/adapt/util/Required.java rename to src/main/java/art/arcane/adapt/util/project/config/Required.java index f7b29d9f4..f4f979fd8 100644 --- a/src/main/java/com/volmit/adapt/util/Required.java +++ b/src/main/java/art/arcane/adapt/util/project/config/Required.java @@ -16,7 +16,7 @@ - along with this program. If not, see . -----------------------------------------------------------------------------*/ -package com.volmit.adapt.util; +package art.arcane.adapt.util.project.config; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/src/main/java/art/arcane/adapt/util/project/config/TomlCodec.java b/src/main/java/art/arcane/adapt/util/project/config/TomlCodec.java new file mode 100644 index 000000000..f455ec2d5 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/config/TomlCodec.java @@ -0,0 +1,468 @@ +package art.arcane.adapt.util.config; + +import art.arcane.adapt.util.common.io.Json; +import com.google.gson.JsonElement; +import com.moandjiezana.toml.Toml; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.*; + +public final class TomlCodec { + private TomlCodec() { + } + + public static T fromToml(String raw, Class type) throws IOException { + try { + Object parsed = parseToml(raw); + String json = Json.toJson(parsed, false); + return Json.fromJson(json, type); + } catch (Throwable e) { + throw new IOException("Invalid toml", e); + } + } + + public static JsonElement toJsonElement(String raw) throws IOException { + try { + Object parsed = parseToml(raw); + String json = Json.toJson(parsed, false); + return Json.fromJson(json, JsonElement.class); + } catch (Throwable e) { + throw new IOException("Invalid toml", e); + } + } + + public static String toToml(Object object, String sourceTag) { + return new ReflectiveTomlWriter(sourceTag).write(object); + } + + public static String toToml(JsonElement element) { + Object data = Json.NORMAL.fromJson(element, Object.class); + return new GenericTomlWriter().write(data); + } + + private static Object parseToml(String raw) { + Toml toml = new Toml().read(raw == null ? "" : raw); + Map map = toml.toMap(); + if (map == null) { + return new LinkedHashMap(); + } + return map; + } + + private static List getSerializableFields(Class type) { + List out = new ArrayList<>(); + collectFields(type, out); + return out; + } + + private static void collectFields(Class type, List out) { + if (type == null || type == Object.class) { + return; + } + + collectFields(type.getSuperclass(), out); + for (Field field : type.getDeclaredFields()) { + if (field.isSynthetic()) { + continue; + } + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) { + continue; + } + field.setAccessible(true); + out.add(field); + } + } + + private static Object getFieldValue(Field field, Object object) { + try { + return field.get(object); + } catch (Throwable ignored) { + return null; + } + } + + private static boolean isInlineValue(Object value) { + if (value == null) { + return true; + } + + if (value instanceof String + || value instanceof Number + || value instanceof Boolean + || value instanceof Character + || value instanceof Enum) { + return true; + } + + if (value.getClass().isArray()) { + int length = Array.getLength(value); + for (int i = 0; i < length; i++) { + Object v = Array.get(value, i); + if (!isInlineValue(v) || v instanceof Map || v instanceof Collection) { + return false; + } + } + return true; + } + + if (value instanceof Collection collection) { + for (Object item : collection) { + if (!isInlineValue(item) || item instanceof Map || item instanceof Collection) { + return false; + } + } + return true; + } + + return false; + } + + private static String formatInlineValue(Object value) { + if (value == null) { + return "\"\""; + } + + if (value instanceof String string) { + return '"' + escape(string) + '"'; + } + if (value instanceof Character c) { + return '"' + escape(String.valueOf(c)) + '"'; + } + if (value instanceof Enum enumValue) { + return '"' + escape(enumValue.name()) + '"'; + } + if (value instanceof Number || value instanceof Boolean) { + return String.valueOf(value); + } + if (value.getClass().isArray()) { + int length = Array.getLength(value); + List parts = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + parts.add(formatInlineValue(Array.get(value, i))); + } + return "[" + String.join(", ", parts) + "]"; + } + if (value instanceof Collection collection) { + List parts = new ArrayList<>(collection.size()); + for (Object item : collection) { + parts.add(formatInlineValue(item)); + } + return "[" + String.join(", ", parts) + "]"; + } + + return '"' + escape(String.valueOf(value)) + '"'; + } + + private static String renderPath(String path) { + if (path == null || path.isBlank()) { + return ""; + } + + String[] parts = path.split("\\."); + List rendered = new ArrayList<>(parts.length); + for (String part : parts) { + rendered.add(formatKey(part)); + } + return String.join(".", rendered); + } + + private static String formatKey(String key) { + if (key == null || key.isBlank()) { + return "\"\""; + } + + if (key.matches("[A-Za-z0-9_-]+")) { + return key; + } + return '"' + escape(key) + '"'; + } + + private static String joinPath(String a, String b) { + if (a == null || a.isBlank()) { + return b; + } + if (b == null || b.isBlank()) { + return a; + } + return a + "." + b; + } + + private static String escape(String input) { + return input + .replace("\\", "\\\\") + .replace("\"", "\\\"") + .replace("\n", "\\n") + .replace("\r", "\\r") + .replace("\t", "\\t"); + } + + private static String normalize(String raw) { + if (raw == null) { + return ""; + } + + String normalized = raw.replace("\r\n", "\n").stripTrailing(); + if (!normalized.isEmpty()) { + normalized += "\n"; + } + return normalized; + } + + private static final class ReflectiveTomlWriter { + private final StringBuilder out = new StringBuilder(); + private final String sourceTag; + + private ReflectiveTomlWriter(String sourceTag) { + this.sourceTag = sourceTag == null ? "config" : sourceTag; + } + + private String write(Object root) { + if (root == null) { + return ""; + } + + out.append("# Adapt configuration - ").append(sourceTag).append('\n'); + out.append("# This file is canonicalized on load; comments and new keys may update automatically.\n"); + ConfigDescription desc = root.getClass().getAnnotation(ConfigDescription.class); + if (desc != null && !desc.value().isBlank()) { + out.append("#\n"); + out.append("# ").append(desc.value().strip()).append('\n'); + } + out.append('\n'); + writePojoSection("", root); + return normalize(out.toString()); + } + + private void writePojoSection(String path, Object sectionObject) { + if (sectionObject == null) { + return; + } + + List fields = getSerializableFields(sectionObject.getClass()); + List deferred = new ArrayList<>(); + + for (Field field : fields) { + Object value = getFieldValue(field, sectionObject); + if (value == null) { + continue; + } + if (!ConfigDocumentation.shouldExposeField(sourceTag, path, field, value)) { + continue; + } + + if (isInlineValue(value)) { + writeFieldComments(path, field, value); + out.append(formatKey(field.getName())).append(" = ").append(formatInlineValue(value)).append('\n'); + } else { + deferred.add(field); + } + } + + for (Field field : deferred) { + Object value = getFieldValue(field, sectionObject); + if (value == null) { + continue; + } + if (!ConfigDocumentation.shouldExposeField(sourceTag, path, field, value)) { + continue; + } + + String childPath = joinPath(path, field.getName()); + if (value instanceof Map map) { + writeMapSection(childPath, map, field); + continue; + } + + writeSectionHeader(childPath, ConfigDocumentation.buildSectionComments(sourceTag, childPath)); + writePojoSection(childPath, value); + } + } + + private void writeMapSection(String sectionPath, Map map, Field sourceField) { + writeSectionHeader(sectionPath, ConfigDocumentation.buildFieldComments(sourceTag, sectionPath, sourceField, map)); + if (map.isEmpty()) { + return; + } + + List> deferred = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + if (entry == null || entry.getKey() == null) { + continue; + } + + Object value = entry.getValue(); + if (value == null) { + continue; + } + + if (isInlineValue(value)) { + out.append(formatKey(String.valueOf(entry.getKey()))) + .append(" = ") + .append(formatInlineValue(value)) + .append('\n'); + } else { + deferred.add(entry); + } + } + + for (Map.Entry entry : deferred) { + Object value = entry.getValue(); + if (value == null || entry.getKey() == null) { + continue; + } + + String childPath = joinPath(sectionPath, String.valueOf(entry.getKey())); + if (value instanceof Map nested) { + writeSectionHeader(childPath, List.of()); + writeMapBody(childPath, nested); + } else { + writeSectionHeader(childPath, List.of()); + writePojoSection(childPath, value); + } + } + } + + private void writeMapBody(String sectionPath, Map map) { + List> deferred = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + if (entry == null || entry.getKey() == null || entry.getValue() == null) { + continue; + } + if (isInlineValue(entry.getValue())) { + out.append(formatKey(String.valueOf(entry.getKey()))) + .append(" = ") + .append(formatInlineValue(entry.getValue())) + .append('\n'); + } else { + deferred.add(entry); + } + } + + for (Map.Entry entry : deferred) { + String childPath = joinPath(sectionPath, String.valueOf(entry.getKey())); + Object value = entry.getValue(); + if (value instanceof Map nested) { + writeSectionHeader(childPath, List.of()); + writeMapBody(childPath, nested); + } else { + writeSectionHeader(childPath, List.of()); + writePojoSection(childPath, value); + } + } + } + + private void writeFieldComments(String path, Field field, Object value) { + List comments = ConfigDocumentation.buildFieldComments(sourceTag, path, field, value); + for (String comment : comments) { + if (comment == null || comment.isBlank()) { + continue; + } + out.append("# ").append(comment.strip()).append('\n'); + } + } + + private void writeSectionHeader(String path, List comments) { + if (path == null || path.isBlank()) { + return; + } + + if (!out.isEmpty() && out.charAt(out.length() - 1) != '\n') { + out.append('\n'); + } + if (!out.isEmpty()) { + out.append('\n'); + } + + for (String comment : comments) { + if (comment == null || comment.isBlank()) { + continue; + } + out.append("# ").append(comment.strip()).append('\n'); + } + out.append('[').append(renderPath(path)).append(']').append('\n'); + } + } + + private static final class GenericTomlWriter { + private final StringBuilder out = new StringBuilder(); + private String lastTopLevelSection; + + private String write(Object root) { + if (root instanceof Map map) { + writeMapSection("", map, 0); + return normalize(out.toString()); + } + + out.append("value = ").append(formatInlineValue(root)).append('\n'); + return normalize(out.toString()); + } + + private void writeMapSection(String path, Map map, int depth) { + if (!path.isBlank()) { + writeSectionHeader(path, depth); + } + + List> deferred = new ArrayList<>(); + String valueIndent = " ".repeat(Math.max(0, depth)); + for (Map.Entry entry : map.entrySet()) { + if (entry == null || entry.getKey() == null || entry.getValue() == null) { + continue; + } + + Object value = entry.getValue(); + if (isInlineValue(value)) { + out.append(valueIndent) + .append(formatKey(String.valueOf(entry.getKey()))) + .append(" = ") + .append(formatInlineValue(value)) + .append('\n'); + } else { + deferred.add(entry); + } + } + + for (Map.Entry entry : deferred) { + String childPath = joinPath(path, String.valueOf(entry.getKey())); + Object value = entry.getValue(); + if (value instanceof Map nested) { + writeMapSection(childPath, nested, depth + 1); + } else { + Map wrapper = new LinkedHashMap<>(); + wrapper.put("value", value); + writeMapSection(childPath, wrapper, depth + 1); + } + } + } + + private void writeSectionHeader(String path, int depth) { + String topLevel = topLevelSegment(path); + if (depth == 1 && (lastTopLevelSection == null || !lastTopLevelSection.equals(topLevel))) { + if (!out.isEmpty()) { + out.append('\n'); + } + out.append("# ").append(topLevel).append('\n'); + lastTopLevelSection = topLevel; + } else if (!out.isEmpty()) { + out.append('\n'); + } + + out.append('[').append(renderPath(path)).append(']').append('\n'); + } + + private String topLevelSegment(String path) { + if (path == null || path.isBlank()) { + return ""; + } + + int dot = path.indexOf('.'); + if (dot == -1) { + return path; + } + return path.substring(0, dot); + } + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/redis/RedisSync.java b/src/main/java/art/arcane/adapt/util/project/redis/RedisSync.java new file mode 100644 index 000000000..241abb35d --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/redis/RedisSync.java @@ -0,0 +1,79 @@ +package art.arcane.adapt.util.project.redis; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.AdaptConfig; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.util.project.redis.codec.Codec; +import art.arcane.adapt.util.project.redis.codec.DataMessage; +import art.arcane.adapt.util.project.redis.codec.DataRequest; +import art.arcane.adapt.util.project.redis.codec.Message; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import io.lettuce.core.RedisClient; +import io.lettuce.core.pubsub.api.reactive.ChannelMessage; +import io.lettuce.core.pubsub.api.reactive.RedisPubSubReactiveCommands; +import lombok.NonNull; +import lombok.extern.java.Log; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Log +public class RedisSync implements AutoCloseable { + private final RedisClient redisClient; + private final RedisPubSubReactiveCommands pubSub; + private final Cache dataCache = Caffeine.newBuilder() + .expireAfterWrite(1, TimeUnit.MINUTES) + .build(); + + public RedisSync() { + if (!AdaptConfig.get().isUseRedis() || !AdaptConfig.get().isUseSql()) { + this.redisClient = null; + this.pubSub = null; + return; + } + + this.redisClient = AdaptConfig.get().getRedis().createClient(); + this.pubSub = redisClient.connectPubSub(Codec.INSTANCE).reactive(); + pubSub.subscribe("Adapt:data").subscribe(); + pubSub.observeChannels().doOnNext(this::update).subscribe(); + } + + private void update(@NotNull ChannelMessage<@NotNull String, @Nullable Message> channelMessage) { + if (!channelMessage.getChannel().equals("Adapt:data")) return; + Message raw = channelMessage.getMessage(); + if (raw instanceof DataMessage message) { + Adapt.verbose("Received player data for " + message.uuid()); + dataCache.put(message.uuid(), message.json()); + } else if (raw instanceof DataRequest message) { + Adapt.instance.getAdaptServer() + .getPlayerData(message.uuid()) + .map(data -> data.toJson(false)) + .ifPresent(data -> publish(message.uuid(), data)); + } + } + + public void publish(@NonNull UUID uuid, @NonNull String playerData) { + if (pubSub == null) return; + Adapt.verbose("Publishing player data for " + uuid); + pubSub.publish("Adapt:data", new DataMessage(uuid, playerData)) + .subscribe() + .dispose(); + } + + @NonNull + public Optional cachedData(@NonNull UUID uuid) { + if (pubSub == null) return Optional.empty(); + return Optional.ofNullable(dataCache.getIfPresent(uuid)) + .map(PlayerData::fromJson); + } + + @Override + public void close() throws Exception { + if (redisClient != null) + redisClient.close(); + } +} diff --git a/src/main/java/art/arcane/adapt/util/project/secret/SecretSplash.java b/src/main/java/art/arcane/adapt/util/project/secret/SecretSplash.java new file mode 100644 index 000000000..85a820cd7 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/project/secret/SecretSplash.java @@ -0,0 +1,88 @@ +/*------------------------------------------------------------------------------ + - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers + - Copyright (c) 2022 Arcane Arts (Volmit Software) + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU General Public License as published by + - the Free Software Foundation, either version 3 of the License, or + - (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU General Public License for more details. + - + - You should have received a copy of the GNU General Public License + - along with this program. If not, see . + -----------------------------------------------------------------------------*/ + +package art.arcane.adapt.util.secret; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.common.format.C; +import art.arcane.volmlib.util.collection.KList; +import lombok.Getter; + +import java.nio.charset.StandardCharsets; +import java.util.Random; + +public class SecretSplash { + + @Getter + public static KList secretSplash = new KList<>( + "\n" + C.BLUE + " ⣞⢽⢪⢣⢣⢣⢫⡺⡵⣝⡮⣗⢷⢽⢽⢽⣮⡷⡽⣜⣜⢮⢺⣜⢷⢽⢝⡽⣝ \n" + + C.BLUE + " ⠸⡸⠜⠕⠕⠁⢁⢇⢏⢽⢺⣪⡳⡝⣎⣏⢯⢞⡿⣟⣷⣳⢯⡷⣽⢽⢯⣳⣫⠇ \n" + + C.BLUE + " ⢀⢀⢄⢬⢪⡪⡎⣆⡈⠚⠜⠕⠇⠗⠝⢕⢯⢫⣞⣯⣿⣻⡽⣏⢗⣗⠏⠀ " + C.DARK_RED + "Adapt\n" + + C.BLUE + " ⠪⡪⡪⣪⢪⢺⢸⢢⢓⢆⢤⢀⠀⠀⠀⠀⠈⢊⢞⡾⣿⡯⣏⢮⠷⠁⠀⠀ " + C.GRAY + "Version: " + C.DARK_RED + Adapt.instance.getDescription().getVersion() + "\n" + + C.BLUE + " ⠈⠊⠆⡃⠕⢕⢇⢇⢇⢇⢇⢏⢎⢎⢆⢄⠀⢑⣽⣿⢝⠲⠉⠀⠀⠀⠀ " + C.GRAY + "By: " + C.WHITE + "Volmit Software (Arcane Arts)\n" + + C.BLUE + " ⡿⠂⠠⠀⡇⢇⠕⢈⣀⠀⠁⠡⠣⡣⡫⣂⣿⠯⢪⠰⠂⠀⠀⠀⠀ " + C.GRAY + "Java Version: " + C.DARK_RED + Adapt.getJavaVersion() + "\n" + + C.BLUE + " ⡦⡙⡂⢀⢤⢣⠣⡈⣾⡃⠠⠄⠀⡄⢱⣌⣶⢏⢊⠂⠀⠀⠀⠀⠀ ⠀\n" + + C.BLUE + " ⢝⡲⣜⡮⡏⢎⢌⢂⠙⠢⠐⢀⢘⢵⣽⣿⡿⠁⠁⠀⠀⠀⠀ ⠀⠀⠀\n" + + C.BLUE + " ⠨⣺⡺⡕⡕⡱⡑⡆⡕⡅⡕⡜⡼⢽⡻⠏⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀\n" + + C.BLUE + " ⣼⣳⣫⣾⣵⣗⡵⡱⡡⢣⢑⢕⢜⢕⡝⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀\n" + + C.BLUE + " ⣴⣿⣾⣿⣿⣿⡿⡽⡑⢌⠪⡢⡣⣣⡟⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀No Splash Screen?\n" + + C.BLUE + " ⡟⡾⣿⢿⢿⢵⣽⣾⣼⣘⢸⢸⣞⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀\n" + + C.BLUE + " ⠁⠇⠡⠩⡫⢿⣝⡻⡮⣒⢽⠋⠀⠀⠀⠀", + + "\n :::. :::::::-. :::. ::::::::::. :::::::::::: \n" + + " ;;`;; ;;, `';, ;;`;; `;;;```.;;;;;;;;;;;'''' " + C.GRAY + "Version: " + C.DARK_RED + Adapt.instance.getDescription().getVersion() + "\n" + + " ,[[ '[[, `[[ [[ ,[[ '[[, `]]nnn]]' [[ " + C.GRAY + "By: " + C.WHITE + "Volmit Software (Arcane Arts)\n" + + " $$$$$$$$ $$, $$ $$$$$$$$ $$$\"\" $$ " + C.GRAY + "Java Version: " + C.DARK_RED + Adapt.getJavaVersion() + "\n" + + " 888 888,888_,o8P' 888 888,888o 88, \n" + + " YMM \"\"` MMMMP\"` YMM \"\"` YMMMb MMM \n", + + C.GRAY + "\n ██░ ██ ▓█████ ██▓ ██▓███ ███▄ ▄███▓▓█████ \n" + + C.GRAY + "▓██░ ██▒▓█ ▀ ▓██▒ ▓██░ ██▒ ▓██▒▀█▀ ██▒▓█ ▀ " + C.DARK_RED + "Adapt \n" + + C.GRAY + "▒██▀▀██░▒███ ▒██░ ▓██░ ██▓▒ ▓██ ▓██░▒███ " + C.GRAY + "Version: " + C.DARK_RED + Adapt.instance.getDescription().getVersion() + " \n" + + C.GRAY + "░▓█ ░██ ▒▓█ ▄ ▒██░ ▒██▄█▓▒ ▒ ▒██ ▒██ ▒▓█ ▄ " + C.GRAY + "By: " + C.WHITE + "Volmit Software (Arcane Arts)\n" + + C.GRAY + "░▓█▒░██▓░▒████▒░██████▒▒██▒ ░ ░ ▒██▒ ░██▒░▒████▒ " + C.GRAY + "Java Version: " + C.DARK_RED + Adapt.getJavaVersion() + " \n" + + C.GRAY + " ▒ ░░▒░▒░░ ▒░ ░░ ▒░▓ ░▒▓▒░ ░ ░ ░ ▒░ ░ ░░░ ▒░ ░ ", + + C.GRAY + "⠀⠀\n" + + C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣶⣿⣿⣷⣶⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n" + + C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣾⣿⣿⡿⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀ ⠀\n" + + C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⡟⠁⣰⣿⣿⣿⡿⠿⠻⠿⣿⣿⣿⣿⣧⠀ ⠀⠀⠀\n" + + C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⠏⠀⣴⣿⣿⣿⠉⠀⠀⠀⠀⠀⠈⢻⣿⣿⣇⠀⠀⠀ \n" + + C.GRAY + "⠀⠀⠀⠀⢀⣠⣼⣿⣿⡏⠀⢠⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⡀⠀ ⠀" + C.DARK_RED + "Adapt \n" + + C.GRAY + "⠀⠀⠀⣰⣿⣿⣿⣿⣿⡇⠀⢸⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⡇⠀ ⠀" + C.GRAY + "Version: " + C.DARK_RED + Adapt.instance.getDescription().getVersion() + " \n" + + C.GRAY + "⠀⠀⢰⣿⣿⡿⣿⣿⣿⡇⠀⠘⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⢀⣸⣿⣿⣿⠁⠀ ⠀" + C.GRAY + "By: " + C.WHITE + "Volmit Software (Arcane Arts)\n" + + C.GRAY + "⠀⠀⣿⣿⣿⠁⣿⣿⣿⡇⠀⠀⠻⣿⣿⣿⣷⣶⣶⣶⣶⣶⣿⣿⣿⣿⠃⠀ ⠀⠀" + C.GRAY + "Java Version: " + C.DARK_RED + Adapt.getJavaVersion() + " \n" + + C.GRAY + "⠀⢰⣿⣿⡇⠀⣿⣿⣿⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀ ⠀\n" + + C.GRAY + "⠀⢸⣿⣿⡇⠀⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠉⠛⠛⠛⠉⢉⣿⣿⠀⠀⠀⠀⠀ ⠀\n" + + C.GRAY + "⠀⢸⣿⣿⣇⠀⣿⣿⣿⠀⠀⠀⠀⠀⢀⣤⣤⣤⡀⠀⠀⢸⣿⣿⣿⣷⣦⠀ ⠀⠀\n" + + C.GRAY + "⠀⠀⢻⣿⣿⣶⣿⣿⣿⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣦⡀⠀⠉⠉⠻⣿⣿⡇⠀ ⠀ \n" + + C.GRAY + "⠀⠀⠀⠛⠿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠈⠹⣿⣿⣇⣀⠀⣠⣾⣿⣿⡇⠀⠀ ⠀⠀" + C.DARK_RED + "sus\n " + + C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣦⣤⣤⣤⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀ ⠀\n" + + C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⢿⣿⣿⣿⣿⣿⣿⠿⠋⠉⠛⠋⠉⠉⠁⠀⠀⠀ ⠀\n" + + C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠁" + + ); + + public static String randomString7() { + byte[] array = new byte[7]; // length is bounded by 7 + new Random().nextBytes(array); + String generatedString = new String(array, StandardCharsets.UTF_8); + + return generatedString; + } +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/Reflect.java b/src/main/java/art/arcane/adapt/util/reflect/Reflect.java new file mode 100644 index 000000000..b8a33aaa0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/Reflect.java @@ -0,0 +1,73 @@ +package art.arcane.adapt.util.reflect; + +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Optional; + +public class Reflect { + + @NotNull + public static Optional> getClass(@NotNull String className) { + try { + return Optional.of(Class.forName(className)); + } catch (ClassNotFoundException e) { + return Optional.empty(); + } + } + + @NotNull + public static > Optional getEnum(@NotNull Class enumClass, @NotNull String enumName) { + try { + return Optional.of(Enum.valueOf(enumClass, enumName)); + } catch (IllegalArgumentException e) { + return Optional.empty(); + } + } + + @NotNull + public static > E getEnum(@NotNull Class enumClass, @NotNull String... enumNames) { + if (enumNames.length == 0) + throw new IllegalArgumentException("Need at least one enum name"); + for (String enumName : enumNames) { + Optional optionalEnum = getEnum(enumClass, enumName); + if (optionalEnum.isPresent()) return optionalEnum.get(); + } + throw new IllegalArgumentException("No Enum found for names " + Arrays.toString(enumNames)); + } + + @NotNull + public static Optional getMethod(@NotNull Class clazz, @NotNull String methodName, @NotNull Class... parameterTypes) { + try { + return Optional.of(clazz.getDeclaredMethod(methodName, parameterTypes)); + } catch (NoSuchMethodException e) { + return Optional.empty(); + } + } + + public static E getField(Class clazz, @NotNull String... fieldNames) { + if (fieldNames.length == 0) + throw new IllegalArgumentException("Need at least one field name"); + for (String fieldName : fieldNames) { + Optional optionalField = getField(clazz, fieldName); + if (optionalField.isPresent()) { + try { + return (E) optionalField.get().get(null); + } catch (IllegalAccessException ignored) { + } + } + } + throw new IllegalArgumentException("No Field found for names " + Arrays.toString(fieldNames)); + } + + @NotNull + public static Optional getField(@NotNull Class clazz, @NotNull String fieldName) { + try { + return Optional.of(clazz.getDeclaredField(fieldName)); + } catch (NoSuchFieldException e) { + return Optional.empty(); + } + } +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/WrappedField.java b/src/main/java/art/arcane/adapt/util/reflect/WrappedField.java new file mode 100644 index 000000000..8b7273d74 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/WrappedField.java @@ -0,0 +1,42 @@ +package art.arcane.adapt.util.reflect; + +import art.arcane.adapt.Adapt; + +import java.lang.reflect.Field; + +public class WrappedField { + + private final Field field; + + public WrappedField(Class origin, String methodName) { + Field f = null; + try { + f = origin.getDeclaredField(methodName); + f.setAccessible(true); + } catch (NoSuchFieldException e) { + Adapt.error("Failed to created WrappedField %s#%s: %s%s".formatted(origin.getSimpleName(), methodName, e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage())); + } + this.field = f; + } + + public T get() { + return get(null); + } + + public T get(C instance) { + if (field == null) { + return null; + } + + try { + return (T) field.get(instance); + } catch (IllegalAccessException e) { + Adapt.error("Failed to get WrappedField %s#%s: %s%s".formatted(field.getDeclaringClass().getSimpleName(), field.getName(), e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage())); + return null; + } + } + + public boolean hasFailed() { + return field == null; + } +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/WrappedReturningMethod.java b/src/main/java/art/arcane/adapt/util/reflect/WrappedReturningMethod.java new file mode 100644 index 000000000..64850db82 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/WrappedReturningMethod.java @@ -0,0 +1,40 @@ +package art.arcane.adapt.util.reflect; + + +import art.arcane.adapt.Adapt; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public final class WrappedReturningMethod { + + private final Method method; + + public WrappedReturningMethod(Class origin, String methodName, Class... paramTypes) { + Method m = null; + try { + m = origin.getDeclaredMethod(methodName, paramTypes); + m.setAccessible(true); + } catch (NoSuchMethodException e) { + Adapt.error("Failed to created WrappedMethod %s#%s: %s%s".formatted(origin.getSimpleName(), methodName, e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage())); + } + this.method = m; + } + + public R invoke(Object... args) { + return invoke(null, args); + } + + public R invoke(C instance, Object... args) { + if (method == null) { + return null; + } + + try { + return (R) method.invoke(instance, args); + } catch (InvocationTargetException | IllegalAccessException e) { + Adapt.error("Failed to invoke WrappedMethod %s#%s: %s%s".formatted(method.getDeclaringClass().getSimpleName(), method.getName(), e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage())); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/art/arcane/adapt/util/reflect/events/ReflectiveEvents.java b/src/main/java/art/arcane/adapt/util/reflect/events/ReflectiveEvents.java new file mode 100644 index 000000000..33f65b84e --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/events/ReflectiveEvents.java @@ -0,0 +1,122 @@ +package art.arcane.adapt.util.reflect.events; + +import art.arcane.adapt.Adapt; +import art.arcane.adapt.util.reflect.Reflect; +import art.arcane.adapt.util.reflect.events.api.Event; +import art.arcane.adapt.util.reflect.events.api.ReflectiveHandler; +import art.arcane.adapt.util.reflect.events.api.entity.EndermanAttackPlayerEvent; +import art.arcane.adapt.util.reflect.events.api.entity.EntityDismountEvent; +import art.arcane.adapt.util.reflect.events.api.entity.EntityMountEvent; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.collection.KMap; +import lombok.NonNull; +import org.bukkit.event.EventException; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.bukkit.plugin.RegisteredListener; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Collectors; + +public class ReflectiveEvents { + private static final KMap, Class> EVENTS = new KMap<>(); + private static final KMap, HandlerList> HANDLERS = new KMap<>(); + + static { + register(EntityMountEvent.class, "org.bukkit.event.entity.EntityMountEvent", "org.spigotmc.event.entity.EntityMountEvent"); + register(EntityDismountEvent.class, "org.bukkit.event.entity.EntityDismountEvent", "org.spigotmc.event.entity.EntityDismountEvent"); + register(EndermanAttackPlayerEvent.class, "com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent"); + } + + public static void register(@NonNull Listener listener) { + if (Adapt.bad) return; + + Arrays.stream(listener.getClass().getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(ReflectiveHandler.class)) + .filter(method -> !Modifier.isStatic(method.getModifiers())) + .filter(method -> method.getParameterCount() == 1) + .filter(method -> Event.class.isAssignableFrom(method.getParameterTypes()[0])) + .collect(Collectors.toMap(method -> HANDLERS.get(method.getParameterTypes()[0]), + method -> { + try { + if (!Modifier.isPublic(method.getModifiers())) { + method.setAccessible(true); + } + } catch (Throwable e) { + return new KList(); + } + + + Class eventClass = method.getParameterTypes()[0]; + Class bukkitClass = EVENTS.get(eventClass); + ReflectiveHandler handler = method.getAnnotation(ReflectiveHandler.class); + + EventExecutor executor = (obj, event) -> { + if (!bukkitClass.isAssignableFrom(event.getClass())) + return; + + try { + method.invoke(obj, newProxy(obj, eventClass)); + } catch (InvocationTargetException e) { + throw new EventException(e.getCause()); + } catch (Throwable e) { + throw new EventException(e); + } + }; + + return new KList<>(new RegisteredListener(listener, executor, handler.priority(), Adapt.instance, handler.ignoreCancelled())); + }, KList::add)) + .forEach((handlerList, registeredListeners) -> { + if (handlerList == null) return; + handlerList.registerAll(registeredListeners); + }); + } + + public static void register(Class eventInterface, String... classes) { + for (String clazz : classes) { + Optional> opt = Reflect.getClass(clazz); + if (opt.isEmpty()) + continue; + + org.bukkit.event.HandlerList handlerList = getHandlerList(opt.get()); + if (handlerList == null) { + Adapt.warn("Event class does not contain HandlerList: " + clazz); + continue; + } + + EVENTS.put(eventInterface, opt.get()); + HANDLERS.put(opt.get(), handlerList); + return; + } + } + + public static boolean exists(@NonNull Class eventInterface) { + return EVENTS.containsKey(eventInterface); + } + + @Nullable + private static HandlerList getHandlerList(Class parent) { + while (parent != null) { + if (!org.bukkit.event.Event.class.isAssignableFrom(parent)) + return null; + + try { + java.lang.reflect.Method method = parent.getDeclaredMethod("getHandlerList"); + return (HandlerList) method.invoke(null); + } catch (Throwable e) { + parent = parent.getSuperclass(); + } + } + return null; + } + + private static Object newProxy(Object o, Class... interfaces) { + return Proxy.newProxyInstance(Event.class.getClassLoader(), interfaces, (proxy, method, args) -> method.invoke(o, args)); + } +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/events/api/Event.java b/src/main/java/art/arcane/adapt/util/reflect/events/api/Event.java new file mode 100644 index 000000000..45fac8cf4 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/events/api/Event.java @@ -0,0 +1,5 @@ +package art.arcane.adapt.util.reflect.events.api; + +public interface Event { + boolean isAsynchronous(); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/events/api/ReflectiveHandler.java b/src/main/java/art/arcane/adapt/util/reflect/events/api/ReflectiveHandler.java new file mode 100644 index 000000000..dc2fe5e8c --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/events/api/ReflectiveHandler.java @@ -0,0 +1,16 @@ +package art.arcane.adapt.util.reflect.events.api; + +import org.bukkit.event.EventPriority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ReflectiveHandler { + EventPriority priority() default EventPriority.NORMAL; + + boolean ignoreCancelled() default false; +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EndermanAttackPlayerEvent.java b/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EndermanAttackPlayerEvent.java new file mode 100644 index 000000000..25af31096 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EndermanAttackPlayerEvent.java @@ -0,0 +1,14 @@ +package art.arcane.adapt.util.reflect.events.api.entity; + +import org.bukkit.entity.Enderman; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +public interface EndermanAttackPlayerEvent extends EntityEvent, Cancellable { + @NotNull + Enderman getEntity(); + + @NotNull + Player getPlayer(); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityDismountEvent.java b/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityDismountEvent.java new file mode 100644 index 000000000..e8831017a --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityDismountEvent.java @@ -0,0 +1,10 @@ +package art.arcane.adapt.util.reflect.events.api.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +public interface EntityDismountEvent extends EntityEvent, Cancellable { + @NotNull + Entity getDismounted(); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityEvent.java b/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityEvent.java new file mode 100644 index 000000000..27e89455e --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityEvent.java @@ -0,0 +1,11 @@ +package art.arcane.adapt.util.reflect.events.api.entity; + +import art.arcane.adapt.util.reflect.events.api.Event; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +public interface EntityEvent extends Event { + Entity getEntity(); + + EntityType getType(); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityMountEvent.java b/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityMountEvent.java new file mode 100644 index 000000000..a784bfb9d --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/events/api/entity/EntityMountEvent.java @@ -0,0 +1,10 @@ +package art.arcane.adapt.util.reflect.events.api.entity; + +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.jetbrains.annotations.NotNull; + +public interface EntityMountEvent extends EntityEvent, Cancellable { + @NotNull + Entity getMount(); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/Attributes.java b/src/main/java/art/arcane/adapt/util/reflect/registries/Attributes.java new file mode 100644 index 000000000..8b7d0428f --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/Attributes.java @@ -0,0 +1,10 @@ +package art.arcane.adapt.util.reflect.registries; + +import org.bukkit.attribute.Attribute; + +public class Attributes { + public static final Attribute GENERIC_ARMOR = RegistryUtil.find(Attribute.class, "generic_armor", "armor"); + public static final Attribute GENERIC_ATTACK_DAMAGE = RegistryUtil.find(Attribute.class, "generic_attack_damage", "attack_damage"); + public static final Attribute GENERIC_MAX_HEALTH = RegistryUtil.find(Attribute.class, "generic_max_health", "max_health"); + public static final Attribute GENERIC_MOVEMENT_SPEED = RegistryUtil.find(Attribute.class, "generic_movement_speed", "movement_speed"); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/Enchantments.java b/src/main/java/art/arcane/adapt/util/reflect/registries/Enchantments.java new file mode 100644 index 000000000..598c3d4d0 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/Enchantments.java @@ -0,0 +1,9 @@ +package art.arcane.adapt.util.reflect.registries; + +import org.bukkit.enchantments.Enchantment; + +public class Enchantments { + public static final Enchantment DURABILITY = RegistryUtil.find(Enchantment.class, "unbreaking", "durability"); + public static final Enchantment ARROW_INFINITE = RegistryUtil.find(Enchantment.class, "infinity", "arrow_infinite"); + public static final Enchantment LOOT_BONUS_BLOCKS = RegistryUtil.find(Enchantment.class, "fortune", "loot_bonus_blocks"); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/EntityTypes.java b/src/main/java/art/arcane/adapt/util/reflect/registries/EntityTypes.java new file mode 100644 index 000000000..6af6540ed --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/EntityTypes.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.reflect.registries; + +import org.bukkit.entity.EntityType; + +public class EntityTypes { + public static final EntityType ENDER_CRYSTAL = RegistryUtil.find(EntityType.class, "ender_crystal", "end_crystal"); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/ItemFlags.java b/src/main/java/art/arcane/adapt/util/reflect/registries/ItemFlags.java new file mode 100644 index 000000000..6144eede9 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/ItemFlags.java @@ -0,0 +1,7 @@ +package art.arcane.adapt.util.reflect.registries; + +import org.bukkit.inventory.ItemFlag; + +public class ItemFlags { + public static final ItemFlag HIDE_POTION_EFFECTS = RegistryUtil.find(ItemFlag.class, "hide_potion_effects", "hide_additional_tooltip"); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/Materials.java b/src/main/java/art/arcane/adapt/util/reflect/registries/Materials.java new file mode 100644 index 000000000..962415311 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/Materials.java @@ -0,0 +1,92 @@ +package art.arcane.adapt.util.reflect.registries; + +import org.bukkit.Material; + +public class Materials { + public static final Material GRASS = RegistryUtil.find(Material.class, "grass", "short_grass"); + + /* Added Materials */ + public static final Material MACE = RegistryUtil.findNullable(Material.class, "mace"); + //1.20+ + public static final Material OAK_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "oak_hanging_sign"); + public static final Material OAK_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "oak_wall_hanging_sign"); + public static final Material SPRUCE_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "spruce_hanging_sign"); + public static final Material SPRUCE_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "spruce_wall_hanging_sign"); + public static final Material BIRCH_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "birch_hanging_sign"); + public static final Material BIRCH_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "birch_wall_hanging_sign"); + public static final Material JUNGLE_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "jungle_hanging_sign"); + public static final Material JUNGLE_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "jungle_wall_hanging_sign"); + public static final Material ACACIA_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "acacia_hanging_sign"); + public static final Material ACACIA_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "acacia_wall_hanging_sign"); + public static final Material DARK_OAK_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "dark_oak_hanging_sign"); + public static final Material DARK_OAK_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "dark_oak_wall_hanging_sign"); + public static final Material MANGROVE_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "mangrove_hanging_sign"); + public static final Material MANGROVE_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "mangrove_wall_hanging_sign"); + public static final Material CRIMSON_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "crimson_hanging_sign"); + public static final Material CRIMSON_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "crimson_wall_hanging_sign"); + public static final Material WARPED_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "warped_hanging_sign"); + public static final Material WARPED_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "warped_wall_hanging_sign"); + + public static final Material CHERRY_LOG = RegistryUtil.findNullable(Material.class, "cherry_log"); + public static final Material CHERRY_WOOD = RegistryUtil.findNullable(Material.class, "cherry_wood"); + public static final Material STRIPPED_CHERRY_LOG = RegistryUtil.findNullable(Material.class, "stripped_cherry_log"); + public static final Material STRIPPED_CHERRY_WOOD = RegistryUtil.findNullable(Material.class, "stripped_cherry_wood"); + public static final Material CHERRY_PLANKS = RegistryUtil.findNullable(Material.class, "cherry_planks"); + public static final Material CHERRY_STAIRS = RegistryUtil.findNullable(Material.class, "cherry_stairs"); + public static final Material CHERRY_SLAB = RegistryUtil.findNullable(Material.class, "cherry_slab"); + public static final Material CHERRY_FENCE = RegistryUtil.findNullable(Material.class, "cherry_fence"); + public static final Material CHERRY_FENCE_GATE = RegistryUtil.findNullable(Material.class, "cherry_fence_gate"); + public static final Material CHERRY_DOOR = RegistryUtil.findNullable(Material.class, "cherry_door"); + public static final Material CHERRY_TRAPDOOR = RegistryUtil.findNullable(Material.class, "cherry_trapdoor"); + public static final Material CHERRY_PRESSURE_PLATE = RegistryUtil.findNullable(Material.class, "cherry_pressure_plate"); + public static final Material CHERRY_BUTTON = RegistryUtil.findNullable(Material.class, "cherry_button"); + public static final Material CHERRY_SIGN = RegistryUtil.findNullable(Material.class, "cherry_sign"); + public static final Material CHERRY_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "cherry_hanging_sign"); + public static final Material CHERRY_WALL_SIGN = RegistryUtil.findNullable(Material.class, "cherry_wall_sign"); + public static final Material CHERRY_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "cherry_wall_hanging_sign"); + public static final Material CHERRY_LEAVES = RegistryUtil.findNullable(Material.class, "cherry_leaves"); + public static final Material CHERRY_SAPLING = RegistryUtil.findNullable(Material.class, "cherry_sapling"); + + public static final Material BAMBOO_BLOCK = RegistryUtil.findNullable(Material.class, "bamboo_block"); + public static final Material STRIPPED_BAMBOO_BLOCK = RegistryUtil.findNullable(Material.class, "stripped_bamboo_block"); + public static final Material BAMBOO_PLANKS = RegistryUtil.findNullable(Material.class, "bamboo_planks"); + public static final Material BAMBOO_MOSAIC = RegistryUtil.findNullable(Material.class, "bamboo_mosaic"); + public static final Material BAMBOO_STAIRS = RegistryUtil.findNullable(Material.class, "bamboo_stairs"); + public static final Material BAMBOO_MOSAIC_STAIRS = RegistryUtil.findNullable(Material.class, "bamboo_mosaic_stairs"); + public static final Material BAMBOO_SLAB = RegistryUtil.findNullable(Material.class, "bamboo_slab"); + public static final Material BAMBOO_MOSAIC_SLAB = RegistryUtil.findNullable(Material.class, "bamboo_mosaic_slab"); + public static final Material BAMBOO_FENCE = RegistryUtil.findNullable(Material.class, "bamboo_fence"); + public static final Material BAMBOO_FENCE_GATE = RegistryUtil.findNullable(Material.class, "bamboo_fence_gate"); + public static final Material BAMBOO_DOOR = RegistryUtil.findNullable(Material.class, "bamboo_door"); + public static final Material BAMBOO_TRAPDOOR = RegistryUtil.findNullable(Material.class, "bamboo_trapdoor"); + public static final Material BAMBOO_PRESSURE_PLATE = RegistryUtil.findNullable(Material.class, "bamboo_pressure_plate"); + public static final Material BAMBOO_BUTTON = RegistryUtil.findNullable(Material.class, "bamboo_button"); + public static final Material BAMBOO_SIGN = RegistryUtil.findNullable(Material.class, "bamboo_sign"); + public static final Material BAMBOO_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "bamboo_hanging_sign"); + public static final Material BAMBOO_WALL_SIGN = RegistryUtil.findNullable(Material.class, "bamboo_wall_sign"); + public static final Material BAMBOO_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "bamboo_wall_hanging_sign"); + + //1.21.4+ + public static final Material PALE_OAK_LOG = RegistryUtil.findNullable(Material.class, "pale_oak_log"); + public static final Material PALE_OAK_WOOD = RegistryUtil.findNullable(Material.class, "pale_oak_wood"); + public static final Material STRIPPED_PALE_OAK_LOG = RegistryUtil.findNullable(Material.class, "stripped_pale_oak_log"); + public static final Material STRIPPED_PALE_OAK_WOOD = RegistryUtil.findNullable(Material.class, "stripped_pale_oak_wood"); + + public static final Material PALE_OAK_PLANKS = RegistryUtil.findNullable(Material.class, "pale_oak_planks"); + public static final Material PALE_OAK_STAIRS = RegistryUtil.findNullable(Material.class, "pale_oak_stairs"); + public static final Material PALE_OAK_SLAB = RegistryUtil.findNullable(Material.class, "pale_oak_slab"); + public static final Material PALE_OAK_FENCE = RegistryUtil.findNullable(Material.class, "pale_oak_fence"); + public static final Material PALE_OAK_FENCE_GATE = RegistryUtil.findNullable(Material.class, "pale_oak_fence_gate"); + public static final Material PALE_OAK_DOOR = RegistryUtil.findNullable(Material.class, "pale_oak_door"); + public static final Material PALE_OAK_TRAPDOOR = RegistryUtil.findNullable(Material.class, "pale_oak_trapdoor"); + public static final Material PALE_OAK_PRESSURE_PLATE = RegistryUtil.findNullable(Material.class, "pale_oak_pressure_plate"); + public static final Material PALE_OAK_BUTTON = RegistryUtil.findNullable(Material.class, "pale_oak_button"); + + public static final Material PALE_OAK_SIGN = RegistryUtil.findNullable(Material.class, "pale_oak_sign"); + public static final Material PALE_OAK_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "pale_oak_hanging_sign"); + public static final Material PALE_OAK_WALL_SIGN = RegistryUtil.findNullable(Material.class, "pale_oak_wall_sign"); + public static final Material PALE_OAK_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "pale_oak_wall_hanging_sign"); + + public static final Material PALE_OAK_LEAVES = RegistryUtil.findNullable(Material.class, "pale_oak_leaves"); + public static final Material PALE_OAK_SAPLING = RegistryUtil.findNullable(Material.class, "pale_oak_sapling"); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/Particles.java b/src/main/java/art/arcane/adapt/util/reflect/registries/Particles.java new file mode 100644 index 000000000..c9205f759 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/Particles.java @@ -0,0 +1,13 @@ +package art.arcane.adapt.util.reflect.registries; + +import org.bukkit.Particle; + +public class Particles { + public static final Particle REDSTONE = RegistryUtil.find(Particle.class, "redstone", "dust"); + public static final Particle ENCHANTMENT_TABLE = RegistryUtil.find(Particle.class, "enchantment_table", "enchant"); + public static final Particle CRIT_MAGIC = RegistryUtil.find(Particle.class, "crit_magic", "crit"); + public static final Particle TOTEM = RegistryUtil.find(Particle.class, "totem", "totem_of_undying"); + public static final Particle BLOCK_CRACK = RegistryUtil.find(Particle.class, "block_crack", "block"); + public static final Particle VILLAGER_HAPPY = RegistryUtil.find(Particle.class, "villager_happy", "happy_villager"); + public static final Particle ITEM_CRACK = RegistryUtil.find(Particle.class, "item_crack", "item"); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/PotionEffectTypes.java b/src/main/java/art/arcane/adapt/util/reflect/registries/PotionEffectTypes.java new file mode 100644 index 000000000..b18fe8693 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/PotionEffectTypes.java @@ -0,0 +1,12 @@ +package art.arcane.adapt.util.reflect.registries; + +import org.bukkit.potion.PotionEffectType; + +public class PotionEffectTypes { + public static final PotionEffectType FAST_DIGGING = RegistryUtil.find(PotionEffectType.class, "fast_digging", "haste"); + public static final PotionEffectType DAMAGE_RESISTANCE = RegistryUtil.find(PotionEffectType.class, "damage_resistance", "resistance"); + public static final PotionEffectType JUMP = RegistryUtil.find(PotionEffectType.class, "jump", "jump_boost"); + public static final PotionEffectType SLOW_DIGGING = RegistryUtil.find(PotionEffectType.class, "slow_digging", "mining_fatigue"); + public static final PotionEffectType CONFUSION = RegistryUtil.find(PotionEffectType.class, "confusion", "nausea"); + public static final PotionEffectType INCREASE_DAMAGE = RegistryUtil.find(PotionEffectType.class, "increase_damage", "strength"); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/PotionTypes.java b/src/main/java/art/arcane/adapt/util/reflect/registries/PotionTypes.java new file mode 100644 index 000000000..63accded1 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/PotionTypes.java @@ -0,0 +1,12 @@ +package art.arcane.adapt.util.reflect.registries; + +import org.bukkit.potion.PotionType; + +public class PotionTypes { + public static final PotionType UNCRAFTABLE = RegistryUtil.findNullable(PotionType.class, "uncraftable", "empty"); + + public static final PotionType INSTANT_HEAL = RegistryUtil.find(PotionType.class, "instant_heal", "healing"); + public static final PotionType SPEED = RegistryUtil.find(PotionType.class, "speed", "swiftness"); + public static final PotionType REGEN = RegistryUtil.find(PotionType.class, "regen", "regeneration"); + public static final PotionType JUMP = RegistryUtil.find(PotionType.class, "jump", "leaping"); +} diff --git a/src/main/java/art/arcane/adapt/util/reflect/registries/RegistryUtil.java b/src/main/java/art/arcane/adapt/util/reflect/registries/RegistryUtil.java new file mode 100644 index 000000000..8321e4b32 --- /dev/null +++ b/src/main/java/art/arcane/adapt/util/reflect/registries/RegistryUtil.java @@ -0,0 +1,245 @@ +package art.arcane.adapt.util.reflect.registries; + +import art.arcane.adapt.util.cache.AtomicCache; +import lombok.NonNull; +import manifold.rt.api.util.Pair; +import org.bukkit.Bukkit; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Collectors; + + +@SuppressWarnings("unchecked") +public class RegistryUtil { + private static final AtomicCache registryLookup = new AtomicCache<>(); + private static final Map, Map> KEYED_REGISTRY = new HashMap<>(); + private static final Map, Map> ENUM_REGISTRY = new HashMap<>(); + private static final Map, Registry> REGISTRY = new HashMap<>(); + + @NonNull + public static T find(@NonNull Class typeClass, @NonNull String... keys) { + return findOptional(typeClass, keys).orElseThrow(notFound(typeClass, keys)); + } + + @NonNull + public static Optional findOptional(@NonNull Class typeClass, @NonNull String... keys) { + return findOptional(typeClass, defaultLookup(), keys); + } + + @Nullable + public static T findNullable(@NonNull Class typeClass, @NonNull String... keys) { + return findOptional(typeClass, defaultLookup(), keys).orElse(null); + } + + @NonNull + public static T find(@NonNull Class typeClass, @Nullable Lookup lookup, @NonNull String... keys) { + return findOptional(typeClass, lookup, keys).orElseThrow(notFound(typeClass, keys)); + } + + @NonNull + public static Optional findOptional(@NonNull Class typeClass, @Nullable Lookup lookup, @NonNull String... keys) { + return findOptional(typeClass, lookup, Arrays.stream(keys).map(NamespacedKey::minecraft).toArray(NamespacedKey[]::new)); + } + + @NonNull + public static T find(@NonNull Class typeClass, @NonNull NamespacedKey... keys) { + return findOptional(typeClass, keys).orElseThrow(notFound(typeClass, keys)); + } + + @NonNull + public static Optional findOptional(@NonNull Class typeClass, @NonNull NamespacedKey... keys) { + return findOptional(typeClass, defaultLookup(), keys); + } + + @NonNull + public static T find(@NonNull Class typeClass, @Nullable Lookup lookup, @NonNull NamespacedKey... keys) { + return findOptional(typeClass, lookup, keys).orElseThrow(notFound(typeClass, keys)); + } + + @NonNull + public static Optional findOptional(@NonNull Class typeClass, @Nullable Lookup lookup, @NonNull NamespacedKey... keys) { + if (keys.length == 0) + throw new IllegalArgumentException("Need at least one key"); + Registry registry = null; + if (Keyed.class.isAssignableFrom(typeClass)) { + registry = getRegistry(typeClass.asSubclass(Keyed.class)); + } + if (registry == null) { + registry = REGISTRY.computeIfAbsent(typeClass, t -> Arrays.stream(Registry.class.getDeclaredFields()) + .filter(field -> Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers())) + .filter(field -> Registry.class.isAssignableFrom(field.getType())) + .filter(field -> ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].equals(t)) + .map(field -> { + try { + return (Registry) field.get(null); + } catch (IllegalAccessException e) { + return null; + } + }) + .filter(Objects::nonNull) + .findFirst() + .orElse(null)); + } + + if (registry != null) { + for (NamespacedKey key : keys) { + Keyed value = registry.get(key); + if (value != null) + return Optional.of((T) value); + } + } + + if (lookup != null) + return lookup.find(typeClass, keys); + return Optional.empty(); + } + + @NonNull + public static Optional findByField(@NonNull Class typeClass, @NonNull NamespacedKey... keys) { + java.util.Map values = KEYED_REGISTRY.computeIfAbsent(typeClass, RegistryUtil::getKeyedValues); + for (NamespacedKey key : keys) { + org.bukkit.Keyed value = values.get(key); + if (value != null) + return Optional.of((T) value); + } + return Optional.empty(); + } + + @NonNull + public static Optional findByEnum(@NonNull Class typeClass, @NonNull NamespacedKey... keys) { + java.util.Map values = ENUM_REGISTRY.computeIfAbsent(typeClass, RegistryUtil::getEnumValues); + for (NamespacedKey key : keys) { + java.lang.Object value = values.get(key); + if (value != null) + return Optional.of((T) value); + } + return Optional.empty(); + } + + @NonNull + public static Lookup defaultLookup() { + return Lookup.combine(RegistryUtil::findByField, RegistryUtil::findByEnum); + } + + private static Map getKeyedValues(@NonNull Class typeClass) { + return Arrays.stream(typeClass.getDeclaredFields()) + .filter(field -> Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) + .filter(field -> Keyed.class.isAssignableFrom(field.getType())) + .map(field -> { + try { + return (Keyed) field.get(null); + } catch (Throwable e) { + return null; + } + }) + .map(keyed -> { + if (keyed == null) return null; + try { + return Pair.make(keyed.getKey(), keyed); + } catch (Throwable e) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)); + } + + private static Map getEnumValues(@NonNull Class typeClass) { + return Arrays.stream(typeClass.getDeclaredFields()) + .filter(field -> Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) + .filter(field -> typeClass.isAssignableFrom(field.getType())) + .map(field -> { + try { + return Map.entry(NamespacedKey.minecraft(field.getName().toLowerCase()), field.get(null)); + } catch (Throwable e) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + } + + @Nullable + private static Registry getRegistry(@NotNull Class type) { + RegistryLookup lookup = registryLookup.aquire(() -> { + RegistryLookup bukkit; + try { + bukkit = Bukkit::getRegistry; + } catch (Throwable ignored) { + bukkit = null; + } + return new DefaultRegistryLookup(bukkit); + }); + return lookup.find(type); + } + + public static Supplier notFound(Class type, T... keys) { + return () -> new IllegalArgumentException("No " + type.getSimpleName() + " found for keys: " + Arrays.deepToString(keys)); + } + + @FunctionalInterface + public interface Lookup { + static Lookup combine(@NonNull Lookup... lookups) { + if (lookups.length == 0) + throw new IllegalArgumentException("Need at least one lookup"); + return (typeClass, keys) -> { + Optional opt = Optional.empty(); + for (Lookup lookup : lookups) { + opt = opt.or(() -> lookup.find(typeClass, keys)); + } + return opt; + }; + } + + @NonNull + Optional find(@NonNull Class typeClass, @NonNull NamespacedKey... keys); + } + + private interface RegistryLookup { + @Nullable + Registry find(@NonNull Class type); + } + + private static class DefaultRegistryLookup implements RegistryLookup { + private final RegistryLookup bukkit; + private final Map registries; + + private DefaultRegistryLookup(RegistryLookup bukkit) { + this.bukkit = bukkit; + registries = Arrays.stream(Registry.class.getDeclaredFields()) + .filter(field -> Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) + .filter(field -> Registry.class.isAssignableFrom(field.getType())) + .map(field -> { + java.lang.reflect.Type type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + try { + return Map.entry(type, field.get(null)); + } catch (Throwable e) { + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a)); + } + + @Nullable + @Override + public Registry find(@NonNull Class type) { + if (bukkit == null) return (Registry) registries.get(type); + try { + return bukkit.find(type); + } catch (Throwable e) { + return (Registry) registries.get(type); + } + } + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/util/ReflectionUtil.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/util/ReflectionUtil.java new file mode 100644 index 000000000..2b548b908 --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/util/ReflectionUtil.java @@ -0,0 +1,109 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.util; + +import com.fren_gor.ultimateAdvancementAPI.util.Versions; +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Optional; + +public final class ReflectionUtil { + public static final String MINECRAFT_VERSION = Bukkit.getBukkitVersion().split("-")[0]; + private static final String COMPATIBLE_MINECRAFT_VERSION = Versions.normalizeMinecraftVersion(MINECRAFT_VERSION); + public static final int MINOR_VERSION = resolveMinorVersion(COMPATIBLE_MINECRAFT_VERSION); + private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName(); + public static final int VERSION = resolveVersion(COMPATIBLE_MINECRAFT_VERSION); + private static final boolean IS_1_17 = VERSION >= 17; + + private ReflectionUtil() { + throw new UnsupportedOperationException("Utility class."); + } + + private static int resolveVersion(String version) { + String[] split = version.split("\\."); + if (split.length >= 2 && "1".equals(split[0])) { + return Integer.parseInt(split[1]); + } + if (split.length >= 1) { + return Integer.parseInt(split[0]); + } + return 0; + } + + private static int resolveMinorVersion(String version) { + String[] split = version.split("\\."); + if (split.length >= 3 && "1".equals(split[0])) { + return Integer.parseInt(split[2]); + } + if (split.length >= 2 && !"1".equals(split[0])) { + return Integer.parseInt(split[1]); + } + return 0; + } + + public static boolean classExists(@NotNull String className) { + Objects.requireNonNull(className, "ClassName cannot be null."); + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + @Nullable + public static Class getNMSClass(@NotNull String name, @NotNull String mcPackage) { + String path; + if (IS_1_17) { + path = "net.minecraft." + mcPackage + '.' + name; + } else { + Optional version = Versions.getNMSVersion(); + if (version.isEmpty()) { + Bukkit.getLogger().severe("[UltimateAdvancementAPI] Unsupported Minecraft version! (" + MINECRAFT_VERSION + ")"); + return null; + } + path = "net.minecraft.server." + version.get() + '.' + name; + } + + try { + return Class.forName(path); + } catch (ClassNotFoundException e) { + Bukkit.getLogger().severe("[UltimateAdvancementAPI] Can't find NMS Class! (" + path + ")"); + return null; + } + } + + @Nullable + public static Class getCBClass(@NotNull String name) { + String cb = CRAFTBUKKIT_PACKAGE + "." + name; + try { + return Class.forName(cb); + } catch (ClassNotFoundException e) { + Bukkit.getLogger().severe("[UltimateAdvancementAPI] Can't find CB Class! (" + cb + ")"); + return null; + } + } + + @Nullable + public static Class getWrapperClass(@NotNull Class clazz) { + Optional version = Versions.getNMSVersion(); + if (version.isEmpty()) { + Bukkit.getLogger().severe("[UltimateAdvancementAPI] Unsupported Minecraft version! (" + MINECRAFT_VERSION + ")"); + return null; + } + + String name = clazz.getName(); + String validPackage = "com.fren_gor.ultimateAdvancementAPI.nms.wrappers."; + if (!name.startsWith(validPackage)) { + throw new IllegalArgumentException("Invalid class " + name + '.'); + } + String wrapper = "com.fren_gor.ultimateAdvancementAPI.nms." + version.get() + "." + name.substring(validPackage.length()) + '_' + version.get(); + try { + return Class.forName(wrapper).asSubclass(clazz); + } catch (ClassNotFoundException e) { + Bukkit.getLogger().severe("[UltimateAdvancementAPI] Can't find Wrapper Class! (" + wrapper + ")"); + return null; + } + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/MinecraftKeyWrapper_v1_21_R7.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/MinecraftKeyWrapper_v1_21_R7.java new file mode 100644 index 000000000..5a816e1cd --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/MinecraftKeyWrapper_v1_21_R7.java @@ -0,0 +1,45 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7; + +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.MinecraftKeyWrapper; +import net.minecraft.IdentifierException; +import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.NotNull; + +public class MinecraftKeyWrapper_v1_21_R7 extends MinecraftKeyWrapper { + private final Identifier key; + + public MinecraftKeyWrapper_v1_21_R7(@NotNull Object key) { + this.key = (Identifier) key; + } + + public MinecraftKeyWrapper_v1_21_R7(@NotNull String namespace, @NotNull String key) { + try { + this.key = Identifier.fromNamespaceAndPath(namespace, key); + } catch (IdentifierException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + @Override + @NotNull + public String getNamespace() { + return key.getNamespace(); + } + + @Override + @NotNull + public String getKey() { + return key.getPath(); + } + + @Override + public int compareTo(@NotNull MinecraftKeyWrapper obj) { + return key.compareTo((Identifier) obj.toNMS()); + } + + @Override + @NotNull + public Identifier toNMS() { + return key; + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/Util.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/Util.java new file mode 100644 index 000000000..0066c7f30 --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/Util.java @@ -0,0 +1,116 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.chat.ComponentSerializer; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.advancements.AdvancementProgress; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.CriterionProgress; +import net.minecraft.advancements.triggers.Criterion; +import net.minecraft.advancements.triggers.ImpossibleTrigger; +import net.minecraft.advancements.triggers.ImpossibleTrigger.TriggerInstance; +import net.minecraft.core.ClientAsset; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.resources.Identifier; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +public final class Util { + public static final Logger ERROR = Logger.getLogger("UltimateAdvancementAPI-NMS"); + + private Util() { + throw new UnsupportedOperationException("Utility class."); + } + + @NotNull + public static Map> getAdvancementCriteria(@Range(from = 1, to = Integer.MAX_VALUE) int maxProgression) { + Preconditions.checkArgument(maxProgression >= 1, "Max progression must be >= 1."); + + Map> advCriteria = Maps.newHashMapWithExpectedSize(maxProgression); + for (int i = 0; i < maxProgression; i++) { + advCriteria.put(String.valueOf(i), new Criterion<>(new ImpossibleTrigger(), new TriggerInstance())); + } + + return advCriteria; + } + + @NotNull + public static AdvancementRequirements getAdvancementRequirements(@NotNull Map> advCriteria) { + Preconditions.checkNotNull(advCriteria, "Advancement criteria map is null."); + + List> list = new ArrayList<>(advCriteria.size()); + for (String name : advCriteria.keySet()) { + list.add(List.of(name)); + } + + return new AdvancementRequirements(list); + } + + @NotNull + public static AdvancementProgress getAdvancementProgress(@NotNull AdvancementHolder mcAdv, @Range(from = 0, to = Integer.MAX_VALUE) int progression) { + Preconditions.checkNotNull(mcAdv, "NMS Advancement is null."); + Preconditions.checkArgument(progression >= 0, "Progression must be >= 0."); + + AdvancementProgress advPrg = new AdvancementProgress(); + advPrg.update(mcAdv.value().requirements()); + + for (int i = 0; i < progression; i++) { + CriterionProgress criteriaPrg = advPrg.getCriterion(String.valueOf(i)); + if (criteriaPrg != null) { + criteriaPrg.grant(); + } + } + + return advPrg; + } + + @NotNull + public static Component fromString(@NotNull String string) { + if (string.isEmpty()) { + return CommonComponents.EMPTY; + } + + return CraftChatMessage.fromStringOrNull(string, true); + } + + @NotNull + public static Component fromComponent(@NotNull BaseComponent component) { + Component base = CraftChatMessage.fromJSONOrNull(ComponentSerializer.toString(component)); + return base == null ? CommonComponents.EMPTY : base; + } + + @Nullable + public static ClientAsset.ResourceTexture parseBackgroundTexture(@Nullable String backgroundTexture) { + if (backgroundTexture == null) { + return null; + } + + Identifier texturePath = Identifier.parse(backgroundTexture); + if (!texturePath.getPath().startsWith("textures/") || !texturePath.getPath().endsWith(".png")) { + ERROR.severe("Invalid background texture \"" + backgroundTexture + "\" (the path should be in the form \"textures/**.png\")"); + return null; + } + + Identifier id = texturePath.withPath(path -> path.substring("textures/".length(), path.length() - ".png".length())); + return new ClientAsset.ResourceTexture(id, texturePath); + } + + public static void sendTo(@NotNull Player player, @NotNull Packet packet) { + Preconditions.checkNotNull(player, "Player is null."); + Preconditions.checkNotNull(packet, "Packet is null."); + ((CraftPlayer) player).getHandle().connection.send(packet); + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementDisplayWrapper_v1_21_R7.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementDisplayWrapper_v1_21_R7.java new file mode 100644 index 000000000..de5204e97 --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementDisplayWrapper_v1_21_R7.java @@ -0,0 +1,98 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.advancement; + +import com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.Util; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementDisplayWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementFrameTypeWrapper; +import net.md_5.bungee.api.chat.BaseComponent; +import net.minecraft.advancements.AdvancementType; +import net.minecraft.advancements.DisplayInfo; +import net.minecraft.core.ClientAsset; +import net.minecraft.world.item.ItemStackTemplate; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public class AdvancementDisplayWrapper_v1_21_R7 extends AdvancementDisplayWrapper { + private final DisplayInfo display; + private final AdvancementFrameTypeWrapper frameType; + + public AdvancementDisplayWrapper_v1_21_R7(@NotNull ItemStack icon, @NotNull String title, @NotNull String description, @NotNull AdvancementFrameTypeWrapper frameType, float x, float y, boolean showToast, boolean announceChat, boolean hidden, @Nullable String backgroundTexture) { + ClientAsset.ResourceTexture clientAsset = Util.parseBackgroundTexture(backgroundTexture); + this.display = new DisplayInfo(ItemStackTemplate.fromNonEmptyStack(CraftItemStack.asNMSCopy(icon)), Util.fromString(title), Util.fromString(description), Optional.ofNullable(clientAsset), (AdvancementType) frameType.toNMS(), showToast, announceChat, hidden); + this.display.setLocation(x, y); + this.frameType = frameType; + } + + public AdvancementDisplayWrapper_v1_21_R7(@NotNull ItemStack icon, @NotNull BaseComponent title, @NotNull BaseComponent description, @NotNull AdvancementFrameTypeWrapper frameType, float x, float y, boolean showToast, boolean announceChat, boolean hidden, @Nullable String backgroundTexture) { + ClientAsset.ResourceTexture clientAsset = Util.parseBackgroundTexture(backgroundTexture); + this.display = new DisplayInfo(ItemStackTemplate.fromNonEmptyStack(CraftItemStack.asNMSCopy(icon)), Util.fromComponent(title), Util.fromComponent(description), Optional.ofNullable(clientAsset), (AdvancementType) frameType.toNMS(), showToast, announceChat, hidden); + this.display.setLocation(x, y); + this.frameType = frameType; + } + + @Override + @NotNull + public ItemStack getIcon() { + return CraftItemStack.asBukkitCopy(display.getIcon().create()); + } + + @Override + @NotNull + public String getTitle() { + return CraftChatMessage.fromComponent(display.getTitle()); + } + + @Override + @NotNull + public String getDescription() { + return CraftChatMessage.fromComponent(display.getDescription()); + } + + @Override + @NotNull + public AdvancementFrameTypeWrapper getAdvancementFrameType() { + return frameType; + } + + @Override + public float getX() { + return display.getX(); + } + + @Override + public float getY() { + return display.getY(); + } + + @Override + public boolean doesShowToast() { + return display.shouldShowToast(); + } + + @Override + public boolean doesAnnounceToChat() { + return display.shouldAnnounceChat(); + } + + @Override + public boolean isHidden() { + return display.isHidden(); + } + + @Override + @Nullable + public String getBackgroundTexture() { + Optional texture = display.getBackground(); + return texture.isEmpty() ? null : texture.get().texturePath().toString(); + } + + @Override + @NotNull + public DisplayInfo toNMS() { + return display; + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementFrameTypeWrapper_v1_21_R7.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementFrameTypeWrapper_v1_21_R7.java new file mode 100644 index 000000000..a16f7a306 --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementFrameTypeWrapper_v1_21_R7.java @@ -0,0 +1,31 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.advancement; + +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementFrameTypeWrapper; +import net.minecraft.advancements.AdvancementType; +import org.jetbrains.annotations.NotNull; + +public class AdvancementFrameTypeWrapper_v1_21_R7 extends AdvancementFrameTypeWrapper { + private final AdvancementType mcFrameType; + private final FrameType frameType; + + public AdvancementFrameTypeWrapper_v1_21_R7(@NotNull FrameType frameType) { + this.frameType = frameType; + this.mcFrameType = switch (frameType) { + case TASK -> AdvancementType.TASK; + case GOAL -> AdvancementType.GOAL; + case CHALLENGE -> AdvancementType.CHALLENGE; + }; + } + + @Override + @NotNull + public FrameType getFrameType() { + return frameType; + } + + @Override + @NotNull + public AdvancementType toNMS() { + return mcFrameType; + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementWrapper_v1_21_R7.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementWrapper_v1_21_R7.java new file mode 100644 index 000000000..69421c94b --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/AdvancementWrapper_v1_21_R7.java @@ -0,0 +1,90 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.advancement; + +import com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.Util; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.MinecraftKeyWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementDisplayWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementWrapper; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.DisplayInfo; +import net.minecraft.advancements.triggers.Criterion; +import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; + +import java.util.Map; +import java.util.Optional; + +public class AdvancementWrapper_v1_21_R7 extends AdvancementWrapper { + private final AdvancementHolder advancementHolder; + private final MinecraftKeyWrapper key; + private final AdvancementWrapper parent; + private final AdvancementDisplayWrapper display; + + public AdvancementWrapper_v1_21_R7(@NotNull MinecraftKeyWrapper key, @NotNull AdvancementDisplayWrapper display, @Range(from = 1, to = Integer.MAX_VALUE) int maxProgression) { + Map> advCriteria = Util.getAdvancementCriteria(maxProgression); + Advancement advancement = new Advancement(Optional.empty(), Optional.of((DisplayInfo) display.toNMS()), AdvancementRewards.EMPTY, advCriteria, Util.getAdvancementRequirements(advCriteria), false, Optional.empty()); + this.advancementHolder = new AdvancementHolder((Identifier) key.toNMS(), advancement); + this.key = key; + this.parent = null; + this.display = display; + } + + public AdvancementWrapper_v1_21_R7(@NotNull MinecraftKeyWrapper key, @NotNull AdvancementWrapper parent, @NotNull AdvancementDisplayWrapper display, @Range(from = 1, to = Integer.MAX_VALUE) int maxProgression) { + Map> advCriteria = Util.getAdvancementCriteria(maxProgression); + Advancement advancement = new Advancement(Optional.of((Identifier) parent.getKey().toNMS()), Optional.of((DisplayInfo) display.toNMS()), AdvancementRewards.EMPTY, advCriteria, Util.getAdvancementRequirements(advCriteria), false, Optional.empty()); + this.advancementHolder = new AdvancementHolder((Identifier) key.toNMS(), advancement); + this.key = key; + this.parent = parent; + this.display = display; + } + + protected AdvancementWrapper_v1_21_R7(@NotNull MinecraftKeyWrapper key, @NotNull AdvancementDisplayWrapper display, @NotNull Map> advCriteria, @NotNull AdvancementRequirements advRequirements) { + Advancement advancement = new Advancement(Optional.empty(), Optional.of((DisplayInfo) display.toNMS()), AdvancementRewards.EMPTY, advCriteria, advRequirements, false, Optional.empty()); + this.advancementHolder = new AdvancementHolder((Identifier) key.toNMS(), advancement); + this.key = key; + this.parent = null; + this.display = display; + } + + protected AdvancementWrapper_v1_21_R7(@NotNull MinecraftKeyWrapper key, @NotNull AdvancementWrapper parent, @NotNull AdvancementDisplayWrapper display, @NotNull Map> advCriteria, @NotNull AdvancementRequirements advRequirements) { + Advancement advancement = new Advancement(Optional.of((Identifier) parent.getKey().toNMS()), Optional.of((DisplayInfo) display.toNMS()), AdvancementRewards.EMPTY, advCriteria, advRequirements, false, Optional.empty()); + this.advancementHolder = new AdvancementHolder((Identifier) key.toNMS(), advancement); + this.key = key; + this.parent = parent; + this.display = display; + } + + @Override + @NotNull + public MinecraftKeyWrapper getKey() { + return key; + } + + @Override + @Nullable + public AdvancementWrapper getParent() { + return parent; + } + + @Override + @NotNull + public AdvancementDisplayWrapper getDisplay() { + return display; + } + + @Override + @Range(from = 1, to = Integer.MAX_VALUE) + public int getMaxProgression() { + return advancementHolder.value().requirements().size(); + } + + @Override + @NotNull + public AdvancementHolder toNMS() { + return advancementHolder; + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/PreparedAdvancementWrapper_v1_21_R7.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/PreparedAdvancementWrapper_v1_21_R7.java new file mode 100644 index 000000000..6904cfeec --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/advancement/PreparedAdvancementWrapper_v1_21_R7.java @@ -0,0 +1,57 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.advancement; + +import com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.Util; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.MinecraftKeyWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementDisplayWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.PreparedAdvancementWrapper; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.triggers.Criterion; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Range; + +import java.util.Map; + +public class PreparedAdvancementWrapper_v1_21_R7 extends PreparedAdvancementWrapper { + private final MinecraftKeyWrapper key; + private final AdvancementDisplayWrapper display; + private final Map> advCriteria; + private final AdvancementRequirements advRequirements; + + public PreparedAdvancementWrapper_v1_21_R7(@NotNull MinecraftKeyWrapper key, @NotNull AdvancementDisplayWrapper display, @Range(from = 1, to = Integer.MAX_VALUE) int maxProgression) { + this.key = key; + this.display = display; + this.advCriteria = Util.getAdvancementCriteria(maxProgression); + this.advRequirements = Util.getAdvancementRequirements(advCriteria); + } + + @Override + @NotNull + public MinecraftKeyWrapper getKey() { + return key; + } + + @Override + @NotNull + public AdvancementDisplayWrapper getDisplay() { + return display; + } + + @Override + @Range(from = 1, to = Integer.MAX_VALUE) + public int getMaxProgression() { + return advRequirements.size(); + } + + @Override + @NotNull + public AdvancementWrapper toRootAdvancementWrapper() { + return new AdvancementWrapper_v1_21_R7(key, display, advCriteria, advRequirements); + } + + @Override + @NotNull + public AdvancementWrapper toBaseAdvancementWrapper(@NotNull AdvancementWrapper parent) { + return new AdvancementWrapper_v1_21_R7(key, parent, display, advCriteria, advRequirements); + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/packets/PacketPlayOutAdvancementsWrapper_v1_21_R7.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/packets/PacketPlayOutAdvancementsWrapper_v1_21_R7.java new file mode 100644 index 000000000..abd8d0891 --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/packets/PacketPlayOutAdvancementsWrapper_v1_21_R7.java @@ -0,0 +1,49 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.packets; + +import com.fren_gor.ultimateAdvancementAPI.nms.util.ListSet; +import com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.Util; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.MinecraftKeyWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.packets.PacketPlayOutAdvancementsWrapper; +import com.google.common.collect.Maps; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.advancements.AdvancementProgress; +import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket; +import net.minecraft.resources.Identifier; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class PacketPlayOutAdvancementsWrapper_v1_21_R7 extends PacketPlayOutAdvancementsWrapper { + private final ClientboundUpdateAdvancementsPacket packet; + + public PacketPlayOutAdvancementsWrapper_v1_21_R7() { + this.packet = new ClientboundUpdateAdvancementsPacket(true, Collections.emptyList(), Collections.emptySet(), Collections.emptyMap(), true); + } + + @SuppressWarnings("unchecked") + public PacketPlayOutAdvancementsWrapper_v1_21_R7(@NotNull Map toSend) { + Map map = Maps.newHashMapWithExpectedSize(toSend.size()); + for (Entry e : toSend.entrySet()) { + AdvancementWrapper adv = e.getKey(); + map.put((Identifier) adv.getKey().toNMS(), Util.getAdvancementProgress((AdvancementHolder) adv.toNMS(), e.getValue())); + } + + this.packet = new ClientboundUpdateAdvancementsPacket(false, (Collection) ListSet.fromWrapperSet(toSend.keySet()), Collections.emptySet(), map, true); + } + + @SuppressWarnings("unchecked") + public PacketPlayOutAdvancementsWrapper_v1_21_R7(@NotNull Set toRemove) { + this.packet = new ClientboundUpdateAdvancementsPacket(false, Collections.emptyList(), (Set) ListSet.fromWrapperSet(toRemove), Collections.emptyMap(), true); + } + + @Override + public void sendTo(@NotNull Player player) { + Util.sendTo(player, packet); + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/packets/PacketPlayOutSelectAdvancementTabWrapper_v1_21_R7.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/packets/PacketPlayOutSelectAdvancementTabWrapper_v1_21_R7.java new file mode 100644 index 000000000..4ed0fc1f1 --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/nms/v1_21_R7/packets/PacketPlayOutSelectAdvancementTabWrapper_v1_21_R7.java @@ -0,0 +1,26 @@ +package com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.packets; + +import com.fren_gor.ultimateAdvancementAPI.nms.v1_21_R7.Util; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.MinecraftKeyWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.packets.PacketPlayOutSelectAdvancementTabWrapper; +import net.minecraft.network.protocol.game.ClientboundSelectAdvancementsTabPacket; +import net.minecraft.resources.Identifier; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class PacketPlayOutSelectAdvancementTabWrapper_v1_21_R7 extends PacketPlayOutSelectAdvancementTabWrapper { + private final ClientboundSelectAdvancementsTabPacket packet; + + public PacketPlayOutSelectAdvancementTabWrapper_v1_21_R7() { + this.packet = new ClientboundSelectAdvancementsTabPacket((Identifier) null); + } + + public PacketPlayOutSelectAdvancementTabWrapper_v1_21_R7(@NotNull MinecraftKeyWrapper key) { + this.packet = new ClientboundSelectAdvancementsTabPacket((Identifier) key.toNMS()); + } + + @Override + public void sendTo(@NotNull Player player) { + Util.sendTo(player, packet); + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/util/AdvancementUtils.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/util/AdvancementUtils.java new file mode 100644 index 000000000..d1696c7d1 --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/util/AdvancementUtils.java @@ -0,0 +1,756 @@ +package com.fren_gor.ultimateAdvancementAPI.util; + +import art.arcane.adapt.util.common.scheduling.J; +import art.arcane.volmlib.util.scheduling.FoliaScheduler; +import com.fren_gor.ultimateAdvancementAPI.AdvancementMain; +import com.fren_gor.ultimateAdvancementAPI.AdvancementTab; +import com.fren_gor.ultimateAdvancementAPI.UltimateAdvancementAPI; +import com.fren_gor.ultimateAdvancementAPI.advancement.Advancement; +import com.fren_gor.ultimateAdvancementAPI.advancement.display.AdvancementDisplay; +import com.fren_gor.ultimateAdvancementAPI.advancement.display.AdvancementFrameType; +import com.fren_gor.ultimateAdvancementAPI.database.TeamProgression; +import com.fren_gor.ultimateAdvancementAPI.exceptions.AsyncExecutionException; +import com.fren_gor.ultimateAdvancementAPI.exceptions.UserNotLoadedException; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.MinecraftKeyWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.VanillaAdvancementDisablerWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementDisplayWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementFrameTypeWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementWrapper; +import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.packets.PacketPlayOutAdvancementsWrapper; +import com.google.common.base.Preconditions; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.GameRule; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; + +public class AdvancementUtils { + + /** + * The {@code show_advancement_messages} game rule (previously called + * {@code announceAdvancements}). + */ + public static final GameRule SHOW_ADVANCEMENT_MESSAGES_GAMERULE = getShowAdvancementMessagesGamerule(); + + public static final MinecraftKeyWrapper ROOT_KEY, NOTIFICATION_KEY; + private static final String ADV_DESCRIPTION = "\n§7A notification."; + private static final AdvancementWrapper ROOT; + + static { + try { + ROOT_KEY = MinecraftKeyWrapper.craft("com.fren_gor", "root"); + NOTIFICATION_KEY = MinecraftKeyWrapper.craft("com.fren_gor", "notification"); + AdvancementDisplayWrapper display = AdvancementDisplayWrapper.craft(new ItemStack(Material.GRASS_BLOCK), "§f§lNotifications§1§2§3§4§5§6§7§8§9§0", "§7Notification page.\n§7Close and reopen advancements to hide.", AdvancementFrameTypeWrapper.TASK, 0, 0, "textures/block/stone.png"); + ROOT = AdvancementWrapper.craftRootAdvancement(ROOT_KEY, display, 1); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private AdvancementUtils() { + throw new UnsupportedOperationException("Utility class."); + } + + /*public static void displayToast(@NotNull Player player, @NotNull ItemStack icon, @NotNull String title, @NotNull AdvancementFrameType frame, @NotNull Advancement base) { + Preconditions.checkNotNull(player, "Player is null."); + Preconditions.checkNotNull(icon, "Icon is null."); + Preconditions.checkNotNull(title, "Title is null."); + Preconditions.checkNotNull(frame, "AdvancementFrameType is null."); + Preconditions.checkNotNull(base, "Advancement is null."); + Preconditions.checkArgument(base.isValid(), "Advancement isn't valid."); + Preconditions.checkArgument(icon.getType() != Material.AIR, "ItemStack is air."); + + final MinecraftKeyWrapper key = getUniqueKey(base.getAdvancementTab()).getNMSWrapper(); + + try { + AdvancementDisplayWrapper display = AdvancementDisplayWrapper.craft(icon, title, ADV_DESCRIPTION, frame.getNMSWrapper(), base.getDisplay().getX() + 1, base.getDisplay().getY(), true, false, false); + AdvancementWrapper adv = AdvancementWrapper.craftBaseAdvancement(key, base.getNMSWrapper(), display, 1); + + PacketPlayOutSelectAdvancementTabWrapper.craftSelectNone().sendTo(player); + PacketPlayOutAdvancementsWrapper.craftSendPacket(Map.of(adv, 1)).sendTo(player); + PacketPlayOutAdvancementsWrapper.craftRemovePacket(Set.of(key)).sendTo(player); + PacketPlayOutSelectAdvancementTabWrapper.craftSelect(key).sendTo(player); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + }*/ + + /** + * Displays a custom toast to a player. + * + * @param player A player to show the toast. + * @param icon The displayed item of the toast. + * @param title The displayed title of the toast. + * @param frame The {@link AdvancementFrameType} of the toast. + * @see UltimateAdvancementAPI#displayCustomToast(Player, ItemStack, String, + * AdvancementFrameType) + */ + public static void displayToast(@NotNull Player player, @NotNull ItemStack icon, @NotNull String title, @NotNull AdvancementFrameType frame) { + Preconditions.checkNotNull(player, "Player is null."); + Preconditions.checkNotNull(icon, "Icon is null."); + Preconditions.checkNotNull(title, "Title is null."); + Preconditions.checkNotNull(frame, "AdvancementFrameType is null."); + Preconditions.checkArgument(icon.getType() != Material.AIR, "ItemStack is air."); + + try { + AdvancementDisplayWrapper display = AdvancementDisplayWrapper.craft(icon, title, ADV_DESCRIPTION, frame.getNMSWrapper(), 1, 0, true, false, false); + AdvancementWrapper notification = AdvancementWrapper.craftBaseAdvancement(NOTIFICATION_KEY, ROOT, display, 1); + PacketPlayOutAdvancementsWrapper.craftSendPacket(Map.of( + ROOT, 1, + notification, 1 + )).sendTo(player); + PacketPlayOutAdvancementsWrapper.craftRemovePacket(Set.of(ROOT_KEY, NOTIFICATION_KEY)).sendTo(player); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + } + + public static void displayToastDuringUpdate(@NotNull Player player, @NotNull Advancement advancement) { + Preconditions.checkNotNull(player, "Player is null."); + Preconditions.checkNotNull(advancement, "Advancement is null."); + Preconditions.checkArgument(advancement.isValid(), "Advancement isn't valid."); + + final AdvancementDisplay display = advancement.getDisplay(); + final MinecraftKeyWrapper keyWrapper = getUniqueKey(advancement.getAdvancementTab()).getNMSWrapper(); + + try { + AdvancementDisplayWrapper displayWrapper = AdvancementDisplayWrapper.craft(display.getIcon(), display.getTitle(), ADV_DESCRIPTION, display.getFrame().getNMSWrapper(), 0, 0, true, false, false); + AdvancementWrapper advWrapper = AdvancementWrapper.craftBaseAdvancement(keyWrapper, advancement.getNMSWrapper(), displayWrapper, 1); + + PacketPlayOutAdvancementsWrapper.craftSendPacket(Map.of(advWrapper, 1)).sendTo(player); + PacketPlayOutAdvancementsWrapper.craftRemovePacket(Set.of(keyWrapper)).sendTo(player); + } catch (ReflectiveOperationException e) { + e.printStackTrace(); + } + } + + @NotNull + private static AdvancementKey getUniqueKey(@NotNull AdvancementTab tab) { + final String namespace = tab.getNamespace(); + StringBuilder builder = new StringBuilder("i"); + AdvancementKey key; + while (tab.getAdvancement(key = new AdvancementKey(namespace, builder.toString())) != null) { + builder.append('i'); + } + return key; + } + + /** + * Disables vanilla advancements. + * + * @throws Exception If disabling fails. + * @see UltimateAdvancementAPI#disableVanillaAdvancements() + */ + public static void disableVanillaAdvancements() throws Exception { + VanillaAdvancementDisablerWrapper.disableVanillaAdvancements(true, false); + } + + /** + * Disables vanilla recipe advancements (i.e. the advancements which unlock + * recipes). + * + * @throws Exception If disabling fails. + * @see UltimateAdvancementAPI#disableVanillaRecipeAdvancements() + */ + public static void disableVanillaRecipeAdvancements() throws Exception { + VanillaAdvancementDisablerWrapper.disableVanillaAdvancements(false, true); + } + + @NotNull + public static BaseComponent[] fromStringList(@NotNull List list) { + return fromStringList(null, list); + } + + @NotNull + public static BaseComponent[] fromStringList(@Nullable String title, @NotNull List list) { + Preconditions.checkNotNull(list); + ComponentBuilder builder = new ComponentBuilder(); + if (title != null) { + builder.append(TextComponent.fromLegacyText(title), FormatRetention.NONE); + if (list.isEmpty()) { + return builder.create(); + } + builder.append("\n", FormatRetention.NONE); + } else if (list.isEmpty()) { + return builder.create(); + } + int i = 0; + for (String s : list) { + builder.append(TextComponent.fromLegacyText(s), FormatRetention.NONE); + if (++i < list.size()) // Don't append \n at the end + builder.append("\n", FormatRetention.NONE); + } + return builder.create(); + } + + public static boolean startsWithEmptyLine(@NotNull String text) { + Preconditions.checkNotNull(text); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '§') { + i++; // Skip next character since it is a color code + } else { + return c == '\n'; + } + } + return false; + } + + @Contract("_ -> param1") + public static int validateProgressionValue(int progression) { + if (progression < 0) { + throw new IllegalArgumentException("Progression value cannot be < 0"); + } + return progression; + } + + public static void validateProgressionValueStrict(int progression, int maxProgression) { + validateProgressionValue(progression); + if (progression > maxProgression) { + throw new IllegalArgumentException("Progression value cannot be greater than the maximum progression (" + maxProgression + ')'); + } + } + + public static void validateIncrement(int increment) { + if (increment <= 0) { + throw new IllegalArgumentException("Increment cannot be zero or less."); + } + } + + @Contract("null -> fail; !null -> param1") + public static TeamProgression validateTeamProgression(TeamProgression pro) { + Preconditions.checkNotNull(pro, "TeamProgression is null."); + Preconditions.checkArgument(pro.isValid(), "Invalid TeamProgression."); + return pro; + } + + public static void checkTeamProgressionNotNull(TeamProgression progression) { + if (progression == null) { + throw new UserNotLoadedException(); + } + } + + public static void checkTeamProgressionNotNull(TeamProgression progression, UUID uuid) { + if (progression == null) { + throw new UserNotLoadedException(uuid); + } + } + + public static void checkSync() { + if (J.isFoliaThreading() || hasFoliaScheduler()) { + return; + } + + if (!Bukkit.isPrimaryThread()) + throw new AsyncExecutionException("Illegal async method call. This method can be called only from the main thread."); + } + + public static void runSync(@NotNull AdvancementMain main, @NotNull Runnable runnable) { + runSync(main.getOwningPlugin(), runnable); + } + + public static void runSync(@NotNull Plugin plugin, @NotNull Runnable runnable) { + runSync(plugin, 1, runnable); + } + + public static void runSync(@NotNull AdvancementMain main, long delay, @NotNull Runnable runnable) { + runSync(main.getOwningPlugin(), delay, runnable); + } + + public static void runSync(@NotNull Plugin plugin, long delay, @NotNull Runnable runnable) { + Preconditions.checkNotNull(plugin, "Plugin is null."); + Preconditions.checkNotNull(runnable, "Runnable is null."); + if (!plugin.isEnabled()) { + return; + } + + int safeDelay = sanitizeDelay(delay); + if (scheduleFoliaSync(plugin, runnable, safeDelay)) { + return; + } + + if (hasFoliaScheduler()) { + if (safeDelay <= 0 && FoliaScheduler.isPrimaryThread()) { + runnable.run(); + return; + } + + plugin.getLogger().warning("Failed to schedule advancement sync task on Folia for plugin " + plugin.getName() + + " (" + safeDelay + "t)."); + return; + } + + if (scheduleLegacySync(runnable, safeDelay)) { + return; + } + + if (scheduleFoliaSync(plugin, runnable, safeDelay)) { + return; + } + + if (safeDelay <= 0 && FoliaScheduler.isPrimaryThread()) { + runnable.run(); + return; + } + + plugin.getLogger().warning("Failed to schedule advancement sync task for plugin " + plugin.getName() + + " (" + safeDelay + "t)."); + } + + private static int sanitizeDelay(long delay) { + if (delay <= 0) { + return 0; + } + + if (delay >= Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + + return (int) delay; + } + + private static boolean scheduleFoliaSync(@NotNull Plugin plugin, @NotNull Runnable runnable, int safeDelay) { + Player player = extractPlayer(runnable); + if (player != null && player.isOnline()) { + if (FoliaScheduler.runEntity(plugin, player, runnable, safeDelay)) { + return true; + } + + if (scheduleEntityReflective(plugin, player, runnable, safeDelay)) { + return true; + } + } + + if (FoliaScheduler.runGlobal(plugin, runnable, safeDelay)) { + return true; + } + + return scheduleGlobalReflective(plugin, runnable, safeDelay); + } + + private static boolean scheduleLegacySync(@NotNull Runnable runnable, int safeDelay) { + try { + if (safeDelay <= 0) { + J.s(runnable); + } else { + J.s(runnable, safeDelay); + } + return true; + } catch (Throwable ex) { + if (isUnsupportedScheduler(ex)) { + return false; + } + + if (ex instanceof RuntimeException runtimeException) { + throw runtimeException; + } + + if (ex instanceof Error error) { + throw error; + } + + throw new RuntimeException(ex); + } + } + + private static boolean isUnsupportedScheduler(@Nullable Throwable throwable) { + Throwable current = throwable; + while (current != null) { + if (current instanceof UnsupportedOperationException) { + return true; + } + + String message = current.getMessage(); + if (message != null && message.contains("BukkitScheduler unsupported")) { + return true; + } + + current = current.getCause(); + } + + return false; + } + + private static boolean hasFoliaScheduler() { + return FoliaScheduler.isFolia(Bukkit.getServer()); + } + + private static boolean scheduleEntityReflective(@NotNull Plugin plugin, @NotNull Player player, @NotNull Runnable runnable, int safeDelay) { + Object scheduler = invokeNoThrow(player, "getScheduler", new Class[0]); + if (scheduler == null) { + return false; + } + + Runnable retired = () -> { + }; + Consumer consumer = task -> runnable.run(); + long safeLongDelay = Math.max(0L, safeDelay); + if (safeLongDelay <= 0L) { + Object immediateExecuted = invokeNoThrow( + scheduler, + "execute", + new Class[]{Plugin.class, Runnable.class, Runnable.class, long.class}, + plugin, + runnable, + retired, + 0L + ); + if (immediateExecuted instanceof Boolean done) { + return done; + } + + if (invokeVoidNoThrow( + scheduler, + "run", + new Class[]{Plugin.class, Consumer.class, Runnable.class}, + plugin, + consumer, + retired + )) { + return true; + } + + if (invokeVoidNoThrow( + scheduler, + "run", + new Class[]{Plugin.class, Runnable.class, Runnable.class}, + plugin, + runnable, + retired + )) { + return true; + } + + if (invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Consumer.class, Runnable.class, long.class}, + plugin, + consumer, + retired, + 1L + )) { + return true; + } + + return invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Runnable.class, Runnable.class, long.class}, + plugin, + runnable, + retired, + 1L + ); + } + + if (invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Consumer.class, Runnable.class, long.class}, + plugin, + consumer, + retired, + safeLongDelay + )) { + return true; + } + + if (invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Consumer.class, Runnable.class, int.class}, + plugin, + consumer, + retired, + safeDelay + )) { + return true; + } + + if (invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Runnable.class, Runnable.class, long.class}, + plugin, + runnable, + retired, + safeLongDelay + )) { + return true; + } + + if (invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Runnable.class, Runnable.class, int.class}, + plugin, + runnable, + retired, + safeDelay + )) { + return true; + } + + Object delayedExecuted = invokeNoThrow( + scheduler, + "execute", + new Class[]{Plugin.class, Runnable.class, Runnable.class, long.class}, + plugin, + runnable, + retired, + safeLongDelay + ); + return delayedExecuted instanceof Boolean done && done; + } + + private static boolean scheduleGlobalReflective(@NotNull Plugin plugin, @NotNull Runnable runnable, int safeDelay) { + Object scheduler = getGlobalScheduler(plugin); + if (scheduler == null) { + return false; + } + + Consumer consumer = task -> runnable.run(); + long safeLongDelay = Math.max(0L, safeDelay); + if (safeLongDelay <= 0L) { + if (invokeVoidNoThrow( + scheduler, + "execute", + new Class[]{Plugin.class, Runnable.class}, + plugin, + runnable + )) { + return true; + } + + if (invokeVoidNoThrow( + scheduler, + "run", + new Class[]{Plugin.class, Consumer.class}, + plugin, + consumer + )) { + return true; + } + + return invokeVoidNoThrow( + scheduler, + "run", + new Class[]{Plugin.class, Runnable.class}, + plugin, + runnable + ); + } + + if (invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Consumer.class, long.class}, + plugin, + consumer, + safeLongDelay + )) { + return true; + } + + if (invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Consumer.class, int.class}, + plugin, + consumer, + safeDelay + )) { + return true; + } + + if (invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Runnable.class, long.class}, + plugin, + runnable, + safeLongDelay + )) { + return true; + } + + return invokeVoidNoThrow( + scheduler, + "runDelayed", + new Class[]{Plugin.class, Runnable.class, int.class}, + plugin, + runnable, + safeDelay + ); + } + + @Nullable + private static Object getGlobalScheduler(@NotNull Plugin plugin) { + Object serverScheduler = invokeNoThrow(plugin.getServer(), "getGlobalRegionScheduler", new Class[0]); + if (serverScheduler != null) { + return serverScheduler; + } + + return invokeStaticNoThrow(Bukkit.class, "getGlobalRegionScheduler", new Class[0]); + } + + @Nullable + private static Object invokeStaticNoThrow( + @NotNull Class owner, + @NotNull String methodName, + @NotNull Class[] parameterTypes, + Object... args + ) { + try { + Method method = owner.getMethod(methodName, parameterTypes); + return method.invoke(null, args); + } catch (Throwable ignored) { + return null; + } + } + + @Nullable + private static Object invokeNoThrow( + @NotNull Object target, + @NotNull String methodName, + @NotNull Class[] parameterTypes, + Object... args + ) { + try { + Method method = target.getClass().getMethod(methodName, parameterTypes); + return method.invoke(target, args); + } catch (Throwable ignored) { + return null; + } + } + + private static boolean invokeVoidNoThrow( + @NotNull Object target, + @NotNull String methodName, + @NotNull Class[] parameterTypes, + Object... args + ) { + try { + Method method = target.getClass().getMethod(methodName, parameterTypes); + method.invoke(target, args); + return true; + } catch (Throwable ignored) { + return false; + } + } + + @Nullable + private static Player extractPlayer(@NotNull Runnable runnable) { + Class current = runnable.getClass(); + while (current != null && current != Object.class) { + for (Field field : current.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + + try { + field.setAccessible(true); + Object value = field.get(runnable); + Player player = asPlayer(value); + if (player != null) { + return player; + } + } catch (Throwable ex) { + // Ignore inaccessible synthetic fields while walking runnable captures. + } + } + + current = current.getSuperclass(); + } + + return null; + } + + @Nullable + private static Player asPlayer(@Nullable Object value) { + if (value instanceof Player player) { + return player; + } + + if (value instanceof OfflinePlayer offlinePlayer) { + return offlinePlayer.getPlayer(); + } + + if (value instanceof UUID uuid) { + return Bukkit.getPlayer(uuid); + } + + return null; + } + + @NotNull + public static UUID uuidFromPlayer(@NotNull Player player) { + Preconditions.checkNotNull(player, "Player is null."); + return player.getUniqueId(); + } + + @NotNull + public static UUID uuidFromPlayer(@NotNull OfflinePlayer player) { + Preconditions.checkNotNull(player, "OfflinePlayer is null."); + return player.getUniqueId(); + } + + @NotNull + public static TeamProgression progressionFromPlayer(@NotNull Player player, @NotNull Advancement advancement) { + return progressionFromPlayer(player, advancement.getAdvancementTab()); + } + + @NotNull + public static TeamProgression progressionFromUUID(@NotNull UUID uuid, @NotNull Advancement advancement) { + return progressionFromUUID(uuid, advancement.getAdvancementTab()); + } + + @NotNull + public static TeamProgression progressionFromPlayer(@NotNull Player player, @NotNull AdvancementTab tab) { + return progressionFromUUID(uuidFromPlayer(player), tab); + } + + @NotNull + public static TeamProgression progressionFromUUID(@NotNull UUID uuid, @NotNull AdvancementTab tab) { + Preconditions.checkNotNull(uuid, "UUID is null."); + return tab.getDatabaseManager().getTeamProgression(uuid); + } + + @SuppressWarnings("unchecked") + private static GameRule getShowAdvancementMessagesGamerule() { + try { + // Spigot 1.21.11+ + return (GameRule) GameRule.class.getDeclaredField("SHOW_ADVANCEMENT_MESSAGES").get(null); + } catch (NoSuchFieldException e) { + // Spigot <= 1.21.10, Paper all versions + try { + return (GameRule) GameRule.class.getDeclaredField("ANNOUNCE_ADVANCEMENTS").get(null); + } catch (NoSuchFieldException ex) { + return null; + } catch (ReflectiveOperationException inner) { + throw new RuntimeException(inner); + } + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/fren_gor/ultimateAdvancementAPI/util/Versions.java b/src/main/java/com/fren_gor/ultimateAdvancementAPI/util/Versions.java new file mode 100644 index 000000000..02565db5b --- /dev/null +++ b/src/main/java/com/fren_gor/ultimateAdvancementAPI/util/Versions.java @@ -0,0 +1,171 @@ +package com.fren_gor.ultimateAdvancementAPI.util; + +import com.fren_gor.ultimateAdvancementAPI.nms.util.ReflectionUtil; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +public final class Versions { + private static final String API_VERSION = "2.7.2"; + + private static final List SUPPORTED_NMS_VERSIONS = List.of( + "v1_15_R1", + "v1_16_R1", + "v1_16_R2", + "v1_16_R3", + "v1_17_R1", + "v1_18_R1", + "v1_18_R2", + "v1_19_R1", + "v1_19_R2", + "v1_19_R3", + "v1_20_R1", + "v1_20_R2", + "v1_20_R3", + "v1_20_R4", + "v1_21_R1", + "v1_21_R2", + "v1_21_R3", + "v1_21_R4", + "v1_21_R5", + "v1_21_R6", + "v1_21_R7" + ); + + private static final Map> NMS_TO_VERSIONS = Map.ofEntries( + Map.entry("v1_15_R1", List.of("1.15", "1.15.1", "1.15.2")), + Map.entry("v1_16_R1", List.of("1.16", "1.16.1", "1.16.2")), + Map.entry("v1_16_R2", List.of("1.16.3", "1.16.4")), + Map.entry("v1_16_R3", List.of("1.16.5")), + Map.entry("v1_17_R1", List.of("1.17", "1.17.1")), + Map.entry("v1_18_R1", List.of("1.18", "1.18.1")), + Map.entry("v1_18_R2", List.of("1.18.2")), + Map.entry("v1_19_R1", List.of("1.19", "1.19.2")), + Map.entry("v1_19_R2", List.of("1.19.3")), + Map.entry("v1_19_R3", List.of("1.19.4")), + Map.entry("v1_20_R1", List.of("1.20", "1.20.1")), + Map.entry("v1_20_R2", List.of("1.20.2")), + Map.entry("v1_20_R3", List.of("1.20.3", "1.20.4")), + Map.entry("v1_20_R4", List.of("1.20.5", "1.20.6")), + Map.entry("v1_21_R1", List.of("1.21", "1.21.1")), + Map.entry("v1_21_R2", List.of("1.21.2", "1.21.3")), + Map.entry("v1_21_R3", List.of("1.21.4")), + Map.entry("v1_21_R4", List.of("1.21.5")), + Map.entry("v1_21_R5", List.of("1.21.6", "1.21.7", "1.21.8")), + Map.entry("v1_21_R6", List.of("1.21.9", "1.21.10")), + Map.entry("v1_21_R7", List.of("1.21.11", "26.2")) + ); + + private static final Map NMS_TO_FANCY = Map.ofEntries( + Map.entry("v1_15_R1", "1.15-1.15.2"), + Map.entry("v1_16_R1", "1.16-1.16.2"), + Map.entry("v1_16_R2", "1.16.3-1.16.4"), + Map.entry("v1_16_R3", "1.16.5"), + Map.entry("v1_17_R1", "1.17-1.17.1"), + Map.entry("v1_18_R1", "1.18-1.18.1"), + Map.entry("v1_18_R2", "1.18.2"), + Map.entry("v1_19_R1", "1.19-1.19.2"), + Map.entry("v1_19_R2", "1.19.3"), + Map.entry("v1_19_R3", "1.19.4"), + Map.entry("v1_20_R1", "1.20-1.20.1"), + Map.entry("v1_20_R2", "1.20.2"), + Map.entry("v1_20_R3", "1.20.3-1.20.4"), + Map.entry("v1_20_R4", "1.20.5-1.20.6"), + Map.entry("v1_21_R1", "1.21-1.21.1"), + Map.entry("v1_21_R2", "1.21.2-1.21.3"), + Map.entry("v1_21_R3", "1.21.4"), + Map.entry("v1_21_R4", "1.21.5"), + Map.entry("v1_21_R5", "1.21.6-1.21.8"), + Map.entry("v1_21_R6", "1.21.9-1.21.10"), + Map.entry("v1_21_R7", "1.21.11, 26.2") + ); + + private static final List SUPPORTED_VERSIONS = SUPPORTED_NMS_VERSIONS.stream() + .flatMap(s -> NMS_TO_VERSIONS.get(s).stream()) + .toList(); + + @Nullable + private static Optional COMPLETE_VERSION; + + private Versions() { + throw new UnsupportedOperationException("Utility class."); + } + + public static String normalizeMinecraftVersion(String version) { + if (version == null || version.isBlank()) { + return version; + } + if ("26.2".equals(version) || version.startsWith("26.2.") || version.startsWith("26.2-")) { + return "1.21.11"; + } + return version; + } + + @NotNull + public static Optional getNMSVersion() { + if (COMPLETE_VERSION != null) { + return COMPLETE_VERSION; + } + + String compatibleVersion = normalizeMinecraftVersion(ReflectionUtil.MINECRAFT_VERSION); + String version = NMS_TO_VERSIONS.entrySet().stream() + .filter(e -> e.getValue().contains(compatibleVersion)) + .map(Entry::getKey) + .findFirst() + .orElse(null); + + COMPLETE_VERSION = version != null ? Optional.of(version) : Optional.empty(); + return COMPLETE_VERSION; + } + + public static String getApiVersion() { + return API_VERSION; + } + + @UnmodifiableView + @NotNull + public static List<@NotNull String> getSupportedVersions() { + return SUPPORTED_VERSIONS; + } + + @UnmodifiableView + @NotNull + public static List<@NotNull String> getSupportedNMSVersions() { + return SUPPORTED_NMS_VERSIONS; + } + + @Nullable + public static String getNMSVersionsRange() { + return getNMSVersion().map(Versions::getNMSVersionsRange).orElse(null); + } + + @Nullable + @Contract("null -> null") + public static String getNMSVersionsRange(String version) { + return NMS_TO_FANCY.get(version); + } + + @UnmodifiableView + @Nullable + public static List<@NotNull String> getNMSVersionsList() { + return getNMSVersion().map(Versions::getNMSVersionsList).orElse(null); + } + + @UnmodifiableView + @Nullable + @Contract("null -> null") + public static List<@NotNull String> getNMSVersionsList(String version) { + return NMS_TO_VERSIONS.get(version); + } + + @Contract("null -> null; !null -> !null") + public static String removeInitialV(String string) { + return string == null || string.isEmpty() || string.charAt(0) != 'v' ? string : string.substring(1); + } +} diff --git a/src/main/java/com/volmit/adapt/Adapt.java b/src/main/java/com/volmit/adapt/Adapt.java deleted file mode 100644 index 00f4754cc..000000000 --- a/src/main/java/com/volmit/adapt/Adapt.java +++ /dev/null @@ -1,491 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt; - -import com.jeff_media.customblockdata.CustomBlockData; -import com.volmit.adapt.api.advancement.AdvancementManager; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingManager; -import com.volmit.adapt.api.protection.ProtectorRegistry; -import com.volmit.adapt.api.tick.Ticker; -import com.volmit.adapt.api.value.MaterialValue; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptServer; -import com.volmit.adapt.api.world.PlayerDataPersistenceQueue; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.content.gui.SkillsGui; -import com.volmit.adapt.content.protector.*; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.config.ConfigMigrationManager; -import com.volmit.adapt.util.redis.RedisSync; -import com.volmit.adapt.util.secret.SecretSplash; -import de.crazydev22.platformutils.AudienceProvider; -import de.crazydev22.platformutils.Platform; -import de.crazydev22.platformutils.PlatformUtils; -import de.slikey.effectlib.EffectManager; -import fr.skytasul.glowingentities.GlowingEntities; -import io.github.slimjar.app.builder.SpigotApplicationBuilder; -import lombok.Getter; -import lombok.SneakyThrows; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; - -import java.io.*; -import java.lang.annotation.Annotation; -import java.net.URL; -import java.text.MessageFormat; -import java.util.*; -import java.util.function.Supplier; - -import static com.volmit.adapt.util.decree.context.AdaptationListingHandler.initializeAdaptationListings; - -public class Adapt extends VolmitPlugin { - public static Adapt instance; - public static HashMap wordKey = new HashMap<>(); - public final EffectManager adaptEffectManager; - public static Platform platform; - public static AudienceProvider audiences; - private KMap, AdaptService> services; - - @Getter - private GlowingEntities glowingEntities; - @Getter - private Ticker ticker; - @Getter - private AdaptServer adaptServer; - @Getter - private SQLManager sqlManager; - @Getter - private ProtectorRegistry protectorRegistry; - @Getter - private Map guiLeftovers = new HashMap<>(); - - @Getter - private AdvancementManager manager; - @Getter - private RedisSync redisSync; - @Getter - private PlayerDataPersistenceQueue playerDataPersistenceQueue; - - - private final KList postShutdown = new KList<>(); - private static VolmitSender sender; - private static final long STARTUP_SLOW_PHASE_MS = 1500L; - - - public Adapt() { - instance = this; - getLogger().info("Loading Libraries..."); - new SpigotApplicationBuilder(this) - .remap(true) - .build(); - getLogger().info("Libraries Loaded!"); - adaptEffectManager = new EffectManager(this); - } - - @SuppressWarnings("unchecked") - public static T service(Class c) { - return (T) instance.services.get(c); - } - - @Override - public void onLoad() { - manager = new AdvancementManager(); - if (getServer().getPluginManager().getPlugin("WorldGuard") != null) { - WorldGuardProtector.registerFlag(); - } - } - - @Override - public void start() { - runStartupPhaseVoid("backup-legacy-configs", ConfigMigrationManager::backupLegacyJsonConfigsOnce); - platform = PlatformUtils.createPlatform(this); - audiences = platform.getAudienceProvider(); - services = new KMap<>(); - runStartupPhaseVoid("discover-services", () -> initialize("com.volmit.adapt.service") - .forEach((i) -> services.put((Class) i.getClass(), (AdaptService) i))); - - runStartupPhaseVoid("language-update", Localizer::updateLanguageFile); - if (!runStartupPhase("models-load", CustomModel::reloadFromDisk)) { - Adapt.warn("Failed to load models config during startup migration."); - } - if (!AdaptConfig.get().isCustomModels()) { - CustomModel.clear(); - } - if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { - new PapiExpansion().register(); - } - printInformation(); - sqlManager = new SQLManager(); - if (AdaptConfig.get().isUseSql()) { - runStartupPhase("sql-connect", () -> { - sqlManager.establishConnection(); - return null; - }); - } - redisSync = new RedisSync(); - playerDataPersistenceQueue = new PlayerDataPersistenceQueue(); - runStartupPhase("start-sim", () -> { - startSim(); - return null; - }); - runStartupPhase("config-canonicalization", () -> { - migrateAllSkillAndAdaptationConfigs(); - return null; - }); - CustomBlockData.registerListener(this); - registerListener(new BrewingManager()); - registerListener(Version.get()); - setupMetrics(); - startupPrint(); // Splash screen - if (AdaptConfig.get().isAutoUpdateCheck()) { - autoUpdateCheck(); - } - protectorRegistry = new ProtectorRegistry(); - if (getServer().getPluginManager().getPlugin("WorldGuard") != null) { - protectorRegistry.registerProtector(new WorldGuardProtector()); - } - if (getServer().getPluginManager().getPlugin("Factions") != null) { - protectorRegistry.registerProtector(new FactionsClaimProtector()); - } - if (getServer().getPluginManager().getPlugin("ChestProtect") != null) { - protectorRegistry.registerProtector(new ChestProtectProtector()); - } - if (getServer().getPluginManager().getPlugin("Residence") != null) { - protectorRegistry.registerProtector(new ResidenceProtector()); - } - if (getServer().getPluginManager().getPlugin("GriefDefender") != null) { - protectorRegistry.registerProtector(new GriefDefenderProtector()); - } - if (getServer().getPluginManager().getPlugin("GriefPrevention") != null) { - protectorRegistry.registerProtector(new GriefPreventionProtector()); - } - if (getServer().getPluginManager().getPlugin("LockettePro") != null) { - protectorRegistry.registerProtector(new LocketteProProtector()); - } - glowingEntities = new GlowingEntities(this); - initializeAdaptationListings(); - services.values().forEach(AdaptService::onEnable); - services.values().forEach(this::registerListener); - } - - private static void runStartupPhaseVoid(String phase, Runnable action) { - runStartupPhase(phase, () -> { - action.run(); - return null; - }); - } - - private static T runStartupPhase(String phase, Supplier action) { - if (phase == null || phase.isBlank()) { - return action.get(); - } - - info("Startup phase: " + phase); - long start = System.currentTimeMillis(); - try { - return action.get(); - } finally { - long elapsed = System.currentTimeMillis() - start; - if (elapsed >= STARTUP_SLOW_PHASE_MS) { - warn("Startup phase '" + phase + "' took " + elapsed + "ms."); - } else { - verbose("Startup phase '" + phase + "' took " + elapsed + "ms."); - } - } - } - - private void migrateAllSkillAndAdaptationConfigs() { - if (adaptServer == null || adaptServer.getSkillRegistry() == null) { - return; - } - - if (!ConfigMigrationManager.hasLegacySkillOrAdaptationJsonFiles()) { - int deletedLegacyJson = ConfigMigrationManager.deleteMigratedLegacyJsonFiles(); - Adapt.info("Skipped skill/adaptation canonicalization (legacy json not found). deletedLegacyJson=" + deletedLegacyJson + "."); - return; - } - - int migratedSkills = 0; - int migratedAdaptations = 0; - for (Skill skill : adaptServer.getSkillRegistry().getSkills()) { - if (skill instanceof SimpleSkill simpleSkill) { - if (simpleSkill.reloadConfigFromDisk(false)) { - migratedSkills++; - } - } - - for (Adaptation adaptation : skill.getAdaptations()) { - if (adaptation instanceof SimpleAdaptation simpleAdaptation) { - if (simpleAdaptation.reloadConfigFromDisk(false)) { - migratedAdaptations++; - } - } - } - } - - int deletedLegacyJson = ConfigMigrationManager.deleteMigratedLegacyJsonFiles(); - Adapt.info("Canonicalized skill/adaptation configs to TOML (skills=" + migratedSkills + ", adaptations=" + migratedAdaptations + ", deletedLegacyJson=" + deletedLegacyJson + ")."); - } - - - public void startSim() { - long startTicker = System.currentTimeMillis(); - ticker = new Ticker(); - verbose("start-sim detail: ticker init in " + (System.currentTimeMillis() - startTicker) + "ms"); - - long startServer = System.currentTimeMillis(); - adaptServer = new AdaptServer(); - long serverMs = System.currentTimeMillis() - startServer; - if (serverMs >= STARTUP_SLOW_PHASE_MS) { - warn("start-sim detail: AdaptServer init took " + serverMs + "ms."); - } else { - verbose("start-sim detail: AdaptServer init in " + serverMs + "ms"); - } - - long startAdv = System.currentTimeMillis(); - manager.enable(); - verbose("start-sim detail: advancement manager enable in " + (System.currentTimeMillis() - startAdv) + "ms"); - } - - public void postShutdown(Runnable r) { - postShutdown.add(r); - } - - public void stopSim() { - if (ticker != null) { - ticker.clear(); - } - postShutdown.forEach(Runnable::run); - if (adaptServer != null) { - adaptServer.unregister(); - } - if (manager != null) { - manager.disable(); - } - MaterialValue.save(); - WorldData.stop(); - CustomModel.clear(); - } - - - @Override - public void stop() { - if (services != null) { - services.values().forEach(AdaptService::onDisable); - } - stopSim(); - if (playerDataPersistenceQueue != null) { - playerDataPersistenceQueue.flushAndShutdown(30_000L); - playerDataPersistenceQueue = null; - } - if (redisSync != null) { - try { - redisSync.close(); - } catch (Exception e) { - Adapt.verbose("Failed to close redis sync: " + e.getMessage()); - } finally { - redisSync = null; - } - } - if (sqlManager != null) { - sqlManager.closeConnection(); - } - if (glowingEntities != null) { - glowingEntities.disable(); - } - if (protectorRegistry != null) { - protectorRegistry.unregisterAll(); - } - if (services != null) { - services.clear(); - } - } - - private void startupPrint() { - if (!AdaptConfig.get().isSplashScreen()) { - return; - } - Random r = new Random(); - int game = r.nextInt(100); - if (game < 90) { - Adapt.info("\n" + C.GRAY + " █████" + C.DARK_RED + "╗ " + C.GRAY + "██████" + C.DARK_RED + "╗ " + C.GRAY + "█████" + C.DARK_RED + "╗ " + C.GRAY + "██████" + C.DARK_RED + "╗ " + C.GRAY + "████████" + C.DARK_RED + "╗\n" + - C.GRAY + "██" + C.DARK_RED + "╔══" + C.GRAY + "██" + C.DARK_RED + "╗" + C.GRAY + "██" + C.DARK_RED + "╔══" + C.GRAY + "██" + C.DARK_RED + "╗" + C.GRAY + "██" + C.DARK_RED + "╔══" + C.GRAY + "██" + C.DARK_RED + "╗" + C.GRAY + "██" + C.DARK_RED + "╔══" + C.GRAY + "██" + C.DARK_RED + "╗╚══" + C.GRAY + "██" + C.DARK_RED + "╔══╝" + C.WHITE + " Version: " + C.DARK_RED + instance.getDescription().getVersion() + " \n" + - C.GRAY + "███████" + C.DARK_RED + "║" + C.GRAY + "██" + C.DARK_RED + "║ " + C.GRAY + "██" + C.DARK_RED + "║" + C.GRAY + "███████" + C.DARK_RED + "║" + C.GRAY + "██████" + C.DARK_RED + "╔╝ " + C.GRAY + "██" + C.DARK_RED + "║" + C.WHITE + " By: " + C.RED + "A" + C.GOLD + "r" + C.YELLOW + "c" + C.GREEN + "a" + C.DARK_GRAY + "n" + C.AQUA + "e " + C.AQUA + "A" + C.BLUE + "r" + C.DARK_BLUE + "t" + C.DARK_PURPLE + "s" + C.WHITE + " (Volmit Software)\n" + - C.GRAY + "██" + C.DARK_RED + "╔══" + C.GRAY + "██" + C.DARK_RED + "║" + C.GRAY + "██" + C.DARK_RED + "║ " + C.GRAY + "██" + C.DARK_RED + "║" + C.GRAY + "██" + C.DARK_RED + "╔══" + C.GRAY + "██" + C.DARK_RED + "║" + C.GRAY + "██" + C.DARK_RED + "╔═══╝ " + C.GRAY + "██" + C.DARK_RED + "║" + C.WHITE + " Java Version: " + C.DARK_RED + getJavaVersion() + " \n" + - C.GRAY + "██" + C.DARK_RED + "║ " + C.GRAY + "██" + C.DARK_RED + "║" + C.GRAY + "██████" + C.DARK_RED + "╔╝" + C.GRAY + "██" + C.DARK_RED + "║ " + C.GRAY + "██" + C.DARK_RED + "║" + C.GRAY + "██" + C.DARK_RED + "║ " + C.GRAY + "██" + C.DARK_RED + "║ \n" + - C.DARK_RED + "╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ \n"); - } else { - info(SecretSplash.getSecretSplash().getRandom()); - } - } - - public File getJarFile() { - return getFile(); - } - - @Override - public String getTag(String subTag) { - return C.BOLD + "" + C.DARK_GRAY + "[" + C.BOLD + "" + C.DARK_RED + "Adapt" + C.BOLD + C.DARK_GRAY + "]" + C.RESET + "" + C.GRAY + ": "; - } - - private void setupMetrics() { - if (AdaptConfig.get().isMetrics()) { - new Metrics(this, 24221); - } - } - - public static VolmitSender getSender() { - if (sender == null) { - sender = new VolmitSender(Bukkit.getConsoleSender()); - sender.setTag(instance.getTag()); - } - return sender; - } - - public static List initialize(String s) { - return initialize(s, null); - } - - public static KList initialize(String s, Class slicedClass) { - JarScanner js = new JarScanner(instance.getFile(), s); - KList v = new KList<>(); - J.attempt(js::scan); - for (Class i : js.getClasses()) { - if (slicedClass == null || i.isAnnotationPresent(slicedClass)) { - try { - Adapt.verbose("Found class: " + i.getName()); - v.add(i.getDeclaredConstructor().newInstance()); - } catch (Throwable e) { - Adapt.verbose("Failed to load class: " + i.getName()); - StringWriter writer = new StringWriter(); - e.printStackTrace(new PrintWriter(writer)); - for (String line : writer.toString().split("\n")) { - verbose(line); - } - } - } - } - - return v; - } - - public static int getJavaVersion() { - String version = System.getProperty("java.version"); - if (version.startsWith("1.")) { - version = version.substring(2, 3); - } else { - int dot = version.indexOf("."); - if (dot != -1) { - version = version.substring(0, dot); - } - } - return Integer.parseInt(version); - } - - public static void printInformation() { - debug("XP Curve: " + AdaptConfig.get().getXpCurve()); - debug("XP/Level base: " + AdaptConfig.get().getPlayerXpPerSkillLevelUpBase()); - debug("XP/Level multiplier: " + AdaptConfig.get().getPlayerXpPerSkillLevelUpLevelMultiplier()); - info("Language: " + AdaptConfig.get().getLanguage() + " - Language Fallback: " + AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing()); - } - - @SneakyThrows - public static void autoUpdateCheck() { - try (BufferedReader in = new BufferedReader(new InputStreamReader(new URL("https://raw.githubusercontent.com/VolmitSoftware/Adapt/main/build.gradle.kts").openStream()))) { - info("Checking for updates..."); - String inputLine; - while ((inputLine = in.readLine()) != null) { - if (inputLine.contains("version '")) { - String version = inputLine.replace("version '", "").replace("'", "").replace("// Needs to be version specific", "").replace(" ", ""); - if (instance.getDescription().getVersion().contains("development")) { - info("Development build detected. Skipping update check."); - return; - } else if (!version.equals(instance.getDescription().getVersion())) { - info(MessageFormat.format("Please update your Adapt plugin to the latest version! (Current: {0} Latest: {1})", instance.getDescription().getVersion(), version)); - } else { - info("You are running the latest version of Adapt!"); - } - break; - } - } - } catch (Throwable e) { - error("Failed to check for updates."); - } - } - - public static void actionbar(Player p, String msg) { - new VolmitSender(p).sendAction(msg); - } - - public static void debug(String string) { - if (AdaptConfig.get().isDebug()) { - msg(C.DARK_PURPLE + string); - } - } - - public static void warn(String string) { - msg(C.YELLOW + string); - } - - public static void error(String string) { - msg(C.RED + string); - } - - public static void verbose(String string) { - if (AdaptConfig.get().isVerbose()) { - msg(C.LIGHT_PURPLE + string); - } - } - - public static void success(String string) { - msg(C.GREEN + string); - } - - public static void info(String string) { - msg(C.WHITE + string); - } - - public static void messagePlayer(Player p, String string) { - String msg = C.GRAY + "[" + C.DARK_RED + "Adapt" + C.GRAY + "]: " + string; - p.sendMessage(msg); - } - - public static void msg(String string) { - try { - if (instance == null) { - System.out.println("[Adapt]: " + string); - return; - } - - String msg = C.GRAY + "[" + C.DARK_RED + "Adapt" + C.GRAY + "]: " + string; - Bukkit.getConsoleSender().sendMessage(msg); - } catch (Throwable e) { - System.out.println("[Adapt]: " + string); - } - } - -} diff --git a/src/main/java/com/volmit/adapt/AdaptConfig.java b/src/main/java/com/volmit/adapt/AdaptConfig.java deleted file mode 100644 index 87031902d..000000000 --- a/src/main/java/com/volmit/adapt/AdaptConfig.java +++ /dev/null @@ -1,229 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt; - -import com.volmit.adapt.api.xp.Curves; -import com.volmit.adapt.util.redis.RedisConfig; -import com.volmit.adapt.util.config.ConfigFileSupport; -import lombok.Getter; -import lombok.Setter; -import org.bukkit.Material; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@SuppressWarnings("ALL") -@Getter -public class AdaptConfig { - private static AdaptConfig config = null; - private static final Object CONFIG_LOCK = new Object(); - public boolean debug = false; - public boolean autoUpdateCheck = true; - public boolean autoUpdateLanguage = true; - public boolean splashScreen = true; - public boolean xpInCreative = false; - public boolean allowAdaptationsInCreative = false; - public String adaptActivatorBlock = "BOOKSHELF"; - public String adaptActivatorBlockName = "a Bookshelf"; - public List blacklistedWorlds = List.of("some_world_adapt_should_not_run_in", "anotherWorldFolderName"); - public int experienceMaxLevel = 1000; - boolean preventHunterSkillsWhenHungerApplied = true; - private ValueConfig value = new ValueConfig(); - private boolean metrics = true; - private String language = "en_US"; - private String fallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing = "en_US"; - private Curves xpCurve = Curves.ADAPT_BALANCED; - private double playerXpPerSkillLevelUpBase = 489; - private double playerXpPerSkillLevelUpLevelMultiplier = 44; - private double powerPerLevel = 0.65; - private boolean hardcoreResetOnPlayerDeath = false; - private boolean hardcoreNoRefunds = false; - private boolean loginBonus = true; - private boolean welcomeMessage = true; - private boolean advancements = true; - private boolean useSql = false; - private boolean useRedis = false; - private int sqlSecondsCheckverify = 30; - private boolean useEnchantmentTableParticleForActiveEffects = true; - private boolean escClosesAllGuis = false; - private boolean guiBackButton = false; - private boolean customModels = true; - private boolean automaticGradients = false; - private int learnUnlearnButtonDelayTicks = 14; - private int maxRecipeListPrecaution = 25; - private boolean actionbarNotifyXp = true; - private boolean actionbarNotifyLevel = true; - private boolean unlearnAllButton = false; - private Effects effects = new Effects(); - private FarmPrevention farmPrevention = new FarmPrevention(); - private AdaptationXp adaptationXp = new AdaptationXp(); - private RedisConfig redis = new RedisConfig(); - private SqlSettings sql = new SqlSettings(); - private Protector protectorSupport = new Protector(); - private Map> adaptationUsageConflicts = defaultAdaptationUsageConflicts(); - private Map> protectionOverrides = Map.of( - "adaptation-name", Map.of( - "WorldGuard", true - ) - ); - - @Setter - private boolean verbose = false; - - public static AdaptConfig get() { - synchronized (CONFIG_LOCK) { - try { - if (config == null) { - config = loadConfig(new AdaptConfig(), true); - } - } catch (Throwable e) { - e.printStackTrace(); - config = new AdaptConfig(); - } - - return config; - } - } - - public static boolean reload() { - synchronized (CONFIG_LOCK) { - try { - config = loadConfig(config == null ? new AdaptConfig() : config, false); - return true; - } catch (Throwable e) { - return false; - } - } - } - - private static AdaptConfig loadConfig(AdaptConfig fallback, boolean overwriteOnFailure) throws IOException { - File canonicalFile = Adapt.instance.getDataFile("adapt", "adapt.toml"); - File legacyFile = Adapt.instance.getDataFile("adapt", "adapt.json"); - return ConfigFileSupport.load( - canonicalFile, - legacyFile, - AdaptConfig.class, - fallback, - overwriteOnFailure, - "core-config", - "Created missing config [adapt/adapt.toml] from defaults." - ); - } - - @Getter - public static class Protector { - private boolean worldguard = true; - private boolean griefdefender = true; - private boolean factionsClaim = false; - private boolean residence = true; - private boolean chestProtect = true; - private boolean griefprevention = true; - private boolean lockettePro = true; - } - - - @Getter - public static class SqlSettings { - private String host = "localhost"; - private int port = 1337; - private String database = "adapt"; - private String username = "user"; - private String password = "password"; - private int poolSize = 10; - private long connectionTimeout = 5000; - } - - @Getter - public static class ValueConfig { - private double baseValue = 1; - private Map valueMutlipliers = defaultValueMultipliersOverrides(); - - private Map defaultValueMultipliersOverrides() { - Map f = new HashMap<>(); - f.put(Material.BLAZE_ROD.name(), 50D); - f.put(Material.ENDER_PEARL.name(), 75D); - f.put(Material.GHAST_TEAR.name(), 100D); - f.put(Material.LEATHER.name(), 1.5D); - f.put(Material.BEEF.name(), 1.125D); - f.put(Material.PORKCHOP.name(), 1.125D); - f.put(Material.EGG.name(), 1.335D); - f.put(Material.CHICKEN.name(), 1.13D); - f.put(Material.MUTTON.name(), 1.125D); - f.put(Material.WHEAT.name(), 1.25D); - f.put(Material.BEETROOT.name(), 1.25D); - f.put(Material.CARROT.name(), 1.25D); - f.put(Material.FLINT.name(), 1.35D); - f.put(Material.IRON_ORE.name(), 1.75D); - f.put(Material.DIAMOND_ORE.name(), 5D); - f.put(Material.GOLD_ORE.name(), 4D); - f.put(Material.LAPIS_ORE.name(), 3.5D); - f.put(Material.COAL_ORE.name(), 1.35D); - f.put(Material.REDSTONE_ORE.name(), 4.5D); - f.put(Material.NETHER_GOLD_ORE.name(), 4.5D); - f.put(Material.NETHER_QUARTZ_ORE.name(), 1.11D); - return f; - } - } - - @Getter - public static class FarmPrevention { - private boolean enabled = true; - private boolean perActivityTracking = true; - private long skillRecoveryMillis = 180000; - private long activityRecoveryMillis = 300000; - private long activityStateTtlMillis = 1800000; - private double skillBasePressure = 1.0; - private double skillXpPressure = 0.02; - private double skillDecayCurve = 14.0; - private double skillFloorMultiplier = 0.08; - private double activityBasePressure = 1.0; - private double activityXpPressure = 0.03; - private double activityDecayCurve = 9.0; - private double activityFloorMultiplier = 0.12; - private double crossSkillRecoveryFactor = 0.9; - } - - @Getter - public static class Effects { - private boolean particlesEnabled = true; - private boolean soundsEnabled = true; - private Map adaptationParticleOverrides = Map.of( - "adaptation-name", true - ); - private Map skillParticleOverrides = Map.of( - "skill-name", true - ); - } - - @Getter - public static class AdaptationXp { - private boolean usageBaselineEnabled = true; - private double usageBaselineXp = 0.8; - private double usageBaselineXpPerLevel = 0.18; - private long usageBaselineCooldownMillis = 12000; - } - - private static Map> defaultAdaptationUsageConflicts() { - return new HashMap<>(); - } - -} diff --git a/src/main/java/com/volmit/adapt/PapiExpansion.java b/src/main/java/com/volmit/adapt/PapiExpansion.java deleted file mode 100644 index 3a9a982e0..000000000 --- a/src/main/java/com/volmit/adapt/PapiExpansion.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.volmit.adapt; - -import com.google.common.collect.Maps; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.world.PlayerData; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.util.Localizer; -import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import org.bukkit.OfflinePlayer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; - -public class PapiExpansion extends PlaceholderExpansion { - - private final Map> skillMap = Maps.newHashMap(); - private final Map> playerMap = Maps.newHashMap(); - private final Map, String>> adaptationMap = Maps.newHashMap(); - - public PapiExpansion() { - // this should be %adapt_skill_level%, %adapt_skill_knowledge%, %adapt_skill_xp%, %adapt_skill_freshness%, %adapt_skill_multiplier%, %adapt_skill_name% - // where skill is the id of the skill eg: %adapt_herbalism_level% - skillMap.put("level", skill -> String.valueOf(skill.getLevel()).equals("-5000") ? "0" : String.valueOf(skill.getLevel())); - skillMap.put("knowledge", skill -> String.valueOf(skill.getKnowledge()).equals("-5000") ? "0" : String.valueOf(skill.getKnowledge())); - skillMap.put("xp", skill -> String.format("%.2f", skill.getXp()).equals("-5000.00") ? "0" : String.format("%.2f", skill.getXp())); - skillMap.put("freshness", skill -> String.valueOf(skill.getFreshness()).equals("-5000") ? "0" : String.valueOf(skill.getFreshness())); - skillMap.put("multiplier", skill -> String.valueOf(skill.getMultiplier()).equals("-5000") ? "0" : String.valueOf(skill.getMultiplier())); - skillMap.put("name", skill -> Localizer.dLocalize("skill." + skill.getLine() + ".name")); - - // this should be %adapt_player_level%, %adapt_player_multiplier%, %adapt_player_availablepower%, %adapt_player_maxpower%, %adapt_player_usedpower%, %adapt_player_wisdom%, %adapt_player_masterxp%, %adapt_player_seenthings% - // the player is provided by the ingame context - playerMap.put("level", playerData -> String.valueOf(playerData.getMultiplier()).equals("-5000") ? "0" : String.valueOf(playerData.getLevel())); - playerMap.put("multiplier", playerData -> String.valueOf(playerData.getMultiplier()).equals("-5000") ? "0" : String.valueOf(playerData.getMultiplier())); - playerMap.put("availablepower", playerData -> String.valueOf(playerData.getAvailablePower()).equals("-5000") ? "0" : String.valueOf(playerData.getAvailablePower())); - playerMap.put("maxpower", playerData -> String.valueOf(playerData.getMaxPower()).equals("-5000") ? "0" : String.valueOf(playerData.getMaxPower())); - playerMap.put("usedpower", playerData -> String.valueOf(playerData.getUsedPower()).equals("-5000") ? "0" : String.valueOf(playerData.getUsedPower())); - playerMap.put("wisdom", playerData -> String.valueOf(playerData.getWisdom()).equals("-5000") ? "0" : String.valueOf(playerData.getWisdom())); - playerMap.put("masterxp", playerData -> String.valueOf(playerData.getMasterXp()).equals("-5000") ? "0" : String.valueOf(playerData.getMasterXp())); - playerMap.put("seenthings", playerData -> String.valueOf(playerData.getSeenBlocks().getSeen().size() - + playerData.getSeenBiomes().getSeen().size() - + playerData.getSeenEnchants().getSeen().size() - + playerData.getSeenEnvironments().getSeen().size() - + playerData.getSeenFoods().getSeen().size() - + playerData.getSeenItems().getSeen().size() - + playerData.getSeenMobs().getSeen().size() - + playerData.getSeenPeople().getSeen().size() - + playerData.getSeenPotionEffects().getSeen().size() + playerData.getSeenRecipes().getSeen().size() - + playerData.getSeenPotionEffects().getSeen().size() + playerData.getSeenWorlds().getSeen().size())); - - // this should be %adapt_adaptation__level%, %adapt_adaptation__maxlevel% - // where adaptation is the adaptation id (e.g. %adapt_adaptation_stealth-ghost-armor_level%) - adaptationMap.put("maxlevel", (playerData, adaptation) -> String.valueOf(adaptation.getMaxLevel())); - adaptationMap.put("level", (playerData, adaptation) -> String.valueOf(getAdaptionLevel(adaptation, playerData))); - adaptationMap.put("name", (playerData, adaptation) -> String.valueOf(getAdaptionLocalizedName(adaptation))); - } - - private Integer getAdaptionLevel(Adaptation adaptation, PlayerData playerData) { - List> skills = Adapt.instance.getAdaptServer().getSkillRegistry().getSkills(); - for (Skill skill : skills) { - List> adaptations = skill.getAdaptations(); - for (Adaptation a : adaptations) { - if (a.equals(adaptation)) { - return playerData.getSkillLine(skill.getName()).getAdaptationLevel(adaptation.getName()); - } - } - } - return 0; - } - - private String getAdaptionLocalizedName(Adaptation adaptation) { - List> skills = Adapt.instance.getAdaptServer().getSkillRegistry().getSkills(); - for (Skill skill : skills) { - List> adaptations = skill.getAdaptations(); - for (Adaptation a : adaptations) { - if (a.equals(adaptation)) { - return Localizer.dLocalize(skill.getId() + "." + adaptation.getDisplayName() + ".name"); - } - } - } - return "Unknown"; - } - - @Override - public @NotNull String getIdentifier() { - return Adapt.instance.getDescription().getName().toLowerCase(); - } - - @Override - public @NotNull String getAuthor() { - return String.join(", ", Adapt.instance.getDescription().getAuthors()); - } - - @Override - public @NotNull String getVersion() { - return Adapt.instance.getDescription().getVersion(); - } - - @Override - public boolean persist() { - return true; - } - - @Override - public @Nullable String onRequest(OfflinePlayer player, @NotNull String params) { - String[] args = params.split("_"); - PlayerData p = Adapt.instance.getAdaptServer().peekData(player.getUniqueId()); - String key = args[0]; - - // Handle player attributes - if (key.equals("player")) { - String playerAttr = args.length > 1 ? args[1] : ""; - if (playerMap.containsKey(playerAttr)) { - return playerMap.get(playerAttr).apply(p); - } - } - - // Handle skill attributes - if (key.equals("skill")) { - String skillID = args.length > 1 ? args[1] : ""; - PlayerSkillLine line = p.getSkillLine(skillID); - String skillAttr = args.length > 2 ? args[2] : ""; - if (line != null && skillMap.containsKey(skillAttr)) { - return skillMap.get(skillAttr).apply(line); - } - } - - // Handle adaptation attributes - if (key.equals("adaptation")) { - String adaptID = args.length > 1 ? args[1] : ""; - String adaptAttr = args.length > 2 ? args[2] : ""; - Adapt.verbose("Triggered adaptation Lookup: " + adaptID + " " + adaptAttr); - List> skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkills(); - - for (Skill s : skill) { - List> adaptations = s.getAdaptations(); - for (Adaptation a : adaptations) { - String adaptationIdWithoutUUID = a.getId().substring(37); - Adapt.verbose(adaptID + " " + adaptationIdWithoutUUID); - if (adaptationIdWithoutUUID.equals(adaptID)) { - Adapt.verbose("Found adaptation: " + a.getId()); - if ("level".equalsIgnoreCase(adaptAttr)) { - Adapt.verbose("Doing Level Lookup"); - return adaptationMap.get("level").apply(p, a); - } else if ("maxlevel".equalsIgnoreCase(adaptAttr)) { - Adapt.verbose("Doing MaxLevel Lookup"); - return adaptationMap.get("maxlevel").apply(p, a); - } else if ("name".equalsIgnoreCase(adaptAttr)) { - Adapt.verbose("Doing Name Lookup"); - return adaptationMap.get("name").apply(p, a); - } - } - } - } - } - return null; - } -} diff --git a/src/main/java/com/volmit/adapt/api/Component.java b/src/main/java/com/volmit/adapt/api/Component.java deleted file mode 100644 index ff9b2b6d0..000000000 --- a/src/main/java/com/volmit/adapt/api/Component.java +++ /dev/null @@ -1,1132 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api; - -import com.francobm.magicosmetics.api.CosmeticType; -import com.francobm.magicosmetics.api.MagicAPI; -import com.google.common.collect.Lists; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.value.MaterialValue; -import com.volmit.adapt.api.xp.XP; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.SoundPlayer; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.EntityPickupItemEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.BoundingBox; -import org.bukkit.util.Vector; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import java.util.function.Predicate; - -import static com.volmit.adapt.util.reflect.registries.Particles.ENCHANTMENT_TABLE; -import static com.volmit.adapt.util.reflect.registries.Particles.REDSTONE; -import static xyz.xenondevs.particle.utils.MathUtils.RANDOM; - -public interface Component { - Set NON_ADAPTABLE_DAMAGE_CAUSES = Set.of( - EntityDamageEvent.DamageCause.VOID, - EntityDamageEvent.DamageCause.LAVA, - EntityDamageEvent.DamageCause.HOT_FLOOR, - EntityDamageEvent.DamageCause.CRAMMING, - EntityDamageEvent.DamageCause.MELTING, - EntityDamageEvent.DamageCause.SUFFOCATION, - EntityDamageEvent.DamageCause.SUICIDE, - EntityDamageEvent.DamageCause.WITHER, - EntityDamageEvent.DamageCause.FLY_INTO_WALL, - EntityDamageEvent.DamageCause.FALL, - EntityDamageEvent.DamageCause.SONIC_BOOM, - EntityDamageEvent.DamageCause.THORNS - ); - - default boolean areParticlesEnabled() { - AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); - return effects == null || effects.isParticlesEnabled(); - } - - default boolean areSoundsEnabled() { - AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); - return effects == null || effects.isSoundsEnabled(); - } - - default void wisdom(Player p, long w) { - XP.wisdom(p, w); - } - - /** - * Attempts to "damage" an item. - * 1. If the item is null, null is returned - * 2. If the item doesnt have durability, (damage) amount will be consumed from the stack, null will be returned if - * more consumed than amount - * 3. If the item has durability, the damage will be consuemd and return the item affected, OR null if it broke - * - * @param item the item (tool) - * @param damage the damage to cause - * @return the damaged item or null if destroyed - */ - default ItemStack damage(ItemStack item, int damage) { - if (item == null) { - return null; - } - - if (item.getItemMeta() == null) { - if (item.getAmount() == 1) { - return null; - } - - item = item.clone(); - item.setAmount(item.getAmount() - 1); - return item; - } - - if (item.getItemMeta() instanceof Damageable d) { - if (d.getDamage() + 1 > item.getType().getMaxDurability()) { - return null; - } - - d.setDamage(d.getDamage() + 1); - item = item.clone(); - item.setItemMeta(d); - return item; - } else { - if (item.getAmount() == 1) { - return null; - } - - item = item.clone(); - item.setAmount(item.getAmount() - 1); - - return item; - } - } - - default void decrementItemstack(ItemStack hand, Player p) { - if (hand.getAmount() > 1) { - hand.setAmount(hand.getAmount() - 1); - } else { - p.getInventory().setItemInMainHand(null); - } - } - - default double getArmorValue(Player player) { - org.bukkit.inventory.PlayerInventory inv = player.getInventory(); - ItemStack boots = inv.getBoots(); - ItemStack helmet = inv.getHelmet(); - ItemStack chest = inv.getChestplate(); - ItemStack pants = inv.getLeggings(); - double armorValue = 0.0; - if (helmet == null) armorValue = armorValue + 0.0; - else if (Bukkit.getServer().getPluginManager().getPlugin("MagicCosmetics") != null && MagicAPI.hasEquipCosmetic(player, CosmeticType.HAT)) { - armorValue = armorValue + 0; - } else if (helmet.getType() == Material.LEATHER_HELMET) armorValue = armorValue + 0.04; - else if (helmet.getType() == Material.GOLDEN_HELMET) armorValue = armorValue + 0.08; - else if (helmet.getType() == Material.TURTLE_HELMET) armorValue = armorValue + 0.08; - else if (helmet.getType() == Material.CHAINMAIL_HELMET) armorValue = armorValue + 0.08; - else if (helmet.getType() == Material.IRON_HELMET) armorValue = armorValue + 0.08; - else if (helmet.getType() == Material.DIAMOND_HELMET) armorValue = armorValue + 0.12; - else if (helmet.getType() == Material.NETHERITE_HELMET) armorValue = armorValue + 0.12; - // - if (boots == null) armorValue = armorValue + 0.0; - else if (boots.getType() == Material.LEATHER_BOOTS) armorValue = armorValue + 0.04; - else if (boots.getType() == Material.GOLDEN_BOOTS) armorValue = armorValue + 0.04; - else if (boots.getType() == Material.CHAINMAIL_BOOTS) armorValue = armorValue + 0.04; - else if (boots.getType() == Material.IRON_BOOTS) armorValue = armorValue + 0.08; - else if (boots.getType() == Material.DIAMOND_BOOTS) armorValue = armorValue + 0.12; - else if (boots.getType() == Material.NETHERITE_BOOTS) armorValue = armorValue + 0.12; - // - if (pants == null) armorValue = armorValue + 0.0; - else if (pants.getType() == Material.LEATHER_LEGGINGS) armorValue = armorValue + 0.08; - else if (pants.getType() == Material.GOLDEN_LEGGINGS) armorValue = armorValue + 0.12; - else if (pants.getType() == Material.CHAINMAIL_LEGGINGS) armorValue = armorValue + 0.16; - else if (pants.getType() == Material.IRON_LEGGINGS) armorValue = armorValue + 0.20; - else if (pants.getType() == Material.DIAMOND_LEGGINGS) armorValue = armorValue + 0.24; - else if (pants.getType() == Material.NETHERITE_LEGGINGS) armorValue = armorValue + 0.24; - // - if (chest == null) armorValue = armorValue + 0.0; - else if (Bukkit.getServer().getPluginManager().getPlugin("MagicCosmetics") != null && MagicAPI.hasEquipCosmetic(player, CosmeticType.BAG)) { - armorValue = armorValue + 0; - } else if (chest.getType() == Material.LEATHER_CHESTPLATE) armorValue = armorValue + 0.12; - else if (chest.getType() == Material.GOLDEN_CHESTPLATE) armorValue = armorValue + 0.20; - else if (chest.getType() == Material.CHAINMAIL_CHESTPLATE) armorValue = armorValue + 0.20; - else if (chest.getType() == Material.IRON_CHESTPLATE) armorValue = armorValue + 0.24; - else if (chest.getType() == Material.DIAMOND_CHESTPLATE) armorValue = armorValue + 0.32; - else if (chest.getType() == Material.NETHERITE_CHESTPLATE) armorValue = armorValue + 0.32; - return armorValue; - } - - default boolean isAdaptableDamageCause(EntityDamageEvent event) { - return !NON_ADAPTABLE_DAMAGE_CAUSES.contains(event.getCause()); - } - - default void addPotionStacks(Player p, PotionEffectType potionEffect, int amplifier, int duration, boolean overlap) { - List activeEffects = new ArrayList<>(p.getActivePotionEffects()); - SoundPlayer sp = SoundPlayer.of(p); - for (PotionEffect activeEffect : activeEffects) { - if (activeEffect.getType() == potionEffect) { - if (!overlap) { - return; // don't modify the effect if overlap is false - } - // modify the effect if overlap is true - int newDuration = activeEffect.getDuration() + duration; - int newAmplifier = Math.max(activeEffect.getAmplifier(), amplifier); - p.removePotionEffect(potionEffect); - p.addPotionEffect(new PotionEffect(potionEffect, newDuration, newAmplifier)); - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.25f); - return; - } - } - // if we didn't find an existing effect, add a new one - J.s(() -> { - p.addPotionEffect(new PotionEffect(potionEffect, duration, amplifier)); - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.25f); - }, 1); - - } - - - default void potion(Player p, PotionEffectType type, int power, int duration) { - p.addPotionEffect(new PotionEffect(type, power, duration, true, false, false)); - } - - default double blockXP(Block block, double xp) { - try { - return Math.round(xp * getBlockMultiplier(block)); - } catch (Exception e) { - Adapt.verbose("Error in blockXP: " + e.getMessage()); - } - return xp; - } - - default double getBlockMultiplier(Block block) { - return WorldData.of(block.getWorld()).reportEarnings(block); - } - - default double getValue(Material material) { - return MaterialValue.getValue(material); - } - - default double getValue(BlockData block) { - return MaterialValue.getValue(block.getMaterial()); - } - - default double getValue(ItemStack f) { - return MaterialValue.getValue(f.getType()); - } - - default double getValue(Block block) { - return MaterialValue.getValue(block.getType()); - } - - default void vfxMovingSphere(Location startLocation, Location endLocation, int ticks, Color color, double size, double density) { - if (!areParticlesEnabled()) { - return; - } - - World world = startLocation.getWorld(); - double startX = startLocation.getX(); - double startY = startLocation.getY(); - double startZ = startLocation.getZ(); - double endX = endLocation.getX(); - double endY = endLocation.getY(); - double endZ = endLocation.getZ(); - double deltaX = (endX - startX) / ticks; - double deltaY = (endY - startY) / ticks; - double deltaZ = (endZ - startZ) / ticks; - Particle.DustOptions dustOptions = new Particle.DustOptions(color, (float) size); - - new BukkitRunnable() { - int tick = 0; - - public void run() { - if (tick >= ticks) { - cancel(); - return; - } - double x = startX + deltaX * tick; - double y = startY + deltaY * tick; - double z = startZ + deltaZ * tick; - Location particleLocation = new Location(world, x, y, z); - - for (double i = 0; i < Math.PI; i += Math.PI / density) { - double radius = Math.sin(i) * size; - double yCoord = Math.cos(i) * size; - for (double j = 0; j < Math.PI * 2; j += Math.PI / density) { - double xCoord = Math.sin(j) * radius; - double zCoord = Math.cos(j) * radius; - - Location loc = particleLocation.clone().add(xCoord, yCoord, zCoord); - world.spawnParticle(REDSTONE, loc, 0, 0, 0, 0, dustOptions); - } - } - - tick++; - } - }.runTaskTimer(Adapt.instance, 0, 1); - } - - default void vfxMovingSwirlingSphere(Location startLocation, Location endLocation, int ticks, Color color, double size, double swirlRadius, double density) { - if (!areParticlesEnabled()) { - return; - } - - World world = startLocation.getWorld(); - double startX = startLocation.getX(); - double startY = startLocation.getY(); - double startZ = startLocation.getZ(); - double endX = endLocation.getX(); - double endY = endLocation.getY(); - double endZ = endLocation.getZ(); - double deltaX = (endX - startX) / ticks; - double deltaY = (endY - startY) / ticks; - double deltaZ = (endZ - startZ) / ticks; - Particle.DustOptions dustOptions = new Particle.DustOptions(color, (float) size); - - new BukkitRunnable() { - int tick = 0; - - public void run() { - if (tick >= ticks) { - cancel(); - return; - } - double x = startX + deltaX * tick; - double y = startY + deltaY * tick; - double z = startZ + deltaZ * tick; - - // Add swirling effect - double swirlAngle = 2 * Math.PI * tick / ticks; - x += swirlRadius * Math.cos(swirlAngle); - z += swirlRadius * Math.sin(swirlAngle); - - Location particleLocation = new Location(world, x, y, z); - - for (double i = 0; i < Math.PI; i += Math.PI / density) { - double radius = Math.sin(i) * size; - double yCoord = Math.cos(i) * size; - for (double j = 0; j < Math.PI * 2; j += Math.PI / density) { - double xCoord = Math.sin(j) * radius; - double zCoord = Math.cos(j) * radius; - - Location loc = particleLocation.clone().add(xCoord, yCoord, zCoord); - world.spawnParticle(REDSTONE, loc, 0, 0, 0, 0, dustOptions); - } - } - - tick++; - } - }.runTaskTimer(Adapt.instance, 0, 1); - } - - default void vfxPlayerBoundingBoxOutline(Player player, Color color, int ticks, int particleCount) { - if (!areParticlesEnabled()) { - return; - } - - World world = player.getWorld(); - Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f); - - new BukkitRunnable() { - int tick = 0; - - public void run() { - if (tick >= ticks) { - cancel(); - return; - } - - BoundingBox boundingBox = player.getBoundingBox(); - double minX = boundingBox.getMinX(); - double minY = boundingBox.getMinY(); - double minZ = boundingBox.getMinZ(); - double maxX = boundingBox.getMaxX(); - double maxY = boundingBox.getMaxY(); - double maxZ = boundingBox.getMaxZ(); - - for (int i = 0; i < particleCount; i++) { - double t = (double) i / (particleCount - 1); - - // Edges along X-axis - world.spawnParticle(REDSTONE, minX + t * (maxX - minX), minY, minZ, 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, minX + t * (maxX - minX), maxY, minZ, 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, minX + t * (maxX - minX), minY, maxZ, 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, minX + t * (maxX - minX), maxY, maxZ, 0, 0, 0, 0, dustOptions); - - // Edges along Y-axis - world.spawnParticle(REDSTONE, minX, minY + t * (maxY - minY), minZ, 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, maxX, minY + t * (maxY - minY), minZ, 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, minX, minY + t * (maxY - minY), maxZ, 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, maxX, minY + t * (maxY - minY), maxZ, 0, 0, 0, 0, dustOptions); - - // Edges along Z-axis - world.spawnParticle(REDSTONE, minX, minY, minZ + t * (maxZ - minZ), 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, maxX, minY, minZ + t * (maxZ - minZ), 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, minX, maxY, minZ + t * (maxZ - minZ), 0, 0, 0, 0, dustOptions); - world.spawnParticle(REDSTONE, maxX, maxY, minZ + t * (maxZ - minZ), 0, 0, 0, 0, dustOptions); - } - - tick++; - } - }.runTaskTimer(Adapt.instance, 0, 1); - } - - default void vfxVortexSphere(Location startLocation, Location endLocation, int ticks, Color color, double radius) { - if (!areParticlesEnabled()) { - return; - } - - World world = startLocation.getWorld(); - Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f); - - double startX = startLocation.getX(); - double startY = startLocation.getY(); - double startZ = startLocation.getZ(); - double endX = endLocation.getX(); - double endY = endLocation.getY(); - double endZ = endLocation.getZ(); - double deltaX = (endX - startX) / ticks; - double deltaY = (endY - startY) / ticks; - double deltaZ = (endZ - startZ) / ticks; - - new BukkitRunnable() { - int tick = 0; - - public void run() { - if (tick >= ticks) { - cancel(); - return; - } - - double x = startX + deltaX * tick; - double y = startY + deltaY * tick; - double z = startZ + deltaZ * tick; - Location particleLocation = new Location(world, x, y, z); - - double currentRadius = radius * (1 - (double) tick / ticks); - - for (double theta = 0; theta < 2 * Math.PI; theta += Math.PI / 10) { - for (double phi = 0; phi < Math.PI; phi += Math.PI / 10) { - double xCoord = currentRadius * Math.sin(phi) * Math.cos(theta); - double yCoord = currentRadius * Math.sin(phi) * Math.sin(theta); - double zCoord = currentRadius * Math.cos(phi); - - Location loc = particleLocation.clone().add(xCoord, yCoord, zCoord); - world.spawnParticle(REDSTONE, loc, 0, 0, 0, 0, dustOptions); - } - } - - tick++; - } - }.runTaskTimer(Adapt.instance, 0, 1); - } - - - default void vfxDome(Location center, double range, Color color, int particleCount) { - if (!areParticlesEnabled()) { - return; - } - - Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1); - World world = center.getWorld(); - - for (int i = 0; i < particleCount; i++) { - double theta = 2 * Math.PI * RANDOM.nextDouble(); - double phi = Math.PI / 2 * RANDOM.nextDouble(); // Adjusted range of phi to create a dome - double x = range * Math.sin(phi) * Math.cos(theta); - double y = range * Math.sin(phi) * Math.sin(theta); - double z = range * Math.cos(phi); - - Location particleLocation = center.clone().add(x, y, z); - world.spawnParticle(REDSTONE, particleLocation, 0, 0, 0, 0, dustOptions); - } - } - - default void vfxSphereV1(Player p, Location l, double radius, Particle particle, int verticalDensity, int radialDensity) { - if (!areParticlesEnabled()) { - return; - } - - for (double phi = 0; phi <= Math.PI; phi += Math.PI / verticalDensity) { - for (double theta = 0; theta <= 2 * Math.PI; theta += Math.PI / radialDensity) { - double x = radius * Math.cos(theta) * Math.sin(phi); - double y = radius * Math.cos(phi) + 1.5; - double z = radius * Math.sin(theta) * Math.sin(phi); - - l.add(x, y, z); - p.getWorld().spawnParticle(particle, l, 1, 0F, 0F, 0F, 0.001); - l.subtract(x, y, z); - } - } - } - - - default void vfxZuck(Location from, Location to) { - if (!areParticlesEnabled()) { - return; - } - - Vector v = from.clone().subtract(to).toVector(); - double l = v.length(); - v.normalize(); - if (AdaptConfig.get().isUseEnchantmentTableParticleForActiveEffects()) { - from.getWorld().spawnParticle(ENCHANTMENT_TABLE, to, 1, 6, 6, 6, 0.6); - } - } - - default void vfxZuck(Location from, Location to, Particle particle) { - if (!areParticlesEnabled()) { - return; - } - - Vector v = from.clone().subtract(to).toVector(); - double l = v.length(); - v.normalize(); - if (AdaptConfig.get().isUseEnchantmentTableParticleForActiveEffects()) { - from.getWorld().spawnParticle(particle, to, 1, 6, 6, 6, 0.6); - } - } - - default void safeGiveItem(Player player, Entity itemEntity, ItemStack is) { - EntityPickupItemEvent e = new EntityPickupItemEvent(player, (Item) itemEntity, 0); - Bukkit.getPluginManager().callEvent(e); - if (!e.isCancelled()) { - itemEntity.remove(); - if (!player.getInventory().addItem(is).isEmpty()) { - player.getWorld().dropItem(player.getLocation(), is); - } - } - } - - - default void safeGiveItem(Player player, ItemStack item) { - if (!player.getInventory().addItem(item).isEmpty()) { - player.getWorld().dropItem(player.getLocation(), item); - } - } - - - default void vfxParticleLine(Location start, Location end, Particle particle, int pointsPerLine, int particleCount, double offsetX, double offsetY, double offsetZ, double extra, @Nullable Double data, boolean forceDisplay, - @Nullable Predicate operationPerPoint) { - if (!areParticlesEnabled()) { - return; - } - - double d = start.distance(end) / pointsPerLine; - for (int i = 0; i < pointsPerLine; i++) { - Location l = start.clone(); - Vector direction = end.toVector().subtract(start.toVector()).normalize(); - Vector v = direction.multiply(i * d); - l.add(v.getX(), v.getY(), v.getZ()); - if (operationPerPoint == null) { - start.getWorld().spawnParticle(particle, l, particleCount, offsetX, offsetY, offsetZ, extra, data, forceDisplay); - continue; - } - if (operationPerPoint.test(l)) { - start.getWorld().spawnParticle(particle, l, particleCount, offsetX, offsetY, offsetZ, extra, data, forceDisplay); - } - } - } - - default void vfxParticleLine(Location start, Location end, int particleCount, Particle particle) { - if (!areParticlesEnabled()) { - return; - } - - World world = start.getWorld(); - double distance = start.distance(end); - Vector direction = end.toVector().subtract(start.toVector()).normalize(); - double step = distance / (particleCount - 1); - - for (int i = 0; i < particleCount; i++) { - Location particleLocation = start.clone().add(direction.clone().multiply(i * step)); - world.spawnParticle(particle, particleLocation, 1); - } - } - - - private List getHollowCuboid(Location loc, double particleDistance) { - List result = Lists.newArrayList(); - World world = loc.getWorld(); - double minX = loc.getBlockX(); - double minY = loc.getBlockY(); - double minZ = loc.getBlockZ(); - double maxX = loc.getBlockX() + 1; - double maxY = loc.getBlockY() + 1; - double maxZ = loc.getBlockZ() + 1; - - for (double x = minX; x <= maxX; x += particleDistance) { - for (double y = minY; y <= maxY; y += particleDistance) { - for (double z = minZ; z <= maxZ; z += particleDistance) { - int components = 0; - if (x == minX || x == maxX) components++; - if (y == minY || y == maxY) components++; - if (z == minZ || z == maxZ) components++; - if (components >= 2) { - result.add(new Location(world, x, y, z)); - } - } - } - } - return result; - } - - private List getHollowCuboid(Location loc, Location loc2, double particleDistance) { - List result = Lists.newArrayList(); - World world = loc.getWorld(); - - double minX = loc.getBlockX(); - double minY = loc.getBlockY(); - double minZ = loc.getBlockZ(); - double maxX = loc2.getBlockX() + 1; - double maxY = loc2.getBlockY() + 1; - double maxZ = loc2.getBlockZ() + 1; - - for (double x = minX; x <= maxX; x += particleDistance) { - for (double y = minY; y <= maxY; y += particleDistance) { - for (double z = minZ; z <= maxZ; z += particleDistance) { - int components = 0; - if (x == minX || x == maxX) components++; - if (y == minY || y == maxY) components++; - if (z == minZ || z == maxZ) components++; - if (components >= 2) { - result.add(new Location(world, x, y, z)); - } - } - } - } - return result; - } - - default void vfxCuboidOutline(Block block, Particle particle) { - if (!areParticlesEnabled()) { - return; - } - - List hollowCube = getHollowCuboid(block.getLocation(), 0.25); - for (Location l : hollowCube) { - block.getWorld().spawnParticle(particle, l, 1, 0F, 0F, 0F, 0.000); - } - } - - default void vfxCuboidOutline(Block blockStart, Block blockEnd, Particle particle) { - if (!areParticlesEnabled()) { - return; - } - - List hollowCube = getHollowCuboid(blockStart.getLocation(), blockEnd.getLocation(), 0.25); - for (Location l : hollowCube) { - blockStart.getWorld().spawnParticle(particle, l, 2, 0F, 0F, 0F, 0.000); - } - } - - default void vfxCuboidOutline(Block blockStart, Block blockEnd, Color color, int size) { - if (!areParticlesEnabled()) { - return; - } - - List hollowCube = getHollowCuboid(blockStart.getLocation(), blockEnd.getLocation(), 0.25); - Particle.DustOptions dustOptions = new Particle.DustOptions(color, size); - for (Location l : hollowCube) { - blockStart.getWorld().spawnParticle(REDSTONE, l, 2, 0F, 0F, 0F, 0.000, dustOptions); - } - } - - default void vfxPrismOutline(Location placer, double outset, Particle particle, int particleCount) { - if (!areParticlesEnabled()) { - return; - } - - - Location top = new Location(placer.getWorld(), placer.getX(), placer.getY() + outset, placer.getZ()); - Location baseCorner1 = new Location(placer.getWorld(), placer.getX() - outset, placer.getY(), placer.getZ() - outset); - Location baseCorner2 = new Location(placer.getWorld(), placer.getX() + outset, placer.getY(), placer.getZ() - outset); - Location baseCorner3 = new Location(placer.getWorld(), placer.getX() + outset, placer.getY(), placer.getZ() + outset); - Location baseCorner4 = new Location(placer.getWorld(), placer.getX() - outset, placer.getY(), placer.getZ() + outset); - - vfxParticleLine(baseCorner1, baseCorner2, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); - vfxParticleLine(baseCorner2, baseCorner3, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); - vfxParticleLine(baseCorner3, baseCorner4, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); - vfxParticleLine(baseCorner4, baseCorner1, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); - - for (Location location : Arrays.asList(baseCorner1, baseCorner2, baseCorner3, baseCorner4)) { - vfxParticleLine(location, top, particle, particleCount, 1, 0.0D, 0D, 0.0D, 0D, null, true, l -> l.getBlock().isPassable()); - } - } - - default void vfxFastSphere(Location center, double range, Color color, int particleCount) { - if (!areParticlesEnabled()) { - return; - } - - Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1); - World world = center.getWorld(); - - for (int i = 0; i < particleCount; i++) { - double x, y, z; - do { - x = RANDOM.nextDouble() * 2 - 1; - y = RANDOM.nextDouble() * 2 - 1; - z = RANDOM.nextDouble() * 2 - 1; - } while (x * x + y * y + z * z > 1); - - double magnitude = Math.sqrt(x * x + y * y + z * z); - x = x / magnitude * range; - y = y / magnitude * range; - z = z / magnitude * range; - - Location particleLocation = center.clone().add(x, y, z); - world.spawnParticle(REDSTONE, particleLocation, 0, 0, 0, 0, dustOptions); - } - } - - default void vfxLoadingRing(Location center, double radius, Color color, int durationTicks, int particleCount) { - if (!areParticlesEnabled()) { - return; - } - - World world = center.getWorld(); - Particle.DustOptions dustOptions = new Particle.DustOptions(color, 1.0f); - - new BukkitRunnable() { - int tick = 0; - - public void run() { - if (tick >= durationTicks) { - cancel(); - return; - } - - double angle = 2 * Math.PI * tick / durationTicks; - double x = radius * Math.cos(angle); - double z = radius * Math.sin(angle); - Location particleLocation = center.clone().add(x, 0, z); - world.spawnParticle(REDSTONE, particleLocation, particleCount, 0, 0, 0, dustOptions); - - tick++; - } - }.runTaskTimer(Adapt.instance, 0, 1); - } - - default void vfxLoadingRing(Location center, double radius, Particle particle, int durationTicks, int particleCount) { - if (!areParticlesEnabled()) { - return; - } - - World world = center.getWorld(); - - new BukkitRunnable() { - int tick = 0; - - public void run() { - if (tick >= durationTicks) { - cancel(); - return; - } - - double angle = 2 * Math.PI * tick / durationTicks; - double x = radius * Math.cos(angle); - double z = radius * Math.sin(angle); - Location particleLocation = center.clone().add(x, 0, z); - world.spawnParticle(particle, particleLocation, particleCount, 0, 0, 0); - - tick++; - } - }.runTaskTimer(Adapt.instance, 0, 1); - } - - - default void vfxLevelUp(Player p) { - if (!areParticlesEnabled()) { - return; - } - - p.spawnParticle(Particle.REVERSE_PORTAL, p.getLocation().clone().add(0, 1.7, 0), 100, 0.1, 0.1, 0.1, 4.1); - } - - default void vfxFastRing(Location location, double radius, Color color) { - if (!areParticlesEnabled()) { - return; - } - - for (int d = 0; d <= 90; d += 1) { - Location particleLoc = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); - particleLoc.setX(location.getX() + Math.cos(d) * radius); - particleLoc.setZ(location.getZ() + Math.sin(d) * radius); - location.getWorld().spawnParticle(REDSTONE, particleLoc, 1, new Particle.DustOptions(color, 1)); - } - } - - default void vfxFastRing(Location location, double radius, Particle particle) { - if (!areParticlesEnabled()) { - return; - } - - for (int d = 0; d <= 90; d += 1) { - Location particleLoc = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); - particleLoc.setX(location.getX() + Math.cos(d) * radius); - particleLoc.setZ(location.getZ() + Math.sin(d) * radius); - location.getWorld().spawnParticle(particle, particleLoc, 1); - } - } - - default void vfxFastRing(Location location, double radius, Particle particle, int angle) { - if (!areParticlesEnabled()) { - return; - } - - for (int d = 0; d <= 90; d += angle) { - Location particleLoc = new Location(location.getWorld(), location.getX(), location.getY(), location.getZ()); - particleLoc.setX(location.getX() + Math.cos(d) * radius); - particleLoc.setZ(location.getZ() + Math.sin(d) * radius); - location.getWorld().spawnParticle(particle, particleLoc, 1); - } - } - - default void vfxShootParticle(Player player, Particle particle, double velocity, int count) { - if (!areParticlesEnabled()) { - return; - } - - Location location = player.getEyeLocation(); - Vector direction = location.getDirection(); - for (int i = 0; i < count; i++) { - player.getWorld().spawnParticle(particle, location.getX(), location.getY(), location.getZ(), 0, (float) direction.getX(), (float) direction.getY(), (float) direction.getZ(), velocity, null); - } - } - - default void vfxParticleSpiral(Location center, int radius, int height, Particle type) { - if (!areParticlesEnabled()) { - return; - } - - double angle = 0; - for (int i = 0; i <= height; i++) { - double x = center.getX() + (radius * Math.cos(angle)); - double z = center.getZ() + (radius * Math.sin(angle)); - center.getWorld().spawnParticle(type, x, +center.getY(), z, 1, 0, 0, 0, 0); - angle += 0.1; - } - } - - - default void vfxXP(Player p, Location l, int amt) { - if (!areParticlesEnabled()) { - return; - } - - if (AdaptConfig.get().isUseEnchantmentTableParticleForActiveEffects()) { - p.spawnParticle(ENCHANTMENT_TABLE, l, Math.min(amt / 10, 20), 0.5, 0.5, 0.5, 1); - } - } - - default void vfxXP(Location l) { - if (!areParticlesEnabled()) { - return; - } - - if (AdaptConfig.get().isUseEnchantmentTableParticleForActiveEffects()) { - l.getWorld().spawnParticle(ENCHANTMENT_TABLE, l.add(0, 1.7, 0), 3, 0.1, 0.1, 0.1, 3); - } - } - - default void damageHand(Player p, int damage) { - ItemStack is = p.getInventory().getItemInMainHand(); - ItemMeta im = is.getItemMeta(); - - if (im == null) { - return; - } - - if (im.isUnbreakable()) { - return; - } - - Damageable dm = (Damageable) im; - dm.setDamage(dm.getDamage() + damage); - - if (dm.getDamage() > is.getType().getMaxDurability()) { - p.getInventory().setItemInMainHand(new ItemStack(Material.AIR)); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f); - return; - } - - is.setItemMeta(im); - p.getInventory().setItemInMainHand(is); - } - - default void damageOffHand(Player p, int damage) { - ItemStack is = p.getInventory().getItemInOffHand(); - ItemMeta im = is.getItemMeta(); - - if (im == null) { - return; - } - - if (im.isUnbreakable()) { - return; - } - - Damageable dm = (Damageable) im; - dm.setDamage(dm.getDamage() + damage); - - if (dm.getDamage() > is.getType().getMaxDurability()) { - p.getInventory().setItemInOffHand(new ItemStack(Material.AIR)); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1f, 1f); - return; - } - - is.setItemMeta(im); - p.getInventory().setItemInOffHand(is); - } - - default Block getRightBlock(Player p, Block b) { - Location l = p.getLocation(); - float yaw = l.getYaw(); - // Make sure yaw is in the range 0 to 360 - while (yaw < 0) { - yaw += 360; - } - yaw = yaw % 360; - // The player's yaw is their rotation in the world, - // so, we can use that to get the right face of a block! - BlockFace rightFace; - // if the player is facing SE to SW - if (yaw < 45 || yaw >= 315) { - rightFace = BlockFace.EAST; - return b.getRelative(rightFace); - } - // if the player is facing SW to NW - else if (yaw < 135) { - rightFace = BlockFace.SOUTH; - return b.getRelative(rightFace); - } - // if the player is facing NW to NE - else if (yaw < 225) { - rightFace = BlockFace.WEST; - return b.getRelative(rightFace); - } - // if the player is facing NE to SE - else if (yaw < 315) { - rightFace = BlockFace.NORTH; - return b.getRelative(rightFace); - } else { - return null; - } - } - - default Block getLeftBlock(Player p, Block b) { - Location l = p.getLocation(); - float yaw = l.getYaw(); - - // Make sure yaw is in the range 0 to 360 - while (yaw < 0) { - yaw += 360; - } - yaw = yaw % 360; - // The player's yaw is their rotation in the world, - // so, we can use that to get the right face of a block! - BlockFace leftFace; - // if the player is facing SE to SW - if (yaw < 45 || yaw >= 315) { - leftFace = BlockFace.WEST; - return b.getRelative(leftFace); - } - // if the player is facing SW to NW - else if (yaw < 135) { - leftFace = BlockFace.NORTH; - return b.getRelative(leftFace); - } - // if the player is facing NW to NE - else if (yaw < 225) { - leftFace = BlockFace.EAST; - return b.getRelative(leftFace); - } - // if the player is facing NE to SE - else if (yaw < 315) { - leftFace = BlockFace.SOUTH; - return b.getRelative(leftFace); - } else { - return null; - } - } - - - default void setExp(Player p, int exp) { - p.setExp(0); - p.setLevel(0); - p.setTotalExperience(0); - - if (exp <= 0) { - return; - } - - giveExp(p, exp); - } - - default void giveExp(Player p, int exp) { - while (exp > 0) { - int xp = getExpToLevel(p) - getExp(p); - if (xp > exp) { - xp = exp; - } - p.giveExp(xp); - exp -= xp; - } - } - - default void takeExp(Player p, int exp) { - takeExp(p, exp, true); - } - - default void takeExp(Player p, int exp, boolean fromTotal) { - int xp = getTotalExp(p); - - if (fromTotal) { - xp -= exp; - } else { - int m = getExp(p) - exp; - if (m < 0) { - m = 0; - } - xp -= getExp(p) + m; - } - - setExp(p, xp); - } - - default int getExp(Player p) { - return (int) (getExpToLevel(p) * p.getExp()); - } - - default int getTotalExp(Player p) { - return getTotalExp(p, false); - } - - default int getTotalExp(Player p, boolean recalc) { - if (recalc) { - recalcTotalExp(p); - } - return p.getTotalExperience(); - } - - default int getLevel(Player p) { - return p.getLevel(); - } - - default int getExpToLevel(Player p) { - return p.getExpToLevel(); - } - - default int getExpToLevel(int level) { - return level >= 30 ? 62 + (level - 30) * 7 : (level >= 15 ? 17 + (level - 15) * 3 : 17); - } - - default void recalcTotalExp(Player p) { - int total = getExp(p); - for (int i = 0; i < p.getLevel(); i++) { - total += getExpToLevel(i); - } - p.setTotalExperience(total); - } - - /** - * Takes a custom amount of the item stack exact type (Ignores the item amount) - * - * @param inv the inv - * @param is the item ignore the amount - * @param amount the amount to use - * @return true if taken, false if not (missing) - */ - default boolean takeAll(Inventory inv, ItemStack is, int amount) { - ItemStack isf = is.clone(); - isf.setAmount(amount); - return takeAll(inv, is); - } - - /** - * Take one of an exact type ignoring the item stack amount - * - * @param inv the inv - * @param is the item ignoring the amount - * @return true if taken, false if diddnt - */ - default boolean takeOne(Inventory inv, ItemStack is, int amount) { - return takeAll(inv, is, 1); - } - - /** - * Take a specific amount of an EXACT META TYPE from an inventory - * - * @param inv the inv - * @param is uses the amount - * @return returns false if it couldnt get enough (and none was taken) - */ - default boolean takeAll(Inventory inv, ItemStack is) { - ItemStack[] items = inv.getStorageContents(); - - int take = is.getAmount(); - - for (int ii = 0; ii < items.length; ii++) { - ItemStack i = items[ii]; - - if (i == null) { - continue; - } - - if (i.isSimilar(is)) { - if (take > i.getAmount()) { - i.setAmount(i.getAmount() - take); - items[ii] = i; - take = 0; - break; - } else { - items[ii] = null; - take -= i.getAmount(); - } - } - } - - if (take > 0) { - return false; - } - - inv.setStorageContents(items); - return true; - } -} diff --git a/src/main/java/com/volmit/adapt/api/adaptation/Adaptation.java b/src/main/java/com/volmit/adapt/api/adaptation/Adaptation.java deleted file mode 100644 index 2b16e81b7..000000000 --- a/src/main/java/com/volmit/adapt/api/adaptation/Adaptation.java +++ /dev/null @@ -1,873 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.adaptation; - -import com.google.common.collect.ImmutableSet; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.Component; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.protection.Protector; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.tick.Ticked; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerData; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.content.event.AdaptAdaptationUseEvent; -import com.volmit.adapt.util.*; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import org.bukkit.inventory.Recipe; - -import java.lang.reflect.Field; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -public interface Adaptation extends Ticked, Component { - Map PERMANENT_LEARN_CONFIRMATIONS = new ConcurrentHashMap<>(); - Map USAGE_BASELINE_XP_COOLDOWNS = new ConcurrentHashMap<>(); - long PERMANENT_LEARN_CONFIRM_WINDOW_MS = 6_000L; - - int getMaxLevel(); - - default void xp(Player p, double amount) { - xp(p, amount, null); - } - - default void xp(Player p, double amount, String rewardKey) { - getSkill().xp(p, amount, adaptationRewardKey(rewardKey)); - } - - default void xp(Player p, Location l, double amount) { - xp(p, l, amount, null); - } - - default void xp(Player p, Location l, double amount, String rewardKey) { - getSkill().xp(p, l, amount, adaptationRewardKey(rewardKey)); - } - - default void xpSilent(Player p, double amount, String rewardKey) { - getSkill().xpSilent(p, amount, adaptationRewardKey(rewardKey)); - } - - default void xpSilent(Player p, double amount) { - xpSilent(p, amount, null); - } - - default String adaptationRewardKey(String rewardKey) { - String suffix = rewardKey == null ? "" : rewardKey.trim(); - if (suffix.isEmpty()) { - suffix = "use"; - } - return "adaptation:" + getName() + ":" + suffix; - } - - @Override - default boolean areParticlesEnabled() { - if (!Component.super.areParticlesEnabled()) { - return false; - } - - AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); - if (effects != null && effects.getAdaptationParticleOverrides() != null && !effects.getAdaptationParticleOverrides().isEmpty()) { - String key = getName(); - Boolean override = effects.getAdaptationParticleOverrides().get(key); - if (override == null && key != null) { - override = effects.getAdaptationParticleOverrides().get(key.toLowerCase(Locale.ROOT)); - } - if (override != null && !override) { - return false; - } - } - - Object config = getConfig(); - if (config == null) { - return true; - } - - Boolean directToggle = readBooleanField(config, "showParticles"); - if (directToggle != null) { - return directToggle; - } - - Boolean genericToggle = readBooleanField(config, "showParticleEffects"); - if (genericToggle != null) { - return genericToggle; - } - - return true; - } - - @Override - default boolean areSoundsEnabled() { - if (!Component.super.areSoundsEnabled()) { - return false; - } - - Object config = getConfig(); - if (config == null) { - return true; - } - - Boolean directToggle = readBooleanField(config, "showSounds"); - if (directToggle != null) { - return directToggle; - } - - return true; - } - - private static Boolean readBooleanField(Object source, String fieldName) { - if (source == null || fieldName == null || fieldName.isBlank()) { - return null; - } - - Class current = source.getClass(); - while (current != null) { - try { - Field field = current.getDeclaredField(fieldName); - field.setAccessible(true); - Object value = field.get(source); - if (value instanceof Boolean bool) { - return bool; - } - return null; - } catch (NoSuchFieldException ignored) { - current = current.getSuperclass(); - } catch (Throwable ignored) { - return null; - } - } - - return null; - } - - default void awardUsageBaselineXp(Player p, int level) { - if (p == null || level <= 0 || !p.getClass().getSimpleName().equals("CraftPlayer")) { - return; - } - - AdaptConfig.AdaptationXp cfg = AdaptConfig.get().getAdaptationXp(); - if (cfg == null || !cfg.isUsageBaselineEnabled()) { - return; - } - - long now = M.ms(); - long cooldown = Math.max(250L, cfg.getUsageBaselineCooldownMillis()); - String key = p.getUniqueId() + "|" + getName(); - Long next = USAGE_BASELINE_XP_COOLDOWNS.get(key); - if (next != null && next > now) { - return; - } - - if (USAGE_BASELINE_XP_COOLDOWNS.size() > 6000) { - USAGE_BASELINE_XP_COOLDOWNS.entrySet().removeIf(i -> i.getValue() <= now); - } - - double reward = cfg.getUsageBaselineXp() + ((Math.max(1, level) - 1) * cfg.getUsageBaselineXpPerLevel()); - if (reward <= 0) { - return; - } - - USAGE_BASELINE_XP_COOLDOWNS.put(key, now + cooldown); - xpSilent(p, reward, "baseline-use"); - } - - default F getStorage(Player p, String key, F defaultValue) { - PlayerData data = getPlayer(p).getData(); - PlayerSkillLine line = data.getSkillLineNullable(getSkill().getName()); - if (line == null) return defaultValue; - PlayerAdaptation adaptation = line.getAdaptation(getName()); - if (adaptation == null) return defaultValue; - Object o = adaptation.getStorage().get(key); - return o == null ? defaultValue : (F) o; - } - - default F getStorage(Player p, String key) { - return getStorage(p, key, null); - } - - default boolean setStorage(Player p, String key, Object value) { - PlayerData data = getPlayer(p).getData(); - PlayerSkillLine line = data.getSkillLineNullable(getSkill().getName()); - if (line == null) return false; - PlayerAdaptation adaptation = line.getAdaptation(getName()); - if (adaptation == null) return false; - if (value == null) { - adaptation.getStorage().remove(key); - return true; - } - - adaptation.getStorage().put(key, value); - return true; - } - - default boolean canUse(AdaptPlayer player) { - Adapt.verbose("Checking if " + player.getPlayer().getName() + " can use " + getName() + "..."); - AdaptAdaptationUseEvent e = new AdaptAdaptationUseEvent(!Bukkit.isPrimaryThread(), player, this); - Bukkit.getServer().getPluginManager().callEvent(e); - return (!e.isCancelled()); - } - - default boolean canUse(Player player) { - return canUse(getPlayer(player)); - } - - default boolean hasBlacklistPermission(Player p, Adaptation a) { - if (p.isOp()) { // If the player is an operator, bypass the permission check - return false; - } - String blacklistPermission = "adapt.blacklist." + a.getName().replaceAll("-", ""); - Adapt.verbose("Checking if player " + p.getName() + " has blacklist permission " + blacklistPermission); - - return p.hasPermission(blacklistPermission); - } - - default String getStorageString(Player p, String key, String defaultValue) { - return getStorage(p, key, defaultValue); - } - - default String getStorageString(Player p, String key) { - return getStorage(p, key); - } - - default Integer getStorageInt(Player p, String key, Integer defaultValue) { - return getStorage(p, key, defaultValue); - } - - default Integer getStorageInt(Player p, String key) { - return getStorage(p, key); - } - - default Double getStorageDouble(Player p, String key, Double defaultValue) { - return getStorage(p, key, defaultValue); - } - - default Double getStorageDouble(Player p, String key) { - return getStorage(p, key); - } - - default Boolean getStorageBoolean(Player p, String key, Boolean defaultValue) { - return getStorage(p, key, defaultValue); - } - - default Boolean getStorageBoolean(Player p, String key) { - return getStorage(p, key); - } - - default Long getStorageLong(Player p, String key, Long defaultValue) { - return getStorage(p, key, defaultValue); - } - - default Long getStorageLong(Player p, String key) { - return getStorage(p, key); - } - - Class getConfigurationClass(); - - void registerConfiguration(Class type); - - boolean isEnabled(); - - boolean isPermanent(); - - T getConfig(); - - AdaptAdvancement buildAdvancements(); - - void addStats(int level, Element v); - - int getBaseCost(); - - String getDescription(); - - Material getIcon(); - - Skill getSkill(); - - void setSkill(Skill skill); - - String getName(); - - int getInitialCost(); - - double getCostFactor(); - - List getRecipes(); - - List getBrewingRecipes(); - - void onRegisterAdvancements(List advancements); - - default Set getProtectors() { - Set protectors = new HashSet<>(Adapt.instance.getProtectorRegistry().getDefaultProtectors()); - Map overrides = AdaptConfig.get().getProtectionOverrides().getOrDefault(this.getName(), Collections.emptyMap()); - overrides.forEach((protector, enabled) -> { - if (enabled) { - Protector p = Adapt.instance.getProtectorRegistry().getAllProtectors() - .stream() - .filter(pr -> pr.getName().equals(protector)) - .findFirst() - .orElse(null); - if (p == null) { - Adapt.error("Could not find protector " + protector + " for adaptation " + this.getName() + ". Skipping..."); - } else { - protectors.add(p); - } - } else { - protectors.removeIf(pr -> pr.getName().equals(protector)); - } - }); - return ImmutableSet.copyOf(protectors); - } - - default boolean canBlockBreak(Player player, Location blockLocation) { - return getProtectors().stream().allMatch(protector -> protector.canBlockBreak(player, blockLocation, this)); - } - - default boolean canBlockPlace(Player player, Location blockLocation) { - return getProtectors().stream().allMatch(protector -> protector.canBlockPlace(player, blockLocation, this)); - } - - default boolean canPVP(Player player, Location victimLocation) { - return getProtectors().stream().allMatch(protector -> protector.canPVP(player, victimLocation, this)); - } - - default boolean canPVE(Player player, Location victimLocation) { - return getProtectors().stream().allMatch(protector -> protector.canPVE(player, victimLocation, this)); - } - - default boolean canInteract(Player player, Location targetLocation) { - return getProtectors().stream().allMatch(protector -> protector.canInteract(player, targetLocation, this)); - } - - default boolean canAccessChest(Player player, Location chestLocation) { - return getProtectors().stream().allMatch(protector -> protector.canAccessChest(player, chestLocation, this)); - } - - default boolean checkRegion(Player player) { - return getProtectors().stream().allMatch(protector -> protector.checkRegion(player, player.getLocation(), this)); - } - - default boolean hasUsageConflict(Player p) { - Map> conflicts = AdaptConfig.get().getAdaptationUsageConflicts(); - if (conflicts == null || conflicts.isEmpty()) { - return false; - } - - String me = getName().toLowerCase(Locale.ROOT); - Set denied = new HashSet<>(); - for (Map.Entry> entry : conflicts.entrySet()) { - if (entry.getKey() != null && entry.getValue() != null && entry.getKey().equalsIgnoreCase(me)) { - entry.getValue().stream() - .filter(Objects::nonNull) - .map(i -> i.toLowerCase(Locale.ROOT)) - .forEach(denied::add); - } - } - - for (Map.Entry> entry : conflicts.entrySet()) { - if (entry.getKey() == null || entry.getValue() == null) { - continue; - } - - boolean containsThisAdaptation = entry.getValue().stream() - .filter(Objects::nonNull) - .map(i -> i.toLowerCase(Locale.ROOT)) - .anyMatch(me::equals); - if (containsThisAdaptation) { - denied.add(entry.getKey().toLowerCase(Locale.ROOT)); - } - } - - denied.remove(me); - for (String conflict : denied) { - if (getPlayer(p).hasAdaptation(conflict)) { - Adapt.verbose("Player " + p.getName() + " has conflicting adaptation " + conflict + " and cannot use " + getName()); - return true; - } - } - - return false; - } - - default int getActiveLevel(Player p) { - try { - if (p == null || p.isDead()) { // Check if player is not invalid - return 0; - } - - int level = getLevel(p); - if (level <= 0) { - return 0; - } - - if (AdaptConfig.get().blacklistedWorlds.contains(p.getWorld().getName())) { - Adapt.verbose("Player " + p.getName() + " is in a blacklisted world. Skipping adaptation " + this.getName()); - return 0; - } - if (p.getGameMode().equals(GameMode.CREATIVE) || p.getGameMode().equals(GameMode.SPECTATOR)) { - Adapt.verbose("Player " + p.getName() + " is in creative or spectator mode. Skipping adaptation " + this.getName()); - return 0; - } - if (!checkRegion(p)) { - Adapt.verbose("Player " + p.getName() + " don't have adaptation - " + this.getName() + " permission."); - return 0; - } - - if (hasBlacklistPermission(p, this)) { - Adapt.verbose("Player " + p.getName() + " has blacklist permission for adaptation " + this.getName()); - return 0; - } - if (hasUsageConflict(p)) { - return 0; - } - if (!canUse(p)) { - Adapt.verbose("Player " + p.getName() + " can't use adaptation, This is an API restriction" + this.getName()); - return 0; - } - awardUsageBaselineXp(p, level); - Adapt.verbose("Player " + p.getName() + " used adaptation " + this.getName()); - return level; - } catch (Exception e) { - if (e instanceof IndexOutOfBoundsException) { // This is that fucking bug with Citizens Spoofing Players. I hate it. - Adapt.verbose("Citizens/PacketSpoofing is Messing stuff up again. I hate it."); - Adapt.verbose(e.getMessage()); - } else { - e.printStackTrace(); - } - return 0; - } - } - - default boolean hasAdaptation(Player p) { - return getActiveLevel(p) > 0; - } - - default int getLevel(Player p) { - if (p == null) { - return 0; - } - if (!p.getClass().getSimpleName().equals("CraftPlayer")) { - Adapt.verbose("Simple name: " + p.getClass().getSimpleName()); - return 0; - } - if (!this.isEnabled()) { - return 0; - } - if (!this.getSkill().isEnabled()) { - return 0; - } - AdaptPlayer adaptPlayer = getPlayer(p); - PlayerSkillLine line = adaptPlayer.getData().getSkillLine(getSkill().getName()); - if (line == null) { - return 0; - } - return line.getAdaptationLevel(getName()); - } - - default double getLevelPercent(Player p) { - if (!this.isEnabled()) { - return 0; - } - if (!this.getSkill().isEnabled()) { - return 0; - } - if (!p.getClass().getSimpleName().equals("CraftPlayer")) { - return 0.0; - } - return Math.min(Math.max(0, M.lerpInverse(0, getMaxLevel(), getLevel(p))), 1); - } - - default double getLevelPercent(int p) { - return Math.min(Math.max(0, M.lerpInverse(0, getMaxLevel(), p)), 1); - } - - default int getCostFor(int level) { - return (int) (Math.max(1, getBaseCost() + (getBaseCost() * (level * getCostFactor())))) + (level == 1 ? getInitialCost() : 0); - } - - default int getPowerCostFor(int level, int myLevel) { - return level - myLevel; - } - - default int getCostFor(int level, int myLevel) { - if (myLevel >= level) { - return 0; - } - - - int c = 0; - - for (int i = myLevel + 1; i <= level; i++) { - c += getCostFor(i); - } - - return c; - } - - default int getRefundCostFor(int level, int myLevel) { - if (myLevel <= level) { - return 0; - } - - int c = 0; - - for (int i = level + 1; i <= myLevel; i++) { - c += getCostFor(i); - } - - return c; - } - - default String getDisplayName() { - if (!this.isEnabled()) { - return C.DARK_GRAY + Form.capitalizeWords(getName().replaceAll("\\Q" + getSkill().getName() + "-\\E", "").replaceAll("\\Q-\\E", " ")); - } - if (!this.getSkill().isEnabled()) { - return C.DARK_GRAY + Form.capitalizeWords(getName().replaceAll("\\Q" + getSkill().getName() + "-\\E", "").replaceAll("\\Q-\\E", " ")); - } - return C.RESET + "" + C.BOLD + getSkill().getColor().toString() + Form.capitalizeWords(getName().replaceAll("\\Q" + getSkill().getName() + "-\\E", "").replaceAll("\\Q-\\E", " ")); - } - - default String getDisplayName(int level) { - if (!this.isEnabled()) { - return getDisplayName(); - } - if (!this.getSkill().isEnabled()) { - return getDisplayName(); - } - if (level >= 1) { - return getDisplayName() + C.RESET + " " + C.UNDERLINE + C.WHITE + Form.toRoman(level) + C.RESET; - } - - return getDisplayName(); - } - - default String getDisplayNameNoRoman(int level) { - if (level >= 1) { - return getDisplayName() + C.RESET + " " + C.UNDERLINE + C.WHITE + level + C.RESET; - } - - return getDisplayName(); - } - - default BlockFace getBlockFace(Player player, int maxrange) { - List lastTwoTargetBlocks = player.getLastTwoTargetBlocks(null, maxrange); - if (lastTwoTargetBlocks.size() != 2 || !lastTwoTargetBlocks.get(1).getType().isOccluding()) return null; - Block targetBlock = lastTwoTargetBlocks.get(1); - Block adjacentBlock = lastTwoTargetBlocks.get(0); - return targetBlock.getFace(adjacentBlock); - } - - default CustomModel getModel() { - return CustomModel.get(getIcon(), "adaptation", getName(), "icon"); - } - - default CustomModel getModel(int level) { - var model = CustomModel.get(getIcon(), "adaptation", getName(), "level-" + level); - if (model.material() == getIcon() && model.model() == 0) - model = CustomModel.get(Material.PAPER, "snippets", "gui", "level", String.valueOf(level)); - if (model.material() == Material.PAPER && model.model() == 0) - model = getModel(); - return model; - } - - default boolean openGui(Player player, boolean checkPermissions) { - if (hasBlacklistPermission(player, this)) { - return false; - } else { - openGui(player); - return true; - } - } - - default void openGui(Player player) { - openGui(player, 0); - } - - default void openGui(Player player, int page) { - if (!isEnabled()) { - return; - } - if (!getSkill().isEnabled()) { - return; - } - if (!Bukkit.isPrimaryThread()) { - int targetPage = page; - J.s(() -> openGui(player, targetPage)); - return; - } - - SoundPlayer spw = SoundPlayer.of(player.getWorld()); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 0.655f); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 0.855f); - - boolean reserveNavigation = AdaptConfig.get().isGuiBackButton(); - GuiLayout.PagePlan plan = GuiLayout.plan(getMaxLevel(), reserveNavigation); - int currentPage = GuiLayout.clampPage(page, plan.pageCount()); - int start = currentPage * plan.itemsPerPage(); - int end = Math.min(getMaxLevel(), start + plan.itemsPerPage()); - - int mylevel = getPlayer(player).getSkillLine(getSkill().getName()).getAdaptationLevel(getName()); - - long k = getPlayer(player).getData().getSkillLine(getSkill().getName()).getKnowledge(); - - Window w = new UIWindow(player); - GuiTheme.apply(w, "skill/" + getSkill().getName() + "/" + getName()); - w.setViewportHeight(plan.rows()); - - List reveal = new ArrayList<>(); - for (int row = 0; row < plan.contentRows(); row++) { - int rowStart = start + (row * GuiLayout.WIDTH); - if (rowStart >= end) { - break; - } - - int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); - for (int i = 0; i < rowCount; i++) { - int lvl = rowStart + i + 1; - int pos = GuiLayout.centeredPosition(i, rowCount); - int c = getCostFor(lvl, mylevel); - int rc = getRefundCostFor(lvl - 1, mylevel); - int pc = getPowerCostFor(lvl, mylevel); - boolean pendingPermanentConfirm = isPermanentLearnConfirmationPending(player, lvl); - Element de = new UIElement("lp-" + lvl + "g") - .setMaterial(new MaterialBlock(getIcon())) - .setModel(getModel(lvl)) - .setName(getDisplayName(lvl)) - .setEnchanted(mylevel >= lvl) - .setProgress(1D) - .addLore(Form.wrapWordsPrefixed(getDescription(), "" + C.GRAY, 40)) - .addLore(mylevel >= lvl ? ("") : ("" + C.WHITE + c + C.GRAY + " " + Localizer.dLocalize("snippets.adapt_menu.knowledge_cost") + " " + (AdaptConfig.get().isHardcoreNoRefunds() ? C.DARK_RED + "" + C.BOLD + Localizer.dLocalize("snippets.adapt_menu.no_refunds") : ""))) - .addLore(mylevel >= lvl ? AdaptConfig.get().isHardcoreNoRefunds() ? (C.GREEN + Localizer.dLocalize("snippets.adapt_menu.already_learned") + " " + C.DARK_RED + "" + C.BOLD + Localizer.dLocalize("snippets.adapt_menu.no_refunds")) : (isPermanent() ? "" : (C.GREEN + Localizer.dLocalize("snippets.adapt_menu.already_learned") + " " + C.GRAY + Localizer.dLocalize("snippets.adapt_menu.unlearn_refund") + " " + C.GREEN + rc + " " + Localizer.dLocalize("snippets.adapt_menu.knowledge_cost"))) : (k >= c ? (C.BLUE + Localizer.dLocalize("snippets.adapt_menu.click_learn") + " " + getDisplayName(lvl)) : (k == 0 ? (C.RED + Localizer.dLocalize("snippets.adapt_menu.no_knowledge")) : (C.RED + "(" + Localizer.dLocalize("snippets.adapt_menu.you_only_have") + " " + C.WHITE + k + C.RED + " " + Localizer.dLocalize("snippets.adapt_menu.knowledge_available") + ")")))) - .addLore(mylevel < lvl && getPlayer(player).getData().hasPowerAvailable(pc) ? C.GREEN + "" + lvl + " " + Localizer.dLocalize("snippets.adapt_menu.power_drain") : mylevel >= lvl ? C.GREEN + "" + lvl + " " + Localizer.dLocalize("snippets.adapt_menu.power_drain") : C.RED + Localizer.dLocalize("snippets.adapt_menu.not_enough_power") + "\n" + C.RED + Localizer.dLocalize("snippets.adapt_menu.how_to_level_up")) - .addLore((isPermanent() ? C.RED + "" + C.BOLD + Localizer.dLocalize("snippets.adapt_menu.may_not_unlearn") : "")) - .addLore(isPermanent() && mylevel < lvl - ? (pendingPermanentConfirm - ? C.GOLD + "" + C.BOLD + "Click again now to confirm permanent learn." - : C.YELLOW + "Double-click required to confirm permanent learn.") - : "") - .onLeftClick((e) -> { - if (mylevel >= lvl) { - unlearn(player, lvl, false); - spw.play(player.getLocation(), Sound.BLOCK_NETHER_GOLD_ORE_PLACE, 0.7f, 1.355f); - spw.play(player.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.4f, 0.755f); - w.close(); - if (AdaptConfig.get().getLearnUnlearnButtonDelayTicks() != 0) { - if (isPermanent()) { - spw.play(player.getLocation(), Sound.ENTITY_BLAZE_DEATH, 0.5f, 1.355f); - player.sendTitle(" ", C.RED + "" + C.BOLD + Localizer.dLocalize("snippets.adapt_menu.may_not_unlearn") + " " + getDisplayName(mylevel), 1, 10, 11); - } else { - player.sendTitle(" ", C.GRAY + Localizer.dLocalize("snippets.adapt_menu.unlearned") + " " + getDisplayName(mylevel), 1, 10, 11); - } - } - J.s(() -> openGui(player, currentPage), AdaptConfig.get().getLearnUnlearnButtonDelayTicks()); - return; - } - - if (k >= c && getPlayer(player).getData().hasPowerAvailable(pc)) { - if (isPermanent() && !consumePermanentLearnConfirmation(player, lvl)) { - spw.play(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.7f, 0.85f); - player.sendTitle(" ", C.GOLD + "" + C.BOLD + "Click again to confirm permanent learn", 1, 16, 8); - J.s(() -> openGui(player, currentPage), 1); - return; - } - - if (getPlayer(player).getData().getSkillLine(getSkill().getName()).spendKnowledge(c)) { - getPlayer(player).getData().getSkillLine(getSkill().getName()).setAdaptation(this, lvl); - spw.play(player.getLocation(), Sound.BLOCK_NETHER_GOLD_ORE_PLACE, 0.9f, 1.355f); - spw.play(player.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1.7f, 0.355f); - spw.play(player.getLocation(), Sound.BLOCK_BEACON_POWER_SELECT, 0.4f, 0.155f); - spw.play(player.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 0.2f, 1.455f); - if (isPermanent()) { - spw.play(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.7f, 1.355f); - spw.play(player.getLocation(), Sound.ITEM_GOAT_HORN_SOUND_1, 0.7f, 1.355f); - } - w.close(); - if (AdaptConfig.get().getLearnUnlearnButtonDelayTicks() != 0) { - player.sendTitle(" ", C.GRAY + Localizer.dLocalize("snippets.adapt_menu.learned") + " " + getDisplayName(lvl), 1, 5, 11); - } - J.s(() -> openGui(player, currentPage), AdaptConfig.get().getLearnUnlearnButtonDelayTicks()); - } else { - spw.play(player.getLocation(), Sound.BLOCK_BAMBOO_HIT, 0.7f, 1.855f); - } - } else { - spw.play(player.getLocation(), Sound.BLOCK_BAMBOO_HIT, 0.7f, 1.855f); - } - }); - de.addLore(" "); - addStats(lvl, de); - reveal.add(new GuiEffects.Placement(pos, row, de)); - } - } - GuiEffects.applyReveal(w, reveal); - - if (plan.hasNavigationRow()) { - int navRow = plan.rows() - 1; - int jumpPages = 5; - int jumpBack = Math.max(0, currentPage - jumpPages); - int jumpForward = Math.min(plan.pageCount() - 1, currentPage + jumpPages); - if (currentPage > 0) { - w.setElement(-4, navRow, new UIElement("adapt-prev") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.WHITE + "Previous") - .addLore(C.GRAY + "Right click: jump -" + jumpPages + " pages") - .onLeftClick((e) -> openGui(player, currentPage - 1)) - .onRightClick((e) -> openGui(player, jumpBack))); - w.setElement(-3, navRow, new UIElement("adapt-first") - .setMaterial(new MaterialBlock(Material.LECTERN)) - .setName(C.GRAY + "First") - .onLeftClick((e) -> openGui(player, 0))); - } - if (currentPage < plan.pageCount() - 1) { - w.setElement(4, navRow, new UIElement("adapt-next") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.WHITE + "Next") - .addLore(C.GRAY + "Right click: jump +" + jumpPages + " pages") - .onLeftClick((e) -> openGui(player, currentPage + 1)) - .onRightClick((e) -> openGui(player, jumpForward))); - w.setElement(3, navRow, new UIElement("adapt-last") - .setMaterial(new MaterialBlock(Material.LECTERN)) - .setName(C.GRAY + "Last") - .onLeftClick((e) -> openGui(player, plan.pageCount() - 1))); - } - - int from = getMaxLevel() <= 0 ? 0 : (start + 1); - int to = getMaxLevel() <= 0 ? 0 : end; - w.setElement(-1, navRow, new UIElement("adapt-page-info") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.AQUA + "Page " + (currentPage + 1) + "/" + plan.pageCount()) - .addLore(C.GRAY + "Showing " + from + "-" + to + " of " + getMaxLevel()) - .setProgress(1D)); - - if (AdaptConfig.get().isGuiBackButton()) { - w.setElement(0, navRow, new UIElement("back") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName("" + C.RESET + C.GRAY + Localizer.dLocalize("snippets.gui.back")) - .onLeftClick((e) -> onGuiClose(player, true))); - } - - } - - AdaptPlayer a = Adapt.instance.getAdaptServer().getPlayer(player); - String pageSuffix = plan.pageCount() > 1 ? " [" + (currentPage + 1) + "/" + plan.pageCount() + "]" : ""; - w.setTitle(getDisplayName() + " " + C.DARK_GRAY + " " + Form.f(a.getSkillLine(getSkill().getName()).getKnowledge()) + " " + Localizer.dLocalize("snippets.adapt_menu.knowledge") + pageSuffix); - w.onClosed((vv) -> J.s(() -> onGuiClose(player, !AdaptConfig.get().isEscClosesAllGuis()))); - w.open(); - Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); - } - - private void onGuiClose(Player player, boolean openPrevGui) { - SoundPlayer spw = SoundPlayer.of(player.getWorld()); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 0.655f); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 0.855f); - if (openPrevGui) { - getSkill().openGui(player); - } else { - Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString()); - } - } - - private static String permanentConfirmPrefix(Player player, Adaptation adaptation) { - return player.getUniqueId() + "|" + adaptation.getName() + "|"; - } - - private static String permanentConfirmKey(Player player, Adaptation adaptation, int level) { - return permanentConfirmPrefix(player, adaptation) + level; - } - - private static boolean isPermanentLearnConfirmationPending(Player player, Adaptation adaptation, int level) { - if (player == null || adaptation == null) { - return false; - } - - Long until = PERMANENT_LEARN_CONFIRMATIONS.get(permanentConfirmKey(player, adaptation, level)); - return until != null && until >= M.ms(); - } - - default boolean isPermanentLearnConfirmationPending(Player player, int level) { - return isPermanentLearnConfirmationPending(player, this, level); - } - - default boolean consumePermanentLearnConfirmation(Player player, int level) { - if (player == null) { - return false; - } - - long now = M.ms(); - PERMANENT_LEARN_CONFIRMATIONS.entrySet().removeIf(e -> e.getValue() < now); - - String key = permanentConfirmKey(player, this, level); - Long until = PERMANENT_LEARN_CONFIRMATIONS.get(key); - if (until != null && until >= now) { - PERMANENT_LEARN_CONFIRMATIONS.remove(key); - return true; - } - - String prefix = permanentConfirmPrefix(player, this); - PERMANENT_LEARN_CONFIRMATIONS.keySet().removeIf(existing -> existing.startsWith(prefix)); - PERMANENT_LEARN_CONFIRMATIONS.put(key, now + PERMANENT_LEARN_CONFIRM_WINDOW_MS); - return false; - } - - default void unlearn(Player player, int lvl, boolean force) { - if (isPermanent() && !force) { - return; - } - int myLevel = getPlayer(player).getSkillLine(getSkill().getName()).getAdaptationLevel(getName()); - int rc = getRefundCostFor(lvl - 1, myLevel); - if (!AdaptConfig.get().isHardcoreNoRefunds()) { - getPlayer(player).getData().getSkillLine(getSkill().getName()).giveKnowledge(rc); - } - getPlayer(player).getData().getSkillLine(getSkill().getName()).setAdaptation(this, lvl - 1); - } - - default void learn(Player player, int lvl, boolean force) { - int myLevel = getPlayer(player).getSkillLine(getSkill().getName()).getAdaptationLevel(getName()); - int c = getCostFor(lvl, myLevel); - if (getPlayer(player).getData().hasPowerAvailable(c) || force) { - if (getPlayer(player).getData().getSkillLine(getSkill().getName()).spendKnowledge(c) || force) { - getPlayer(player).getData().getSkillLine(getSkill().getName()).setAdaptation(this, lvl); - } - } - } - - default boolean isAdaptationRecipe(Recipe recipe) { - if (!this.isEnabled()) { - return false; - } - if (!this.getSkill().isEnabled()) { - return false; - } - for (AdaptRecipe i : getRecipes()) { - if (i.is(recipe)) { - return true; - } - } - return false; - } -} diff --git a/src/main/java/com/volmit/adapt/api/adaptation/SimpleAdaptation.java b/src/main/java/com/volmit/adapt/api/adaptation/SimpleAdaptation.java deleted file mode 100644 index db83b52af..000000000 --- a/src/main/java/com/volmit/adapt/api/adaptation/SimpleAdaptation.java +++ /dev/null @@ -1,305 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.adaptation; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementSpec; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.tick.TickedObject; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigFileSupport; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.bukkit.Material; - -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.collection.KList; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -@EqualsAndHashCode(callSuper = false) -@Data -public abstract class SimpleAdaptation extends TickedObject implements Adaptation { - private int maxLevel; - private int initialCost; - private int baseCost; - private double costFactor; - private String displayName; - private Skill skill; - private String description; - private Material icon; - private String name; - private List cachedAdvancements; - private List recipes; - private List brewingRecipes; - private KList statTrackers; - private Class configType; - private volatile T config; - - public SimpleAdaptation(String name) { - super("adaptations", UUID.randomUUID() + "-" + name, 1000); - cachedAdvancements = new ArrayList<>(); - recipes = new ArrayList<>(); - brewingRecipes = new ArrayList<>(); - statTrackers = new KList<>(); - setMaxLevel(5); - setCostFactor(0.45); - setBaseCost(4); - setIcon(Material.PAPER); - setInitialCost(2); - setDescription("No Description Provided"); - this.name = name; - } - - @Override - public Class getConfigurationClass() { - return configType; - } - - @Override - public void registerConfiguration(Class type) { - this.configType = type; - } - - protected File getConfigFile() { - return Adapt.instance.getDataFile("adapt", "adaptations", getName() + ".toml"); - } - - protected File getLegacyConfigFile() { - return Adapt.instance.getDataFile("adapt", "adaptations", getName() + ".json"); - } - - protected T createDefaultConfig() { - try { - return getConfigurationClass().getConstructor().newInstance(); - } catch (Throwable e) { - throw new IllegalStateException("Failed to create default config for adaptation " + getName(), e); - } - } - - public synchronized boolean reloadConfigFromDisk(boolean announce) { - if (getConfigurationClass() == null) { - return false; - } - - T previous = config; - File file = getConfigFile(); - try { - T loaded = loadConfig(file, previous == null ? createDefaultConfig() : previous, previous == null); - config = loaded; - applySharedConfigValues(loaded); - onConfigReload(previous, loaded); - if (announce) { - Adapt.info("Hotloaded " + file.getPath()); - } - return true; - } catch (Throwable e) { - Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid config: " + e.getMessage()); - return false; - } - } - - private T loadConfig(File file, T fallback, boolean overwriteOnReadFailure) throws IOException { - return ConfigFileSupport.load( - file, - getLegacyConfigFile(), - getConfigurationClass(), - fallback, - overwriteOnReadFailure, - "adaptation:" + getName(), - "Created missing adaptation config [adapt/adaptations/" + getName() + ".toml] from defaults." - ); - } - - private void applySharedConfigValues(T currentConfig) { - applyIntField(currentConfig, "baseCost", this::setBaseCost); - applyIntField(currentConfig, "initialCost", this::setInitialCost); - applyIntField(currentConfig, "maxLevel", this::setMaxLevel); - applyLongField(currentConfig, "setInterval", this::setInterval); - } - - protected void onConfigReload(T previousConfig, T newConfig) { - applyDoubleField(newConfig, "costFactor", this::setCostFactor); - } - - private void applyIntField(T source, String fieldName, java.util.function.IntConsumer consumer) { - Number number = getNumericField(source, fieldName); - if (number != null) { - consumer.accept(number.intValue()); - } - } - - private void applyLongField(T source, String fieldName, java.util.function.LongConsumer consumer) { - Number number = getNumericField(source, fieldName); - if (number != null) { - consumer.accept(number.longValue()); - } - } - - private void applyDoubleField(T source, String fieldName, java.util.function.DoubleConsumer consumer) { - Number number = getNumericField(source, fieldName); - if (number != null) { - consumer.accept(number.doubleValue()); - } - } - - private Number getNumericField(T source, String fieldName) { - Field f = getField(source.getClass(), fieldName); - if (f == null) { - return null; - } - - try { - f.setAccessible(true); - Object value = f.get(source); - if (value instanceof Number number) { - return number; - } - } catch (Throwable ignored) { - Adapt.verbose("Failed reading config field '" + fieldName + "' for adaptation " + getName()); - } - - return null; - } - - private Field getField(Class type, String name) { - Class current = type; - while (current != null) { - try { - return current.getDeclaredField(name); - } catch (NoSuchFieldException ignored) { - current = current.getSuperclass(); - } - } - - return null; - } - - @Override - public T getConfig() { - T local = config; - if (local != null) { - return local; - } - - synchronized (this) { - local = config; - if (local != null) { - return local; - } - - boolean loaded = reloadConfigFromDisk(false); - local = config; - if (!loaded || local == null) { - local = createDefaultConfig(); - applySharedConfigValues(local); - onConfigReload(null, local); - config = local; - Adapt.warn("Falling back to in-memory defaults for adaptation config " + getName() + "."); - } - } - - return local; - } - - public void registerRecipe(AdaptRecipe r) { - recipes.add(r); - } - - public void registerBrewingRecipe(BrewingRecipe r) { - brewingRecipes.add(r); - } - - @Override - public String getDisplayName() { - try { - return displayName == null ? Adaptation.super.getDisplayName() : (C.RESET + "" + C.BOLD + getSkill().getColor().toString() + displayName); - } catch (Exception ignored) { - Adapt.verbose("Failed to get display name for " + getName()); - return null; - } - } - - public void registerStatTracker(AdaptStatTracker tracker) { - statTrackers.add(tracker); - } - - public KList getStatTrackers() { - return statTrackers; - } - - public void registerAdvancement(AdaptAdvancement a) { - cachedAdvancements.add(a); - } - - protected void registerAdvancementSpec(AdvancementSpec spec) { - if (spec == null) { - return; - } - - registerAdvancement(spec.toAdvancement()); - } - - protected void registerMilestone(AdvancementSpec spec, String stat, double goal, double reward) { - if (spec == null) { - return; - } - - registerAdvancementSpec(spec); - registerStatTracker(spec.statTracker(stat, goal, reward)); - } - - protected void registerMilestone(String advancementKey, String stat, double goal, double reward) { - registerStatTracker(AdaptStatTracker.builder() - .advancement(advancementKey) - .stat(stat) - .goal(goal) - .reward(reward) - .build()); - } - - @Override - public void onRegisterAdvancements(List advancements) { - advancements.addAll(cachedAdvancements); - } - - public AdaptAdvancement buildAdvancements() { - List a = new ArrayList<>(); - onRegisterAdvancements(a); - - return AdaptAdvancement.builder() - .key("adaptation_" + getName()) - .title(C.WHITE + "[ " + getDisplayName() + C.WHITE + " ]") - .description(getDescription() + ". " + Localizer.dLocalize("snippets.gui.unlock_this_by_clicking") + " " + AdaptConfig.get().adaptActivatorBlockName) - .icon(getIcon()) - .children(a) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build(); - } -} diff --git a/src/main/java/com/volmit/adapt/api/adaptation/chunk/ChunkLoading.java b/src/main/java/com/volmit/adapt/api/adaptation/chunk/ChunkLoading.java deleted file mode 100644 index 22d494d09..000000000 --- a/src/main/java/com/volmit/adapt/api/adaptation/chunk/ChunkLoading.java +++ /dev/null @@ -1,37 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.adaptation.chunk; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.J; -import org.bukkit.Chunk; -import org.bukkit.Location; - -import java.util.function.Consumer; - -public class ChunkLoading { - public static void loadChunkAsync(Location l, Consumer chunk) { - if (l.getWorld().isChunkLoaded(l.getBlockX() >> 4, l.getBlockZ() >> 4)) { - chunk.accept(l.getChunk()); - return; - } - Adapt.verbose("Loading chunk async for " + l); - Adapt.platform.getChunkAtAsync(l).thenAccept(c -> J.s(() -> chunk.accept(c))); - } -} diff --git a/src/main/java/com/volmit/adapt/api/advancement/AdaptAdvancement.java b/src/main/java/com/volmit/adapt/api/advancement/AdaptAdvancement.java deleted file mode 100644 index dbabd4486..000000000 --- a/src/main/java/com/volmit/adapt/api/advancement/AdaptAdvancement.java +++ /dev/null @@ -1,146 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.advancement; - - -import com.fren_gor.ultimateAdvancementAPI.AdvancementTab; -import com.fren_gor.ultimateAdvancementAPI.advancement.Advancement; -import com.fren_gor.ultimateAdvancementAPI.advancement.BaseAdvancement; -import com.fren_gor.ultimateAdvancementAPI.advancement.RootAdvancement; -import com.fren_gor.ultimateAdvancementAPI.advancement.display.AdvancementDisplay; -import com.fren_gor.ultimateAdvancementAPI.database.TeamProgression; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.collection.KList; -import lombok.Builder; -import lombok.Data; -import lombok.Singular; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -@Builder -@Data -public class AdaptAdvancement { - private String background; - @Builder.Default - private Material icon = Material.EMERALD; - @Builder.Default - private CustomModel model = null; - @Builder.Default - private String title = "MISSING TITLE"; - @Builder.Default - private String description = "MISSING DESCRIPTION"; - @Builder.Default - private AdaptAdvancementFrame frame = AdaptAdvancementFrame.TASK; - @Builder.Default - private boolean toast = false; - @Builder.Default - private boolean announce = false; - @Builder.Default - private AdvancementVisibility visibility = AdvancementVisibility.PARENT_GRANTED; - @Builder.Default - private String key = "root"; - @Singular - private List children; - - private Advancement toAdvancement(Advancement parent, int index, int depth) { - if (children == null) { - children = new ArrayList<>(); - } - - var icon = getModel() != null ? - getModel().toItemStack() : - new ItemStack(getIcon()); - AdvancementDisplay d = new AdvancementDisplay.Builder(icon, getTitle()) - .description(getDescription()) - .frame(getFrame().toUaaFrame()) - .showToast(toast) - .x(1f + depth) - .y(1f + index) - .build(); - - if (parent == null) { - if (background == null) - throw new IllegalArgumentException("Background cannot be null"); - - return new MainAdvancement(Adapt.instance.getManager().createAdvancementTab(getKey()), getKey(), d, background); - } - - return new SubAdvancement(getKey(), d, parent, getVisibility()); - } - - public KList toAdvancements() { - return toAdvancements(null, 0, 0); - } - - private KList toAdvancements(Advancement p, int index, int depth) { - KList aa = new KList<>(); - Advancement a = toAdvancement(p, index, depth); - if (children != null && !children.isEmpty()) { - for (AdaptAdvancement i : children) { - aa.addAll(i.toAdvancements(a, aa.size(), depth + 1)); - } - } - - aa.add(a); - - return aa; - } - - private static class MainAdvancement extends RootAdvancement { - - public MainAdvancement(@NotNull AdvancementTab advancementTab, @NotNull String key, @NotNull AdvancementDisplay display, @NotNull String backgroundTexture) { - super(advancementTab, key, display, backgroundTexture); - } - - @Override - public void grant(@NotNull Player player, boolean giveRewards) { - super.grant(player, giveRewards); - getAdvancementTab().showTab(player); - } - - @Override - public void revoke(@NotNull Player player) { - super.revoke(player); - getAdvancementTab().hideTab(player); - } - } - - private static class SubAdvancement extends BaseAdvancement { - private final AdvancementVisibility visibility; - - public SubAdvancement(@NotNull String key, - @NotNull AdvancementDisplay display, - @NotNull Advancement parent, - @NotNull AdvancementVisibility visibility) { - super(key, display, parent); - this.visibility = visibility; - } - - @Override - public boolean isVisible(@NotNull TeamProgression progression) { - return visibility.isVisible(this, progression); - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/advancement/AdaptAdvancementFrame.java b/src/main/java/com/volmit/adapt/api/advancement/AdaptAdvancementFrame.java deleted file mode 100644 index 9425511ff..000000000 --- a/src/main/java/com/volmit/adapt/api/advancement/AdaptAdvancementFrame.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.volmit.adapt.api.advancement; - -import com.fren_gor.ultimateAdvancementAPI.advancement.display.AdvancementFrameType; - -public enum AdaptAdvancementFrame { - TASK, - GOAL, - CHALLENGE; - - public AdvancementFrameType toUaaFrame() { - return switch (this) { - case GOAL -> AdvancementFrameType.GOAL; - case CHALLENGE -> AdvancementFrameType.CHALLENGE; - case TASK -> AdvancementFrameType.TASK; - }; - } -} diff --git a/src/main/java/com/volmit/adapt/api/advancement/AdvancementManager.java b/src/main/java/com/volmit/adapt/api/advancement/AdvancementManager.java deleted file mode 100644 index 22a96be9a..000000000 --- a/src/main/java/com/volmit/adapt/api/advancement/AdvancementManager.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.volmit.adapt.api.advancement; - -import com.fren_gor.ultimateAdvancementAPI.AdvancementMain; -import com.fren_gor.ultimateAdvancementAPI.AdvancementTab; -import com.fren_gor.ultimateAdvancementAPI.advancement.Advancement; -import com.fren_gor.ultimateAdvancementAPI.advancement.BaseAdvancement; -import com.fren_gor.ultimateAdvancementAPI.advancement.RootAdvancement; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.util.J; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; - -import static com.volmit.adapt.Adapt.instance; - -public class AdvancementManager { - private final AdvancementMain main; - private final Map advancements; - private final AtomicBoolean loaded = new AtomicBoolean(false); - private final AtomicBoolean enabled = new AtomicBoolean(false); - - public AdvancementManager() { - AdvancementMain loadedMain = null; - try { - loadedMain = new AdvancementMain(instance); - loadedMain.load(); - loaded.set(true); - } catch (Throwable e) { - loadedMain = null; - Adapt.warn("UltimateAdvancementAPI is unavailable: " + e.getMessage() + ". Advancements will be disabled."); - } - - main = loadedMain; - advancements = new HashMap<>(); - } - - AdvancementTab createAdvancementTab(String namespace) { - if (main == null) { - throw new IllegalStateException("UltimateAdvancementAPI is unavailable"); - } - - return main.createAdvancementTab(instance, "adapt_" + namespace); - } - - public void grant(AdaptPlayer player, String key, boolean toast) { - player.getData().ensureGranted(key); - Player p = player.getPlayer(); - if (!AdaptConfig.get().isAdvancements() || !enabled.get() || p == null || !p.isOnline()) return; - Advancement advancement = advancements.get(key); - if (advancement == null) { - Adapt.verbose("Advancement key '" + key + "' is not registered; skipping grant."); - return; - } - - J.s(() -> { - Player target = player.getPlayer(); - if (target == null || !target.isOnline()) { - return; - } - - try { - advancement.grant(target, true); - } catch (Throwable t) { - if (isUserNotLoadedError(t)) { - Adapt.verbose("Skipped advancement grant '" + key + "' because user data is not loaded yet for " + target.getName() + "."); - return; - } - - Adapt.warn("Failed to grant advancement '" + key + "' for " + target.getName() + ": " + t.getMessage()); - } - }, 5); - - if (toast) { - try { - advancement.displayToastToPlayer(p); - } catch (Throwable t) { - if (isUserNotLoadedError(t)) { - Adapt.verbose("Skipped advancement toast '" + key + "' because user data is not loaded yet for " + p.getName() + "."); - return; - } - - Adapt.warn("Failed to display advancement toast '" + key + "' for " + p.getName() + ": " + t.getMessage()); - } - } - } - - private boolean isUserNotLoadedError(Throwable throwable) { - Throwable current = throwable; - while (current != null) { - if ("UserNotLoadedException".equals(current.getClass().getSimpleName())) { - return true; - } - - current = current.getCause(); - } - - return false; - } - - public void unlockExisting(AdaptPlayer player) { - if (!AdaptConfig.get().isAdvancements() || !enabled.get()) return; - J.s(() -> { - instance.getAdaptServer() - .getSkillRegistry() - .getSkills() - .stream() - .map(Skill::buildAdvancements) - .forEach(aa -> unlockExisting(player, aa)); - - player.getAdvancementHandler().setReady(true); - }, 20); - } - - private void unlockExisting(AdaptPlayer player, AdaptAdvancement aa) { - if (aa.getChildren() != null) { - for (AdaptAdvancement i : aa.getChildren()) { - unlockExisting(player, i); - } - } - - if (player.getData().isGranted(aa.getKey())) { - grant(player, aa.getKey(), false); - } - } - - public void enable() { - if (main == null) { - return; - } - - if (loaded.compareAndSet(false, true)) - main.load(); - - if (!AdaptConfig.get().isAdvancements() || !enabled.compareAndSet(false, true)) - return; - if (AdaptConfig.get().isUseSql()) { - AdaptConfig.SqlSettings sql = AdaptConfig.get().getSql(); - main.enableMySQL(sql.getUsername(), sql.getPassword(), sql.getDatabase(), sql.getHost(), sql.getPort(), sql.getPoolSize(), sql.getConnectionTimeout()); - } else { - main.enableSQLite(instance.getDataFile("data", "advancements.db")); - } - - for (Skill i : instance.getAdaptServer().getSkillRegistry().getSkills()) { - AdaptAdvancement aa = i.buildAdvancements(); - Set set = new HashSet<>(); - RootAdvancement root = null; - - for (var a : aa.toAdvancements().reverse()) { - advancements.put(a.getKey().getKey(), a); - if (a instanceof RootAdvancement r && root == null) root = r; - else if (a instanceof BaseAdvancement b) set.add(b); - } - - if (root == null) { - Adapt.error("Root advancement not found for " + i.getId()); - continue; - } - root.getAdvancementTab().registerAdvancements(root, set); - } - } - - public void disable() { - if (main == null) { - enabled.set(false); - loaded.set(false); - return; - } - - main.disable(); - enabled.set(false); - loaded.set(false); - } -} diff --git a/src/main/java/com/volmit/adapt/api/advancement/AdvancementSpec.java b/src/main/java/com/volmit/adapt/api/advancement/AdvancementSpec.java deleted file mode 100644 index a981b3fe2..000000000 --- a/src/main/java/com/volmit/adapt/api/advancement/AdvancementSpec.java +++ /dev/null @@ -1,102 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.advancement; - -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.CustomModel; -import lombok.Builder; -import lombok.Data; -import lombok.Singular; -import org.bukkit.Material; - -import java.util.List; - -@Builder(toBuilder = true) -@Data -public class AdvancementSpec { - private String key; - private String title; - private String description; - @Builder.Default - private Material icon = Material.EMERALD; - @Builder.Default - private CustomModel model = null; - @Builder.Default - private AdaptAdvancementFrame frame = AdaptAdvancementFrame.TASK; - @Builder.Default - private AdvancementVisibility visibility = AdvancementVisibility.PARENT_GRANTED; - @Builder.Default - private boolean toast = false; - @Builder.Default - private boolean announce = false; - @Singular - private List children; - - public static AdvancementSpec challenge(String key, Material icon, String title, String description) { - return AdvancementSpec.builder() - .key(key) - .icon(icon) - .title(title) - .description(description) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build(); - } - - public AdvancementSpec withChild(AdvancementSpec child) { - if (child == null) { - return this; - } - - return toBuilder().child(child).build(); - } - - public AdaptAdvancement toAdvancement() { - AdaptAdvancement.AdaptAdvancementBuilder builder = AdaptAdvancement.builder() - .key(key) - .title(title) - .description(description) - .icon(icon) - .model(model) - .frame(frame) - .toast(toast) - .announce(announce) - .visibility(visibility); - - if (children != null) { - for (AdvancementSpec child : children) { - if (child == null) { - continue; - } - builder.child(child.toAdvancement()); - } - } - - return builder.build(); - } - - public AdaptStatTracker statTracker(String stat, double goal, double reward) { - return AdaptStatTracker.builder() - .stat(stat) - .goal(goal) - .reward(reward) - .advancement(key) - .build(); - } -} diff --git a/src/main/java/com/volmit/adapt/api/advancement/AdvancementVisibility.java b/src/main/java/com/volmit/adapt/api/advancement/AdvancementVisibility.java deleted file mode 100644 index 72330d431..000000000 --- a/src/main/java/com/volmit/adapt/api/advancement/AdvancementVisibility.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.volmit.adapt.api.advancement; - -import com.fren_gor.ultimateAdvancementAPI.advancement.Advancement; -import com.fren_gor.ultimateAdvancementAPI.advancement.BaseAdvancement; -import com.fren_gor.ultimateAdvancementAPI.advancement.multiParents.AbstractMultiParentsAdvancement; -import com.fren_gor.ultimateAdvancementAPI.database.TeamProgression; -import com.google.common.base.Preconditions; -import org.jetbrains.annotations.NotNull; - - -public interface AdvancementVisibility { - - /** - * Advancements with this Visibility will always be visible - */ - AdvancementVisibility ALWAYS = (advancement, progression) -> true; - - /** - * Advancements with this Visibility will be visible once their parent or any of their children is granted - */ - AdvancementVisibility PARENT_GRANTED = (advancement, progression) -> { - Preconditions.checkNotNull(advancement, "Advancement is null."); - Preconditions.checkNotNull(progression, "TeamProgression is null."); - if (advancement.getProgression(progression) > 0) - return true; - - if (advancement instanceof AbstractMultiParentsAdvancement multiParent) { - return multiParent.isAnyParentGranted(progression); - } - if (advancement instanceof BaseAdvancement base) { - return base.getParent().isGranted(progression); - } - return false; - }; - - /** - * Advancements with this Visibility will be visible once they are granted or any of their children is granted (Similar to Vanilla "hidden") - */ - AdvancementVisibility HIDDEN = (advancement, progression) -> { - Preconditions.checkNotNull(advancement, "Advancement is null."); - Preconditions.checkNotNull(progression, "TeamProgression is null."); - return advancement.getProgression(progression) > 0; - }; - - /** - * Advancements with this Visibility will be visible once their parent or grandparent or any of their children is granted (Similar to Vanilla behavior) - */ - AdvancementVisibility VANILLA = (advancement, progression) -> { - Preconditions.checkNotNull(advancement, "Advancement is null."); - Preconditions.checkNotNull(progression, "TeamProgression is null."); - if (advancement.getProgression(progression) > 0) - return true; - - if (advancement instanceof AbstractMultiParentsAdvancement multiParent) { - return multiParent.isAnyGrandparentGranted(progression); - } else if (advancement instanceof BaseAdvancement base) { - Advancement parent = base.getParent(); - - if (parent.isGranted(progression)) { - return true; - } - if (parent instanceof AbstractMultiParentsAdvancement multiParent) { - return multiParent.isAnyParentGranted(progression); - } else if (parent instanceof BaseAdvancement baseA) { - return baseA.getParent().isGranted(progression); - } - return false; - } - return false; - }; - - /** - * Do not call this method directly, use {@link AdvancementVisibility} to get accurate visibility data - * - * @param advancement Advancement to check - * @param progression Progression to check - * @return true if advancement should be visible - */ - boolean isVisible(@NotNull Advancement advancement, @NotNull TeamProgression progression); -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/api/data/WorldData.java b/src/main/java/com/volmit/adapt/api/data/WorldData.java deleted file mode 100644 index 2a9b4eb2e..000000000 --- a/src/main/java/com/volmit/adapt/api/data/WorldData.java +++ /dev/null @@ -1,118 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.data; - -import art.arcane.spatial.mantle.Mantle; -import art.arcane.spatial.matter.ClassReader; -import art.arcane.spatial.matter.SpatialMatter; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.data.unit.Earnings; -import com.volmit.adapt.api.tick.TickedObject; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.collection.KMap; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.event.EventHandler; -import org.bukkit.event.world.WorldSaveEvent; -import org.bukkit.event.world.WorldUnloadEvent; - -public class WorldData extends TickedObject { - private static final KMap mantles = new KMap<>(); - - static { - SpatialMatter.registerSliceType(new Earnings.EarningsMatter()); - ClassReader.add(WorldData.class.getClassLoader()); - } - - private final World world; - private final int minHeight; - private final Mantle mantle; - - public WorldData(World world) { - super("world-data", world.getUID().toString(), 30_000); - this.world = world; - this.minHeight = world.getMinHeight(); - mantle = new Mantle(Adapt.instance.getDataFolder("data", "mantle", world.getName()), world.getMaxHeight()); - } - - public static void stop() { - mantles.v().forEach(WorldData::unregister); - } - - public static WorldData of(World world) { - return mantles.computeIfAbsent(world, WorldData::new); - } - - public double getEarningsMultiplier(Block block) { - Earnings e = get(block, Earnings.class); - - if (e == null) { - return 1; - } - - return 1 / (double) (e.getEarnings() == 0 ? 1 : e.getEarnings()); - } - - public T get(Block block, Class type) { - return mantle.get(block.getX(), block.getY() - minHeight, block.getZ(), type); - } - - public void set(Block block, T value) { - mantle.set(block.getX(), block.getY() - minHeight, block.getZ(), value); - } - - public void remove(Block block, Class type) { - mantle.remove(block.getX(), block.getY() - minHeight, block.getZ(), type); - } - - public double reportEarnings(Block block) { - Earnings e = get(block, Earnings.class); - e = e == null ? new Earnings(0) : e; - - if (e.getEarnings() >= 127) { - return 1 / (double) (e.getEarnings() == 0 ? 1 : e.getEarnings()); - } - - set(block, e.increment()); - return 1 / (double) (e.getEarnings() == 0 ? 1 : e.getEarnings()); - } - - public void unregister() { - super.unregister(); - mantle.close(); - mantles.remove(world); - } - - @EventHandler - public void on(WorldSaveEvent e) { - if (e.getWorld() != world) return; - J.a(mantle::saveAll); - } - - @EventHandler - public void on(WorldUnloadEvent e) { - if (e.getWorld() != world) return; - unregister(); - } - - @Override - public void onTick() { - mantle.trim(60_000); - } -} diff --git a/src/main/java/com/volmit/adapt/api/data/unit/Earnings.java b/src/main/java/com/volmit/adapt/api/data/unit/Earnings.java deleted file mode 100644 index 2bce44d0b..000000000 --- a/src/main/java/com/volmit/adapt/api/data/unit/Earnings.java +++ /dev/null @@ -1,61 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.data.unit; - -import art.arcane.spatial.matter.slices.RawMatter; -import lombok.AllArgsConstructor; -import lombok.Data; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -@Data -@AllArgsConstructor -public class Earnings { - private final int earnings; - - public Earnings increment() { - if (earnings >= 127) { - return this; - } - - return new Earnings(getEarnings() + 1); - } - - public static class EarningsMatter extends RawMatter { - public EarningsMatter() { - this(1, 1, 1); - } - - public EarningsMatter(int width, int height, int depth) { - super(width, height, depth, Earnings.class); - } - - @Override - public void writeNode(Earnings earnings, DataOutputStream dataOutputStream) throws IOException { - dataOutputStream.writeByte(earnings.getEarnings()); - } - - @Override - public Earnings readNode(DataInputStream dataInputStream) throws IOException { - return new Earnings(dataInputStream.readByte()); - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/item/DataItem.java b/src/main/java/com/volmit/adapt/api/item/DataItem.java deleted file mode 100644 index cff12b05d..000000000 --- a/src/main/java/com/volmit/adapt/api/item/DataItem.java +++ /dev/null @@ -1,88 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.item; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.BukkitGson; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.persistence.PersistentDataType; - -import java.util.ArrayList; -import java.util.List; - -public interface DataItem { - Material getMaterial(); - - Class getType(); - - void applyLore(T data, List lore); - - void applyMeta(T data, ItemMeta meta); - - default ItemStack blank() { - return new ItemStack(getMaterial()); - } - - default T getData(ItemStack stack) { - if (stack != null - && stack.getType().equals(getMaterial()) - && stack.getItemMeta() != null) { - String r = stack.getItemMeta().getPersistentDataContainer().get(new NamespacedKey(Adapt.instance, getType().getCanonicalName().hashCode() + ""), PersistentDataType.STRING); - if (r != null) { - return BukkitGson.gson.fromJson(r, getType()); - } - } - - return null; - } - - default boolean hasData(ItemStack stack) { - if (stack != null - && stack.getType().equals(getMaterial()) - && stack.getItemMeta() != null) { - return stack.getItemMeta().getPersistentDataContainer().has(new NamespacedKey(Adapt.instance, getType().getCanonicalName().hashCode() + ""), PersistentDataType.STRING); - } - return false; - } - - - default void setData(ItemStack item, T t) { - item.setItemMeta(withData(t).getItemMeta()); - } - - default ItemStack withData(T t) { - ItemStack item = blank(); - ItemMeta meta = item.getItemMeta(); - - if (meta == null) { - return null; - } - - applyMeta(t, meta); - List lore = new ArrayList<>(); - applyLore(t, lore); - meta.setLore(lore); - meta.getPersistentDataContainer().set(new NamespacedKey(Adapt.instance, getType().getCanonicalName().hashCode() + ""), PersistentDataType.STRING, BukkitGson.gson.toJson(t)); - item.setItemMeta(meta); - return item; - } -} diff --git a/src/main/java/com/volmit/adapt/api/item/PotionItem.java b/src/main/java/com/volmit/adapt/api/item/PotionItem.java deleted file mode 100644 index e2e890286..000000000 --- a/src/main/java/com/volmit/adapt/api/item/PotionItem.java +++ /dev/null @@ -1,51 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.item; - -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Form; -import lombok.NoArgsConstructor; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.potion.PotionEffectType; - -import java.util.List; - -public abstract class PotionItem implements DataItem { - @Override - public Class getType() { - return Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - lore.add(C.GREEN + "Grants " + data.getType().getName() + " " + Form.toRoman(data.getPower() + 1)); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - - } - - @lombok.Data - @NoArgsConstructor - public static class Data { - private PotionEffectType type; - private int power; - } -} diff --git a/src/main/java/com/volmit/adapt/api/notification/ActionBarNotification.java b/src/main/java/com/volmit/adapt/api/notification/ActionBarNotification.java deleted file mode 100644 index 6a3968a29..000000000 --- a/src/main/java/com/volmit/adapt/api/notification/ActionBarNotification.java +++ /dev/null @@ -1,60 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.notification; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.util.M; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class ActionBarNotification implements Notification { - @Builder.Default - private final long duration = 750; - @Builder.Default - private final String title = " "; - @Builder.Default - private final String group = "default"; - @Builder.Default - private final long maxTTL = Long.MAX_VALUE; - - @Override - public long getTotalDuration() { - if (M.ms() > maxTTL) { - return 0; - } - return duration; - } - - @Override - public String getGroup() { - return group; - } - - @Override - public void play(AdaptPlayer p) { - if (M.ms() > maxTTL) { - return; - } - - Adapt.actionbar(p.getPlayer(), title); - } -} diff --git a/src/main/java/com/volmit/adapt/api/notification/AdvancementNotification.java b/src/main/java/com/volmit/adapt/api/notification/AdvancementNotification.java deleted file mode 100644 index c64954dc7..000000000 --- a/src/main/java/com/volmit/adapt/api/notification/AdvancementNotification.java +++ /dev/null @@ -1,72 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.notification; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.util.AdvancementUtils; -import com.volmit.adapt.api.world.AdaptPlayer; - -import com.volmit.adapt.util.CustomModel; -import lombok.Builder; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -@Data -@Builder -public class AdvancementNotification implements Notification { - @Builder.Default - private final Material icon = Material.DIAMOND; - @Builder.Default - private final CustomModel model = null; - @Builder.Default - private final String title = " "; - @Builder.Default - private final String description = " "; - @Builder.Default - private final AdaptAdvancementFrame frameType = AdaptAdvancementFrame.TASK; - @Builder.Default - private final String group = "default"; - - @Override - public long getTotalDuration() { - return 100; // TODO: Actually calculate - } - - @Override - public String getGroup() { - return group; - } - - @Override - public void play(AdaptPlayer p) { - if (p.getPlayer() != null) { - var icon = getModel() != null ? getModel().toItemStack() : new ItemStack(getIcon()); - AdvancementUtils.displayToast(p.getPlayer(), icon, title, description, frameType); - } - } - - public String buildTitle() { - if (description.trim().isEmpty()) { - return title; - } - - return title + "\n" + description; - } -} diff --git a/src/main/java/com/volmit/adapt/api/notification/Notifier.java b/src/main/java/com/volmit/adapt/api/notification/Notifier.java deleted file mode 100644 index 6f9a666f6..000000000 --- a/src/main/java/com/volmit/adapt/api/notification/Notifier.java +++ /dev/null @@ -1,163 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.notification; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.tick.TickedObject; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.collection.KMap; -import lombok.Data; -import lombok.EqualsAndHashCode; - -import java.util.Arrays; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -@EqualsAndHashCode(callSuper = true) -@Data -public class Notifier extends TickedObject { - private final Queue queue; - private final AdaptPlayer target; - private final KMap lastSkills; - private final KMap lastSkillValues; - private int busyTicks; - private int delayTicks; - private long lastInstance; - - public Notifier(AdaptPlayer target) { - super("notifications", target.getPlayer().getUniqueId() + "-notify", 97); - queue = new ConcurrentLinkedQueue<>(); - lastSkills = new KMap<>(); - lastSkillValues = new KMap<>(); - busyTicks = 0; - delayTicks = 0; - this.target = target; - lastInstance = 0; - } - - public void notifyXP(String line, double value) { - try { - if (!lastSkills.containsKey(line)) { - lastSkillValues.put(line, 0d); - } - - lastSkills.put(line, M.ms()); - lastSkillValues.put(line, lastSkillValues.get(line) + value); - lastInstance = M.ms(); - - - StringBuilder sb = new StringBuilder(); - - for (String i : lastSkills.sortKNumber().reverse()) { - Skill sk = getServer().getSkillRegistry().getSkill(i); - sb.append(i.equals(line) ? sk.getDisplayName() : sk.getShortName()) - .append(C.RESET).append(C.GRAY) - .append(" +").append(C.WHITE) - .append(line.equals(i) ? C.UNDERLINE : "") - .append(Form.f(lastSkillValues.get(i).intValue())) - .append(C.RESET).append(C.GRAY) - .append("XP "); - } - - while (lastSkills.size() > 5) { - String s = lastSkills.sortKNumber().reverse().get(0); - lastSkills.remove(s); - lastSkillValues.remove(s); - } - - target.getActionBarNotifier().queue(ActionBarNotification.builder() - .duration(0) - .maxTTL(M.ms() + 100) - .title(sb.toString()) - .group("xp") - .build()); - } catch (Throwable e) { - Adapt.verbose("Failed to notify xp: " + e.getMessage()); - } - } - - public void queue(Notification... f) { - queue.addAll(Arrays.asList(f)); - } - - public boolean isBusy() { - return busyTicks > 1 || !queue.isEmpty(); - } - - @Override - public void onTick() { - cleanupSkills(); - - if (busyTicks > 6) { - busyTicks = 6; - } - - if (busyTicks-- > 0) { - return; - } - - if (busyTicks < 0) { - busyTicks = 0; - } - - delayTicks--; - if (delayTicks > 0) { - return; - } - - if (delayTicks < 0) { - delayTicks = 0; - } - - - if (!isBusy()) { - cleanupStackedNotifications(); - } - - Notification n = queue.poll(); - - if (n == null) { - return; - } - - delayTicks += (n.getTotalDuration() / 50D) + 1; - Adapt.verbose("Playing Notification " + n + " --> " + System.identityHashCode(this)); - n.play(target); - } - - private void cleanupStackedNotifications() { - - } - - private void cleanupSkills() { - for (String i : lastSkills.k()) { - if (lastSkills.get(i) == null) { // Shouldn't happen, but just in case I guess. - return; - } - if (M.ms() - lastSkills.get(i) > 10000 || (M.ms() - lastInstance > 3100 && M.ms() - lastSkills.get(i) > 3100)) { - lastSkills.remove(i); - lastSkillValues.remove(i); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/notification/SoundNotification.java b/src/main/java/com/volmit/adapt/api/notification/SoundNotification.java deleted file mode 100644 index 633d57722..000000000 --- a/src/main/java/com/volmit/adapt/api/notification/SoundNotification.java +++ /dev/null @@ -1,77 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.notification; - -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.SoundPlayer; -import lombok.Builder; -import lombok.Data; -import org.bukkit.Sound; - -@Data -@Builder -public class SoundNotification implements Notification { - @Builder.Default - private final long isolation = 0; - @Builder.Default - private final long predelay = 0; - @Builder.Default - private final Sound sound = Sound.BLOCK_LEVER_CLICK; - @Builder.Default - private final float volume = 1F; - @Builder.Default - private final float pitch = 1F; - @Builder.Default - private final String group = "default"; - - public SoundNotification withXP(double xp) { - double sig = xp / 1000D; - float pitch = this.pitch; - float volume = this.volume; - pitch -= sig / 6.6; - pitch = pitch < 0.1 ? (float) 0.1 : pitch; - double vp = sig / 5; - vp = Math.min(vp, 0.8); - volume += vp; - pitch = pitch < 0.1 ? (float) 0.1 : pitch; - - return SoundNotification.builder() - .sound(sound) - .isolation(isolation) - .predelay(predelay) - .volume(volume) - .pitch(pitch) - .build(); - } - - @Override - public long getTotalDuration() { - return isolation; - } - - @Override - public String getGroup() { - return group; - } - - public void play(AdaptPlayer p) { - SoundPlayer.of(p.getPlayer()).play(p.getPlayer().getLocation(), sound, volume, pitch); - } -} diff --git a/src/main/java/com/volmit/adapt/api/notification/TitleNotification.java b/src/main/java/com/volmit/adapt/api/notification/TitleNotification.java deleted file mode 100644 index 8551de2cc..000000000 --- a/src/main/java/com/volmit/adapt/api/notification/TitleNotification.java +++ /dev/null @@ -1,55 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.notification; - -import com.volmit.adapt.api.world.AdaptPlayer; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class TitleNotification implements Notification { - @Builder.Default - private final long in = 250; - @Builder.Default - private final long stay = 1450; - @Builder.Default - private final long out = 750; - @Builder.Default - private final String title = " "; - @Builder.Default - private final String subtitle = " "; - @Builder.Default - private final String group = "default"; - - @Override - public long getTotalDuration() { - return in + out + stay; - } - - @Override - public String getGroup() { - return group; - } - - @Override - public void play(AdaptPlayer p) { - p.getPlayer().sendTitle(title.isEmpty() ? " " : title, subtitle, (int) (in / 50D), (int) (stay / 50D), (int) (out / 50D)); - } -} diff --git a/src/main/java/com/volmit/adapt/api/potion/BrewingManager.java b/src/main/java/com/volmit/adapt/api/potion/BrewingManager.java deleted file mode 100644 index 3c3fa7d3e..000000000 --- a/src/main/java/com/volmit/adapt/api/potion/BrewingManager.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.volmit.adapt.api.potion; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.reflect.Reflect; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.BrewingStand; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.BrewerInventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.potion.PotionType; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -public class BrewingManager implements Listener { - - private static final Map> recipes = Maps.newHashMap(); - private static final Map activeTasks = Maps.newHashMap(); - - public static void registerRecipe(String adaptation, BrewingRecipe recipe) { - recipes.putIfAbsent(recipe, Lists.newArrayList(adaptation)); - recipes.computeIfPresent(recipe, (k, v) -> { - if (!v.contains(adaptation)) - v.add(adaptation); - return v; - }); - } - - @EventHandler - public void onInventoryClick(InventoryClickEvent e) { - if (e.getView().getTopInventory().getType() != InventoryType.BREWING || e.getView().getTopInventory().getHolder() == null) { - return; - } - Adapt.verbose("Brewing click: " + e.getRawSlot()); - BrewerInventory inv = (BrewerInventory) e.getInventory(); - boolean doTheThing = inv.getIngredient() == null - && e.getCursor() != null - && e.getRawSlot() == 3 - && e.getClickedInventory() != null - && e.getClickedInventory().getType().equals(InventoryType.BREWING) - && (e.getClick() == ClickType.LEFT); - if (doTheThing) { - Adapt.verbose("Brewing Stand Ingredient Clicked"); - e.setCancelled(true); - } - J.s(() -> { - if (doTheThing) { - inv.setIngredient(e.getCursor()); - e.setCursor(null); - } - BrewingStand stand = inv.getHolder(); - AdaptPlayer p = Adapt.instance.getAdaptServer().getPlayer((Player) e.getWhoClicked()); - Optional recipe = recipes.keySet().stream().filter(r -> BrewingTask.isValid(r, stand.getLocation())).findFirst(); - recipe.ifPresent(r -> { - if (activeTasks.containsKey(stand.getLocation())) { - BrewingTask t = activeTasks.get(stand.getLocation()); - if (!t.getRecipe().getId().equals(r.getId())) { - activeTasks.remove(stand.getLocation()).cancel(); - if (recipes.get(r).stream().noneMatch(p::hasAdaptation)) { - return; - } - activeTasks.put(stand.getLocation(), new BrewingTask(r, stand.getLocation())); - } - } else { - if (recipes.get(r).stream().noneMatch(p::hasAdaptation)) { - return; - } - activeTasks.put(stand.getLocation(), new BrewingTask(r, stand.getLocation())); - } - }); - if (recipe.isEmpty() && activeTasks.containsKey(stand.getLocation())) { - activeTasks.remove(stand.getLocation()).cancel(); - } - }); - } - - @EventHandler - public void onBrew(BrewEvent e) { - Material m = e.getContents().getIngredient().getType(); - if (m != Material.GUNPOWDER && m != Material.DRAGON_BREATH) { - return; - } - for (int i = 0; i < 3; i++) { - ItemStack s = e.getContents().getItem(i); - if (s == null) continue; - PotionMeta meta = (PotionMeta) s.getItemMeta(); - var opt = Reflect.getEnum(PotionType.class, "UNCRAFTABLE"); - if (opt.isEmpty() && meta.getBasePotionData() != null) - continue; - if (opt.isPresent() && meta.getBasePotionData().getType() == opt.get()) - continue; - ItemStack newStack = s.clone(); - if (m == Material.GUNPOWDER) { - newStack.setType(Material.SPLASH_POTION); - } else { - newStack.setType(Material.LINGERING_POTION); - /*PotionMeta meta = (PotionMeta)newStack.getItemMeta(); - List newEffects = Lists.newArrayList(); - meta.getCustomEffects().forEach(effect -> newEffects.add(new PotionEffect(effect.getType(), effect.getDuration() / 4, effect.getAmplifier()))); - meta.clearCustomEffects(); - newEffects.forEach(effect -> meta.addCustomEffect(effect, true)); - newStack.setItemMeta(meta);*/ - } - e.getResults().set(i, newStack); - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/potion/BrewingRecipe.java b/src/main/java/com/volmit/adapt/api/potion/BrewingRecipe.java deleted file mode 100644 index ce8297ad0..000000000 --- a/src/main/java/com/volmit/adapt/api/potion/BrewingRecipe.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.volmit.adapt.api.potion; - -import lombok.Builder; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -@Data -@Builder -public class BrewingRecipe { - private final String id; - private final Material ingredient; - private final ItemStack basePotion, result; - private final int brewingTime, fuelCost; -} diff --git a/src/main/java/com/volmit/adapt/api/potion/BrewingTask.java b/src/main/java/com/volmit/adapt/api/potion/BrewingTask.java deleted file mode 100644 index 66643f906..000000000 --- a/src/main/java/com/volmit/adapt/api/potion/BrewingTask.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.volmit.adapt.api.potion; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.SoundPlayer; -import lombok.Getter; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.BrewingStand; -import org.bukkit.entity.Player; -import org.bukkit.inventory.BrewerInventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; - -public class BrewingTask extends BukkitRunnable { - - private static final int DEFAULT_BREW_TIME = 400; - - @Getter - private final BrewingRecipe recipe; - - private final Location location; - private int brewTime; - - public BrewingTask(BrewingRecipe recipe, Location loc) { - this.recipe = recipe; - this.location = loc; - this.brewTime = recipe.getBrewingTime(); - - BrewingStand block = (BrewingStand) loc.getBlock().getState(); - if (block.getFuelLevel() > recipe.getFuelCost()) { - block.setFuelLevel(block.getFuelLevel() - recipe.getFuelCost()); - } else { - int rest = recipe.getFuelCost() - block.getFuelLevel(); - block.getInventory().setIngredient(decrease(block.getInventory().getFuel(), 1 + rest / 20)); - block.setFuelLevel(20 - rest % 20); - } - - block.setBrewingTime(DEFAULT_BREW_TIME); - block.update(true); - - runTaskTimer(Adapt.instance, 0L, 1L); - } - - public static ItemStack decrease(ItemStack source, int amount) { - if (source.getAmount() > amount) { - source.setAmount(source.getAmount() - amount); - return source; - } else { - return new ItemStack(Material.AIR); - } - } - - public static boolean isValid(BrewingRecipe recipe, Location loc) { - BrewingStand block = (BrewingStand) loc.getBlock().getState(); - BrewerInventory inv = block.getInventory(); - if (inv.getIngredient() == null || recipe.getIngredient() != inv.getIngredient().getType()) { - return false; - } - - int totalFuel = (inv.getFuel() != null && inv.getFuel().getType() != Material.AIR ? inv.getFuel().getAmount() * 20 : 0) + block.getFuelLevel(); - if (totalFuel < recipe.getFuelCost()) { - return false; - } - - for (int i = 0; i < 3; i++) { - if (recipe.getBasePotion().isSimilar(inv.getItem(i))) { - return true; - } - } - - return false; - } - - @Override - public void run() { - BrewingStand block = (BrewingStand) this.location.getBlock().getState(); - BrewerInventory inventory = block.getInventory(); - if (brewTime <= 0) { - inventory.setIngredient(decrease(inventory.getIngredient(), 1)); - - for (int i = 0; i < 3; i++) { - if (recipe.getBasePotion().equals(inventory.getItem(i))) { - inventory.setItem(i, recipe.getResult()); - } - } - - inventory.getViewers().forEach(e -> { - if (e instanceof Player p) { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(block.getLocation(), Sound.BLOCK_BREWING_STAND_BREW, 1, 1); - } - }); - cancel(); - return; - } - brewTime--; - block.setBrewingTime(getRemainingTime()); - block.update(true); - } - - private int getRemainingTime() { - return (int) (DEFAULT_BREW_TIME * (brewTime / (float) recipe.getBrewingTime())); - } -} diff --git a/src/main/java/com/volmit/adapt/api/potion/PotionBuilder.java b/src/main/java/com/volmit/adapt/api/potion/PotionBuilder.java deleted file mode 100644 index bb8b348de..000000000 --- a/src/main/java/com/volmit/adapt/api/potion/PotionBuilder.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.volmit.adapt.api.potion; - -import com.google.common.collect.Lists; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.util.reflect.registries.PotionTypes; -import lombok.AllArgsConstructor; -import lombok.Getter; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextDecoration; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -@Getter -public class PotionBuilder { - - private final List effects = Lists.newArrayList(); - private final Type type; - - private Component name; - private List lore; - private Color color; - private PotionType baseType = PotionTypes.UNCRAFTABLE; - private ItemStack baseItem; - - private PotionBuilder(Type type) { - this.type = type; - } - - public static ItemStack vanilla(@NotNull Type type, @NotNull PotionType potion) { - return of(type) - .setBaseType(potion) - .build(); - } - - public static PotionBuilder of(@NotNull Type type) { - return new PotionBuilder(type); - } - - public static PotionBuilder of(@NotNull ItemStack item) { - return Version.get().editPotion(item); - } - - public PotionBuilder setColor(@Nullable Color color) { - this.color = color; - return this; - } - - public PotionBuilder addEffect(@NotNull PotionEffect effect) { - effects.add(effect); - return this; - } - - public PotionBuilder addEffect(PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, boolean icon) { - effects.add(new PotionEffect(type, duration, amplifier, ambient, particles, icon)); - return this; - } - - public PotionBuilder setName(@Nullable String name) { - this.name = name != null ? Component.text(name) - .decoration(TextDecoration.ITALIC, false) : null; - return this; - } - - public PotionBuilder setName(@Nullable Component name) { - this.name = name; - return this; - } - - public PotionBuilder addLore(@NotNull Component lore) { - if(this.lore == null) { - this.lore = Lists.newArrayList(); - } - - this.lore.add(lore); - return this; - } - - public PotionBuilder setLore(@Nullable List<@NotNull Component> lore) { - this.lore = lore; - return this; - } - - public PotionBuilder setBaseItem(@Nullable ItemStack item) { - this.baseItem = item; - return this; - } - - public PotionBuilder setBaseType(@Nullable PotionType data) { - this.baseType = data; - return this; - } - - @SuppressWarnings("ConstantConditions") - public ItemStack build() { - return Version.get().buildPotion(this); - } - - @Getter - @AllArgsConstructor - public enum Type { - REGULAR(Material.POTION), - SPLASH(Material.SPLASH_POTION), - LINGERING(Material.LINGERING_POTION); - - private final Material material; - } -} diff --git a/src/main/java/com/volmit/adapt/api/protection/Protector.java b/src/main/java/com/volmit/adapt/api/protection/Protector.java deleted file mode 100644 index 87e1166d7..000000000 --- a/src/main/java/com/volmit/adapt/api/protection/Protector.java +++ /dev/null @@ -1,64 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.protection; - -import com.volmit.adapt.api.adaptation.Adaptation; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import java.util.UUID; - -public interface Protector { - - default boolean canBlockBreak(Player player, Location blockLocation, Adaptation adaptation) { - return true; - } - - default boolean canBlockPlace(Player player, Location blockLocation, Adaptation adaptation) { - return true; - } - - default boolean canPVP(Player player, Location victimLocation, Adaptation adaptation) { - return true; - } - - default boolean canPVE(Player player, Location victimLocation, Adaptation adaptation) { - return true; - } - - default boolean canInteract(Player player, Location targetLocation, Adaptation adaptation) { - return true; - } - - default boolean canAccessChest(Player player, Location chestLocation, Adaptation adaptation) { - return true; - } - - default boolean checkRegion(Player player, Location location, Adaptation adaptation) { - return true; - } - - - String getName(); - - boolean isEnabledByDefault(); - - default void unregister() { - } -} diff --git a/src/main/java/com/volmit/adapt/api/protection/ProtectorRegistry.java b/src/main/java/com/volmit/adapt/api/protection/ProtectorRegistry.java deleted file mode 100644 index 5eddab983..000000000 --- a/src/main/java/com/volmit/adapt/api/protection/ProtectorRegistry.java +++ /dev/null @@ -1,52 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.protection; - -import com.google.common.collect.ImmutableList; -import com.volmit.adapt.Adapt; - -import java.util.ArrayList; -import java.util.List; - -public class ProtectorRegistry { - private final List protectors = new ArrayList<>(); - - public void registerProtector(Protector protector) { - Adapt.verbose("Protector: \"" + protector.getName() + "\" registered."); - protectors.add(protector); - } - - public void unregisterProtector(Protector protector) { - protector.unregister(); - protectors.remove(protector); - } - - public List getDefaultProtectors() { - return protectors.stream().filter(Protector::isEnabledByDefault).collect(ImmutableList.toImmutableList()); - } - - public List getAllProtectors() { - return ImmutableList.copyOf(protectors); - } - - public void unregisterAll() { - protectors.forEach(Protector::unregister); - protectors.clear(); - } -} diff --git a/src/main/java/com/volmit/adapt/api/recipe/AdaptRecipe.java b/src/main/java/com/volmit/adapt/api/recipe/AdaptRecipe.java deleted file mode 100644 index e98496099..000000000 --- a/src/main/java/com/volmit/adapt/api/recipe/AdaptRecipe.java +++ /dev/null @@ -1,341 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.recipe; - -import com.volmit.adapt.Adapt; -import lombok.Builder; -import lombok.Data; -import lombok.Singular; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.inventory.*; - -import java.util.List; - -public interface AdaptRecipe { - static Shapeless.ShapelessBuilder shapeless() { - return Shapeless.builder(); - } - - static Shaped.ShapedBuilder shaped() { - return Shaped.builder(); - } - - static Smithing.SmithingBuilder smithing() { - return Smithing.builder(); - } - - static Stonecutter.StonecutterBuilder stonecutter() { - return Stonecutter.builder(); - } - - static Smoker.SmokerBuilder smoker() { - return Smoker.builder(); - } - - static Blast.BlastBuilder blast() { - return Blast.builder(); - } - - static Furnace.FurnaceBuilder furnace() { - return Furnace.builder(); - } - - static Campfire.CampfireBuilder campfire() { - return Campfire.builder(); - } - - ItemStack getResult(); - - String getKey(); - - default NamespacedKey getNSKey() { - return new NamespacedKey(Adapt.instance, getKey()); - } - - void register(); - - boolean is(Recipe recipe); - - void unregister(); - - @Builder - @Data - class Smoker implements AdaptRecipe { - private String key; - private ItemStack result; - private Material ingredient; - private float experience; - private int cookTime; - - @Override - public ItemStack getResult() { - return null; - } - - public void register() { - SmokingRecipe s = new SmokingRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient, experience, cookTime); - Bukkit.getServer().addRecipe(s); - Adapt.verbose("Registered Smoker Recipe " + s.getKey()); - } - - @Override - public boolean is(Recipe recipe) { - return recipe instanceof SmokingRecipe s && s.getKey().equals(getNSKey()); - } - - - @Override - public void unregister() { - Bukkit.getServer().removeRecipe(getNSKey()); - Adapt.verbose("Unregistered Smoker Recipe " + getKey()); - } - } - - @Builder - @Data - class Furnace implements AdaptRecipe { - private String key; - private ItemStack result; - private Material ingredient; - // private float experience = 1; -// private int cookTime = 20; - private float experience; - private int cookTime; - - @Override - public ItemStack getResult() { - return null; - } - - public void register() { - FurnaceRecipe s = new FurnaceRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient, experience, cookTime); - Bukkit.getServer().addRecipe(s); - Adapt.verbose("Registered Furnace Recipe " + s.getKey()); - } - - @Override - public boolean is(Recipe recipe) { - return recipe instanceof FurnaceRecipe s && s.getKey().equals(getNSKey()); - } - - - @Override - public void unregister() { - Bukkit.getServer().removeRecipe(getNSKey()); - Adapt.verbose("Unregistered Furnace Recipe " + getKey()); - } - } - - @Builder - @Data - class Campfire implements AdaptRecipe { - private String key; - private ItemStack result; - private Material ingredient; - private float experience; - private int cookTime; - - @Override - public ItemStack getResult() { - return null; - } - - public void register() { - CampfireRecipe s = new CampfireRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient, experience, cookTime); - Bukkit.getServer().addRecipe(s); - Adapt.verbose("Registered Campfire Recipe " + s.getKey()); - } - - @Override - public boolean is(Recipe recipe) { - return recipe instanceof CampfireRecipe s && s.getKey().equals(getNSKey()); - } - - @Override - public void unregister() { - Bukkit.getServer().removeRecipe(getNSKey()); - Adapt.verbose("Unregistered Campfire Recipe " + getKey()); - } - } - - @Builder - @Data - class Blast implements AdaptRecipe { - private String key; - private ItemStack result; - private Material ingredient; - private float experience; - private int cookTime; - - @Override - public ItemStack getResult() { - return null; - } - - public void register() { - BlastingRecipe s = new BlastingRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient, experience, cookTime); - Bukkit.getServer().addRecipe(s); - Adapt.verbose("Registered Blast Furnace Recipe " + s.getKey()); - } - - @Override - public boolean is(Recipe recipe) { - return recipe instanceof BlastingRecipe s && s.getKey().equals(getNSKey()); - } - - - @Override - public void unregister() { - Bukkit.getServer().removeRecipe(getNSKey()); - Adapt.verbose("Unregistered Blast Furnace Recipe " + getKey()); - } - } - - @Builder - @Data - class Shapeless implements AdaptRecipe { - private String key; - private ItemStack result; - @Singular - private List ingredients; - - @Override - public ItemStack getResult() { - return null; - } - - public void register() { - ShapelessRecipe s = new ShapelessRecipe(new NamespacedKey(Adapt.instance, getKey()), result); - ingredients.forEach(s::addIngredient); - Bukkit.getServer().addRecipe(s); - Adapt.verbose("Registered Shapeless Crafting Recipe " + s.getKey()); - } - - @Override - public boolean is(Recipe recipe) { - return recipe instanceof ShapelessRecipe s && s.getKey().equals(getNSKey()); - } - - - @Override - public void unregister() { - Bukkit.getServer().removeRecipe(getNSKey()); - Adapt.verbose("Unregistered Shapeless Crafting Recipe " + getKey()); - } - } - - @Builder - @Data - class Stonecutter implements AdaptRecipe { - private String key; - private ItemStack result; - private Material ingredient; - - @Override - public ItemStack getResult() { - return null; - } - - public void register() { - StonecuttingRecipe s = new StonecuttingRecipe(new NamespacedKey(Adapt.instance, getKey()), result, ingredient); - Bukkit.getServer().addRecipe(s); - Adapt.verbose("Registered Stone Cutter Recipe " + s.getKey()); - } - - @Override - public boolean is(Recipe recipe) { - return recipe instanceof StonecuttingRecipe s && s.getKey().equals(getNSKey()); - } - - - @Override - public void unregister() { - Bukkit.getServer().removeRecipe(getNSKey()); - Adapt.verbose("Unregistered Stone Cutter Recipe " + getKey()); - } - } - - @Builder - @Data - class Shaped implements AdaptRecipe { - private String key; - private ItemStack result; - @Singular - private List ingredients; - @Singular - private List shapes; - - @Override - public ItemStack getResult() { - return null; - } - - public void register() { - ShapedRecipe s = new ShapedRecipe(new NamespacedKey(Adapt.instance, getKey()), result); - s.shape(shapes.toArray(new String[0])); - ingredients.forEach(i -> s.setIngredient(i.getCharacter(), i.getChoice())); - Bukkit.getServer().addRecipe(s); - Adapt.verbose("Registered Shaped Crafting Recipe " + s.getKey()); - } - - @Override - public boolean is(Recipe recipe) { - return recipe instanceof ShapedRecipe s && s.getKey().equals(getNSKey()); - } - - @Override - public void unregister() { - Bukkit.getServer().removeRecipe(getNSKey()); - Adapt.verbose("Unregistered Shaped Crafting Recipe " + getKey()); - } - } - - @Builder - @Data - class Smithing implements AdaptRecipe { - private String key; - private ItemStack result; - private Material base; - private Material addition; - - @Override - public ItemStack getResult() { - return null; - } - - public void register() { - SmithingRecipe s = new SmithingRecipe(new NamespacedKey(Adapt.instance, getKey()), result, new RecipeChoice.ExactChoice(new ItemStack(base)), new RecipeChoice.ExactChoice(new ItemStack(addition))); - Bukkit.getServer().addRecipe(s); - Adapt.verbose("Registered Smithing Table Recipe " + s.getKey()); - } - - @Override - public boolean is(Recipe recipe) { - return recipe instanceof SmithingRecipe s && s.getKey().equals(getNSKey()); - } - - @Override - public void unregister() { - Bukkit.getServer().removeRecipe(getNSKey()); - Adapt.verbose("Unregistered Smithing Table Recipe " + getKey()); - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/recipe/MaterialChar.java b/src/main/java/com/volmit/adapt/api/recipe/MaterialChar.java deleted file mode 100644 index 2645376f6..000000000 --- a/src/main/java/com/volmit/adapt/api/recipe/MaterialChar.java +++ /dev/null @@ -1,48 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.recipe; - -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.Tag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.RecipeChoice; - -@AllArgsConstructor -@Data -public class MaterialChar { - private char character; - private RecipeChoice choice; - - public MaterialChar(char character, Tag tag) { - this.character = character; - this.choice = new RecipeChoice.MaterialChoice(tag); - } - - public MaterialChar(char character, Material... material) { - this.character = character; - this.choice = new RecipeChoice.MaterialChoice(material); - } - - public MaterialChar(char character, ItemStack... itemStack) { - this.character = character; - this.choice = new RecipeChoice.ExactChoice(itemStack); - } -} diff --git a/src/main/java/com/volmit/adapt/api/runtime/AdaptationGate.java b/src/main/java/com/volmit/adapt/api/runtime/AdaptationGate.java deleted file mode 100644 index c31d828a9..000000000 --- a/src/main/java/com/volmit/adapt/api/runtime/AdaptationGate.java +++ /dev/null @@ -1,70 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.runtime; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.skill.Skill; -import org.bukkit.GameMode; -import org.bukkit.World; -import org.bukkit.entity.Player; - -public final class AdaptationGate { - private AdaptationGate() { - } - - public static boolean shouldSkipPlayer(Player player, Skill skill, boolean hasAdaptPlayer) { - if (player == null || skill == null) { - return true; - } - - if (!player.getClass().getSimpleName().equals("CraftPlayer")) { - return true; - } - - return !skill.isEnabled() - || skill.hasBlacklistPermission(player, skill) - || isWorldBlacklisted(player) - || isInCreativeOrSpectator(player) - || !hasAdaptPlayer; - } - - public static boolean shouldSkipWorld(World world, Skill skill) { - if (world == null || skill == null) { - return true; - } - - return !skill.isEnabled() || AdaptConfig.get().blacklistedWorlds.contains(world.getName()); - } - - public static boolean isWorldBlacklisted(Player player) { - if (player == null) { - return true; - } - return AdaptConfig.get().blacklistedWorlds.contains(player.getWorld().getName()); - } - - public static boolean isInCreativeOrSpectator(Player player) { - if (player == null) { - return true; - } - - return !AdaptConfig.get().isXpInCreative() - && (player.getGameMode().equals(GameMode.CREATIVE) || player.getGameMode().equals(GameMode.SPECTATOR)); - } -} diff --git a/src/main/java/com/volmit/adapt/api/skill/SimpleSkill.java b/src/main/java/com/volmit/adapt/api/skill/SimpleSkill.java deleted file mode 100644 index 9395276f3..000000000 --- a/src/main/java/com/volmit/adapt/api/skill/SimpleSkill.java +++ /dev/null @@ -1,357 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.skill; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.runtime.AdaptationGate; -import com.volmit.adapt.api.tick.TickedObject; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigFileSupport; -import com.volmit.adapt.util.collection.KList; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.UUID; - -@EqualsAndHashCode(callSuper = false) -@Data -public abstract class SimpleSkill extends TickedObject implements Skill { - private final String name; - private final String emojiName; - private C color; - private double minXp; - private String description; - private String displayName; - private Material icon; - @EqualsAndHashCode.Exclude - private KList> adaptations; - private KList statTrackers; - private KList cachedAdvancements; - private String advancementBackground; - private KList recipes; - private Class configType; - private volatile T config; - - public SimpleSkill(String name, String emojiName) { - super("skill", UUID.randomUUID() + "-skill-" + name, 50); - statTrackers = new KList<>(); - recipes = new KList<>(); - cachedAdvancements = new KList<>(); - this.emojiName = emojiName; - adaptations = new KList<>(); - setColor(C.WHITE); - this.name = name; - setIcon(Material.BOOK); - setDescription("No Description Provided"); - setMinXp(100); - setAdvancementBackground("minecraft:textures/block/deepslate_tiles.png"); - - J.a(() -> { - J.attempt(this::getConfig); - getAdaptations().forEach(i -> J.attempt(i::getConfig)); - }, 1); - } - - @Override - public Class getConfigurationClass() { - return configType; - } - - @Override - public void registerConfiguration(Class type) { - this.configType = type; - } - - protected File getConfigFile() { - return Adapt.instance.getDataFile("adapt", "skills", getName() + ".toml"); - } - - protected File getLegacyConfigFile() { - return Adapt.instance.getDataFile("adapt", "skills", getName() + ".json"); - } - - protected T createDefaultConfig() { - try { - return getConfigurationClass().getConstructor().newInstance(); - } catch (Throwable e) { - throw new IllegalStateException("Failed to create default config for skill " + getName(), e); - } - } - - public synchronized boolean reloadConfigFromDisk(boolean announce) { - if (getConfigurationClass() == null) { - return false; - } - - T previous = config; - File file = getConfigFile(); - try { - T loaded = loadConfig(file, previous == null ? createDefaultConfig() : previous, previous == null); - config = loaded; - onConfigReload(previous, loaded); - if (announce) { - Adapt.info("Hotloaded " + file.getPath()); - } - return true; - } catch (Throwable e) { - Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid config: " + e.getMessage()); - return false; - } - } - - private T loadConfig(File file, T fallback, boolean overwriteOnReadFailure) throws IOException { - return ConfigFileSupport.load( - file, - getLegacyConfigFile(), - getConfigurationClass(), - fallback, - overwriteOnReadFailure, - "skill:" + getName(), - "Created missing skill config [adapt/skills/" + getName() + ".toml] from defaults." - ); - } - - protected void onConfigReload(T previousConfig, T newConfig) { - applyDoubleField(newConfig, "minXp", this::setMinXp); - Number interval = getNumericField(newConfig, "setInterval"); - if (interval != null) { - setInterval(interval.longValue()); - } - } - - private void applyDoubleField(T source, String fieldName, java.util.function.DoubleConsumer consumer) { - Number number = getNumericField(source, fieldName); - if (number != null) { - consumer.accept(number.doubleValue()); - } - } - - private Number getNumericField(T source, String fieldName) { - Field f = getField(source.getClass(), fieldName); - if (f == null) { - return null; - } - - try { - f.setAccessible(true); - Object value = f.get(source); - if (value instanceof Number number) { - return number; - } - } catch (Throwable ignored) { - Adapt.verbose("Failed reading config field '" + fieldName + "' for skill " + getName()); - } - - return null; - } - - private Field getField(Class type, String name) { - Class current = type; - while (current != null) { - try { - return current.getDeclaredField(name); - } catch (NoSuchFieldException ignored) { - current = current.getSuperclass(); - } - } - - return null; - } - - @Override - public T getConfig() { - T local = config; - if (local != null) { - return local; - } - - synchronized (this) { - local = config; - if (local != null) { - return local; - } - - boolean loaded = reloadConfigFromDisk(false); - local = config; - if (!loaded || local == null) { - local = createDefaultConfig(); - onConfigReload(null, local); - config = local; - Adapt.warn("Falling back to in-memory defaults for skill config " + getName() + "."); - } - } - - return local; - } - - public void registerRecipe(AdaptRecipe r) { - recipes.add(r); - } - - public void registerAdvancement(AdaptAdvancement a) { - cachedAdvancements.add(a); - } - - public boolean checkValidEntity(EntityType e) { - if (!e.isAlive() || e.equals(EntityType.PARROT)) { - return false; - } - return !ItemListings.getInvalidDamageableEntities().contains(e); - } - - protected boolean shouldReturnForPlayer(Player p) { - try { - if (p == null) { - return true; - } - Adapt.verbose("Checking " + p.getName() + " for " + getName()); - return AdaptationGate.shouldSkipPlayer(p, this, getPlayer(p) != null); - } catch (Exception ignored) { - return true; - } - } - protected void shouldReturnForPlayer(Player p, Runnable r) { - try { - if (shouldReturnForPlayer(p)) { - return; - } - r.run(); - } catch (Exception ignored) { - } - } - - protected void shouldReturnForPlayer(Player p, Cancellable c, Runnable r) { - try { - if (c.isCancelled()) { - return; - } - if (shouldReturnForPlayer(p)) { - return; - } - r.run(); - } catch (Exception ignored) { - } - } - - protected boolean shouldReturnForWorld(World world, Skill skill) { - try { - return AdaptationGate.shouldSkipWorld(world, skill); - } catch (Exception ignored) { - return true; - } - } - - protected boolean isWorldBlacklisted(Player p) { - return AdaptationGate.isWorldBlacklisted(p); - } - - protected boolean isInCreativeOrSpectator(Player p) { - return AdaptationGate.isInCreativeOrSpectator(p); - } - - @Override - public String getDisplayName() { - return displayName == null ? Skill.super.getDisplayName() : (C.RESET + "" + C.BOLD + getColor().toString() + getEmojiName() + " " + displayName); - } - - @Override - public void onRegisterAdvancements(KList advancements) { - advancements.addAll(cachedAdvancements); - } - - public AdaptAdvancement buildAdvancements() { - KList a = new KList<>(); - onRegisterAdvancements(a); - - for (Adaptation i : getAdaptations()) { - a.add(i.buildAdvancements()); - } - - return AdaptAdvancement.builder() - .background(getAdvancementBackground()) - .key("skill_" + getName()) - .title(displayName) - .description(getDescription()) - .icon(getIcon()) - .model(getModel()) - .children(a) - .visibility(AdvancementVisibility.HIDDEN) - .build(); - } - - @Override - public void registerStatTracker(AdaptStatTracker tracker) { - getStatTrackers().add(tracker); - } - - protected void registerMilestone(String advancementKey, String stat, double goal, double reward) { - registerStatTracker(AdaptStatTracker.builder() - .advancement(advancementKey) - .stat(stat) - .goal(goal) - .reward(reward) - .build()); - } - - protected void checkStatTrackersForOnlinePlayers() { - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player player = adaptPlayer.getPlayer(); - if (shouldReturnForPlayer(player)) { - continue; - } - checkStatTrackers(adaptPlayer); - } - } - - @Override - public KList getStatTrackers() { - return statTrackers; - } - - @Override - public void registerAdaptation(Adaptation a) { - a.setSkill(this); - adaptations.add(a); - } - - @Override - public void unregister() { - super.unregister(); - adaptations.forEach(Adaptation::unregister); - } - - @Override - public abstract void onTick(); -} diff --git a/src/main/java/com/volmit/adapt/api/skill/Skill.java b/src/main/java/com/volmit/adapt/api/skill/Skill.java deleted file mode 100644 index 372662c29..000000000 --- a/src/main/java/com/volmit/adapt/api/skill/Skill.java +++ /dev/null @@ -1,477 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.skill; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.Component; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.tick.Ticked; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerData; -import com.volmit.adapt.api.xp.XP; -import com.volmit.adapt.content.gui.SkillsGui; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.collection.KList; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; - -public interface Skill extends Ticked, Component { - AdaptAdvancement buildAdvancements(); - - Class getConfigurationClass(); - - void registerConfiguration(Class type); - - boolean isEnabled(); - - T getConfig(); - - String getName(); - - String getEmojiName(); - - Material getIcon(); - - String getDescription(); - - KList getRecipes(); - - void registerAdaptation(Adaptation a); - - void registerStatTracker(AdaptStatTracker tracker); - - KList getStatTrackers(); - - @Override - default boolean areParticlesEnabled() { - if (!Component.super.areParticlesEnabled()) { - return false; - } - - AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); - if (effects != null && effects.getSkillParticleOverrides() != null && !effects.getSkillParticleOverrides().isEmpty()) { - String key = getName(); - Boolean override = effects.getSkillParticleOverrides().get(key); - if (override == null && key != null) { - override = effects.getSkillParticleOverrides().get(key.toLowerCase(Locale.ROOT)); - } - if (override != null && !override) { - return false; - } - } - - Object config = getConfig(); - if (config != null) { - Boolean directToggle = readBooleanField(config, "showParticles"); - if (directToggle != null && !directToggle) { - return false; - } - - Boolean genericToggle = readBooleanField(config, "showParticleEffects"); - if (genericToggle != null && !genericToggle) { - return false; - } - } - - return true; - } - - @Override - default boolean areSoundsEnabled() { - if (!Component.super.areSoundsEnabled()) { - return false; - } - - Object config = getConfig(); - if (config != null) { - Boolean directToggle = readBooleanField(config, "showSounds"); - if (directToggle != null && !directToggle) { - return false; - } - } - - return true; - } - - private static Boolean readBooleanField(Object source, String fieldName) { - if (source == null || fieldName == null || fieldName.isBlank()) { - return null; - } - - Class current = source.getClass(); - while (current != null) { - try { - Field field = current.getDeclaredField(fieldName); - field.setAccessible(true); - Object value = field.get(source); - if (value instanceof Boolean bool) { - return bool; - } - return null; - } catch (NoSuchFieldException ignored) { - current = current.getSuperclass(); - } catch (Throwable ignored) { - return null; - } - } - - return null; - } - - default void checkStatTrackers(AdaptPlayer player) { - if (!this.isEnabled()) { - return; - } - if (!player.getPlayer().getClass().getSimpleName().equals("CraftPlayer")) { - return; - } - if (!AdaptConfig.get().isAdvancements()) { - return; - } - PlayerData d = player.getData(); - - for (AdaptStatTracker i : getStatTrackers()) { - if (!d.isGranted(i.getAdvancement()) && d.getStat(i.getStat()) >= i.getGoal()) { - player.getAdvancementHandler().grant(i.getAdvancement()); - xp(player.getPlayer(), i.getReward()); - } - } - - for (Adaptation adaptation : getAdaptations()) { - if (!(adaptation instanceof SimpleAdaptation sa)) continue; - if (!adaptation.isEnabled()) continue; - for (AdaptStatTracker tracker : sa.getStatTrackers()) { - if (!d.isGranted(tracker.getAdvancement()) && d.getStat(tracker.getStat()) >= tracker.getGoal()) { - player.getAdvancementHandler().grant(tracker.getAdvancement()); - xp(player.getPlayer(), tracker.getReward()); - } - } - } - } - - KList> getAdaptations(); - - C getColor(); - - double getMinXp(); - - void onRegisterAdvancements(KList advancements); - - default boolean hasBlacklistPermission(Player p, Skill s) { - if (p.isOp()) { // If the player is an operator, bypass the permission check - return false; - } - String blacklistPermission = "adapt.blacklist." + s.getName().replaceAll("-", ""); - Adapt.verbose("Checking if player " + p.getName() + " has blacklist permission " + blacklistPermission); - return p.hasPermission(blacklistPermission); - } - - default String getDisplayName() { - if (!this.isEnabled()) { - return C.DARK_GRAY + Form.capitalize(getName()); - } - return C.RESET + "" + C.BOLD + getColor().toString() + getEmojiName() + " " + Form.capitalize(getName()); - } - - default String getShortName() { - if (!this.isEnabled()) { - return C.DARK_GRAY + Form.capitalize(getName()); - } - return C.RESET + "" + C.BOLD + getColor().toString() + getEmojiName(); - } - - default String getDisplayName(int level) { - if (!this.isEnabled()) { - return C.DARK_GRAY + Form.capitalize(getName()); - } - return getDisplayName() + C.RESET + " " + C.UNDERLINE + C.WHITE + level + C.RESET; - } - - default CustomModel getModel() { - return CustomModel.get(getIcon(), "skill", getName()); - } - - default void xp(Player p, double xp) { - xp(p, xp, null); - } - - default void xp(Player p, double xp, String rewardKey) { - if (!this.isEnabled()) { - return; - } - if (!p.getClass().getSimpleName().equals("CraftPlayer")) { - return; - } - xp(p, p.getLocation(), xp, rewardKey); - - } - - default void xp(Player p, Location at, double xp) { - xp(p, at, xp, null); - } - - default void xp(Player p, Location at, double xp, String rewardKey) { - if (!this.isEnabled()) { - return; - } - if (!p.getClass().getSimpleName().equals("CraftPlayer")) { - return; - } - try { - XP.xp(p, this, xp, rewardKey); - if (xp > 50) { - vfxXP(p, at, (int) xp); - } - Adapt.verbose("Gave " + p.getName() + " " + xp + " xp in " + getName() + " " + this.getClass()); - } catch (Exception e) { - Adapt.verbose("Failed to give xp to " + p.getName() + " for " + getName() + " (" + xp + ")"); - } - } - - default void xpS(Player p, Location at, double xp) { - xpS(p, at, xp, null); - } - - default void xpS(Player p, Location at, double xp, String rewardKey) { - if (!this.isEnabled()) { - return; - } - if (!p.getClass().getSimpleName().equals("CraftPlayer")) { - return; - } - try { - XP.xpSilent(p, this, xp, rewardKey); - if (xp > 50) { - vfxXP(p, at, (int) xp); - } - Adapt.verbose("Gave " + p.getName() + " " + xp + " xp in " + getName() + " " + this.getClass()); - } catch (Exception e) { - Adapt.verbose("Failed to give xp to " + p.getName() + " for " + getName() + " (" + xp + ")"); - } - } - - default void xpSilent(Player p, double xp) { - xpSilent(p, xp, null); - } - - default void xpSilent(Player p, double xp, String rewardKey) { - if (!this.isEnabled()) { - return; - } - if (!p.getClass().getSimpleName().equals("CraftPlayer")) { - return; - } - try { - XP.xpSilent(p, this, xp, rewardKey); - } catch ( - Exception ignored) { // Player was Given XP (Likely Teleportation) before i can see it because some plugin has higher priority than me and moves a player. so im not going to throw an error, as i know why it's happening. - Adapt.verbose("Player was Given XP (Likely Teleportation) before i can see it because some plugin has higher priority than me and moves a player. so im not going to throw an error, as i know why it's happening."); - } - } - - - default void xp(Location at, double xp, int rad, long duration) { - XP.spatialXP(at, this, xp, rad, duration); - vfxXP(at); - } - - default void knowledge(Player p, long k) { - if (!this.isEnabled()) { - return; - } - XP.knowledge(p, this, k); - } - - default boolean openGui(Player player, boolean checkPermissions) { - if (hasBlacklistPermission(player, this)) { - return false; - } else { - openGui(player); - return true; - } - } - - default void openGui(Player player) { - openGui(player, 0); - } - - default void openGui(Player player, int page) { - if (!this.isEnabled()) { - return; - } - if (!player.getClass().getSimpleName().equals("CraftPlayer")) { - return; - } - if (!Bukkit.isPrimaryThread()) { - int targetPage = page; - J.s(() -> openGui(player, targetPage)); - return; - } - - SoundPlayer spw = SoundPlayer.of(player.getWorld()); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 1.455f); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 1.855f); - - List> visibleAdaptations = new ArrayList<>(); - for (Adaptation adaptation : getAdaptations()) { - if (!adaptation.isEnabled()) { - continue; - } - if (!adaptation.getSkill().isEnabled()) { - continue; - } - if (adaptation.hasBlacklistPermission(player, adaptation)) { - continue; - } - visibleAdaptations.add(adaptation); - } - visibleAdaptations.sort(Comparator.comparing(adaptation -> normalizeSortKey(adaptation.getDisplayName()))); - - boolean reserveNavigation = AdaptConfig.get().isGuiBackButton(); - GuiLayout.PagePlan plan = GuiLayout.plan(visibleAdaptations.size(), reserveNavigation); - int currentPage = GuiLayout.clampPage(page, plan.pageCount()); - int start = currentPage * plan.itemsPerPage(); - int end = Math.min(visibleAdaptations.size(), start + plan.itemsPerPage()); - - Window w = new UIWindow(player); - GuiTheme.apply(w, "skill/" + getName()); - w.setViewportHeight(plan.rows()); - - if (visibleAdaptations.isEmpty()) { - w.setElement(0, 0, new UIElement("ada-empty") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.GRAY + "No adaptations available")); - } else { - List reveal = new ArrayList<>(); - for (int row = 0; row < plan.contentRows(); row++) { - int rowStart = start + (row * GuiLayout.WIDTH); - if (rowStart >= end) { - break; - } - - int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); - for (int i = 0; i < rowCount; i++) { - Adaptation adaptation = visibleAdaptations.get(rowStart + i); - int lvl = getPlayer(player).getData().getSkillLine(getName()).getAdaptationLevel(adaptation.getName()); - int pos = GuiLayout.centeredPosition(i, rowCount); - Element element = new UIElement("ada-" + adaptation.getName()) - .setMaterial(new MaterialBlock(adaptation.getIcon())) - .setModel(adaptation.getModel()) - .setName(adaptation.getDisplayName(lvl)) - .addLore(Form.wrapWordsPrefixed(adaptation.getDescription(), "" + C.GRAY, 45)) - .addLore(lvl == 0 ? (C.DARK_GRAY + Localizer.dLocalize("snippets.gui.not_learned")) : (C.GRAY + Localizer.dLocalize("snippets.gui.level") + " " + C.WHITE + Form.toRoman(lvl))) - .setProgress(1D) - .onLeftClick((e) -> adaptation.openGui(player)); - reveal.add(new GuiEffects.Placement(pos, row, element)); - } - } - GuiEffects.applyReveal(w, reveal); - } - - if (plan.hasNavigationRow()) { - int navRow = plan.rows() - 1; - int jumpPages = 5; - int jumpBack = Math.max(0, currentPage - jumpPages); - int jumpForward = Math.min(plan.pageCount() - 1, currentPage + jumpPages); - if (currentPage > 0) { - w.setElement(-4, navRow, new UIElement("skill-prev") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.WHITE + "Previous") - .addLore(C.GRAY + "Right click: jump -" + jumpPages + " pages") - .onLeftClick((e) -> openGui(player, currentPage - 1)) - .onRightClick((e) -> openGui(player, jumpBack))); - w.setElement(-3, navRow, new UIElement("skill-first") - .setMaterial(new MaterialBlock(Material.LECTERN)) - .setName(C.GRAY + "First") - .onLeftClick((e) -> openGui(player, 0))); - } - if (currentPage < plan.pageCount() - 1) { - w.setElement(4, navRow, new UIElement("skill-next") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.WHITE + "Next") - .addLore(C.GRAY + "Right click: jump +" + jumpPages + " pages") - .onLeftClick((e) -> openGui(player, currentPage + 1)) - .onRightClick((e) -> openGui(player, jumpForward))); - w.setElement(3, navRow, new UIElement("skill-last") - .setMaterial(new MaterialBlock(Material.LECTERN)) - .setName(C.GRAY + "Last") - .onLeftClick((e) -> openGui(player, plan.pageCount() - 1))); - } - - int from = visibleAdaptations.isEmpty() ? 0 : (start + 1); - int to = visibleAdaptations.isEmpty() ? 0 : end; - w.setElement(-1, navRow, new UIElement("skill-page-info") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.AQUA + "Page " + (currentPage + 1) + "/" + plan.pageCount()) - .addLore(C.GRAY + "Showing " + from + "-" + to + " of " + visibleAdaptations.size()) - .setProgress(1D)); - - if (AdaptConfig.get().isGuiBackButton()) { - w.setElement(0, navRow, new UIElement("back") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName("" + C.RESET + C.GRAY + Localizer.dLocalize("snippets.gui.back")) - .onLeftClick((e) -> onGuiClose(player, true))); - } - - } - - AdaptPlayer a = Adapt.instance.getAdaptServer().getPlayer(player); - String pageSuffix = plan.pageCount() > 1 ? " [" + (currentPage + 1) + "/" + plan.pageCount() + "]" : ""; - w.setTitle(getDisplayName(a.getSkillLine(getName()).getLevel()) + " " + Form.pc(XP.getLevelProgress(a.getSkillLine(getName()).getXp())) + " (" + Form.f((int) XP.getXpUntilLevelUp(a.getSkillLine(getName()).getXp())) + Localizer.dLocalize("snippets.gui.xp") + " " + (a.getSkillLine(getName()).getLevel() + 1) + ")" + pageSuffix); - w.onClosed((vv) -> J.s(() -> onGuiClose(player, !AdaptConfig.get().isEscClosesAllGuis()))); - w.open(); - Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); - } - - private void onGuiClose(Player player, boolean openPrevGui) { - SoundPlayer spw = SoundPlayer.of(player.getWorld()); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 1.455f); - spw.play(player.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 1.855f); - if (openPrevGui) { - SkillsGui.open(player); - } else { - Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString()); - } - } - - private static String normalizeSortKey(String value) { - if (value == null) { - return ""; - } - - String normalized = C.stripColor(value).toLowerCase(Locale.ROOT).trim(); - return normalized.replaceFirst("^[^\\p{L}\\p{N}]+", ""); - } -} diff --git a/src/main/java/com/volmit/adapt/api/skill/SkillRegistry.java b/src/main/java/com/volmit/adapt/api/skill/SkillRegistry.java deleted file mode 100644 index e2f24fc5d..000000000 --- a/src/main/java/com/volmit/adapt/api/skill/SkillRegistry.java +++ /dev/null @@ -1,506 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.skill; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.potion.BrewingManager; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.tick.TickedObject; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.api.xp.XPMultiplier; -import com.volmit.adapt.content.gui.SkillsGui; -import com.volmit.adapt.content.skill.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.reflect.registries.Particles; -import org.bukkit.Bukkit; -import org.bukkit.Keyed; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.Sound; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerExpChangeEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.Recipe; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.scheduler.BukkitTask; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -public class SkillRegistry extends TickedObject { - private static final long SLOW_SKILL_REG_MS = 300L; - private static final int DEFERRED_SKILLS_PER_TICK = 2; - - public static final KMap> skills = new KMap<>(); - private final KMap> knownSkills = new KMap<>(); - private final KMap>> skillTypes = new KMap<>(); - private final Map> adaptationRecipeIndex = new ConcurrentHashMap<>(); - private final Deque> deferredBootstrapRecipeRegistration = new ArrayDeque<>(); - private volatile BukkitTask deferredBootstrapRecipeTask; - private volatile boolean bootstrapLoading = true; - - public SkillRegistry() { - super("registry", UUID.randomUUID() + "-sk", 1250); - registerSkill(SkillAgility.class); - registerSkill(SkillArchitect.class); - registerSkill(SkillAxes.class); - registerSkill(SkillBlocking.class); - registerSkill(SkillChronos.class); - registerSkill(SkillCrafting.class); - registerSkill(SkillDiscovery.class); - registerSkill(SkillEnchanting.class); - registerSkill(SkillHerbalism.class); - registerSkill(SkillHunter.class); - registerSkill(SkillPickaxes.class); - registerSkill(SkillRanged.class); - registerSkill(SkillRift.class); - registerSkill(SkillSeaborne.class); - registerSkill(SkillStealth.class); - registerSkill(SkillSwords.class); - registerSkill(SkillTaming.class); - registerSkill(SkillTragOul.class); - registerSkill(SkillUnarmed.class); - registerSkill(SkillExcavation.class); - registerSkill(SkillBrewing.class); - registerSkill(SkillNether.class); - bootstrapLoading = false; - scheduleDeferredBootstrapRecipeRegistration(); - } - - @EventHandler - public void on(PlayerExpChangeEvent e) { - Player p = e.getPlayer(); - if (e.getAmount() > 0) { - getPlayer(p).boostXPToRecents(0.03, 10000); - } - } - - private boolean canInteract(Player player, Location targetLocation) { - return Adapt.instance.getProtectorRegistry().getAllProtectors().stream().allMatch(protector -> protector.canInteract(player, targetLocation, null)); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - - boolean commonConditions = p.isSneaking() && e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && e.getClickedBlock() != null; - boolean isLectern = commonConditions && e.getClickedBlock().getType().equals(Material.LECTERN); - boolean isObserver = commonConditions && e.getClickedBlock().getType().equals(Material.OBSERVER); - boolean isAdaptActivator = !e.getBlockFace().equals(BlockFace.UP) && !e.getBlockFace().equals(BlockFace.DOWN) && !p.isSneaking() && e.getAction().equals(Action.RIGHT_CLICK_BLOCK) - && e.getClickedBlock() != null - && canInteract(p, e.getClickedBlock().getLocation()) - && e.getClickedBlock().getType().equals(Material.valueOf(AdaptConfig.get().adaptActivatorBlock)) && (p.getInventory().getItemInMainHand().getType().equals(Material.AIR) - || !p.getInventory().getItemInMainHand().getType().isBlock()) && - (p.getInventory().getItemInOffHand().getType().equals(Material.AIR) || !p.getInventory().getItemInOffHand().getType().isBlock()); - - if (isAdaptActivator) { - SoundPlayer spw = SoundPlayer.of(e.getClickedBlock().getWorld()); - spw.play(e.getClickedBlock().getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.5f, 0.72f); - spw.play(e.getClickedBlock().getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 0.35f, 0.755f); - SkillsGui.open(p); - e.setCancelled(true); - p.getWorld().spawnParticle(Particles.CRIT_MAGIC, e.getClickedBlock().getLocation().clone().add(0.5, 1, 0.5), 25, 0, 0, 0, 1.1); - p.getWorld().spawnParticle(Particles.ENCHANTMENT_TABLE, e.getClickedBlock().getLocation().clone().add(0.5, 1, 0.5), 12, 0, 0, 0, 1.1); - } - - if (isLectern) { - ItemStack it = p.getInventory().getItemInMainHand(); - if (it.getItemMeta() != null && !it.getItemMeta().getPersistentDataContainer().getKeys().isEmpty()) { - e.setCancelled(true); - playDebug(p); - it.getItemMeta().getPersistentDataContainer().getKeys().forEach(k -> Bukkit.getServer().getConsoleSender().sendMessage(k + " = " + it.getItemMeta().getPersistentDataContainer().getOrDefault(k, PersistentDataType.STRING, "Not a String"))); - } - } - - if (isObserver) { - ItemStack it = p.getInventory().getItemInMainHand(); - if (it.getType().equals(Material.EXPERIENCE_BOTTLE)) { - e.setCancelled(true); - Bukkit.getServer().getConsoleSender().sendMessage(" "); - p.setCooldown(Material.ENCHANTED_BOOK, 3); - AdaptPlayer a = getPlayer(p); - playDebug(p); - - String xv = a.getData().getMultiplier() - 1d > 0 ? "+" + Form.pc(a.getData().getMultiplier() - 1D) : Form.pc(a.getData().getMultiplier() - 1D); - Bukkit.getServer().getConsoleSender().sendMessage("Global" + C.GRAY + ": " + C.GREEN + xv); - - for (XPMultiplier i : a.getData().getMultipliers()) { - String vv = i.getMultiplier() > 0 ? "+" + Form.pc(i.getMultiplier()) : Form.pc(i.getMultiplier()); - Bukkit.getServer().getConsoleSender().sendMessage(C.GREEN + "* " + vv + C.GRAY + " for " + Form.duration(i.getGoodFor() - M.ms(), 0)); - } - for (XPMultiplier i : Adapt.instance.getAdaptServer().getData().getMultipliers()) { - String vv = i.getMultiplier() > 0 ? "+" + Form.pc(i.getMultiplier()) : Form.pc(i.getMultiplier()); - Bukkit.getServer().getConsoleSender().sendMessage(C.GREEN + "* " + vv + C.GRAY + " for " + Form.duration(i.getGoodFor() - M.ms(), 0)); - } - - for (PlayerSkillLine i : a.getData().getSkillLines().v()) { - Skill s = i.getRawSkill(a); - if (s == null) { - continue; - } - String v = i.getMultiplier() - a.getData().getMultiplier() > 0 ? "+" + Form.pc(i.getMultiplier() - a.getData().getMultiplier()) : Form.pc(i.getMultiplier() - a.getData().getMultiplier()); - Bukkit.getServer().getConsoleSender().sendMessage(" " + s.getDisplayName() + C.GRAY + ": " + s.getColor() + v); - for (XPMultiplier j : i.getMultipliers()) { - String vv = j.getMultiplier() > 0 ? "+" + Form.pc(j.getMultiplier()) : Form.pc(j.getMultiplier()); - Bukkit.getServer().getConsoleSender().sendMessage(" " + s.getShortName() + C.GRAY + " " + vv + " for " + Form.duration(j.getGoodFor() - M.ms(), 0)); - } - } - } - } - } - - private void playDebug(Player p) { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.BLOCK_BELL_RESONATE, 1f, 0.6f); - sp.play(p.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 1f, 0.1f); - sp.play(p.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1f, 1.6f); - sp.play(p.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1f, 1.2f); - - } - - public Skill getSkill(String i) { - if (i == null) { - return null; - } - - Skill direct = skills.get(i); - if (direct != null) { - return direct; - } - - return skills.get(normalizeSkillName(i)); - } - - public Skill getAnySkill(String i) { - if (i == null) { - return null; - } - - Skill direct = knownSkills.get(i); - if (direct != null) { - return direct; - } - - return knownSkills.get(normalizeSkillName(i)); - } - - public List> getSkills() { - return skills.v(); - } - - public List> getAllSkills() { - return new ArrayList<>(knownSkills.v()); - } - - public synchronized void registerSkill(Class> skillType) { - long started = System.currentTimeMillis(); - long instantiateStarted = started; - Skill skill = instantiateSkill(skillType); - long instantiateMs = System.currentTimeMillis() - instantiateStarted; - if (skill == null) { - return; - } - - String skillName = normalizeSkillName(skill.getName()); - skillTypes.put(skillName, skillType); - Skill previous = knownSkills.put(skillName, skill); - if (previous != null && previous != skill) { - unregisterRecipes(previous); - previous.unregister(); - } - - if (!skill.isEnabled()) { - skill.unregister(); - skills.remove(skillName); - return; - } - - skills.put(skillName, skill); - if (bootstrapLoading) { - deferredBootstrapRecipeRegistration.addLast(skill); - } else { - registerRecipes(skill); - } - - long totalMs = System.currentTimeMillis() - started; - if (totalMs >= SLOW_SKILL_REG_MS || instantiateMs >= SLOW_SKILL_REG_MS) { - Adapt.warn("Skill registration slow-path [" + skillName + "] total=" + totalMs + "ms instantiate=" + instantiateMs + "ms bootstrap=" + bootstrapLoading + "."); - } - } - - public synchronized boolean hotReloadSkillConfig(String skillName) { - String normalized = normalizeSkillName(skillName); - Skill loaded = knownSkills.get(normalized); - if (loaded instanceof SimpleSkill simpleSkill) { - boolean wasEnabled = loaded.isEnabled(); - boolean ok = simpleSkill.reloadConfigFromDisk(false); - if (!ok) { - return false; - } - - if (!loaded.isEnabled()) { - unregisterRecipes(loaded); - if (wasEnabled) { - loaded.unregister(); - } - skills.remove(normalized); - return true; - } - - if (!wasEnabled) { - return replaceSkillInstance(normalized, inferSkillType(normalized, loaded), loaded); - } - - skills.put(normalized, loaded); - unregisterRecipes(loaded); - registerRecipes(loaded); - return true; - } - - Class> skillType = inferSkillType(normalized, loaded); - if (skillType == null) { - Adapt.verbose("No known skill type for config hotload: " + skillName); - return false; - } - - return replaceSkillInstance(normalized, skillType, loaded); - } - - @SuppressWarnings("unchecked") - private Class> inferSkillType(String normalizedSkillName, Skill loaded) { - Class> skillType = skillTypes.get(normalizedSkillName); - if (skillType != null) { - return skillType; - } - - if (loaded != null && Skill.class.isAssignableFrom(loaded.getClass())) { - return (Class>) loaded.getClass(); - } - - return null; - } - - private boolean replaceSkillInstance(String normalizedName, Class> skillType, Skill previousLoaded) { - Skill replacement = instantiateSkill(skillType); - if (replacement == null) { - return false; - } - - Skill previousKnown = knownSkills.put(normalizedName, replacement); - if (previousKnown != null && previousKnown != replacement) { - unregisterRecipes(previousKnown); - previousKnown.unregister(); - } else if (previousLoaded != null && previousLoaded != replacement) { - unregisterRecipes(previousLoaded); - previousLoaded.unregister(); - } - - if (!replacement.isEnabled()) { - replacement.unregister(); - skills.remove(normalizedName); - return true; - } - - Skill previous = skills.put(normalizedName, replacement); - if (previous != null && previous != replacement) { - unregisterRecipes(previous); - previous.unregister(); - } - - registerRecipes(replacement); - return true; - } - - public synchronized void refreshRecipes(Skill skill) { - if (skill == null) { - return; - } - - unregisterRecipes(skill); - if (skill.isEnabled()) { - registerRecipes(skill); - } - } - - public boolean isKnownSkill(String skillName) { - if (skillName == null) { - return false; - } - - return skillTypes.containsKey(normalizeSkillName(skillName)); - } - - public Adaptation getRequiredAdaptation(Recipe recipe) { - if (!(recipe instanceof Keyed keyed)) { - return null; - } - - return adaptationRecipeIndex.get(keyed.getKey()); - } - - private Skill instantiateSkill(Class> skillType) { - try { - return skillType.getConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - e.printStackTrace(); - return null; - } - } - - private void unregisterRecipes(Skill s) { - s.getRecipes().forEach(AdaptRecipe::unregister); - s.getAdaptations().forEach(adaptation -> { - adaptation.getRecipes().forEach(recipe -> { - removeAdaptationRecipeIndex(recipe, adaptation); - recipe.unregister(); - }); - }); - } - - private void registerRecipes(Skill s) { - if (!s.isEnabled()) { - return; - } - s.getRecipes().forEach(AdaptRecipe::register); - s.getAdaptations().forEach(adaptation -> { - if (!adaptation.isEnabled()) { - return; - } - adaptation.getRecipes().forEach(recipe -> { - recipe.register(); - indexAdaptationRecipe(recipe, adaptation); - }); - adaptation.getBrewingRecipes().forEach(r -> BrewingManager.registerRecipe(adaptation.getName(), r)); - }); - } - - @Override - public void unregister() { - BukkitTask pendingTask = deferredBootstrapRecipeTask; - if (pendingTask != null) { - pendingTask.cancel(); - deferredBootstrapRecipeTask = null; - } - deferredBootstrapRecipeRegistration.clear(); - for (Skill i : knownSkills.v()) { - i.unregister(); - unregisterRecipes(i); - } - skills.clear(); - knownSkills.clear(); - skillTypes.clear(); - adaptationRecipeIndex.clear(); - } - - @Override - public void onTick() { - - } - - private String normalizeSkillName(String raw) { - String normalized = raw.trim().toLowerCase(Locale.ROOT); - if (normalized.startsWith("[skill]-")) { - normalized = normalized.substring("[skill]-".length()); - } - - if (normalized.equals("chrono")) { - normalized = "chronos"; - } - - return normalized; - } - - private void indexAdaptationRecipe(AdaptRecipe recipe, Adaptation adaptation) { - if (recipe == null || adaptation == null) { - return; - } - - NamespacedKey key = recipe.getNSKey(); - Adaptation previous = adaptationRecipeIndex.put(key, adaptation); - if (previous != null && previous != adaptation) { - Adapt.warn("Recipe key conflict for " + key + ": " + previous.getName() + " replaced by " + adaptation.getName()); - } - } - - private void removeAdaptationRecipeIndex(AdaptRecipe recipe, Adaptation adaptation) { - if (recipe == null) { - return; - } - - NamespacedKey key = recipe.getNSKey(); - if (adaptation == null) { - adaptationRecipeIndex.remove(key); - return; - } - - adaptationRecipeIndex.computeIfPresent(key, (k, current) -> current == adaptation ? null : current); - } - - private synchronized void scheduleDeferredBootstrapRecipeRegistration() { - if (deferredBootstrapRecipeRegistration.isEmpty() || deferredBootstrapRecipeTask != null) { - return; - } - - Adapt.info("Deferring recipe registration for " + deferredBootstrapRecipeRegistration.size() + " skills."); - deferredBootstrapRecipeTask = Bukkit.getScheduler().runTaskTimer(Adapt.instance, () -> { - int processed = 0; - long started = System.currentTimeMillis(); - while (processed < DEFERRED_SKILLS_PER_TICK) { - Skill skill = deferredBootstrapRecipeRegistration.pollFirst(); - if (skill == null) { - break; - } - registerRecipes(skill); - processed++; - if (System.currentTimeMillis() - started > 8L) { - break; - } - } - - if (deferredBootstrapRecipeRegistration.isEmpty()) { - BukkitTask task = deferredBootstrapRecipeTask; - deferredBootstrapRecipeTask = null; - if (task != null) { - task.cancel(); - } - Adapt.info("Deferred recipe registration completed."); - } - }, 1L, 1L); - } -} diff --git a/src/main/java/com/volmit/adapt/api/tick/Ticked.java b/src/main/java/com/volmit/adapt/api/tick/Ticked.java deleted file mode 100644 index 6bf539a16..000000000 --- a/src/main/java/com/volmit/adapt/api/tick/Ticked.java +++ /dev/null @@ -1,66 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.tick; - -import com.volmit.adapt.api.world.AdaptComponent; -import com.volmit.adapt.util.M; - -public interface Ticked extends AdaptComponent { - default void retick() { - burst(1); - } - - default void skip() { - skip(1); - } - - void unregister(); - - boolean isBursting(); - - boolean isSkipping(); - - void stopBursting(); - - void stopSkipping(); - - long getTickCount(); - - long getAge(); - - void burst(int ticks); - - void skip(int ticks); - - long getLastTick(); - - long getInterval(); - - void setInterval(long ms); - - void tick(); - - String getGroup(); - - String getId(); - - default boolean shouldTick() { - return M.ms() - getLastTick() > getInterval(); - } -} diff --git a/src/main/java/com/volmit/adapt/api/tick/TickedObject.java b/src/main/java/com/volmit/adapt/api/tick/TickedObject.java deleted file mode 100644 index e8102a848..000000000 --- a/src/main/java/com/volmit/adapt/api/tick/TickedObject.java +++ /dev/null @@ -1,255 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.tick; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.M; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -import java.lang.reflect.Method; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -public abstract class TickedObject implements Ticked, Listener { - private static final Set LISTENER_INTROSPECTION_WARNED = ConcurrentHashMap.newKeySet(); - - private final AtomicLong lastTick; - private final AtomicLong interval; - private final AtomicInteger skip; - private final AtomicInteger burst; - private final AtomicLong ticks; - private final AtomicInteger dieIn; - private final AtomicBoolean die; - private final AtomicBoolean pendingSyncTick; - private final long start; - private final String group; - private final String id; - private final boolean listenerRegistered; - - public TickedObject() { - this("null"); - } - - public TickedObject(String group, String id) { - this(group, id, 1000); - } - - public TickedObject(String group) { - this(group, UUID.randomUUID().toString(), 1000); - } - - public TickedObject(String group, long interval) { - this(group, UUID.randomUUID().toString(), interval); - } - - public TickedObject(String group, String id, long interval) { - this.group = group; - this.id = id; - this.die = new AtomicBoolean(false); - this.dieIn = new AtomicInteger(0); - this.interval = new AtomicLong(interval); - this.lastTick = new AtomicLong(M.ms()); - this.burst = new AtomicInteger(0); - this.skip = new AtomicInteger(0); - this.ticks = new AtomicLong(0); - this.pendingSyncTick = new AtomicBoolean(false); - this.start = M.ms(); - this.listenerRegistered = shouldRegisterAsListener(); - Adapt.instance.getTicker().register(this); - if (listenerRegistered) { - Adapt.instance.registerListener(this); - } - } - - public void dieAfter(int ticks) { - dieIn.set(ticks); - die.set(true); - } - - @Override - public void unregister() { - Adapt.instance.getTicker().unregister(this); - if (listenerRegistered) { - Adapt.instance.unregisterListener(this); - } - } - - @Override - public long getLastTick() { - return lastTick.get(); - } - - @Override - public long getInterval() { - if (burst.get() > 0) { - return 0; - } - - return interval.get(); - } - - @Override - public void setInterval(long ms) { - interval.set(ms); - } - - @Override - public void tick() { - if (!Bukkit.isPrimaryThread()) { - if (pendingSyncTick.compareAndSet(false, true)) { - J.s(() -> { - try { - tick(); - } finally { - pendingSyncTick.set(false); - } - }); - } - return; - } - - if (skip.getAndDecrement() > 0) { - return; - } - - if (die.get() && dieIn.decrementAndGet() <= 0) { - unregister(); - return; - } - - lastTick.set(M.ms()); - burst.decrementAndGet(); - onTick(); - } - - public abstract void onTick(); - - protected boolean shouldRegisterAsListener() { - try { - return hasEventHandlerMethods(getClass()); - } catch (Throwable e) { - warnListenerIntrospectionFailure(getClass(), e); - return false; - } - } - - @Override - public String getGroup() { - return group; - } - - @Override - public String getId() { - return id; - } - - @Override - public long getTickCount() { - return ticks.get(); - } - - @Override - public long getAge() { - return M.ms() - start; - } - - @Override - public boolean isBursting() { - return burst.get() > 0; - } - - @Override - public void burst(int ticks) { - if (burst.get() < 0) { - burst.set(ticks); - return; - } - - burst.addAndGet(ticks); - } - - @Override - public boolean isSkipping() { - return skip.get() > 0; - } - - @Override - public void stopBursting() { - burst.set(0); - } - - @Override - public void stopSkipping() { - skip.set(0); - } - - @Override - public void skip(int ticks) { - if (skip.get() < 0) { - skip.set(ticks); - return; - } - - skip.addAndGet(ticks); - } - - private static boolean hasEventHandlerMethods(Class type) { - Class current = type; - while (current != null && current != Object.class) { - Method[] methods; - try { - methods = current.getDeclaredMethods(); - } catch (Throwable e) { - warnListenerIntrospectionFailure(current, e); - return false; - } - - for (Method method : methods) { - try { - if (method.isAnnotationPresent(EventHandler.class)) { - return true; - } - } catch (Throwable e) { - warnListenerIntrospectionFailure(current, e); - return false; - } - } - current = current.getSuperclass(); - } - return false; - } - - private static void warnListenerIntrospectionFailure(Class type, Throwable error) { - if (type == null) { - return; - } - - String key = type.getName() + ":" + error.getClass().getName() + ":" + (error.getMessage() == null ? "" : error.getMessage()); - if (LISTENER_INTROSPECTION_WARNED.add(key)) { - Adapt.warn("Skipping listener registration for " + type.getName() + " due to missing/incompatible event class: " + error.getClass().getSimpleName() + (error.getMessage() == null ? "" : " (" + error.getMessage() + ")")); - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/tick/Ticker.java b/src/main/java/com/volmit/adapt/api/tick/Ticker.java deleted file mode 100644 index 31e91972f..000000000 --- a/src/main/java/com/volmit/adapt/api/tick/Ticker.java +++ /dev/null @@ -1,164 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.tick; - -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.collection.KList; - -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; - -public class Ticker { - private final KList ticklist; - private final KList newTicks; - private final KList removeTicks; - private final Map metrics; - private final AtomicLong windowStartMs; - private volatile boolean ticking; - - public Ticker() { - this.ticklist = new KList<>(4096); - this.newTicks = new KList<>(128); - this.removeTicks = new KList<>(128); - this.metrics = new ConcurrentHashMap<>(); - this.windowStartMs = new AtomicLong(System.currentTimeMillis()); - ticking = false; - J.sr(() -> { - if (!ticking) { - tick(); - } - }, 1); - } - - public void register(Ticked ticked) { - synchronized (newTicks) { - newTicks.add(ticked); - } - } - - public void unregister(Ticked ticked) { - synchronized (removeTicks) { - removeTicks.add(ticked.getId()); - } - } - - public void clear() { - synchronized (ticklist) { - ticklist.clear(); - } - synchronized (removeTicks) { - removeTicks.clear(); - } - synchronized (newTicks) { - newTicks.clear(); - } - metrics.clear(); - windowStartMs.set(System.currentTimeMillis()); - - } - - public void resetMetrics() { - metrics.clear(); - windowStartMs.set(System.currentTimeMillis()); - } - - public long getMetricsWindowMs() { - return Math.max(0, System.currentTimeMillis() - windowStartMs.get()); - } - - public List topMetrics(int limit) { - int safeLimit = Math.max(1, limit); - return metrics.entrySet().stream() - .sorted(Comparator.comparingLong((Map.Entry e) -> e.getValue().totalNanos.get()).reversed()) - .limit(safeLimit) - .map(entry -> formatMetric(entry.getKey(), entry.getValue())) - .toList(); - } - - private void tick() { - ticking = true; - for (int i = 0; i < ticklist.size(); i++) { - Ticked t = ticklist.get(i); - if (t != null && t.shouldTick()) { - long start = System.nanoTime(); - try { - t.tick(); - } catch (Throwable exxx) { - exxx.printStackTrace(); - } finally { - recordMetric(t, System.nanoTime() - start); - } - } - } - - synchronized (newTicks) { - while (newTicks.isNotEmpty()) { - ticklist.add(newTicks.popRandom()); - } - } - - synchronized (removeTicks) { - while (removeTicks.isNotEmpty()) { - String id = removeTicks.popRandom(); - - for (int i = 0; i < ticklist.size(); i++) { - if (ticklist.get(i).getId().equals(id)) { - ticklist.remove(i); - break; - } - } - } - } - - ticking = false; - } - - private void recordMetric(Ticked ticked, long durationNs) { - if (ticked == null || durationNs < 0) { - return; - } - - String key = ticked.getGroup() + ":" + ticked.getId(); - TickMetric metric = metrics.computeIfAbsent(key, unused -> new TickMetric()); - metric.calls.incrementAndGet(); - metric.totalNanos.addAndGet(durationNs); - metric.maxNanos.updateAndGet(old -> Math.max(old, durationNs)); - } - - private String formatMetric(String key, TickMetric metric) { - long calls = Math.max(1, metric.calls.get()); - double totalMs = metric.totalNanos.get() / 1_000_000D; - double avgMs = totalMs / (double) calls; - double maxMs = metric.maxNanos.get() / 1_000_000D; - return key + " total=" + String.format(Locale.US, "%.3fms", totalMs) - + " avg=" + String.format(Locale.US, "%.3fms", avgMs) - + " max=" + String.format(Locale.US, "%.3fms", maxMs) - + " calls=" + calls; - } - - private static class TickMetric { - private final AtomicLong calls = new AtomicLong(); - private final AtomicLong totalNanos = new AtomicLong(); - private final AtomicLong maxNanos = new AtomicLong(); - } -} diff --git a/src/main/java/com/volmit/adapt/api/value/MaterialValue.java b/src/main/java/com/volmit/adapt/api/value/MaterialValue.java deleted file mode 100644 index 7fddbfa35..000000000 --- a/src/main/java/com/volmit/adapt/api/value/MaterialValue.java +++ /dev/null @@ -1,253 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.value; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.util.*; -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.inventory.*; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -@Getter -public class MaterialValue { - private static final Map valueMultipliers = new HashMap<>(); - private static MaterialValue valueCache = null; - - static { - AdaptConfig.get().getValue().getValueMutlipliers().forEach((k, v) -> { - try { - Material m = Material.valueOf(k.toUpperCase()); - valueMultipliers.put(m, v); - } catch (Exception e) { - Adapt.verbose("Invalid material value multiplier: " + k); - } - }); - } - - private final Map value = new HashMap<>(); - - public static void save() { - if (valueCache == null) { - return; - } - - File l = Adapt.instance.getDataFile("data", "value-cache.json"); - try { - IO.writeAll(l, Json.toJson(valueCache, true)); - } catch (IOException e) { - Adapt.verbose("Failed to save value cache"); - } - } - - public static MaterialValue get() { - if (valueCache == null) { - MaterialValue dummy = new MaterialValue(); - File l = Adapt.instance.getDataFile("data", "value-cache.json"); - - if (!l.exists()) { - try { - IO.writeAll(l, Json.toJson(dummy, true)); - } catch (IOException e) { - e.printStackTrace(); - valueCache = dummy; - return dummy; - } - } - - try { - valueCache = Json.fromJson(IO.readAll(l), MaterialValue.class); - } catch (IOException e) { - e.printStackTrace(); - valueCache = new MaterialValue(); - } - } - - return valueCache; - } - - public static void debugValue(Material m) { - debugValue(m, 0, 1, new HashSet<>()); - } - - private static void debugValue(Material m, int ind, int x, Set ignore) { - PrecisionStopwatch p = PrecisionStopwatch.start(); - Adapt.verbose(Form.repeat(" ", ind) + m.name() + ": " + getValue(m) + (x == 1 ? "" : " (x" + x + ")")); - - int r = 0; - for (MaterialRecipe i : getRecipes(m)) { - if (ignore.contains(i)) { - continue; - } - - ignore.add(i); - if (ignore.size() > AdaptConfig.get().getMaxRecipeListPrecaution()) { - Adapt.verbose("Avoiding infinite loop"); - return; - } - - int o = i.getOutput().getAmount(); - Adapt.verbose(Form.repeat(" ", ind) + "# Recipe [" + ind + "x" + r + (o == 1 ? "]" : "] (x" + o + ") ")); - - for (MaterialCount j : i.getInput()) { - debugValue(j.getMaterial(), ind + 1, j.getAmount(), ignore); - } - - r++; - } - Adapt.verbose(Form.repeat(" ", ind) + " took " + Form.duration(p.getMilliseconds(), 0)); - } - - private static double getMultiplier(Material m) { - Double d = AdaptConfig.get().getValue().getValueMutlipliers().get(m); - return d == null ? 1 : d; - } - - public static double getValue(Material m) { - try { - return getValue(m, new HashSet<>()); - } catch (Exception ignored) { - return 1; - } - } - - private static double getValue(Material m, Set ignore) { - if (get().value.containsKey(m)) { - if (m.isBlock() && m.getHardness() == 0) { - return 0; - } - return get().value.get(m); - } - double v = AdaptConfig.get().getValue().getBaseValue(); - List recipes = getRecipes(m); - if (recipes.isEmpty()) { - get().value.put(m, v * getMultiplier(m)); // No recipes, just use base value, if no base value then 1 - } else { - List d = new ArrayList<>(); - for (MaterialRecipe i : recipes) { - if (ignore.contains(i)) { - continue; - } - ignore.add(i); - double vx = v; - for (MaterialCount j : i.getInput()) { - vx += getValue(j.getMaterial(), ignore); - } - d.add(vx / i.getOutput().getAmount()); - } - if (d.size() > 0) { - v += d.stream().mapToDouble(i -> i).average().getAsDouble(); - } - if (v > AdaptConfig.get().getMaxRecipeListPrecaution()) { - get().value.put(m, (v / 10 + 1) * getMultiplier(m)); - } else { - get().value.put(m, v); - } - - } - if (m.isBlock() && m.getHardness() == 0) { - return 0; - } - return get().value.get(m); - } - - private static List getRecipes(Material mat) { - List r = new ArrayList<>(); - try { - ItemStack is = new ItemStack(mat); - try { - is.setDurability((short) -1); - } catch (Throwable e) { - Adapt.verbose("Failed to set durability of " + mat.name()); - } - Bukkit.getRecipesFor(is).forEach(i -> { - if (i instanceof AdaptRecipe) { - Adapt.verbose("Skipping Adapt Recipe to prevent duplicates, " + mat.name() + " -> " + ((AdaptRecipe) i).getKey() + ""); - return; - } - MaterialRecipe rx = toMaterial(i); - if (rx != null) { - r.add(rx); - } - }); - } catch (Throwable e) { - Adapt.verbose("Failed to get recipes for " + mat.name()); - } - return r; - } - - private static MaterialRecipe toMaterial(Recipe r) { - try { - if (r instanceof ShapelessRecipe recipe) { - return MaterialRecipe.builder() - .input(new ArrayList<>(recipe.getIngredientList().stream().map(i -> new MaterialCount(i.getType(), 1)).toList())) - .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) - .build(); - } else if (r instanceof ShapedRecipe recipe) { - MaterialRecipe re = MaterialRecipe.builder() - .input(new ArrayList<>()) - .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) - .build(); - Map f = new HashMap<>(); - for (ItemStack i : recipe.getIngredientMap().values()) { - if (i == null || i.getType().isAir()) { - continue; - } - - f.compute(i.getType(), (k, v) -> v == null ? 1 : v + 1); - } - - f.forEach((k, v) -> re.getInput().add(new MaterialCount(k, v))); - - return re; - } else if (r instanceof CookingRecipe recipe) { - List a = new ArrayList<>(); - a.add(new MaterialCount(recipe.getInput().getType(), 1)); - - return MaterialRecipe.builder() - .input(a) - .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) - .build(); - } else if (r instanceof MerchantRecipe recipe) { - return MaterialRecipe.builder() - .input(new ArrayList<>(recipe.getIngredients().stream().map(i -> new MaterialCount(i.getType(), 1)).toList())) - .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) - .build(); - } else if (r instanceof StonecuttingRecipe recipe) { - List a = new ArrayList<>(); - a.add(new MaterialCount(recipe.getInput().getType(), 1)); - - return MaterialRecipe.builder() - .input(a) - .output(new MaterialCount(recipe.getResult().getType(), recipe.getResult().getAmount())) - .build(); - } - } catch (Throwable e) { - e.printStackTrace(); - } - - return null; - } -} diff --git a/src/main/java/com/volmit/adapt/api/version/IAttribute.java b/src/main/java/com/volmit/adapt/api/version/IAttribute.java deleted file mode 100644 index 669f42d51..000000000 --- a/src/main/java/com/volmit/adapt/api/version/IAttribute.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.volmit.adapt.api.version; - -import com.volmit.adapt.util.collection.KList; -import lombok.*; -import org.bukkit.NamespacedKey; -import org.bukkit.attribute.AttributeModifier; - -import java.util.Optional; -import java.util.UUID; - -public interface IAttribute { - - double getValue(); - - double getDefaultValue(); - - double getBaseValue(); - - void setBaseValue(double baseValue); - - default void setModifier(UUID uuid, NamespacedKey key, double amount, AttributeModifier.Operation operation) { - removeModifier(uuid, key); - addModifier(uuid, key, amount, operation); - } - - void addModifier(UUID uuid, NamespacedKey key, double amount, AttributeModifier.Operation operation); - - boolean hasModifier(UUID uuid, NamespacedKey key); - - void removeModifier(UUID uuid, NamespacedKey key); - - KList getModifier(UUID uuid, NamespacedKey key); - - @ToString - @EqualsAndHashCode - @AllArgsConstructor - class Modifier { - private final UUID uuid; - private final NamespacedKey key; - @Getter - private final double amount; - @Getter - private final AttributeModifier.Operation operation; - - public Optional getUUID() { - return Optional.ofNullable(uuid); - } - - public Optional getKey() { - return Optional.ofNullable(key); - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/version/IBindings.java b/src/main/java/com/volmit/adapt/api/version/IBindings.java deleted file mode 100644 index 6fbb11e27..000000000 --- a/src/main/java/com/volmit/adapt/api/version/IBindings.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.volmit.adapt.api.version; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.potion.PotionBuilder.Type; -import com.volmit.adapt.util.CustomModel; -import org.bukkit.attribute.Attributable; -import org.bukkit.attribute.Attribute; -import org.bukkit.entity.EntityType; -import org.bukkit.event.Listener; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.PotionMeta; -import org.jetbrains.annotations.Unmodifiable; - -import java.util.List; - -public interface IBindings extends Listener { - - default void applyModel(CustomModel model, ItemMeta meta) { - meta.setCustomModelData(model.model()); - } - - IAttribute getAttribute(Attributable attributable, Attribute modifier); - - default ItemStack buildPotion(PotionBuilder builder) { - ItemStack stack = builder.getBaseItem(); - if (stack == null) stack = new ItemStack(builder.getType().getMaterial()); - else if (stack.getType() != builder.getType().getMaterial()) stack.setType(builder.getType().getMaterial()); - PotionMeta meta = (PotionMeta) stack.getItemMeta(); - assert meta != null; - meta.clearCustomEffects(); - builder.getEffects().forEach(e -> meta.addCustomEffect(e, true)); - if (builder.getColor() != null) - meta.setColor(builder.getColor()); - stack.setItemMeta(meta); - - Adapt.platform.editItem(stack) - .lore(builder.getLore()) - .customName(builder.getName()) - .build(); - return stack; - } - - default PotionBuilder editPotion(ItemStack stack) { - Type type = null; - for (final var val : Type.values()) { - if (val.getMaterial() == stack.getType()) { - type = val; - break; - } - } - - if (type == null) { - throw new IllegalArgumentException("Invalid potion type!"); - } - final var editor = Adapt.platform.editItem(stack); - final var builder = PotionBuilder.of(type) - .setBaseItem(stack); - - PotionMeta meta = (PotionMeta) stack.getItemMeta(); - assert meta != null; - builder.setBaseType(meta.getBasePotionType()) - .setLore(editor.lore()) - .setColor(meta.getColor()) - .setName(editor.customName()); - for (var effect : meta.getCustomEffects()) { - builder.addEffect(effect); - } - - return builder; - } - - @Unmodifiable - List getInvalidDamageableEntities(); -} diff --git a/src/main/java/com/volmit/adapt/api/version/RuntimeAttribute.java b/src/main/java/com/volmit/adapt/api/version/RuntimeAttribute.java deleted file mode 100644 index fd340633c..000000000 --- a/src/main/java/com/volmit/adapt/api/version/RuntimeAttribute.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.volmit.adapt.api.version; - -import com.volmit.adapt.util.collection.KList; -import org.bukkit.NamespacedKey; -import org.bukkit.attribute.AttributeInstance; -import org.bukkit.attribute.AttributeModifier; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.UUID; -import java.util.stream.Collectors; - -public record RuntimeAttribute(AttributeInstance instance) implements IAttribute { - private static final Method GET_KEY_METHOD = findMethod("getKey"); - private static final Method GET_UUID_METHOD = findMethod("getUniqueId"); - private static final Method GET_NAME_METHOD = findMethod("getName"); - - @Override - public double getValue() { - return instance.getValue(); - } - - @Override - public double getDefaultValue() { - return instance.getDefaultValue(); - } - - @Override - public double getBaseValue() { - return instance.getBaseValue(); - } - - @Override - public void setBaseValue(double baseValue) { - instance.setBaseValue(baseValue); - } - - @Override - public void addModifier(UUID uuid, NamespacedKey key, double amount, AttributeModifier.Operation operation) { - instance.addModifier(createModifier(uuid, key, amount, operation)); - } - - @Override - public boolean hasModifier(UUID uuid, NamespacedKey key) { - return instance.getModifiers() - .stream() - .anyMatch(modifier -> matches(modifier, uuid, key)); - } - - @Override - public void removeModifier(UUID uuid, NamespacedKey key) { - instance.getModifiers() - .stream() - .filter(modifier -> matches(modifier, uuid, key)) - .toList() - .forEach(instance::removeModifier); - } - - @Override - public KList getModifier(UUID uuid, NamespacedKey key) { - return instance.getModifiers() - .stream() - .filter(modifier -> matches(modifier, uuid, key)) - .map(RuntimeAttribute::wrap) - .collect(Collectors.toCollection(KList::new)); - } - - private static AttributeModifier createModifier(UUID uuid, NamespacedKey key, double amount, AttributeModifier.Operation operation) { - for (Constructor constructor : AttributeModifier.class.getConstructors()) { - AttributeModifier modifier = tryCreateKeyed(constructor, key, amount, operation); - if (modifier != null) { - return modifier; - } - } - - String legacyName = key.getNamespace() + "-" + key.getKey(); - for (Constructor constructor : AttributeModifier.class.getConstructors()) { - AttributeModifier modifier = tryCreateLegacy(constructor, uuid, legacyName, amount, operation); - if (modifier != null) { - return modifier; - } - } - - throw new IllegalStateException("No compatible AttributeModifier constructor found"); - } - - private static AttributeModifier tryCreateKeyed(Constructor constructor, NamespacedKey key, double amount, AttributeModifier.Operation operation) { - Class[] params = constructor.getParameterTypes(); - if (params.length < 3 || params.length > 4 || params[0] != NamespacedKey.class || params[1] != double.class || params[2] != AttributeModifier.Operation.class) { - return null; - } - - Object[] args = new Object[params.length]; - args[0] = key; - args[1] = amount; - args[2] = operation; - if (params.length == 4) { - Object slot = resolveEnum(params[3]); - if (slot == null) { - return null; - } - args[3] = slot; - } - - try { - return (AttributeModifier) constructor.newInstance(args); - } catch (ReflectiveOperationException ignored) { - return null; - } - } - - private static AttributeModifier tryCreateLegacy(Constructor constructor, UUID uuid, String name, double amount, AttributeModifier.Operation operation) { - Class[] params = constructor.getParameterTypes(); - if (params.length < 4 || params.length > 5 || params[0] != UUID.class || params[1] != String.class || params[2] != double.class || params[3] != AttributeModifier.Operation.class) { - return null; - } - - Object[] args = new Object[params.length]; - args[0] = uuid; - args[1] = name; - args[2] = amount; - args[3] = operation; - if (params.length == 5) { - Object slot = resolveEnum(params[4]); - if (slot == null) { - return null; - } - args[4] = slot; - } - - try { - return (AttributeModifier) constructor.newInstance(args); - } catch (ReflectiveOperationException ignored) { - return null; - } - } - - private static boolean matches(AttributeModifier modifier, UUID uuid, NamespacedKey key) { - NamespacedKey modifierKey = readKey(modifier); - if (modifierKey != null && modifierKey.equals(key)) { - return true; - } - - UUID modifierUuid = readUuid(modifier); - if (modifierUuid != null && modifierUuid.equals(uuid)) { - return true; - } - - String modifierName = readName(modifier); - return modifierName != null && modifierName.equals(key.getNamespace() + "-" + key.getKey()); - } - - private static Modifier wrap(AttributeModifier modifier) { - return new Modifier(readUuid(modifier), readKey(modifier), modifier.getAmount(), modifier.getOperation()); - } - - private static NamespacedKey readKey(AttributeModifier modifier) { - if (GET_KEY_METHOD == null) { - return null; - } - - try { - return (NamespacedKey) GET_KEY_METHOD.invoke(modifier); - } catch (ReflectiveOperationException ignored) { - return null; - } - } - - private static UUID readUuid(AttributeModifier modifier) { - if (GET_UUID_METHOD == null) { - return null; - } - - try { - return (UUID) GET_UUID_METHOD.invoke(modifier); - } catch (ReflectiveOperationException ignored) { - return null; - } - } - - private static String readName(AttributeModifier modifier) { - if (GET_NAME_METHOD == null) { - return null; - } - - try { - return (String) GET_NAME_METHOD.invoke(modifier); - } catch (ReflectiveOperationException ignored) { - return null; - } - } - - private static Method findMethod(String methodName) { - try { - return AttributeModifier.class.getMethod(methodName); - } catch (NoSuchMethodException ignored) { - return null; - } - } - - private static Object resolveEnum(Class type) { - if (!type.isEnum()) { - return null; - } - - Object any = enumConstant(type, "ANY"); - if (any != null) { - return any; - } - - Object hand = enumConstant(type, "HAND"); - if (hand != null) { - return hand; - } - - Object[] constants = type.getEnumConstants(); - return constants == null || constants.length == 0 ? null : constants[0]; - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - private static Object enumConstant(Class type, String name) { - try { - return Enum.valueOf((Class) type, name); - } catch (IllegalArgumentException ignored) { - return null; - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/version/RuntimeBindings.java b/src/main/java/com/volmit/adapt/api/version/RuntimeBindings.java deleted file mode 100644 index 7f1ea6cd4..000000000 --- a/src/main/java/com/volmit/adapt/api/version/RuntimeBindings.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.volmit.adapt.api.version; - -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.util.CustomModel; -import org.bukkit.NamespacedKey; -import org.bukkit.attribute.Attributable; -import org.bukkit.attribute.Attribute; -import org.bukkit.entity.EntityType; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.potion.PotionType; -import org.jetbrains.annotations.Unmodifiable; - -import java.lang.reflect.Method; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -public class RuntimeBindings implements IBindings { - private static final Method SET_ITEM_MODEL_METHOD = findMethod(ItemMeta.class, "setItemModel", NamespacedKey.class); - private static final Method SET_BASE_POTION_TYPE_METHOD = findMethod(PotionMeta.class, "setBasePotionType", PotionType.class); - private static final List INVALID_DAMAGEABLE_ENTITIES = detectInvalidDamageableEntities(); - - @Override - public void applyModel(CustomModel model, ItemMeta meta) { - NamespacedKey modelKey = model.modelKey(); - if (modelKey != null && !CustomModel.EMPTY_KEY.equals(modelKey) && SET_ITEM_MODEL_METHOD != null) { - try { - SET_ITEM_MODEL_METHOD.invoke(meta, modelKey); - return; - } catch (ReflectiveOperationException ignored) { - // Fallback is custom model data for older API variants. - } - } - - meta.setCustomModelData(model.model()); - } - - @Override - public IAttribute getAttribute(Attributable attributable, Attribute modifier) { - return Optional.ofNullable(attributable.getAttribute(modifier)) - .map(RuntimeAttribute::new) - .orElse(null); - } - - @Override - public ItemStack buildPotion(PotionBuilder builder) { - ItemStack stack = IBindings.super.buildPotion(builder); - PotionMeta meta = (PotionMeta) stack.getItemMeta(); - if (meta == null || builder.getBaseType() == null || SET_BASE_POTION_TYPE_METHOD == null) { - return stack; - } - - try { - SET_BASE_POTION_TYPE_METHOD.invoke(meta, builder.getBaseType()); - stack.setItemMeta(meta); - } catch (ReflectiveOperationException ignored) { - // Older APIs may not expose base potion type mutators. - } - - return stack; - } - - @Override - @Unmodifiable - public List getInvalidDamageableEntities() { - return INVALID_DAMAGEABLE_ENTITIES; - } - - private static List detectInvalidDamageableEntities() { - Set entities = new LinkedHashSet<>(); - - addIfPresent(entities, - "ARMOR_STAND", - "ITEM_FRAME", - "GLOW_ITEM_FRAME", - "PAINTING", - "LEASH_HITCH", - "LEASH_KNOT", - "EVOKER_FANGS", - "MARKER", - "BOAT", - "CHEST_BOAT", - "MINECART" - ); - addByPrefix(entities, "MINECART_"); - addBySuffix(entities, "_MINECART"); - addBySuffix(entities, "_BOAT"); - addBySuffix(entities, "_CHEST_BOAT"); - addBySuffix(entities, "_RAFT"); - addBySuffix(entities, "_CHEST_RAFT"); - - return List.copyOf(entities); - } - - private static void addIfPresent(Set entities, String... names) { - for (String name : names) { - try { - entities.add(EntityType.valueOf(name)); - } catch (IllegalArgumentException ignored) { - // Entity was renamed/removed in this API version. - } - } - } - - private static void addByPrefix(Set entities, String prefix) { - for (EntityType entity : EntityType.values()) { - if (entity.name().startsWith(prefix)) { - entities.add(entity); - } - } - } - - private static void addBySuffix(Set entities, String suffix) { - for (EntityType entity : EntityType.values()) { - if (entity.name().endsWith(suffix)) { - entities.add(entity); - } - } - } - - private static Method findMethod(Class type, String name, Class... parameters) { - try { - return type.getMethod(name, parameters); - } catch (NoSuchMethodException ignored) { - return null; - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/version/Version.java b/src/main/java/com/volmit/adapt/api/version/Version.java deleted file mode 100644 index e728ddff2..000000000 --- a/src/main/java/com/volmit/adapt/api/version/Version.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.volmit.adapt.api.version; - -import org.bukkit.inventory.InventoryView; - -public class Version { - private static final IBindings bindings = new RuntimeBindings(); - public static final boolean SET_TITLE; - - public static IBindings get() { - return bindings; - } - - static { - boolean titleMethod = false; - try { - InventoryView.class.getDeclaredMethod("setTitle", String.class); - titleMethod = true; - } catch (Throwable ignored) {} - SET_TITLE = titleMethod; - } -} diff --git a/src/main/java/com/volmit/adapt/api/world/AdaptComponent.java b/src/main/java/com/volmit/adapt/api/world/AdaptComponent.java deleted file mode 100644 index ecfcb8ab3..000000000 --- a/src/main/java/com/volmit/adapt/api/world/AdaptComponent.java +++ /dev/null @@ -1,258 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.world; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.reflect.registries.Materials; -import org.bukkit.Material; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -import static org.bukkit.Material.*; - -public interface AdaptComponent { - default AdaptServer getServer() { - return Adapt.instance.getAdaptServer(); - } - - default AdaptPlayer getPlayer(Player p) { - return getServer().getPlayer(p); - } - - default boolean isItem(ItemStack is) { - return is != null && !is.getType().equals(Material.AIR); - } - - default boolean isTool(ItemStack is) { - return isAxe(is) || isPickaxe(is) || isHoe(is) || isShovel(is) || isSword(is) || isTrident(is) || isMace(is); - } - - default boolean isMelee(ItemStack is) { - return isTool(is); - } - - default boolean isMace(ItemStack is) { - return is.getType() == Materials.MACE; - } - - default boolean isShield(ItemStack is) { - return is.getType().equals(Material.SHIELD); - } - - default boolean isXpBlock(Material material) { - return material.equals(Material.EXPERIENCE_BOTTLE); - } - - default boolean isRanged(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case BOW, CROSSBOW -> true; - default -> false; - }; - } - - return false; - } - - default boolean isSword(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case DIAMOND_SWORD, GOLDEN_SWORD, IRON_SWORD, NETHERITE_SWORD, STONE_SWORD, WOODEN_SWORD -> true; - default -> false; - }; - } - - return false; - } - - default boolean isTrident(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case TRIDENT, SEA_PICKLE -> true; - default -> false; - }; - } - - return false; - } - - default boolean isAxe(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case DIAMOND_AXE, GOLDEN_AXE, IRON_AXE, NETHERITE_AXE, STONE_AXE, WOODEN_AXE -> true; - default -> false; - }; - } - - return false; - } - - default boolean isPickaxe(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case DIAMOND_PICKAXE, GOLDEN_PICKAXE, IRON_PICKAXE, NETHERITE_PICKAXE, STONE_PICKAXE, WOODEN_PICKAXE -> - true; - default -> false; - }; - } - - return false; - } - - default boolean isShovel(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case DIAMOND_SHOVEL, GOLDEN_SHOVEL, IRON_SHOVEL, NETHERITE_SHOVEL, STONE_SHOVEL, WOODEN_SHOVEL -> true; - default -> false; - }; - } - return false; - } - - default boolean isLog(ItemStack it) { - if (isItem(it)) { - return List.of(MUSHROOM_STEM, BROWN_MUSHROOM_BLOCK, RED_MUSHROOM_BLOCK, MANGROVE_ROOTS, MUDDY_MANGROVE_ROOTS).contains(it.getType()) - || it.getType().name().endsWith("_LOG") - || it.getType().name().endsWith("_WOOD"); - } - - return false; - } - - default boolean isLeaves(ItemStack it) { - if (isItem(it)) { - return List.of(Material.MANGROVE_ROOTS, Material.MUDDY_MANGROVE_ROOTS).contains(it.getType()) - || it.getType().name().endsWith("_LEAVES"); - } - - return false; - } - - default boolean isBoots(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case DIAMOND_BOOTS, GOLDEN_BOOTS, IRON_BOOTS, NETHERITE_BOOTS, CHAINMAIL_BOOTS, LEATHER_BOOTS -> true; - default -> false; - }; - } - - return false; - } - - default boolean isHelmet(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case CHAINMAIL_HELMET, DIAMOND_HELMET, GOLDEN_HELMET, IRON_HELMET, LEATHER_HELMET, NETHERITE_HELMET, TURTLE_HELMET -> - true; - default -> false; - }; - } - - return false; - } - - default boolean isLeggings(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case DIAMOND_LEGGINGS, GOLDEN_LEGGINGS, IRON_LEGGINGS, NETHERITE_LEGGINGS, CHAINMAIL_LEGGINGS, LEATHER_LEGGINGS -> - true; - default -> false; - }; - } - - return false; - } - - default boolean isChestplate(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case DIAMOND_CHESTPLATE, GOLDEN_CHESTPLATE, IRON_CHESTPLATE, NETHERITE_CHESTPLATE, CHAINMAIL_CHESTPLATE, LEATHER_CHESTPLATE -> - true; - default -> false; - }; - } - - return false; - } - - default boolean isElytra(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case ELYTRA, LEGACY_ELYTRA -> true; - default -> false; - }; - } - - return false; - } - - default boolean isHoe(ItemStack it) { - if (isItem(it)) { - return switch (it.getType()) { - case DIAMOND_HOE, GOLDEN_HOE, IRON_HOE, NETHERITE_HOE, STONE_HOE, WOODEN_HOE -> true; - default -> false; - }; - } - - return false; - } - - default boolean isOre(BlockData b) { - return switch (b.getMaterial()) { - case COPPER_ORE, DEEPSLATE_COPPER_ORE, COAL_ORE, GOLD_ORE, IRON_ORE, DIAMOND_ORE, LAPIS_ORE, EMERALD_ORE, NETHER_QUARTZ_ORE, NETHER_GOLD_ORE, REDSTONE_ORE, DEEPSLATE_COAL_ORE, DEEPSLATE_IRON_ORE, DEEPSLATE_GOLD_ORE, DEEPSLATE_LAPIS_ORE, DEEPSLATE_DIAMOND_ORE, DEEPSLATE_EMERALD_ORE, DEEPSLATE_REDSTONE_ORE -> - true; - default -> false; - }; - } - - default boolean isStorage(BlockData b) { - return switch (b.getMaterial()) { - case CHEST, - SMOKER, - TRAPPED_CHEST, - SHULKER_BOX, - WHITE_SHULKER_BOX, - ORANGE_SHULKER_BOX, - MAGENTA_SHULKER_BOX, - LIGHT_BLUE_SHULKER_BOX, - YELLOW_SHULKER_BOX, - LIME_SHULKER_BOX, - PINK_SHULKER_BOX, - GRAY_SHULKER_BOX, - LIGHT_GRAY_SHULKER_BOX, - CYAN_SHULKER_BOX, - PURPLE_SHULKER_BOX, - BLUE_SHULKER_BOX, - BROWN_SHULKER_BOX, - GREEN_SHULKER_BOX, - RED_SHULKER_BOX, - BLACK_SHULKER_BOX, - BARREL, - DISPENSER, - DROPPER, - FURNACE, - BLAST_FURNACE, - HOPPER -> true; - default -> false; - }; - } -} diff --git a/src/main/java/com/volmit/adapt/api/world/AdaptPlayer.java b/src/main/java/com/volmit/adapt/api/world/AdaptPlayer.java deleted file mode 100644 index a34adb80d..000000000 --- a/src/main/java/com/volmit/adapt/api/world/AdaptPlayer.java +++ /dev/null @@ -1,358 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.world; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.notification.AdvancementNotification; -import com.volmit.adapt.api.notification.Notifier; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.tick.TickedObject; -import com.volmit.adapt.util.*; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; - -import java.io.File; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -@EqualsAndHashCode(callSuper = false) -@Data -public class AdaptPlayer extends TickedObject { - private final Player player; - private final PlayerData data; - private ChronoLatch savelatch; - private ChronoLatch updatelatch; - private Notifier not; - private Notifier actionBarNotifier; - private AdvancementHandler advancementHandler; - private RollingSequence speed; - private long lastloc; - private Vector velocity; - private Location lastpos; - private long lastSeen; - private volatile boolean pendingDataDeletion; - - public AdaptPlayer(Player p) { - this(p, null); - } - - public AdaptPlayer(Player p, PlayerData prefetchedData) { - super("players", p.getUniqueId().toString(), 50); - this.player = p; - data = prefetchedData == null ? loadPlayerData(p.getUniqueId()) : prefetchedData; - updatelatch = new ChronoLatch(1000); - savelatch = new ChronoLatch(60000); - not = new Notifier(this); - actionBarNotifier = new Notifier(this); - advancementHandler = new AdvancementHandler(this); - speed = new RollingSequence(7); - lastloc = M.ms(); - lastSeen = M.ms(); - velocity = new Vector(); - } - - public boolean canConsumeFood(double cost, int minFood) { - return (player.getFoodLevel() + player.getSaturation()) - minFood > cost; - } - - public boolean consumeFood(double cost, int minFood) { - if (canConsumeFood(cost, minFood)) { - int food = player.getFoodLevel(); - double sat = player.getSaturation(); - - if (sat >= cost) { - sat = (player.getSaturation() - cost); - cost = 0; - } else if (player.getSaturation() > 0) { - cost -= sat; - sat = 0; - } - - if (cost >= 1) { - food -= (int) Math.floor(cost); - cost = Math.floor(cost); - } - - if (cost > 0) { - if (sat >= cost) { - sat -= cost; - cost = 0; - } else { - sat++; - food--; - } - } - - if (sat >= cost && cost > 0) { - sat -= cost; - cost = 0; - } - - player.setFoodLevel(food); - player.setSaturation((float) sat); - - return true; - } - - return false; - } - - public boolean isBusy() { - return not.isBusy(); - } - - public PlayerSkillLine getSkillLine(String l) { - return getData().getSkillLine(l); - } - - private void save() { - UUID uuid = player.getUniqueId(); - File playerDataFile = getPlayerDataFile(uuid); - - if (pendingDataDeletion) { - queueDelete(uuid, playerDataFile); - return; - } - - String json = this.data.toJson(AdaptConfig.get().isUseSql()); - PlayerDataPersistenceQueue queue = Adapt.instance.getPlayerDataPersistenceQueue(); - if (queue != null) { - queue.queueSave(uuid, json, playerDataFile); - return; - } - - if (AdaptConfig.get().isUseSql()) { - if (Adapt.instance.getRedisSync() != null) { - Adapt.instance.getRedisSync().publish(uuid, json); - } - if (Adapt.instance.getSqlManager() != null) { - Adapt.instance.getSqlManager().updateData(uuid, json); - } - } else { - J.attempt(() -> IO.writeAll(playerDataFile, json)); - } - } - - @Override - public void unregister() { - super.unregister(); - save(); - } - - public void delete(UUID uuid) { - pendingDataDeletion = true; - File local = getPlayerDataFile(uuid); - Adapt.warn("Deleting Player Data: " + local.getAbsolutePath()); - queueDelete(uuid, local); - - Player p = player; - if (!p.isOnline()) { - return; - } - - J.s(() -> p.kickPlayer("Your data has been deleted."), 20); - } - - public boolean shouldUnload() { - if (player.isOnline()) { - lastSeen = M.ms(); - return false; - } - - return lastSeen + 60_000 < System.currentTimeMillis(); - } - - public static PlayerData loadPlayerData(UUID uuid) { - boolean upload = false; - if (AdaptConfig.get().isUseSql()) { - if (Adapt.instance.getRedisSync() != null) { - var opt = Adapt.instance.getRedisSync().cachedData(uuid); - if (opt.isPresent()) { - Adapt.verbose("Using cached data for player: " + uuid); - return opt.get(); - } - } - - if (Adapt.instance.getSqlManager() != null) { - String sqlData = Adapt.instance.getSqlManager().fetchData(uuid); - if (sqlData != null) { - return PlayerData.fromJson(sqlData); - } - upload = true; - } - } - - File f = getPlayerDataFile(uuid); - if (f.exists()) { - try { - String text = IO.readAll(f); - if (upload) { - PlayerDataPersistenceQueue queue = Adapt.instance.getPlayerDataPersistenceQueue(); - if (queue != null) { - queue.queueSave(uuid, text, f); - } else if (Adapt.instance.getSqlManager() != null) { - Adapt.instance.getSqlManager().updateData(uuid, text); - } - } - return PlayerData.fromJson(text); - } catch (Throwable ignored) { - Adapt.verbose("Failed to load player data for " + uuid); - } - } - - return new PlayerData(); - } - - @Override - public void onTick() { - if (updatelatch.flip()) { - getData().update(this); - } - - if (savelatch.flip()) { - save(); - } - - getServer().takeSpatial(this); - - Location at = player.getLocation(); - - if (lastpos != null) { - if (lastpos.getWorld().equals(at.getWorld())) { - if (lastpos.distanceSquared(at) <= 7 * 7) { - speed.put(lastpos.distance(at) / ((double) (M.ms() - lastloc) / 50D)); - velocity = velocity.clone().add(at.clone().subtract(lastpos).toVector()).multiply(0.5); - velocity.setX(Math.abs(velocity.getX()) < 0.01 ? 0 : velocity.getX()); - velocity.setY(Math.abs(velocity.getY()) < 0.01 ? 0 : velocity.getY()); - velocity.setZ(Math.abs(velocity.getZ()) < 0.01 ? 0 : velocity.getZ()); - } - } - } - - lastpos = at.clone(); - lastloc = M.ms(); - } - - public double getSpeed() { - return speed.getAverage(); - } - - public boolean hasAdaptation(String id) { - if (id == null || id.isBlank()) { - return false; - } - - int separator = id.indexOf('-'); - if (separator <= 0) { - return false; - } - - String skillLine = id.substring(0, separator); - if (skillLine.isBlank()) { - return false; - } - - PlayerSkillLine line = getData().getSkillLine(skillLine); - if (line == null) { - return false; - } - - PlayerAdaptation adaptation = line.getAdaptation(id); - return adaptation != null && adaptation.getLevel() > 0; - } - - public void giveXPToRecents(AdaptPlayer p, double xpGained, int ms) { - for (PlayerSkillLine i : p.getData().getSkillLines().v()) { - if (M.ms() - i.getLast() < ms) { - i.giveXP(not, xpGained); - } - } - } - - public void giveXPToRandom(AdaptPlayer p, double xpGained) { - p.getData().getSkillLines().v().getRandom().giveXP(p.getNot(), xpGained); - } - - public void boostXPToRandom(AdaptPlayer p, double boost, int ms) { - p.getData().getSkillLines().v().getRandom().boost(boost, ms); - } - - public void boostXPToRecents(double boost, int ms) { - for (PlayerSkillLine i : this.getData().getSkillLines().v()) { - if (M.ms() - i.getLast() < ms) { - i.boost(boost, ms); - } - } - } - - public void loggedIn() { - lastSeen = M.ms(); - if (AdaptConfig.get().isLoginBonus()) { - long timeGone = M.ms() - getData().getLastLogin(); - boolean first = getData().getLastLogin() == 0; - getData().setLastLogin(M.ms()); - long boostTime = (long) Math.min(timeGone / 12D, TimeUnit.HOURS.toMillis(1)); - if (boostTime < TimeUnit.MINUTES.toMillis(5)) { - return; - } - double boostAmount = M.lerp(0.1, 0.25, (double) boostTime / (double) TimeUnit.HOURS.toMillis(1)); - getData().globalXPMultiplier(boostAmount, (int) boostTime); - if (!AdaptConfig.get().isWelcomeMessage()) - return; - getNot().queue(AdvancementNotification.builder() - .title(first ? Localizer.dLocalize("snippets.gui.welcome") : Localizer.dLocalize("snippets.gui.welcome_back")) - .description("+" + C.GREEN + Form.pc(boostAmount, 0) + C.GRAY + " " + Localizer.dLocalize("snippets.gui.xp_bonus_for_time") + " " + C.AQUA + Form.duration(boostTime, 0)) - .model(CustomModel.get(Material.DIAMOND, "snippets", "gui", first ? "welcome" : "welcomeback")) - .build()); - } - } - - public boolean hasSkill(Skill s) { - if (s == null) { - return false; - } - - PlayerSkillLine line = getData().getSkillLine(s.getName()); - return line != null && line.getXp() > 1; - } - - private static File getPlayerDataFile(UUID uuid) { - return new File(Adapt.instance.getDataFolder("data", "players"), uuid.toString() + ".json"); - } - - private void queueDelete(UUID uuid, File localFile) { - PlayerDataPersistenceQueue queue = Adapt.instance.getPlayerDataPersistenceQueue(); - if (queue != null) { - queue.queueDelete(uuid, localFile); - return; - } - - if (localFile.exists() && !localFile.delete()) { - Adapt.verbose("Failed to delete local player data file " + localFile.getAbsolutePath()); - } - if (AdaptConfig.get().isUseSql() && Adapt.instance.getSqlManager() != null) { - Adapt.instance.getSqlManager().delete(uuid); - } - } -} diff --git a/src/main/java/com/volmit/adapt/api/world/AdaptServer.java b/src/main/java/com/volmit/adapt/api/world/AdaptServer.java deleted file mode 100644 index fbf2b183f..000000000 --- a/src/main/java/com/volmit/adapt/api/world/AdaptServer.java +++ /dev/null @@ -1,383 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.world; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.notification.AdvancementNotification; -import com.volmit.adapt.api.notification.SoundNotification; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.skill.SkillRegistry; -import com.volmit.adapt.api.tick.TickedObject; -import com.volmit.adapt.api.xp.SpatialXP; -import com.volmit.adapt.api.xp.XP; -import com.volmit.adapt.api.xp.XPMultiplier; -import com.volmit.adapt.content.gui.SkillsGui; -import com.volmit.adapt.content.item.ExperienceOrb; -import com.volmit.adapt.content.item.KnowledgeOrb; -import com.volmit.adapt.util.*; -import lombok.Getter; -import lombok.NonNull; -import lombok.SneakyThrows; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.entity.Snowball; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -import java.io.File; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -public class AdaptServer extends TickedObject { - private final ReentrantLock clearLock = new ReentrantLock(); - private final Map players = new ConcurrentHashMap<>(); - private final Cache prefetchedPlayerData = Caffeine.newBuilder() - .expireAfterWrite(2, TimeUnit.MINUTES) - .maximumSize(2048) - .build(); - @Getter - private volatile List onlinePlayerSnapshot = List.of(); - @Getter - private volatile List onlineAdaptPlayerSnapshot = List.of(); - @Getter - private final List spatialTickets = new ArrayList<>(); - @Getter - private final SkillRegistry skillRegistry = new SkillRegistry(); - @Getter - private AdaptServerData data = new AdaptServerData(); - - public AdaptServer() { - super("core", UUID.randomUUID().toString(), 1000); - load(); - - Bukkit.getOnlinePlayers().forEach(this::join); - refreshOnlinePlayerSnapshots(); - } - - public void offer(SpatialXP xp) { - if (xp == null || xp.getSkill() == null || xp.getLocation() == null) { - return; - } - if (xp.getRadius() <= 0 || xp.getXp() <= 0 || xp.getMs() <= M.ms()) { - return; - } - synchronized (spatialTickets) { - spatialTickets.add(xp); - } - } - - public void takeSpatial(AdaptPlayer p) { - try { - SpatialXP x; - synchronized (spatialTickets) { - int size = spatialTickets.size(); - if (size == 0) { - return; - } - x = spatialTickets.get(size - 1); - } - - if (M.ms() > x.getMs()) { - synchronized (spatialTickets) { - spatialTickets.remove(x); - } - return; - } - - if (!p.getPlayer().getClass().getSimpleName().equals("CraftPlayer")) { - synchronized (spatialTickets) { - spatialTickets.remove(x); - } - return; - } - - if (p.getPlayer().getWorld().equals(x.getLocation().getWorld())) { - double c = p.getPlayer().getLocation().distanceSquared(x.getLocation()); - if (c < x.getRadius() * x.getRadius()) { - double distl = M.lerpInverse(0, x.getRadius() * x.getRadius(), c); - double xp = x.getXp() / (1.5D * ((distl * 9) + 1)); - synchronized (spatialTickets) { - x.setXp(x.getXp() - xp); - - if (x.getXp() < 10) { - xp += x.getXp(); - spatialTickets.remove(x); - } - } - - XP.xp(p, x.getSkill(), xp); - } - } - } catch (Throwable ignored) { - } - } - - public void join(Player p) { - PlayerData prefetched = takePrefetchedData(p.getUniqueId()); - AdaptPlayer a = new AdaptPlayer(p, prefetched); - players.put(p.getUniqueId(), a); - refreshOnlinePlayerSnapshots(); - a.loggedIn(); - } - - public void quit(UUID p) { - AdaptPlayer a = players.get(p); - if (a == null) return; - a.unregister(); - players.remove(p); - prefetchedPlayerData.invalidate(p); - refreshOnlinePlayerSnapshots(); - } - - @Override - public void unregister() { - new HashSet<>(players.keySet()).forEach(this::quit); - prefetchedPlayerData.invalidateAll(); - onlinePlayerSnapshot = List.of(); - onlineAdaptPlayerSnapshot = List.of(); - skillRegistry.unregister(); - save(); - super.unregister(); - } - - @EventHandler(priority = EventPriority.LOWEST) - public void on(ProjectileLaunchEvent e) { - if (e.getEntity() instanceof Snowball s && e.getEntity().getShooter() instanceof Player p) { - KnowledgeOrb.Data data = KnowledgeOrb.get(s.getItem()); - if (data != null) { - Skill skill = getSkillRegistry().getSkill(data.getSkill()); - data.apply(p); - SoundNotification.builder() - .sound(Sound.ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM) - .volume(0.35f).pitch(1.455f) - .build().play(getPlayer(p)); - SoundNotification.builder() - .sound(Sound.ENTITY_SHULKER_OPEN) - .volume(1f).pitch(1.655f) - .build().play(getPlayer(p)); - getPlayer(p).getNot().queue(AdvancementNotification.builder() - .icon(Material.BOOK) - .model(CustomModel.get(Material.BOOK, "snippets", "gui", "knowledge")) - .title(C.GRAY + "+ " + C.WHITE + data.getKnowledge() + " " + skill.getDisplayName() + " Knowledge") - .build()); - } else { - ExperienceOrb.Data datax = ExperienceOrb.get(s.getItem()); - if (datax != null) { - datax.apply(p); - SoundNotification.builder() - .sound(Sound.ENTITY_ALLAY_AMBIENT_WITHOUT_ITEM) - .volume(0.35f).pitch(1.455f) - .build().play(getPlayer(p)); - SoundNotification.builder() - .sound(Sound.ENTITY_SHULKER_OPEN) - .volume(1f).pitch(1.655f) - .build().play(getPlayer(p)); - } - } - } - - } - - @EventHandler(priority = EventPriority.LOWEST) - public void on(PlayerJoinEvent e) { - Player p = e.getPlayer(); - join(p); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(AsyncPlayerPreLoginEvent e) { - if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { - return; - } - - UUID uuid = e.getUniqueId(); - if (players.containsKey(uuid) || prefetchedPlayerData.getIfPresent(uuid) != null) { - return; - } - - try { - prefetchedPlayerData.put(uuid, AdaptPlayer.loadPlayerData(uuid)); - } catch (Throwable ignored) { - Adapt.verbose("Failed to prefetch player data for " + uuid); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - quit(p.getUniqueId()); - } - - @EventHandler - public void on(CraftItemEvent e) { - if (e.getWhoClicked() instanceof Player p) { - Adaptation required = getSkillRegistry().getRequiredAdaptation(e.getRecipe()); - if (required == null || required.hasAdaptation(p)) { - return; - } - - Skill requiredSkill = required.getSkill(); - String skillName = requiredSkill == null ? "Unknown Skill" : requiredSkill.getDisplayName(); - SoundPlayer sp = SoundPlayer.of(p); - Adapt.actionbar(p, C.RED + "Requires " + required.getDisplayName() + C.RED + " from " + skillName); - sp.play(p.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.5f, 1.8f); - e.setCancelled(true); - } - } - - @Override - public void onTick() { - data.getMultipliers().removeIf(multiplier -> multiplier == null || multiplier.isExpired()); - - synchronized (spatialTickets) { - spatialTickets.removeIf(ticket -> M.ms() > ticket.getMs()); - } - - if (!clearLock.tryLock()) - return; - - try { - int sizeBefore = players.size(); - players.values().removeIf(AdaptPlayer::shouldUnload); - if (players.size() != sizeBefore) { - refreshOnlinePlayerSnapshots(); - } - } finally { - clearLock.unlock(); - } - } - - public PlayerData peekData(UUID player) { - if (Bukkit.getPlayer(player) != null) { - return getPlayer(Bukkit.getPlayer(player)).getData(); - } - - if (AdaptConfig.get().isUseSql()) { - String sqlData = Adapt.instance.getSqlManager().fetchData(player); - if (sqlData != null) { - return Json.fromJson(sqlData, PlayerData.class); - } - } - - File f = new File(Adapt.instance.getDataFolder("data", "players"), player + ".json"); - if (f.exists()) { - try { - return Json.fromJson(IO.readAll(f), PlayerData.class); - } catch (Throwable ignored) { - Adapt.verbose("Failed to load player data for " + player); - } - } - - return new PlayerData(); - } - - @NonNull - public Optional getPlayerData(@NonNull UUID uuid) { - return Optional.ofNullable(players.get(uuid)) - .map(AdaptPlayer::getData); - } - - public AdaptPlayer getPlayer(Player p) { - AdaptPlayer existing = players.get(p.getUniqueId()); - if (existing != null) { - return existing; - } - - AdaptPlayer created = players.computeIfAbsent(p.getUniqueId(), player -> { - Adapt.warn("Failed to find AdaptPlayer for " + p.getName() + " (" + p.getUniqueId() + ")"); - Adapt.warn("Loading new AdaptPlayer..."); - return new AdaptPlayer(p, takePrefetchedData(player)); - }); - refreshOnlinePlayerSnapshots(); - return created; - } - - private PlayerData takePrefetchedData(UUID uuid) { - PlayerData prefetched = prefetchedPlayerData.getIfPresent(uuid); - if (prefetched != null) { - prefetchedPlayerData.invalidate(uuid); - } - return prefetched; - } - - private void refreshOnlinePlayerSnapshots() { - ArrayList adaptPlayers = new ArrayList<>(players.size()); - ArrayList playerSnapshot = new ArrayList<>(players.size()); - - for (AdaptPlayer adaptPlayer : players.values()) { - if (adaptPlayer == null) { - continue; - } - Player online = adaptPlayer.getPlayer(); - if (online == null || !online.isOnline()) { - continue; - } - adaptPlayers.add(adaptPlayer); - playerSnapshot.add(online); - } - - onlineAdaptPlayerSnapshot = Collections.unmodifiableList(adaptPlayers); - onlinePlayerSnapshot = Collections.unmodifiableList(playerSnapshot); - } - - public void openSkillGUI(Skill skill, Player p) { - skill.openGui(p); - } - - public void openAdaptGui(Player p) { - SkillsGui.open(p); - } - - public void openAdaptationGUI(Adaptation adaptation, Player p) { - adaptation.openGui(p); - } - - public void boostXP(double boost, int ms) { - data.getMultipliers().add(new XPMultiplier(boost, ms)); - } - - public void load() { - File f = new File(Adapt.instance.getDataFolder("data"), "server-data.json"); - if (f.exists()) { - try { - data = Json.fromJson(IO.readAll(f), AdaptServerData.class); - } catch (Throwable ignored) { - Adapt.verbose("Failed to load global boosts data"); - } - } - } - - @SneakyThrows - public void save() { - IO.writeAll(new File(Adapt.instance.getDataFolder("data"), "server-data.json"), Json.toJson(data, true)); - } -} diff --git a/src/main/java/com/volmit/adapt/api/world/AdvancementHandler.java b/src/main/java/com/volmit/adapt/api/world/AdvancementHandler.java deleted file mode 100644 index 3d5427ce1..000000000 --- a/src/main/java/com/volmit/adapt/api/world/AdvancementHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.world; - -import com.volmit.adapt.AdaptConfig; -import lombok.Data; - -import static com.volmit.adapt.Adapt.instance; - -@Data -public class AdvancementHandler { - private AdaptPlayer player; - private boolean ready; - - public AdvancementHandler(AdaptPlayer player) { - this.player = player; - instance.getManager().unlockExisting(player); - } - - public void grant(String key, boolean toast) { - if (!AdaptConfig.get().isAdvancements()) return; - instance.getManager().grant(getPlayer(), key, toast); - } - - public void grant(String key) { - grant(key, true); - } -} diff --git a/src/main/java/com/volmit/adapt/api/world/PlayerData.java b/src/main/java/com/volmit/adapt/api/world/PlayerData.java deleted file mode 100644 index 3115d57d0..000000000 --- a/src/main/java/com/volmit/adapt/api/world/PlayerData.java +++ /dev/null @@ -1,371 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.world; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.notification.ActionBarNotification; -import com.volmit.adapt.api.notification.SoundNotification; -import com.volmit.adapt.api.notification.TitleNotification; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.xp.XP; -import com.volmit.adapt.api.xp.XPMultiplier; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Json; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.collection.KSet; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.entity.EntityType; - -@Data -@NoArgsConstructor -public class PlayerData { - private final KMap skillLines = new KMap<>(); - private KMap stats = new KMap<>(); - private String last = "none"; - private KSet advancements = new KSet<>(); - private Discovery seenBiomes = new Discovery<>(); - private Discovery seenMobs = new Discovery<>(); - private Discovery seenFoods = new Discovery<>(); - private Discovery seenItems = new Discovery<>(); - private Discovery seenRecipes = new Discovery<>(); - private Discovery seenEnchants = new Discovery<>(); - private Discovery seenWorlds = new Discovery<>(); - private Discovery seenPeople = new Discovery<>(); - private Discovery seenEnvironments = new Discovery<>(); - private Discovery seenPotionEffects = new Discovery<>(); - private Discovery seenBlocks = new Discovery<>(); - private KList multipliers = new KList<>(); - private long wisdom = 0; - private double multiplier = 0; - private long lastLogin = 0; - private double masterXp = 1; - private double lastMasterXp = 0; - - public void giveMasterXp(double xp) { - masterXp += xp; - } - - public void globalXPMultiplier(double v, int duration) { - multipliers.add(new XPMultiplier(v, duration)); - } - - public boolean isGranted(String advancement) { - return advancements.contains(advancement); - } - - public void ensureGranted(String advancement) { - advancements.add(advancement); - } - - public double getStat(String key) { - Double d = stats.get(key); - return d == null ? 0 : d; - } - - public void addStat(String key, double amt) { - if (!stats.containsKey(key)) { - stats.put(key, amt); - } else { - stats.put(key, stats.get(key) + amt); - } - } - - public void update(AdaptPlayer p) { - double m = 1D; - m += collectActivePlayerMultiplierBonus(); - m += collectGlobalMultiplierBonus(); - - if (m <= 0) { - m = 0.01; - } - - if (m > 1000) { - m = 1000; - } - - multiplier = m; - - for (var entry : skillLines.entrySet()) { - String lineId = entry.getKey(); - Skill loadedSkill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(lineId); - if (loadedSkill == null) { - if (Adapt.instance.getAdaptServer().getSkillRegistry().isKnownSkill(lineId)) { - continue; - } - skillLines.remove(lineId, entry.getValue()); - Adapt.warn("Removed unknown skill line '" + lineId + "' from " + p.getPlayer().getName()); - continue; - } - - PlayerSkillLine lineData = entry.getValue(); - if (lineData == null) { - skillLines.remove(lineId); - continue; - } - - if (lineData.getXp() == 0 && lineData.getKnowledge() == 0) { - skillLines.remove(lineId, lineData); - continue; - } - - lineData.update(p, lineId, this); - } - - int oldLevel = (int) XP.getLevelForXp(getLastMasterXp()); - int level = (int) XP.getLevelForXp(getMasterXp()); - - if (oldLevel != level) { - setLastMasterXp(getMasterXp()); - p.getNot().queue(SoundNotification.builder() - .sound(Sound.BLOCK_ENCHANTMENT_TABLE_USE) - .volume(1f) - .pitch(0.54f) - .group("lvl") - .build(), - SoundNotification.builder() - .sound(Sound.BLOCK_AMETHYST_BLOCK_CHIME) - .volume(1f) - .pitch(0.44f) - .group("lvl") - .build(), - SoundNotification.builder() - .sound(Sound.BLOCK_AMETHYST_BLOCK_CHIME) - .volume(1f) - .pitch(0.74f) - .group("lvl") - .build(), - SoundNotification.builder() - .sound(Sound.BLOCK_AMETHYST_BLOCK_CHIME) - .volume(1f) - .pitch(1.34f) - .group("lvl") - .build(), - TitleNotification.builder() - .in(250) - .stay(1450) - .out(2250) - .group("lvl") - .title("") - .subtitle(C.GOLD + Localizer.dLocalize("snippets.gui.level") +" " + level)// I'm sorry I missed this! - .build()); - p.getActionBarNotifier().queue( - ActionBarNotification.builder() - .duration(450) - .group("power") - .title(C.GOLD + "" + Form.f(level * AdaptConfig.get().getPowerPerLevel(), 0) + C.GRAY + " " + Localizer.dLocalize("snippets.gui.max_ability_power")) // I'm sorry I missed this! - .build()); - - } - } - - private double collectActivePlayerMultiplierBonus() { - double bonus = 0D; - for (int i = multipliers.size() - 1; i >= 0; i--) { - XPMultiplier active = multipliers.get(i); - if (active == null || active.isExpired()) { - multipliers.remove(i); - continue; - } - bonus += active.getMultiplier(); - } - return bonus; - } - - private double collectGlobalMultiplierBonus() { - double bonus = 0D; - KList globalMultipliers = Adapt.instance.getAdaptServer().getData().getMultipliers(); - for (int i = 0; i < globalMultipliers.size(); i++) { - XPMultiplier active = globalMultipliers.get(i); - if (active == null || active.isExpired()) { - continue; - } - bonus += active.getMultiplier(); - } - return bonus; - } - - public int getAvailablePower() { - return getMaxPower() - getUsedPower(); - } - - public boolean hasPowerAvailable() { - return hasPowerAvailable(1); - } - - public boolean hasPowerAvailable(int amount) { - return getAvailablePower() >= amount; - } - - public int getUsedPower() { - return skillLines.values().stream().mapToInt(i -> i.getAdaptations().values().stream().mapToInt(PlayerAdaptation::getLevel).sum()).sum(); - } - - public int getLevel() { - return (int) XP.getLevelForXp(getMasterXp()); - } - - public int getMaxPower() { - return (int) (XP.getLevelForXp(getMasterXp()) * AdaptConfig.get().getPowerPerLevel()); - } - - public PlayerSkillLine getSkillLine(String skillLine) { - if (Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skillLine) == null) { - return null; - } - - synchronized (skillLines) { - try { - PlayerSkillLine s = skillLines.get(skillLine); - - if (s != null) { - return s; - } - } catch (Throwable e) { - e.printStackTrace(); - Adapt.error("Failed to get skill line " + skillLine); - } - - PlayerSkillLine s = new PlayerSkillLine(); - s.setLine(skillLine); - skillLines.put(skillLine, s); - return s; - } - } - - public PlayerSkillLine getSkillLineNullable(String skillLine) { - return skillLines.get(skillLine); - } - - public void resetMonotonyForOtherSkills(String currentSkill) { - for (PlayerSkillLine line : skillLines.values()) { - if (!line.getLine().equals(currentSkill)) { - line.relaxStalenessForActivitySwitch(); - } - } - } - - public void addWisdom() { - wisdom++; - } - - public void clearXp() { - for (PlayerSkillLine line : skillLines.values()) { - line.setXp(0); - line.setLastXP(0); - line.setLastLevel(0); - line.setMonotonyCounter(0); - line.setMonotonyMultiplier(1.0); - line.setLastXpTimestamp(0); - line.setSkillStaleness(new PlayerSkillLine.RewardStalenessState()); - line.getActivityStaleness().clear(); - line.getAdaptations().clear(); - } - masterXp = 1; - lastMasterXp = 0; - } - - public void clearKnowledge() { - for (PlayerSkillLine line : skillLines.values()) { - line.setKnowledge(0); - } - } - - public void clearAdaptations() { - for (PlayerSkillLine line : skillLines.values()) { - line.getAdaptations().clear(); - } - } - - public void clearStats() { - stats.clear(); - } - - public void clearDiscoveries() { - seenBiomes = new Discovery<>(); - seenMobs = new Discovery<>(); - seenFoods = new Discovery<>(); - seenItems = new Discovery<>(); - seenRecipes = new Discovery<>(); - seenEnchants = new Discovery<>(); - seenWorlds = new Discovery<>(); - seenPeople = new Discovery<>(); - seenEnvironments = new Discovery<>(); - seenPotionEffects = new Discovery<>(); - seenBlocks = new Discovery<>(); - } - - public void pruneAdaptationsForPowerBudget() { - while (getUsedPower() > getMaxPower()) { - String worstSkill = null; - String worstAdaptation = null; - int worstLevel = Integer.MAX_VALUE; - - for (var skillEntry : skillLines.entrySet()) { - for (var adaptEntry : skillEntry.getValue().getAdaptations().entrySet()) { - int level = adaptEntry.getValue().getLevel(); - if (level > 0 && level < worstLevel) { - worstLevel = level; - worstSkill = skillEntry.getKey(); - worstAdaptation = adaptEntry.getKey(); - } - } - } - - if (worstSkill == null) { - break; - } - - PlayerAdaptation adapt = skillLines.get(worstSkill).getAdaptations().get(worstAdaptation); - if (adapt.getLevel() <= 1) { - skillLines.get(worstSkill).getAdaptations().remove(worstAdaptation); - } else { - adapt.setLevel(adapt.getLevel() - 1); - } - } - } - - public void clearAll() { - clearXp(); - clearKnowledge(); - clearAdaptations(); - clearStats(); - clearDiscoveries(); - advancements.clear(); - multipliers.clear(); - wisdom = 0; - } - - public String toJson(boolean raw) { - synchronized (skillLines) { - return Json.toJson(this, !raw); - } - } - - public static PlayerData fromJson(String json) { - return Json.fromJson(json, PlayerData.class); - } -} diff --git a/src/main/java/com/volmit/adapt/api/world/PlayerDataPersistenceQueue.java b/src/main/java/com/volmit/adapt/api/world/PlayerDataPersistenceQueue.java deleted file mode 100644 index bb274e139..000000000 --- a/src/main/java/com/volmit/adapt/api/world/PlayerDataPersistenceQueue.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.volmit.adapt.api.world; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.util.IO; - -import java.io.File; -import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public class PlayerDataPersistenceQueue implements AutoCloseable { - private static final long DEFAULT_SHUTDOWN_TIMEOUT_MS = 30_000L; - - private final ExecutorService ioExecutor; - private final AtomicBoolean acceptingTasks = new AtomicBoolean(true); - - public PlayerDataPersistenceQueue() { - ioExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { - private int tid = 0; - - @Override - public Thread newThread(Runnable runnable) { - Thread thread = new Thread(runnable, "Adapt PlayerData IO " + (++tid)); - thread.setDaemon(true); - thread.setUncaughtExceptionHandler((t, e) -> - Adapt.warn("Uncaught async persistence exception in " + t.getName() + ": " + e.getMessage())); - return thread; - } - }); - } - - public void queueSave(UUID uuid, String json, File localFile) { - submit("save", uuid, () -> { - if (AdaptConfig.get().isUseSql()) { - if (Adapt.instance.getRedisSync() != null) { - Adapt.instance.getRedisSync().publish(uuid, json); - } - if (Adapt.instance.getSqlManager() != null) { - Adapt.instance.getSqlManager().updateData(uuid, json); - } - return; - } - - IO.writeAll(localFile, json); - }); - } - - public void queueDelete(UUID uuid, File localFile) { - submit("delete", uuid, () -> { - if (localFile.exists() && !localFile.delete()) { - Adapt.verbose("Failed to delete local player data file " + localFile.getAbsolutePath()); - } - - if (AdaptConfig.get().isUseSql() && Adapt.instance.getSqlManager() != null) { - Adapt.instance.getSqlManager().delete(uuid); - } - }); - } - - public void flushAndShutdown(long timeoutMs) { - acceptingTasks.set(false); - ioExecutor.shutdown(); - try { - if (!ioExecutor.awaitTermination(timeoutMs, TimeUnit.MILLISECONDS)) { - Adapt.warn("Timed out waiting for player data persistence queue to drain. Forcing shutdown."); - ioExecutor.shutdownNow(); - ioExecutor.awaitTermination(Math.max(1000, timeoutMs / 2), TimeUnit.MILLISECONDS); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - Adapt.warn("Interrupted while shutting down player data persistence queue."); - ioExecutor.shutdownNow(); - } - } - - @Override - public void close() { - flushAndShutdown(DEFAULT_SHUTDOWN_TIMEOUT_MS); - } - - private void submit(String operation, UUID uuid, ThrowingRunnable runnable) { - if (!acceptingTasks.get()) { - return; - } - - try { - ioExecutor.execute(() -> { - try { - runnable.run(); - } catch (Throwable e) { - Adapt.warn("Failed to " + operation + " player data for " + uuid + ": " + e.getMessage()); - } - }); - } catch (RejectedExecutionException ignored) { - Adapt.verbose("Rejected player data " + operation + " task for " + uuid + " because the queue is shutting down."); - } - } - - @FunctionalInterface - private interface ThrowingRunnable { - void run() throws Exception; - } -} diff --git a/src/main/java/com/volmit/adapt/api/world/PlayerSkillLine.java b/src/main/java/com/volmit/adapt/api/world/PlayerSkillLine.java deleted file mode 100644 index 010e6d047..000000000 --- a/src/main/java/com/volmit/adapt/api/world/PlayerSkillLine.java +++ /dev/null @@ -1,451 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.world; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.notification.ActionBarNotification; -import com.volmit.adapt.api.notification.Notifier; -import com.volmit.adapt.api.notification.SoundNotification; -import com.volmit.adapt.api.notification.TitleNotification; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.xp.XP; -import com.volmit.adapt.api.xp.XPMultiplier; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.collection.KMap; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.bukkit.Sound; - -@Data -@NoArgsConstructor -public class PlayerSkillLine { - private String line = ""; - private double xp = 0; - private double lastXP = 0; - private long knowledge = 0; - private double multiplier = 1D; - private double freshness = 1D; - private double rfreshness = 1D; - private int lastLevel = 0; - private long last = M.ms(); - private int monotonyCounter = 0; - private long lastXpTimestamp = 0; - private double monotonyMultiplier = 1.0; - private RewardStalenessState skillStaleness = new RewardStalenessState(); - private long lastStalenessCleanup = 0; - private final KMap storage = new KMap<>(); - private final KMap adaptations = new KMap<>(); - private final KList multipliers = new KList<>(); - private final KMap activityStaleness = new KMap<>(); - - private static double diff(long a, long b) { - return Math.abs(a - b / (double) (a == 0 ? 1 : a)); - } - - public void update(AdaptPlayer p, String line, PlayerData data) { - grantSkillsAndAdaptations(p, line); - checkMaxLevel(p, line); - updateFreshness(); - updateMultiplier(data); - updateEarnedXP(p, line); - updateLevel(p, line, data); - } - - private void grantSkillsAndAdaptations(AdaptPlayer p, String line) { - if (!p.getData().isGranted("skill_" + line) && AdaptConfig.get().isAdvancements()) { - p.getAdvancementHandler().grant("skill_" + line); - } - - for (String i : getAdaptations().keySet()) { - if (!p.getData().isGranted("adaptation_" + i) && AdaptConfig.get().isAdvancements()) { - p.getAdvancementHandler().grant("adaptation_" + i); - } - } - } - - private void checkMaxLevel(AdaptPlayer p, String line) { - if (!p.isBusy() && getXp() > XP.getXpForLevel(AdaptConfig.get().experienceMaxLevel)) { - p.getData().addWisdom(); - Adapt.warn("A Player has reached the maximum level of " + AdaptConfig.get().experienceMaxLevel + " and has been granted 1 wisdom, Dropping Level to " + lastLevel); - setXp(XP.getXpForLevel(AdaptConfig.get().experienceMaxLevel - 1)); - } - } - - private void updateFreshness() { - double max = 1D + (getLevel() * 0.004); - - freshness += (0.08 * freshness) + 0.003; - if (freshness > max) freshness = max; - if (freshness < 0.01) freshness = 0.01; - if (freshness < rfreshness) rfreshness -= ((rfreshness - freshness) * 0.003); - if (freshness > rfreshness) rfreshness += (freshness - rfreshness) * 0.265; - } - - private void updateMultiplier(PlayerData data) { - double m = rfreshness; - for (int i = multipliers.size() - 1; i >= 0; i--) { - XPMultiplier active = multipliers.get(i); - if (active == null || active.isExpired()) { - multipliers.remove(i); - continue; - } - m += active.getMultiplier(); - } - - m = Math.max(0.01, Math.min(m, 1000)); - multiplier = m * data.getMultiplier(); - } - - private void updateEarnedXP(AdaptPlayer p, String line) { - double earned = xp - lastXP; - if (earned > p.getServer().getSkillRegistry().getSkill(line).getMinXp()) lastXP = xp; - } - - private void updateLevel(AdaptPlayer p, String line, PlayerData data) { - if (lastLevel < getLevel()) { - long kb = getKnowledge(); - for (int i = lastLevel; i < getLevel(); i++) { - giveKnowledge((i / 13) + 1); - p.getData().giveMasterXp((i * AdaptConfig.get().getPlayerXpPerSkillLevelUpLevelMultiplier()) + AdaptConfig.get().getPlayerXpPerSkillLevelUpBase()); - } - - if (AdaptConfig.get().isActionbarNotifyLevel()) notifyLevel(p, getLevel(), getKnowledge()); - lastLevel = getLevel(); - } - } - - public void giveXP(Notifier p, double xp) { - giveXP(p, xp, null); - } - - public void giveXP(Notifier p, double xp, String rewardKey) { - freshness -= 0.012 + (xp * 0.00025); - - long now = System.currentTimeMillis(); - lastXpTimestamp = now; - monotonyCounter++; - monotonyMultiplier = computeStalenessMultiplier(xp, rewardKey, now); - - xp = multiplier * monotonyMultiplier * xp; - this.xp += xp; - - if (p != null) { - last = M.ms(); - if (AdaptConfig.get().isActionbarNotifyXp()) { - p.notifyXP(line, xp); - } - } - } - - public void relaxStalenessForActivitySwitch() { - AdaptConfig.FarmPrevention prevention = AdaptConfig.get().getFarmPrevention(); - if (prevention == null || !prevention.isEnabled()) { - monotonyCounter = 0; - monotonyMultiplier = 1.0; - return; - } - - double factor = clamp(prevention.getCrossSkillRecoveryFactor(), 0.0, 1.0); - applyRecoveryFactor(skillStaleness, factor); - for (RewardStalenessState state : activityStaleness.values()) { - applyRecoveryFactor(state, factor); - } - monotonyCounter = 0; - } - - private double computeStalenessMultiplier(double awardXp, String rewardKey, long now) { - if (awardXp <= 0) { - return 1.0; - } - - AdaptConfig.FarmPrevention prevention = AdaptConfig.get().getFarmPrevention(); - if (prevention == null || !prevention.isEnabled()) { - return 1.0; - } - - double skillGain = prevention.getSkillBasePressure() + (awardXp * prevention.getSkillXpPressure()); - double skillMultiplier = applyStaleness( - ensureSkillStaleness(), - now, - skillGain, - prevention.getSkillRecoveryMillis(), - prevention.getSkillDecayCurve(), - prevention.getSkillFloorMultiplier() - ); - - double activityMultiplier = 1.0; - if (prevention.isPerActivityTracking()) { - String normalizedRewardKey = normalizeRewardKey(rewardKey); - if (normalizedRewardKey != null) { - cleanupActivityStaleness(now, prevention.getActivityStateTtlMillis()); - RewardStalenessState activityState = activityStaleness.computeIfAbsent(normalizedRewardKey, k -> new RewardStalenessState()); - double activityGain = prevention.getActivityBasePressure() + (awardXp * prevention.getActivityXpPressure()); - activityMultiplier = applyStaleness( - activityState, - now, - activityGain, - prevention.getActivityRecoveryMillis(), - prevention.getActivityDecayCurve(), - prevention.getActivityFloorMultiplier() - ); - } - } - - double floor = clamp(prevention.getSkillFloorMultiplier(), 0.0, 1.0); - if (prevention.isPerActivityTracking()) { - floor = clamp(floor * prevention.getActivityFloorMultiplier(), 0.0, 1.0); - } - return clamp(skillMultiplier * activityMultiplier, floor, 1.0); - } - - private RewardStalenessState ensureSkillStaleness() { - if (skillStaleness == null) { - skillStaleness = new RewardStalenessState(); - } - return skillStaleness; - } - - private double applyStaleness(RewardStalenessState state, long now, double gain, long recoveryMillis, double curve, double floor) { - if (state == null) { - return 1.0; - } - - decayState(state, now, recoveryMillis); - state.setPressure(clamp(state.getPressure() + Math.max(0.0, gain), 0.0, 100000.0)); - state.setLastAwardAt(now); - - double clampedFloor = clamp(floor, 0.0, 1.0); - if (curve <= 0) { - return 1.0; - } - - double scaled = Math.exp(-state.getPressure() / curve); - return clamp(clampedFloor + ((1.0 - clampedFloor) * scaled), clampedFloor, 1.0); - } - - private void decayState(RewardStalenessState state, long now, long recoveryMillis) { - if (state == null || recoveryMillis <= 0) { - return; - } - - long lastAward = state.getLastAwardAt(); - if (lastAward <= 0) { - state.setLastAwardAt(now); - return; - } - - long elapsed = Math.max(0, now - lastAward); - if (elapsed == 0) { - return; - } - - double decay = Math.exp(-(double) elapsed / (double) recoveryMillis); - state.setPressure(Math.max(0.0, state.getPressure() * decay)); - } - - private void cleanupActivityStaleness(long now, long ttl) { - if (ttl <= 0) { - return; - } - if (now - lastStalenessCleanup < 15000) { - return; - } - - activityStaleness.entrySet().removeIf(entry -> { - RewardStalenessState state = entry.getValue(); - return state == null || (state.getLastAwardAt() > 0 && now - state.getLastAwardAt() > ttl); - }); - lastStalenessCleanup = now; - } - - private void applyRecoveryFactor(RewardStalenessState state, double factor) { - if (state == null) { - return; - } - state.setPressure(Math.max(0.0, state.getPressure() * factor)); - } - - private String normalizeRewardKey(String rewardKey) { - if (rewardKey == null) { - return null; - } - String normalized = rewardKey.trim(); - return normalized.isEmpty() ? null : normalized; - } - - private double clamp(double value, double min, double max) { - return Math.max(min, Math.min(max, value)); - } - - public void giveXPFresh(Notifier p, double xp) { - xp = multiplier * xp; - this.xp += xp; - - if (p != null) { - last = M.ms(); - if (AdaptConfig.get().isActionbarNotifyXp()) { - p.notifyXP(line, xp); - } - } - } - - public boolean hasEarnedWithin(long ms) { - return M.ms() - last < ms; - } - - public PlayerAdaptation getAdaptation(String id) { - return adaptations.get(id); - } - - public int getAdaptationLevel(String id) { - PlayerAdaptation a = getAdaptation(id); - - if (a == null) { - return 0; - } - - return a.getLevel(); - } - - public void setAdaptation(Adaptation a, int level) { - if (level <= 1) { - adaptations.remove(a.getName()); - } - - PlayerAdaptation v = new PlayerAdaptation(); - v.setId(a.getName()); - v.setLevel(Math.min(level, a.getMaxLevel())); - adaptations.put(a.getName(), v); - } - - public Skill getRawSkill(AdaptPlayer p) { - return p.getServer().getSkillRegistry().getSkill(line); - } - - private void notifyLevel(AdaptPlayer p, double lvl, long kn) { -// Skill s = p.getServer().getSkillRegistry().getSkill(getLine()); - if (lvl % 10 == 0) { - p.getNot().queue(SoundNotification.builder() - .sound(Sound.UI_TOAST_CHALLENGE_COMPLETE) - .volume(1f) - .pitch(1.35f) - .group("lvl" + getLine()) - .build(), SoundNotification.builder() - .sound(Sound.UI_TOAST_CHALLENGE_COMPLETE) - .volume(1f) - .pitch(0.75f) - .group("lvl" + getLine()) - .build(), TitleNotification.builder() - .in(250) - .stay(1450) - .out(2250) - .group("lvl" + getLine()) - .title("") - .subtitle(p.getServer().getSkillRegistry().getSkill(getLine()).getDisplayName(getLevel())) - .build()); - p.getActionBarNotifier().queue( - ActionBarNotification.builder() - .duration(450) - .group("know" + getLine()) - .title(kn + " " + p.getServer().getSkillRegistry().getSkill(getLine()).getShortName() + " Knowledge") - .build()); - - } else { - p.getActionBarNotifier().queue( - SoundNotification.builder() - .sound(Sound.BLOCK_AMETHYST_BLOCK_BREAK) - .volume(1f) - .pitch(1.74f) - .group("lvl" + getLine()) - .build(), - SoundNotification.builder() - .sound(Sound.BLOCK_AMETHYST_BLOCK_CHIME) - .volume(1f) - .pitch(0.74f) - .group("lvl" + getLine()) - .build(), - ActionBarNotification.builder() - .duration(450) - .group("lvl" + getLine()) - .title(p.getServer().getSkillRegistry().getSkill(getLine()).getDisplayName(getLevel())) - .build()); - } - - lastLevel = (int) Math.floor(XP.getLevelForXp(getXp())); - } - - public void giveKnowledge(long points) { - this.knowledge += points; - } - - public double getMinimumXPForLevel() { - return XP.getXpForLevel(getLevel()); - } - - public double getXPForLevelUpAbsolute() { - return getMaximumXPForLevel() - getXp(); - } - - public double getXPForLevelUp() { - return getMaximumXPForLevel() - getMinimumXPForLevel(); - } - - public double getMaximumXPForLevel() { - return XP.getXpForLevel(getLevel()); - } - - public double getAbsoluteLevel() { - return XP.getLevelForXp(xp); - } - - public double getLevelProgress() { - return getAbsoluteLevel() - getLevel(); - } - - public double getLevelProgressRemaining() { - return 1D - getLevelProgress(); - } - - public int getLevel() { - return (int) Math.floor(getAbsoluteLevel()); - } - - public void boost(double v, int i) { - multipliers.add(new XPMultiplier(v, i)); - } - - public boolean spendKnowledge(int c) { - if (getKnowledge() >= c) { - setKnowledge(getKnowledge() - c); - return true; - } - - return false; - } - - @Data - @NoArgsConstructor - public static class RewardStalenessState { - private double pressure = 0; - private long lastAwardAt = 0; - } -} diff --git a/src/main/java/com/volmit/adapt/api/xp/Curves.java b/src/main/java/com/volmit/adapt/api/xp/Curves.java deleted file mode 100644 index 7a3592b43..000000000 --- a/src/main/java/com/volmit/adapt/api/xp/Curves.java +++ /dev/null @@ -1,204 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.xp; - -import lombok.Getter; - -import java.util.function.Function; - -public enum Curves { - // Strange ones - QLOG(resolved(level -> Math.pow(level, 2) * Math.log(level), xp -> Math.sqrt(xp / Math.log(xp)), 0.001)), - ELIN(resolved(level -> 1000 * Math.exp(0.001 * level), xp -> Math.log(xp / 1000) / 0.001, 0.001)), - CUBRT(resolved(level -> Math.pow(level, 1 / 3.0), xp -> Math.pow(xp, 3), 0.001)), - HYPER(resolved(level -> 1000 / (2 - level), xp -> 2 - (1000 / xp), 0.001)), - SIGM(resolved(level -> 1000 / (1 + Math.exp(-0.01 * (level - 50))), xp -> 50 + Math.log(xp / (1000 - xp)) / -0.01, 0.001)), - - // Normal ones - X1D2(resolved(level -> Math.pow(level, 1.2), xp -> Math.pow(xp, 1D / 1.2D), 0.001)), - X1D5(resolved(level -> Math.pow(level, 1.5), xp -> Math.pow(xp, 1D / 1.5D), 0.001)), - X2(resolved(level -> Math.pow(level, 2), xp -> Math.pow(xp, 1D / 2D), 0.001)), - X3(resolved(level -> Math.pow(level, 3), xp -> Math.pow(xp, 1D / 3D), 0.001)), - X4(resolved(level -> Math.pow(level, 4), xp -> Math.pow(xp, 1D / 4D), 0.001)), - X5(resolved(level -> Math.pow(level, 5), xp -> Math.pow(xp, 1D / 5D), 0.001)), - X6(resolved(level -> Math.pow(level, 6), xp -> Math.pow(xp, 1D / 6D), 0.001)), - X7(resolved(level -> Math.pow(level, 7), xp -> Math.pow(xp, 1D / 7D), 0.001)), - L1K(resolved(level -> level * 1000D, xp -> xp / 1000D, 0.001)), - L4K(resolved(level -> level * 4000D, xp -> xp / 4000D, 0.001)), - L8K(resolved(level -> level * 8000D, xp -> xp / 8000D, 0.001)), - L16K(resolved(level -> level * 16000D, xp -> xp / 16000D, 0.001)), - - // Game ones - SKYRIM(SkyrimNewtonCurve.create()), - WOW(WOWNewtonCurve.create()), - - // Adapt ones - XL05L7(level -> ((537 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL1L7(level -> ((1337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL15L7(level -> ((1837 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL2L7(level -> ((2337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL3L7(level -> ((3337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL4L7(level -> ((4337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL5L7(level -> ((5337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL6L7(level -> ((6337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL7L7(level -> ((7337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL8L7(level -> ((8337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL9L7(level -> ((9337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL20L7(level -> ((20337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL40L7(level -> ((40337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL80L7(level -> ((80337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL160L7(level -> ((160337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - XL100L7(level -> ((1000337 * level) + Math.pow(level * 0.95, Math.PI)) / 1.137), - ADAPT_BALANCED(resolved( - level -> 1200 * level + 100 * Math.pow(level, 2), - xp -> (-1200 + Math.sqrt(1440000 + 400 * xp)) / 200, - 0.001 - )), - - LINEAR_EXPONENTIAL_1(resolved(level -> 1000 * level + 100 * Math.pow(level, 2), xp -> { - double a = 1000; - double b = 100; - return (-a + Math.sqrt(a * a + 4 * b * xp)) / (2 * b); - }, 0.001)), - - LINEAR_EXPONENTIAL_2(resolved(level -> 2000 * level + 50 * Math.pow(level, 2.5), xp -> { - double a = 2000; - double b = 50; - double lvl = (-a + Math.sqrt(a * a + 4 * b * xp)) / (2 * b); - return Math.pow((xp - a * lvl) / b, 1 / 2.5); - }, 0.001)), - - LINEAR_EXPONENTIAL_3(resolved(level -> 500 * level + 200 * Math.pow(level, 1.5), xp -> { - double a = 500; - double b = 200; - double lvl = (-a + Math.sqrt(a * a + 4 * b * xp)) / (2 * b); - return Math.pow((xp - a * lvl) / b, 1 / 1.5); - }, 0.001)); - - - @Getter - private final NewtonCurve curve; - - Curves(NewtonCurve curve) { - this.curve = curve; - } - - private static NewtonCurve resolved(Function xpForLevel, Function levelForXP, double maxError) { - return new NewtonCurve() { - @Override - public double getXPForLevel(double level) { - return xpForLevel.apply(level); - } - - @Override - public double computeLevelForXP(double xp, double maxError) { - return levelForXP.apply(xp); - } - }; - } - - public static class SkyrimNewtonCurve { - public static NewtonCurve create() { - Function xpForLevel = level -> { - double f = 0; - for (int i = 1; i < level; i++) { - f += getNextLevelCost(i); - } - return f; - }; - - Function levelForXP = xp -> { - double currentLevel = 1; - while (xp >= getNextLevelCost(currentLevel)) { - xp -= getNextLevelCost(currentLevel); - currentLevel++; - } - return currentLevel; - }; - - return Curves.resolved(xpForLevel, levelForXP, 0.001); - } - - private static double getNextLevelCost(double currentLevel) { - return Math.pow(currentLevel - 1, 1.95) + 300; - } - } - - - public class WOWNewtonCurve { - public static NewtonCurve create() { - Function xpForLevel = level -> { - double f = 0; - for (int i = 1; i < level; i++) { - f += getNextLevelCost(i); - } - return f; - }; - - Function levelForXP = xp -> { - double currentLevel = 1; - while (xp >= getNextLevelCost(currentLevel)) { - xp -= getNextLevelCost(currentLevel); - currentLevel++; - } - return currentLevel; - }; - - return Curves.resolved(xpForLevel, levelForXP, 0.001); - } - - private static double getNextLevelCost(double currentLevel) { - return ((8 * currentLevel) + getDiff(currentLevel)) * getMXP(currentLevel) * getDRF(currentLevel); - } - - private static double getMXP(double currentLevel) { - return 235 + (5 * currentLevel); - } - - private static double getDRF(double currentLevel) { - if (currentLevel >= 11 && currentLevel <= 27) { - return (1 - (currentLevel - 10) / 100); - } - - if (currentLevel >= 28 && currentLevel <= 59) { - return 0.82; - } - - return 1; - } - - private static double getDiff(double currentLevel) { - if (currentLevel <= 28) { - return 0; - } - if (currentLevel == 29) { - return 1; - } - if (currentLevel == 30) { - return 3; - } - if (currentLevel == 31) { - return 6; - } - - return 5 * (Math.min(currentLevel, 59) - 30); - } - } - -} diff --git a/src/main/java/com/volmit/adapt/api/xp/NewtonCurve.java b/src/main/java/com/volmit/adapt/api/xp/NewtonCurve.java deleted file mode 100644 index b3ef54a0c..000000000 --- a/src/main/java/com/volmit/adapt/api/xp/NewtonCurve.java +++ /dev/null @@ -1,60 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.xp; - -import com.volmit.adapt.AdaptConfig; - -@FunctionalInterface -public interface NewtonCurve { - double getXPForLevel(double level); - - default double computeLevelForXP(double xp, double maxError) { - double div = 2; - int iterations = 0; - double jumpSize = 100; - double cursor = 0; - double test; - boolean last = false; - - while (jumpSize > maxError && iterations < 100) { - iterations++; - test = getXPForLevel(cursor); - if (test < xp) { - if (last) { - jumpSize /= div; - } - last = false; - cursor += jumpSize; - } else { - if (!last) { - jumpSize /= div; - } - - last = true; - cursor -= jumpSize; - } - // Check if the level has exceeded the maximum allowed (1000) - if (cursor > AdaptConfig.get().experienceMaxLevel) { - cursor = AdaptConfig.get().experienceMaxLevel; - break; - } - } - return cursor; - } -} diff --git a/src/main/java/com/volmit/adapt/api/xp/SpatialXP.java b/src/main/java/com/volmit/adapt/api/xp/SpatialXP.java deleted file mode 100644 index 514d72829..000000000 --- a/src/main/java/com/volmit/adapt/api/xp/SpatialXP.java +++ /dev/null @@ -1,41 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.xp; - -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.util.M; -import lombok.Data; -import org.bukkit.Location; - -@Data -public class SpatialXP { - private Location location; - private double radius; - private Skill skill; - private double xp; - private long ms; - - public SpatialXP(Location l, Skill s, double xp, double radius, long duration) { - this.location = l; - this.skill = s; - this.xp = xp; - this.ms = M.ms() + duration; - this.radius = radius; - } -} diff --git a/src/main/java/com/volmit/adapt/api/xp/XP.java b/src/main/java/com/volmit/adapt/api/xp/XP.java deleted file mode 100644 index 58a372c97..000000000 --- a/src/main/java/com/volmit/adapt/api/xp/XP.java +++ /dev/null @@ -1,117 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.xp; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.util.M; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -public class XP { - public static void xp(Player p, Skill skill, double xp) { - xp(Adapt.instance.getAdaptServer().getPlayer(p), skill, xp, null); - } - - public static void xp(Player p, Skill skill, double xp, String rewardKey) { - xp(Adapt.instance.getAdaptServer().getPlayer(p), skill, xp, rewardKey); - } - - public static void xp(AdaptPlayer p, Skill skill, double xp) { - xp(p, skill, xp, null); - } - - public static void xp(AdaptPlayer p, Skill skill, double xp, String rewardKey) { - PlayerSkillLine skillLine = p.getSkillLine(skill.getName()); - if (skillLine != null) { - p.getData().resetMonotonyForOtherSkills(skill.getName()); - skillLine.giveXP(p.getNot(), xp, rewardKey); - } - } - - public static void xpSilent(Player p, Skill skill, double xp) { - xpSilent(Adapt.instance.getAdaptServer().getPlayer(p), skill, xp, null); - } - - public static void xpSilent(Player p, Skill skill, double xp, String rewardKey) { - xpSilent(Adapt.instance.getAdaptServer().getPlayer(p), skill, xp, rewardKey); - } - - public static void xpSilent(AdaptPlayer p, Skill skill, double xp) { - xpSilent(p, skill, xp, null); - } - - public static void xpSilent(AdaptPlayer p, Skill skill, double xp, String rewardKey) { - if (p.getSkillLine(skill.getName()) != null) { - p.getData().resetMonotonyForOtherSkills(skill.getName()); - p.getSkillLine(skill.getName()).giveXP(null, xp, rewardKey); - } - } - - public static void spatialXP(Location l, Skill skill, double xp, int rad, long duration) { - Adapt.instance.getAdaptServer().offer(new SpatialXP(l, skill, xp, rad, duration)); - } - - public static void wisdom(Player p, long k) { - wisdom(Adapt.instance.getAdaptServer().getPlayer(p), k); - } - - public static void wisdom(AdaptPlayer p, long k) { - p.getData().setWisdom(p.getData().getWisdom() + k); - } - - public static void knowledge(Player p, Skill skill, long k) { - knowledge(Adapt.instance.getAdaptServer().getPlayer(p), skill, k); - } - - public static void knowledge(AdaptPlayer p, Skill skill, long k) { - p.getSkillLine(skill.getName()).giveKnowledge(k); - } - - public static void boostXP(Player p, Skill skill, double percentChange, int durationMS) { - boostXP(Adapt.instance.getAdaptServer().getPlayer(p), skill, percentChange, durationMS); - } - - public static void boostXP(AdaptPlayer p, Skill skill, double percentChange, int durationMS) { - p.getSkillLine(skill.getName()).boost(percentChange, durationMS); - } - - public static double getXpUntilLevelUp(double xp) { - double level = getLevelForXp(xp); - double xa = getXpForLevel((int) level); - double xb = getXpForLevel((int) level + 1); - return M.lerp(xb - xa, 0, level - (int) level); - } - - public static double getLevelProgress(double xp) { - double level = getLevelForXp(xp); - return level - (int) level; - } - - public static double getXpForLevel(double level) { - return AdaptConfig.get().getXpCurve().getCurve().getXPForLevel(level); - } - - public static double getLevelForXp(double xp) { - return AdaptConfig.get().getXpCurve().getCurve().computeLevelForXP(xp, 0.000001); - } -} diff --git a/src/main/java/com/volmit/adapt/api/xp/XPMultiplier.java b/src/main/java/com/volmit/adapt/api/xp/XPMultiplier.java deleted file mode 100644 index d14d6fa8e..000000000 --- a/src/main/java/com/volmit/adapt/api/xp/XPMultiplier.java +++ /dev/null @@ -1,39 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.api.xp; - -import com.volmit.adapt.util.M; -import lombok.Data; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -@Data -public class XPMultiplier { - private double multiplier = 0D; - private long goodFor = M.ms() + 10000; - - public XPMultiplier(double percentChange, long duration) { - this.multiplier = percentChange; - this.goodFor = M.ms() + duration; - } - - public boolean isExpired() { - return M.ms() > goodFor; - } -} diff --git a/src/main/java/com/volmit/adapt/command/CommandAdapt.java b/src/main/java/com/volmit/adapt/command/CommandAdapt.java deleted file mode 100644 index fed993e02..000000000 --- a/src/main/java/com/volmit/adapt/command/CommandAdapt.java +++ /dev/null @@ -1,333 +0,0 @@ -package com.volmit.adapt.command; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.skill.SkillRegistry; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptServer; -import com.volmit.adapt.api.world.PlayerData; -import com.volmit.adapt.content.gui.ConfigGui; -import com.volmit.adapt.content.gui.SkillsGui; -import com.volmit.adapt.content.item.ExperienceOrb; -import com.volmit.adapt.content.item.KnowledgeOrb; -import com.volmit.adapt.util.command.FConst; -import com.volmit.adapt.util.config.ConfigMigrationManager; -import com.volmit.adapt.util.decree.DecreeExecutor; -import com.volmit.adapt.util.decree.DecreeOrigin; -import com.volmit.adapt.util.decree.annotations.Decree; -import com.volmit.adapt.util.decree.annotations.Param; -import com.volmit.adapt.util.decree.context.AdaptationListingHandler; -import com.volmit.adapt.util.decree.specialhandlers.NullablePlayerHandler; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; - -@Decree(name = "adapt", description = "Basic Command") -public class CommandAdapt implements DecreeExecutor { - private CommandDebug debug; - private CommandClear clear; - private CommandReset reset; - private CommandDefault defaults; - - @Decree(description = "Boost Target player Experience gain.") - public void boost( - @Param(aliases = "seconds", description = "Amount of seconds", defaultValue = "10") - int seconds, - @Param(aliases = "multiplier", description = "Strength of the boost ", defaultValue = "10") - double multiplier, - @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - if (!sender().hasPermission("adapt.boost")) { - FConst.error("You lack the Permission 'adapt.boost'").send(sender()); - return; - } - - Player targetPlayer = player; - if (targetPlayer == null && sender().isConsole()) { - FConst.error("You must specify a player when using this command from console.").send(sender()); - return; - } else if (targetPlayer == null) { - targetPlayer = player(); - } - - AdaptServer adaptServer = Adapt.instance.getAdaptServer(); - PlayerData playerData = adaptServer.getPlayer(targetPlayer).getData(); - playerData.globalXPMultiplier(multiplier, seconds * 1000); - - FConst.success("Boosted XP by " + multiplier + " for " + seconds + " seconds").send(sender()); - } - - @Decree(description = "Boost Global Experience gain.", name = "global-boost") - public void globalBoost( - @Param(aliases = "seconds", description = "Amount of seconds", defaultValue = "10") - int seconds, - @Param(aliases = "multiplier", description = "Strength of the boost ", defaultValue = "10") - double multiplier - ) { - if (!sender().hasPermission("adapt.boost.global")) { - FConst.error("You lack the Permission 'adapt.boost.global'").send(sender()); - return; - } - - AdaptServer adaptServer = Adapt.instance.getAdaptServer(); - adaptServer.boostXP(multiplier, seconds * 1000); - - FConst.success("Boosted XP by " + multiplier + " for " + seconds + " seconds").send(sender()); - } - - @Decree(description = "Open the Adapt GUI") - public void gui( - @Param(aliases = "target", defaultValue = "[Main]") - AdaptationListingHandler.AdaptationList guiTarget, - @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player, - @Param(aliases = "force", defaultValue = "false") - boolean force - ) { - if (!sender().hasPermission("adapt.gui")) { - FConst.error("You lack the Permission 'adapt.gui'").send(sender()); - return; - } - - Player targetPlayer = player; - if (targetPlayer == null && sender().isConsole()) { - FConst.error("You must specify a player when using this command from console.").send(sender()); - return; - } else if (targetPlayer == null) { - targetPlayer = player(); - } - - if (guiTarget.equals("[Main]")) { - SkillsGui.open(targetPlayer); - return; - } - - if (guiTarget.startsWith("[Skill]-")) { - for (Skill skill : SkillRegistry.skills.sortV()) { - if (guiTarget.equals("[Skill]-" + skill.getName())) { - if (force || skill.openGui(targetPlayer, true)) { - FConst.success("Opened GUI for " + skill.getName() + " for " + targetPlayer.getName()).send(sender()); - } else { - FConst.error("Failed to open GUI for " + skill.getName() + " for " + targetPlayer.getName() + " - No Permission, remove from blacklist!").send(sender()); - } - return; - } - } - } - - if (guiTarget.startsWith("[Adaptation]-")) { - for (Skill skill : SkillRegistry.skills.sortV()) { - for (Adaptation adaptation : skill.getAdaptations()) { - if (!adaptation.isEnabled()) { - continue; - } - if (guiTarget.equals("[Adaptation]-" + adaptation.getName())) { - if (force || adaptation.openGui(targetPlayer, true)) { - FConst.success("Opened GUI for " + adaptation.getName() + " for " + targetPlayer.getName()).send(sender()); - } else { - FConst.error("Failed to open GUI for " + adaptation.getName() + " for " + targetPlayer.getName() + " - No Permission, remove from blacklist!").send(sender()); - } - return; - } - } - } - } - } - - @Decree(name = "configure", aliases = {"config", "cfg"}, origin = DecreeOrigin.PLAYER, description = "Open the in-game Adapt config editor") - public void configure() { - if (!ConfigGui.canConfigure(player())) { - FConst.error("You need operator status or the permission 'adapt.configurator'").send(sender()); - return; - } - - ConfigGui.open(player()); - } - - @Decree(description = "Give yourself an experience orb") - public void experience( - @Param(aliases = "skill") - AdaptationListingHandler.AdaptationSkillList skillName, - @Param(aliases = "amount", defaultValue = "10") - int amount, - @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - - ) { - if (!sender().hasPermission("adapt.cheatitem")) { - FConst.error("You lack the Permission 'adapt.cheatitem'").send(sender()); - return; - } - - Player targetPlayer = player; - - if (targetPlayer == null) { - if (sender().isPlayer()) { - targetPlayer = player(); - } else { - FConst.error("You must be a player to use this command, or Reference a player").send(sender()); - return; - } - } - - if (skillName.equals("[all]")) { - Map experienceMap = new HashMap<>(); - for (Skill skill : SkillRegistry.skills.sortV()) { - experienceMap.put(skill.getName(), (double) amount); - } - targetPlayer.getInventory().addItem(ExperienceOrb.with(experienceMap)); - FConst.success("Giving all orbs").send(sender()); - return; - } - - if (skillName.equals("[random]")) { - targetPlayer.getInventory().addItem(ExperienceOrb.with(SkillRegistry.skills.sortV().getRandom().getName(), amount)); - FConst.success("Giving random orb").send(sender()); - return; - } - - Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skillName.name()); - if (skill != null) { - targetPlayer.getInventory().addItem(ExperienceOrb.with(skill.getName(), amount)); - FConst.success("Giving " + skill.getName() + " orb").send(sender()); - } - } - - @Decree(description = "Give yourself a knowledge orb") - public void knowledge( - @Param(aliases = "skill") - AdaptationListingHandler.AdaptationSkillList skillName, - @Param(aliases = "amount", defaultValue = "10") - int amount, - @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - if (!sender().hasPermission("adapt.cheatitem")) { - FConst.error("You lack the Permission 'adapt.cheatitem'").send(sender()); - return; - } - Player targetPlayer = player; - - if(targetPlayer == null){ - if (sender().isPlayer()) { - targetPlayer = player(); - } else { - FConst.error("You must be a player to use this command").send(sender()); - return; - } - } - - if (skillName.equals("[all]")) { - Map knowledgeMap = new HashMap<>(); - for (Skill skill : SkillRegistry.skills.sortV()) { - knowledgeMap.put(skill.getName(), amount); - } - targetPlayer.getInventory().addItem(KnowledgeOrb.with(knowledgeMap)); - FConst.success("Giving all orbs").send(sender()); - return; - } - - if (skillName.equals("[random]")){ - targetPlayer.getInventory().addItem(KnowledgeOrb.with(SkillRegistry.skills.sortV().getRandom().getName(), amount)); - FConst.success("Giving random orb").send(sender()); - return; - } - - Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skillName.name()); - if(skill != null){ - targetPlayer.getInventory().addItem(KnowledgeOrb.with(skill.getName(), amount)); - FConst.success("Giving " + skill.getName() + " orb").send(sender()); - } - } - - @Decree(description = "Assign a skill, or UnAssign a skill as if you are learning / unlearning a skill.") - public void determine( - @Param(aliases = "adaptationTarget") - AdaptationListingHandler.AdaptationProvider adaptationTarget, - @Param(aliases = "assign") - boolean assign, - @Param(aliases = "force") - boolean force, - @Param(aliases = "level") - int level, - @Param(aliases = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - - ) { - if (!sender().hasPermission("adapt.determine")) { - FConst.error("You lack the Permission 'adapt.determine'").send(sender()); - return; - } - - Player targetPlayer = player; - if (targetPlayer == null && sender().isConsole()) { - FConst.error("You must specify a player when using this command from console.").send(sender()); - } else if (targetPlayer == null) { - targetPlayer = player(); - } - - //the format is skillname:adaptationname - String[] split = adaptationTarget.name().split(":"); - String skillname = split[0]; - String adaptationname = split[1]; - - for (Skill skill : SkillRegistry.skills.sortV()) { - if (skill.getName().equalsIgnoreCase(skillname)) { - for (Adaptation adaptation : skill.getAdaptations()) { - if (adaptation.getName().equalsIgnoreCase(adaptationname)) { - if (targetPlayer != null) { - if (assign) { - adaptation.learn(player, level, force); - } else { - adaptation.unlearn(player, level, force); - } - } else { - FConst.error("You must specify a player when using this command from console.").send(sender()); - } - return; - } - } - return; - } - } - } - - @Decree(name = "migrate-configs", description = "Force migrate and rewrite all skill/adaptation configs to canonical TOML with comments.") - public void migrateConfigs() { - if (!sender().hasPermission("adapt.debug")) { - FConst.error("You lack the Permission 'adapt.debug'").send(sender()); - return; - } - - if (Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { - FConst.error("Adapt server is not ready yet. Try again in a few seconds.").send(sender()); - return; - } - - int migratedSkills = 0; - int migratedAdaptations = 0; - for (Skill skill : Adapt.instance.getAdaptServer().getSkillRegistry().getSkills()) { - if (skill instanceof SimpleSkill simpleSkill) { - if (simpleSkill.reloadConfigFromDisk(false)) { - migratedSkills++; - } - } - - for (Adaptation adaptation : skill.getAdaptations()) { - if (adaptation instanceof SimpleAdaptation simpleAdaptation) { - if (simpleAdaptation.reloadConfigFromDisk(false)) { - migratedAdaptations++; - } - } - } - } - - int deletedLegacyJson = ConfigMigrationManager.deleteMigratedLegacyJsonFiles(); - FConst.success("Canonicalized TOML configs. skills=" + migratedSkills + ", adaptations=" + migratedAdaptations + ", deletedLegacyJson=" + deletedLegacyJson).send(sender()); - } - -} diff --git a/src/main/java/com/volmit/adapt/command/CommandClear.java b/src/main/java/com/volmit/adapt/command/CommandClear.java deleted file mode 100644 index 638b7258e..000000000 --- a/src/main/java/com/volmit/adapt/command/CommandClear.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.volmit.adapt.command; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.world.PlayerData; -import com.volmit.adapt.util.command.FConst; -import com.volmit.adapt.util.decree.DecreeExecutor; -import com.volmit.adapt.util.decree.DecreeOrigin; -import com.volmit.adapt.util.decree.annotations.Decree; -import com.volmit.adapt.util.decree.annotations.Param; -import com.volmit.adapt.util.decree.specialhandlers.NullablePlayerHandler; -import org.bukkit.entity.Player; - -@Decree(name = "clear", origin = DecreeOrigin.BOTH, description = "Clear player progression data") -public class CommandClear implements DecreeExecutor { - - @Decree(description = "Clear all player data (XP, knowledge, adaptations, stats, discoveries, advancements, wisdom)") - public void all( - @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - Player targetPlayer = resolveTarget(player); - if (targetPlayer == null) return; - - PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); - data.clearAll(); - FConst.success("Cleared all data for " + targetPlayer.getName()).send(sender()); - } - - @Decree(description = "Clear XP across all skill lines") - public void xp( - @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - Player targetPlayer = resolveTarget(player); - if (targetPlayer == null) return; - - PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); - data.clearXp(); - FConst.success("Cleared XP for " + targetPlayer.getName()).send(sender()); - } - - @Decree(description = "Clear knowledge across all skill lines") - public void knowledge( - @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - Player targetPlayer = resolveTarget(player); - if (targetPlayer == null) return; - - PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); - data.clearKnowledge(); - FConst.success("Cleared knowledge for " + targetPlayer.getName()).send(sender()); - } - - @Decree(description = "Unlearn all adaptations across all skill lines") - public void adaptations( - @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - Player targetPlayer = resolveTarget(player); - if (targetPlayer == null) return; - - PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); - data.clearAdaptations(); - FConst.success("Cleared adaptations for " + targetPlayer.getName()).send(sender()); - } - - @Decree(description = "Clear the stats map") - public void stats( - @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - Player targetPlayer = resolveTarget(player); - if (targetPlayer == null) return; - - PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); - data.clearStats(); - FConst.success("Cleared stats for " + targetPlayer.getName()).send(sender()); - } - - @Decree(description = "Clear all discovery data (biomes, mobs, foods, items, recipes, etc.)") - public void discoveries( - @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - Player targetPlayer = resolveTarget(player); - if (targetPlayer == null) return; - - PlayerData data = Adapt.instance.getAdaptServer().getPlayer(targetPlayer).getData(); - data.clearDiscoveries(); - FConst.success("Cleared discoveries for " + targetPlayer.getName()).send(sender()); - } - - private Player resolveTarget(Player player) { - if (!sender().hasPermission("adapt.clear")) { - FConst.error("You lack the Permission 'adapt.clear'").send(sender()); - return null; - } - - if (player != null) { - return player; - } - - if (sender().isConsole()) { - FConst.error("You must specify a player when using this command from console.").send(sender()); - return null; - } - - return player(); - } -} diff --git a/src/main/java/com/volmit/adapt/command/CommandDebug.java b/src/main/java/com/volmit/adapt/command/CommandDebug.java deleted file mode 100644 index f570f186f..000000000 --- a/src/main/java/com/volmit/adapt/command/CommandDebug.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.volmit.adapt.command; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.command.FConst; -import com.volmit.adapt.util.decree.DecreeExecutor; -import com.volmit.adapt.util.decree.DecreeOrigin; -import com.volmit.adapt.util.decree.annotations.Decree; -import com.volmit.adapt.util.decree.annotations.Param; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Player; - -import java.util.List; - -@Decree(name = "debug", origin = DecreeOrigin.BOTH, description = "Adapt Debug Command", aliases = {"dev"}) -public class CommandDebug implements DecreeExecutor { - - @Decree(description = "Toggle verbose mode") - public void verbose() { - if (!sender().hasPermission("adapt.idontknowwhatimdoingiswear")) { - FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(sender()); - return; - } - - AdaptConfig.get().setVerbose(!AdaptConfig.get().isVerbose()); - FConst.success("Verbose is now " + (AdaptConfig.get().isVerbose() ? "enabled" : "disabled")).send(sender()); - } - - @Decree(name = "pap", description = "Generate Perms for Adaptations!") - public void pap() { - if (!sender().hasPermission("adapt.idontknowwhatimdoingiswear")) { - FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(sender()); - return; - } - - StringBuilder builder = new StringBuilder(); - Adapt.instance.getAdaptServer().getSkillRegistry().getSkills().forEach(skill -> skill.getAdaptations().forEach(adaptation -> builder - .append("adapt.blacklist.") - .append(adaptation.getName() - .replaceAll("-", "")) - .append("\n"))); - Adapt.info("Permissions: \n" + builder); - FConst.success("Permissions have been printed to console.").send(sender()); - } - - @Decree(name = "psp", description = "Generate Perms for Skills!") - public void psp() { - if (!sender().hasPermission("adapt.idontknowwhatimdoingiswear")) { - FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(sender()); - return; - } - - StringBuilder builder = new StringBuilder(); - Adapt.instance.getAdaptServer().getSkillRegistry().getSkills().forEach(skill -> builder - .append("adapt.blacklist.") - .append(skill.getName() - .replaceAll("-", "")) - .append("\n")); - Adapt.info("Permissions: \n" + builder); - FConst.success("Permissions have been printed to console.").send(sender()); - } - - @Decree(name = "particle", origin = DecreeOrigin.PLAYER, description = "Summon a particle in front of you for testing!") - public void particle(@Param Particle particle) { - if (!sender().hasPermission("adapt.idontknowwhatimdoingiswear")) { - FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(sender()); - return; - } - - Player player = player(); - player.spawnParticle(particle, player.getLocation(), 10, 10); - } - - @Decree(name = "particle", origin = DecreeOrigin.PLAYER, description = "Summon a particle in front of you for testing!") - public void particle(@Param Sound sound) { - if (!sender().hasPermission("adapt.idontknowwhatimdoingiswear")) { - FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(sender()); - return; - } - - SoundPlayer sp = SoundPlayer.of(player()); - sp.play(player().getLocation(), sound, 1, 1); - } - - @Decree(description = "Show Adapt ticker hotspots") - public void perf( - @Param(description = "Top results to print", defaultValue = "12") - int top, - @Param(description = "Reset metrics after printing", defaultValue = "false") - boolean reset - ) { - if (!sender().hasPermission("adapt.idontknowwhatimdoingiswear")) { - FConst.error("You lack the Permission 'adapt.idontknowwhatimdoingiswear'").send(sender()); - return; - } - - List lines = Adapt.instance.getTicker().topMetrics(top); - long windowMs = Adapt.instance.getTicker().getMetricsWindowMs(); - FConst.success("Ticker window: " + windowMs + "ms").send(sender()); - if (lines.isEmpty()) { - FConst.success("No tick metrics collected yet.").send(sender()); - } else { - lines.forEach(line -> FConst.info(line).send(sender())); - } - - if (reset) { - Adapt.instance.getTicker().resetMetrics(); - FConst.success("Ticker metrics reset.").send(sender()); - } - } -} diff --git a/src/main/java/com/volmit/adapt/command/CommandDefault.java b/src/main/java/com/volmit/adapt/command/CommandDefault.java deleted file mode 100644 index 72b70cf48..000000000 --- a/src/main/java/com/volmit/adapt/command/CommandDefault.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.volmit.adapt.command; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.util.command.FConst; -import com.volmit.adapt.util.decree.DecreeExecutor; -import com.volmit.adapt.util.decree.DecreeOrigin; -import com.volmit.adapt.util.decree.annotations.Decree; -import com.volmit.adapt.util.decree.annotations.Param; -import com.volmit.adapt.util.decree.context.AdaptationListingHandler; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.text.SimpleDateFormat; -import java.util.Date; - -@Decree(name = "default", origin = DecreeOrigin.BOTH, description = "Reset configs to defaults") -public class CommandDefault implements DecreeExecutor { - - @Decree(description = "Reset a skill config to defaults") - public void skill( - @Param(description = "skill to reset") - AdaptationListingHandler.SkillProvider skillTarget - ) { - if (!sender().isOp()) { - FConst.error("This command can only be run by server operators.").send(sender()); - return; - } - - Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skillTarget.name()); - if (skill == null) { - FConst.error("Unknown skill: " + skillTarget.name()).send(sender()); - return; - } - - if (!(skill instanceof SimpleSkill simpleSkill)) { - FConst.error("Skill " + skill.getName() + " does not support config reset.").send(sender()); - return; - } - - File configFile = Adapt.instance.getDataFile("adapt", "skills", skill.getName() + ".toml"); - if (configFile.exists() && !configFile.delete()) { - FConst.error("Failed to delete config file for " + skill.getName()).send(sender()); - return; - } - - simpleSkill.reloadConfigFromDisk(false); - FConst.success("Reset config for skill " + skill.getName() + " to defaults.").send(sender()); - } - - @Decree(description = "Reset an adaptation config to defaults") - public void adaptation( - @Param(description = "adaptation to reset (skill:adaptation)") - AdaptationListingHandler.AdaptationProvider adaptationTarget - ) { - if (!sender().isOp()) { - FConst.error("This command can only be run by server operators.").send(sender()); - return; - } - - String[] split = adaptationTarget.name().split(":"); - if (split.length != 2) { - FConst.error("Invalid format. Use skill:adaptation").send(sender()); - return; - } - - Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(split[0]); - if (skill == null) { - FConst.error("Unknown skill: " + split[0]).send(sender()); - return; - } - - Adaptation adaptation = null; - for (Adaptation a : skill.getAdaptations()) { - if (a.getName().equalsIgnoreCase(split[1])) { - adaptation = a; - break; - } - } - - if (adaptation == null) { - FConst.error("Unknown adaptation: " + split[1] + " in skill " + skill.getName()).send(sender()); - return; - } - - if (!(adaptation instanceof SimpleAdaptation simpleAdaptation)) { - FConst.error("Adaptation " + adaptation.getName() + " does not support config reset.").send(sender()); - return; - } - - File configFile = Adapt.instance.getDataFile("adapt", "adaptations", adaptation.getName() + ".toml"); - if (configFile.exists() && !configFile.delete()) { - FConst.error("Failed to delete config file for " + adaptation.getName()).send(sender()); - return; - } - - simpleAdaptation.reloadConfigFromDisk(false); - FConst.success("Reset config for adaptation " + adaptation.getName() + " to defaults.").send(sender()); - } - - @Decree(description = "Reset ALL configs to defaults and archive the old settings") - public void all() { - if (!sender().isOp()) { - FConst.error("This command can only be run by server operators.").send(sender()); - return; - } - - String timestamp = new SimpleDateFormat("yyyy-MM-dd_HHmmss").format(new Date()); - File archiveDir = Adapt.instance.getDataFolder("config-archive", timestamp); - - int archived = 0; - int reset = 0; - - // Archive and reset main config - File mainConfig = Adapt.instance.getDataFile("adapt", "adapt.toml"); - if (mainConfig.exists()) { - if (archiveFile(mainConfig, new File(archiveDir, "adapt.toml"))) { - archived++; - } - mainConfig.delete(); - } - - // Archive and reset skill configs - File skillsDir = Adapt.instance.getDataFolder("adapt", "skills"); - if (skillsDir.exists()) { - File archiveSkillsDir = new File(archiveDir, "skills"); - archiveSkillsDir.mkdirs(); - File[] skillFiles = skillsDir.listFiles((dir, name) -> name.endsWith(".toml")); - if (skillFiles != null) { - for (File f : skillFiles) { - if (archiveFile(f, new File(archiveSkillsDir, f.getName()))) { - archived++; - } - f.delete(); - } - } - } - - // Archive and reset adaptation configs - File adaptationsDir = Adapt.instance.getDataFolder("adapt", "adaptations"); - if (adaptationsDir.exists()) { - File archiveAdaptationsDir = new File(archiveDir, "adaptations"); - archiveAdaptationsDir.mkdirs(); - File[] adaptationFiles = adaptationsDir.listFiles((dir, name) -> name.endsWith(".toml")); - if (adaptationFiles != null) { - for (File f : adaptationFiles) { - if (archiveFile(f, new File(archiveAdaptationsDir, f.getName()))) { - archived++; - } - f.delete(); - } - } - } - - // Reload main config from defaults - AdaptConfig.reload(); - - // Reload all skill and adaptation configs from defaults - for (Skill skill : Adapt.instance.getAdaptServer().getSkillRegistry().getSkills()) { - if (skill instanceof SimpleSkill simpleSkill) { - if (simpleSkill.reloadConfigFromDisk(false)) { - reset++; - } - } - - for (Adaptation adaptation : skill.getAdaptations()) { - if (adaptation instanceof SimpleAdaptation simpleAdaptation) { - if (simpleAdaptation.reloadConfigFromDisk(false)) { - reset++; - } - } - } - } - - FConst.success("Archived " + archived + " config files to config-archive/" + timestamp + "/").send(sender()); - FConst.success("Reset " + reset + " configs to defaults.").send(sender()); - } - - private boolean archiveFile(File source, File destination) { - try { - destination.getParentFile().mkdirs(); - Files.copy(source.toPath(), destination.toPath(), StandardCopyOption.REPLACE_EXISTING); - return true; - } catch (IOException e) { - Adapt.warn("Failed to archive " + source.getPath() + ": " + e.getMessage()); - return false; - } - } -} diff --git a/src/main/java/com/volmit/adapt/command/CommandReset.java b/src/main/java/com/volmit/adapt/command/CommandReset.java deleted file mode 100644 index c209611e2..000000000 --- a/src/main/java/com/volmit/adapt/command/CommandReset.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.volmit.adapt.command; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.util.command.FConst; -import com.volmit.adapt.util.decree.DecreeExecutor; -import com.volmit.adapt.util.decree.DecreeOrigin; -import com.volmit.adapt.util.decree.annotations.Decree; -import com.volmit.adapt.util.decree.annotations.Param; -import com.volmit.adapt.util.decree.specialhandlers.NullablePlayerHandler; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -@Decree(name = "reset", origin = DecreeOrigin.BOTH, description = "Permanently delete all Adapt data for a player") -public class CommandReset implements DecreeExecutor { - private static final Map pendingConfirmations = new HashMap<>(); - private static final long CONFIRMATION_TIMEOUT_MS = 30_000; - - @Decree(description = "Permanently delete all Adapt data for a player. Requires op. Run twice to confirm.") - public void confirm( - @Param(description = "player", defaultValue = "---", customHandler = NullablePlayerHandler.class) - Player player - ) { - if (!sender().isOp()) { - FConst.error("This command can only be run by server operators.").send(sender()); - return; - } - - Player targetPlayer = player; - if (targetPlayer == null && sender().isConsole()) { - FConst.error("You must specify a player when using this command from console.").send(sender()); - return; - } else if (targetPlayer == null) { - targetPlayer = player(); - } - - UUID senderUuid = sender().isPlayer() ? player().getUniqueId() : new UUID(0, 0); - UUID targetUuid = targetPlayer.getUniqueId(); - long now = System.currentTimeMillis(); - - PendingReset pending = pendingConfirmations.get(senderUuid); - if (pending != null && pending.targetUuid.equals(targetUuid) && now - pending.timestamp < CONFIRMATION_TIMEOUT_MS) { - pendingConfirmations.remove(senderUuid); - - AdaptPlayer adaptPlayer = Adapt.instance.getAdaptServer().getPlayer(targetPlayer); - adaptPlayer.delete(targetUuid); - Adapt.info("Operator " + sender().getName() + " reset all Adapt data for " + targetPlayer.getName()); - FConst.success("All Adapt data for " + targetPlayer.getName() + " has been permanently deleted.").send(sender()); - return; - } - - pendingConfirmations.put(senderUuid, new PendingReset(targetUuid, now)); - FConst.error("WARNING: This will permanently delete ALL Adapt data for " + targetPlayer.getName() + ".").send(sender()); - FConst.error("This includes XP, skills, adaptations, discoveries, stats, and advancements.").send(sender()); - FConst.error("Run this command again within 30 seconds to confirm.").send(sender()); - } - - private static class PendingReset { - final UUID targetUuid; - final long timestamp; - - PendingReset(UUID targetUuid, long timestamp) { - this.targetUuid = targetUuid; - this.timestamp = timestamp; - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityArmorUp.java b/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityArmorUp.java deleted file mode 100644 index 0282b54b7..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityArmorUp.java +++ /dev/null @@ -1,192 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.agility; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Attributes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.Particle; -import org.bukkit.attribute.AttributeModifier; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerQuitEvent; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class AgilityArmorUp extends SimpleAdaptation { - private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-armor-up".getBytes()); - private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString( "adapt:armor-up"); - private final Map ticksRunning; - - - public AgilityArmorUp() { - super("agility-armor-up"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("agility.armor_up.description")); - setIcon(Material.IRON_CHESTPLATE); - setDisplayName(Localizer.dLocalize("agility.armor_up.name")); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setInitialCost(getConfig().initialCost); - setInterval(350); - ticksRunning = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_CHESTPLATE) - .key("challenge_agility_armor_up_30min") - .title(Localizer.dLocalize("advancement.challenge_agility_armor_up_30min.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_armor_up_30min.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_CHESTPLATE) - .key("challenge_agility_armor_up_5hr") - .title(Localizer.dLocalize("advancement.challenge_agility_armor_up_5hr.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_armor_up_5hr.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_agility_armor_up_30min", "agility.armor-up.ticks-armored", 36000, 500); - registerMilestone("challenge_agility_armor_up_5hr", "agility.armor-up.ticks-armored", 360000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getWindupArmor(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("agility.armor_up.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getWindupTicks(getLevelPercent(level)) * 50D, 1) + " " + C.GRAY + Localizer.dLocalize("agility.armor_up.lore2")); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - ticksRunning.remove(p); - } - - private double getWindupTicks(double factor) { - return M.lerp(getConfig().windupTicksSlowest, getConfig().windupTicksFastest, factor); - } - - private double getWindupArmor(double factor) { - return getConfig().windupArmorBase + (factor * getConfig().windupArmorLevelMultiplier); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - var attribute = Version.get().getAttribute(p, Attributes.GENERIC_ARMOR); - if (attribute == null) continue; - - try { - attribute.removeModifier(MODIFIER, MODIFIER_KEY); - } catch (Exception e) { - Adapt.verbose("Failed to remove windup modifier: " + e.getMessage()); - } - - if (p.isSwimming() || p.isFlying() || p.isGliding() || p.isSneaking()) { - ticksRunning.remove(p); - continue; - } - - if (p.isSprinting() && hasAdaptation(p)) { - ticksRunning.compute(p, (k, v) -> { - if (v == null) { - return 1; - } - return v + 1; - }); - - Integer tr = ticksRunning.get(p); - - if (tr == null || tr <= 0) { - continue; - } - - double factor = getLevelPercent(p); - double ticksToMax = getWindupTicks(factor); - double progress = Math.min(M.lerpInverse(0, ticksToMax, tr), 1); - double armorInc = M.lerp(0, getWindupArmor(factor), progress); - - if (areParticlesEnabled()) { - if (M.r(0.2 * progress)) { - p.getWorld().spawnParticle(Particle.END_ROD, p.getLocation(), 1); - } - - if (M.r(0.25 * progress)) { - p.getWorld().spawnParticle(Particle.WAX_ON, p.getLocation(), 1, 0, 0, 0, 0); - } - } - attribute.setModifier(MODIFIER, MODIFIER_KEY, armorInc * 10, AttributeModifier.Operation.MULTIPLY_SCALAR_1); - getPlayer(p).getData().addStat("agility.armor-up.ticks-armored", 1); - } else { - ticksRunning.remove(p); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - - @NoArgsConstructor - @ConfigDescription("Gain more armor the longer you sprint.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Agility Armor Up adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Windup Ticks Slowest for the Agility Armor Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windupTicksSlowest = 180; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Windup Ticks Fastest for the Agility Armor Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windupTicksFastest = 60; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Windup Armor Base for the Agility Armor Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windupArmorBase = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Windup Armor Level Multiplier for the Agility Armor Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windupArmorLevelMultiplier = 0.525; - } - -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityLadderSlide.java b/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityLadderSlide.java deleted file mode 100644 index e7f5900f7..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityLadderSlide.java +++ /dev/null @@ -1,280 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.agility; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class AgilityLadderSlide extends SimpleAdaptation { - private final Map upwardStates; - - public AgilityLadderSlide() { - super("agility-ladder-slide"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("agility.ladder_slide.description")); - setDisplayName(Localizer.dLocalize("agility.ladder_slide.name")); - setIcon(Material.LADDER); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(50); - upwardStates = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LADDER) - .key("challenge_agility_ladder_500") - .title(Localizer.dLocalize("advancement.challenge_agility_ladder_500.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_ladder_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_CHAIN) - .key("challenge_agility_ladder_10k") - .title(Localizer.dLocalize("advancement.challenge_agility_ladder_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_ladder_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_agility_ladder_500", "agility.ladder-slide.blocks-climbed", 500, 300); - registerMilestone("challenge_agility_ladder_10k", "agility.ladder-slide.blocks-climbed", 10000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getConfig().speedMultiplier, 1) + "x " + C.GRAY + Localizer.dLocalize("agility.ladder_slide.lore1")); - v.addLore(C.YELLOW + "Downward speed boost coming in a future update."); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerMoveEvent e) { - if (e.getTo() == null) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || p.isFlying() || p.isGliding() || p.isSwimming()) { - clearUpwardState(p); - return; - } - - Location location = p.getLocation(); - if (!canInteract(p, location)) { - clearUpwardState(p); - return; - } - - Block activeLadder = getActiveLadderBlock(location); - if (activeLadder == null) { - clearUpwardState(p); - return; - } - - double dy = e.getTo().getY() - e.getFrom().getY(); - boolean lookingUp = p.getLocation().getPitch() <= -Math.abs(getConfig().lookUpPitchThreshold); - if (!lookingUp) { - clearUpwardState(p); - return; - } - - double epsilon = Math.abs(getConfig().movementDirectionEpsilonUpward); - Vector velocity = p.getVelocity(); - if (p.isSneaking()) { - clearUpwardState(p); - applyVerticalVelocity(p, velocity, 0); - return; - } - - boolean movingUp = dy > epsilon; - if (!movingUp) { - clearUpwardState(p); - return; - } - - double baseUp = Math.max(0, getConfig().normalUpwardLadderSpeed); - double targetUp = isNearLadderEnd(activeLadder, true) ? baseUp : getUpwardSpeed(); - applySmoothUpwardVelocity(p, velocity, baseUp, targetUp); - p.setFallDistance(0); - getPlayer(p).getData().addStat("agility.ladder-slide.blocks-climbed", 1); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - clearUpwardState(e.getPlayer()); - } - - private void applySmoothUpwardVelocity(Player p, Vector velocity, double baseUpwardSpeed, double targetUpwardSpeed) { - double minUp = Math.max(0, baseUpwardSpeed); - double target = Math.max(minUp, targetUpwardSpeed); - long now = System.currentTimeMillis(); - UpwardState state = upwardStates.get(p.getUniqueId()); - if (state == null || now - state.lastSeenAt > Math.max(0, getConfig().upwardStateResetMs)) { - state = new UpwardState(Math.max(minUp, Math.max(0, velocity.getY())), now); - upwardStates.put(p.getUniqueId(), state); - } - - double smoothing = clamp(getConfig().upwardAccelerationSmoothing, 0.01, 1.0); - double current = Math.max(minUp, state.currentSpeed); - double next = current + ((target - current) * smoothing); - state.currentSpeed = Math.min(target, Math.max(minUp, next)); - state.lastSeenAt = now; - applyVerticalVelocity(p, velocity, state.currentSpeed); - } - - private void clearUpwardState(Player p) { - if (p == null) { - return; - } - upwardStates.remove(p.getUniqueId()); - } - - private double clamp(double v, double min, double max) { - return Math.max(min, Math.min(max, v)); - } - - private void applyVerticalVelocity(Player p, Vector velocity, double targetY) { - if (Math.abs(velocity.getY() - targetY) <= getConfig().velocityEpsilon) { - return; - } - - p.setVelocity(velocity.setY(targetY)); - } - - private Block getActiveLadderBlock(Location location) { - Block feet = location.getBlock(); - if (feet.getType() == Material.LADDER) { - return feet; - } - - Block head = location.clone().add(0, 1, 0).getBlock(); - if (head.getType() == Material.LADDER) { - return head; - } - - return null; - } - - private boolean isNearLadderEnd(Block ladder, boolean upward) { - int laddersAhead = 0; - Block cursor = ladder; - BlockFace direction = upward ? BlockFace.UP : BlockFace.DOWN; - int limit = Math.max(1, getConfig().maxLadderScanDistance); - for (int i = 0; i < limit; i++) { - cursor = cursor.getRelative(direction); - if (cursor.getType() != Material.LADDER) { - break; - } - - laddersAhead++; - if (laddersAhead > getConfig().revertDistanceBlocks) { - return false; - } - } - - return laddersAhead <= getConfig().revertDistanceBlocks; - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Climb and slide ladders much faster in both directions.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Multiplier applied to baseUpwardLadderSpeed to compute the target climb speed.", impact = "Higher values increase final ladder climb speed after the ramp-up phase.") - double speedMultiplier = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Velocity difference threshold used to skip tiny Y-velocity adjustments.", impact = "Lower values apply more frequent micro-updates; higher values reduce minor velocity writes.") - double velocityEpsilon = 0.003; - @com.volmit.adapt.util.config.ConfigDoc(value = "Baseline climb speed used before the speed multiplier is applied.", impact = "Higher values raise the base climb profile and increase total ladder ascent speed.") - double baseUpwardLadderSpeed = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Vanilla-like upward speed used near ladder endpoints to avoid overshooting.", impact = "Higher values make endpoint climbing snappier; lower values keep transitions conservative.") - double normalUpwardLadderSpeed = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum positive Y movement treated as intentional upward ladder motion.", impact = "Lower values are more sensitive to slight upward input; higher values require clearer upward movement.") - double movementDirectionEpsilonUpward = 0.0004; - @com.volmit.adapt.util.config.ConfigDoc(value = "Smoothing factor for blending current upward velocity toward target ladder speed.", impact = "Values near 1.0 ramp quickly; lower values create a slower curve-like acceleration profile.") - double upwardAccelerationSmoothing = 0.28; - @com.volmit.adapt.util.config.ConfigDoc(value = "How long to retain previous upward speed state between ladder movement samples.", impact = "Lower values reset ramp-up sooner; higher values preserve momentum between short interruptions.") - long upwardStateResetMs = 200; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum upward look angle required to activate upward ladder acceleration.", impact = "Larger values require players to look farther upward before acceleration engages.") - double lookUpPitchThreshold = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Distance from ladder top where motion reverts toward normal upward speed.", impact = "Higher values begin fallback earlier near ladder ends; lower values keep boosted speed longer.") - int revertDistanceBlocks = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum blocks scanned to detect ladder continuity when checking ladder endpoints.", impact = "Higher values support taller ladders at slightly higher per-check cost.") - int maxLadderScanDistance = 64; - } - - private double getUpwardSpeed() { - return getConfig().baseUpwardLadderSpeed * getConfig().speedMultiplier; - } - - private static class UpwardState { - private double currentSpeed; - private long lastSeenAt; - - private UpwardState(double currentSpeed, long lastSeenAt) { - this.currentSpeed = currentSpeed; - this.lastSeenAt = lastSeenAt; - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityParkourMomentum.java b/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityParkourMomentum.java deleted file mode 100644 index 8911d4d30..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityParkourMomentum.java +++ /dev/null @@ -1,351 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.agility; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.VelocitySpeed; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class AgilityParkourMomentum extends SimpleAdaptation { - private final Map momentum = new HashMap<>(); - private final Map wasOnGround = new HashMap<>(); - private final Map speedBoosting = new HashMap<>(); - - public AgilityParkourMomentum() { - super("agility-parkour-momentum"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("agility.parkour_momentum.description")); - setDisplayName(Localizer.dLocalize("agility.parkour_momentum.name")); - setIcon(Material.RABBIT_FOOT); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(10); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.RABBIT_FOOT) - .key("challenge_agility_parkour_500") - .title(Localizer.dLocalize("advancement.challenge_agility_parkour_500.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_parkour_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_agility_parkour_500", "agility.parkour-momentum.ledge-landings", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getMaxMomentum(level) + C.GRAY + " " + Localizer.dLocalize("agility.parkour_momentum.lore1")); - v.addLore(C.GREEN + "+ " + getMaxSpeedAmplifier(level) + C.GRAY + " " + Localizer.dLocalize("agility.parkour_momentum.lore2")); - v.addLore(C.GREEN + "+ " + getMaxJumpAmplifier(level) + C.GRAY + " " + Localizer.dLocalize("agility.parkour_momentum.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - UUID id = e.getPlayer().getUniqueId(); - momentum.remove(id); - wasOnGround.remove(id); - speedBoosting.remove(id); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(PlayerMoveEvent e) { - if (e.getTo() == null || e.getFrom().distanceSquared(e.getTo()) < getConfig().minimumMoveSquared) { - return; - } - - Player p = e.getPlayer(); - UUID id = p.getUniqueId(); - if (!hasAdaptation(p)) { - momentum.remove(id); - wasOnGround.remove(id); - return; - } - - boolean onGroundNow = p.isOnGround(); - boolean onGroundBefore = wasOnGround.getOrDefault(id, onGroundNow); - int current = momentum.getOrDefault(id, 0); - - if (!onGroundBefore && onGroundNow) { - if (isMomentumLanding(p) && isOnLedge(p)) { - current += getConfig().landingGain; - getPlayer(p).getData().addStat("agility.parkour-momentum.ledge-landings", 1); - } else { - current -= getConfig().failedLandingPenalty; - } - } else if (onGroundNow && !p.isSprinting()) { - current -= getConfig().groundDecayOnMove; - } - - current = clampMomentum(current, getMaxMomentum(getLevel(p))); - momentum.put(id, current); - wasOnGround.put(id, onGroundNow); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - UUID id = p.getUniqueId(); - if (!hasAdaptation(p)) { - momentum.remove(id); - wasOnGround.remove(id); - invalidateMomentumSpeed(p, id, true); - continue; - } - - int level = getLevel(p); - int maxMomentum = getMaxMomentum(level); - int current = momentum.getOrDefault(id, 0); - if (current <= 0) { - invalidateMomentumSpeed(p, id, false); - continue; - } - - if (p.isOnGround() && !isOnLedge(p)) { - current -= getConfig().offLedgeDecayPerTick; - momentum.put(id, clampMomentum(current, maxMomentum)); - brakeMomentumSpeed(p, id); - continue; - } - - int speedAmp = Math.max(0, Math.min(getMaxSpeedAmplifier(level), (int) Math.floor((current / (double) maxMomentum) * (getMaxSpeedAmplifier(level) + 1)) - 1)); - int jumpAmp = Math.max(0, Math.min(getMaxJumpAmplifier(level), (int) Math.floor((current / (double) maxMomentum) * (getMaxJumpAmplifier(level) + 1)) - 1)); - - p.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, 25, jumpAmp, false, false)); - - if (speedAmp <= 0) { - brakeMomentumSpeed(p, id); - } else if (!isVelocityEligible(p)) { - invalidateMomentumSpeed(p, id, true); - } else { - VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); - if (!input.hasHorizontal()) { - brakeMomentumSpeed(p, id); - } else { - applyMomentumSpeed(p, id, input, speedAmp); - } - } - - if (p.isOnGround() && !p.isSprinting()) { - current -= getConfig().passiveGroundDecayPerTick; - } - - momentum.put(id, clampMomentum(current, maxMomentum)); - } - } - - private void applyMomentumSpeed(Player p, UUID id, VelocitySpeed.InputSnapshot input, int speedAmp) { - Vector direction = VelocitySpeed.resolveHorizontalDirection(p, input); - if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { - brakeMomentumSpeed(p, id); - return; - } - - double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, - Math.max(0, getConfig().baseHorizontalSpeed * VelocitySpeed.speedAmplifierScalar(speedAmp))); - Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); - Vector targetHorizontal = direction.multiply(targetSpeed); - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); - nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - speedBoosting.put(id, true); - } - - private void brakeMomentumSpeed(Player p, UUID id) { - if (!speedBoosting.getOrDefault(id, false)) { - return; - } - - Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); - double stopThreshold = Math.max(0, getConfig().stopThreshold); - if (horizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - speedBoosting.put(id, false); - return; - } - - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); - if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - speedBoosting.put(id, false); - return; - } - - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - } - - private void invalidateMomentumSpeed(Player p, UUID id, boolean invalidState) { - if (!speedBoosting.getOrDefault(id, false)) { - return; - } - - if (invalidState && getConfig().hardStopOnInvalidState) { - VelocitySpeed.hardStopHorizontal(p); - } - - speedBoosting.put(id, false); - } - - private boolean isVelocityEligible(Player p) { - GameMode mode = p.getGameMode(); - if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { - return false; - } - - return !p.isDead() && !p.isFlying() && !p.isGliding() && !p.isSwimming() && p.getVehicle() == null; - } - - private boolean isMomentumLanding(Player p) { - return p.isSprinting() && !p.isSwimming() && !p.isGliding() && !p.isFlying(); - } - - private boolean isOnLedge(Player p) { - if (!p.isOnGround()) { - return false; - } - - Block feet = p.getLocation().getBlock(); - Block below = feet.getRelative(BlockFace.DOWN); - if (!below.getType().isSolid()) { - return false; - } - - BlockFace[] sides = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}; - for (BlockFace side : sides) { - Block sideAtFeet = feet.getRelative(side); - Block sideBelow = below.getRelative(side); - if (!sideAtFeet.getType().isSolid() && !sideBelow.getType().isSolid()) { - return true; - } - } - - return false; - } - - private int clampMomentum(int value, int max) { - return Math.max(0, Math.min(max, value)); - } - - private int getMaxMomentum(int level) { - return Math.max(3, (int) Math.round(getConfig().momentumBase + (getLevelPercent(level) * getConfig().momentumFactor))); - } - - private int getMaxSpeedAmplifier(int level) { - return Math.max(0, (int) Math.round(getConfig().speedAmplifierBase + (getLevelPercent(level) * getConfig().speedAmplifierFactor))); - } - - private int getMaxJumpAmplifier(int level) { - return Math.max(0, (int) Math.round(getConfig().jumpAmplifierBase + (getLevelPercent(level) * getConfig().jumpAmplifierFactor))); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Build momentum by chaining sprint-jumps and landings to gain speed and jump boosts.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Momentum Base for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double momentumBase = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Momentum Factor for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double momentumFactor = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Amplifier Base for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedAmplifierBase = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Amplifier Factor for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedAmplifierFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Jump Amplifier Base for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double jumpAmplifierBase = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Jump Amplifier Factor for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double jumpAmplifierFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Landing Gain for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int landingGain = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Failed Landing Penalty for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int failedLandingPenalty = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ground Decay On Move for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int groundDecayOnMove = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Passive Ground Decay Per Tick for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int passiveGroundDecayPerTick = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Off Ledge Decay Per Tick for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int offLedgeDecayPerTick = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Move Squared for the Agility Parkour Momentum adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minimumMoveSquared = 0.0025; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for momentum velocity scaling.", impact = "Higher values increase movement speed when momentum speed is active.") - double baseHorizontalSpeed = 0.13; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent excessive momentum carry.") - double maxHorizontalSpeed = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the momentum target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") - double accelPerTick = 0.04; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity decays when movement input is released.", impact = "Higher values stop faster and reduce carry momentum.") - double brakePerTick = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny motion longer.") - double stopThreshold = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "If true, speed velocity is force-cleared when entering invalid states.", impact = "Prevents retained boosts if state transitions skip expected checks.") - boolean hardStopOnInvalidState = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") - double fallbackInputVelocityThreshold = 0.0008; - - double fallbackInputVelocityThresholdSquared() { - double threshold = Math.max(0, fallbackInputVelocityThreshold); - return threshold * threshold; - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityRollLanding.java b/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityRollLanding.java deleted file mode 100644 index 2fe48fc4e..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityRollLanding.java +++ /dev/null @@ -1,288 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.agility; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class AgilityRollLanding extends SimpleAdaptation { - private final Map rollInputs = new HashMap<>(); - private final Map proneUntilMillis = new HashMap<>(); - - public AgilityRollLanding() { - super("agility-roll-landing"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("agility.roll_landing.description")); - setDisplayName(Localizer.dLocalize("agility.roll_landing.name")); - setIcon(Material.HAY_BLOCK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1200); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.HAY_BLOCK) - .key("challenge_agility_roll_100") - .title(Localizer.dLocalize("advancement.challenge_agility_roll_100.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_roll_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SLIME_BLOCK) - .key("challenge_agility_roll_1000") - .title(Localizer.dLocalize("advancement.challenge_agility_roll_1000.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_roll_1000.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ELYTRA) - .key("challenge_agility_fearless") - .title(Localizer.dLocalize("advancement.challenge_agility_fearless.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_fearless.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.HIDDEN) - .build()); - registerMilestone("challenge_agility_roll_100", "agility.roll-landing.damage-prevented", 100, 300); - registerMilestone("challenge_agility_roll_1000", "agility.roll-landing.damage-prevented", 1000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getFallReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("agility.roll_landing.lore1")); - v.addLore(C.GREEN + "+ " + Form.duration(getInputWindowMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("agility.roll_landing.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("agility.roll_landing.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - rollInputs.remove(e.getPlayer().getUniqueId()); - proneUntilMillis.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(PlayerToggleSneakEvent e) { - Player p = e.getPlayer(); - if (e.isSneaking()) { - recordRollInput(p, null, null); - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(PlayerMoveEvent e) { - recordRollInput(e.getPlayer(), e.getFrom().getY(), e.getTo() == null ? null : e.getTo().getY()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageEvent e) { - if (e.isCancelled() || !(e.getEntity() instanceof Player p) || e.getCause() != EntityDamageEvent.DamageCause.FALL) { - return; - } - - if (!hasAdaptation(p) || p.hasCooldown(Material.HAY_BLOCK)) { - return; - } - - long now = System.currentTimeMillis(); - long input = rollInputs.getOrDefault(p.getUniqueId(), 0L); - int level = getLevel(p); - if (now - input > getInputWindowMillis(level)) { - return; - } - - double absorbCap = e.getDamage() * getFallReduction(level); - int hungerNeeded = (int) Math.ceil(absorbCap * getHungerPerDamage(level)); - if (hungerNeeded <= 0 || p.getFoodLevel() <= 0) { - return; - } - - int usableFood = Math.min(p.getFoodLevel(), hungerNeeded); - double absorbed = usableFood / getHungerPerDamage(level); - if (absorbed <= 0) { - return; - } - - p.setFoodLevel(Math.max(0, p.getFoodLevel() - usableFood)); - e.setDamage(Math.max(0, e.getDamage() - absorbed)); - if (e.getDamage() <= 0.01) { - e.setCancelled(true); - } - - p.setCooldown(Material.HAY_BLOCK, getCooldownTicks(level)); - triggerRollPose(p, level); - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_PLAYER_SMALL_FALL, 0.8f, 0.7f); - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1.0f, 0.89f); - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_WOOL_BREAK, 0.55f, 0.9f); - getPlayer(p).getData().addStat("agility.roll-landing.damage-prevented", absorbed); - if (p.getFallDistance() >= 30 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_agility_fearless")) { - getPlayer(p).getAdvancementHandler().grant("challenge_agility_fearless"); - } - xp(p, absorbed * getConfig().xpPerDamagePrevented); - } - - private void recordRollInput(Player p, Double fromY, Double toY) { - if (!hasAdaptation(p) || !p.isSneaking()) { - return; - } - - if (p.isOnGround() || p.isFlying() || p.isGliding()) { - return; - } - - boolean descending = p.getVelocity().getY() <= getConfig().maxVerticalVelocityForRollInput; - if (!descending && fromY != null && toY != null) { - descending = toY < fromY; - } - - if (!descending) { - return; - } - - rollInputs.put(p.getUniqueId(), System.currentTimeMillis()); - } - - private void triggerRollPose(Player p, int level) { - int proneTicks = getProneTicks(level); - long until = System.currentTimeMillis() + (proneTicks * 50L); - UUID id = p.getUniqueId(); - proneUntilMillis.put(id, until); - p.setSwimming(true); - - J.s(() -> { - if (!p.isOnline() || p.isDead()) { - proneUntilMillis.remove(id); - return; - } - - long expectedUntil = proneUntilMillis.getOrDefault(id, 0L); - if (expectedUntil > System.currentTimeMillis()) { - return; - } - - proneUntilMillis.remove(id); - if (!p.isInWater()) { - p.setSwimming(false); - } - }, proneTicks); - } - - private double getFallReduction(int level) { - return Math.min(getConfig().maxReduction, getConfig().reductionBase + (getLevelPercent(level) * getConfig().reductionFactor)); - } - - private long getInputWindowMillis(int level) { - return Math.max(60L, Math.round(getConfig().inputWindowMillisBase + (getLevelPercent(level) * getConfig().inputWindowMillisFactor))); - } - - private double getHungerPerDamage(int level) { - return Math.max(0.1, getConfig().hungerPerDamageBase - (getLevelPercent(level) * getConfig().hungerPerDamageReduction)); - } - - private int getCooldownTicks(int level) { - return Math.max(4, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - private int getProneTicks(int level) { - return Math.max(2, (int) Math.round(getConfig().proneTicksBase + (getLevelPercent(level) * getConfig().proneTicksFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Timed sneak before landing converts part of fall damage into hunger cost.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.62; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reduction Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reductionBase = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reduction Factor for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reductionFactor = 0.43; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Reduction for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxReduction = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Input Window Millis Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double inputWindowMillisBase = 190; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Input Window Millis Factor for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double inputWindowMillisFactor = 260; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hunger Per Damage Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hungerPerDamageBase = 1.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hunger Per Damage Reduction for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hungerPerDamageReduction = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Vertical Velocity For Roll Input for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxVerticalVelocityForRollInput = -0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Prone Ticks Base for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double proneTicksBase = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Prone Ticks Factor for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double proneTicksFactor = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Damage Prevented for the Agility Roll Landing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerDamagePrevented = 4.2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilitySuperJump.java b/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilitySuperJump.java deleted file mode 100644 index 252ac687b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilitySuperJump.java +++ /dev/null @@ -1,192 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.agility; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Particles; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; - - -public class AgilitySuperJump extends SimpleAdaptation { - private final Map lastJump; - - public AgilitySuperJump() { - super("agility-super-jump"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("agility.super_jump.description")); - setDisplayName(Localizer.dLocalize("agility.super_jump.name")); - setIcon(Material.LEATHER_BOOTS); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(9999); - lastJump = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEATHER_BOOTS) - .key("challenge_agility_super_jump_100") - .title(Localizer.dLocalize("advancement.challenge_agility_super_jump_100.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_super_jump_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GOLDEN_BOOTS) - .key("challenge_agility_super_jump_5k") - .title(Localizer.dLocalize("advancement.challenge_agility_super_jump_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_super_jump_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_agility_super_jump_100", "agility.super-jump.jumps", 100, 300); - registerMilestone("challenge_agility_super_jump_5k", "agility.super-jump.jumps", 5000, 1500); - } - - private double getJumpHeight(int level) { - return getConfig().baseJumpMultiplier + (getConfig().jumpLevelMultiplier * level); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getJumpHeight(level), 0) + C.GRAY + " " + Localizer.dLocalize("agility.super_jump.lore1")); - v.addLore(C.LIGHT_PURPLE + " " + Localizer.dLocalize("agility.super_jump.lore2")); - - } - - @EventHandler - public void on(PlayerToggleSneakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - - if (e.isSneaking() && p.isOnGround()) { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 0.3f, 0.35f); - } - } - - @EventHandler - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - lastJump.remove(p); - } - - @EventHandler - public void on(PlayerMoveEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (p.isSwimming() || p.isFlying() || p.isGliding() || p.isSprinting()) { - return; - } - - if (p.isSneaking() && hasAdaptation(p) && canUse(getPlayer(p))) { - Vector velocity = p.getVelocity(); - - if (velocity.getY() > 0) { - double jumpVelocity = 0.4; - PotionEffect jumpPotion = p.getPotionEffect(PotionEffectTypes.JUMP); - - if (jumpPotion != null) { - jumpVelocity += (double) ((float) jumpPotion.getAmplifier() + 1) * 0.1F; - } - - if (lastJump.get(p) != null && M.ms() - lastJump.get(p) < 1000) { - return; - } else if (lastJump.get(p) != null && M.ms() - lastJump.get(p) > 1500) { - lastJump.remove(p); - } - if (p.getLocation().getBlock().getType() != Material.LADDER && velocity.getY() > jumpVelocity && p.isOnline()) { - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1.25f, 0.7f); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1.25f, 1.7f); - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particles.BLOCK_CRACK, p.getLocation().clone().add(0, 0.3, 0), 15, 0.1, 0.8, 0.1, 0.1, p.getLocation().getBlock().getRelative(BlockFace.DOWN).getBlockData()); - } - p.setVelocity(p.getVelocity().setY(getJumpHeight(getLevel(p)))); - lastJump.put(p, M.ms()); - getPlayer(p).getData().addStat("agility.super-jump.jumps", 1); - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak and jump for exceptional height advantage.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Agility Super Jump adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.55; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Jump Multiplier for the Agility Super Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseJumpMultiplier = 0.23; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Jump Level Multiplier for the Agility Super Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double jumpLevelMultiplier = 0.23; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityWallJump.java b/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityWallJump.java deleted file mode 100644 index b3ed617ee..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityWallJump.java +++ /dev/null @@ -1,324 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.agility; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Particles; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; - -public class AgilityWallJump extends SimpleAdaptation { - private final Map airjumps; - private final Map horizontalIntent; - private final Map horizontalIntentTime; - - public AgilityWallJump() { - super("agility-wall-jump"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("agility.wall_jump.description")); - setDisplayName(Localizer.dLocalize("agility.wall_jump.name")); - setIcon(Material.VINE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(50); - airjumps = new HashMap<>(); - horizontalIntent = new HashMap<>(); - horizontalIntentTime = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LADDER) - .key("challenge_agility_wall_jump_500") - .title(Localizer.dLocalize("advancement.challenge_agility_wall_jump_500.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_wall_jump_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FEATHER) - .key("challenge_agility_parkour_master") - .title(Localizer.dLocalize("advancement.challenge_agility_parkour_master.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_parkour_master.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.HIDDEN) - .build()); - registerMilestone("challenge_agility_wall_jump_500", "agility.wall-jump.air-jumps", 500, 500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getMaxJumps(level) + C.GRAY + " " + Localizer.dLocalize("agility.wall_jump.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getJumpHeight(level), 0) + C.GRAY + " " + Localizer.dLocalize("agility.wall_jump.lore2")); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - airjumps.remove(p); - horizontalIntent.remove(p); - horizontalIntentTime.remove(p); - } - - private int getMaxJumps(int level) { - return (int) (level + (level / getConfig().maxJumpsLevelBonusDivisor)); - } - - private double getJumpHeight(int level) { - return getConfig().jumpHeightBase + (getLevelPercent(level) * getConfig().jumpHeightBonusLevelMultiplier); - } - - @EventHandler - public void on(PlayerMoveEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (!canInteract(p, p.getLocation())) { - return; - } - if (airjumps.containsKey(p)) { - if (p.isOnGround() && !p.getLocation().getBlock().getRelative(BlockFace.DOWN).getBlockData().getMaterial().isAir()) { - airjumps.remove(p); - } - } - - if (e.getTo() == null || e.getFrom().getWorld() == null || e.getTo().getWorld() == null || !e.getFrom().getWorld().equals(e.getTo().getWorld())) { - return; - } - - Vector delta = e.getTo().toVector().subtract(e.getFrom().toVector()); - delta.setY(0); - double movementThresholdSq = getConfig().inputMovementThreshold * getConfig().inputMovementThreshold; - if (delta.lengthSquared() >= movementThresholdSq) { - horizontalIntent.put(p, delta.normalize()); - horizontalIntentTime.put(p, M.ms()); - } - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - int level = getLevel(p); - if (level <= 0) { - continue; - } - - Double j = airjumps.get(p); - - if (j != null && j - 0.25 >= getMaxJumps(level)) { - p.setGravity(true); - continue; - } - - if (p.isOnGround()) { - airjumps.remove(p); - if (!p.hasGravity()) { - p.setGravity(true); - } - continue; - } - - if (!canInteract(p, p.getLocation())) { - continue; - } - - Block stickBlock = stickToWall(p); - if (p.isFlying() || !p.isSneaking() || p.getFallDistance() < 0.3) { - boolean jumped = false; - - if (!p.hasGravity() && p.getFallDistance() > 0.45 && stickBlock != null) { - j = j == null ? 0 : j; - j++; - - if (j - 0.25 <= getMaxJumps(level)) { - jumped = true; - Vector launch = p.getVelocity().clone().setY(getJumpHeight(level)); - if (isBackwardLaunch(p)) { - Vector direction = p.getLocation().getDirection().clone().setY(0); - if (direction.lengthSquared() > 0.000001) { - direction.normalize().multiply(-getConfig().backwardPushSpeed); - launch.setX(direction.getX()); - launch.setZ(direction.getZ()); - } - } - p.setVelocity(launch); - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particles.BLOCK_CRACK, p.getLocation().clone().add(0, 0.3, 0), 15, 0.1, 0.8, 0.1, 0.1, stickBlock.getBlockData()); - } - getPlayer(p).getData().addStat("agility.wall-jump.air-jumps", 1); - if (j >= 5 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_agility_parkour_master")) { - getPlayer(p).getAdvancementHandler().grant("challenge_agility_parkour_master"); - } - } - airjumps.put(p, j); - } - - if (!jumped && !p.hasGravity()) { - p.setGravity(true); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1f, 0.439f); - } - continue; - } - - if (stickBlock != null) { - if (p.hasGravity()) { - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 1f, 0.89f); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_CHAIN, 1f, 1.39f); - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particles.BLOCK_CRACK, p.getLocation().clone().add(0, 0.3, 0), 15, 0.1, 0.2, 0.1, 0.1, stickBlock.getBlockData()); - } - } - - applyWallStickForce(p, stickBlock); - p.setGravity(false); - Vector c = p.getVelocity(); - p.setVelocity(p.getVelocity().setY((c.getY() * 0.35) - 0.0025)); - Double vv = airjumps.get(p); - vv = vv == null ? 0 : vv; - vv += 0.0127; - airjumps.put(p, vv); - } - - if (stickBlock == null && !p.hasGravity()) { - p.setGravity(true); - } - } - } - - private boolean isBackwardLaunch(Player p) { - Long at = horizontalIntentTime.get(p); - Vector intent = horizontalIntent.get(p); - if (at == null || intent == null || M.ms() - at > getConfig().inputWindowMs) { - return false; - } - - Vector facing = p.getLocation().getDirection().clone().setY(0); - if (facing.lengthSquared() <= 0.000001) { - return false; - } - - facing.normalize(); - return intent.dot(facing) <= -Math.abs(getConfig().backwardIntentDotThreshold); - } - - private Block stickToWall(Player p) { - for (Block wall : getBlocks(p)) { - if (wall.getBlockData().getMaterial().isSolid()) { - return wall; - } - } - - return null; - } - - private void applyWallStickForce(Player p, Block wall) { - Vector velocity = p.getVelocity(); - Vector shift = p.getLocation().toVector().subtract(wall.getLocation().clone().add(0.5, 0.5, 0.5).toVector()); - velocity.setX(velocity.getX() - (shift.getX() / 16)); - velocity.setZ(velocity.getZ() - (shift.getZ() / 16)); - p.setVelocity(velocity); - } - - private Block[] getBlocks(Player p) { - Block base = p.getLocation().getBlock(); - return new Block[]{ - base.getRelative(BlockFace.NORTH), - base.getRelative(BlockFace.SOUTH), - base.getRelative(BlockFace.EAST), - base.getRelative(BlockFace.WEST), - base.getRelative(BlockFace.NORTH_EAST), - base.getRelative(BlockFace.SOUTH_EAST), - base.getRelative(BlockFace.NORTH_WEST), - base.getRelative(BlockFace.SOUTH_WEST), - base.getRelative(BlockFace.NORTH_EAST).getRelative(BlockFace.UP), - base.getRelative(BlockFace.SOUTH_EAST).getRelative(BlockFace.UP), - base.getRelative(BlockFace.NORTH_WEST).getRelative(BlockFace.UP), - base.getRelative(BlockFace.SOUTH_WEST).getRelative(BlockFace.UP), - base.getRelative(BlockFace.NORTH).getRelative(BlockFace.UP), - base.getRelative(BlockFace.SOUTH).getRelative(BlockFace.UP), - base.getRelative(BlockFace.EAST).getRelative(BlockFace.UP), - base.getRelative(BlockFace.WEST).getRelative(BlockFace.UP), - }; - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Hold shift while mid-air against a wall to latch and jump.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Agility Wall Jump adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Jumps Level Bonus Divisor for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxJumpsLevelBonusDivisor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Jump Height Base for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double jumpHeightBase = 0.625; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Jump Height Bonus Level Multiplier for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double jumpHeightBonusLevelMultiplier = 0.225; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Backward Push Speed for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double backwardPushSpeed = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Backward Intent Dot Threshold for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double backwardIntentDotThreshold = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Input Movement Threshold for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double inputMovementThreshold = 0.0025; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Input Window Ms for the Agility Wall Jump adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long inputWindowMs = 450; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityWindUp.java b/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityWindUp.java deleted file mode 100644 index f24e4e83b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/agility/AgilityWindUp.java +++ /dev/null @@ -1,296 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.agility; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.VelocitySpeed; -import com.volmit.adapt.util.reflect.events.api.ReflectiveHandler; -import com.volmit.adapt.util.reflect.events.api.entity.EntityDismountEvent; -import com.volmit.adapt.util.reflect.events.api.entity.EntityMountEvent; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class AgilityWindUp extends SimpleAdaptation { - private final Map ticksRunning; - private final Map speedBoosting; - - public AgilityWindUp() { - super("agility-wind-up"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("agility.wind_up.description")); - setDisplayName(Localizer.dLocalize("agility.wind_up.name")); - setIcon(Material.POWERED_RAIL); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setInitialCost(getConfig().initialCost); - setInterval(120); - ticksRunning = new HashMap<>(); - speedBoosting = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.POWERED_RAIL) - .key("challenge_agility_wind_up_10min") - .title(Localizer.dLocalize("advancement.challenge_agility_wind_up_10min.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_wind_up_10min.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ACTIVATOR_RAIL) - .key("challenge_agility_wind_up_2hr") - .title(Localizer.dLocalize("advancement.challenge_agility_wind_up_2hr.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_wind_up_2hr.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_agility_wind_up_10min", "agility.wind-up.max-speed-ticks", 12000, 400); - registerMilestone("challenge_agility_wind_up_2hr", "agility.wind-up.max-speed-ticks", 144000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getWindupSpeed(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("agility.wind_up.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getWindupTicks(getLevelPercent(level)) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("agility.wind_up.lore2")); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - UUID id = e.getPlayer().getUniqueId(); - ticksRunning.remove(id); - speedBoosting.remove(id); - } - - @ReflectiveHandler - public void on(EntityMountEvent event) { - if (event.getEntity().getType() != EntityType.PLAYER) { - return; - } - - Player p = (Player) event.getEntity(); - UUID id = p.getUniqueId(); - ticksRunning.remove(id); - invalidateWindupSpeed(p, id, true); - } - - @ReflectiveHandler - public void on(EntityDismountEvent event) { - if (event.getEntity().getType() != EntityType.PLAYER) { - return; - } - - Player p = (Player) event.getEntity(); - ticksRunning.remove(p.getUniqueId()); - } - - private double getWindupTicks(double factor) { - return M.lerp(getConfig().windupTicksSlowest, getConfig().windupTicksFastest, factor); - } - - private double getWindupSpeed(double factor) { - return getConfig().windupSpeedBase + (factor * getConfig().windupSpeedLevelMultiplier); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - UUID id = p.getUniqueId(); - if (!hasAdaptation(p) || !isVelocityEligible(p)) { - ticksRunning.remove(id); - invalidateWindupSpeed(p, id, true); - continue; - } - - if (!p.isSprinting()) { - ticksRunning.remove(id); - brakeWindupSpeed(p, id); - continue; - } - - ticksRunning.compute(id, (k, v) -> v == null ? 1 : v + 1); - int tr = ticksRunning.getOrDefault(id, 0); - if (tr <= 0) { - continue; - } - - double factor = getLevelPercent(p); - double ticksToMax = getWindupTicks(factor); - double progress = Math.min(M.lerpInverse(0, ticksToMax, tr), 1); - double speedIncrease = M.lerp(0, getWindupSpeed(factor), progress); - - if (areParticlesEnabled()) { - if (M.r(0.2 * progress)) { - p.getWorld().spawnParticle(Particle.LAVA, p.getLocation(), 1); - } - - if (M.r(0.25 * progress)) { - p.getWorld().spawnParticle(Particle.FLAME, p.getLocation(), 1, 0, 0, 0, 0); - } - } - - VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); - if (!input.hasHorizontal()) { - brakeWindupSpeed(p, id); - } else { - applyWindupSpeed(p, id, input, speedIncrease); - } - - if (progress >= 1.0) { - getPlayer(p).getData().addStat("agility.wind-up.max-speed-ticks", 1); - } - } - } - - private void applyWindupSpeed(Player p, UUID id, VelocitySpeed.InputSnapshot input, double speedIncrease) { - Vector direction = VelocitySpeed.resolveHorizontalDirection(p, input); - if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { - brakeWindupSpeed(p, id); - return; - } - - double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, - Math.max(0, getConfig().baseHorizontalSpeed * (1.0 + Math.max(0, speedIncrease)))); - Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); - Vector targetHorizontal = direction.multiply(targetSpeed); - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); - nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - speedBoosting.put(id, true); - } - - private void brakeWindupSpeed(Player p, UUID id) { - if (!speedBoosting.getOrDefault(id, false)) { - return; - } - - Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); - double stopThreshold = Math.max(0, getConfig().stopThreshold); - if (horizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - speedBoosting.put(id, false); - return; - } - - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); - if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - speedBoosting.put(id, false); - return; - } - - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - } - - private void invalidateWindupSpeed(Player p, UUID id, boolean invalidState) { - if (!speedBoosting.getOrDefault(id, false)) { - return; - } - - if (invalidState && getConfig().hardStopOnInvalidState) { - VelocitySpeed.hardStopHorizontal(p); - } - - speedBoosting.put(id, false); - } - - private boolean isVelocityEligible(Player p) { - GameMode mode = p.getGameMode(); - if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { - return false; - } - - return !p.isDead() - && !p.isSwimming() - && !p.isFlying() - && !p.isGliding() - && !p.isSneaking() - && p.getVehicle() == null; - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - - @NoArgsConstructor - @ConfigDescription("Get faster the longer you sprint.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Agility Wind Up adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Windup Ticks Slowest for the Agility Wind Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windupTicksSlowest = 180; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Windup Ticks Fastest for the Agility Wind Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windupTicksFastest = 60; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Windup Speed Base for the Agility Wind Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windupSpeedBase = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Windup Speed Level Multiplier for the Agility Wind Up adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windupSpeedLevelMultiplier = 0.225; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for windup velocity scaling.", impact = "Higher values increase movement speed while windup is active.") - double baseHorizontalSpeed = 0.13; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent runaway momentum.") - double maxHorizontalSpeed = 0.31; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the windup target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") - double accelPerTick = 0.045; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity decays when sprint movement is not applied.", impact = "Higher values stop faster and reduce carry momentum.") - double brakePerTick = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny motion longer.") - double stopThreshold = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "If true, windup velocity is force-cleared when entering invalid states.", impact = "Prevents retained speed from skipped state transitions.") - boolean hardStopOnInvalidState = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") - double fallbackInputVelocityThreshold = 0.0008; - - double fallbackInputVelocityThresholdSquared() { - double threshold = Math.max(0, fallbackInputVelocityThreshold); - return threshold * threshold; - } - } - -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectElevator.java b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectElevator.java deleted file mode 100644 index 970e85cf6..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectElevator.java +++ /dev/null @@ -1,433 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.architect; - -import com.jeff_media.customblockdata.CustomBlockData; -import com.jeff_media.customblockdata.events.CustomBlockDataMoveEvent; -import com.jeff_media.customblockdata.events.CustomBlockDataRemoveEvent; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockExplodeEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.util.BoundingBox; -import org.bukkit.util.VoxelShape; -import org.jetbrains.annotations.Nullable; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; - -public class ArchitectElevator extends SimpleAdaptation { - private static final NamespacedKey ELEVATOR_KEY = new NamespacedKey(Adapt.instance, "elevator"); - private static final NamespacedKey TARGET_DOWN = new NamespacedKey(Adapt.instance, "target_down"); - private static final NamespacedKey TARGET_UP = new NamespacedKey(Adapt.instance, "target_up"); - - private static final int PARTICLE_COUNT = 20; - private static final float SOUND_VOLUME = 1f; - private static final float SOUND_PITCH = 1f; - - private final Set players = new HashSet<>(); - - public ArchitectElevator() { - super("architect-elevator"); - registerConfiguration(ArchitectElevator.Config.class); - setDescription(Localizer.dLocalize("architect.elevator.description")); - setDisplayName(Localizer.dLocalize("architect.elevator.name")); - setIcon(Material.HEAVY_WEIGHTED_PRESSURE_PLATE); - setInterval(988); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - - registerRecipe(AdaptRecipe.shaped() - .key("elevator") - .shape("XXX") - .shape("XYX") - .shape("XXX") - .ingredient(new MaterialChar('X', Tag.WOOL)) - .ingredient(new MaterialChar('Y', Material.ENDER_PEARL)) - .result(getElevatorItem()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WHITE_WOOL) - .key("challenge_architect_elevator_100") - .title(Localizer.dLocalize("advancement.challenge_architect_elevator_100.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_elevator_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.WHITE_WOOL) - .key("challenge_architect_elevator_penthouse") - .title(Localizer.dLocalize("advancement.challenge_architect_elevator_penthouse.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_elevator_penthouse.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_architect_elevator_100", "architect.elevator.trips", 100, 300); - } - - @Override - public void addStats(int level, Element v) { - - } - - public ItemStack getElevatorItem() { - ItemStack elevatorItem = CustomModel.get(Material.NOTE_BLOCK, "architect", "elevator", "item") - .toItemStack(); - ItemMeta meta = elevatorItem.getItemMeta(); - if (meta != null) { - meta.getPersistentDataContainer().set(ELEVATOR_KEY, PersistentDataType.BYTE, (byte) 0); - meta.setDisplayName(Localizer.dLocalize("items.elevator_block.name")); - meta.setLore(List.of(Localizer.dLocalize("items.elevator_block.usage1"), - Localizer.dLocalize("items.elevator_block.usage2"), - Localizer.dLocalize("items.elevator_block.usage3"))); - elevatorItem.setItemMeta(meta); - } - return elevatorItem; - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerMoveEvent e) { - if (e.getTo() == null) return; - Player player = e.getPlayer(); - - if (!players.add(player.getUniqueId())) { - if (e.getFrom().getY() < e.getTo().getY() || player.isFlying()) - players.remove(player.getUniqueId()); - return; - } - - if (player.isFlying() || player.getVelocity().getY() <= 0 || e.getFrom().getY() >= e.getTo().getY()) - return; - - Block block = findElevator(player); - if (block == null) return; - handleElevatorMovement(block, player, false); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerToggleSneakEvent event) { - if (!event.isSneaking() || event.getPlayer().isInsideVehicle()) return; - Player player = event.getPlayer(); - Block block = findElevator(player); - if (block == null) return; - handleElevatorMovement(block, player, true); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(BlockPlaceEvent event) { - ItemMeta meta = event.getItemInHand().getItemMeta(); - if (meta == null || !meta.getPersistentDataContainer().has(ELEVATOR_KEY, PersistentDataType.BYTE)) - return; - int maxDistance = getMaxDistance(event.getPlayer()); - if (maxDistance <= 0) { - event.setCancelled(true); - return; - } - - Block block = event.getBlock(); - World world = block.getWorld(); - CustomBlockData data = new CustomBlockData(block, Adapt.instance); - data.set(ELEVATOR_KEY, PersistentDataType.INTEGER, maxDistance); - - int lowerDist = Math.min(block.getY() - world.getMinHeight(), maxDistance); - for (int d = 1; d <= lowerDist; d++) { - var lower = block.getRelative(BlockFace.DOWN, d); - if (checkElevator(lower, TARGET_UP, d)) { - data.set(TARGET_DOWN, PersistentDataType.INTEGER, d); - break; - } - } - - int upperDist = Math.min(world.getMaxHeight() - block.getY(), maxDistance); - for (int d = 1; d <= upperDist; d++) { - var upper = block.getRelative(BlockFace.UP, d); - if (checkElevator(upper, TARGET_DOWN, d)) { - data.set(TARGET_UP, PersistentDataType.INTEGER, d); - } - } - } - - public int getMaxDistance(Player player) { - int level = getLevel(player); - if (level == 0) return 0; - Config config = getConfig(); - return config.baseDistance * (level * config.multiplier); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on(CustomBlockDataMoveEvent event) { - if (!event.getCustomBlockData().has(ELEVATOR_KEY)) return; - event.setCancelled(true); - - Event bukkit = event.getBukkitEvent(); - if (bukkit instanceof Cancellable cancellable) { - cancellable.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on(BlockExplodeEvent event) { - event.blockList().removeIf(ArchitectElevator::isElevator); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on(EntityExplodeEvent event) { - event.blockList().removeIf(ArchitectElevator::isElevator); - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void on(CustomBlockDataRemoveEvent event) { - CustomBlockData data = event.getCustomBlockData(); - if (!data.has(ELEVATOR_KEY)) return; - Event bukkit = event.getBukkitEvent(); - if (!(bukkit instanceof BlockBreakEvent breakEvent)) { - if (bukkit instanceof Cancellable cancellable) - cancellable.setCancelled(true); - event.setCancelled(true); - return; - } - - breakEvent.setDropItems(false); - Block block = event.getBlock(); - World world = block.getWorld(); - Location location = block.getLocation(); - world.dropItemNaturally(location, getElevatorItem()); - - data.remove(ELEVATOR_KEY); - int y = block.getY(); - int lowerY = data.getOrDefault(TARGET_DOWN, PersistentDataType.INTEGER, 0); - int upperY = data.getOrDefault(TARGET_UP, PersistentDataType.INTEGER, 0); - data.remove(TARGET_DOWN); - data.remove(TARGET_UP); - - if (y - lowerY < world.getMinHeight()) - lowerY = 0; - - if (y + upperY > world.getMaxHeight()) - upperY = 0; - - if (lowerY != 0 && upperY != 0) { - Block lower = block.getRelative(BlockFace.DOWN, lowerY); - Block upper = block.getRelative(BlockFace.UP, upperY); - - boolean lowerElevator = isElevator(lower); - boolean upperElevator = isElevator(upper); - - if (lowerElevator && upperElevator) { - CustomBlockData lowerData = new CustomBlockData(lower, Adapt.instance); - CustomBlockData upperData = new CustomBlockData(upper, Adapt.instance); - - int dist = upperY + lowerY; - int lowerDist = lowerData.getOrDefault(ELEVATOR_KEY, PersistentDataType.INTEGER, 0); - int upperDist = upperData.getOrDefault(ELEVATOR_KEY, PersistentDataType.INTEGER, 0); - int maxDistance = Math.max(upperDist, lowerDist); - - if (dist <= maxDistance) { - lowerData.set(TARGET_UP, PersistentDataType.INTEGER, dist); - upperData.set(TARGET_DOWN, PersistentDataType.INTEGER, dist); - } else { - lowerData.remove(TARGET_UP); - upperData.remove(TARGET_DOWN); - } - } else if (lowerElevator) { - new CustomBlockData(lower, Adapt.instance) - .remove(TARGET_UP); - } else if (upperElevator) { - new CustomBlockData(upper, Adapt.instance) - .remove(TARGET_DOWN); - } - } else if (lowerY != 0) { - Block lower = block.getRelative(BlockFace.DOWN, lowerY); - - if (isElevator(lower)) { - new CustomBlockData(lower, Adapt.instance) - .remove(TARGET_UP); - } - } else if (upperY != 0) { - Block upper = block.getRelative(BlockFace.UP, upperY); - - if (isElevator(upper)) { - new CustomBlockData(upper, Adapt.instance) - .remove(TARGET_DOWN); - } - } - } - - @Nullable - private Block findElevator(Player player) { - Block base = player.getLocation().getBlock(); - for (int d = 1; d <= 2; d++) { - Block rel = base.getRelative(BlockFace.DOWN, d); - if (isElevator(rel)) - return rel; - } - return null; - } - - private boolean checkElevator(Block block, NamespacedKey key, int source) { - if (!isElevator(block)) - return false; - - new CustomBlockData(block, Adapt.instance) - .set(key, PersistentDataType.INTEGER, source); - return true; - } - - private void handleElevatorMovement(Block block, Player player, boolean down) { - if (!isElevator(block) || player.isInsideVehicle()) - return; - - CustomBlockData data = new CustomBlockData(block, Adapt.instance); - int distance = data.getOrDefault(down ? TARGET_DOWN : TARGET_UP, PersistentDataType.INTEGER, 0); - if (distance == 0) - return; - int targetY = block.getY() + (down ? -distance : distance); - if (targetY < block.getWorld().getMinHeight() || targetY > block.getWorld().getMaxHeight()) - return; - - Block target = block.getRelative(down ? BlockFace.DOWN : BlockFace.UP, distance); - if (!isElevator(target)) - return; - - var loc = player.getLocation(); - loc.setY(target.getY() + 1); - - if (!hasEnoughSpace(player, loc.getBlockY())) - return; - - teleportPlayer(player, loc); - getPlayer(player).getData().addStat("architect.elevator.trips", 1); - if (distance >= 50 && AdaptConfig.get().isAdvancements() && !getPlayer(player).getData().isGranted("challenge_architect_elevator_penthouse")) { - getPlayer(player).getAdvancementHandler().grant("challenge_architect_elevator_penthouse"); - } - } - - private static boolean isElevator(Block b) { - return b.getType() == Material.NOTE_BLOCK - && CustomBlockData.hasCustomBlockData(b, Adapt.instance) - && new CustomBlockData(b, Adapt.instance) - .has(ELEVATOR_KEY, PersistentDataType.INTEGER); - } - - private static boolean hasEnoughSpace(Player player, int targetY) { - BoundingBox box = player.getBoundingBox() - .shift(0, -player.getLocation().getY(), 0) - .shift(0, targetY, 0); - - double maxX = Math.ceil(box.getMaxX()); - double maxY = Math.ceil(box.getMaxY()); - double maxZ = Math.ceil(box.getMaxZ()); - World world = player.getWorld(); - for (int x = (int) box.getMinX(); x <= maxX; x++) { - for (int z = (int) box.getMinZ(); z <= maxZ; z++) { - for (int y = (int) box.getMinY(); y <= maxY; y++) { - Block block = world.getBlockAt(x, y, z); - if (block.isPassable() || block.isLiquid()) - continue; - VoxelShape shape = block.getCollisionShape(); - box.shift(-x, -y, -z); - if (shape.overlaps(box)) - return false; - box.shift(x, y, z); - } - } - } - return true; - } - - private void teleportPlayer(Player p, Location l) { - playTeleportEffects(p); - p.teleport(l); - SoundPlayer.of(p.getWorld()).play(p, Sound.ENTITY_ENDERMAN_TELEPORT, SOUND_VOLUME, SOUND_PITCH); - playTeleportEffects(p); - } - - private void playTeleportEffects(Player p) { - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.PORTAL, p.getLocation(), PARTICLE_COUNT); - } - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Build wool elevators to teleport vertically.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Distance for the Architect Elevator adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseDistance = 32; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Multiplier for the Architect Elevator adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int multiplier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.40; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectFoundation.java b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectFoundation.java deleted file mode 100644 index 5a4910d1b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectFoundation.java +++ /dev/null @@ -1,358 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.architect; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Particles; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockExplodeEvent; -import org.bukkit.event.block.BlockPistonExtendEvent; -import org.bukkit.event.block.BlockPistonRetractEvent; -import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.bukkit.entity.ItemFrame; -import org.bukkit.entity.Painting; - -public class ArchitectFoundation extends SimpleAdaptation { - private static final BlockData AIR = Material.AIR.createBlockData(); - private static final BlockData BLOCK = Material.TINTED_GLASS.createBlockData(); - private final Map blockPower; - private final Map cooldowns; - private final Set active; - private final Set activeBlocks; - - public ArchitectFoundation() { - super("architect-foundation"); - registerConfiguration(ArchitectFoundation.Config.class); - setDescription(Localizer.dLocalize("architect.foundation.description")); - setDisplayName(Localizer.dLocalize("architect.foundation.name")); - setIcon(Material.TINTED_GLASS); - setInterval(988); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - blockPower = new HashMap<>(); - cooldowns = new HashMap<>(); - active = new HashSet<>(); - activeBlocks = new HashSet<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SCAFFOLDING) - .key("challenge_architect_foundation_1k") - .title(Localizer.dLocalize("advancement.challenge_architect_foundation_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_foundation_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SCAFFOLDING) - .key("challenge_architect_foundation_10k") - .title(Localizer.dLocalize("advancement.challenge_architect_foundation_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_foundation_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_architect_foundation_1k", "architect.foundation.blocks-placed", 1000, 300); - registerMilestone("challenge_architect_foundation_10k", "architect.foundation.blocks-placed", 10000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("architect.foundation.lore1") - + (getBlockPower(getLevelPercent(level))) + C.GRAY + " " - + Localizer.dLocalize("architect.foundation.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerMoveEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (!p.isSneaking()) { - return; - } - if (!hasAdaptation(p)) { - return; - } - if (!canBlockPlace(p, p.getLocation())) { - return; - } - if (!e.getFrom().getBlock().equals(e.getTo().getBlock())) { - return; - } - if (!this.active.contains(p)) { - return; - } - int power = blockPower.get(p); - - if (power <= 0) { - return; - } - - Location l = e.getTo(); - World world = l.getWorld(); - Set locs = new HashSet<>(); - locs.add(world.getBlockAt(l.clone().add(0.3, -1, -0.3))); - locs.add(world.getBlockAt(l.clone().add(-0.3, -1, -0.3))); - locs.add(world.getBlockAt(l.clone().add(0.3, -1, 0.3))); - locs.add(world.getBlockAt(l.clone().add(-0.3, -1, +0.3))); - - for (Block b : locs) { - if (addFoundation(b)) { - power--; - getPlayer(p).getData().addStat("architect.foundation.blocks-placed", 1); - } - - if (power <= 0) { - break; - } - } - - blockPower.put(p, power); - } - - // prevent piston from moving blocks // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockPistonExtendEvent e) { - if (e.isCancelled()) { - return; - } - e.getBlocks().forEach(b -> { - if (activeBlocks.contains(b)) { - Adapt.verbose("Cancelled Piston Extend on Adaptation Foundation Block"); - e.setCancelled(true); - } - }); - } - - // prevent piston from pulling blocks // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockPistonRetractEvent e) { - if (e.isCancelled()) { - return; - } - e.getBlocks().forEach(b -> { - if (activeBlocks.contains(b)) { - Adapt.verbose("Cancelled Piston Retract on Adaptation Foundation Block"); - e.setCancelled(true); - } - }); - } - - // prevent TNT from destroying blocks // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockExplodeEvent e) { - if (e.isCancelled()) { - return; - } - if (activeBlocks.contains(e.getBlock())) { - Adapt.verbose("Cancelled Block Explosion on Adaptation Foundation Block"); - e.setCancelled(true); - } - } - - // prevent block from being destroyed // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockBreakEvent e) { - if (activeBlocks.contains(e.getBlock())) { - e.setCancelled(true); - } - } - - // prevent Entities from destroying blocks // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityExplodeEvent e) { - if (e.isCancelled()) { - return; - } - e.blockList().removeIf(activeBlocks::contains); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerToggleSneakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (!hasAdaptation(p) || p.getGameMode().equals(GameMode.CREATIVE) - || p.getGameMode().equals(GameMode.SPECTATOR)) { - return; - } - - boolean ready = !hasCooldown(p); - boolean active = this.active.contains(p); - - if (e.isSneaking() && ready && !active) { - this.active.add(p); - cooldowns.put(p, Long.MAX_VALUE); - // effect start placing - } else if (!e.isSneaking() && active) { - this.active.remove(p); - cooldowns.put(p, M.ms() + getConfig().cooldown); - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 1.0f, 10.0f); - sp.play(p.getLocation(), Sound.BLOCK_SCULK_CATALYST_BREAK, 1.0f, 0.81f); - } - } - - public boolean addFoundation(Block block) { - if (!block.getType().isAir()) { - return false; - } - - if(!block.getWorld() - .getNearbyEntities(block.getLocation() - .add(.5, .5, .5), .5, .5, .5, entity -> - entity instanceof ItemFrame || entity instanceof Painting).isEmpty()) - return false; - - - J.s(() -> { - block.setBlockData(BLOCK); - activeBlocks.add(block); - }); - SoundPlayer spw = SoundPlayer.of(block.getWorld()); - spw.play(block.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 1.0f, 1.0f); - if (areParticlesEnabled()) { - - vfxCuboidOutline(block, Particle.REVERSE_PORTAL); - vfxCuboidOutline(block, Particle.ASH); - } - J.s(() -> removeFoundation(block), 3 * 20); - return true; - } - - public void removeFoundation(Block block) { - if (!block.getBlockData().equals(BLOCK)) { - return; - } - - J.s(() -> { - block.setBlockData(AIR); - activeBlocks.remove(block); - SoundPlayer spw = SoundPlayer.of(block.getWorld()); - spw.play(block.getLocation(), Sound.BLOCK_DEEPSLATE_BREAK, 1.0f, 1.0f); - }); - if (areParticlesEnabled()) { - vfxCuboidOutline(block, Particles.ENCHANTMENT_TABLE); - } - } - - public int getBlockPower(double factor) { - return (int) Math.floor(M.lerp(getConfig().minBlocks, getConfig().maxBlocks, factor)); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - if (!hasAdaptation(i)) { - continue; - } - - boolean ready = !hasCooldown(i); - int availablePower = getBlockPower(getLevelPercent(i)); - blockPower.compute(i, (k, v) -> { - if ((k == null || v == null) || (ready && v != availablePower)) { - if (i == null) { - return 0; - } - final var world = i.getWorld(); - final var location = i.getLocation(); - - SoundPlayer spw = SoundPlayer.of(world); - spw.play(location, Sound.BLOCK_BEACON_ACTIVATE, 1.0f, 10.0f); - spw.play(location, Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 1.0f, 0.81f); - - return availablePower; - } - return v; - }); - } - } - - private boolean hasCooldown(Player i) { - if (cooldowns.containsKey(i)) { - if (M.ms() >= cooldowns.get(i)) { - cooldowns.remove(i); - } - } - - return cooldowns.containsKey(i); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak to place a temporary foundation beneath you.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration for the Architect Foundation adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public long duration = 3000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Blocks for the Architect Foundation adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public int minBlocks = 9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Blocks for the Architect Foundation adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public int maxBlocks = 35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Architect Foundation adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public int cooldown = 5000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Foundation adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.40; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectGlass.java b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectGlass.java deleted file mode 100644 index f4d4aad8f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectGlass.java +++ /dev/null @@ -1,135 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.architect; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.inventory.ItemStack; - - -public class ArchitectGlass extends SimpleAdaptation { - public ArchitectGlass() { - super("architect-glass"); - registerConfiguration(ArchitectGlass.Config.class); - setDescription(Localizer.dLocalize("architect.glass.description")); - setDisplayName(Localizer.dLocalize("architect.glass.name")); - setIcon(Material.GLASS); - setInterval(25000); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GLASS) - .key("challenge_architect_glass_200") - .title(Localizer.dLocalize("advancement.challenge_architect_glass_200.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_glass_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GLASS) - .key("challenge_architect_glass_5k") - .title(Localizer.dLocalize("advancement.challenge_architect_glass_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_glass_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_architect_glass_200", "architect.glass.blocks-recovered", 200, 300); - registerMilestone("challenge_architect_glass_5k", "architect.glass.blocks-recovered", 5000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("architect.glass.lore1")); - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (hasAdaptation(p) && (p.getInventory().getItemInMainHand().getType() == Material.AIR || !isTool(p.getInventory().getItemInMainHand())) && !e.isCancelled()) { - if (!canBlockBreak(p, e.getBlock().getLocation())) { - return; - } - if (e.getBlock().getType().toString().contains("GLASS") && !e.getBlock().getType().toString().contains("TINTED_GLASS")) { - e.getBlock().getWorld().dropItemNaturally(e.getBlock().getLocation(), new ItemStack(e.getBlock().getType(), 1)); - SoundPlayer spw = SoundPlayer.of(e.getBlock().getWorld()); - spw.play(e.getBlock().getLocation(), Sound.BLOCK_LARGE_AMETHYST_BUD_BREAK, 1.0f, 1.0f); - if (areParticlesEnabled()) { - - e.getBlock().getWorld().spawnParticle(Particle.SCRAPE, e.getBlock().getLocation(), 1); - vfxCuboidOutline(e.getBlock(), Particle.REVERSE_PORTAL); - } - e.getBlock().breakNaturally(); - getPlayer(p).getData().addStat("architect.glass.blocks-recovered", 1); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Silk-touch glass blocks when breaking them with an empty hand.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Glass adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectPlacement.java b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectPlacement.java deleted file mode 100644 index 5e8b55025..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectPlacement.java +++ /dev/null @@ -1,318 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.architect; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.Container; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; -import java.util.Map; - -public class ArchitectPlacement extends SimpleAdaptation { - private final KMap> totalMap = new KMap<>(); - - public ArchitectPlacement() { - super("architect-placement"); - registerConfiguration(ArchitectPlacement.Config.class); - setDescription(Localizer.dLocalize("architect.placement.description")); - setDisplayName(Localizer.dLocalize("architect.placement.name")); - setIcon(Material.SCAFFOLDING); - setInterval(360); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BRICKS) - .key("challenge_architect_placement_1k") - .title(Localizer.dLocalize("advancement.challenge_architect_placement_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_placement_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BRICKS) - .key("challenge_architect_placement_25k") - .title(Localizer.dLocalize("advancement.challenge_architect_placement_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_placement_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_architect_placement_1k", "architect.placement.blocks-placed", 1000, 300); - registerMilestone("challenge_architect_placement_25k", "architect.placement.blocks-placed", 25000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("architect.placement.lore3")); - } - - private BlockFace getBlockFace(Player player) { - List lastTwoTargetBlocks = player.getLastTwoTargetBlocks(null, 5); - if (lastTwoTargetBlocks.size() != 2 || !lastTwoTargetBlocks.get(1).getType().isOccluding()) return null; - Block targetBlock = lastTwoTargetBlocks.get(1); - Block adjacentBlock = lastTwoTargetBlocks.get(0); - return targetBlock.getFace(adjacentBlock); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - totalMap.remove(p); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(BlockPlaceEvent e) { - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p) || !p.isSneaking()) - return; - - var blocks = totalMap.get(p); - if (blocks == null || blocks.isEmpty()) - return; - - ItemStack hand = e.getItemInHand(); - if (!hand.getType().isBlock() || blocks.keys().nextElement().getType() != hand.getType()) - return; - - double v = getValue(e.getBlock()); - Block ignored = blocks.keySet() - .stream() - .filter(b -> b.getRelative(blocks.get(b)).equals(e.getBlock())) - .findFirst() - .orElse(null); - - if (hand.getAmount() < blocks.size()) { - Adapt.messagePlayer(p, C.RED + Localizer.dLocalize("architect.placement.lore1") + " " + C.GREEN + blocks.size() + C.RED + " " + Localizer.dLocalize("architect.placement.lore2")); - return; - } - - if (ignored != null) blocks.remove(ignored); - for (Block b : blocks.keySet()) { // Block Placer - Block relative = b.getRelative(blocks.get(b)); - if (!relative.getType().isAir()) - continue; - - if (!canBlockPlace(p, relative.getLocation())) { - Adapt.verbose("Player " + p.getName() + " doesn't have permission."); - continue; - } - - relative.setBlockData(b.getBlockData()); - getPlayer(p).getData().addStat("blocks.placed", 1); - getPlayer(p).getData().addStat("blocks.placed.value", v); - getPlayer(p).getData().addStat("architect.placement.blocks-placed", 1); - sp.play(b.getLocation(), Sound.BLOCK_AZALEA_BREAK, 0.4f, 0.25f); - xp(p, 2); - - hand.setAmount(hand.getAmount() - 1); - } - - if (ignored != null) { - e.getBlock().setBlockData(ignored.getBlockData()); - getPlayer(p).getData().addStat("blocks.placed", 1); - getPlayer(p).getData().addStat("blocks.placed.value", v); - getPlayer(p).getData().addStat("architect.placement.blocks-placed", 1); - sp.play(ignored.getLocation(), Sound.BLOCK_AZALEA_BREAK, 0.4f, 0.25f); - xp(p, 2); - - hand.setAmount(hand.getAmount() - 1); - } else e.setCancelled(true); - - totalMap.remove(p); - if (hand.getAmount() > 0) { - runPlayerViewport(getBlockFace(p), p.getTargetBlock(null, 5), p.getInventory().getItemInMainHand().getType(), p); - } - } - - - @EventHandler - public void on(PlayerToggleSneakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (hasAdaptation(p) && p.isSneaking()) { - totalMap.remove(p); - } - - if (hasAdaptation(p) && !p.isSneaking() && p.getInventory().getItemInMainHand().getType().isBlock()) { - Block block = p.getTargetBlock(null, 5); // 5 is the range of player - if (block instanceof Container) { // return if block is a container - return; - } - Material handMaterial = p.getInventory().getItemInMainHand().getType(); - if (handMaterial.isAir()) { - return; - } - BlockFace viewPortBlock = getBlockFace(p); - runPlayerViewport(viewPortBlock, block, handMaterial, p); - } - } - - - @EventHandler - public void on(PlayerMoveEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (hasAdaptation(p) && !p.isSneaking()) { - totalMap.remove(p); - } - - if (hasAdaptation(p) && p.isSneaking() && p.getInventory().getItemInMainHand().getType().isBlock()) { - Block block = p.getTargetBlock(null, 5); // 5 is the range of player - if (block instanceof Container) { // return if block is a container - return; - } - Material handMaterial = p.getInventory().getItemInMainHand().getType(); - if (handMaterial.isAir()) { - return; - } - BlockFace viewPortBlock = getBlockFace(p); - runPlayerViewport(viewPortBlock, block, handMaterial, p); - - } - } - - public void runPlayerViewport(BlockFace viewPortBlock, Block block, Material handMaterial, Player p) { - if (viewPortBlock != null && (viewPortBlock.getDirection().equals(BlockFace.NORTH.getDirection()) || viewPortBlock.getDirection().equals(BlockFace.SOUTH.getDirection()))) { // North & South = X - for (int x = block.getX() - 1; x <= block.getX() + 1; x++) { // 1 is the radius of the blocks - for (int y = block.getY() - 1; y <= block.getY() + 1; y++) { - if (handMaterial == block.getWorld().getBlockAt(x, y, block.getZ()).getType()) { - if (totalMap.get(p) == null) { - KMap map = new KMap<>(); - map.put(block.getWorld().getBlockAt(x, y, block.getZ()), viewPortBlock); - totalMap.put(p, map); - } else if (totalMap.get(p).size() <= getConfig().maxBlocks) { - totalMap.get(p).put(block.getWorld().getBlockAt(x, y, block.getZ()), viewPortBlock); - } - } - } - } - } else if (viewPortBlock != null && (viewPortBlock.getDirection().equals(BlockFace.EAST.getDirection()) || viewPortBlock.getDirection().equals(BlockFace.WEST.getDirection()))) { // East & West = Z - for (int z = block.getZ() - 1; z <= block.getZ() + 1; z++) { // 1 is the radius of the blocks - for (int y = block.getY() - 1; y <= block.getY() + 1; y++) { - if (handMaterial == block.getWorld().getBlockAt(block.getX(), y, z).getType()) { - if (totalMap.get(p) == null) { - KMap map = new KMap<>(); - map.put(block.getWorld().getBlockAt(block.getX(), y, z), viewPortBlock); - totalMap.put(p, map); - } else if (totalMap.get(p).size() <= getConfig().maxBlocks) { - totalMap.get(p).put(block.getWorld().getBlockAt(block.getX(), y, z), viewPortBlock); - } - } - } - } - } else if (viewPortBlock != null && (viewPortBlock.getDirection().equals(BlockFace.UP.getDirection()) || viewPortBlock.getDirection().equals(BlockFace.DOWN.getDirection()))) { // Up & Down = Y - for (int z = block.getZ() - 1; z <= block.getZ() + 1; z++) { // 1 is the radius of the blocks - for (int x = block.getX() - 1; x <= block.getX() + 1; x++) { - if (handMaterial == block.getWorld().getBlockAt(x, block.getY(), z).getType()) { - if (totalMap.get(p) == null) { - KMap map = new KMap<>(); - map.put(block.getWorld().getBlockAt(x, block.getY(), z), viewPortBlock); - totalMap.put(p, map); - } else if (totalMap.get(p).size() <= getConfig().maxBlocks) { - totalMap.get(p).put(block.getWorld().getBlockAt(x, block.getY(), z), viewPortBlock); - } - } - } - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - - @Override - public void onTick() { - if (!totalMap.isEmpty()) { - for (Player p : totalMap.keySet()) { // Get every player that has a map - if (!hasAdaptation(p) || !p.isSneaking()) { - totalMap.clear(); - return; - } - Map blockRender = totalMap.get(p); - for (Block b : blockRender.keySet()) { // Get the blocks in that map that bind with a BlockFace - if (b instanceof Container) { // return if block is a container - return; - } - BlockFace bf = blockRender.get(b); // Get that blockface - Block transposedBlock = b.getRelative(bf); - if (areParticlesEnabled()) { - vfxCuboidOutline(transposedBlock, Particle.REVERSE_PORTAL); - } - } - } - } - } - - @NoArgsConstructor - @ConfigDescription("Place multiple blocks at once while sneaking with a matching block.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Blocks for the Architect Placement adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public int maxBlocks = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Placement adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectSmartShape.java b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectSmartShape.java deleted file mode 100644 index 407e440ce..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectSmartShape.java +++ /dev/null @@ -1,260 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.architect; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Directional; -import org.bukkit.block.data.Orientable; -import org.bukkit.block.data.Rotatable; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.Axis; -import org.bukkit.Sound; -import org.bukkit.block.BlockFace; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Set; - -public class ArchitectSmartShape extends SimpleAdaptation { - private static final List ROTATION_ORDER = Arrays.asList( - BlockFace.NORTH, - BlockFace.NORTH_NORTH_EAST, - BlockFace.NORTH_EAST, - BlockFace.EAST_NORTH_EAST, - BlockFace.EAST, - BlockFace.EAST_SOUTH_EAST, - BlockFace.SOUTH_EAST, - BlockFace.SOUTH_SOUTH_EAST, - BlockFace.SOUTH, - BlockFace.SOUTH_SOUTH_WEST, - BlockFace.SOUTH_WEST, - BlockFace.WEST_SOUTH_WEST, - BlockFace.WEST, - BlockFace.WEST_NORTH_WEST, - BlockFace.NORTH_WEST, - BlockFace.NORTH_NORTH_WEST - ); - - public ArchitectSmartShape() { - super("architect-smart-shape"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("architect.smart_shape.description")); - setDisplayName(Localizer.dLocalize("architect.smart_shape.name")); - setIcon(Material.BRICKS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(800); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.QUARTZ_STAIRS) - .key("challenge_architect_smart_shape_200") - .title(Localizer.dLocalize("advancement.challenge_architect_smart_shape_200.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_smart_shape_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.QUARTZ_STAIRS) - .key("challenge_architect_smart_shape_5k") - .title(Localizer.dLocalize("advancement.challenge_architect_smart_shape_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_smart_shape_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_architect_smart_shape_200", "architect.smart-shape.rotations", 200, 300); - registerMilestone("challenge_architect_smart_shape_5k", "architect.smart-shape.rotations", 5000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("architect.smart_shape.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("architect.smart_shape.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerInteractEvent e) { - if (e.getAction() != Action.LEFT_CLICK_BLOCK || e.getClickedBlock() == null) { - return; - } - - if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking()) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (isItem(hand) && hand.getType() != Material.AIR) { - return; - } - - Block target = e.getClickedBlock(); - if (!canBlockPlace(p, target.getLocation())) { - return; - } - - BlockData data = target.getBlockData().clone(); - int options = rotateData(data); - if (options <= 0) { - return; - } - - target.setBlockData(data, true); - e.setCancelled(true); - SoundPlayer.of(p.getWorld()).play(target.getLocation(), Sound.ITEM_AXE_STRIP, 0.45f, 1.8f); - xp(p, Math.max(getConfig().minXpPerRotate, options * getConfig().xpPerOrientationOption)); - getPlayer(p).getData().addStat("architect.smart-shape.rotations", 1); - } - - private int rotateData(BlockData data) { - if (data instanceof Directional directional) { - BlockFace next = getNextFace(directional.getFacing(), directional.getFaces()); - if (next != null && next != directional.getFacing()) { - directional.setFacing(next); - return directional.getFaces().size(); - } - } - - if (data instanceof Rotatable rotatable) { - BlockFace next = getNextFace(rotatable.getRotation(), Set.copyOf(ROTATION_ORDER), ROTATION_ORDER); - if (next != null && next != rotatable.getRotation()) { - rotatable.setRotation(next); - return ROTATION_ORDER.size(); - } - } - - if (data instanceof Orientable orientable) { - Axis current = orientable.getAxis(); - Axis next = switch (current) { - case X -> Axis.Y; - case Y -> Axis.Z; - case Z -> Axis.X; - }; - - if (orientable.getAxes().contains(next)) { - orientable.setAxis(next); - return orientable.getAxes().size(); - } - - if (orientable.getAxes().contains(Axis.X)) { - orientable.setAxis(Axis.X); - return orientable.getAxes().size(); - } - } - - return 0; - } - - private BlockFace getNextFace(BlockFace current, Set supported) { - if (supported == null || supported.isEmpty()) { - return null; - } - - List ordered = new ArrayList<>(supported); - ordered.sort(Comparator.comparingInt(Enum::ordinal)); - return getNextFace(current, supported, ordered); - } - - private BlockFace getNextFace(BlockFace current, Set supported, List order) { - if (supported == null || supported.isEmpty()) { - return null; - } - - int idx = order.indexOf(current); - if (idx < 0) { - for (BlockFace face : order) { - if (supported.contains(face)) { - return face; - } - } - - return null; - } - - for (int i = 1; i <= order.size(); i++) { - BlockFace candidate = order.get((idx + i) % order.size()); - if (supported.contains(candidate)) { - return candidate; - } - } - - return current; - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-left-click a block with an empty hand to rotate its orientation.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Xp Per Rotate for the Architect Smart Shape adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minXpPerRotate = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Orientation Option for the Architect Smart Shape adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerOrientationOption = 0.16; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectWirelessRedstone.java b/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectWirelessRedstone.java deleted file mode 100644 index 04e80d3fd..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/architect/ArchitectWirelessRedstone.java +++ /dev/null @@ -1,313 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.architect; - -import static com.volmit.adapt.api.adaptation.chunk.ChunkLoading.loadChunkAsync; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.BoundRedstoneTorch; -import com.volmit.adapt.util.*; - -import java.util.HashMap; -import java.util.Map; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Color; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.data.AnaloguePowerable; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Player; -import org.bukkit.event.Event.Result; -import org.bukkit.event.EventHandler; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; - - -public class ArchitectWirelessRedstone extends SimpleAdaptation { - private final Map cooldowns; - - public ArchitectWirelessRedstone() { - super("architect-wireless-redstone"); - registerConfiguration(ArchitectWirelessRedstone.Config.class); - setDescription(Localizer.dLocalize("architect.wireless_redstone.description")); - setDisplayName(Localizer.dLocalize("architect.wireless_redstone.name")); - setIcon(Material.REDSTONE_TORCH); - setInterval(100); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shapeless() - .key("remote-redstone-torch") - .ingredient(Material.REDSTONE_TORCH) - .ingredient(Material.TARGET) - .ingredient(Material.ENDER_PEARL) - .result(BoundRedstoneTorch.io.withData(new BoundRedstoneTorch.Data(null))) - .build()); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.REDSTONE) - .key("challenge_architect_wireless_100") - .title(Localizer.dLocalize("advancement.challenge_architect_wireless_100.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_wireless_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.REDSTONE) - .key("challenge_architect_wireless_5k") - .title(Localizer.dLocalize("advancement.challenge_architect_wireless_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_architect_wireless_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_architect_wireless_100", "architect.wireless-redstone.pulses", 100, 300); - registerMilestone("challenge_architect_wireless_5k", "architect.wireless-redstone.pulses", 5000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("architect.wireless_redstone.lore1")); - } - - - @EventHandler - public void onPlaceBlock(BlockPlaceEvent event) { - ItemStack item = event.getItemInHand(); - if (BoundRedstoneTorch.hasItemData(item) && isRedstoneTorch(item)) { - event.setBuild(false); - event.setCancelled(true); - } - } - - @EventHandler - public void onPlayerInteract(PlayerInteractEvent event) { - if (event.getHand() != EquipmentSlot.HAND && event.getHand() != EquipmentSlot.OFF_HAND) { - return; - } - - ItemStack itemInHand = event.getItem(); - - if (itemInHand == null) { - return; - } - - boolean specialItem = - isRedstoneTorch(itemInHand) && BoundRedstoneTorch.hasItemData(itemInHand); - if (!specialItem) { - return; - } - - Player player = event.getPlayer(); - - if (!hasAdaptation(player)) { - return; - } - - boolean canUseInCreative = AdaptConfig.get().allowAdaptationsInCreative; - boolean inCreative = player.getGameMode() == GameMode.CREATIVE; - if (inCreative && !canUseInCreative) { - return; - } - - if (!canInteract(event.getPlayer(), event.getPlayer().getLocation())) { - return; - } - - switch (event.getAction()) { - case LEFT_CLICK_BLOCK -> handleLeftClickBlock(event, player); - case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> handleRightClick(event, player); - } - } - - - private boolean isRedstoneTorch(ItemStack item) { - return item.getType().equals(Material.REDSTONE_TORCH); - } - - - private void handleLeftClickBlock(PlayerInteractEvent event, Player player) { - Adapt.verbose("Player " + player.getName() + " is left clicking a block"); - if (!player.isSneaking()) { - return; - } - - // main hand only - if (event.getHand() != EquipmentSlot.HAND) { - return; - } - - if (event.getClickedBlock() == null) { - SoundPlayer sp = SoundPlayer.of(player); - sp.play(player.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 0.1f, 0.9f); - return; - } - - // prevent breaking block - event.setUseItemInHand(Result.DENY); - - Location location = new Location(event.getClickedBlock().getWorld(), event.getClickedBlock().getX(), event.getClickedBlock().getY(), event.getClickedBlock().getZ()); - linkTorch(player, location); - } - - private void handleRightClick(PlayerInteractEvent event, Player player) { - Adapt.verbose("Player " + player.getName() + " is right clicking"); - - if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { - event.setUseItemInHand(Result.DENY); - event.setUseInteractedBlock(Result.DENY); - } - - if (hasCooldown(player)) { - SoundPlayer sp = SoundPlayer.of(player); - sp.play(player.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 0.1f, 0.9f); - } else { - cooldowns.put(player, System.currentTimeMillis() + getConfig().cooldown); - updatePlayerCooldown(player, false); - triggerPulse(player, event.getItem()); - } - } - - public void updatePlayerCooldown(Player player, boolean reset) { - player.setCooldown(Material.REDSTONE_TORCH, reset ? 0 : 5000); - } - - - private boolean hasCooldown(Player i) { - if (cooldowns.containsKey(i)) { - if (M.ms() >= cooldowns.get(i)) { - cooldowns.remove(i); - } - } - return cooldowns.containsKey(i); - } - - - private void linkTorch(Player p, Location l) { - if (!l.getBlock().getType().equals(Material.TARGET)) { - return; - } - if (areParticlesEnabled()) { - vfxCuboidOutline(l.getBlock(), l.getBlock(), Color.RED, 1); - } - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(l, Sound.BLOCK_CHEST_OPEN, 0.1f, 9f); - spw.play(l, Sound.ENTITY_ENDER_EYE_DEATH, 0.2f, 0.48f); - ItemStack hand = p.getInventory().getItemInMainHand(); - if (hand.getAmount() == 1) { - BoundRedstoneTorch.setData(hand, l); - } else { - hand.setAmount(hand.getAmount() - 1); - ItemStack torch = BoundRedstoneTorch.withData(l); - p.getInventory().addItem(torch).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); - } - } - - - private void triggerPulse(Player p, ItemStack item) { - Location l = BoundRedstoneTorch.getLocation(item); - if (isBound(item) && l != null) { - loadChunkAsync(l, chunk -> { - Block b = l.getBlock(); - BlockData data = b.getBlockData(); - if (data instanceof AnaloguePowerable redBlock && b.getType().equals(Material.TARGET)) { - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(l, Sound.BLOCK_CHEST_OPEN, 0.1f, 9f); - redBlock.setPower(15); - vfxCuboidOutline(l.getBlock(), l.getBlock(), Color.RED, 1); - b.setBlockData(redBlock); - getPlayer(p).getData().addStat("architect.wireless-redstone.pulses", 1); - J.s(() -> { - redBlock.setPower(0); - b.setBlockData(redBlock); - }, 2); - } else { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 0.1f, 0.9f); - } - }); - } - } - - private boolean isBound(ItemStack stack) { - return (stack.getType().equals(Material.REDSTONE_TORCH) && BoundRedstoneTorch.getLocation(stack) != null); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - ItemStack hand = p.getInventory().getItemInMainHand(); - ItemStack offhand = p.getInventory().getItemInOffHand(); - if ((isRedstoneTorch(hand) && BoundRedstoneTorch.hasItemData(hand)) || ( - isRedstoneTorch(offhand) && BoundRedstoneTorch.hasItemData(offhand))) { - J.s(() -> updatePlayerCooldown(p, false)); - } else { - J.s(() -> updatePlayerCooldown(p, true)); - } - } - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Use a crafted redstone remote to toggle redstone at a distance.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Architect Wireless Redstone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public int cooldown = 125; - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Architect Wireless Redstone adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeChop.java b/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeChop.java deleted file mode 100644 index 9dcc00e15..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeChop.java +++ /dev/null @@ -1,207 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.axe; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; - -public class AxeChop extends SimpleAdaptation { - - public AxeChop() { - super("axe-chop"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("axe.chop.description")); - setDisplayName(Localizer.dLocalize("axe.chop.name")); - setIcon(Material.IRON_AXE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(6911); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_AXE) - .key("challenge_axe_chop_100") - .title(Localizer.dLocalize("advancement.challenge_axe_chop_100.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_chop_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_AXE) - .key("challenge_axe_chop_2500") - .title(Localizer.dLocalize("advancement.challenge_axe_chop_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_chop_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.NETHERITE_AXE) - .key("challenge_axe_chop_one_swing") - .title(Localizer.dLocalize("advancement.challenge_axe_chop_one_swing.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_chop_one_swing.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_axe_chop_100", "axe.chop.trees-felled", 100, 400); - registerMilestone("challenge_axe_chop_2500", "axe.chop.trees-felled", 2500, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + level + C.GRAY + " " + Localizer.dLocalize("axe.chop.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTime(getLevelPercent(level)) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("axe.chop.lore2")); - v.addLore(C.RED + "- " + getDamagePerBlock(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("axe.chop.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - if (p.getCooldown(p.getInventory().getItemInMainHand().getType()) > 0) { - return; - } - - if (e.getClickedBlock() != null && e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && isAxe(p.getInventory().getItemInMainHand()) && hasAdaptation(p)) { - if (!canBlockBreak(p, e.getClickedBlock().getLocation())) { - return; - } - BlockData b = e.getClickedBlock().getBlockData(); - if (isLog(new ItemStack(b.getMaterial()))) { - e.setCancelled(true); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ITEM_AXE_STRIP, 1.25f, 0.6f); - int logsChopped = 0; - for (int i = 0; i < getLevel(p); i++) { - if (breakStuff(e.getClickedBlock(), getRange(getLevel(p)), p)) { - logsChopped++; - p.setCooldown(p.getInventory().getItemInMainHand().getType(), getCooldownTime(getLevelPercent(p))); - damageHand(p, getDamagePerBlock(getLevelPercent(p))); - } - } - if (logsChopped > 0) { - getPlayer(p).getData().addStat("axe.chop.trees-felled", 1); - if (logsChopped >= 30 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_axe_chop_one_swing")) { - getPlayer(p).getAdvancementHandler().grant("challenge_axe_chop_one_swing"); - } - } - } - } - } - - private int getRange(int level) { - return level * getConfig().rangeLevelMultiplier; - } - - private int getCooldownTime(double levelPercent) { - return (int) (getConfig().cooldownTicksBase + (getConfig().cooldownTicksInverseLevelMultiplier * ((1D - levelPercent)))); - } - - private int getDamagePerBlock(double levelPercent) { - return (int) (getConfig().damagePerBlockBase + (getConfig().damagePerBlockInverseLevelMultiplier * ((1D - levelPercent)))); - } - - private boolean breakStuff(Block b, int power, Player player) { - Block last = b; - for (int i = b.getY(); i < power + b.getY(); i++) { - Block bb = b.getWorld().getBlockAt(b.getX(), i, b.getZ()); - if (isLog(new ItemStack(bb.getType()))) { - last = bb; - } else { - break; - } - } - - if (!canBlockBreak(player, last.getLocation())) { - Adapt.verbose("Player " + player.getName() + " doesn't have permission."); - return false; - } - - if (!isLog(new ItemStack(last.getType()))) { - return false; - } - - Block ll = last; - - SoundPlayer spw = SoundPlayer.of(b.getWorld()); - spw.play(ll.getLocation(), Sound.ITEM_AXE_STRIP, 0.75f, 1.3f); - - player.breakBlock(ll); - return true; - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Chop down trees by right-clicking the base log.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Range Level Multiplier for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int rangeLevelMultiplier = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Inverse Level Multiplier for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksInverseLevelMultiplier = 16; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Per Block Base for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damagePerBlockBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Per Block Inverse Level Multiplier for the Axe Chop adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damagePerBlockInverseLevelMultiplier = 4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeCraftLogSwap.java b/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeCraftLogSwap.java deleted file mode 100644 index af55a4aeb..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeCraftLogSwap.java +++ /dev/null @@ -1,1076 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.axe; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; -import com.volmit.adapt.util.reflect.registries.Materials; - -public class AxeCraftLogSwap extends SimpleAdaptation { - - public AxeCraftLogSwap() { - super("axe-logswap"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("axe.log_swap.description")); - setDisplayName(Localizer.dLocalize("axe.log_swap.name")); - setIcon(Material.MUDDY_MANGROVE_ROOTS); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(17773); - - //Birch -> Types - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapbirchoak") - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.OAK_SAPLING) - .result(new ItemStack(Material.OAK_PLANKS, 1)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapbirchacacia") - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.ACACIA_SAPLING) - .result(new ItemStack(Material.ACACIA_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapbirchdarkoak") - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.DARK_OAK_SAPLING) - .result(new ItemStack(Material.DARK_OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapbirchjungle") - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.JUNGLE_SAPLING) - .result(new ItemStack(Material.JUNGLE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapbirchspruce") - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.SPRUCE_SAPLING) - .result(new ItemStack(Material.SPRUCE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapbirchmangrove") - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.MANGROVE_PROPAGULE) - .result(new ItemStack(Material.MANGROVE_LOG, 8)) - .build()); - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapbirchcherry") - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Materials.CHERRY_SAPLING) - .result(new ItemStack(Materials.CHERRY_LOG, 8)) - .build()); - } - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapbirchpaleoak") - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Material.BIRCH_LOG) - .ingredient(Materials.PALE_OAK_SAPLING) - .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) - .build()); - } - - //Oak -> Types - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapoakbirch") - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.BIRCH_SAPLING) - .result(new ItemStack(Material.BIRCH_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapoakacacia") - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.ACACIA_SAPLING) - .result(new ItemStack(Material.ACACIA_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapoakdarkoak") - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.DARK_OAK_SAPLING) - .result(new ItemStack(Material.DARK_OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapoakjungle") - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.JUNGLE_SAPLING) - .result(new ItemStack(Material.JUNGLE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapoakspruce") - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.SPRUCE_SAPLING) - .result(new ItemStack(Material.SPRUCE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapoakmangrove") - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.MANGROVE_PROPAGULE) - .result(new ItemStack(Material.MANGROVE_LOG, 8)) - .build()); - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapoakcherry") - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Materials.CHERRY_SAPLING) - .result(new ItemStack(Materials.CHERRY_LOG, 8)) - .build()); - } - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapoakpaleoak") - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Material.OAK_LOG) - .ingredient(Materials.PALE_OAK_SAPLING) - .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) - .build()); - } - - //Acacia -> Types - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapacaciabirch") - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.BIRCH_SAPLING) - .result(new ItemStack(Material.BIRCH_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapacaciaoak") - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.OAK_SAPLING) - .result(new ItemStack(Material.OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapacaciadarkoak") - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.DARK_OAK_SAPLING) - .result(new ItemStack(Material.DARK_OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapacaciajungle") - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.JUNGLE_SAPLING) - .result(new ItemStack(Material.JUNGLE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapacaciaspruce") - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.SPRUCE_SAPLING) - .result(new ItemStack(Material.SPRUCE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapacaciamangrove") - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.MANGROVE_PROPAGULE) - .result(new ItemStack(Material.MANGROVE_LOG, 8)) - .build()); - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapacaciacherry") - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Materials.CHERRY_SAPLING) - .result(new ItemStack(Materials.CHERRY_LOG, 8)) - .build()); - } - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapacaciapaleoak") - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Material.ACACIA_LOG) - .ingredient(Materials.PALE_OAK_SAPLING) - .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) - .build()); - } - - //Dark Oak -> Types - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapdarkoakbirch") - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.BIRCH_SAPLING) - .result(new ItemStack(Material.BIRCH_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapdarkoakoak") - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.OAK_SAPLING) - .result(new ItemStack(Material.OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapdarkoakacacia") - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.ACACIA_SAPLING) - .result(new ItemStack(Material.ACACIA_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapdarkoakjungle") - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.JUNGLE_SAPLING) - .result(new ItemStack(Material.JUNGLE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapdarkoakspruce") - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.SPRUCE_SAPLING) - .result(new ItemStack(Material.SPRUCE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapdarkoakmangrove") - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.MANGROVE_PROPAGULE) - .result(new ItemStack(Material.MANGROVE_LOG, 8)) - .build()); - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapdarkoakcherry") - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Materials.CHERRY_SAPLING) - .result(new ItemStack(Materials.CHERRY_LOG, 8)) - .build()); - } - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapdarkoakpaleoak") - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Material.DARK_OAK_LOG) - .ingredient(Materials.PALE_OAK_SAPLING) - .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) - .build()); - } - - //Jungle -> Types - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapjunglebirch") - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.BIRCH_SAPLING) - .result(new ItemStack(Material.BIRCH_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapjungleoak") - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.OAK_SAPLING) - .result(new ItemStack(Material.OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapjungleacacia") - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.ACACIA_SAPLING) - .result(new ItemStack(Material.ACACIA_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapjungledarkoak") - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.DARK_OAK_SAPLING) - .result(new ItemStack(Material.DARK_OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapjunglespruce") - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.SPRUCE_SAPLING) - .result(new ItemStack(Material.SPRUCE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapjunglemangrove") - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.MANGROVE_PROPAGULE) - .result(new ItemStack(Material.MANGROVE_LOG, 8)) - .build()); - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapjunglecherry") - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Materials.CHERRY_SAPLING) - .result(new ItemStack(Materials.CHERRY_LOG, 8)) - .build()); - } - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapjunglepaleoak") - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Material.JUNGLE_LOG) - .ingredient(Materials.PALE_OAK_SAPLING) - .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) - .build()); - } - - //Spruce -> Types - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapsprucebirch") - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.BIRCH_SAPLING) - .result(new ItemStack(Material.BIRCH_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapspruceoak") - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.OAK_SAPLING) - .result(new ItemStack(Material.OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapspruceacacia") - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.ACACIA_SAPLING) - .result(new ItemStack(Material.ACACIA_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapsprucedarkoak") - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.DARK_OAK_SAPLING) - .result(new ItemStack(Material.DARK_OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapsprucejungle") - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.JUNGLE_SAPLING) - .result(new ItemStack(Material.JUNGLE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapsprucemangrove") - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.MANGROVE_PROPAGULE) - .result(new ItemStack(Material.MANGROVE_LOG, 8)) - .build()); - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapsprucecherry") - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Materials.CHERRY_SAPLING) - .result(new ItemStack(Materials.CHERRY_LOG, 8)) - .build()); - } - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapsprucepaleoak") - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Material.SPRUCE_LOG) - .ingredient(Materials.PALE_OAK_SAPLING) - .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) - .build()); - } - - //Mangrove -> Types - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapmangrovebirch") - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.BIRCH_SAPLING) - .result(new ItemStack(Material.BIRCH_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapmangroveoak") - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.OAK_SAPLING) - .result(new ItemStack(Material.OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapmangroveacacia") - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.ACACIA_SAPLING) - .result(new ItemStack(Material.ACACIA_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapmangrovedarkoak") - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.DARK_OAK_SAPLING) - .result(new ItemStack(Material.DARK_OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapmangrovejungle") - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.JUNGLE_SAPLING) - .result(new ItemStack(Material.JUNGLE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapmangrovespruce") - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.SPRUCE_SAPLING) - .result(new ItemStack(Material.SPRUCE_LOG, 8)) - .build()); - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapmangrovecherry") - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Materials.CHERRY_SAPLING) - .result(new ItemStack(Materials.CHERRY_LOG, 8)) - .build()); - } - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapmangrovepaleoak") - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Material.MANGROVE_LOG) - .ingredient(Materials.PALE_OAK_SAPLING) - .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) - .build()); - } - - //Cherry -> Types - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapcherrybirch") - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Material.BIRCH_SAPLING) - .result(new ItemStack(Material.BIRCH_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapcherryoak") - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Material.OAK_SAPLING) - .result(new ItemStack(Material.OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapcherryacacia") - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Material.ACACIA_SAPLING) - .result(new ItemStack(Material.ACACIA_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapcherrydarkoak") - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Material.DARK_OAK_SAPLING) - .result(new ItemStack(Material.DARK_OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapcherryjungle") - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Material.JUNGLE_SAPLING) - .result(new ItemStack(Material.JUNGLE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapcherryspruce") - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Material.SPRUCE_SAPLING) - .result(new ItemStack(Material.SPRUCE_LOG, 8)) - .build()); - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swapcherrypaleoak") - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.CHERRY_LOG) - .ingredient(Materials.PALE_OAK_SAPLING) - .result(new ItemStack(Materials.PALE_OAK_LOG, 8)) - .build()); - } - } - - //Pale Oak -> Types - if (Materials.PALE_OAK_LOG != null && Materials.PALE_OAK_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swappaleoakbirch") - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Material.BIRCH_SAPLING) - .result(new ItemStack(Material.BIRCH_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swappaleoakoak") - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Material.OAK_SAPLING) - .result(new ItemStack(Material.OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swappaleoakacacia") - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Material.ACACIA_SAPLING) - .result(new ItemStack(Material.ACACIA_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swappaleoakdarkoak") - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Material.DARK_OAK_SAPLING) - .result(new ItemStack(Material.DARK_OAK_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swappaleoakjungle") - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Material.JUNGLE_SAPLING) - .result(new ItemStack(Material.JUNGLE_LOG, 8)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swappaleoakspruce") - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Material.SPRUCE_SAPLING) - .result(new ItemStack(Material.SPRUCE_LOG, 8)) - .build()); - if (Materials.CHERRY_LOG != null && Materials.CHERRY_SAPLING != null) { - registerRecipe(AdaptRecipe.shapeless() - .key("axe-swappaleoakcherry") - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.PALE_OAK_LOG) - .ingredient(Materials.CHERRY_SAPLING) - .result(new ItemStack(Materials.CHERRY_LOG, 8)) - .build()); - } - } - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.OAK_SAPLING) - .key("challenge_axe_log_swap_500") - .title(Localizer.dLocalize("advancement.challenge_axe_log_swap_500.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_log_swap_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_axe_log_swap_500", "axe.log-swap.conversions", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("axe.log_swap.lore1")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getWhoClicked() instanceof Player p) || !hasAdaptation(p)) { - return; - } - if (e.getRecipe() instanceof org.bukkit.inventory.ShapelessRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && recipe.getKey().getKey().startsWith("axe-swap")) { - getPlayer(p).getData().addStat("axe.log-swap.conversions", 1); - } - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Convert log types using a sapling in a crafting table.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeDropToInventory.java b/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeDropToInventory.java deleted file mode 100644 index b97dceee5..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeDropToInventory.java +++ /dev/null @@ -1,136 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.axe; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockDropItemEvent; - -import java.util.List; - -public class AxeDropToInventory extends SimpleAdaptation { - public AxeDropToInventory() { - super("axe-drop-to-inventory"); - registerConfiguration(AxeDropToInventory.Config.class); - setDescription(Localizer.dLocalize("pickaxe.drop_to_inventory.description")); - setDisplayName(Localizer.dLocalize("axe.drop_to_inventory.name")); - setIcon(Material.BARREL); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(8800); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_axe_dti_5k") - .title(Localizer.dLocalize("advancement.challenge_axe_dti_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_dti_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_axe_dti_5k", "axe.drop-to-inv.items-caught", 5000, 500); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("pickaxe.drop_to_inventory.lore1")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockDropItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - if (p.getGameMode() != GameMode.SURVIVAL) { - return; - } - if (!canBlockBreak(p, e.getBlock().getLocation())) { - return; - } - if (ItemListings.toolAxes.contains(p.getInventory().getItemInMainHand().getType())) { - List items = new KList<>(e.getItems()); - e.getItems().clear(); - int caught = 0; - for (Item i : items) { - sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); - if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { - p.getWorld().dropItem(p.getLocation(), i.getItemStack()); - } - caught++; - } - if (caught > 0) { - getPlayer(p).getData().addStat("axe.drop-to-inv.items-caught", caught); - } - } - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - - @Override - public void onTick() { - } - - @NoArgsConstructor - @ConfigDescription("Chopped wood drops directly into your inventory.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeGroundSmash.java b/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeGroundSmash.java deleted file mode 100644 index d2b29cb30..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeGroundSmash.java +++ /dev/null @@ -1,187 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.axe; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.SoundCategory; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; - -public class AxeGroundSmash extends SimpleAdaptation { - public AxeGroundSmash() { - super("axe-ground-smash"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("axe.ground_smash.description")); - setDisplayName(Localizer.dLocalize("axe.ground_smash.name")); - setIcon(Material.NETHERITE_AXE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(4333); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_AXE) - .key("challenge_axe_ground_smash_500") - .title(Localizer.dLocalize("advancement.challenge_axe_ground_smash_500.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_ground_smash_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.NETHERITE_AXE) - .key("challenge_axe_ground_smash_5") - .title(Localizer.dLocalize("advancement.challenge_axe_ground_smash_5.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_ground_smash_5.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_axe_ground_smash_500", "axe.ground-smash.mobs-hit", 500, 500); - } - - @Override - public void addStats(int level, Element v) { - double f = getLevelPercent(level); - v.addLore(C.RED + "+ " + Form.f(getFalloffDamage(f), 1) + " - " + Form.f(getDamage(f), 1) + C.GRAY + " " + Localizer.dLocalize("axe.ground_smash.lore1")); - v.addLore(C.RED + "+ " + Form.f(getRadius(f), 1) + C.GRAY + " " + Localizer.dLocalize("axe.ground_smash.lore2")); - v.addLore(C.RED + "+ " + Form.pc(getForce(f), 0) + C.GRAY + " " + Localizer.dLocalize("axe.ground_smash.lore3")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTime(getLevelPercent(level)) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("axe.ground_smash.lore4")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p && hasAdaptation(p) && p.isSneaking()) { - if (!isAxe(p.getInventory().getItemInMainHand())) { - return; - } - - double f = getLevelPercent(p); - - if (p.hasCooldown(p.getInventory().getItemInMainHand().getType())) { - return; - } - - p.setCooldown(p.getInventory().getItemInMainHand().getType(), getCooldownTime(f)); - double radius = getRadius(f); - new Impulse(radius) - .damage(getDamage(f), getFalloffDamage(f)) - .force(getForce(f)) - .punch(e.getEntity().getLocation()); - int mobsHit = 0; - for (Entity nearby : e.getEntity().getWorld().getNearbyEntities(e.getEntity().getLocation(), radius, radius, radius)) { - if (nearby instanceof LivingEntity && nearby != p) { - mobsHit++; - } - } - if (mobsHit > 0) { - getPlayer(p).getData().addStat("axe.ground-smash.mobs-hit", mobsHit); - if (mobsHit >= 5 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_axe_ground_smash_5")) { - getPlayer(p).getAdvancementHandler().grant("challenge_axe_ground_smash_5"); - } - } - SoundPlayer spw = SoundPlayer.of(e.getEntity().getWorld()); - spw.play(e.getEntity().getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, SoundCategory.HOSTILE, 0.6f, 0.4f); - spw.play(e.getEntity().getLocation(), Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR, SoundCategory.HOSTILE, 0.5f, 0.1f); - spw.play(e.getEntity().getLocation(), Sound.ENTITY_TURTLE_EGG_CRACK, SoundCategory.HOSTILE, 1f, 0.4f); - } - } - - - public int getCooldownTime(double factor) { - return (int) (((1D - factor) * getConfig().cooldownTicksInverseLevelMultiplier) + getConfig().cooldownTicksBase); - } - - public double getRadius(double factor) { - return getConfig().radiusLevelFactorMultiplier * factor; - } - - public double getDamage(double factor) { - return getConfig().damageLevelFactorMultiplier * factor; - } - - public double getForce(double factor) { - return (getConfig().forceFactorMultiplier * factor) + getConfig().forceBase; - } - - public double getFalloffDamage(double factor) { - return getConfig().falloffFactor * factor; - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Jump then crouch to smash all nearby enemies with your axe.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Falloff Factor for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double falloffFactor = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Level Factor Multiplier for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusLevelFactorMultiplier = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Level Factor Multiplier for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageLevelFactorMultiplier = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Force Factor Multiplier for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double forceFactorMultiplier = 1.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Force Base for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double forceBase = 0.27; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Inverse Level Multiplier for the Axe Ground Smash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksInverseLevelMultiplier = 225; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeLeafVeinminer.java b/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeLeafVeinminer.java deleted file mode 100644 index e0fa06af0..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeLeafVeinminer.java +++ /dev/null @@ -1,211 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.axe; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Particles; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.*; - -import static com.volmit.adapt.util.data.Metadata.VEIN_MINED; - -public class AxeLeafVeinminer extends SimpleAdaptation { - public AxeLeafVeinminer() { - super("axe-leaf-veinminer"); - registerConfiguration(AxeLeafVeinminer.Config.class); - setDescription(Localizer.dLocalize("axe.leaf_miner.description")); - setDisplayName(Localizer.dLocalize("axe.leaf_miner.name")); - setIcon(Material.BIRCH_LEAVES); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(5849); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.OAK_LEAVES) - .key("challenge_axe_leaf_5k") - .title(Localizer.dLocalize("advancement.challenge_axe_leaf_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_leaf_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_axe_leaf_5k", "axe.leaf-veinminer.leaves-broken", 5000, 400); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("axe.leaf_miner.lore1")); - v.addLore(C.GREEN + "" + (level + getConfig().baseRange) + C.GRAY + " " + Localizer.dLocalize("axe.leaf_miner.lore2")); - v.addLore(C.ITALIC + Localizer.dLocalize("axe.leaf_miner.lore3")); - } - - private int getRadius(int lvl) { - return lvl + getConfig().baseRange; - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - - if (VEIN_MINED.get(e.getBlock())) { - return; - } - - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - - if (!p.isSneaking()) { - return; - } - - if (!isAxe(p.getInventory().getItemInMainHand())) { - return; - } - - Material blockType = e.getBlock().getType(); - if (!blockType.isItem() || !isLeaves(new ItemStack(blockType))) { - return; - } - - VEIN_MINED.add(e.getBlock()); - - Block block = e.getBlock(); - Map blockMap = new HashMap<>(); - Deque stack = new LinkedList<>(); - stack.push(block); - int radius = getRadius(getLevel(p)); - int radiusSquared = radius * radius; - while (!stack.isEmpty() && blockMap.size() < radius) { - Block currentBlock = stack.pop(); - if (blockMap.containsKey(currentBlock.getLocation())) continue; - blockMap.put(currentBlock.getLocation(), currentBlock); - for (int x = -1; x <= 1; x++) { - for (int y = -1; y <= 1; y++) { - for (int z = -1; z <= 1; z++) { - if (x == 0 && y == 0 && z == 0) continue; - Block b = currentBlock.getRelative(x, y, z); - if (b.getType() != block.getType() - || blockMap.containsKey(b.getLocation()) - || stack.contains(b)) - continue; - if (currentBlock.getLocation().distanceSquared(b.getLocation()) <= radiusSquared && canBlockBreak(p, b.getLocation())) { - stack.push(b); - } - } - } - } - } - - int leavesBroken = blockMap.size(); - J.s(() -> { - for (Location l : blockMap.keySet()) { - Block b = block.getWorld().getBlockAt(l); - PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("axes"); - PlayerAdaptation adaptation = line != null ? line.getAdaptation("axe-drop-to-inventory") : null; - - VEIN_MINED.add(b); - if (adaptation != null && adaptation.getLevel() > 0) { - Collection items = block.getDrops(); - for (ItemStack i : items) { - sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.01f, 0.01f); - HashMap extra = p.getInventory().addItem(i); - if (!extra.isEmpty()) { - p.getWorld().dropItem(p.getLocation(), extra.get(0)); - } - } - p.breakBlock(b); - } else { - b.breakNaturally(p.getItemInUse()); - SoundPlayer spw = SoundPlayer.of(block.getWorld()); - spw.play(b.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 0.01f, 0.25f); - if (areParticlesEnabled()) { - b.getWorld().spawnParticle(Particle.ASH, b.getLocation().add(0.5, 0.5, 0.5), 25, 0.5, 0.5, 0.5, 0.1); - } - } - if (areParticlesEnabled()) { - this.vfxCuboidOutline(b, Particles.ENCHANTMENT_TABLE); - } - VEIN_MINED.remove(b); - } - VEIN_MINED.remove(block); - }); - if (leavesBroken > 0) { - getPlayer(p).getData().addStat("axe.leaf-veinminer.leaves-broken", leavesBroken); - } - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Break bulk leaves at once while sneaking.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Axe Leaf Veinminer adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.325; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Range for the Axe Leaf Veinminer adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseRange = 5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeTimberMark.java b/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeTimberMark.java deleted file mode 100644 index 1c10bf28d..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeTimberMark.java +++ /dev/null @@ -1,320 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.axe; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Event; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.EquipmentSlot; - -import java.util.ArrayDeque; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -public class AxeTimberMark extends SimpleAdaptation { - public AxeTimberMark() { - super("axe-timber-mark"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("axe.timber_mark.description")); - setDisplayName(Localizer.dLocalize("axe.timber_mark.name")); - setIcon(Material.OAK_LOG); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.OAK_LOG) - .key("challenge_axe_timber_200") - .title(Localizer.dLocalize("advancement.challenge_axe_timber_200.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_timber_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_AXE) - .key("challenge_axe_timber_40") - .title(Localizer.dLocalize("advancement.challenge_axe_timber_40.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_timber_40.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_axe_timber_200", "axe.timber-mark.marks-felled", 200, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getMaxBlocks(level) + C.GRAY + " " + Localizer.dLocalize("axe.timber_mark.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getMarkDurationMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("axe.timber_mark.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - UUID id = e.getPlayer().getUniqueId(); - setStorage(e.getPlayer(), "timberMarkBlock", null); - setStorage(e.getPlayer(), "timberMarkUntil", 0L); - setStorage(e.getPlayer(), "timberMarkOwner", id.toString()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - if (e.isCancelled() || e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getClickedBlock() == null) { - return; - } - - if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking() || !isAxe(p.getInventory().getItemInMainHand())) { - return; - } - - Block clicked = e.getClickedBlock(); - if (!isLog(new org.bukkit.inventory.ItemStack(clicked.getType()))) { - return; - } - - setStorage(p, "timberMarkBlock", clicked.getLocation().toString()); - setStorage(p, "timberMarkUntil", System.currentTimeMillis() + getMarkDurationMillis(getLevel(p))); - e.setUseInteractedBlock(Event.Result.DENY); - e.setUseItemInHand(Event.Result.DENY); - e.setCancelled(true); - SoundPlayer.of(p.getWorld()).play(clicked.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 0.6f, 1.8f); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !isAxe(p.getInventory().getItemInMainHand())) { - return; - } - - Long until = getStorageLong(p, "timberMarkUntil", 0L); - String marked = getStorageString(p, "timberMarkBlock", ""); - if (until == null || until < System.currentTimeMillis() || marked == null || marked.isEmpty()) { - return; - } - - if (!e.getBlock().getLocation().toString().equals(marked)) { - return; - } - - Material type = e.getBlock().getType(); - int maxBlocks = getMaxBlocks(getLevel(p)); - Set connected = floodLogs(e.getBlock(), type, maxBlocks); - int logsFelled = 0; - for (Block b : connected) { - if (b.equals(e.getBlock())) { - continue; - } - - if (!canBlockBreak(p, b.getLocation())) { - continue; - } - - b.breakNaturally(p.getInventory().getItemInMainHand()); - xp(p, getConfig().xpPerExtraLog); - logsFelled++; - } - - int level = getLevel(p); - Set leaves = floodLeaves(connected, getMaxLeaves(level)); - for (Block leaf : leaves) { - if (!canBlockBreak(p, leaf.getLocation())) { - continue; - } - - leaf.breakNaturally(p.getInventory().getItemInMainHand()); - xp(p, getConfig().xpPerLeafCleared); - } - - SoundPlayer.of(p.getWorld()).play(e.getBlock().getLocation(), Sound.BLOCK_WOOD_BREAK, 0.6f, 0.8f); - setStorage(p, "timberMarkUntil", 0L); - setStorage(p, "timberMarkBlock", ""); - getPlayer(p).getData().addStat("axe.timber-mark.marks-felled", 1); - if (logsFelled >= 40 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_axe_timber_40")) { - getPlayer(p).getAdvancementHandler().grant("challenge_axe_timber_40"); - } - } - - private Set floodLogs(Block start, Material type, int maxBlocks) { - Set visited = new HashSet<>(); - ArrayDeque queue = new ArrayDeque<>(); - queue.add(start); - visited.add(start); - while (!queue.isEmpty() && visited.size() < maxBlocks) { - Block b = queue.poll(); - for (int x = -1; x <= 1; x++) { - for (int y = -1; y <= 1; y++) { - for (int z = -1; z <= 1; z++) { - Block n = b.getRelative(x, y, z); - if (n.getType() != type || visited.contains(n)) { - continue; - } - - visited.add(n); - queue.add(n); - if (visited.size() >= maxBlocks) { - return visited; - } - } - } - } - } - return visited; - } - - private Set floodLeaves(Set logs, int maxLeaves) { - Set visited = new HashSet<>(); - ArrayDeque queue = new ArrayDeque<>(); - - for (Block log : logs) { - for (int x = -1; x <= 1; x++) { - for (int y = -1; y <= 1; y++) { - for (int z = -1; z <= 1; z++) { - Block n = log.getRelative(x, y, z); - if (!isLeafMaterial(n.getType()) || visited.contains(n)) { - continue; - } - - visited.add(n); - queue.add(n); - if (visited.size() >= maxLeaves) { - return visited; - } - } - } - } - } - - while (!queue.isEmpty() && visited.size() < maxLeaves) { - Block b = queue.poll(); - for (int x = -1; x <= 1; x++) { - for (int y = -1; y <= 1; y++) { - for (int z = -1; z <= 1; z++) { - Block n = b.getRelative(x, y, z); - if (!isLeafMaterial(n.getType()) || visited.contains(n)) { - continue; - } - - visited.add(n); - queue.add(n); - if (visited.size() >= maxLeaves) { - return visited; - } - } - } - } - } - - return visited; - } - - private boolean isLeafMaterial(Material type) { - return type.name().endsWith("_LEAVES"); - } - - private int getMaxBlocks(int level) { - return Math.max(8, (int) Math.round(getConfig().maxBlocksBase + (getLevelPercent(level) * getConfig().maxBlocksFactor))); - } - - private long getMarkDurationMillis(int level) { - return (long) Math.max(1000, Math.round(getConfig().markDurationMillisBase + (getLevelPercent(level) * getConfig().markDurationMillisFactor))); - } - - private int getMaxLeaves(int level) { - return Math.max(0, (int) Math.round(getConfig().maxLeavesBase + (getLevelPercent(level) * getConfig().maxLeavesFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Mark a trunk, then break the marked log to fell connected wood.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Blocks Base for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxBlocksBase = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Blocks Factor for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxBlocksFactor = 56; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mark Duration Millis Base for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double markDurationMillisBase = 6000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mark Duration Millis Factor for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double markDurationMillisFactor = 9000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Extra Log for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerExtraLog = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Leaves Base for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxLeavesBase = 24; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Leaves Factor for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxLeavesFactor = 180; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Leaf Cleared for the Axe Timber Mark adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerLeafCleared = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeWoodVeinminer.java b/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeWoodVeinminer.java deleted file mode 100644 index 30826927a..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/axe/AxeWoodVeinminer.java +++ /dev/null @@ -1,222 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.axe; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Particles; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import static com.volmit.adapt.util.data.Metadata.VEIN_MINED; - -public class AxeWoodVeinminer extends SimpleAdaptation { - public AxeWoodVeinminer() { - super("axe-wood-veinminer"); - registerConfiguration(AxeWoodVeinminer.Config.class); - setDescription(Localizer.dLocalize("axe.wood_miner.description")); - setDisplayName(Localizer.dLocalize("axe.wood_miner.name")); - setIcon(Material.DIAMOND_AXE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(5849); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.OAK_LOG) - .key("challenge_axe_wood_vein_2500") - .title(Localizer.dLocalize("advancement.challenge_axe_wood_vein_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_wood_vein_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_AXE) - .key("challenge_axe_wood_vein_cascade") - .title(Localizer.dLocalize("advancement.challenge_axe_wood_vein_cascade.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_wood_vein_cascade.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_axe_wood_vein_2500", "axe.wood-veinminer.logs-veinmined", 2500, 500); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("axe.wood_miner.lore1")); - v.addLore(C.GREEN + "" + (level + getConfig().baseRange) + C.GRAY + " " + Localizer.dLocalize("axe.wood_miner.lore2")); - v.addLore(C.ITALIC + Localizer.dLocalize("axe.wood_miner.lore3")); - } - - private int getRadius(int lvl) { - return lvl + getConfig().baseRange; - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - if (VEIN_MINED.get(e.getBlock())) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - - if (!p.isSneaking()) { - return; - } - - if (!isAxe(p.getInventory().getItemInMainHand())) { - return; - } - - if (!isLog(new ItemStack(e.getBlock().getType()))) { - return; - } - - VEIN_MINED.add(e.getBlock()); - Block block = e.getBlock(); - Set blockMap = new HashSet<>(); - int blockCount = 0; - int radius = getRadius(getLevel(p)); - int radiusSquared = radius * radius; - for (int i = 0; i < radius; i++) { - for (int x = -i; x <= i; x++) { - for (int y = -i; y <= i; y++) { - for (int z = -i; z <= i; z++) { - Block b = block.getRelative(x, y, z); - if (b.getType() == block.getType()) { - blockCount++; - if (blockCount > getConfig().maxBlocks) { - Adapt.verbose("Block: " + blockCount + " > " + getConfig().maxBlocks); - continue; - } - if (block.getLocation().distanceSquared(b.getLocation()) > radiusSquared) { - Adapt.verbose("Block: " + b.getLocation() + " is too far away from " + block.getLocation() + " (" + radius + ")"); - continue; - } - if (!canBlockBreak(p, b.getLocation())) { - Adapt.verbose("Player " + p.getName() + " doesn't have permission."); - continue; - } - blockMap.add(b); - } - } - } - } - } - - int logsVeinmined = blockMap.size(); - J.s(() -> { - for (Block blocks : blockMap) { - PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("axes"); - PlayerAdaptation adaptation = line != null ? line.getAdaptation("axe-drop-to-inventory") : null; - VEIN_MINED.add(blocks); - if (adaptation != null && adaptation.getLevel() > 0) { - Collection items = blocks.getDrops(); - for (ItemStack item : items) { - safeGiveItem(p, item); - Adapt.verbose("Giving item: " + item); - } - blocks.setType(Material.AIR); - } else { - blocks.breakNaturally(p.getItemInUse()); - SoundPlayer spw = SoundPlayer.of(blocks.getWorld()); - spw.play(e.getBlock().getLocation(), Sound.BLOCK_FUNGUS_BREAK, 0.01f, 0.25f); - if (areParticlesEnabled()) { - blocks.getWorld().spawnParticle(Particle.ASH, blocks.getLocation().add(0.5, 0.5, 0.5), 25, 0.5, 0.5, 0.5, 0.1); - } - } - if (areParticlesEnabled()) { - this.vfxCuboidOutline(blocks, Particles.ENCHANTMENT_TABLE); - } - VEIN_MINED.remove(blocks); - } - VEIN_MINED.remove(block); - }); - if (logsVeinmined > 0) { - getPlayer(p).getData().addStat("axe.wood-veinminer.logs-veinmined", logsVeinmined); - if (logsVeinmined >= 15 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_axe_wood_vein_cascade")) { - getPlayer(p).getAdvancementHandler().grant("challenge_axe_wood_vein_cascade"); - } - } - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Break bulk wood at once while sneaking.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Axe Wood Veinminer adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.95; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Blocks for the Axe Wood Veinminer adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxBlocks = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Range for the Axe Wood Veinminer adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseRange = 3; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingBastionStance.java b/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingBastionStance.java deleted file mode 100644 index ef46e4978..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingBastionStance.java +++ /dev/null @@ -1,211 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.blocking; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.player.PlayerVelocityEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; - -import java.util.concurrent.ThreadLocalRandom; - -public class BlockingBastionStance extends SimpleAdaptation { - public BlockingBastionStance() { - super("blocking-bastion-stance"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("blocking.bastion_stance.description")); - setDisplayName(Localizer.dLocalize("blocking.bastion_stance.name")); - setIcon(Material.SHIELD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_blocking_bastion_500") - .title(Localizer.dLocalize("advancement.challenge_blocking_bastion_500.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_bastion_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_blocking_bastion_500", "blocking.bastion-stance.projectiles-softened", 500, 500); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_blocking_bastion_10") - .title(Localizer.dLocalize("advancement.challenge_blocking_bastion_10.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_bastion_10.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getKnockbackReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.bastion_stance.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getProjectileReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.bastion_stance.lore2")); - v.addLore(C.GREEN + "+ " + Form.pc(getProjectileNegateChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.bastion_stance.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getEntity() instanceof Player defender) || !isBastionStance(defender)) { - return; - } - - if (!(e.getDamager() instanceof Projectile)) { - return; - } - - int level = getLevel(defender); - - // Track session counter for special achievement - int sessionCount = getStorageInt(defender, "bastionSessionCount", 0) + 1; - setStorage(defender, "bastionSessionCount", sessionCount); - if (sessionCount >= 10 && AdaptConfig.get().isAdvancements() && !getPlayer(defender).getData().isGranted("challenge_blocking_bastion_10")) { - getPlayer(defender).getAdvancementHandler().grant("challenge_blocking_bastion_10"); - } - - if (ThreadLocalRandom.current().nextDouble() <= getProjectileNegateChance(level)) { - e.setCancelled(true); - SoundPlayer.of(defender.getWorld()).play(defender.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1f, 0.9f); - xp(defender, getConfig().xpOnNegate); - getPlayer(defender).getData().addStat("blocking.bastion-stance.projectiles-softened", 1); - return; - } - - e.setDamage(Math.max(0, e.getDamage() * (1D - getProjectileReduction(level)))); - SoundPlayer.of(defender.getWorld()).play(defender.getLocation(), Sound.ITEM_SHIELD_BLOCK, 0.75f, 0.75f); - xp(defender, e.getDamage() * getConfig().xpPerMitigatedDamage); - getPlayer(defender).getData().addStat("blocking.bastion-stance.projectiles-softened", 1); - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(PlayerVelocityEvent e) { - Player p = e.getPlayer(); - if (!isBastionStance(p)) { - return; - } - - double factor = 1D - getKnockbackReduction(getLevel(p)); - Vector v = e.getVelocity(); - e.setVelocity(new Vector(v.getX() * factor, v.getY(), v.getZ() * factor)); - } - - private boolean isBastionStance(Player p) { - return hasAdaptation(p) && p.isBlocking() && p.isSneaking() && hasShield(p); - } - - private boolean hasShield(Player p) { - ItemStack main = p.getInventory().getItemInMainHand(); - ItemStack off = p.getInventory().getItemInOffHand(); - return (isItem(main) && main.getType() == Material.SHIELD) || (isItem(off) && off.getType() == Material.SHIELD); - } - - private double getKnockbackReduction(int level) { - return Math.min(getConfig().maxKnockbackReduction, getConfig().knockbackReductionBase + (getLevelPercent(level) * getConfig().knockbackReductionFactor)); - } - - private double getProjectileReduction(int level) { - return Math.min(getConfig().maxProjectileReduction, getConfig().projectileReductionBase + (getLevelPercent(level) * getConfig().projectileReductionFactor)); - } - - private double getProjectileNegateChance(int level) { - return Math.min(getConfig().maxProjectileNegateChance, getConfig().projectileNegateChanceBase + (getLevelPercent(level) * getConfig().projectileNegateChanceFactor)); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (hasAdaptation(p) && !isBastionStance(p)) { - setStorage(p, "bastionSessionCount", 0); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-block with a shield to brace against knockback and soften projectiles.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.68; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Knockback Reduction Base for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double knockbackReductionBase = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Knockback Reduction Factor for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double knockbackReductionFactor = 0.52; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Knockback Reduction for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxKnockbackReduction = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Projectile Reduction Base for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double projectileReductionBase = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Projectile Reduction Factor for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double projectileReductionFactor = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Projectile Reduction for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxProjectileReduction = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Projectile Negate Chance Base for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double projectileNegateChanceBase = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Projectile Negate Chance Factor for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double projectileNegateChanceFactor = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Projectile Negate Chance for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxProjectileNegateChance = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mitigated Damage for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerMitigatedDamage = 2.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Negate for the Blocking Bastion Stance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnNegate = 8.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingBulwarkBash.java b/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingBulwarkBash.java deleted file mode 100644 index a4f52dec8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingBulwarkBash.java +++ /dev/null @@ -1,297 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.blocking; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerToggleSprintEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class BlockingBulwarkBash extends SimpleAdaptation { - private final Map lastSprintMillis = new HashMap<>(); - - public BlockingBulwarkBash() { - super("blocking-bulwark-bash"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("blocking.bulwark_bash.description")); - setDisplayName(Localizer.dLocalize("blocking.bulwark_bash.name")); - setIcon(Material.BELL); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_blocking_bulwark_500") - .title(Localizer.dLocalize("advancement.challenge_blocking_bulwark_500.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_bulwark_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_blocking_bulwark_500", "blocking.bulwark-bash.mobs-bashed", 500, 500); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_blocking_bulwark_4") - .title(Localizer.dLocalize("advancement.challenge_blocking_bulwark_4.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_bulwark_4.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRange(level)) + C.GRAY + " " + Localizer.dLocalize("blocking.bulwark_bash.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getDamageBonus(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.bulwark_bash.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("blocking.bulwark_bash.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(PlayerToggleSprintEvent e) { - if (e.isSprinting()) { - lastSprintMillis.put(e.getPlayer().getUniqueId(), System.currentTimeMillis()); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - lastSprintMillis.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Player p) || !(e.getEntity() instanceof LivingEntity target) || !hasAdaptation(p)) { - return; - } - - if (p.getInventory().getItemInOffHand().getType() != Material.SHIELD || p.hasCooldown(Material.SHIELD)) { - return; - } - - if (!isJumpCrit(p) || !wasRecentlySprinting(p)) { - return; - } - - if (target instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - return; - } - } else if (!canPVE(p, target.getLocation())) { - return; - } - - int level = getLevel(p); - int affected = 0; - double radius = getRange(level); - for (org.bukkit.entity.Entity nearby : target.getWorld().getNearbyEntities(target.getLocation(), radius, radius, radius)) { - if (!(nearby instanceof LivingEntity hit) || hit == p) { - continue; - } - - if (hit instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - continue; - } - } else if (!canPVE(p, hit.getLocation())) { - continue; - } - - applyImpact(p, hit, level); - affected++; - } - - if (affected <= 0) { - return; - } - - e.setDamage(e.getDamage() + getBaseDamage(level)); - p.setCooldown(Material.SHIELD, getCooldownTicks(level)); - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.SWEEP_ATTACK, p.getLocation().add(0, 1, 0), 1, 0, 0, 0, 0); - } - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.CLOUD, p.getLocation().add(0, 0.3, 0), 18, 0.35, 0.1, 0.35, 0.06); - } - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1f, 0.85f); - sp.play(p.getLocation(), Sound.ENTITY_PLAYER_ATTACK_SWEEP, 0.9f, 0.7f); - xp(p, getConfig().xpPerTargetHit * affected); - getPlayer(p).getData().addStat("blocking.bulwark-bash.mobs-bashed", affected); - - // Special achievement: hit 4+ enemies in single bash - if (affected >= 4 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_blocking_bulwark_4")) { - getPlayer(p).getAdvancementHandler().grant("challenge_blocking_bulwark_4"); - } - } - - private void applyImpact(Player p, LivingEntity target, int level) { - Vector kb = target.getLocation().toVector().subtract(p.getLocation().toVector()).setY(0); - if (kb.lengthSquared() <= 0.0001) { - kb = p.getLocation().getDirection().setY(0); - } - if (kb.lengthSquared() <= 0.0001) { - return; - } - - kb.normalize(); - target.setVelocity(target.getVelocity().multiply(0.25).add(kb.multiply(getKnockback(level)).setY(getUpwardKnockback(level)))); - target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getStunTicks(level), getStunAmplifier(level), false, false, true), true); - } - - private boolean wasRecentlySprinting(Player p) { - long last = lastSprintMillis.getOrDefault(p.getUniqueId(), 0L); - return p.isSprinting() || (System.currentTimeMillis() - last) <= getConfig().recentSprintWindowMillis; - } - - private boolean isJumpCrit(Player p) { - return p.getFallDistance() > getConfig().minFallDistanceForCrit - && !p.isOnGround() - && !p.isInWater() - && !p.isInsideVehicle() - && !p.isClimbing(); - } - - private double getRange(int level) { - return getConfig().rangeBase + (getLevelPercent(level) * getConfig().rangeFactor); - } - - private double getDamageBonus(int level) { - return getConfig().damageBonusBase + (getLevelPercent(level) * getConfig().damageBonusFactor); - } - - private double getBaseDamage(int level) { - return getConfig().baseDamage + getDamageBonus(level); - } - - private double getKnockback(int level) { - return getConfig().knockbackBase + (getLevelPercent(level) * getConfig().knockbackFactor); - } - - private double getUpwardKnockback(int level) { - return getConfig().upwardKnockbackBase + (getLevelPercent(level) * getConfig().upwardKnockbackFactor); - } - - private int getStunTicks(int level) { - return Math.max(10, (int) Math.round(getConfig().stunTicksBase + (getLevelPercent(level) * getConfig().stunTicksFactor))); - } - - private int getStunAmplifier(int level) { - return Math.max(0, (int) Math.round(getConfig().stunAmplifierBase + (getLevelPercent(level) * getConfig().stunAmplifierFactor))); - } - - private int getCooldownTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - @Override - public void onTick() { - long cutoff = System.currentTimeMillis() - Math.max(1000L, getConfig().recentSprintWindowMillis * 3L); - lastSprintMillis.entrySet().removeIf(i -> i.getValue() < cutoff); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sprint, jump, and land a shielded crit to unleash a bash shockwave.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Damage for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseDamage = 1.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageBonusBase = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageBonusFactor = 2.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Range Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rangeBase = 2.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Range Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rangeFactor = 1.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Knockback Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double knockbackBase = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Knockback Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double knockbackFactor = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Upward Knockback Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double upwardKnockbackBase = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Upward Knockback Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double upwardKnockbackFactor = 0.14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stun Ticks Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double stunTicksBase = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stun Ticks Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double stunTicksFactor = 24; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stun Amplifier Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double stunAmplifierBase = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stun Amplifier Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double stunAmplifierFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 220; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 120; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Fall Distance For Crit for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - float minFallDistanceForCrit = 0.08f; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Recent Sprint Window Millis for the Blocking Bulwark Bash adaptation.", impact = "Allows crit bash to trigger shortly after sprint momentum, even if sprint toggles off mid-air.") - long recentSprintWindowMillis = 900L; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Target Hit for the Blocking Bulwark Bash adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerTargetHit = 8; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingChainArmorer.java b/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingChainArmorer.java deleted file mode 100644 index 04b87a3b3..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingChainArmorer.java +++ /dev/null @@ -1,145 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.blocking; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public class BlockingChainArmorer extends SimpleAdaptation { - - public BlockingChainArmorer() { - super("blocking-chainarmorer"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("blocking.chain_armorer.description")); - setDisplayName(Localizer.dLocalize("blocking.chain_armorer.name")); - setIcon(Material.CHAINMAIL_CHESTPLATE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(17774); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-chainarmorer-boots") - .ingredient(new MaterialChar('I', Material.IRON_NUGGET)) - .shapes(List.of( - "I I", - "I I")) - .result(new ItemStack(Material.CHAINMAIL_BOOTS, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-chainarmorer-leggings") - .ingredient(new MaterialChar('I', Material.IRON_NUGGET)) - .shapes(List.of( - "III", - "I I", - "I I")) - .result(new ItemStack(Material.CHAINMAIL_LEGGINGS, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-chainarmorer-chestplate") - .ingredient(new MaterialChar('I', Material.IRON_NUGGET)) - .shapes(List.of( - "I I", - "III", - "III")) - .result(new ItemStack(Material.CHAINMAIL_CHESTPLATE, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-chainarmorer-helmet") - .ingredient(new MaterialChar('I', Material.IRON_NUGGET)) - .shapes(List.of( - "III", - "I I")) - .result(new ItemStack(Material.CHAINMAIL_HELMET, 1)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CHAINMAIL_CHESTPLATE) - .key("challenge_blocking_chain_25") - .title(Localizer.dLocalize("advancement.challenge_blocking_chain_25.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_chain_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_blocking_chain_25", "blocking.chain-armorer.pieces-crafted", 25, 400); - } - - @EventHandler - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getWhoClicked() instanceof Player p && hasAdaptation(p) && isAdaptationRecipe(e.getRecipe())) { - getPlayer(p).getData().addStat("blocking.chain-armorer.pieces-crafted", 1); - } - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("blocking.chain_armorer.lore1")); - } - - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Chainmail Armor using iron nuggets.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingCounterGuard.java b/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingCounterGuard.java deleted file mode 100644 index ec27723f9..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingCounterGuard.java +++ /dev/null @@ -1,202 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.blocking; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.inventory.ItemStack; - -public class BlockingCounterGuard extends SimpleAdaptation { - public BlockingCounterGuard() { - super("blocking-counter-guard"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("blocking.counter_guard.description")); - setDisplayName(Localizer.dLocalize("blocking.counter_guard.name")); - setIcon(Material.IRON_BARS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_blocking_counter_500") - .title(Localizer.dLocalize("advancement.challenge_blocking_counter_500.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_counter_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_blocking_counter_500", "blocking.counter-guard.damage-reflected", 500, 500); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_blocking_counter_max") - .title(Localizer.dLocalize("advancement.challenge_blocking_counter_max.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_counter_max.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getMaxStacks(level) + C.GRAY + " " + Localizer.dLocalize("blocking.counter_guard.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getReflectChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.counter_guard.lore2")); - v.addLore(C.GREEN + "+ " + Form.f(getReflectDamage(level)) + C.GRAY + " " + Localizer.dLocalize("blocking.counter_guard.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getEntity() instanceof Player defender)) { - return; - } - - if (!hasAdaptation(defender) || !hasShield(defender)) { - return; - } - - int level = getLevel(defender); - int stacks = getStorageInt(defender, "counterStacks", 0); - - if (defender.isBlocking()) { - stacks = Math.min(getMaxStacks(level), stacks + 1); - setStorage(defender, "counterStacks", stacks); - } - - if (stacks <= 0 || !M.r(getReflectChance(level))) { - return; - } - - Entity source = e.getDamager(); - if (source instanceof Projectile projectile && projectile.getShooter() instanceof Entity shooter) { - source = shooter; - } - - if (!(source instanceof LivingEntity attacker)) { - return; - } - - if (attacker instanceof Player p && !canPVP(defender, p.getLocation())) { - return; - } - - if (!(attacker instanceof Player) && !canPVE(defender, attacker.getLocation())) { - return; - } - - // Special achievement: reach max stacks and release - if (stacks >= getMaxStacks(level) && AdaptConfig.get().isAdvancements() && !getPlayer(defender).getData().isGranted("challenge_blocking_counter_max")) { - getPlayer(defender).getAdvancementHandler().grant("challenge_blocking_counter_max"); - } - - double reflected = getReflectDamage(level) + (stacks * getConfig().damagePerStack); - attacker.damage(reflected, defender); - setStorage(defender, "counterStacks", Math.max(0, stacks - getConfig().stackCostOnReflect)); - xp(defender, reflected * getConfig().xpPerReflectedDamage); - getPlayer(defender).getData().addStat("blocking.counter-guard.damage-reflected", reflected); - } - - private boolean hasShield(Player p) { - ItemStack main = p.getInventory().getItemInMainHand(); - ItemStack off = p.getInventory().getItemInOffHand(); - return (isItem(main) && main.getType() == Material.SHIELD) || (isItem(off) && off.getType() == Material.SHIELD); - } - - private double getReflectChance(int level) { - return Math.min(getConfig().maxReflectChance, getConfig().reflectChanceBase + (getLevelPercent(level) * getConfig().reflectChanceFactor)); - } - - private int getMaxStacks(int level) { - return Math.max(1, (int) Math.round(getConfig().baseStacks + (getLevelPercent(level) * getConfig().stackFactor))); - } - - private double getReflectDamage(int level) { - return getConfig().baseReflectDamage + (getLevelPercent(level) * getConfig().reflectDamageFactor); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Blocking builds retaliation stacks that reflect damage back to attackers.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Stacks for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseStacks = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Factor for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double stackFactor = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflect Chance Base for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectChanceBase = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflect Chance Factor for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectChanceFactor = 0.27; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Reflect Chance for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxReflectChance = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Reflect Damage for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseReflectDamage = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflect Damage Factor for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectDamageFactor = 3.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Per Stack for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damagePerStack = 0.28; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Cost On Reflect for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int stackCostOnReflect = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Reflected Damage for the Blocking Counter Guard adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerReflectedDamage = 5.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingHorseArmorer.java b/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingHorseArmorer.java deleted file mode 100644 index 9df08cd71..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingHorseArmorer.java +++ /dev/null @@ -1,155 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.blocking; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public class BlockingHorseArmorer extends SimpleAdaptation { - - public BlockingHorseArmorer() { - super("blocking-horsearmorer"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("blocking.horse_armorer.description")); - setDisplayName(Localizer.dLocalize("blocking.horse_armorer.name")); - setIcon(Material.GOLDEN_HORSE_ARMOR); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(17774); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-horsearmorerleather") - .ingredient(new MaterialChar('I', Material.LEATHER)) - .ingredient(new MaterialChar('U', Material.SADDLE)) - .shapes(List.of( - "III", - "IUI", - "III")) - .result(new ItemStack(Material.LEATHER_HORSE_ARMOR, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-horsearmoreriron") - .ingredient(new MaterialChar('I', Material.IRON_INGOT)) - .ingredient(new MaterialChar('U', Material.SADDLE)) - .shapes(List.of( - "III", - "IUI", - "III")) - .result(new ItemStack(Material.IRON_HORSE_ARMOR, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-horsearmorergold") - .ingredient(new MaterialChar('I', Material.GOLD_INGOT)) - .ingredient(new MaterialChar('U', Material.SADDLE)) - .shapes(List.of( - "III", - "IUI", - "III")) - .result(new ItemStack(Material.GOLDEN_HORSE_ARMOR, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-horsearmorerdiamond") - .ingredient(new MaterialChar('I', Material.DIAMOND)) - .ingredient(new MaterialChar('U', Material.SADDLE)) - .shapes(List.of( - "III", - "IUI", - "III")) - .result(new ItemStack(Material.DIAMOND_HORSE_ARMOR, 1)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_HORSE_ARMOR) - .key("challenge_blocking_horse_armor_10") - .title(Localizer.dLocalize("advancement.challenge_blocking_horse_armor_10.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_horse_armor_10.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_blocking_horse_armor_10", "blocking.horse-armorer.armor-crafted", 10, 400); - } - - @EventHandler - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getWhoClicked() instanceof Player p && hasAdaptation(p) && isAdaptationRecipe(e.getRecipe())) { - getPlayer(p).getData().addStat("blocking.horse-armorer.armor-crafted", 1); - } - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("blocking.horse_armorer.lore1")); - v.addLore("XXX"); - v.addLore("XSX"); - v.addLore("XXX"); - - } - - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Horse Armor by surrounding a saddle with material.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingMirrorBlock.java b/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingMirrorBlock.java deleted file mode 100644 index 1aecdef1f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingMirrorBlock.java +++ /dev/null @@ -1,270 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.blocking; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.metadata.MetadataValue; -import org.bukkit.util.Vector; - -import java.util.concurrent.ThreadLocalRandom; - -public class BlockingMirrorBlock extends SimpleAdaptation { - private static final String REFLECTED_META = "adapt-mirror-reflected"; - private static final String DAMAGE_FACTOR_META = "adapt-mirror-damage-factor"; - - public BlockingMirrorBlock() { - super("blocking-mirror-block"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("blocking.mirror_block.description")); - setDisplayName(Localizer.dLocalize("blocking.mirror_block.name")); - setIcon(Material.LIGHT_WEIGHTED_PRESSURE_PLATE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1200); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_blocking_mirror_100") - .title(Localizer.dLocalize("advancement.challenge_blocking_mirror_100.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_mirror_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_blocking_mirror_100", "blocking.mirror-block.projectiles-reflected", 100, 500); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_blocking_mirror_3in5") - .title(Localizer.dLocalize("advancement.challenge_blocking_mirror_3in5.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_mirror_3in5.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getReflectChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.mirror_block.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getReflectedDamageFactor(level), 0) + C.GRAY + " " + Localizer.dLocalize("blocking.mirror_block.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getReflectCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("blocking.mirror_block.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Projectile projectile)) { - return; - } - - applyReflectedDamageModifier(e, projectile); - - if (!(e.getEntity() instanceof Player defender) || !isMirrorReady(defender) || projectile.hasMetadata(REFLECTED_META)) { - return; - } - - int level = getLevel(defender); - long now = System.currentTimeMillis(); - long next = getStorageLong(defender, "mirrorBlockNext", 0L); - if (next > now) { - return; - } - - if (ThreadLocalRandom.current().nextDouble() > getReflectChance(level)) { - return; - } - - e.setCancelled(true); - reflectProjectile(defender, projectile, level); - setStorage(defender, "mirrorBlockNext", now + getReflectCooldownMillis(level)); - - SoundPlayer sp = SoundPlayer.of(defender.getWorld()); - sp.play(defender.getLocation(), Sound.ITEM_SHIELD_BLOCK, 1f, 1.35f); - sp.play(defender.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_HIT, 0.8f, 0.8f); - if (areParticlesEnabled()) { - defender.spawnParticle(Particle.CRIT, defender.getLocation().add(0, 1, 0), 20, 0.35, 0.3, 0.35, 0.08); - } - xp(defender, getConfig().xpOnReflect); - getPlayer(defender).getData().addStat("blocking.mirror-block.projectiles-reflected", 1); - - // Special achievement: reflect 3 projectiles within 5 seconds - long windowStart = getStorageLong(defender, "mirrorWindowStart", 0L); - int windowCount = getStorageInt(defender, "mirrorWindowCount", 0); - if (now - windowStart > 5000L) { - windowStart = now; - windowCount = 1; - } else { - windowCount++; - } - setStorage(defender, "mirrorWindowStart", windowStart); - setStorage(defender, "mirrorWindowCount", windowCount); - if (windowCount >= 3 && AdaptConfig.get().isAdvancements() && !getPlayer(defender).getData().isGranted("challenge_blocking_mirror_3in5")) { - getPlayer(defender).getAdvancementHandler().grant("challenge_blocking_mirror_3in5"); - } - } - - private void applyReflectedDamageModifier(EntityDamageByEntityEvent e, Projectile projectile) { - if (!(projectile.getShooter() instanceof Player shooter) || !projectile.hasMetadata(DAMAGE_FACTOR_META)) { - return; - } - - if (e.getEntity() instanceof Player victim) { - if (!canPVP(shooter, victim.getLocation())) { - return; - } - } else if (!canPVE(shooter, e.getEntity().getLocation())) { - return; - } - - double factor = getMetadataDouble(projectile, DAMAGE_FACTOR_META, 1D); - e.setDamage(e.getDamage() * factor); - } - - private void reflectProjectile(Player defender, Projectile projectile, int level) { - Vector incoming = projectile.getVelocity().clone(); - Vector reflected = incoming.multiply(-Math.max(0.01, getReflectVelocityFactor(level))); - if (reflected.lengthSquared() < getConfig().minReflectedVelocitySquared) { - reflected = defender.getEyeLocation().getDirection().normalize().multiply(getConfig().fallbackReflectedSpeed); - } - - projectile.teleport(defender.getEyeLocation().add(defender.getEyeLocation().getDirection().multiply(0.55))); - projectile.setShooter(defender); - projectile.setVelocity(reflected); - projectile.setMetadata(REFLECTED_META, new FixedMetadataValue(Adapt.instance, true)); - projectile.setMetadata(DAMAGE_FACTOR_META, new FixedMetadataValue(Adapt.instance, getReflectedDamageFactor(level))); - } - - private boolean isMirrorReady(Player p) { - return hasAdaptation(p) && p.isBlocking() && hasShield(p); - } - - private boolean hasShield(Player p) { - ItemStack main = p.getInventory().getItemInMainHand(); - ItemStack off = p.getInventory().getItemInOffHand(); - return (isItem(main) && main.getType() == Material.SHIELD) || (isItem(off) && off.getType() == Material.SHIELD); - } - - private double getMetadataDouble(Projectile projectile, String key, double fallback) { - for (MetadataValue value : projectile.getMetadata(key)) { - if (value.getOwningPlugin() == Adapt.instance) { - return value.asDouble(); - } - } - - return fallback; - } - - private double getReflectChance(int level) { - return Math.min(getConfig().maxReflectChance, getConfig().reflectChanceBase + (getLevelPercent(level) * getConfig().reflectChanceFactor)); - } - - private double getReflectedDamageFactor(int level) { - return Math.min(getConfig().maxReflectedDamageFactor, - getConfig().reflectedDamageFactorBase + (getLevelPercent(level) * getConfig().reflectedDamageFactorIncrease)); - } - - private double getReflectVelocityFactor(int level) { - return Math.min(getConfig().maxReflectVelocityFactor, getConfig().reflectVelocityFactorBase + (getLevelPercent(level) * getConfig().reflectVelocityFactor)); - } - - private long getReflectCooldownMillis(int level) { - return Math.max(100L, Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Blocking with a shield can reflect incoming projectiles at reduced force and damage.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflect Chance Base for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectChanceBase = 0.1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflect Chance Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectChanceFactor = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Reflect Chance for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxReflectChance = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflected Damage Factor Base for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectedDamageFactorBase = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflected Damage Factor Increase for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectedDamageFactorIncrease = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Reflected Damage Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxReflectedDamageFactor = 0.95; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflect Velocity Factor Base for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectVelocityFactorBase = 0.42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reflect Velocity Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reflectVelocityFactor = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Reflect Velocity Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxReflectVelocityFactor = 1.1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisBase = 2000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisFactor = 1200; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Reflected Velocity Squared for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minReflectedVelocitySquared = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fallback Reflected Speed for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fallbackReflectedSpeed = 0.95; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Reflect for the Blocking Mirror Block adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnReflect = 8; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingMultiArmor.java b/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingMultiArmor.java deleted file mode 100644 index 6cb8eeb4f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingMultiArmor.java +++ /dev/null @@ -1,269 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.blocking; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.content.item.multiItems.MultiArmor; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryAction; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - -public class BlockingMultiArmor extends SimpleAdaptation { - private static final MultiArmor multiarmor = new MultiArmor(); - private final Map cooldowns; - - - public BlockingMultiArmor() { - super("blocking-multiarmor"); - registerConfiguration(BlockingMultiArmor.Config.class); - setDisplayName(Localizer.dLocalize("blocking.multi_armor.name")); - setDescription(Localizer.dLocalize("blocking.multi_armor.description")); - setIcon(Material.ELYTRA); - setInterval(20202); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ELYTRA) - .key("challenge_blocking_multi_200") - .title(Localizer.dLocalize("advancement.challenge_blocking_multi_200.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_multi_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_CHESTPLATE) - .key("challenge_blocking_multi_5k") - .title(Localizer.dLocalize("advancement.challenge_blocking_multi_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_multi_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_blocking_multi_200", "blocking.multi-armor.swaps", 200, 400); - registerMilestone("challenge_blocking_multi_5k", "blocking.multi-armor.swaps", 5000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("blocking.multi_armor.lore1")); - v.addLore(C.GRAY + "" + C.GRAY + Localizer.dLocalize("blocking.multi_armor.lore2")); - v.addLore(C.GREEN + Localizer.dLocalize("blocking.multi_armor.lore3")); - v.addLore(C.RED + Localizer.dLocalize("blocking.multi_armor.lore4")); - v.addLore(C.GRAY + Localizer.dLocalize("blocking.multi_armor.lore5")); - v.addLore(C.UNDERLINE + Localizer.dLocalize("blocking.multi_armor.lore6")); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public void onTick() { - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(PlayerMoveEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - ItemStack chest = p.getInventory().getChestplate(); - if (chest != null && hasAdaptation(p) && validateArmor(chest)) { - Long cooldown = cooldowns.get(p); - if (cooldown != null) { - if (cooldown + 3000 > System.currentTimeMillis()) - return; - else cooldowns.remove(p); - } - - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - if (p.isOnGround() && !p.isFlying()) { - if (isChestplate(chest)) { - return; - } - J.s(() -> p.getInventory().setChestplate(multiarmor.nextChestplate(chest))); - cooldowns.put(p, System.currentTimeMillis()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - spw.play(p.getLocation(), Sound.BLOCK_BEEHIVE_SHEAR, 0.5f, 0.77f); - getPlayer(p).getData().addStat("blocking.multi-armor.swaps", 1); - - } else if (p.getFallDistance() > 4) { - if (isElytra(chest)) { - return; - } - J.s(() -> p.getInventory().setChestplate(multiarmor.nextElytra(chest))); - cooldowns.put(p, System.currentTimeMillis()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - spw.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.5f, 0.77f); - getPlayer(p).getData().addStat("blocking.multi-armor.swaps", 1); - } - } - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerDropItemEvent e) { - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - if (p.isSneaking()) { - if (validateArmor(e.getItemDrop().getItemStack())) { - List drops = multiarmor.explode(e.getItemDrop().getItemStack()); - for (ItemStack i : drops) { - Damageable iDmgable = (Damageable) i.getItemMeta(); - if (i.hasItemMeta()) { - ItemMeta im = i.getItemMeta().clone(); - ItemMeta im2 = im; - if (im.hasDisplayName()) { - im2.setDisplayName(im.getDisplayName()); - } - if (im.hasEnchants()) { - Map enchants = im.getEnchants(); - for (Enchantment enchant : enchants.keySet()) { - im2.addEnchant(enchant, enchants.get(enchant), true); - } - } - if (iDmgable != null && iDmgable.hasDamage()) { - ((Damageable) im2).setDamage(iDmgable.getDamage()); - } - im2.setLore(null); - i.setItemMeta(im2); - } - drops.set(drops.indexOf(i), i); - } - - J.s(() -> { - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_DEATH, 0.25f, 0.77f); - for (ItemStack i : drops) { - p.getWorld().dropItem(p.getLocation(), i); - } - }); - e.getItemDrop().setItemStack(new ItemStack(Material.AIR)); - } - } - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(InventoryClickEvent e) { - if (!hasAdaptation((Player) e.getWhoClicked())) { - return; - } - if (e.getClickedInventory() != null - && e.getClick().equals(ClickType.SHIFT_LEFT) - && e.getClickedInventory().getItem(e.getSlot()) != null - && e.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { - ItemStack cursor = e.getWhoClicked().getItemOnCursor().clone(); - ItemStack clicked = e.getClickedInventory().getItem(e.getSlot()).clone(); - - if (cursor.getType().equals(Material.ELYTRA) || clicked.getType().equals(Material.ELYTRA)) { // One must be an ELYTRA - - if (multiarmor.explode(cursor).size() > 1 || multiarmor.explode(clicked).size() > 1) { - - if (multiarmor.explode(cursor).size() >= getSlots(getLevel((Player) e.getWhoClicked())) || multiarmor.explode(clicked).size() >= getSlots(getLevel((Player) e.getWhoClicked()))) { - e.setCancelled(true); - SoundPlayer sp = SoundPlayer.of((Player) e.getWhoClicked()); - sp.play(e.getWhoClicked().getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 1f, 0.77f); - return; - } - } - if (ItemListings.getMultiArmorable().contains(cursor.getType()) && ItemListings.getMultiArmorable().contains(clicked.getType())) { // Chest/Elytra Only - - if (!cursor.getType().isAir() && !clicked.getType().isAir() && multiarmor.supportsItem(cursor) && multiarmor.supportsItem(clicked)) { - e.setCancelled(true); - e.getWhoClicked().setItemOnCursor(new ItemStack(Material.AIR)); - e.getClickedInventory().setItem(e.getSlot(), multiarmor.build(cursor, clicked)); - SoundPlayer spw = SoundPlayer.of(e.getWhoClicked().getWorld()); - spw.play(e.getWhoClicked().getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - } - } - } - } - } - - - private boolean validateArmor(ItemStack item) { - if (item.getItemMeta() != null && item.getItemMeta().getLore() != null) { - for (String lore : item.getItemMeta().getLore()) { - if (lore != null && lore.contains("MultiArmor")) { - return true; - } - } - } - return false; - } - - - private double getSlots(double level) { - return getConfig().startingSlots + level; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Bind Elytras to armor for dynamic merge and swap.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Starting Slots for the Blocking Multi Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int startingSlots = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingSaddlecrafter.java b/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingSaddlecrafter.java deleted file mode 100644 index 778027075..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/blocking/BlockingSaddlecrafter.java +++ /dev/null @@ -1,121 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.blocking; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public class BlockingSaddlecrafter extends SimpleAdaptation { - - public BlockingSaddlecrafter() { - super("blocking-saddlecrafter"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("blocking.saddle_crafter.description")); - setDisplayName(Localizer.dLocalize("blocking.saddle_crafter.name")); - setIcon(Material.LEATHER_HORSE_ARMOR); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(17774); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shaped() - .key("blocking-saddlecrafter") - .ingredient(new MaterialChar('I', Material.LEATHER)) - .shapes(List.of( - "I I", - "III")) - .result(new ItemStack(Material.SADDLE, 1)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SADDLE) - .key("challenge_blocking_saddle_25") - .title(Localizer.dLocalize("advancement.challenge_blocking_saddle_25.title")) - .description(Localizer.dLocalize("advancement.challenge_blocking_saddle_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_blocking_saddle_25", "blocking.saddlecrafter.saddles-crafted", 25, 400); - } - - @EventHandler - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getWhoClicked() instanceof Player p && hasAdaptation(p) && isAdaptationRecipe(e.getRecipe())) { - getPlayer(p).getData().addStat("blocking.saddlecrafter.saddles-crafted", 1); - } - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("blocking.saddle_crafter.lore1")); - v.addLore("X-X"); - v.addLore("XXX"); - } - - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft a Saddle using leather.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingAbsorption.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingAbsorption.java deleted file mode 100644 index 477992d6f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingAbsorption.java +++ /dev/null @@ -1,140 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionEffectType; - - -public class BrewingAbsorption extends SimpleAdaptation { - public BrewingAbsorption() { - super("brewing-absorption"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.absorption.description")); - setDisplayName(Localizer.dLocalize("brewing.absorption.name")); - setIcon(Material.QUARTZ); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1333); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-absorption-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.QUARTZ) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.INSTANT_HEAL)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Absorption") - .setColor(Color.GRAY) - .addEffect(PotionEffectType.ABSORPTION, 1200, 1, true, true, true) - .build()) - .build() - ); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-absorption-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.QUARTZ_BLOCK) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.INSTANT_HEAL)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Haste 2") - .setColor(Color.GRAY) - .addEffect(PotionEffectType.ABSORPTION, 600, 2, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_APPLE) - .key("challenge_brewing_absorption_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_absorption_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_absorption_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_absorption_25", "brewing.absorption.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.absorption.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.absorption.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.absorption.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Absorption from Instant Heal and Quartz.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingBlindness.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingBlindness.java deleted file mode 100644 index ff24f9f42..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingBlindness.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; - - -public class BrewingBlindness extends SimpleAdaptation { - public BrewingBlindness() { - super("brewing-blindness"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.blindness.description")); - setDisplayName(Localizer.dLocalize("brewing.blindness.name")); - setIcon(Material.INK_SAC); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1333); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-blindness-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.INK_SAC) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Blindness") - .setColor(Color.OLIVE) - .addEffect(PotionEffectType.BLINDNESS, 600, 1, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-blindness-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.GLOW_INK_SAC) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Blindness 2") - .setColor(Color.OLIVE) - .addEffect(PotionEffectType.BLINDNESS, 300, 3, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.INK_SAC) - .key("challenge_brewing_blindness_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_blindness_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_blindness_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_blindness_25", "brewing.blindness.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.blindness.lore1")); -// v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.blindness.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.blindness.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Blindness from Awkward Potion and Ink Sack.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingDarkness.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingDarkness.java deleted file mode 100644 index 3d6c00253..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingDarkness.java +++ /dev/null @@ -1,127 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; - - -public class BrewingDarkness extends SimpleAdaptation { - public BrewingDarkness() { - super("brewing-darkness"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.darkness.description")); - setDisplayName(Localizer.dLocalize("brewing.darkness.name")); - setIcon(Material.BLACK_CONCRETE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1335); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-darkness") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.BLACK_CONCRETE) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.NIGHT_VISION)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Darkness") - .setColor(Color.BLACK) - .addEffect(PotionEffectType.DARKNESS, 600, 100, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BREWING_STAND) - .key("challenge_brewing_darkness_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_darkness_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_darkness_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_darkness_25", "brewing.darkness.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.darkness.lore1")); - v.addLore(C.GRAY + "- " + Localizer.dLocalize("brewing.darkness.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.darkness.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Darkness from NightVision Potion and Black Concrete.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingDecay.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingDecay.java deleted file mode 100644 index d720bb89a..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingDecay.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; - - -public class BrewingDecay extends SimpleAdaptation { - public BrewingDecay() { - super("brewing-decay"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.decay.description")); - setDisplayName(Localizer.dLocalize("brewing.decay.name")); - setIcon(Material.WITHER_ROSE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1334); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-decay-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.POISONOUS_POTATO) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Decay") - .setColor(Color.MAROON) - .addEffect(PotionEffectType.WITHER, 300, 1, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-decay-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.CRIMSON_ROOTS) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Decay 2") - .setColor(Color.MAROON) - .addEffect(PotionEffectType.WITHER, 150, 2, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WITHER_ROSE) - .key("challenge_brewing_decay_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_decay_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_decay_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_decay_25", "brewing.decay.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.decay.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.decay.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.decay.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Wither from Weakness Potion and Poisonous Potato.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingFatigue.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingFatigue.java deleted file mode 100644 index 03db038f4..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingFatigue.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionType; - - -public class BrewingFatigue extends SimpleAdaptation { - public BrewingFatigue() { - super("brewing-fatigue"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.fatigue.description")); - setDisplayName(Localizer.dLocalize("brewing.fatigue.name")); - setIcon(Material.SLIME_BALL); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1332); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-fatigue-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.SLIME_BALL) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Fatigue") - .setColor(Color.fromRGB(0, 66, 0)) - .addEffect(PotionEffectTypes.SLOW_DIGGING, 1200, 1, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-fatigue-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.SLIME_BLOCK) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Fatigue 2") - .setColor(Color.fromRGB(0, 66, 0)) - .addEffect(PotionEffectTypes.SLOW_DIGGING, 600, 2, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BREWING_STAND) - .key("challenge_brewing_fatigue_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_fatigue_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_fatigue_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_fatigue_25", "brewing.fatigue.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.fatigue.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.fatigue.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.fatigue.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Fatigue from Weakness Potion and Slime.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHaste.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHaste.java deleted file mode 100644 index 7e831670f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHaste.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import com.volmit.adapt.util.reflect.registries.PotionTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; - - -public class BrewingHaste extends SimpleAdaptation { - public BrewingHaste() { - super("brewing-haste"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.haste.description")); - setDisplayName(Localizer.dLocalize("brewing.haste.name")); - setIcon(Material.AMETHYST_SHARD); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1334); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-haste-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.AMETHYST_SHARD) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.SPEED)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Haste") - .setColor(Color.YELLOW) - .addEffect(PotionEffectTypes.FAST_DIGGING, 1200, 1, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-haste-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.AMETHYST_BLOCK) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.SPEED)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Haste 2") - .setColor(Color.YELLOW) - .addEffect(PotionEffectTypes.FAST_DIGGING, 600, 2, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BREWING_STAND) - .key("challenge_brewing_haste_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_haste_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_haste_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_haste_25", "brewing.haste.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.haste.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.haste.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.haste.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Haste from Speed Potion and Amethyst.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHealthBoost.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHealthBoost.java deleted file mode 100644 index 05d1f6a77..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHealthBoost.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionEffectType; - - -public class BrewingHealthBoost extends SimpleAdaptation { - public BrewingHealthBoost() { - super("brewing-healthboost"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.health_boost.description")); - setDisplayName(Localizer.dLocalize("brewing.health_boost.name")); - setIcon(Material.ENCHANTED_GOLDEN_APPLE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1330); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-healthboost") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.GOLDEN_APPLE) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.INSTANT_HEAL)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Life") - .setColor(Color.RED) - .addEffect(PotionEffectType.HEALTH_BOOST, 1200, 1, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-healthboost") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.ENCHANTED_GOLDEN_APPLE) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.INSTANT_HEAL)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Life") - .setColor(Color.RED) - .addEffect(PotionEffectType.HEALTH_BOOST, 1200, 2, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GLISTERING_MELON_SLICE) - .key("challenge_brewing_health_boost_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_health_boost_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_health_boost_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_health_boost_25", "brewing.health-boost.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.health_boost.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.health_boost.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.health-boost.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Health Boost from Instant Heal and Golden Apple.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHunger.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHunger.java deleted file mode 100644 index 09ab37ef8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingHunger.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; - - -public class BrewingHunger extends SimpleAdaptation { - public BrewingHunger() { - super("brewing-hunger"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.hunger.description")); - setDisplayName(Localizer.dLocalize("brewing.hunger.name")); - setIcon(Material.ROTTEN_FLESH); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1331); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-hunger-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.ROTTEN_FLESH) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Hunger") - .setColor(Color.GREEN) - .addEffect(PotionEffectType.HUNGER, 1200, 1, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-hunger-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.ROTTEN_FLESH) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.WEAKNESS)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Hunger 2") - .setColor(Color.GREEN) - .addEffect(PotionEffectType.HUNGER, 600, 3, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ROTTEN_FLESH) - .key("challenge_brewing_hunger_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_hunger_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_hunger_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_hunger_25", "brewing.hunger.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.hunger.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.hunger.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.hunger.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Hunger from Awkward Potion and Rotten Flesh.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingLingering.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingLingering.java deleted file mode 100644 index e5a9e443f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingLingering.java +++ /dev/null @@ -1,370 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerData; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.reflect.registries.ItemFlags; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.format.TextDecoration; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.attribute.Attribute; -import org.bukkit.attribute.AttributeModifier; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; -import java.util.Locale; -import java.util.Map; -import java.util.function.Function; - -public class BrewingLingering extends SimpleAdaptation { - private static final Function getColor; - private static final Function> getEffectAttributes; - private static final Function3 getAttributeModifierAmount; - private static final DecimalFormat ATTRIBUTE_MODIFIER_FORMAT = new DecimalFormat("#.##"); - - public BrewingLingering() { - super("brewing-lingering"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.lingering.description")); - setDisplayName(Localizer.dLocalize("brewing.lingering.name")); - setIcon(Material.DRAGON_BREATH); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(4788); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LINGERING_POTION) - .key("challenge_brewing_lingering_200") - .title(Localizer.dLocalize("advancement.challenge_brewing_lingering_200.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_lingering_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DRAGON_BREATH) - .key("challenge_brewing_lingering_5k") - .title(Localizer.dLocalize("advancement.challenge_brewing_lingering_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_lingering_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_brewing_lingering_200", "brewing.lingering.potions-extended", 200, 300); - registerMilestone("challenge_brewing_lingering_5k", "brewing.lingering.potions-extended", 5000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.duration((long) getDurationBoost(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("brewing.lingering.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getPercentBoost(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("brewing.lingering.lore2")); - } - - public double getDurationBoost(double factor) { - return (getConfig().durationBoostFactorTicks * factor) + getConfig().baseDurationBoostTicks; - } - - public double getPercentBoost(double factor) { - return 1 + ((factor * factor * getConfig().durationMultiplierFactor) + getConfig().baseDurationMultiplier); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - if (!e.getBlock().getType().equals(Material.BREWING_STAND)) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - - if (owner == null) { - Adapt.verbose("No Owner"); - return; - } - - PlayerData data = null; - var results = e.getResults(); - boolean ef = false; - for (int i = 0; i < results.size(); i++) { - ItemStack is = results.get(i); - - if (is == null || is.getItemMeta() == null || !(is.getItemMeta() instanceof PotionMeta p)) - continue; - - data = data == null ? getServer().peekData(owner.getOwner()) : data; - - if (data.getSkillLines().containsKey(getSkill().getName()) && data.getSkillLine(getSkill().getName()).getAdaptations().containsKey(getName())) { - PlayerAdaptation a = data.getSkillLine(getSkill().getName()).getAdaptations().get(getName()); - - if (a.getLevel() > 0) { - double factor = getLevelPercent(a.getLevel()); - boolean enhanced = enhance(factor, is, p); - if (enhanced) { - data.addStat("brewing.lingering.potions-extended", 1); - } - ef = enhanced || ef; - results.set(i, is); - } - } - } - - if (ef) { - SoundPlayer spw = SoundPlayer.of(e.getBlock().getWorld()); - spw.play(e.getBlock().getLocation(), Sound.BLOCK_BREWING_STAND_BREW, 1f, 0.75f); - spw.play(e.getBlock().getLocation(), Sound.BLOCK_BREWING_STAND_BREW, 1f, 1.75f); - } - } - - private boolean enhance(double factor, ItemStack is, PotionMeta p) { - var effects = p.getBasePotionType().getPotionEffects(); - if (effects.stream() - .map(PotionEffect::getType) - .allMatch(PotionEffectType::isInstant)) - return false; - - p.clearCustomEffects(); - for (final PotionEffect effect : effects) { - if (effect.getType().isInstant()) { - p.addCustomEffect(effect, true); - continue; - } - - p.addCustomEffect(new PotionEffect( - effect.getType(), - (int) (getDurationBoost(factor) + (effect.getDuration() * getPercentBoost(factor))), - effect.getAmplifier() - ), true); - } - - p.addItemFlags(ItemFlags.HIDE_POTION_EFFECTS); - is.setItemMeta(p); - - if (getConfig().useCustomLore) { - KList lore = new KList<>(); - KList modifiers = new KList<>(); - for (var effect : p.getCustomEffects()) { - var type = effect.getType(); - var key = type.getKey(); - var name = Component.translatable("effect." + key.getNamespace() + "." + key.getKey()); - if (effect.getAmplifier() > 0) { - name = Component.translatable("potion.withAmplifier", name, - Component.translatable("potion.potency." + effect.getAmplifier())); - } - - if (effect.getDuration() > 20) { - name = Component.translatable("potion.withDuration", name, formatDuration(effect)); - } - - lore.add(name.color(getColor.apply(type))); - getEffectAttributes.apply(type) - .entrySet() - .stream() - .map(Modifier::new) - .map(m -> m.adjust(type, effect.getAmplifier())) - .filter(m -> m.amount != 0) - .forEach(modifiers::add); - } - - if (!modifiers.isEmpty()) { - lore.add(Component.empty()); - lore.add(Component.translatable("potion.whenDrank").color(NamedTextColor.DARK_PURPLE)); - - for (Modifier modifier : modifiers) { - double amount = modifier.amount; - var formatted = Component.text(ATTRIBUTE_MODIFIER_FORMAT.format(modifier.operation == AttributeModifier.Operation.ADD_NUMBER ? amount : amount * 100d)); - var name = Component.translatable("attribute.name." + modifier.attribute.getKey().getKey()); - - if (amount > 0) { - lore.add(Component.translatable("attribute.modifier.plus." + modifier.operation.ordinal(), formatted, name) - .color(NamedTextColor.BLUE)); - } else { - lore.add(Component.translatable("attribute.modifier.take." + modifier.operation.ordinal(), formatted, name) - .color(NamedTextColor.RED)); - } - } - } - lore.replaceAll(c -> c.decoration(TextDecoration.ITALIC, false)); - - Adapt.platform.editItem(is) - .lore(lore) - .build(); - } - - return true; - } - - private Component formatDuration(PotionEffect effect) { - if (effect.isInfinite()) { - return Component.translatable("effect.duration.infinite"); - } else { - int seconds = effect.getDuration() / 20; - int minutes = seconds / 60; - seconds %= 60; - int hours = minutes / 60; - minutes %= 60; - return Component.text(hours > 0 ? - "%02d:%02d:%02d".formatted(hours, minutes, seconds) : - "%02d:%02d".formatted(minutes, seconds)); - } - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brewed potions last longer.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Duration Boost Ticks for the Brewing Lingering adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseDurationBoostTicks = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Boost Factor Ticks for the Brewing Lingering adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durationBoostFactorTicks = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Multiplier Factor for the Brewing Lingering adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durationMultiplierFactor = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Duration Multiplier for the Brewing Lingering adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseDurationMultiplier = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Use Custom Lore for the Brewing Lingering adaptation.", impact = "True enables this behavior and false disables it.") - boolean useCustomLore = true; - } - - private record Modifier(Attribute attribute, AttributeModifier.Operation operation, double amount) { - private Modifier(Map.Entry entry) { - this(entry.getKey(), entry.getValue()); - } - - private Modifier(Attribute attribute, AttributeModifier modifier) { - this(attribute, modifier.getOperation(), modifier.getAmount()); - } - - private Modifier adjust(PotionEffectType type, int amplifier) { - return new Modifier( - attribute, - operation, - getAttributeModifierAmount.apply(type, attribute, amplifier) - ); - } - } - - static { - var lookup = MethodHandles.lookup(); - MethodHandle getCategory; - try { - var method = PotionEffectType.class.getDeclaredMethod("getCategory"); - getCategory = lookup.unreflect(method); - } catch (Throwable ignored) { - getCategory = null; - } - - MethodHandle modifiersHandle; - MethodHandle amountHandle; - try { - modifiersHandle = lookup.findVirtual(PotionEffectType.class, "getEffectAttributes", MethodType.methodType(Map.class)); - amountHandle = lookup.findVirtual(PotionEffectType.class, "getAttributeModifierAmount", MethodType.methodType(double.class, Attribute.class, int.class)); - } catch (Throwable ignored) { - Adapt.verbose("Failed to find attributes for potion effect type"); - modifiersHandle = null; - amountHandle = null; - } - - if (getCategory != null) { - MethodHandle handle = getCategory; - getColor = type -> { - try { - return ((Enum) handle.invoke(type)).ordinal() == 1 ? NamedTextColor.RED : NamedTextColor.BLUE; - } catch (Throwable err) { - throw new RuntimeException(err); - } - }; - } else getColor = $ -> NamedTextColor.BLUE; - - if (modifiersHandle != null) { - MethodHandle handle = modifiersHandle; - getEffectAttributes = type -> { - try { - return (Map) handle.invoke(type); - } catch (Throwable err) { - throw new RuntimeException(err); - } - }; - } else getEffectAttributes = $ -> Map.of(); - - if (amountHandle != null) { - MethodHandle handle = amountHandle; - getAttributeModifierAmount = (type, attribute, level) -> { - try { - return (double) handle.invoke(type, attribute, level); - } catch (Throwable err) { - throw new RuntimeException(err); - } - }; - } else getAttributeModifierAmount = ($, $$, $$$) -> 0d; - - ATTRIBUTE_MODIFIER_FORMAT.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT)); - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingNausea.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingNausea.java deleted file mode 100644 index 868aad768..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingNausea.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionType; - - -public class BrewingNausea extends SimpleAdaptation { - public BrewingNausea() { - super("brewing-nausea"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.nausea.description")); - setDisplayName(Localizer.dLocalize("brewing.nausea.name")); - setIcon(Material.CRIMSON_FUNGUS); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1333); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-nausea-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.BROWN_MUSHROOM) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Nausea") - .setColor(Color.LIME) - .addEffect(PotionEffectTypes.CONFUSION, 600, 1, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-nausea-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.CRIMSON_FUNGUS) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Nausea 2") - .setColor(Color.LIME) - .addEffect(PotionEffectTypes.CONFUSION, 300, 2, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.POISONOUS_POTATO) - .key("challenge_brewing_nausea_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_nausea_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_nausea_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_nausea_25", "brewing.nausea.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.nausea.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.nausea.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.nausea.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Nausea from Awkward Potion and Mushroom.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingResistance.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingResistance.java deleted file mode 100644 index e5b0900ed..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingResistance.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; - - -public class BrewingResistance extends SimpleAdaptation { - public BrewingResistance() { - super("brewing-resistance"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.resistance.description")); - setDisplayName(Localizer.dLocalize("brewing.resistance.name")); - setIcon(Material.IRON_BLOCK); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1333); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-resistance-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.IRON_INGOT) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Resistance") - .setColor(Color.WHITE) - .addEffect(PotionEffectType.RESISTANCE, 1200, 1, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-resistance-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.IRON_BLOCK) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionType.AWKWARD)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Resistance 2") - .setColor(Color.WHITE) - .addEffect(PotionEffectType.RESISTANCE, 600, 2, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_CHESTPLATE) - .key("challenge_brewing_resistance_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_resistance_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_resistance_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_resistance_25", "brewing.resistance.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.resistance.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.resistance.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.resistance.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Resistance from Awkward Potion and Iron.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingSaturation.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingSaturation.java deleted file mode 100644 index 007e3a0be..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingSaturation.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.potion.BrewingRecipe; -import com.volmit.adapt.api.potion.PotionBuilder; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.potion.PotionEffectType; - - -public class BrewingSaturation extends SimpleAdaptation { - public BrewingSaturation() { - super("brewing-saturation"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.saturation.description")); - setDisplayName(Localizer.dLocalize("brewing.saturation.name")); - setIcon(Material.BAKED_POTATO); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1334); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-saturation-1") - .brewingTime(320) - .fuelCost(16) - .ingredient(Material.BAKED_POTATO) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.REGEN)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Saturation") - .setColor(Color.ORANGE) - .addEffect(PotionEffectType.SATURATION, 1, 4, true, true, true) - .build()) - .build()); - registerBrewingRecipe(BrewingRecipe.builder() - .id("brewing-saturation-2") - .brewingTime(320) - .fuelCost(32) - .ingredient(Material.HAY_BLOCK) - .basePotion(PotionBuilder.vanilla(PotionBuilder.Type.REGULAR, PotionTypes.REGEN)) - .result(PotionBuilder.of(PotionBuilder.Type.REGULAR) - .setName("Bottled Saturation 2") - .setColor(Color.ORANGE) - .addEffect(PotionEffectType.SATURATION, 1, 8, true, true, true) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_CARROT) - .key("challenge_brewing_saturation_25") - .title(Localizer.dLocalize("advancement.challenge_brewing_saturation_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_saturation_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_brewing_saturation_25", "brewing.saturation.potions-brewed", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.saturation.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("brewing.saturation.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.saturation.potions-brewed", 1); - } - } - - @Override - public void onTick() { - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brew a Potion of Saturation from Regen Potion and Baked Potato.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingSuperHeated.java b/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingSuperHeated.java deleted file mode 100644 index a8502301c..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/brewing/BrewingSuperHeated.java +++ /dev/null @@ -1,262 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.brewing; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerData; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.block.BrewingStand; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.BrewEvent; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryMoveItemEvent; -import org.bukkit.event.inventory.InventoryType; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - - -public class BrewingSuperHeated extends SimpleAdaptation { - - private static final int MAX_CHECKS_BEFORE_REMOVE = 20; - private final Map activeStands = new HashMap<>(); - - public BrewingSuperHeated() { - super("brewing-super-heated"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("brewing.super_heated.description")); - setDisplayName(Localizer.dLocalize("brewing.super_heated.name")); - setIcon(Material.LAVA_BUCKET); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(253); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BLAZE_POWDER) - .key("challenge_brewing_super_heated_100") - .title(Localizer.dLocalize("advancement.challenge_brewing_super_heated_100.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_super_heated_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.MAGMA_CREAM) - .key("challenge_brewing_super_heated_2500") - .title(Localizer.dLocalize("advancement.challenge_brewing_super_heated_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_brewing_super_heated_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_brewing_super_heated_100", "brewing.super-heated.brews-accelerated", 100, 300); - registerMilestone("challenge_brewing_super_heated_2500", "brewing.super-heated.brews-accelerated", 2500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getFireBoost(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("brewing.super_heated.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getLavaBoost(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("brewing.super_heated.lore2")); - } - - public double getLavaBoost(double factor) { - return (getConfig().lavaMultiplier) * (getConfig().multiplierFactor * factor); - } - - public double getFireBoost(double factor) { - return (getConfig().fireMultiplier) * (getConfig().multiplierFactor * factor); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(InventoryMoveItemEvent e) { - if (e.isCancelled()) { - return; - } - J.s(() -> { - if (e.getDestination().getType().equals(InventoryType.BREWING)) { - activeStands.put(e.getDestination().getLocation().getBlock(), MAX_CHECKS_BEFORE_REMOVE); - } - }); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BrewEvent e) { - if (e.isCancelled()) { - return; - } - if (activeStands.containsKey(e.getBlock())) { - BrewingStandOwner owner = WorldData.of(e.getBlock().getWorld()).get(e.getBlock(), BrewingStandOwner.class); - if (owner != null) { - getServer().peekData(owner.getOwner()).addStat("brewing.super-heated.brews-accelerated", 1); - } - } - J.s(() -> { - if (((BrewingStand) e.getBlock().getState()).getBrewingTime() > 0) { - activeStands.put(e.getBlock(), MAX_CHECKS_BEFORE_REMOVE); - } - }); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(InventoryClickEvent e) { - if (e.getClickedInventory() == null || e.isCancelled()) { - return; - } - if (e.getView().getTopInventory().getType().equals(InventoryType.BREWING)) { - activeStands.put(e.getView().getTopInventory().getLocation().getBlock(), MAX_CHECKS_BEFORE_REMOVE); - } - } - - - @Override - public void onTick() { - if (activeStands.isEmpty()) { - return; - } - - Iterator it = activeStands.keySet().iterator(); - - J.s(() -> { - while (it.hasNext()) { - BlockState s = it.next().getState(); - - if (s instanceof BrewingStand b) { - if (b.getBrewingTime() <= 0) { - J.s(() -> { - BrewingStand bb = (BrewingStand) s.getBlock().getState(); - if (bb.getBrewingTime() <= 0) { - if (activeStands.get(b.getBlock()) == 0) { - activeStands.remove(b.getBlock()); - } - if (activeStands.containsKey(b.getBlock())) { - activeStands.put(b.getBlock(), activeStands.get(b.getBlock()) - 1); - } - } - }); - continue; - } - - BrewingStandOwner owner = WorldData.of(b.getWorld()).get(b.getBlock(), BrewingStandOwner.class); - - if (owner == null) { - it.remove(); - continue; - } - - PlayerData p = getServer().peekData(owner.getOwner()); - - PlayerSkillLine line = p.getSkillLineNullable(getSkill().getName()); - PlayerAdaptation adaptation = line != null ? line.getAdaptation(getName()) : null; - if (adaptation != null && adaptation.getLevel() > 0) { - updateHeat(b, getLevelPercent(adaptation.getLevel())); - } else { - it.remove(); - } - } else { - it.remove(); - } - } - }); - } - - private void updateHeat(BrewingStand b, double factor) { - double l = 0; - double f = 0; - - switch (b.getBlock().getRelative(BlockFace.DOWN).getType()) { - case LAVA -> l = l + 1; - case FIRE -> f = f + 1; - } - switch (b.getBlock().getRelative(BlockFace.NORTH).getType()) { - case LAVA -> l = l + 1; - case FIRE -> f = f + 1; - } - switch (b.getBlock().getRelative(BlockFace.SOUTH).getType()) { - case LAVA -> l = l + 1; - case FIRE -> f = f + 1; - } - switch (b.getBlock().getRelative(BlockFace.EAST).getType()) { - case LAVA -> l = l + 1; - case FIRE -> f = f + 1; - } - switch (b.getBlock().getRelative(BlockFace.WEST).getType()) { - case LAVA -> l = l + 1; - case FIRE -> f = f + 1; - } - - double pct = (getFireBoost(factor) * f) + (getLavaBoost(factor) * l) + 1; - int warp = (int) ((getInterval() / 50D) * pct); - b.setBrewingTime(Math.max(1, b.getBrewingTime() - warp)); - b.update(); - - if (M.r(1D / (333D / getInterval()))) { - SoundPlayer spw = SoundPlayer.of(b.getBlock().getWorld()); - spw.play(b.getBlock().getLocation(), Sound.BLOCK_FIRE_AMBIENT, 1f, 1f + RNG.r.f(0.3f, 0.6f)); - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brewing stands work faster when surrounded by fire or lava.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Multiplier Factor for the Brewing Super Heated adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double multiplierFactor = 1.33; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fire Multiplier for the Brewing Super Heated adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fireMultiplier = 0.14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Lava Multiplier for the Brewing Super Heated adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double lavaMultiplier = 0.69; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosAberrantTouch.java b/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosAberrantTouch.java deleted file mode 100644 index e589e6b31..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosAberrantTouch.java +++ /dev/null @@ -1,235 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.chronos; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class ChronosAberrantTouch extends SimpleAdaptation { - private final Map cooldowns; - private final Map targetStacks; - - public ChronosAberrantTouch() { - super("chronos-aberrant-touch"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("chronos.aberrant_touch.description")); - setDisplayName(Localizer.dLocalize("chronos.aberrant_touch.name")); - setIcon(Material.SPIDER_EYE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1000); - cooldowns = new HashMap<>(); - targetStacks = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CLOCK) - .key("challenge_chronos_aberrant_500") - .title(Localizer.dLocalize("advancement.challenge_chronos_aberrant_500.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_aberrant_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CLOCK) - .key("challenge_chronos_aberrant_frozen") - .title(Localizer.dLocalize("advancement.challenge_chronos_aberrant_frozen.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_aberrant_frozen.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_chronos_aberrant_500", "chronos.aberrant-touch.slowness-stacks-applied", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("chronos.aberrant_touch.lore1")); - v.addLore(C.YELLOW + "+ " + getPvEDurationCapTicks(level) / 20D + "s " + Localizer.dLocalize("chronos.aberrant_touch.lore2")); - v.addLore(C.RED + "* " + getConfig().playerAmplifierCap + " " + Localizer.dLocalize("chronos.aberrant_touch.lore3")); - v.addLore(C.AQUA + "* " + getConfig().rootAtStacks + " stacks roots for " + (getConfig().rootDurationTicks / 20D) + "s"); - } - - private int getPvEAmplifierCap(int level) { - return Math.max(0, Math.min(getConfig().entityAmplifierCap, level)); - } - - private int getPvEDurationCapTicks(int level) { - return getConfig().entityDurationCapTicks + (level * getConfig().entityDurationCapPerLevelTicks); - } - - private int getDurationAddedTicks(int level) { - return getConfig().durationAddTicks + (level * getConfig().durationPerLevelTicks); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Player attacker) || !hasAdaptation(attacker)) { - return; - } - - if (!(e.getEntity() instanceof LivingEntity target)) { - return; - } - - long now = System.currentTimeMillis(); - long cooldownUntil = cooldowns.getOrDefault(attacker.getUniqueId(), 0L); - if (cooldownUntil > now) { - return; - } - - if (target instanceof Player playerTarget) { - if (!canPVP(attacker, playerTarget.getLocation())) { - return; - } - } else { - if (!canPVE(attacker, target.getLocation())) { - return; - } - } - - if (!getPlayer(attacker).consumeFood(getConfig().hungerCost, getConfig().minimumFoodLevel)) { - return; - } - - int level = getLevel(attacker); - int amplifierCap = target instanceof Player ? getConfig().playerAmplifierCap : getPvEAmplifierCap(level); - int durationCap = target instanceof Player ? getConfig().playerDurationCapTicks : getPvEDurationCapTicks(level); - - PotionEffect existing = target.getPotionEffect(PotionEffectType.SLOWNESS); - int currentAmplifier = existing == null ? -1 : existing.getAmplifier(); - int currentDuration = existing == null ? 0 : existing.getDuration(); - - int newAmplifier = Math.min(amplifierCap, currentAmplifier + 1); - int newDuration = Math.min(durationCap, currentDuration + getDurationAddedTicks(level)); - - target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, Math.max(20, newDuration), Math.max(0, newAmplifier), true, true, true), true); - getPlayer(attacker).getData().addStat("chronos.aberrant-touch.slowness-stacks-applied", 1); - - StackState state = targetStacks.getOrDefault(target.getUniqueId(), new StackState(0, 0L)); - int stacks = (now - state.lastHitMillis() > getConfig().stackResetMillis) ? 1 : state.stacks() + 1; - if (stacks >= getConfig().rootAtStacks) { - target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getConfig().rootDurationTicks, getConfig().rootAmplifier, true, true, true), true); - target.setVelocity(new Vector()); - stacks = 0; - if (AdaptConfig.get().isAdvancements() && !getPlayer(attacker).getData().isGranted("challenge_chronos_aberrant_frozen")) { - getPlayer(attacker).getAdvancementHandler().grant("challenge_chronos_aberrant_frozen"); - } - } - targetStacks.put(target.getUniqueId(), new StackState(stacks, now)); - - if (getConfig().playClockSounds) { - ChronosSoundFX.playTouchProc(attacker, target.getLocation()); - } - xp(attacker, attacker.getLocation(), getConfig().xpPerProc + (getConfig().xpPerLevel * level)); - cooldowns.put(attacker.getUniqueId(), now + getConfig().cooldownMillis); - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); - targetStacks.entrySet().removeIf(entry -> now - entry.getValue().lastHitMillis() > getConfig().stackResetMillis); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Melee attacks apply stacking slowness at the cost of hunger, with PvP caps.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Aberrant Touch adaptation.", impact = "True enables this behavior and false disables it.") - boolean playClockSounds = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.38; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Add Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int durationAddTicks = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Per Level Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int durationPerLevelTicks = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Player Duration Cap Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int playerDurationCapTicks = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Player Amplifier Cap for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int playerAmplifierCap = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Entity Duration Cap Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int entityDurationCapTicks = 120; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Entity Duration Cap Per Level Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int entityDurationCapPerLevelTicks = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Entity Amplifier Cap for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int entityAmplifierCap = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hungerCost = 1.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Food Level for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int minimumFoodLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Root At Stacks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int rootAtStacks = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Root Duration Ticks for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int rootDurationTicks = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Root Amplifier for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int rootAmplifier = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Reset Millis for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long stackResetMillis = 2500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownMillis = 250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Proc for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerProc = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Level for the Chronos Aberrant Touch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerLevel = 1.25; - } - - private record StackState(int stacks, long lastHitMillis) { - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosInstantRecall.java b/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosInstantRecall.java deleted file mode 100644 index 0e953376e..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosInstantRecall.java +++ /dev/null @@ -1,1260 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.chronos; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ChronoTimeBombItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.World; -import org.bukkit.attribute.Attribute; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.event.player.PlayerToggleFlightEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.Vector; - -import java.util.*; - -public class ChronosInstantRecall extends SimpleAdaptation { - private static final EnumSet RECALL_ACTIONS = EnumSet.of( - Action.RIGHT_CLICK_AIR, - Action.RIGHT_CLICK_BLOCK, - Action.LEFT_CLICK_AIR, - Action.LEFT_CLICK_BLOCK - ); - private static final Map TELEPORT_XP_SUPPRESS_UNTIL = new HashMap<>(); - - private final Map> snapshots; - private final Map lastSnapshot; - private final Map cooldowns; - private final Set cooldownReadyNotify; - private final Map rewindProtection; - private final Set rewinding; - private final Map recallXpStamps; - private final Map jumpArmUntil; - private final Map lastOnGround; - - public ChronosInstantRecall() { - super("chronos-instant-recall"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("chronos.instant_recall.description")); - setDisplayName(Localizer.dLocalize("chronos.instant_recall.name")); - setIcon(Material.RECOVERY_COMPASS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(50); - snapshots = new HashMap<>(); - lastSnapshot = new HashMap<>(); - cooldowns = new HashMap<>(); - cooldownReadyNotify = new HashSet<>(); - rewindProtection = new HashMap<>(); - rewinding = new HashSet<>(); - recallXpStamps = new HashMap<>(); - jumpArmUntil = new HashMap<>(); - lastOnGround = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CLOCK) - .key("challenge_chronos_recall_50") - .title(Localizer.dLocalize("advancement.challenge_chronos_recall_50.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_recall_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.RECOVERY_COMPASS) - .key("challenge_chronos_recall_1k") - .title(Localizer.dLocalize("advancement.challenge_chronos_recall_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_recall_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.RECOVERY_COMPASS) - .key("challenge_chronos_recall_cheat_death") - .title(Localizer.dLocalize("advancement.challenge_chronos_recall_cheat_death.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_recall_cheat_death.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_chronos_recall_50", "chronos.instant-recall.recalls", 50, 300); - registerMilestone("challenge_chronos_recall_1k", "chronos.instant-recall.recalls", 1000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.duration(getRewindDurationMillis(level), 1) + " " + Localizer.dLocalize("chronos.instant_recall.lore1")); - v.addLore(C.RED + "* " + Form.duration(getCooldownMillis(level), 1) + " " + Localizer.dLocalize("chronos.instant_recall.lore2")); - v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.instant_recall.lore3")); - List combos = getTriggerCombos(); - if (combos.isEmpty()) { - v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + "none"); - return; - } - - for (String combo : combos) { - v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + combo); - } - } - - @Override - public String getDescription() { - return "Rewind to a recent snapshot with health and hunger restored. " + summarizeTriggerDescription(); - } - - private String summarizeTriggerDescription() { - List combos = getTriggerCombos(); - if (combos.isEmpty()) { - return "No active triggers are currently enabled."; - } - - if (combos.size() == 1) { - return "Trigger: " + combos.get(0) + "."; - } - - if (combos.size() == 2) { - return "Triggers: " + combos.get(0) + " or " + combos.get(1) + "."; - } - - return "Triggers: " + combos.get(0) + ", " + combos.get(1) + ", +" + (combos.size() - 2) + " more."; - } - - private long getRewindDurationMillis(int level) { - return (long) (getRewindDurationSeconds(level) * 1000D); - } - - private double getRewindDurationSeconds(int level) { - double raw = getConfig().baseRewindSeconds + (level * getConfig().rewindSecondsPerLevel); - return Math.max(0.25D, Math.min(getConfig().maxRewindSeconds, raw)); - } - - private long getCooldownMillis(int level) { - return getRewindDurationMillis(level) + (getConfig().cooldownPaddingSeconds * 1000L); - } - - private long getMaximumHistoryMillis() { - return (long) ((getRewindDurationSeconds(getConfig().maxLevel) + getConfig().historyPaddingSeconds) * 1000D); - } - - private int getRewindAnimationTicks() { - int durationMillis = Math.max(100, getConfig().rewindAnimationDurationMillis); - int ticks = (int) Math.ceil(durationMillis / 50D); - if (ticks <= 1) { - ticks = Math.max(2, getConfig().rewindAnimationTicks); - } - - return Math.max(2, ticks); - } - - private boolean isSingleWorldPath(List path) { - if (path.isEmpty()) { - return false; - } - - String world = path.get(0).worldName(); - for (Snapshot snapshot : path) { - if (!Objects.equals(world, snapshot.worldName())) { - return false; - } - } - - return true; - } - - private ArmorStand spawnRecallCameraAnchor(Player p) { - Location location = p.getLocation().clone(); - if (location.getWorld() == null) { - return null; - } - - return location.getWorld().spawn(location, ArmorStand.class, stand -> { - stand.setInvisible(true); - stand.setMarker(false); - stand.setGravity(false); - stand.setSilent(true); - stand.setInvulnerable(true); - stand.setCollidable(false); - stand.setBasePlate(false); - stand.setSmall(true); - stand.setPersistent(false); - }); - } - - private List getTriggerCombos() { - List triggers = new ArrayList<>(); - String clickSurface = getClickSurfaceLabel(); - if (getConfig().enableClockClickTrigger) { - appendClickCombos(triggers, "Clock", getConfig().clockClickLeftClick, getConfig().clockClickRightClick, clickSurface); - } - - if (getConfig().enableSprintClickTrigger) { - appendClickCombos(triggers, "Sprint + Clock", getConfig().sprintClickLeftClick, getConfig().sprintClickRightClick, clickSurface); - } - - if (getConfig().enableSingleSneakTrigger) { - String combo = getConfig().singleSneakRequiresSprint ? "Sprint + Sneak" : "Sneak"; - if (getConfig().singleSneakRequiresClockInHand) { - combo += " + Clock"; - } - triggers.add(combo); - } - - if (getConfig().enableDoubleJumpTrigger) { - String combo = "Double Jump"; - if (getConfig().doubleJumpRequiresSprint) { - combo += " + Sprint"; - } - if (getConfig().doubleJumpRequiresClockInHand) { - combo += " + Clock"; - } - triggers.add(combo); - } - - return triggers; - } - - private void appendClickCombos(List triggers, String prefix, boolean allowLeft, boolean allowRight, String clickSurface) { - if (clickSurface.isBlank()) { - return; - } - - if (allowLeft) { - triggers.add(prefix + " + Left Click" + clickSurface); - } - - if (allowRight) { - triggers.add(prefix + " + Right Click" + clickSurface); - } - } - - private String getClickSurfaceLabel() { - if (getConfig().allowAirClicks && getConfig().allowBlockClicks) { - return " (air/block)"; - } - - if (getConfig().allowAirClicks) { - return " (air)"; - } - - if (getConfig().allowBlockClicks) { - return " (block)"; - } - - return ""; - } - - private RecallXPContext buildRecallXPContext(Snapshot from, Snapshot to) { - double distance; - if (Objects.equals(from.worldName(), to.worldName())) { - double dx = from.x() - to.x(); - double dy = from.y() - to.y(); - double dz = from.z() - to.z(); - distance = Math.sqrt((dx * dx) + (dy * dy) + (dz * dz)); - } else { - distance = getConfig().xpCrossWorldDistanceCredit; - } - - double healthRecovered = Math.max(0D, to.health() - from.health()); - double hungerRecovered = Math.max(0D, to.foodLevel() - from.foodLevel()); - double saturationRecovered = Math.max(0D, to.saturation() - from.saturation()); - - return new RecallXPContext( - from.worldName(), - from.x(), - from.y(), - from.z(), - to.worldName(), - to.x(), - to.y(), - to.z(), - distance, - healthRecovered, - hungerRecovered, - saturationRecovered); - } - - private boolean pointsAreSimilar(String worldA, double ax, double ay, double az, String worldB, double bx, double by, double bz, double radius) { - if (!Objects.equals(worldA, worldB)) { - return false; - } - - double dx = ax - bx; - double dy = ay - by; - double dz = az - bz; - return (dx * dx) + (dy * dy) + (dz * dz) <= (radius * radius); - } - - private boolean isRepeatRecall(RecallXPFarmStamp stamp, RecallXPContext context) { - return pointsAreSimilar(stamp.fromWorld(), stamp.fromX(), stamp.fromY(), stamp.fromZ(), - context.fromWorld(), context.fromX(), context.fromY(), context.fromZ(), - getConfig().xpRepeatSourceRadius) - && pointsAreSimilar(stamp.toWorld(), stamp.toX(), stamp.toY(), stamp.toZ(), - context.toWorld(), context.toX(), context.toY(), context.toZ(), - getConfig().xpRepeatTargetRadius); - } - - private double computeRecallXPGain(UUID playerId, int level, RecallXPContext context, long now) { - double raw = (context.distance() * getConfig().xpPerDistanceBlock) - + (context.healthRecovered() * getConfig().xpPerHealthPoint) - + (context.hungerRecovered() * getConfig().xpPerHungerPoint) - + (context.saturationRecovered() * getConfig().xpPerSaturationPoint); - - if (raw < getConfig().xpMinRawReward) { - return 0D; - } - - double leveled = raw * (1D + ((Math.max(1, level) - 1) * getConfig().xpLevelMultiplierPerLevel)); - double multiplier = 1D; - - RecallXPFarmStamp previous = recallXpStamps.get(playerId); - if (previous != null) { - long elapsed = now - previous.awardedAt(); - if (elapsed < getConfig().xpDiminishWindowMillis) { - double t = Math.max(0D, Math.min(1D, elapsed / (double) Math.max(1L, getConfig().xpDiminishWindowMillis))); - multiplier *= getConfig().xpDiminishMinMultiplier + ((1D - getConfig().xpDiminishMinMultiplier) * t); - } - - if (elapsed < getConfig().xpRepeatWindowMillis && isRepeatRecall(previous, context)) { - multiplier *= getConfig().xpRepeatPenaltyMultiplier; - } - } - - double reward = Math.min(getConfig().xpMaxAward, leveled * multiplier); - if (reward < getConfig().xpMinAward) { - return 0D; - } - - return reward; - } - - private Snapshot snapshotFromPlayer(Player p, long now) { - return new Snapshot(now, - p.getWorld().getName(), - p.getLocation().getX(), - p.getLocation().getY(), - p.getLocation().getZ(), - p.getLocation().getYaw(), - p.getLocation().getPitch(), - p.getHealth(), - p.getFoodLevel(), - p.getSaturation(), - p.getExhaustion(), - p.getFireTicks()); - } - - private void captureSnapshot(Player p) { - long now = M.ms(); - UUID id = p.getUniqueId(); - long last = lastSnapshot.getOrDefault(id, 0L); - if (now - last < getConfig().snapshotIntervalMillis) { - return; - } - - lastSnapshot.put(id, now); - Deque queue = snapshots.computeIfAbsent(id, k -> new ArrayDeque<>()); - queue.addLast(snapshotFromPlayer(p, now)); - - long maxAge = getMaximumHistoryMillis(); - while (!queue.isEmpty() && now - queue.getFirst().timestamp() > maxAge) { - queue.removeFirst(); - } - } - - private Snapshot findSnapshot(Player p, long rewindMillis) { - Deque queue = snapshots.get(p.getUniqueId()); - if (queue == null || queue.isEmpty()) { - return null; - } - - long target = M.ms() - rewindMillis; - Snapshot fallback = queue.getFirst(); - - for (Snapshot s : queue) { - if (s.timestamp() <= target) { - fallback = s; - } else { - break; - } - } - - return fallback; - } - - private List buildRewindPath(Player p, long rewindMillis, Snapshot anchor) { - List path = new ArrayList<>(); - long now = M.ms(); - path.add(snapshotFromPlayer(p, now)); - - Deque queue = snapshots.get(p.getUniqueId()); - if (queue == null || queue.isEmpty()) { - path.add(anchor); - return path; - } - - Iterator reverse = queue.descendingIterator(); - while (reverse.hasNext()) { - Snapshot snap = reverse.next(); - if (snap.timestamp() < anchor.timestamp()) { - break; - } - if (snap.timestamp() <= now) { - path.add(snap); - } - } - - Snapshot last = path.get(path.size() - 1); - if (last.timestamp() != anchor.timestamp()) { - path.add(anchor); - } - - if (path.size() < 2) { - path.add(anchor); - } - - return path; - } - - private List buildAnimationPath(List rawPath, int animationTicks) { - List animationPath = new ArrayList<>(); - if (rawPath.isEmpty()) { - return animationPath; - } - - if (animationTicks <= 1 || rawPath.size() == 1) { - animationPath.add(rawPath.get(rawPath.size() - 1)); - return animationPath; - } - - for (int step = 0; step < animationTicks; step++) { - double progress = step / (double) (animationTicks - 1); - double scaled = progress * (rawPath.size() - 1); - int lower = (int) Math.floor(scaled); - int upper = Math.min(rawPath.size() - 1, lower + 1); - double alpha = scaled - lower; - Snapshot a = rawPath.get(lower); - Snapshot b = rawPath.get(upper); - animationPath.add(interpolateSnapshot(a, b, alpha)); - } - - Snapshot anchor = rawPath.get(rawPath.size() - 1); - animationPath.set(animationPath.size() - 1, anchor); - return animationPath; - } - - private Snapshot interpolateSnapshot(Snapshot a, Snapshot b, double alpha) { - if (alpha <= 0D) { - return a; - } - if (alpha >= 1D) { - return b; - } - - long timestamp = (long) Math.round(lerp(a.timestamp(), b.timestamp(), alpha)); - String worldName = alpha < 0.5D ? a.worldName() : b.worldName(); - double x = lerp(a.x(), b.x(), alpha); - double y = lerp(a.y(), b.y(), alpha); - double z = lerp(a.z(), b.z(), alpha); - float yaw = lerpAngle(a.yaw(), b.yaw(), alpha); - float pitch = (float) lerp(a.pitch(), b.pitch(), alpha); - double health = lerp(a.health(), b.health(), alpha); - int foodLevel = (int) Math.round(lerp(a.foodLevel(), b.foodLevel(), alpha)); - float saturation = (float) lerp(a.saturation(), b.saturation(), alpha); - float exhaustion = (float) lerp(a.exhaustion(), b.exhaustion(), alpha); - int fireTicks = (int) Math.round(lerp(a.fireTicks(), b.fireTicks(), alpha)); - - return new Snapshot(timestamp, worldName, x, y, z, yaw, pitch, health, foodLevel, saturation, exhaustion, fireTicks); - } - - private double lerp(double a, double b, double alpha) { - return a + ((b - a) * alpha); - } - - private float lerpAngle(float from, float to, double alpha) { - float delta = to - from; - while (delta > 180F) { - delta -= 360F; - } - while (delta < -180F) { - delta += 360F; - } - - return from + (float) (delta * alpha); - } - - private Location toLocation(Snapshot snapshot, World fallback) { - World world = Bukkit.getWorld(snapshot.worldName()); - if (world == null) { - world = fallback; - } - return new Location(world, snapshot.x(), snapshot.y(), snapshot.z(), snapshot.yaw(), snapshot.pitch()); - } - - private void applySnapshotState(Player p, Snapshot snapshot) { - double maxHealth = p.getAttribute(Attribute.MAX_HEALTH) == null ? 20D : p.getAttribute(Attribute.MAX_HEALTH).getValue(); - p.setHealth(Math.max(1, Math.min(maxHealth, snapshot.health()))); - p.setFoodLevel(Math.max(0, Math.min(20, snapshot.foodLevel()))); - p.setSaturation(Math.max(0, snapshot.saturation())); - p.setExhaustion(Math.max(0, snapshot.exhaustion())); - p.setFireTicks(Math.max(0, snapshot.fireTicks())); - p.setFallDistance(0); - p.setVelocity(new Vector()); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - clearPlayerState(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - if (!isRecallEligible(p)) { - return; - } - - if (shouldTriggerSprintClockClick(e)) { - e.setCancelled(true); - attemptRecall(p); - return; - } - - if (shouldTriggerClockClick(e)) { - e.setCancelled(true); - attemptRecall(p); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerToggleFlightEvent e) { - Player p = e.getPlayer(); - UUID id = p.getUniqueId(); - if (!isRecallEligible(p) || !getConfig().enableDoubleJumpTrigger) { - return; - } - - Long armUntil = jumpArmUntil.get(id); - if (armUntil == null) { - return; - } - - e.setCancelled(true); - p.setFlying(false); - clearDoubleJumpArm(p, id); - if (armUntil > M.ms()) { - attemptRecall(p); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerToggleSneakEvent e) { - Player p = e.getPlayer(); - if (!e.isSneaking() || !isRecallEligible(p) || !getConfig().enableSingleSneakTrigger) { - return; - } - - if (getConfig().singleSneakRequiresSprint && !p.isSprinting()) { - return; - } - - if (getConfig().singleSneakRequiresClockInHand && !hasRecallClockInEitherHand(p)) { - return; - } - - attemptRecall(p); - } - - private EquipmentSlot resolveRecallHand(Player p, EquipmentSlot eventHand) { - ItemStack main = p.getInventory().getItemInMainHand(); - ItemStack off = p.getInventory().getItemInOffHand(); - - if (eventHand == null) { - if (isRecallClock(main)) { - return EquipmentSlot.HAND; - } - - if (isRecallClock(off)) { - return EquipmentSlot.OFF_HAND; - } - - return null; - } - - if (eventHand == EquipmentSlot.HAND) { - return isRecallClock(main) ? EquipmentSlot.HAND : null; - } - - if (eventHand == EquipmentSlot.OFF_HAND) { - if (isRecallClock(main)) { - return null; - } - - return isRecallClock(off) ? EquipmentSlot.OFF_HAND : null; - } - - return null; - } - - private boolean isRecallEligible(Player p) { - return hasAdaptation(p) && p.getGameMode() == GameMode.SURVIVAL; - } - - private boolean isLeftClick(Action action) { - return action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK; - } - - private boolean isRightClick(Action action) { - return action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK; - } - - private boolean isBlockClick(Action action) { - return action == Action.LEFT_CLICK_BLOCK || action == Action.RIGHT_CLICK_BLOCK; - } - - private boolean isActionAllowed(Action action) { - if (!RECALL_ACTIONS.contains(action)) { - return false; - } - - if (!getConfig().allowAirClicks && !isBlockClick(action)) { - return false; - } - - if (!getConfig().allowBlockClicks && isBlockClick(action)) { - return false; - } - - return true; - } - - private boolean shouldTriggerClockClick(PlayerInteractEvent e) { - if (!getConfig().enableClockClickTrigger) { - return false; - } - - Action action = e.getAction(); - if (!isActionAllowed(action)) { - return false; - } - - if (isLeftClick(action) && !getConfig().clockClickLeftClick) { - return false; - } - - if (isRightClick(action) && !getConfig().clockClickRightClick) { - return false; - } - - return resolveRecallHand(e.getPlayer(), e.getHand()) != null; - } - - private boolean shouldTriggerSprintClockClick(PlayerInteractEvent e) { - if (!getConfig().enableSprintClickTrigger || !e.getPlayer().isSprinting()) { - return false; - } - - Action action = e.getAction(); - if (!isActionAllowed(action)) { - return false; - } - - if (isLeftClick(action) && !getConfig().sprintClickLeftClick) { - return false; - } - - if (isRightClick(action) && !getConfig().sprintClickRightClick) { - return false; - } - - return resolveRecallHand(e.getPlayer(), e.getHand()) != null; - } - - private boolean hasRecallClockInEitherHand(Player p) { - return isRecallClock(p.getInventory().getItemInMainHand()) - || isRecallClock(p.getInventory().getItemInOffHand()); - } - - private boolean canArmDoubleJump(Player p) { - if (rewinding.contains(p.getUniqueId())) { - return false; - } - - if (getConfig().doubleJumpRequiresSprint && !p.isSprinting()) { - return false; - } - - return !getConfig().doubleJumpRequiresClockInHand || hasRecallClockInEitherHand(p); - } - - private void clearDoubleJumpArm(Player p, UUID id) { - if (jumpArmUntil.remove(id) == null) { - return; - } - - if (p.getGameMode() == GameMode.SURVIVAL) { - p.setAllowFlight(false); - p.setFlying(false); - } - } - - private void armDoubleJump(Player p, UUID id) { - int triggerWindowMillis = Math.max(150, getConfig().doubleJumpWindowMillis); - jumpArmUntil.put(id, M.ms() + triggerWindowMillis); - p.setAllowFlight(true); - J.s(() -> { - if (!p.isOnline()) { - return; - } - - Long armUntil = jumpArmUntil.get(id); - if (armUntil != null && armUntil <= M.ms()) { - clearDoubleJumpArm(p, id); - } - }, Math.max(1, (int) Math.ceil(triggerWindowMillis / 50D))); - } - - private boolean isDoubleJumpStart(boolean wasOnGround, boolean onGround, Player p) { - return wasOnGround - && !onGround - && p.getVelocity().getY() >= getConfig().doubleJumpMinVerticalVelocity; - } - - private void clearPlayerState(UUID id) { - snapshots.remove(id); - lastSnapshot.remove(id); - cooldowns.remove(id); - cooldownReadyNotify.remove(id); - rewindProtection.remove(id); - rewinding.remove(id); - recallXpStamps.remove(id); - jumpArmUntil.remove(id); - lastOnGround.remove(id); - TELEPORT_XP_SUPPRESS_UNTIL.remove(id); - } - - private static void markRecallTeleportSuppressed(UUID id, long suppressUntilMillis) { - long current = TELEPORT_XP_SUPPRESS_UNTIL.getOrDefault(id, 0L); - if (suppressUntilMillis > current) { - TELEPORT_XP_SUPPRESS_UNTIL.put(id, suppressUntilMillis); - } - } - - public static boolean isRecallTeleportSuppressed(Player p) { - if (p == null) { - return false; - } - - UUID id = p.getUniqueId(); - long until = TELEPORT_XP_SUPPRESS_UNTIL.getOrDefault(id, 0L); - if (until <= M.ms()) { - TELEPORT_XP_SUPPRESS_UNTIL.remove(id); - return false; - } - - return true; - } - - private boolean isRecallClock(ItemStack stack) { - return stack != null - && stack.getType() == Material.CLOCK - && !ChronoTimeBombItem.isBindableItem(stack); - } - - private void attemptRecall(Player p) { - UUID id = p.getUniqueId(); - if (!isRecallEligible(p)) { - return; - } - - clearDoubleJumpArm(p, id); - if (rewinding.contains(id)) { - return; - } - - long now = M.ms(); - long cooldown = cooldowns.getOrDefault(id, 0L); - if (cooldown > now) { - if (getConfig().playClockSounds) { - ChronosSoundFX.playClockReject(p); - } - return; - } - - int level = getLevel(p); - long rewindMillis = getRewindDurationMillis(level); - Snapshot anchor = findSnapshot(p, rewindMillis); - if (anchor == null) { - if (getConfig().playClockSounds) { - ChronosSoundFX.playClockReject(p); - } - return; - } - - int animationTicks = getRewindAnimationTicks(); - List path = buildRewindPath(p, rewindMillis, anchor); - List animationPath = buildAnimationPath(path, animationTicks); - if (animationPath.isEmpty()) { - if (getConfig().playClockSounds) { - ChronosSoundFX.playClockReject(p); - } - return; - } - - Snapshot finalSnapshot = animationPath.get(animationPath.size() - 1); - RecallXPContext xpContext = buildRecallXPContext(animationPath.get(0), finalSnapshot); - double healthBeforeRecall = p.getHealth(); - double healthAfterRecall = finalSnapshot.health(); - long castAt = M.ms(); - GameMode originalGameMode = p.getGameMode(); - boolean temporarySpectator = getConfig().rewindUseTemporarySpectator && originalGameMode == GameMode.SURVIVAL; - boolean allowClientCamera = temporarySpectator - && getConfig().rewindUseClientCamera - && isSingleWorldPath(animationPath); - ArmorStand cameraAnchor = null; - if (temporarySpectator) { - p.setGameMode(GameMode.SPECTATOR); - p.setFlying(true); - if (allowClientCamera) { - cameraAnchor = spawnRecallCameraAnchor(p); - if (cameraAnchor != null && cameraAnchor.isValid()) { - p.setSpectatorTarget(cameraAnchor); - } else { - allowClientCamera = false; - } - } - } - - cooldowns.put(id, castAt + getCooldownMillis(level)); - cooldownReadyNotify.add(id); - rewinding.add(id); - long protectionUntil = castAt + ((long) (animationTicks + getConfig().rewindProtectionTicks) * 50L); - rewindProtection.put(id, protectionUntil); - markRecallTeleportSuppressed(id, protectionUntil + ((long) Math.max(0, getConfig().rewindTeleportXpSuppressExtraTicks) * 50L)); - p.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, animationTicks + getConfig().rewindProtectionTicks, 0, true, false, false), true); - - if (getConfig().playClockSounds) { - ChronosSoundFX.playRewindStart(p); - ChronosSoundFX.playRewindFinish(p); - } - - final boolean initialClientCamera = allowClientCamera; - final ArmorStand initialCameraAnchor = cameraAnchor; - new BukkitRunnable() { - int step = 0; - Location lastLoc = p.getLocation().clone(); - final boolean[] clientCameraActive = new boolean[]{initialClientCamera}; - final ArmorStand[] cameraRef = new ArmorStand[]{initialCameraAnchor}; - - private void cleanupVisualState(boolean restoreGameMode) { - if (cameraRef[0] != null) { - Entity anchorEntity = cameraRef[0]; - cameraRef[0] = null; - if (anchorEntity.isValid()) { - anchorEntity.remove(); - } - } - - if (temporarySpectator) { - p.setSpectatorTarget(null); - if (restoreGameMode && p.getGameMode() == GameMode.SPECTATOR) { - p.setGameMode(originalGameMode); - p.setFlying(false); - } - } - } - - @Override - public void run() { - if (!p.isOnline() || p.isDead()) { - rewinding.remove(id); - cleanupVisualState(true); - cancel(); - return; - } - - float progress = animationTicks <= 1 ? 1f : (float) step / (float) (animationTicks - 1); - int index = Math.min(animationPath.size() - 1, step); - Snapshot snapshot = animationPath.get(index); - Location destination = toLocation(snapshot, p.getWorld()); - - if (getConfig().showRewindTraceParticles && lastLoc.getWorld() != null && lastLoc.getWorld().equals(destination.getWorld())) { - vfxParticleLine(lastLoc.clone().add(0, 1, 0), destination.clone().add(0, 1, 0), Particle.REVERSE_PORTAL, - Math.max(4, getConfig().rewindTracePoints), 1, 0.08D, 0.08D, 0.08D, 0D, null, true, - l -> l.getBlock().isPassable()); - } - - boolean movedClient = false; - if (clientCameraActive[0] && cameraRef[0] != null && cameraRef[0].isValid()) { - Entity target = p.getSpectatorTarget(); - if (target == null || !target.getUniqueId().equals(cameraRef[0].getUniqueId())) { - p.setSpectatorTarget(cameraRef[0]); - target = p.getSpectatorTarget(); - } - - if (target != null - && target.getUniqueId().equals(cameraRef[0].getUniqueId()) - && destination.getWorld() != null - && destination.getWorld().equals(cameraRef[0].getWorld())) { - cameraRef[0].teleport(destination, PlayerTeleportEvent.TeleportCause.PLUGIN); - movedClient = true; - } else { - clientCameraActive[0] = false; - } - } - - if (!movedClient) { - p.teleport(destination, PlayerTeleportEvent.TeleportCause.PLUGIN); - } - - if (!temporarySpectator || step >= animationPath.size() - 1) { - applySnapshotState(p, snapshot); - } - - if (getConfig().playClockSounds) { - ChronosSoundFX.playRewindStep(p, progress); - } - - lastLoc = destination; - step++; - - if (step >= animationTicks) { - cleanupVisualState(true); - Location finalDestination = toLocation(finalSnapshot, p.getWorld()); - if (finalDestination.getWorld() != null) { - p.teleport(finalDestination, PlayerTeleportEvent.TeleportCause.PLUGIN); - } - applySnapshotState(p, finalSnapshot); - - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.TOTEM_OF_UNDYING, p.getLocation().add(0, 1, 0), 26, 0.25, 0.35, 0.25, 0.01); - } - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.ITEM, p.getLocation().add(0, 1, 0), 18, 0.30, 0.30, 0.30, 0.01, new ItemStack(Material.CLOCK)); - } - if (getConfig().playClockSounds) { - ChronosSoundFX.playRewindFinish(p); - } - rewinding.remove(id); - getPlayer(p).getData().addStat("chronos.instant-recall.recalls", 1); - if (healthBeforeRecall <= 4 && healthAfterRecall >= 16 - && AdaptConfig.get().isAdvancements() - && !getPlayer(p).getData().isGranted("challenge_chronos_recall_cheat_death")) { - getPlayer(p).getAdvancementHandler().grant("challenge_chronos_recall_cheat_death"); - } - long awardAt = M.ms(); - double xpGain = computeRecallXPGain(id, level, xpContext, awardAt); - if (xpGain > 0D) { - xp(p, p.getLocation(), xpGain); - recallXpStamps.put(id, new RecallXPFarmStamp( - awardAt, - xpContext.fromWorld(), - xpContext.fromX(), - xpContext.fromY(), - xpContext.fromZ(), - xpContext.toWorld(), - xpContext.toX(), - xpContext.toY(), - xpContext.toZ())); - } - cancel(); - } - } - }.runTaskTimer(Adapt.instance, 0, 1); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Player p)) { - return; - } - - UUID id = p.getUniqueId(); - long protectedUntil = rewindProtection.getOrDefault(id, 0L); - if (rewinding.contains(id) || protectedUntil > M.ms()) { - e.setCancelled(true); - p.setNoDamageTicks(Math.max(p.getNoDamageTicks(), getConfig().rewindProtectionTicks)); - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(PlayerMoveEvent e) { - Player p = e.getPlayer(); - if (!isRecallEligible(p)) { - return; - } - - captureSnapshot(p); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onDoubleJumpMove(PlayerMoveEvent e) { - Player p = e.getPlayer(); - UUID id = p.getUniqueId(); - boolean wasOnGround = lastOnGround.getOrDefault(id, true); - boolean onGround = p.isOnGround(); - lastOnGround.put(id, onGround); - - if (!isRecallEligible(p) || !getConfig().enableDoubleJumpTrigger) { - clearDoubleJumpArm(p, id); - return; - } - - if (!wasOnGround && onGround) { - clearDoubleJumpArm(p, id); - return; - } - - if (!canArmDoubleJump(p)) { - clearDoubleJumpArm(p, id); - return; - } - - if (cooldowns.getOrDefault(id, 0L) > M.ms()) { - clearDoubleJumpArm(p, id); - return; - } - - if (isDoubleJumpStart(wasOnGround, onGround, p)) { - armDoubleJump(p, id); - return; - } - - Long armUntil = jumpArmUntil.get(id); - if (armUntil != null && armUntil <= M.ms()) { - clearDoubleJumpArm(p, id); - } - } - - @Override - public void onTick() { - long now = M.ms(); - - Iterator ready = cooldownReadyNotify.iterator(); - while (ready.hasNext()) { - UUID id = ready.next(); - Player p = Bukkit.getPlayer(id); - if (p == null) { - ready.remove(); - continue; - } - - long cooldown = cooldowns.getOrDefault(id, 0L); - if (cooldown <= now) { - if (getConfig().playClockSounds) { - ChronosSoundFX.playCooldownReady(p); - } - ready.remove(); - } - } - - cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); - rewindProtection.entrySet().removeIf(entry -> entry.getValue() <= now); - TELEPORT_XP_SUPPRESS_UNTIL.entrySet().removeIf(entry -> entry.getValue() <= now); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Click with a clock to rewind to a recent snapshot with health and hunger restored.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Instant Recall adaptation.", impact = "True enables this behavior and false disables it.") - boolean playClockSounds = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Rewind Trace Particles for the Chronos Instant Recall adaptation.", impact = "True enables this behavior and false disables it.") - boolean showRewindTraceParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Rewind Trace Points for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int rewindTracePoints = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Target rewind animation duration in milliseconds.", impact = "Higher values make recall rewind visuals slower/smoother; lower values make them faster.") - int rewindAnimationDurationMillis = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Legacy fallback rewind animation ticks used when duration is invalid.", impact = "Retained for backward compatibility with older configs.") - int rewindAnimationTicks = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Temporarily switches to spectator during rewind animation for smoother camera movement through obstacles.", impact = "True improves rewind smoothness; false keeps player in survival during rewind.") - boolean rewindUseTemporarySpectator = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Uses a client-side spectator camera anchor during rewind so server position only updates at the end.", impact = "True reduces jitter and rubber-banding by avoiding per-tick player teleports.") - boolean rewindUseClientCamera = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Extra ticks to suppress teleport XP/stat tracking after recall rewinds.", impact = "Higher values make it safer against teleport-XP side effects from other skills.") - int rewindTeleportXpSuppressExtraTicks = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables direct click-with-clock activation for instant recall.", impact = "True allows recall by clicking with a valid recall clock; false disables direct clock-click activation.") - boolean enableClockClickTrigger = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows left-click to activate recall when clock-click trigger is enabled.", impact = "True allows left-click activation; false blocks left-click activation.") - boolean clockClickLeftClick = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows right-click to activate recall when clock-click trigger is enabled.", impact = "True allows right-click activation; false blocks right-click activation.") - boolean clockClickRightClick = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables sprint + click activation for instant recall with a valid recall clock.", impact = "True allows sprint-click activation; false disables sprint-click activation.") - boolean enableSprintClickTrigger = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows left-click for sprint-click recall trigger.", impact = "True enables left-click sprint activation; false disables it.") - boolean sprintClickLeftClick = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows right-click for sprint-click recall trigger.", impact = "True enables right-click sprint activation; false disables it.") - boolean sprintClickRightClick = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows click-in-air interactions for recall click triggers.", impact = "True lets air-clicks activate enabled click modes; false blocks air-click activations.") - boolean allowAirClicks = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows click-on-block interactions for recall click triggers.", impact = "True lets block-clicks activate enabled click modes; false blocks block-click activations.") - boolean allowBlockClicks = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables single-sneak activation for instant recall.", impact = "True allows pressing sneak once to trigger recall.") - boolean enableSingleSneakTrigger = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Require sprinting for single-sneak instant recall trigger.", impact = "True requires sprint state when using single-sneak activation.") - boolean singleSneakRequiresSprint = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Require holding a valid recall clock in either hand for single-sneak trigger.", impact = "True keeps single-sneak recall tied to clock usage.") - boolean singleSneakRequiresClockInHand = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables double-tap jump activation for instant recall.", impact = "True allows jump-based recall trigger; false disables jump trigger.") - boolean enableDoubleJumpTrigger = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Require sprinting while double-jumping to trigger recall.", impact = "True requires sprint state for double-jump trigger; false allows it without sprint.") - boolean doubleJumpRequiresSprint = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Require holding a valid recall clock in either hand for double-jump trigger.", impact = "True requires clock-in-hand for jump trigger; false allows jump trigger without holding a clock.") - boolean doubleJumpRequiresClockInHand = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum milliseconds allowed between jump taps for double-jump recall.", impact = "Higher values make double-tap detection easier; lower values make it stricter.") - int doubleJumpWindowMillis = 450; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum upward velocity required to arm double-jump recall.", impact = "Higher values reduce accidental arm events; lower values increase sensitivity.") - double doubleJumpMinVerticalVelocity = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Rewind Seconds for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseRewindSeconds = 3.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Rewind Seconds Per Level for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rewindSecondsPerLevel = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Hard cap for recall rewind duration in seconds.", impact = "Prevents high levels/config values from exceeding this rewind window.") - double maxRewindSeconds = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Padding Seconds for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int cooldownPaddingSeconds = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Snapshot Interval Millis for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int snapshotIntervalMillis = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls History Padding Seconds for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int historyPaddingSeconds = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Rewind Protection Ticks for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int rewindProtectionTicks = 25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Distance Block for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerDistanceBlock = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Health Point for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerHealthPoint = 0.85; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Hunger Point for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerHungerPoint = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Saturation Point for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerSaturationPoint = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Level Multiplier Per Level for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpLevelMultiplierPerLevel = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Min Raw Reward for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpMinRawReward = 1.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Min Award for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpMinAward = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Max Award for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpMaxAward = 36; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Cross World Distance Credit for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpCrossWorldDistanceCredit = 16; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Diminish Window Millis for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long xpDiminishWindowMillis = 45000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Diminish Min Multiplier for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpDiminishMinMultiplier = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Repeat Window Millis for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long xpRepeatWindowMillis = 180000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Repeat Source Radius for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpRepeatSourceRadius = 3.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Repeat Target Radius for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpRepeatTargetRadius = 3.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Repeat Penalty Multiplier for the Chronos Instant Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpRepeatPenaltyMultiplier = 0.2; - } - - private record Snapshot(long timestamp, - String worldName, - double x, - double y, - double z, - float yaw, - float pitch, - double health, - int foodLevel, - float saturation, - float exhaustion, - int fireTicks) { - } - - private record RecallXPContext(String fromWorld, - double fromX, - double fromY, - double fromZ, - String toWorld, - double toX, - double toY, - double toZ, - double distance, - double healthRecovered, - double hungerRecovered, - double saturationRecovered) { - } - - private record RecallXPFarmStamp(long awardedAt, - String fromWorld, - double fromX, - double fromY, - double fromZ, - String toWorld, - double toX, - double toY, - double toZ) { - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosSoundFX.java b/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosSoundFX.java deleted file mode 100644 index c91728d52..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosSoundFX.java +++ /dev/null @@ -1,215 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.chronos; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.util.J; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.entity.Player; - -public final class ChronosSoundFX { - private ChronosSoundFX() { - } - - private static void play(Location location, Sound sound, float volume, float pitch) { - if (location == null || location.getWorld() == null) { - return; - } - if (!areSoundsEnabled()) { - return; - } - - Location at = location.clone(); - Runnable playTask = () -> { - World world = at.getWorld(); - if (world != null) { - world.playSound(at, sound, volume, pitch); - } - }; - - if (Bukkit.isPrimaryThread()) { - playTask.run(); - } else { - J.s(playTask); - } - } - - private static void playLater(Location location, Sound sound, float volume, float pitch, int delayTicks) { - if (location == null || location.getWorld() == null) { - return; - } - if (!areSoundsEnabled()) { - return; - } - - Location at = location.clone(); - Runnable playTask = () -> { - World world = at.getWorld(); - if (world != null) { - world.playSound(at, sound, volume, pitch); - } - }; - - if (delayTicks <= 0 && Bukkit.isPrimaryThread()) { - playTask.run(); - return; - } - - J.s(playTask, Math.max(0, delayTicks)); - } - - private static void playOnPlayer(Player player, Sound sound, float volume, float pitch) { - if (player == null || !player.isOnline()) { - return; - } - if (!areSoundsEnabled()) { - return; - } - - Runnable playTask = () -> { - if (!player.isOnline()) { - return; - } - Location at = player.getLocation(); - World world = at.getWorld(); - if (world != null) { - world.playSound(at, sound, volume, pitch); - } - }; - - if (Bukkit.isPrimaryThread()) { - playTask.run(); - } else { - J.s(playTask); - } - } - - private static void playOnPlayerLater(Player player, Sound sound, float volume, float pitch, int delayTicks) { - if (player == null || !player.isOnline()) { - return; - } - if (!areSoundsEnabled()) { - return; - } - - Runnable playTask = () -> { - if (!player.isOnline()) { - return; - } - Location at = player.getLocation(); - World world = at.getWorld(); - if (world != null) { - world.playSound(at, sound, volume, pitch); - } - }; - - if (delayTicks <= 0 && Bukkit.isPrimaryThread()) { - playTask.run(); - return; - } - - J.s(playTask, Math.max(0, delayTicks)); - } - - public static void playClockReject(Player p) { - Location l = p.getLocation(); - play(l, Sound.BLOCK_NOTE_BLOCK_BASS, 0.42f, 0.58f); - play(l, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.32f, 0.62f); - } - - public static void playCooldownReady(Player p) { - Location l = p.getLocation(); - play(l, Sound.BLOCK_LEVER_CLICK, 0.35f, 1.75f); - playLater(l, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.28f, 1.93f, 1); - } - - public static void playBottleUse(Player p, Location at, int advanceTicks) { - float pitch = Math.min(1.95f, 0.8f + (advanceTicks / 175f)); - play(at, Sound.BLOCK_LEVER_CLICK, 0.55f, pitch); - play(at, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.35f, Math.min(2f, pitch + 0.24f)); - playLater(at, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.62f, Math.min(2f, pitch + 0.12f), 2); - } - - public static void playRewindStart(Player p) { - Location l = p.getLocation(); - play(l, Sound.BLOCK_NOTE_BLOCK_BASS, 0.45f, 0.82f); - play(l, Sound.BLOCK_LEVER_CLICK, 0.5f, 0.75f); - } - - public static void playRewindStep(Player p, float progress) { - Location l = p.getLocation(); - float clamped = Math.max(0f, Math.min(1f, progress)); - float pitch = 0.74f + (clamped * 0.95f); - play(l, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.2f, Math.min(2f, pitch + 0.1f)); - play(l, Sound.BLOCK_LEVER_CLICK, 0.24f, pitch); - } - - public static void playRewindFinish(Player p) { - // Reverse wind-up + 3 tines with varied tone/volume, all following the caster. - playOnPlayer(p, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.68f, 0.9f); - playOnPlayerLater(p, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.76f, 1.16f, 1); - playOnPlayerLater(p, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.84f, 1.42f, 2); - - // Low tine - playOnPlayerLater(p, Sound.BLOCK_BELL_RESONATE, 1.70f, 0.78f, 3); - playOnPlayerLater(p, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.88f, 1.28f, 4); - - // Mid tine - playOnPlayerLater(p, Sound.BLOCK_BELL_RESONATE, 1.44f, 0.98f, 7); - playOnPlayerLater(p, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.80f, 1.5f, 8); - - // High tine + trailing echo - playOnPlayerLater(p, Sound.BLOCK_BELL_RESONATE, 1.16f, 1.2f, 11); - playOnPlayerLater(p, Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.72f, 1.74f, 12); - playOnPlayerLater(p, Sound.BLOCK_BELL_RESONATE, 0.56f, 1.34f, 15); - } - - public static void playTouchProc(Player p, Location target) { - Location l = target == null ? p.getLocation() : target; - play(l, Sound.BLOCK_LEVER_CLICK, 0.34f, 1.7f); - play(l, Sound.BLOCK_NOTE_BLOCK_BASS, 0.26f, 1.18f); - } - - public static void playTimeBombArm(Player p) { - Location l = p.getLocation(); - play(l, Sound.BLOCK_LEVER_CLICK, 0.46f, 1.35f); - play(l, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.35f, 1.1f); - } - - public static void playTimeBombDetonate(Location center) { - play(center, Sound.BLOCK_NOTE_BLOCK_BASS, 0.8f, 0.6f); - play(center, Sound.BLOCK_LEVER_CLICK, 0.8f, 0.68f); - playLater(center, Sound.BLOCK_BELL_RESONATE, 0.55f, 1.05f, 2); - } - - public static void playTimeFieldTick(Location center, float pitch) { - float clamped = Math.max(0.35f, Math.min(2f, pitch)); - play(center, Sound.BLOCK_NOTE_BLOCK_BASS, 0.14f, Math.max(0.3f, Math.min(2f, clamped * 0.78f))); - play(center, Sound.BLOCK_LEVER_CLICK, 0.22f, clamped); - play(center, Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.15f, Math.min(2f, clamped + 0.18f)); - } - - private static boolean areSoundsEnabled() { - AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); - return effects == null || effects.isSoundsEnabled(); - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTemporalEcho.java b/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTemporalEcho.java deleted file mode 100644 index 8c55e3632..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTemporalEcho.java +++ /dev/null @@ -1,216 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.chronos; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Arrow; -import org.bukkit.entity.Egg; -import org.bukkit.entity.EnderPearl; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.entity.Snowball; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class ChronosTemporalEcho extends SimpleAdaptation { - private static final String ECHO_META = "adapt-chronos-temporal-echo"; - private final Map cooldowns = new HashMap<>(); - - public ChronosTemporalEcho() { - super("chronos-temporal-echo"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("chronos.temporal_echo.description")); - setDisplayName(Localizer.dLocalize("chronos.temporal_echo.name")); - setIcon(Material.AMETHYST_CLUSTER); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1600); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_chronos_echo_200") - .title(Localizer.dLocalize("advancement.challenge_chronos_echo_200.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_echo_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_chronos_echo_200", "chronos.temporal-echo.echo-hits", 200, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.duration(getEchoDelayTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("chronos.temporal_echo.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getEchoVelocityFactor(level), 0) + C.GRAY + " " + Localizer.dLocalize("chronos.temporal_echo.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("chronos.temporal_echo.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(ProjectileLaunchEvent e) { - if (!(e.getEntity().getShooter() instanceof Player p) || !hasAdaptation(p) || e.getEntity().hasMetadata(ECHO_META)) { - return; - } - - EchoType echoType = getEchoType(e.getEntity()); - if (echoType == null) { - return; - } - - int level = getLevel(p); - long now = System.currentTimeMillis(); - if (now < cooldowns.getOrDefault(p.getUniqueId(), 0L)) { - return; - } - - cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); - Projectile original = e.getEntity(); - Vector originalVelocity = original.getVelocity().clone(); - int delay = getEchoDelayTicks(level); - J.s(() -> spawnEcho(p, echoType, originalVelocity, level), delay); - } - - private void spawnEcho(Player p, EchoType type, Vector velocity, int level) { - if (!p.isOnline() || p.isDead()) { - return; - } - - Projectile echo = switch (type) { - case ARROW -> p.getWorld().spawnArrow(p.getEyeLocation().add(p.getLocation().getDirection().normalize().multiply(0.35)), - p.getLocation().getDirection(), 0.6f, 0f); - case SNOWBALL -> p.getWorld().spawn(p.getEyeLocation().add(p.getLocation().getDirection().normalize().multiply(0.35)), Snowball.class); - case EGG -> p.getWorld().spawn(p.getEyeLocation().add(p.getLocation().getDirection().normalize().multiply(0.35)), Egg.class); - case ENDER_PEARL -> p.getWorld().spawn(p.getEyeLocation().add(p.getLocation().getDirection().normalize().multiply(0.35)), EnderPearl.class); - }; - echo.setShooter(p); - echo.setVelocity(velocity.multiply(getEchoVelocityFactor(level))); - echo.setMetadata(ECHO_META, new FixedMetadataValue(Adapt.instance, true)); - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.75f, 1.35f); - getPlayer(p).getData().addStat("chronos.temporal-echo.echo-hits", 1); - xp(p, getConfig().xpPerEcho); - } - - private EchoType getEchoType(Projectile projectile) { - if (projectile instanceof Arrow) { - return EchoType.ARROW; - } - - if (projectile instanceof Snowball) { - return EchoType.SNOWBALL; - } - - if (projectile instanceof Egg) { - return EchoType.EGG; - } - - if (projectile instanceof EnderPearl) { - return EchoType.ENDER_PEARL; - } - - return null; - } - - private int getEchoDelayTicks(int level) { - return Math.max(1, (int) Math.round(getConfig().echoDelayTicksBase - (getLevelPercent(level) * getConfig().echoDelayTicksFactor))); - } - - private double getEchoVelocityFactor(int level) { - return Math.min(getConfig().maxEchoVelocityFactor, getConfig().echoVelocityFactorBase + (getLevelPercent(level) * getConfig().echoVelocityFactorFactor)); - } - - private long getCooldownMillis(int level) { - return Math.max(500L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Replays your last projectile action shortly after firing at reduced power.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Echo Delay Ticks Base for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double echoDelayTicksBase = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Echo Delay Ticks Factor for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double echoDelayTicksFactor = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Echo Velocity Factor Base for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double echoVelocityFactorBase = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Echo Velocity Factor Factor for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double echoVelocityFactorFactor = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Echo Velocity Factor for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxEchoVelocityFactor = 0.92; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisBase = 5000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisFactor = 2600; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Echo for the Chronos Temporal Echo adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerEcho = 12; - } - - private enum EchoType { - ARROW, - SNOWBALL, - EGG, - ENDER_PEARL - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTimeBomb.java b/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTimeBomb.java deleted file mode 100644 index ef7fb9ee0..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTimeBomb.java +++ /dev/null @@ -1,825 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.chronos; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ChronoTimeBombItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Color; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.entity.ThrownPotion; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.entity.LingeringPotionSplashEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; - -public class ChronosTimeBomb extends SimpleAdaptation { - private static final EnumSet SUPPORTED_ACTIONS = EnumSet.of( - Action.RIGHT_CLICK_AIR, - Action.RIGHT_CLICK_BLOCK - ); - private static final long PROJECTILE_TRACK_TTL_MS = 15000L; - - private final Map cooldowns; - private final Set cooldownReadyNotify; - private final List fields; - private final Map activeBombProjectiles; - private final Map frozenEntities; - private final Map frozenPlayers; - private final AtomicBoolean syncTickQueued; - - public ChronosTimeBomb() { - super("chronos-time-bomb"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("chronos.time_bomb.description")); - setDisplayName(Localizer.dLocalize("chronos.time_bomb.name")); - setIcon(Material.TNT); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(50); - - registerRecipe(AdaptRecipe.shapeless() - .key("chronos-time-bomb") - .ingredient(Material.SNOWBALL) - .ingredient(Material.CLOCK) - .ingredient(Material.DIAMOND) - .ingredient(Material.SAND) - .result(ChronoTimeBombItem.withData()) - .build()); - - cooldowns = new HashMap<>(); - cooldownReadyNotify = new HashSet<>(); - fields = new ArrayList<>(); - activeBombProjectiles = new HashMap<>(); - frozenEntities = new HashMap<>(); - frozenPlayers = new HashMap<>(); - syncTickQueued = new AtomicBoolean(false); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ICE) - .key("challenge_chronos_bomb_freeze_50") - .title(Localizer.dLocalize("advancement.challenge_chronos_bomb_freeze_50.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_bomb_freeze_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ICE) - .key("challenge_chronos_bomb_crowd_8") - .title(Localizer.dLocalize("advancement.challenge_chronos_bomb_crowd_8.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_bomb_crowd_8.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_chronos_bomb_freeze_50", "chronos.time-bomb.projectiles-frozen", 50, 500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getRadius(level) + " " + Localizer.dLocalize("chronos.time_bomb.lore1")); - v.addLore(C.YELLOW + "+ " + (getDurationTicks(level) / 20D) + "s " + Localizer.dLocalize("chronos.time_bomb.lore2")); - v.addLore(C.RED + "* " + (getCooldownMillis() / 1000D) + "s " + Localizer.dLocalize("chronos.time_bomb.lore3")); - v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.time_bomb.lore4")); - } - - private double getRadius(int level) { - return getConfig().baseRadius + ((Math.max(1, level) - 1) * getConfig().radiusPerLevel); - } - - private int getDurationTicks(int level) { - return getConfig().baseDurationTicks + ((Math.max(1, level) - 1) * getConfig().durationPerLevelTicks); - } - - private long getCooldownMillis() { - return Math.max(0L, getConfig().cooldownMillis); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - UUID id = e.getPlayer().getUniqueId(); - cooldowns.remove(id); - cooldownReadyNotify.remove(id); - frozenPlayers.remove(id); - activeBombProjectiles.entrySet().removeIf(entry -> entry.getValue().owner().equals(id)); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerInteractEvent e) { - Action action = e.getAction(); - if (!SUPPORTED_ACTIONS.contains(action)) { - return; - } - - EquipmentSlot handSlot = e.getHand(); - if (handSlot == null) { - return; - } - - Player p = e.getPlayer(); - if (handSlot == EquipmentSlot.OFF_HAND && ChronoTimeBombItem.isBindableItem(p.getInventory().getItemInMainHand())) { - return; - } - - ItemStack hand = getItemInHand(p, handSlot); - if (!ChronoTimeBombItem.isBindableItem(hand)) { - return; - } - - if (!hasAdaptation(p)) { - e.setCancelled(true); - return; - } - - long now = M.ms(); - long cooldown = cooldowns.getOrDefault(p.getUniqueId(), 0L); - if (cooldown > now) { - e.setCancelled(true); - if (getConfig().playClockSounds) { - ChronosSoundFX.playClockReject(p); - } - } - } - - private ItemStack getItemInHand(Player p, EquipmentSlot handSlot) { - return handSlot == EquipmentSlot.OFF_HAND - ? p.getInventory().getItemInOffHand() - : p.getInventory().getItemInMainHand(); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(ProjectileLaunchEvent e) { - if (!(e.getEntity() instanceof ThrownPotion potion)) { - return; - } - - if (!(potion.getShooter() instanceof Player p)) { - return; - } - - ItemStack item = potion.getItem(); - if (!ChronoTimeBombItem.isBindableItem(item)) { - return; - } - - if (!hasAdaptation(p)) { - e.setCancelled(true); - return; - } - - long now = M.ms(); - long cooldown = cooldowns.getOrDefault(p.getUniqueId(), 0L); - if (cooldown > now) { - e.setCancelled(true); - if (getConfig().playClockSounds) { - ChronosSoundFX.playClockReject(p); - } - return; - } - - int level = getLevel(p); - cooldowns.put(p.getUniqueId(), now + getCooldownMillis()); - cooldownReadyNotify.add(p.getUniqueId()); - activeBombProjectiles.put(potion.getUniqueId(), new ArmedBombProjectile(p.getUniqueId(), level, now)); - - if (getConfig().playClockSounds) { - ChronosSoundFX.playTimeBombArm(p); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(LingeringPotionSplashEvent e) { - ThrownPotion potion = e.getEntity(); - UUID projectileId = potion.getUniqueId(); - ArmedBombProjectile armed = activeBombProjectiles.remove(projectileId); - boolean bindable = ChronoTimeBombItem.isBindableItem(potion.getItem()); - - if (!bindable && armed == null) { - return; - } - - if (e.getAreaEffectCloud() != null) { - e.getAreaEffectCloud().remove(); - } - - if (armed == null) { - return; - } - - Location deployCenter = potion.getLocation().clone().add(0, getConfig().fieldCenterYOffset, 0); - Player owner = Bukkit.getPlayer(armed.owner()); - if (owner != null && !canInteract(owner, deployCenter)) { - return; - } - - deployField(armed.owner(), armed.level(), deployCenter); - } - - private void deployField(UUID ownerId, int level, Location center) { - TemporalField field = new TemporalField( - ownerId, - center, - getRadius(level), - M.ms() + (getDurationTicks(level) * 50L), - level, - M.ms(), - M.ms()); - fields.add(field); - - if (getConfig().playClockSounds) { - ChronosSoundFX.playTimeBombDetonate(center); - } - - if (areParticlesEnabled() && center.getWorld() != null) { - center.getWorld().spawnParticle(Particle.ENCHANT, center, 45, field.radius() * 0.4, 0.35, field.radius() * 0.4, 0.15); - center.getWorld().spawnParticle(Particle.END_ROD, center, 20, field.radius() * 0.2, 0.2, field.radius() * 0.2, 0.02); - } - - Player owner = Bukkit.getPlayer(ownerId); - if (owner != null) { - xp(owner, center, getConfig().xpOnCast + (level * getConfig().xpPerLevel)); - } - } - - private void freezeEntity(Entity entity) { - if (entity instanceof Player) { - return; - } - - UUID id = entity.getUniqueId(); - FrozenEntityState state = frozenEntities.get(id); - if (state == null) { - Boolean hadAi = null; - if (entity instanceof LivingEntity living) { - hadAi = living.hasAI(); - } - - state = new FrozenEntityState(entity.getVelocity().clone(), entity.hasGravity(), hadAi); - frozenEntities.put(id, state); - } else if (getConfig().accumulateFrozenImpulse) { - state.captureImpulse(entity.getVelocity(), getConfig().frozenImpulseMinMagnitude, getConfig().frozenImpulseSampleCap); - } - - entity.setGravity(false); - entity.setVelocity(new Vector()); - entity.setFallDistance(0); - - if (entity instanceof LivingEntity living) { - living.setAI(false); - } - } - - private void freezePlayer(Player player) { - UUID id = player.getUniqueId(); - if (!frozenPlayers.containsKey(id)) { - frozenPlayers.put(id, new FrozenPlayerState(player.getAllowFlight(), player.isFlying(), player.hasGravity())); - } - - if (!getConfig().freezePlayersInAir || player.isOnGround()) { - return; - } - - player.setFallDistance(0); - player.setAllowFlight(true); - player.setFlying(true); - player.setGravity(false); - player.setVelocity(new Vector()); - } - - private void unfreezePlayer(UUID playerId, FrozenPlayerState state) { - Player player = Bukkit.getPlayer(playerId); - if (player == null || !player.isOnline()) { - return; - } - - player.setGravity(state.gravity()); - if (state.allowFlight()) { - player.setAllowFlight(true); - player.setFlying(state.flying()); - } else { - player.setFlying(false); - player.setAllowFlight(false); - } - } - - private boolean shouldFreezePlayer(Player player) { - if (!getConfig().freezePlayersInAir) { - return false; - } - - UUID playerId = player.getUniqueId(); - for (TemporalField field : fields) { - if (field.center().getWorld() == null || !field.center().getWorld().equals(player.getWorld())) { - continue; - } - - if (field.center().distanceSquared(player.getLocation()) > field.radius() * field.radius()) { - continue; - } - - if (playerId.equals(field.owner())) { - continue; - } - - Player owner = Bukkit.getPlayer(field.owner()); - if (owner != null && !canPVP(owner, player.getLocation())) { - continue; - } - - return true; - } - - return false; - } - - private void unfreezeEntity(UUID entityId, FrozenEntityState state) { - Entity entity = Bukkit.getEntity(entityId); - if (entity == null || entity.isDead() || !entity.isValid()) { - return; - } - - entity.setGravity(state.gravity()); - entity.setVelocity(state.buildReleaseVelocity(getConfig().frozenImpulseReleaseCap)); - - if (entity instanceof LivingEntity living && state.hadAi() != null) { - living.setAI(state.hadAi()); - } - } - - private boolean isInsideAnyField(Location location) { - if (location.getWorld() == null) { - return false; - } - - for (TemporalField field : fields) { - if (field.center().getWorld() == null || !field.center().getWorld().equals(location.getWorld())) { - continue; - } - - if (field.center().distanceSquared(location) <= field.radius() * field.radius()) { - return true; - } - } - - return false; - } - - private void applyField(TemporalField field, long now) { - if (field.center().getWorld() == null) { - return; - } - - Player owner = Bukkit.getPlayer(field.owner()); - - Collection nearby = field.center().getWorld().getNearbyEntities(field.center(), field.radius(), field.radius(), field.radius()); - int entitiesSlowed = 0; - for (Entity entity : nearby) { - if (entity.getLocation().distanceSquared(field.center()) > field.radius() * field.radius()) { - continue; - } - - if (!(entity instanceof Player)) { - boolean wasNew = !frozenEntities.containsKey(entity.getUniqueId()); - freezeEntity(entity); - if (wasNew && owner != null) { - entitiesSlowed++; - if (entity instanceof Projectile) { - getPlayer(owner).getData().addStat("chronos.time-bomb.projectiles-frozen", 1); - } - getPlayer(owner).getData().addStat("chronos.time-bomb.entities-slowed", 1); - } - continue; - } - - LivingEntity living = (LivingEntity) entity; - boolean caster = entity.getUniqueId().equals(field.owner()); - if (caster) { - living.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getConfig().effectRefreshTicks, getConfig().casterSlownessAmplifier, true, false, false), true); - living.addPotionEffect(new PotionEffect(PotionEffectType.MINING_FATIGUE, getConfig().effectRefreshTicks, getConfig().fatigueAmplifier, true, false, false), true); - continue; - } - - if (owner != null) { - if (living instanceof Player targetPlayer) { - if (!canPVP(owner, targetPlayer.getLocation())) { - continue; - } - } else { - if (!canPVE(owner, living.getLocation())) { - continue; - } - } - } - - living.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getConfig().effectRefreshTicks, getConfig().slownessAmplifier, true, false, false), true); - living.addPotionEffect(new PotionEffect(PotionEffectType.MINING_FATIGUE, getConfig().effectRefreshTicks, getConfig().fatigueAmplifier, true, false, false), true); - freezePlayer((Player) living); - living.setVelocity(new Vector()); - entitiesSlowed++; - } - - if (entitiesSlowed >= 8 && owner != null - && AdaptConfig.get().isAdvancements() - && !getPlayer(owner).getData().isGranted("challenge_chronos_bomb_crowd_8")) { - getPlayer(owner).getAdvancementHandler().grant("challenge_chronos_bomb_crowd_8"); - } - - if (areParticlesEnabled()) { - spawnFieldSphere(field, now); - } - - if (getConfig().playClockSounds && now >= field.nextTickSoundAt()) { - long totalDurationMillis = Math.max(50L, getDurationTicks(field.level()) * 50L); - long remainingMillis = Math.max(0L, field.expiresAt() - now); - double progress = 1D - (remainingMillis / (double) totalDurationMillis); - progress = Math.max(0D, Math.min(1D, progress)); - - double pitchCurve = Math.pow(progress, Math.max(0.1D, getConfig().fieldTickPitchCurveExponent)); - float pitch = (float) (getConfig().fieldTickPitchStart - + ((getConfig().fieldTickPitchEnd - getConfig().fieldTickPitchStart) * pitchCurve)); - ChronosSoundFX.playTimeFieldTick(field.center(), pitch); - - double acceleration = Math.max(0D, Math.min(0.95D, getConfig().fieldTickAccelerationFactor)); - long interval = (long) Math.max(getConfig().fieldTickMinIntervalMillis, - getConfig().fieldTickSoundIntervalMillis * (1D - (progress * acceleration))); - field.setNextTickSoundAt(now + interval); - } - } - - private void spawnFieldSphere(TemporalField field, long now) { - if (!getConfig().showFieldSphere || now < field.nextVisualAt() || field.center().getWorld() == null) { - return; - } - - int particles = Math.max(24, getConfig().fieldSphereParticleCount); - double radius = Math.max(0.1, field.radius()); - vfxFastSphere(field.center(), radius, Color.BLACK, particles); - vfxFastSphere(field.center(), Math.max(0.2, radius * 0.75), Color.fromRGB(210, 210, 210), Math.max(12, particles / 2)); - - field.setNextVisualAt(now + Math.max(1, getConfig().fieldSphereRefreshMillis)); - } - - @Override - public void onTick() { - if (Bukkit.isPrimaryThread()) { - onTickSync(); - return; - } - - if (!syncTickQueued.compareAndSet(false, true)) { - return; - } - - J.s(() -> { - try { - onTickSync(); - } finally { - syncTickQueued.set(false); - } - }); - } - - private void onTickSync() { - long now = M.ms(); - cleanupBombProjectiles(now); - - Iterator ready = cooldownReadyNotify.iterator(); - while (ready.hasNext()) { - UUID id = ready.next(); - Player p = Bukkit.getPlayer(id); - if (p == null) { - ready.remove(); - continue; - } - - long cooldown = cooldowns.getOrDefault(id, 0L); - if (cooldown <= now) { - if (getConfig().playClockSounds) { - ChronosSoundFX.playCooldownReady(p); - } - ready.remove(); - } - } - - fields.removeIf(field -> field.expiresAt() <= now); - cooldowns.entrySet().removeIf(entry -> entry.getValue() <= now); - - for (TemporalField field : fields) { - applyField(field, now); - } - - Iterator> frozenPlayerIterator = frozenPlayers.entrySet().iterator(); - while (frozenPlayerIterator.hasNext()) { - Map.Entry entry = frozenPlayerIterator.next(); - Player player = Bukkit.getPlayer(entry.getKey()); - if (player == null || !player.isOnline() || player.isDead()) { - frozenPlayerIterator.remove(); - continue; - } - - if (shouldFreezePlayer(player)) { - freezePlayer(player); - continue; - } - - unfreezePlayer(entry.getKey(), entry.getValue()); - frozenPlayerIterator.remove(); - } - - Iterator> frozenIterator = frozenEntities.entrySet().iterator(); - while (frozenIterator.hasNext()) { - Map.Entry entry = frozenIterator.next(); - Entity entity = Bukkit.getEntity(entry.getKey()); - if (entity == null || entity.isDead() || !entity.isValid()) { - frozenIterator.remove(); - continue; - } - - if (isInsideAnyField(entity.getLocation())) { - freezeEntity(entity); - continue; - } - - unfreezeEntity(entry.getKey(), entry.getValue()); - frozenIterator.remove(); - } - } - - private void cleanupBombProjectiles(long now) { - Iterator> iterator = activeBombProjectiles.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - Entity entity = Bukkit.getEntity(entry.getKey()); - if (entity == null || !entity.isValid() || entity.isDead()) { - iterator.remove(); - continue; - } - - if (now - entry.getValue().launchedAt() > PROJECTILE_TRACK_TTL_MS) { - iterator.remove(); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Throw a crafted chrono bomb that creates a temporal field, slowing entities and freezing projectiles.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") - boolean playClockSounds = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Radius for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseRadius = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Per Level for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusPerLevel = 1.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Duration Ticks for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseDurationTicks = 60; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Per Level Ticks for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int durationPerLevelTicks = 25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownMillis = 15000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Target Deploy Range for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double targetDeployRange = 64; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Center YOffset for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fieldCenterYOffset = 1.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Slowness Amplifier for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int slownessAmplifier = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Caster Slowness Amplifier for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int casterSlownessAmplifier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fatigue Amplifier for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int fatigueAmplifier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Freeze Players In Air for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") - boolean freezePlayersInAir = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Accumulate Frozen Impulse for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") - boolean accumulateFrozenImpulse = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Frozen Impulse Min Magnitude for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double frozenImpulseMinMagnitude = 0.03; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Frozen Impulse Sample Cap for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double frozenImpulseSampleCap = 2.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Frozen Impulse Release Cap for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double frozenImpulseReleaseCap = 7.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effect Refresh Ticks for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int effectRefreshTicks = 24; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Field Sphere for the Chronos Time Bomb adaptation.", impact = "True enables this behavior and false disables it.") - boolean showFieldSphere = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Sphere Particle Count for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int fieldSphereParticleCount = 280; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Sphere Refresh Millis for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long fieldSphereRefreshMillis = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Tick Sound Interval Millis for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int fieldTickSoundIntervalMillis = 325; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Tick Min Interval Millis for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int fieldTickMinIntervalMillis = 70; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Tick Pitch Start for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fieldTickPitchStart = 0.42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Tick Pitch End for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fieldTickPitchEnd = 1.96; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Tick Pitch Curve Exponent for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fieldTickPitchCurveExponent = 3.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Field Tick Acceleration Factor for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fieldTickAccelerationFactor = 0.82; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Cast for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnCast = 28; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Level for the Chronos Time Bomb adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerLevel = 3; - } - - private static final class TemporalField { - private final UUID owner; - private final Location center; - private final double radius; - private final long expiresAt; - private final int level; - private long nextTickSoundAt; - private long nextVisualAt; - - private TemporalField(UUID owner, Location center, double radius, long expiresAt, int level, long nextTickSoundAt, long nextVisualAt) { - this.owner = owner; - this.center = center; - this.radius = radius; - this.expiresAt = expiresAt; - this.level = level; - this.nextTickSoundAt = nextTickSoundAt; - this.nextVisualAt = nextVisualAt; - } - - private UUID owner() { - return owner; - } - - private Location center() { - return center; - } - - private double radius() { - return radius; - } - - private long expiresAt() { - return expiresAt; - } - - private int level() { - return level; - } - - private long nextTickSoundAt() { - return nextTickSoundAt; - } - - private void setNextTickSoundAt(long nextTickSoundAt) { - this.nextTickSoundAt = nextTickSoundAt; - } - - private long nextVisualAt() { - return nextVisualAt; - } - - private void setNextVisualAt(long nextVisualAt) { - this.nextVisualAt = nextVisualAt; - } - } - - private static final class FrozenEntityState { - private final Vector originalVelocity; - private final boolean gravity; - private final Boolean hadAi; - private final Vector impulseDirectionAccumulator; - private double impulseMagnitudeAccumulator; - private int impulseSamples; - - private FrozenEntityState(Vector originalVelocity, boolean gravity, Boolean hadAi) { - this.originalVelocity = originalVelocity.clone(); - this.gravity = gravity; - this.hadAi = hadAi; - this.impulseDirectionAccumulator = new Vector(); - this.impulseMagnitudeAccumulator = 0D; - this.impulseSamples = 0; - } - - private boolean gravity() { - return gravity; - } - - private Boolean hadAi() { - return hadAi; - } - - private void captureImpulse(Vector currentVelocity, double minMagnitude, double sampleCap) { - if (currentVelocity == null) { - return; - } - - Vector sample = currentVelocity.clone(); - double magnitude = sample.length(); - if (magnitude < Math.max(0D, minMagnitude)) { - return; - } - - if (sampleCap > 0D && magnitude > sampleCap) { - sample = sample.normalize().multiply(sampleCap); - magnitude = sampleCap; - } - - if (magnitude <= 0D) { - return; - } - - impulseDirectionAccumulator.add(sample); - impulseMagnitudeAccumulator += magnitude; - impulseSamples++; - } - - private Vector buildReleaseVelocity(double releaseCap) { - Vector release = originalVelocity.clone(); - if (impulseSamples <= 0 - || impulseMagnitudeAccumulator <= 0D - || impulseDirectionAccumulator.lengthSquared() <= 1.0E-6D) { - return release; - } - - double magnitude = impulseMagnitudeAccumulator; - if (releaseCap > 0D) { - magnitude = Math.min(magnitude, releaseCap); - } - - Vector direction = impulseDirectionAccumulator.clone().normalize(); - release.add(direction.multiply(magnitude)); - return release; - } - } - - private record FrozenPlayerState(boolean allowFlight, boolean flying, boolean gravity) { - } - - private record ArmedBombProjectile(UUID owner, int level, long launchedAt) { - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTimeInABottle.java b/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTimeInABottle.java deleted file mode 100644 index 5ef7b7f41..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/chronos/ChronosTimeInABottle.java +++ /dev/null @@ -1,820 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.chronos; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ChronoTimeBottle; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Keyed; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.TreeType; -import org.bukkit.block.Block; -import org.bukkit.block.BrewingStand; -import org.bukkit.block.Campfire; -import org.bukkit.block.Furnace; -import org.bukkit.block.data.Ageable; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.type.Sapling; -import org.bukkit.entity.Player; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.event.player.PlayerItemConsumeEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.potion.PotionType; - -import java.util.concurrent.ThreadLocalRandom; - -public class ChronosTimeInABottle extends SimpleAdaptation { - private static final String RECIPE_KEY = "chronos-time-in-a-bottle"; - - public ChronosTimeInABottle() { - super("chronos-time-bottle"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("chronos.time_in_a_bottle.description")); - setDisplayName(Localizer.dLocalize("chronos.time_in_a_bottle.name")); - setIcon(Material.CLOCK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1000); - - registerRecipe(AdaptRecipe.shapeless() - .key(RECIPE_KEY) - .ingredient(Material.CLOCK) - .ingredient(Material.POTION) - .ingredient(Material.GLASS_BOTTLE) - .result(ChronoTimeBottle.withStoredSeconds(0)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CLOCK) - .key("challenge_chronos_bottle_1k") - .title(Localizer.dLocalize("advancement.challenge_chronos_bottle_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_bottle_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.RECOVERY_COMPASS) - .key("challenge_chronos_bottle_25k") - .title(Localizer.dLocalize("advancement.challenge_chronos_bottle_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_bottle_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_chronos_bottle_1k", "chronos.time-bottle.charges-spent", 1000, 500); - registerMilestone("challenge_chronos_bottle_25k", "chronos.time-bottle.charges-spent", 25000, 2000); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(CraftItemEvent e) { - if (!(e.getRecipe() instanceof Keyed keyed) || !keyed.getKey().getKey().equals(RECIPE_KEY)) { - return; - } - - boolean hasSwiftnessPotion = false; - for (ItemStack item : e.getInventory().getMatrix()) { - if (item == null || item.getType() != Material.POTION) { - continue; - } - if (item.getItemMeta() instanceof PotionMeta meta && meta.getBasePotionType() == PotionType.SWIFTNESS) { - hasSwiftnessPotion = true; - break; - } - } - - if (!hasSwiftnessPotion) { - e.setCancelled(true); - if (e.getWhoClicked() instanceof Player p && getConfig().playClockSounds) { - ChronosSoundFX.playClockReject(p); - } - } - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + (getConfig().chargePerSecond + (level * getConfig().chargePerSecondPerLevel)) + " " + Localizer.dLocalize("chronos.time_in_a_bottle.lore1")); - v.addLore(C.YELLOW + "+ " + Math.round(getCookTicksPerStoredSecond(level)) + " " + Localizer.dLocalize("chronos.time_in_a_bottle.lore2")); - v.addLore(C.GRAY + "* " + Localizer.dLocalize("chronos.time_in_a_bottle.lore3")); - } - - private double getCookTicksPerStoredSecond(int level) { - return getConfig().baseCookTicksPerStoredSecond + (level * getConfig().cookTicksPerSecondPerLevel); - } - - private int getMaxCookTicksPerUse(int level) { - return getConfig().maxCookTicksPerUse + (level * getConfig().maxCookTicksPerUsePerLevel); - } - - private double getBrewingTicksPerStoredSecond(int level) { - return getConfig().baseBrewingTicksPerStoredSecond + (level * getConfig().brewingTicksPerSecondPerLevel); - } - - private int getMaxBrewingTicksPerUse(int level) { - return getConfig().maxBrewingTicksPerUse + (level * getConfig().maxBrewingTicksPerUsePerLevel); - } - - private double getCampfireTicksPerStoredSecond(int level) { - return getConfig().baseCampfireTicksPerStoredSecond + (level * getConfig().campfireTicksPerSecondPerLevel); - } - - private int getMaxCampfireTicksPerUse(int level) { - return getConfig().maxCampfireTicksPerUse + (level * getConfig().maxCampfireTicksPerUsePerLevel); - } - - private double getFurnaceSpendMultiplier() { - return Math.max(0.01, getConfig().furnaceSpendMultiplier); - } - - private double getBrewingSpendMultiplier() { - return Math.max(0.01, getConfig().brewingSpendMultiplier); - } - - private double getCampfireSpendMultiplier() { - return Math.max(0.01, getConfig().campfireSpendMultiplier); - } - - private double getEntityAgeTicksPerStoredSecond(int level) { - return getConfig().baseEntityAgeTicksPerStoredSecond + (level * getConfig().entityAgeTicksPerSecondPerLevel); - } - - private int getMaxEntityAgeTicksPerUse(int level) { - return getConfig().maxEntityAgeTicksPerUse + (level * getConfig().maxEntityAgeTicksPerUsePerLevel); - } - - private double getEntitySpendMultiplier() { - return Math.max(0.01, getConfig().entitySpendMultiplier); - } - - private int getMaxGrowthStepsPerUse(int level) { - return getConfig().maxGrowthStepsPerUse + (level * getConfig().maxGrowthStepsPerUsePerLevel); - } - - private double getGrowthLevelScale(int level) { - return Math.max(getConfig().minGrowthCostLevelScale, 1D - (level * getConfig().growthCostReductionPerLevel)); - } - - private double getSaplingGrowChance(int level) { - return Math.max(0, Math.min(1, getConfig().saplingGrowChanceBase + (level * getConfig().saplingGrowChancePerLevel))); - } - - private TreeType getTreeType(Material type) { - return switch (type) { - case OAK_SAPLING -> ThreadLocalRandom.current().nextBoolean() ? TreeType.TREE : TreeType.BIG_TREE; - case SPRUCE_SAPLING -> ThreadLocalRandom.current().nextBoolean() ? TreeType.REDWOOD : TreeType.TALL_REDWOOD; - case BIRCH_SAPLING -> TreeType.BIRCH; - case JUNGLE_SAPLING -> ThreadLocalRandom.current().nextBoolean() ? TreeType.SMALL_JUNGLE : TreeType.JUNGLE; - case ACACIA_SAPLING -> TreeType.ACACIA; - case DARK_OAK_SAPLING -> TreeType.DARK_OAK; - case CHERRY_SAPLING -> TreeType.CHERRY; - case MANGROVE_PROPAGULE -> ThreadLocalRandom.current().nextBoolean() ? TreeType.MANGROVE : TreeType.TALL_MANGROVE; - case AZALEA, FLOWERING_AZALEA -> TreeType.AZALEA; - default -> null; - }; - } - - private double getGrowthStepCostSeconds(Block target, int level) { - GrowthProfile profile = detectGrowthProfile(target.getType()); - double naturalSeconds = getNaturalGrowthSeconds(profile); - int steps = getEstimatedGrowthSteps(target, profile); - double baseStepSeconds = naturalSeconds / Math.max(1, steps); - double profileMultiplier = getGrowthProfileCostMultiplier(profile); - double scaled = baseStepSeconds * profileMultiplier * getConfig().growthCostMultiplier * getGrowthLevelScale(level); - return Math.max(getConfig().minGrowthStepSeconds, scaled); - } - - private int getEstimatedGrowthSteps(Block target, GrowthProfile profile) { - BlockData data = target.getBlockData(); - if (data instanceof Ageable ageable) { - return Math.max(1, ageable.getMaximumAge()); - } - - if (data instanceof Sapling) { - return Math.max(1, getConfig().saplingGrowthSteps); - } - - return switch (profile) { - case SAPLING -> Math.max(1, getConfig().saplingGrowthSteps); - case STEM -> Math.max(1, getConfig().stemGrowthSteps); - case BERRY_BUSH -> Math.max(1, getConfig().berryGrowthSteps); - case VINE -> Math.max(1, getConfig().vineGrowthSteps); - case CAVE_VINE -> Math.max(1, getConfig().caveVineGrowthSteps); - case KELP -> Math.max(1, getConfig().kelpGrowthSteps); - default -> Math.max(1, getConfig().defaultGrowthSteps); - }; - } - - private double getNaturalGrowthSeconds(GrowthProfile profile) { - return switch (profile) { - case CROP -> getConfig().cropNaturalSeconds; - case NETHER_WART -> getConfig().netherWartNaturalSeconds; - case SAPLING -> getConfig().saplingNaturalSeconds; - case STEM -> getConfig().stemNaturalSeconds; - case BERRY_BUSH -> getConfig().berryBushNaturalSeconds; - case VINE -> getConfig().vineNaturalSeconds; - case CAVE_VINE -> getConfig().caveVineNaturalSeconds; - case KELP -> getConfig().kelpNaturalSeconds; - default -> getConfig().defaultGrowableNaturalSeconds; - }; - } - - private double getGrowthProfileCostMultiplier(GrowthProfile profile) { - return switch (profile) { - case CROP -> getConfig().cropCostMultiplier; - case NETHER_WART -> getConfig().netherWartCostMultiplier; - case SAPLING -> getConfig().saplingCostMultiplier; - case STEM -> getConfig().stemCostMultiplier; - case BERRY_BUSH -> getConfig().berryBushCostMultiplier; - case VINE -> getConfig().vineCostMultiplier; - case CAVE_VINE -> getConfig().caveVineCostMultiplier; - case KELP -> getConfig().kelpCostMultiplier; - default -> getConfig().defaultGrowableCostMultiplier; - }; - } - - private GrowthProfile detectGrowthProfile(Material type) { - String name = type.name(); - - if (type == Material.NETHER_WART) { - return GrowthProfile.NETHER_WART; - } - - if (type == Material.SWEET_BERRY_BUSH) { - return GrowthProfile.BERRY_BUSH; - } - - if (type == Material.KELP || type == Material.KELP_PLANT) { - return GrowthProfile.KELP; - } - - if (type == Material.CAVE_VINES || type == Material.CAVE_VINES_PLANT) { - return GrowthProfile.CAVE_VINE; - } - - if (type == Material.VINE || type == Material.WEEPING_VINES || type == Material.WEEPING_VINES_PLANT - || type == Material.TWISTING_VINES || type == Material.TWISTING_VINES_PLANT) { - return GrowthProfile.VINE; - } - - if (name.endsWith("_SAPLING") || type == Material.MANGROVE_PROPAGULE || type == Material.AZALEA - || type == Material.FLOWERING_AZALEA) { - return GrowthProfile.SAPLING; - } - - if (type == Material.PUMPKIN_STEM || type == Material.MELON_STEM || type == Material.ATTACHED_MELON_STEM - || type == Material.ATTACHED_PUMPKIN_STEM) { - return GrowthProfile.STEM; - } - - if (type == Material.WHEAT || type == Material.CARROTS || type == Material.POTATOES || type == Material.BEETROOTS - || type == Material.COCOA || type == Material.TORCHFLOWER_CROP || type == Material.PITCHER_CROP) { - return GrowthProfile.CROP; - } - - return GrowthProfile.DEFAULT; - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerItemConsumeEvent e) { - if (ChronoTimeBottle.isBindableItem(e.getItem())) { - e.setCancelled(true); - if (getConfig().playClockSounds) { - ChronosSoundFX.playClockReject(e.getPlayer()); - } - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerInteractEvent e) { - Action action = e.getAction(); - if (action != Action.RIGHT_CLICK_BLOCK && action != Action.RIGHT_CLICK_AIR) { - return; - } - - Player p = e.getPlayer(); - EquipmentSlot handSlot = e.getHand(); - if (handSlot == null) { - return; - } - - ItemStack hand = handSlot == EquipmentSlot.OFF_HAND - ? p.getInventory().getItemInOffHand() - : p.getInventory().getItemInMainHand(); - if (!ChronoTimeBottle.isBindableItem(hand)) { - return; - } - - // Chrono bottles are never drinkable; always deny vanilla potion use. - e.setUseItemInHand(Event.Result.DENY); - - if (!hasAdaptation(p) || action != Action.RIGHT_CLICK_BLOCK || e.getClickedBlock() == null) { - return; - } - - Block clicked = e.getClickedBlock(); - if (!canInteract(p, clicked.getLocation())) { - return; - } - - int level = getLevel(p); - double storedSeconds = ChronoTimeBottle.getStoredSeconds(hand); - if (storedSeconds <= 0) { - return; - } - - TimeSpendResult result = accelerateTarget(clicked, storedSeconds, level); - if (!result.applied()) { - return; - } - - e.setCancelled(true); - ChronoTimeBottle.setStoredSeconds(hand, Math.max(0, storedSeconds - result.spentSeconds())); - getPlayer(p).getData().addStat("chronos.time-bottle.charges-spent", 1); - - if (getConfig().playClockSounds) { - ChronosSoundFX.playBottleUse(p, clicked.getLocation().add(0.5, 1.0, 0.5), result.effectTicks()); - } - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.ENCHANT, clicked.getLocation().add(0.5, 1.0, 0.5), 32, 0.35, 0.3, 0.35, 0.08); - p.getWorld().spawnParticle(Particle.END_ROD, clicked.getLocation().add(0.5, 1.0, 0.5), 8, 0.1, 0.2, 0.1, 0.01); - } - - xp(p, clicked.getLocation().add(0.5, 1.0, 0.5), Math.min(getConfig().maxXPPerUse, result.xpGain())); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerInteractEntityEvent e) { - if (e.getHand() != EquipmentSlot.HAND) { - return; - } - - Player p = e.getPlayer(); - ItemStack hand = p.getInventory().getItemInMainHand(); - if (!ChronoTimeBottle.isBindableItem(hand) || !hasAdaptation(p)) { - return; - } - - if (!(e.getRightClicked() instanceof org.bukkit.entity.Ageable ageable)) { - return; - } - - if (!canInteract(p, e.getRightClicked().getLocation())) { - return; - } - - int currentAge = ageable.getAge(); - if (currentAge == 0) { - return; - } - - int level = getLevel(p); - double storedSeconds = ChronoTimeBottle.getStoredSeconds(hand); - if (storedSeconds <= 0) { - return; - } - - TimeSpendResult result = accelerateAgeableEntity(ageable, storedSeconds, level); - if (!result.applied()) { - return; - } - - e.setCancelled(true); - ChronoTimeBottle.setStoredSeconds(hand, Math.max(0, storedSeconds - result.spentSeconds())); - getPlayer(p).getData().addStat("chronos.time-bottle.charges-spent", 1); - - if (getConfig().playClockSounds) { - ChronosSoundFX.playBottleUse(p, ageable.getLocation().add(0, 1.0, 0), result.effectTicks()); - } - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.ENCHANT, ageable.getLocation().add(0, 1.0, 0), 24, 0.3, 0.4, 0.3, 0.05); - p.getWorld().spawnParticle(Particle.END_ROD, ageable.getLocation().add(0, 1.0, 0), 7, 0.1, 0.25, 0.1, 0.01); - } - - xp(p, ageable.getLocation().add(0, 1.0, 0), Math.min(getConfig().maxXPPerUse, result.xpGain())); - } - - private TimeSpendResult accelerateTarget(Block clicked, double storedSeconds, int level) { - if (clicked.getState() instanceof Furnace furnace) { - return accelerateFurnace(furnace, storedSeconds, level); - } - - if (clicked.getState() instanceof BrewingStand brewingStand) { - return accelerateBrewingStand(brewingStand, storedSeconds, level); - } - - if (clicked.getState() instanceof Campfire campfire) { - return accelerateCampfire(campfire, storedSeconds, level); - } - - return accelerateGrowables(clicked, storedSeconds, level); - } - - private TimeSpendResult accelerateFurnace(Furnace furnace, double storedSeconds, int level) { - if (furnace.getInventory().getSmelting() == null || furnace.getInventory().getSmelting().getType().isAir()) { - return TimeSpendResult.none(); - } - - int totalCookTime = furnace.getCookTimeTotal(); - int currentCookTime = furnace.getCookTime(); - int remainingCookTime = Math.max(0, totalCookTime - currentCookTime); - if (remainingCookTime <= 0) { - return TimeSpendResult.none(); - } - - double cookTicksPerStoredSecond = getCookTicksPerStoredSecond(level); - double spendMultiplier = getFurnaceSpendMultiplier(); - int affordableTicks = (int) Math.floor((storedSeconds / spendMultiplier) * cookTicksPerStoredSecond); - int advanceTicks = Math.min(remainingCookTime, Math.min(affordableTicks, getMaxCookTicksPerUse(level))); - if (advanceTicks <= 0) { - return TimeSpendResult.none(); - } - - furnace.setCookTime((short) Math.min(totalCookTime, currentCookTime + advanceTicks)); - furnace.update(true, true); - - double spentSeconds = (advanceTicks / cookTicksPerStoredSecond) * spendMultiplier; - return new TimeSpendResult(spentSeconds, advanceTicks, advanceTicks * getConfig().xpPerCookTick); - } - - private TimeSpendResult accelerateBrewingStand(BrewingStand stand, double storedSeconds, int level) { - int brewingTime = stand.getBrewingTime(); - if (brewingTime <= 0) { - return TimeSpendResult.none(); - } - - double brewTicksPerStoredSecond = getBrewingTicksPerStoredSecond(level); - double spendMultiplier = getBrewingSpendMultiplier(); - int affordableTicks = (int) Math.floor((storedSeconds / spendMultiplier) * brewTicksPerStoredSecond); - int advanceTicks = Math.min(brewingTime, Math.min(affordableTicks, getMaxBrewingTicksPerUse(level))); - if (advanceTicks <= 0) { - return TimeSpendResult.none(); - } - - stand.setBrewingTime(Math.max(0, brewingTime - advanceTicks)); - stand.update(true, true); - - double spentSeconds = (advanceTicks / brewTicksPerStoredSecond) * spendMultiplier; - return new TimeSpendResult(spentSeconds, advanceTicks, advanceTicks * getConfig().xpPerBrewTick); - } - - private TimeSpendResult accelerateCampfire(Campfire campfire, double storedSeconds, int level) { - double campfireTicksPerStoredSecond = getCampfireTicksPerStoredSecond(level); - double spendMultiplier = getCampfireSpendMultiplier(); - int affordableTicks = (int) Math.floor((storedSeconds / spendMultiplier) * campfireTicksPerStoredSecond); - int budgetTicks = Math.min(affordableTicks, getMaxCampfireTicksPerUse(level)); - if (budgetTicks <= 0) { - return TimeSpendResult.none(); - } - - int usedTicks = 0; - for (int i = 0; i < campfire.getSize() && usedTicks < budgetTicks; i++) { - ItemStack item = campfire.getItem(i); - if (item == null || item.getType().isAir()) { - continue; - } - - int total = campfire.getCookTimeTotal(i); - int current = campfire.getCookTime(i); - int remaining = Math.max(0, total - current); - if (remaining <= 0) { - continue; - } - - int step = Math.min(remaining, budgetTicks - usedTicks); - campfire.setCookTime(i, current + step); - usedTicks += step; - } - - if (usedTicks <= 0) { - return TimeSpendResult.none(); - } - - campfire.update(true, true); - double spentSeconds = (usedTicks / campfireTicksPerStoredSecond) * spendMultiplier; - return new TimeSpendResult(spentSeconds, usedTicks, usedTicks * getConfig().xpPerCampfireTick); - } - - private TimeSpendResult accelerateAgeableEntity(org.bukkit.entity.Ageable entity, double storedSeconds, int level) { - int currentAge = entity.getAge(); - if (currentAge == 0) { - return TimeSpendResult.none(); - } - - double ageTicksPerStoredSecond = getEntityAgeTicksPerStoredSecond(level); - double spendMultiplier = getEntitySpendMultiplier(); - int affordableTicks = (int) Math.floor((storedSeconds / spendMultiplier) * ageTicksPerStoredSecond); - int advanceTicks = Math.min(Math.abs(currentAge), Math.min(affordableTicks, getMaxEntityAgeTicksPerUse(level))); - if (advanceTicks <= 0) { - return TimeSpendResult.none(); - } - - if (currentAge < 0) { - entity.setAge(Math.min(0, currentAge + advanceTicks)); - } else { - entity.setAge(Math.max(0, currentAge - advanceTicks)); - } - - double spentSeconds = (advanceTicks / ageTicksPerStoredSecond) * spendMultiplier; - return new TimeSpendResult(spentSeconds, advanceTicks, advanceTicks * getConfig().xpPerEntityAgeTick); - } - - private TimeSpendResult accelerateGrowables(Block clicked, double storedSeconds, int level) { - int attempts = getMaxGrowthStepsPerUse(level); - if (attempts <= 0 || storedSeconds <= 0) { - return TimeSpendResult.none(); - } - - double remainingSeconds = storedSeconds; - double spentSeconds = 0; - int successes = 0; - for (int i = 0; i < attempts; i++) { - Block target = clicked.getWorld().getBlockAt(clicked.getLocation()); - double stepCost = getGrowthStepCostSeconds(target, level); - if (remainingSeconds + 1.0E-6 < stepCost) { - break; - } - - if (!applyDirectGrowthStep(target, level)) { - break; - } - - remainingSeconds -= stepCost; - spentSeconds += stepCost; - successes++; - } - - if (successes <= 0 || spentSeconds <= 0) { - return TimeSpendResult.none(); - } - - int effectTicks = Math.max(8, successes * 20); - return new TimeSpendResult(spentSeconds, effectTicks, successes * getConfig().xpPerGrowthStep); - } - - private boolean applyDirectGrowthStep(Block block, int level) { - BlockData data = block.getBlockData(); - if (data instanceof Sapling sapling) { - if (sapling.getStage() < sapling.getMaximumStage()) { - sapling.setStage(Math.min(sapling.getMaximumStage(), sapling.getStage() + 1)); - block.setBlockData(data, true); - return true; - } - - if (!getConfig().allowSaplingTreeGeneration) { - return false; - } - - if (ThreadLocalRandom.current().nextDouble() > getSaplingGrowChance(level)) { - return true; - } - - TreeType treeType = getTreeType(block.getType()); - return treeType != null && block.getWorld().generateTree(block.getLocation(), treeType); - } - - if (data instanceof Ageable ageable && ageable.getAge() < ageable.getMaximumAge()) { - ageable.setAge(Math.min(ageable.getMaximumAge(), ageable.getAge() + 1)); - block.setBlockData(data, true); - return true; - } - - return false; - } - - private record TimeSpendResult(double spentSeconds, int effectTicks, double xpGain) { - private static TimeSpendResult none() { - return new TimeSpendResult(0, 0, 0); - } - - private boolean applied() { - return spentSeconds > 0; - } - } - - private enum GrowthProfile { - CROP, - NETHER_WART, - SAPLING, - STEM, - BERRY_BUSH, - VINE, - CAVE_VINE, - KELP, - DEFAULT - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (!hasAdaptation(p)) { - continue; - } - - int level = getLevel(p); - double chargePerSecond = getConfig().chargePerSecond + (level * getConfig().chargePerSecondPerLevel); - - for (ItemStack stack : p.getInventory().getContents()) { - if (!ChronoTimeBottle.isBindableItem(stack)) { - continue; - } - - double stored = ChronoTimeBottle.getStoredSeconds(stack); - double capped = Math.min(getConfig().maxStoredSeconds, stored + chargePerSecond); - if (capped > stored) { - ChronoTimeBottle.setStoredSeconds(stack, capped); - } - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Carry a temporal bottle that stores time to accelerate timed blocks and baby animals.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Chronos Time In ABottle adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Play Clock Sounds for the Chronos Time In ABottle adaptation.", impact = "True enables this behavior and false disables it.") - boolean playClockSounds = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Stored Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxStoredSeconds = 900; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Charge Per Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double chargePerSecond = 0.1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Charge Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double chargePerSecondPerLevel = 0.02; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Cook Ticks Per Stored Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseCookTicksPerStoredSecond = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cook Ticks Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cookTicksPerSecondPerLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Cook Ticks Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxCookTicksPerUse = 140; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Cook Ticks Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxCookTicksPerUsePerLevel = 35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Furnace Spend Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double furnaceSpendMultiplier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Brewing Ticks Per Stored Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseBrewingTicksPerStoredSecond = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Brewing Ticks Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double brewingTicksPerSecondPerLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Brewing Ticks Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxBrewingTicksPerUse = 140; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Brewing Ticks Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxBrewingTicksPerUsePerLevel = 35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Brewing Spend Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double brewingSpendMultiplier = 1.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Campfire Ticks Per Stored Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseCampfireTicksPerStoredSecond = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Campfire Ticks Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double campfireTicksPerSecondPerLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Campfire Ticks Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxCampfireTicksPerUse = 160; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Campfire Ticks Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxCampfireTicksPerUsePerLevel = 40; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Campfire Spend Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double campfireSpendMultiplier = 0.9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Entity Age Ticks Per Stored Second for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseEntityAgeTicksPerStoredSecond = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Entity Age Ticks Per Second Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double entityAgeTicksPerSecondPerLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Entity Age Ticks Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxEntityAgeTicksPerUse = 180; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Entity Age Ticks Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxEntityAgeTicksPerUsePerLevel = 55; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Entity Spend Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double entitySpendMultiplier = 1.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Growth Steps Per Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxGrowthStepsPerUse = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Growth Steps Per Use Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxGrowthStepsPerUsePerLevel = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Allow Sapling Tree Generation for the Chronos Time In ABottle adaptation.", impact = "True enables this behavior and false disables it.") - boolean allowSaplingTreeGeneration = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sapling Grow Chance Base for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double saplingGrowChanceBase = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sapling Grow Chance Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double saplingGrowChancePerLevel = 0.04; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Growth Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double growthCostMultiplier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Growth Cost Reduction Per Level for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double growthCostReductionPerLevel = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Growth Cost Level Scale for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minGrowthCostLevelScale = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Growth Step Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minGrowthStepSeconds = 0.06; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sapling Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int saplingGrowthSteps = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stem Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int stemGrowthSteps = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Berry Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int berryGrowthSteps = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Vine Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int vineGrowthSteps = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cave Vine Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int caveVineGrowthSteps = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Kelp Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int kelpGrowthSteps = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Default Growth Steps for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int defaultGrowthSteps = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Crop Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cropNaturalSeconds = 300; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Nether Wart Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double netherWartNaturalSeconds = 420; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sapling Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double saplingNaturalSeconds = 900; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stem Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double stemNaturalSeconds = 660; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Berry Bush Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double berryBushNaturalSeconds = 260; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Vine Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double vineNaturalSeconds = 300; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cave Vine Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double caveVineNaturalSeconds = 280; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Kelp Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double kelpNaturalSeconds = 240; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Default Growable Natural Seconds for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double defaultGrowableNaturalSeconds = 420; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Crop Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cropCostMultiplier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Nether Wart Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double netherWartCostMultiplier = 1.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sapling Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double saplingCostMultiplier = 2.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stem Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double stemCostMultiplier = 1.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Berry Bush Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double berryBushCostMultiplier = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Vine Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double vineCostMultiplier = 0.85; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cave Vine Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double caveVineCostMultiplier = 0.9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Kelp Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double kelpCostMultiplier = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Default Growable Cost Multiplier for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double defaultGrowableCostMultiplier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Cook Tick for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerCookTick = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Brew Tick for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerBrewTick = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Campfire Tick for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerCampfireTick = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Entity Age Tick for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerEntityAgeTick = 0.06; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Growth Step for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerGrowthStep = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max XPPer Use for the Chronos Time In ABottle adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxXPPerUse = 55; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingBackpacks.java b/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingBackpacks.java deleted file mode 100644 index 7571af672..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingBackpacks.java +++ /dev/null @@ -1,122 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.crafting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdvancementSpec; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public class CraftingBackpacks extends SimpleAdaptation { - - public CraftingBackpacks() { - super("crafting-backpacks"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("crafting.backpacks.description")); - setDisplayName(Localizer.dLocalize("crafting.backpacks.name")); - setIcon(Material.BUNDLE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(17779); - registerRecipe(AdaptRecipe.shaped() - .key("crafting-backpacks") - .ingredient(new MaterialChar('I', Material.LEATHER)) - .ingredient(new MaterialChar('L', Material.LEAD)) - .ingredient(new MaterialChar('C', Material.CHEST)) - .ingredient(new MaterialChar('X', Material.BARREL)) - .shapes(List.of( - "ILI", - "IXI", - "ICI")) - .result(new ItemStack(Material.BUNDLE, 1)) - .build()); - AdvancementSpec backpacksCrafted = AdvancementSpec.challenge( - "challenge_crafting_backpack_25", - Material.BUNDLE, - Localizer.dLocalize("advancement.challenge_crafting_backpack_25.title"), - Localizer.dLocalize("advancement.challenge_crafting_backpack_25.description") - ); - registerMilestone(backpacksCrafted, "crafting.backpacks.bundles-crafted", 25, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("crafting.backpacks.lore1")); - v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.backpacks.lore2")); - v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.backpacks.lore3")); - v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.backpacks.lore4")); - - } - - - @EventHandler - public void on(CraftItemEvent e) { - if (e.isCancelled()) return; - Player p = (Player) e.getWhoClicked(); - if (!hasAdaptation(p)) return; - if (e.getRecipe() != null && e.getRecipe().getResult().getType() == Material.BUNDLE) { - getPlayer(p).getData().addStat("crafting.backpacks.bundles-crafted", 1); - } - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Bundles for portable item storage.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingDeconstruction.java b/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingDeconstruction.java deleted file mode 100644 index c6f52aaee..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingDeconstruction.java +++ /dev/null @@ -1,216 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.crafting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdvancementSpec; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.Recipe; -import org.bukkit.inventory.ShapedRecipe; -import org.bukkit.inventory.ShapelessRecipe; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.util.RayTraceResult; - -import java.util.*; - -public class CraftingDeconstruction extends SimpleAdaptation { - public CraftingDeconstruction() { - super("crafting-deconstruction"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("crafting.deconstruction.description")); - setDisplayName(Localizer.dLocalize("crafting.deconstruction.name")); - setIcon(Material.SHEARS); - setBaseCost(getConfig().baseCost); - setMaxLevel(1); - setInterval(5590); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - AdvancementSpec deconstruction5k = AdvancementSpec.challenge( - "challenge_crafting_decon_5k", - Material.IRON_INGOT, - Localizer.dLocalize("advancement.challenge_crafting_decon_5k.title"), - Localizer.dLocalize("advancement.challenge_crafting_decon_5k.description") - ); - AdvancementSpec deconstruction200 = AdvancementSpec.challenge( - "challenge_crafting_decon_200", - Material.SHEARS, - Localizer.dLocalize("advancement.challenge_crafting_decon_200.title"), - Localizer.dLocalize("advancement.challenge_crafting_decon_200.description") - ).withChild(deconstruction5k); - registerAdvancementSpec(deconstruction200); - registerStatTracker(deconstruction200.statTracker("crafting.deconstruction.items-deconstructed", 200, 300)); - registerStatTracker(deconstruction5k.statTracker("crafting.deconstruction.items-deconstructed", 5000, 1000)); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("crafting.deconstruction.lore1")); - v.addLore(C.GREEN + Localizer.dLocalize("crafting.deconstruction.lore2")); - } - - public ItemStack getDeconstructionOffering(ItemStack forStuff) { - if (forStuff == null) return null; - - int maxPow = 0; - Recipe selectedRecipe = null; - - for (Recipe recipe : Bukkit.getRecipesFor(forStuff)) { - int currentPower = 0; - if (recipe instanceof ShapelessRecipe r) { - currentPower = r.getIngredientList().stream().mapToInt(ItemStack::getAmount).sum(); - } else if (recipe instanceof ShapedRecipe r) { - currentPower = r.getIngredientMap().values().stream().mapToInt(f -> f == null ? 0 : f.getAmount()).sum(); - } - if (currentPower > maxPow) { - selectedRecipe = recipe; - maxPow = currentPower; - } - } - - if (selectedRecipe == null) return null; - - int v = 0; - int outa = 1; - ItemStack sel = null; - - if (selectedRecipe instanceof ShapelessRecipe r) { - for (ItemStack i : r.getIngredientList()) { - int amount = i.getAmount() * forStuff.getAmount(); - if (amount > v) { - v = amount; - sel = i; - outa = r.getResult().getAmount(); - } - } - } else { - ShapedRecipe r = (ShapedRecipe) selectedRecipe; - Map ings = new HashMap<>(); - r.getIngredientMap().values().stream().filter(Objects::nonNull).forEach(i -> ings.merge(i.getType(), i.getAmount(), Integer::sum)); - - for (Map.Entry entry : ings.entrySet()) { - int amount = entry.getValue() * forStuff.getAmount(); - if (amount > v) { - v = amount; - sel = new ItemStack(entry.getKey(), entry.getValue()); - outa = r.getResult().getAmount(); - } - } - } - - if (sel != null && sel.getAmount() * forStuff.getAmount() > 1) { - int a = ((sel.getAmount() * forStuff.getAmount()) / outa) / 2; - if (a <= sel.getMaxStackSize() && getValue(sel) < getValue(forStuff)) { - sel.setAmount(a); - return sel.clone(); - } - } - - return null; - } - - - @EventHandler - public void on(PlayerInteractEvent e) { - Player player = e.getPlayer(); - ItemStack mainHandItem = player.getInventory().getItemInMainHand(); - if (!hasAdaptation(player)) { - return; - } - - if (!player.isSneaking() || mainHandItem.getType() != Material.SHEARS) { - return; - } - - // Perform a ray trace for 6 blocks looking for an item - RayTraceResult rayTrace = player.getWorld().rayTraceEntities(player.getEyeLocation(), player.getLocation().getDirection(), 6, entity -> entity instanceof Item); - if (rayTrace != null && rayTrace.getHitEntity() instanceof Item itemEntity) { - processItemInteraction(player, mainHandItem, itemEntity); - } - } - - private void processItemInteraction(Player player, ItemStack mainHandItem, Item itemEntity) { - ItemStack forStuff = itemEntity.getItemStack(); - ItemStack offering = getDeconstructionOffering(forStuff); - - SoundPlayer spw = SoundPlayer.of(player.getWorld()); - if (offering != null) { - itemEntity.setItemStack(offering); - spw.play(itemEntity.getLocation(), Sound.BLOCK_BASALT_BREAK, 1F, 0.2f); - spw.play(itemEntity.getLocation(), Sound.BLOCK_BEEHIVE_SHEAR, 1F, 0.7f); - xp(player, getValue(offering), "deconstruct"); - getPlayer(player).getData().addStat("crafting.deconstruction.items-deconstructed", 1); - - // Damage the shears - Damageable damageable = (Damageable) mainHandItem.getItemMeta(); - int newDamage = damageable.getDamage() + 8 * forStuff.getAmount(); - if (newDamage >= mainHandItem.getType().getMaxDurability()) { - player.getInventory().setItemInMainHand(null); // Break the shears - } else { - damageable.setDamage(newDamage); - mainHandItem.setItemMeta(damageable); - } - } else { - spw.play(itemEntity.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 1F, 1f); // Burnt torch sound - } - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Deconstruct blocks and items into salvageable base components using shears.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingLeather.java b/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingLeather.java deleted file mode 100644 index 760488716..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingLeather.java +++ /dev/null @@ -1,110 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.crafting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdvancementSpec; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; - -public class CraftingLeather extends SimpleAdaptation { - - public CraftingLeather() { - super("crafting-leather"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("crafting.leather.description")); - setDisplayName(Localizer.dLocalize("crafting.leather.name")); - setIcon(Material.LEATHER); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(17776); - registerRecipe(AdaptRecipe.campfire() - .key("crafting-leather") - .ingredient(Material.ROTTEN_FLESH) - .cookTime(100) - .experience(1) - .result(new ItemStack(Material.LEATHER, 1)) - .build()); - AdvancementSpec leatherCrafted = AdvancementSpec.challenge( - "challenge_crafting_leather_100", - Material.LEATHER, - Localizer.dLocalize("advancement.challenge_crafting_leather_100.title"), - Localizer.dLocalize("advancement.challenge_crafting_leather_100.description") - ); - registerMilestone(leatherCrafted, "crafting.leather.leather-crafted", 100, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("crafting.leather.lore1")); - } - - @EventHandler - public void on(PlayerInteractEvent e) { - if (e.getItem() != null && e.getItem().getType() == Material.ROTTEN_FLESH && e.getClickedBlock() != null && e.getClickedBlock().getType() == Material.CAMPFIRE) { - if (!hasAdaptation(e.getPlayer())) { - e.setCancelled(true); - } else { - getPlayer(e.getPlayer()).getData().addStat("crafting.leather.leather-crafted", 1); - } - } - } - - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Leather from Rotten Flesh on a campfire.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingReconstruction.java b/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingReconstruction.java deleted file mode 100644 index 3e0f2e1d3..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingReconstruction.java +++ /dev/null @@ -1,368 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.crafting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; - - -public class CraftingReconstruction extends SimpleAdaptation { - public CraftingReconstruction() { - super("crafting-reconstruction"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("crafting.reconstruction.description")); - setDisplayName(Localizer.dLocalize("crafting.reconstruction.name")); - setIcon(Material.COAL_ORE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(80248); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-iron-ore") - .ingredient(Material.STONE) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .result(new ItemStack(Material.IRON_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-gold-ore") - .ingredient(Material.STONE) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .result(new ItemStack(Material.GOLD_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-copper-ore") - .ingredient(Material.STONE) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .result(new ItemStack(Material.COPPER_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-lapis-ore") - .ingredient(Material.STONE) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .result(new ItemStack(Material.LAPIS_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-redstone-ore") - .ingredient(Material.STONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .result(new ItemStack(Material.REDSTONE_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-emerald-ore") - .ingredient(Material.STONE) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .result(new ItemStack(Material.EMERALD_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-diamond-ore") - .ingredient(Material.STONE) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .result(new ItemStack(Material.DIAMOND_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-coal-ore") - .ingredient(Material.STONE) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .result(new ItemStack(Material.COAL_ORE)) - .build()); - - // Use Deepslate - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-deepslate-iron-ore") - .ingredient(Material.DEEPSLATE) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .ingredient(Material.IRON_INGOT) - .result(new ItemStack(Material.DEEPSLATE_IRON_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-deepslate-gold-ore") - .ingredient(Material.DEEPSLATE) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .result(new ItemStack(Material.DEEPSLATE_GOLD_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-deepslate-copper-ore") - .ingredient(Material.DEEPSLATE) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .ingredient(Material.COPPER_INGOT) - .result(new ItemStack(Material.DEEPSLATE_COPPER_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-deepslate-lapis-ore") - .ingredient(Material.DEEPSLATE) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .ingredient(Material.LAPIS_LAZULI) - .result(new ItemStack(Material.DEEPSLATE_LAPIS_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-deepslate-redstone-ore") - .ingredient(Material.DEEPSLATE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .ingredient(Material.REDSTONE) - .result(new ItemStack(Material.DEEPSLATE_REDSTONE_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-deepslate-emerald-ore") - .ingredient(Material.DEEPSLATE) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .ingredient(Material.EMERALD) - .result(new ItemStack(Material.DEEPSLATE_EMERALD_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-deepslate-diamond-ore") - .ingredient(Material.DEEPSLATE) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .ingredient(Material.DIAMOND) - .result(new ItemStack(Material.DEEPSLATE_DIAMOND_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-deepslate-coal-ore") - .ingredient(Material.DEEPSLATE) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .ingredient(Material.COAL) - .result(new ItemStack(Material.DEEPSLATE_COAL_ORE)) - .build()); - -// Use Nether Bricks - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-nether-gold-ore") - .ingredient(Material.NETHER_BRICKS) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .ingredient(Material.GOLD_INGOT) - .result(new ItemStack(Material.NETHER_GOLD_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-nether-quartz-ore") - .ingredient(Material.NETHER_BRICKS) - .ingredient(Material.QUARTZ) - .ingredient(Material.QUARTZ) - .ingredient(Material.QUARTZ) - .ingredient(Material.QUARTZ) - .ingredient(Material.QUARTZ) - .ingredient(Material.QUARTZ) - .ingredient(Material.QUARTZ) - .ingredient(Material.QUARTZ) - .result(new ItemStack(Material.NETHER_QUARTZ_ORE)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("reconstruction-ancient-debris") - .ingredient(Material.NETHER_BRICKS) - .ingredient(Material.NETHERITE_SCRAP) - .ingredient(Material.NETHERITE_SCRAP) - .ingredient(Material.NETHERITE_SCRAP) - .ingredient(Material.NETHERITE_SCRAP) - .ingredient(Material.NETHERITE_SCRAP) - .ingredient(Material.NETHERITE_SCRAP) - .ingredient(Material.NETHERITE_SCRAP) - .ingredient(Material.NETHERITE_SCRAP) - .result(new ItemStack(Material.ANCIENT_DEBRIS)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.RAW_IRON) - .key("challenge_crafting_recon_100") - .title(Localizer.dLocalize("advancement.challenge_crafting_recon_100.title")) - .description(Localizer.dLocalize("advancement.challenge_crafting_recon_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_crafting_recon_100", "crafting.reconstruction.ores-reconstructed", 100, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("crafting.reconstruction.lore1")); - v.addLore(C.UNDERLINE + Localizer.dLocalize("crafting.reconstruction.lore2")); - v.addLore(C.YELLOW + Localizer.dLocalize("crafting.reconstruction.lore3")); - v.addLore(C.YELLOW + Localizer.dLocalize("crafting.reconstruction.lore4")); - } - - @EventHandler - public void on(PlayerInteractEvent e) { - - } - - @EventHandler - public void on(CraftItemEvent e) { - if (e.isCancelled()) return; - Player p = (Player) e.getWhoClicked(); - if (!hasAdaptation(p)) return; - if (e.getRecipe() != null && (e.getRecipe().getResult().getType().name().contains("ORE") || e.getRecipe().getResult().getType() == Material.ANCIENT_DEBRIS)) { - getPlayer(p).getData().addStat("crafting.reconstruction.ores-reconstructed", 1); - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Recraft ores from their base smelted components.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingSkulls.java b/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingSkulls.java deleted file mode 100644 index eedb0f4bb..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingSkulls.java +++ /dev/null @@ -1,174 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.crafting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdvancementSpec; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public class CraftingSkulls extends SimpleAdaptation { - - public CraftingSkulls() { - super("crafting-skulls"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("crafting.skulls.description")); - setDisplayName(Localizer.dLocalize("crafting.skulls.name")); - setIcon(Material.SKELETON_SKULL); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(17776); - registerRecipe(AdaptRecipe.shaped() - .key("crafting-skeletonskull") - .ingredient(new MaterialChar('I', Material.BONE)) - .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) - .shapes(List.of( - "III", - "IXI", - "III")) - .result(new ItemStack(Material.SKELETON_SKULL, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("crafting-witherskeletonskull") - .ingredient(new MaterialChar('I', Material.NETHER_BRICK)) - .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) - .shapes(List.of( - "III", - "IXI", - "III")) - .result(new ItemStack(Material.WITHER_SKELETON_SKULL, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("crafting-zombieskull") - .ingredient(new MaterialChar('I', Material.ROTTEN_FLESH)) - .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) - .shapes(List.of( - "III", - "IXI", - "III")) - .result(new ItemStack(Material.ZOMBIE_HEAD, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("crafting-creeperhead") - .ingredient(new MaterialChar('I', Material.GUNPOWDER)) - .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) - .shapes(List.of( - "III", - "IXI", - "III")) - .result(new ItemStack(Material.CREEPER_HEAD, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("crafting-dragonhead") - .ingredient(new MaterialChar('I', Material.DRAGON_BREATH)) - .ingredient(new MaterialChar('X', Material.BONE_BLOCK)) - .shapes(List.of( - "III", - "IXI", - "III")) - .result(new ItemStack(Material.DRAGON_HEAD, 1)) - .build()); - AdvancementSpec skulls100 = AdvancementSpec.challenge( - "challenge_crafting_skulls_100", - Material.WITHER_SKELETON_SKULL, - Localizer.dLocalize("advancement.challenge_crafting_skulls_100.title"), - Localizer.dLocalize("advancement.challenge_crafting_skulls_100.description") - ); - AdvancementSpec skulls10 = AdvancementSpec.challenge( - "challenge_crafting_skulls_10", - Material.SKELETON_SKULL, - Localizer.dLocalize("advancement.challenge_crafting_skulls_10.title"), - Localizer.dLocalize("advancement.challenge_crafting_skulls_10.description") - ).withChild(skulls100); - registerAdvancementSpec(skulls10); - registerStatTracker(skulls10.statTracker("crafting.skulls.skulls-crafted", 10, 300)); - registerStatTracker(skulls100.statTracker("crafting.skulls.skulls-crafted", 100, 1000)); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore1")); - v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore2")); - v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore3")); - v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore4")); - v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore5")); - v.addLore(C.YELLOW + "- " + C.GRAY + Localizer.dLocalize("crafting.skulls.lore6")); - } - - - @EventHandler - public void on(CraftItemEvent e) { - if (e.isCancelled()) return; - Player p = (Player) e.getWhoClicked(); - if (!hasAdaptation(p)) return; - if (e.getRecipe() != null) { - Material result = e.getRecipe().getResult().getType(); - if (result == Material.SKELETON_SKULL || result == Material.WITHER_SKELETON_SKULL - || result == Material.ZOMBIE_HEAD || result == Material.CREEPER_HEAD - || result == Material.DRAGON_HEAD) { - getPlayer(p).getData().addStat("crafting.skulls.skulls-crafted", 1); - } - } - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Mob Skulls using materials surrounding a Bone Block.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingStations.java b/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingStations.java deleted file mode 100644 index 857549eea..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingStations.java +++ /dev/null @@ -1,178 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.crafting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdvancementSpec; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.block.Action; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; - - -public class CraftingStations extends SimpleAdaptation { - public CraftingStations() { - super("crafting-stations"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("crafting.stations.description")); - setDisplayName(Localizer.dLocalize("crafting.stations.name")); - setIcon(Material.CRAFTING_TABLE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(9248); - AdvancementSpec stations5k = AdvancementSpec.challenge( - "challenge_crafting_stations_5k", - Material.SMITHING_TABLE, - Localizer.dLocalize("advancement.challenge_crafting_stations_5k.title"), - Localizer.dLocalize("advancement.challenge_crafting_stations_5k.description") - ); - AdvancementSpec stations200 = AdvancementSpec.challenge( - "challenge_crafting_stations_200", - Material.CRAFTING_TABLE, - Localizer.dLocalize("advancement.challenge_crafting_stations_200.title"), - Localizer.dLocalize("advancement.challenge_crafting_stations_200.description") - ).withChild(stations5k); - registerAdvancementSpec(stations200); - registerStatTracker(stations200.statTracker("crafting.stations.portable-opens", 200, 300)); - registerStatTracker(stations5k.statTracker("crafting.stations.portable-opens", 5000, 1000)); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.RED + Localizer.dLocalize("crafting.stations.lore2")); - v.addLore(C.GRAY + Localizer.dLocalize("crafting.stations.lore3")); - } - - @EventHandler - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - - if (p.hasCooldown(hand.getType())) { - e.setCancelled(true); - return; - } - - if ((e.getAction().equals(Action.RIGHT_CLICK_AIR) || e.getAction().equals(Action.LEFT_CLICK_AIR) || e.getAction().equals(Action.LEFT_CLICK_BLOCK))) { - - SoundPlayer sp = SoundPlayer.of(p); - switch (hand.getType()) { - case CRAFTING_TABLE -> { - p.setCooldown(hand.getType(), 1000); - sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); - p.openWorkbench(null, true); - getPlayer(p).getData().addStat("crafting.stations.portable-opens", 1); - } - case GRINDSTONE -> { - p.setCooldown(hand.getType(), 1000); - sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); - Inventory inv = Bukkit.createInventory(p, InventoryType.GRINDSTONE); - p.openInventory(inv); - getPlayer(p).getData().addStat("crafting.stations.portable-opens", 1); - } - case ANVIL -> { - p.setCooldown(hand.getType(), 1000); - sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); - Inventory inv = Bukkit.createInventory(p, InventoryType.ANVIL); - p.openInventory(inv); - getPlayer(p).getData().addStat("crafting.stations.portable-opens", 1); - } - case STONECUTTER -> { - p.setCooldown(hand.getType(), 1000); - sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); - Inventory inv = Bukkit.createInventory(p, InventoryType.STONECUTTER); - p.openInventory(inv); - getPlayer(p).getData().addStat("crafting.stations.portable-opens", 1); - } - case CARTOGRAPHY_TABLE -> { - p.setCooldown(hand.getType(), 1000); - sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); - Inventory inv = Bukkit.createInventory(p, InventoryType.CARTOGRAPHY); - p.openInventory(inv); - getPlayer(p).getData().addStat("crafting.stations.portable-opens", 1); - } - case LOOM -> { - p.setCooldown(hand.getType(), 1000); - sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); - Inventory inv = Bukkit.createInventory(p, InventoryType.LOOM); - p.openInventory(inv); - getPlayer(p).getData().addStat("crafting.stations.portable-opens", 1); - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Use crafting tables, anvils, and other stations in the palm of your hand.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Crafting Stations adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public int cooldown = 125; - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingXP.java b/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingXP.java deleted file mode 100644 index ce44d166a..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/crafting/CraftingXP.java +++ /dev/null @@ -1,137 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.crafting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdvancementSpec; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -import java.util.HashMap; -import java.util.Map; - - -public class CraftingXP extends SimpleAdaptation { - private final Map cooldown = new HashMap<>(); - - - public CraftingXP() { - super("crafting-xp"); - registerConfiguration(CraftingXP.Config.class); - setDisplayName(Localizer.dLocalize("crafting.xp.name")); - setDescription(Localizer.dLocalize("crafting.xp.description")); - setIcon(Material.ENCHANTED_BOOK); - setInterval(5580); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - AdvancementSpec xp25k = AdvancementSpec.challenge( - "challenge_crafting_xp_25k", - Material.EXPERIENCE_BOTTLE, - Localizer.dLocalize("advancement.challenge_crafting_xp_25k.title"), - Localizer.dLocalize("advancement.challenge_crafting_xp_25k.description") - ); - AdvancementSpec xp1k = AdvancementSpec.challenge( - "challenge_crafting_xp_1k", - Material.CRAFTING_TABLE, - Localizer.dLocalize("advancement.challenge_crafting_xp_1k.title"), - Localizer.dLocalize("advancement.challenge_crafting_xp_1k.description") - ).withChild(xp25k); - registerAdvancementSpec(xp1k); - registerStatTracker(xp1k.statTracker("crafting.xp.items-crafted", 1000, 300)); - registerStatTracker(xp25k.statTracker("crafting.xp.items-crafted", 25000, 1500)); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("crafting.xp.lore1")); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - cooldown.remove(p); - } - - - @EventHandler(priority = EventPriority.LOW) - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = (Player) e.getWhoClicked(); - if (e.getInventory().getResult() != null && !e.isCancelled() && hasAdaptation(p) && e.getInventory().getResult().getAmount() > 0) { - if (e.getInventory().getResult() != null && e.getCursor() != null && e.getCursor().getAmount() < 64) { - if (p.getInventory().addItem(e.getCurrentItem()).isEmpty()) { - p.getInventory().removeItem(e.getCurrentItem()); - if (cooldown.containsKey(p) && cooldown.get(p) + 20000 < System.currentTimeMillis()) { - cooldown.remove(p); - } else if (cooldown.containsKey(p) && cooldown.get(p) + 20000 > System.currentTimeMillis()) { - return; - } - cooldown.put(p, System.currentTimeMillis()); - p.getWorld().spawn(p.getLocation(), org.bukkit.entity.ExperienceOrb.class).setExperience(getLevel(p) * 2); - getPlayer(p).getData().addStat("crafting.xp.items-crafted", 1); - } - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain passive XP when crafting items.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryArchaeologist.java b/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryArchaeologist.java deleted file mode 100644 index 6287b13d8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryArchaeologist.java +++ /dev/null @@ -1,429 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.discovery; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Event; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.plugin.EventExecutor; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicBoolean; - -public class DiscoveryArchaeologist extends SimpleAdaptation { - private static final String BLOCK_BRUSH_EVENT_CLASS = "org.bukkit.event.block.BlockBrushEvent"; - private static final long BRUSH_FALLBACK_WINDOW_MILLIS = 25000L; - private final Map cooldowns = new HashMap<>(); - private final Map pendingBrushes = new HashMap<>(); - private final AtomicBoolean brushEventFailureWarned = new AtomicBoolean(false); - private final BrushEventBridge brushEventBridge; - - public DiscoveryArchaeologist() { - super("discovery-archaeologist"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("discovery.archaeologist.description")); - setDisplayName(Localizer.dLocalize("discovery.archaeologist.name")); - setIcon(Material.BRUSH); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2300); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BRUSH) - .key("challenge_discovery_archaeologist_50") - .title(Localizer.dLocalize("advancement.challenge_discovery_archaeologist_50.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_archaeologist_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DECORATED_POT) - .key("challenge_discovery_archaeologist_500") - .title(Localizer.dLocalize("advancement.challenge_discovery_archaeologist_500.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_archaeologist_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discovery_archaeologist_50", "discovery.archaeologist.bonus-finds", 50, 300); - registerMilestone("challenge_discovery_archaeologist_500", "discovery.archaeologist.bonus-finds", 500, 1000); - brushEventBridge = BrushEventBridge.create(); - registerBrushEventBridge(brushEventBridge); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getBonusRollChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("discovery.archaeologist.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getRareRewardChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("discovery.archaeologist.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("discovery.archaeologist.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - UUID id = e.getPlayer().getUniqueId(); - cooldowns.remove(id); - pendingBrushes.remove(id); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerInteractEvent e) { - if (e.getAction() != Action.RIGHT_CLICK_BLOCK) { - return; - } - - ItemStack hand = e.getItem(); - if (hand == null || hand.getType() != Material.BRUSH) { - return; - } - - Block block = e.getClickedBlock(); - if (block == null || !isSuspiciousBlock(block.getType())) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !canBlockBreak(p, block.getLocation())) { - return; - } - - pendingBrushes.put(p.getUniqueId(), PendingBrush.from(block, System.currentTimeMillis() + BRUSH_FALLBACK_WINDOW_MILLIS)); - } - - private void registerBrushEventBridge(BrushEventBridge bridge) { - if (bridge == null) { - Adapt.verbose("BlockBrushEvent not available; discovery-archaeologist will use interaction fallback tracking."); - return; - } - - EventExecutor executor = (listener, event) -> onBrush(event, bridge); - Bukkit.getPluginManager().registerEvent(bridge.eventClass, this, EventPriority.HIGHEST, executor, Adapt.instance, true); - } - - private void onBrush(Event event, BrushEventBridge bridge) { - try { - Player p = bridge.player(event); - Block block = bridge.block(event); - Material originalType = block == null ? null : block.getType(); - if (p != null) { - PendingBrush pending = pendingBrushes.get(p.getUniqueId()); - if (pending != null && isSuspiciousBlock(pending.originalType)) { - originalType = pending.originalType; - } - } - - Material newStateType = bridge.newStateType(event); - handleBrush(p, block, originalType, newStateType); - if (p != null && newStateType != null && !isSuspiciousBlock(newStateType)) { - pendingBrushes.remove(p.getUniqueId()); - } - } catch (Throwable t) { - if (brushEventFailureWarned.compareAndSet(false, true)) { - Adapt.warn("DiscoveryArchaeologist brush event bridge failed once (" + t.getClass().getSimpleName() + ": " + t.getMessage() + ")."); - } - } - } - - private void handleBrush(Player p, Block block, Material originalType, Material newStateType) { - if (p == null || block == null) { - return; - } - - if (!hasAdaptation(p)) { - return; - } - - if (!isSuspiciousBlock(originalType)) { - return; - } - - // Only award when brushing actually completes and the suspicious block resolves. - if (newStateType == null || isSuspiciousBlock(newStateType)) { - return; - } - - if (!canBlockBreak(p, block.getLocation())) { - return; - } - - int level = getLevel(p); - long now = System.currentTimeMillis(); - long nextReady = cooldowns.getOrDefault(p.getUniqueId(), 0L); - if (now < nextReady) { - return; - } - - cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); - if (ThreadLocalRandom.current().nextDouble() > getBonusRollChance(level)) { - return; - } - - ItemStack reward = rollReward(level); - Map overflow = p.getInventory().addItem(reward); - overflow.values().forEach(item -> p.getWorld().dropItemNaturally(p.getLocation(), item)); - - if (areParticlesEnabled()) { - block.getWorld().spawnParticle(Particle.ENCHANT, block.getLocation().add(0.5, 0.65, 0.5), 18, 0.25, 0.2, 0.25, 0.2); - } - if (areParticlesEnabled()) { - block.getWorld().spawnParticle(Particle.CRIT, block.getLocation().add(0.5, 0.65, 0.5), 12, 0.22, 0.22, 0.22, 0.02); - } - SoundPlayer sp = SoundPlayer.of(block.getWorld()); - sp.play(block.getLocation().add(0.5, 0.5, 0.5), Sound.ITEM_BRUSH_BRUSHING_SAND_COMPLETE, 1f, 1.15f); - sp.play(block.getLocation().add(0.5, 0.5, 0.5), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.7f, 1.55f); - xp(p, getConfig().xpPerReward + (getValue(reward.getType()) * getConfig().rewardValueXpMultiplier)); - getPlayer(p).getData().addStat("discovery.archaeologist.bonus-finds", 1); - } - - private static final class BrushEventBridge { - private final Class eventClass; - private final Method getPlayer; - private final Method getBlock; - private final Method getNewState; - private final Method getBlockStateType; - - private BrushEventBridge(Class eventClass, Method getPlayer, Method getBlock, Method getNewState, Method getBlockStateType) { - this.eventClass = eventClass; - this.getPlayer = getPlayer; - this.getBlock = getBlock; - this.getNewState = getNewState; - this.getBlockStateType = getBlockStateType; - } - - private static BrushEventBridge create() { - try { - Class eventType = Class.forName(BLOCK_BRUSH_EVENT_CLASS); - if (!Event.class.isAssignableFrom(eventType)) { - return null; - } - - Method getPlayer = eventType.getMethod("getPlayer"); - Method getBlock = eventType.getMethod("getBlock"); - Method getNewState = eventType.getMethod("getNewState"); - Class blockStateType = Class.forName("org.bukkit.block.BlockState"); - Method getBlockStateType = blockStateType.getMethod("getType"); - - @SuppressWarnings("unchecked") - Class typedEvent = (Class) eventType; - return new BrushEventBridge(typedEvent, getPlayer, getBlock, getNewState, getBlockStateType); - } catch (Throwable ignored) { - return null; - } - } - - private Player player(Event event) throws ReflectiveOperationException { - Object value = getPlayer.invoke(event); - return value instanceof Player p ? p : null; - } - - private Block block(Event event) throws ReflectiveOperationException { - Object value = getBlock.invoke(event); - return value instanceof Block b ? b : null; - } - - private Material newStateType(Event event) throws ReflectiveOperationException { - Object newState = getNewState.invoke(event); - if (newState == null) { - return null; - } - - Object material = getBlockStateType.invoke(newState); - return material instanceof Material m ? m : null; - } - } - - private ItemStack rollReward(int level) { - ThreadLocalRandom random = ThreadLocalRandom.current(); - if (random.nextDouble() <= getRareRewardChance(level)) { - return switch (random.nextInt(4)) { - case 0 -> new ItemStack(Material.DIAMOND, 1); - case 1 -> new ItemStack(Material.EMERALD, 1); - case 2 -> new ItemStack(Material.GOLD_INGOT, 1 + random.nextInt(2)); - default -> new ItemStack(Material.AMETHYST_SHARD, 2 + random.nextInt(3)); - }; - } - - return switch (random.nextInt(6)) { - case 0 -> new ItemStack(Material.BRICK, 1 + random.nextInt(2)); - case 1 -> new ItemStack(Material.CLAY_BALL, 2 + random.nextInt(3)); - case 2 -> new ItemStack(Material.BONE, 1 + random.nextInt(2)); - case 3 -> new ItemStack(Material.FLINT, 1 + random.nextInt(2)); - case 4 -> new ItemStack(Material.STRING, 1 + random.nextInt(2)); - default -> new ItemStack(Material.COAL, 1 + random.nextInt(2)); - }; - } - - private boolean isSuspiciousBlock(Material type) { - return type == Material.SUSPICIOUS_SAND || type == Material.SUSPICIOUS_GRAVEL; - } - - private double getBonusRollChance(int level) { - return Math.min(getConfig().maxBonusRollChance, - getConfig().bonusRollChanceBase + (getLevelPercent(level) * getConfig().bonusRollChanceFactor)); - } - - private double getRareRewardChance(int level) { - return Math.min(getConfig().maxRareRewardChance, - getConfig().rareRewardChanceBase + (getLevelPercent(level) * getConfig().rareRewardChanceFactor)); - } - - private long getCooldownMillis(int level) { - return Math.max(250L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - @Override - public void onTick() { - if (pendingBrushes.isEmpty()) { - return; - } - - long now = System.currentTimeMillis(); - Iterator> iterator = pendingBrushes.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - PendingBrush pending = entry.getValue(); - if (pending.expiresAt <= now) { - iterator.remove(); - continue; - } - - Player p = Bukkit.getPlayer(entry.getKey()); - if (p == null || !p.isOnline() || !hasAdaptation(p)) { - iterator.remove(); - continue; - } - - Block current = pending.resolveBlock(); - if (current == null) { - iterator.remove(); - continue; - } - - Material currentType = current.getType(); - if (isSuspiciousBlock(currentType)) { - continue; - } - - handleBrush(p, current, pending.originalType, currentType); - iterator.remove(); - } - } - - private static final class PendingBrush { - private final UUID worldId; - private final int x; - private final int y; - private final int z; - private final Material originalType; - private final long expiresAt; - - private PendingBrush(UUID worldId, int x, int y, int z, Material originalType, long expiresAt) { - this.worldId = worldId; - this.x = x; - this.y = y; - this.z = z; - this.originalType = originalType; - this.expiresAt = expiresAt; - } - - private static PendingBrush from(Block block, long expiresAt) { - return new PendingBrush(block.getWorld().getUID(), block.getX(), block.getY(), block.getZ(), block.getType(), expiresAt); - } - - private Block resolveBlock() { - World world = Bukkit.getWorld(worldId); - return world == null ? null : world.getBlockAt(x, y, z); - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Brushing suspicious blocks has a chance to grant bonus archaeology loot.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Roll Chance Base for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusRollChanceBase = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Roll Chance Factor for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusRollChanceFactor = 0.43; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Bonus Roll Chance for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxBonusRollChance = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Rare Reward Chance Base for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rareRewardChanceBase = 0.04; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Rare Reward Chance Factor for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rareRewardChanceFactor = 0.24; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Rare Reward Chance for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxRareRewardChance = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisBase = 1600; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisFactor = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Reward for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerReward = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reward Value Xp Multiplier for the Discovery Archaeologist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rewardValueXpMultiplier = 0.45; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryArmor.java b/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryArmor.java deleted file mode 100644 index 6d809cdd9..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryArmor.java +++ /dev/null @@ -1,211 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.discovery; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.version.IAttribute; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.reflect.registries.Attributes; -import com.volmit.adapt.util.reflect.registries.Particles; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.attribute.AttributeModifier; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.util.Vector; - -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -public class DiscoveryArmor extends SimpleAdaptation { - private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-discovery-armor".getBytes()); - private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString( "adapt:discovery-armor"); - private static final long UPDATE_COOLDOWN = TimeUnit.SECONDS.toMillis(3); - private static final Sphere SPHERE = new Sphere(5); - - private final KMap playerData = new KMap<>(); - - public DiscoveryArmor() { - super("discovery-world-armor"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("discovery.armor.description")); - setDisplayName(Localizer.dLocalize("discovery.armor.name")); - setIcon(Material.TURTLE_HELMET); - setInterval(305); - setBaseCost(getConfig().baseCost); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_CHESTPLATE) - .key("challenge_discovery_armor_1hr") - .title(Localizer.dLocalize("advancement.challenge_discovery_armor_1hr.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_armor_1hr.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_CHESTPLATE) - .key("challenge_discovery_armor_24hr") - .title(Localizer.dLocalize("advancement.challenge_discovery_armor_24hr.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_armor_24hr.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discovery_armor_1hr", "discovery.armor.ticks-with-bonus", 72000, 400); - registerMilestone("challenge_discovery_armor_24hr", "discovery.armor.ticks-with-bonus", 1728000, 2000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("discovery.armor.lore1") + C.GRAY + ", " + Localizer.dLocalize("discovery.armor.lore2")); - v.addLore(C.YELLOW + "~ " + Localizer.dLocalize("discovery.armor.lore3") + C.BLUE + " +" + level * 0.25); - } - - public double getArmorPoints(Material m) { - return Math.log(Math.min(2000, m.getBlastResistance() * m.getBlastResistance())) + Math.log((m.getHardness() < 0 ? 50 : Math.min(50, m.getHardness() + 25)) * 0.33); - } - - public double getArmor(Location l, int level) { - Block center = l.getBlock(); - double armorValue = 0.0; - double count = 0; - - var sphere = SPHERE.clone(); - - while (sphere.hasNext()) { - var r = sphere.next(); - Block b = center.getRelative(r.getX(), r.getY(), r.getZ()); - if (b.isEmpty() || b.isLiquid()) - continue; - - count++; - double a = getArmorPoints(b.getType()); - if (Double.isNaN(a) || a < 0) { - a = 0; - } - armorValue += a; - - if (a > 2 && M.r(0.005 * a)) { - Vector v = VectorMath.directionNoNormal(l, b.getLocation().add(0.5, 0.5, 0.5)); - if (areParticlesEnabled()) { - l.getWorld().spawnParticle(Particles.ENCHANTMENT_TABLE, l.clone().add(0, 1, 0), 0, v.getX(), v.getY(), v.getZ()); - } - } - } - - return Math.min((armorValue / count) * (level / 2D) * 0.65, 10); - } - - - private double getRadius(double factor) { - return factor * getConfig().radiusFactor; - } - - private double getStrength(double factor) { - return Math.pow(factor, getConfig().strengthExponent); - } - - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (p == null || !p.isOnline()) continue; - - long now = M.ms(); - var nextUpdate = playerData.getOrDefault(p.getUniqueId(), now); - if (nextUpdate > now) continue; - playerData.put(p.getUniqueId(), now + UPDATE_COOLDOWN); - - var attribute = Version.get().getAttribute(p, Attributes.GENERIC_ARMOR); - if (attribute == null) continue; - - if (!hasAdaptation(p)) { - attribute.removeModifier(MODIFIER, MODIFIER_KEY); - } else { - double oldArmor = attribute.getModifier(MODIFIER, MODIFIER_KEY) - .stream() - .mapToDouble(IAttribute.Modifier::getAmount) - .max() - .orElse(0); - - double armor = getArmor(p.getLocation(), getLevel(p)); - armor = Double.isNaN(armor) ? 0 : armor; - - double lArmor = M.lerp(oldArmor, armor, 0.3); - lArmor = Double.isNaN(lArmor) ? 0 : lArmor; - attribute.setModifier(MODIFIER, MODIFIER_KEY, lArmor, AttributeModifier.Operation.ADD_NUMBER); - if (lArmor > 0) { - adaptPlayer.getData().addStat("discovery.armor.ticks-with-bonus", 1); - } - } - } - } - - @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) { - playerData.remove(event.getPlayer().getUniqueId()); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain passive armor based on nearby block hardness.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Discovery Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public int radiusFactor = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Strength Exponent for the Discovery Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double strengthExponent = 1.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Discovery Armor adaptation.", impact = "True enables this behavior and false disables it.") - public boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryBetterMending.java b/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryBetterMending.java deleted file mode 100644 index 2d51c01b8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryBetterMending.java +++ /dev/null @@ -1,218 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.discovery; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; - -public class DiscoveryBetterMending extends SimpleAdaptation { - public DiscoveryBetterMending() { - super("discovery-better-mending"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("discovery.better_mending.description")); - setDisplayName(Localizer.dLocalize("discovery.better_mending.name")); - setIcon(Material.PHANTOM_MEMBRANE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ANVIL) - .key("challenge_discovery_mending_10k") - .title(Localizer.dLocalize("advancement.challenge_discovery_mending_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_mending_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENCHANTED_GOLDEN_APPLE) - .key("challenge_discovery_mending_100k") - .title(Localizer.dLocalize("advancement.challenge_discovery_mending_100k.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_mending_100k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discovery_mending_10k", "discovery.better-mending.durability-restored", 10000, 400); - registerMilestone("challenge_discovery_mending_100k", "discovery.better-mending.durability-restored", 100000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRepairPerXp(level)) + C.GRAY + " " + Localizer.dLocalize("discovery.better_mending.lore1")); - v.addLore(C.GREEN + "+ " + getMaxXpSpend(level) + C.GRAY + " " + Localizer.dLocalize("discovery.better_mending.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("discovery.better_mending.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - if (e.isCancelled() || e.getHand() != EquipmentSlot.HAND) { - return; - } - - Action action = e.getAction(); - if (action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking()) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (!canMend(hand) || p.hasCooldown(hand.getType())) { - return; - } - - Damageable damageable = (Damageable) hand.getItemMeta(); - if (damageable == null || damageable.getDamage() <= 0) { - return; - } - - int level = getLevel(p); - int availableXp = getTotalExp(p); - if (availableXp <= 0) { - return; - } - - double repairPerXp = getRepairPerXp(level); - int maxXpSpend = Math.min(getMaxXpSpend(level), availableXp); - int currentDamage = damageable.getDamage(); - int xpNeeded = (int) Math.ceil(currentDamage / repairPerXp); - int xpSpent = Math.min(maxXpSpend, xpNeeded); - if (xpSpent <= 0) { - return; - } - - int repaired = Math.max(1, (int) Math.round(xpSpent * repairPerXp)); - int newDamage = Math.max(0, currentDamage - repaired); - - takeExp(p, xpSpent, true); - damageable.setDamage(newDamage); - hand.setItemMeta(damageable); - p.getInventory().setItemInMainHand(hand); - p.setCooldown(hand.getType(), getCooldownTicks(level)); - e.setCancelled(true); - - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8f, 1.45f); - if (newDamage <= 0) { - sp.play(p.getLocation(), Sound.BLOCK_ANVIL_USE, 0.5f, 1.65f); - } - - xp(p, Math.max(1D, (currentDamage - newDamage) * getConfig().skillXpPerDurability)); - getPlayer(p).getData().addStat("discovery.better-mending.durability-restored", repaired); - } - - private boolean canMend(ItemStack hand) { - if (!isItem(hand) || hand.getType().getMaxDurability() <= 0) { - return false; - } - - if (!hand.containsEnchantment(Enchantment.MENDING)) { - return false; - } - - if (!(hand.getItemMeta() instanceof Damageable damageable)) { - return false; - } - - return damageable.getDamage() > 0; - } - - private double getRepairPerXp(int level) { - return Math.max(0.1, getConfig().repairPerXpBase + (getLevelPercent(level) * getConfig().repairPerXpFactor)); - } - - private int getMaxXpSpend(int level) { - return Math.max(1, (int) Math.round(getConfig().maxXpSpendBase + (getLevelPercent(level) * getConfig().maxXpSpendFactor))); - } - - private int getCooldownTicks(int level) { - return Math.max(6, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksReduction))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-left-click to spend XP and directly mend the Mending item in your hand.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Repair Per Xp Base for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double repairPerXpBase = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Repair Per Xp Factor for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double repairPerXpFactor = 4.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Xp Spend Base for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxXpSpendBase = 14.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Xp Spend Factor for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxXpSpendFactor = 130.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 38.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Reduction for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksReduction = 26.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Skill Xp Per Durability for the Discovery Better Mending adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double skillXpPerDurability = 0.35; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryCartographerPulse.java b/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryCartographerPulse.java deleted file mode 100644 index 39386b57d..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryCartographerPulse.java +++ /dev/null @@ -1,275 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.discovery; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class DiscoveryCartographerPulse extends SimpleAdaptation { - private final Map cooldowns = new HashMap<>(); - - public DiscoveryCartographerPulse() { - super("discovery-cartographer-pulse"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("discovery.cartographer_pulse.description")); - setDisplayName(Localizer.dLocalize("discovery.cartographer_pulse.name")); - setIcon(Material.COMPASS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.COMPASS) - .key("challenge_discovery_cartographer_100") - .title(Localizer.dLocalize("advancement.challenge_discovery_cartographer_100.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_cartographer_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.FILLED_MAP) - .key("challenge_discovery_cartographer_1k") - .title(Localizer.dLocalize("advancement.challenge_discovery_cartographer_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_cartographer_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discovery_cartographer_100", "discovery.cartographer-pulse.pulses", 100, 300); - registerMilestone("challenge_discovery_cartographer_1k", "discovery.cartographer-pulse.pulses", 1000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getSearchRange(level)) + C.GRAY + " " + Localizer.dLocalize("discovery.cartographer_pulse.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("discovery.cartographer_pulse.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerInteractEvent e) { - if (e.getHand() != EquipmentSlot.HAND) { - return; - } - - Action action = e.getAction(); - if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking() || p.getInventory().getItemInMainHand().getType() != Material.COMPASS) { - return; - } - - int level = getLevel(p); - long now = System.currentTimeMillis(); - if (now < cooldowns.getOrDefault(p.getUniqueId(), 0L)) { - return; - } - - Location target = locateNearestStructureFallback(p.getWorld(), p.getLocation(), getSearchRange(level)); - if (target == null) { - target = p.getWorld().getSpawnLocation(); - } - - p.setCompassTarget(target); - p.sendMessage(C.AQUA + "Compass pulse: " + C.WHITE + Form.f(target.getBlockX()) + ", " + Form.f(target.getBlockZ())); - cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ITEM_LODESTONE_COMPASS_LOCK, 0.8f, 1.3f); - xp(p, getConfig().xpPerPulse); - getPlayer(p).getData().addStat("discovery.cartographer-pulse.pulses", 1); - } - - private Location locateNearestStructureFallback(World world, Location from, int range) { - try { - for (Method m : world.getClass().getMethods()) { - if (!m.getName().equals("locateNearestStructure")) { - continue; - } - - Class[] p = m.getParameterTypes(); - if (p.length < 4 || p[0] != Location.class || p[2] != int.class || p[3] != boolean.class) { - continue; - } - - Object structure = resolvePreferredStructureType(p[1]); - if (structure == null) { - continue; - } - - Object out; - if (p.length == 4) { - out = m.invoke(world, from, structure, range, false); - } else if (p.length == 5 && p[4] == boolean.class) { - out = m.invoke(world, from, structure, range, false, false); - } else { - continue; - } - - Location location = extractLocation(out); - if (location != null) { - return location; - } - } - } catch (Throwable ignored) { - } - - return null; - } - - private Object resolvePreferredStructureType(Class structureTypeClass) { - String[] preferred = {"VILLAGE", "STRONGHOLD", "RUINED_PORTAL", "MINESHAFT", "SHIPWRECK", "TRAIL_RUINS"}; - - if (structureTypeClass.isEnum()) { - Object[] constants = structureTypeClass.getEnumConstants(); - if (constants == null || constants.length == 0) { - return null; - } - - for (String name : preferred) { - Object c = Arrays.stream(constants).filter(i -> ((Enum) i).name().equals(name)).findFirst().orElse(null); - if (c != null) { - return c; - } - } - - return constants[0]; - } - - for (String name : preferred) { - try { - Field f = structureTypeClass.getField(name); - Object value = f.get(null); - if (value != null) { - return value; - } - } catch (Throwable ignored) { - } - } - - try { - Method values = structureTypeClass.getMethod("values"); - Object out = values.invoke(null); - if (out instanceof Object[] a && a.length > 0) { - return a[0]; - } - } catch (Throwable ignored) { - } - - return null; - } - - private Location extractLocation(Object out) { - if (out instanceof Location location) { - return location; - } - - if (out == null) { - return null; - } - - try { - Method getter = out.getClass().getMethod("getLocation"); - Object loc = getter.invoke(out); - if (loc instanceof Location location) { - return location; - } - } catch (Throwable ignored) { - } - - return null; - } - - private int getSearchRange(int level) { - return Math.max(128, (int) Math.round(getConfig().searchRangeBase + (getLevelPercent(level) * getConfig().searchRangeFactor))); - } - - private long getCooldownMillis(int level) { - return Math.max(1500L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-right-click with a compass to pulse toward a nearby structure target.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Search Range Base for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double searchRangeBase = 640; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Search Range Factor for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double searchRangeFactor = 768; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisBase = 26000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisFactor = 14000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Pulse for the Discovery Cartographer Pulse adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerPulse = 25; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryUnity.java b/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryUnity.java deleted file mode 100644 index 143e77bdf..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryUnity.java +++ /dev/null @@ -1,142 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.discovery; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerExpChangeEvent; - -import java.util.List; - -import static xyz.xenondevs.particle.utils.MathUtils.RANDOM; - -public class DiscoveryUnity extends SimpleAdaptation { - public DiscoveryUnity() { - super("discovery-unity"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("discovery.unity.description")); - setDisplayName(Localizer.dLocalize("discovery.unity.name")); - setIcon(Material.END_CRYSTAL); - setBaseCost(getConfig().baseCost); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInterval(666); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.EXPERIENCE_BOTTLE) - .key("challenge_discovery_unity_5k") - .title(Localizer.dLocalize("advancement.challenge_discovery_unity_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_unity_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENCHANTING_TABLE) - .key("challenge_discovery_unity_50k") - .title(Localizer.dLocalize("advancement.challenge_discovery_unity_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_unity_50k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discovery_unity_5k", "discovery.unity.orbs-distributed", 5000, 400); - registerMilestone("challenge_discovery_unity_50k", "discovery.unity.orbs-distributed", 50000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getXPGained(getLevelPercent(level), 1), 0) + " " + Localizer.dLocalize("discovery.unity.lore1") + C.GRAY + " " + Localizer.dLocalize("discovery.unity.lore2")); - } - - //Give random XP to the player when they gain XP! - @EventHandler(priority = EventPriority.LOW) - public void on(PlayerExpChangeEvent e) { - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - AdaptPlayer ap = getPlayer(p); - if (hasAdaptation(p) && e.getAmount() > 0) { - xp(p, 5); - sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1.9f); - //get a random skill that they have unlocked already - List skills = ap.getData().getSkillLines().sortV(); - if (skills.size() > 0) { - PlayerSkillLine skill = skills.get(RANDOM.nextInt(skills.size())); - //give them a random amount of XP in that skill - skill.giveXPFresh(Adapt.instance.getAdaptServer().getPlayer(p).getNot(), getXPGained(getLevelPercent(getLevel(p)), RANDOM.nextInt(3) + 1)); - getPlayer(p).getData().addStat("discovery.unity.orbs-distributed", 1); - } - - } - } - - private double getXPGained(double factor, int amount) { - return amount * getConfig().xpGainedMultiplier * factor; - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Collecting Experience Orbs adds XP to random skills.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Gained Multiplier for the Discovery Unity adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpGainedMultiplier = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Boost Multiplier for the Discovery Unity adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpBoostMultiplier = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Boost Duration for the Discovery Unity adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int xpBoostDuration = 15000; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryVillagerAtt.java b/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryVillagerAtt.java deleted file mode 100644 index 244cb9dbc..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryVillagerAtt.java +++ /dev/null @@ -1,207 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.discovery; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.collection.KMap; -import de.slikey.effectlib.effect.BleedEffect; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.entity.Villager; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.inventory.InventoryOpenEvent; -import org.bukkit.event.player.PlayerInteractEntityEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class DiscoveryVillagerAtt extends SimpleAdaptation { - private final KMap active = new KMap<>(); - - public DiscoveryVillagerAtt() { - super("discovery-villager-att"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("discovery.villager.description")); - setDisplayName(Localizer.dLocalize("discovery.villager.name")); - setIcon(Material.GLASS_BOTTLE); - setInterval(2432); - setBaseCost(getConfig().baseCost); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.EMERALD) - .key("challenge_discovery_villager_100") - .title(Localizer.dLocalize("advancement.challenge_discovery_villager_100.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_villager_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.EMERALD_BLOCK) - .key("challenge_discovery_villager_2500") - .title(Localizer.dLocalize("advancement.challenge_discovery_villager_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_villager_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discovery_villager_100", "discovery.villager-att.improved-trades", 100, 300); - registerMilestone("challenge_discovery_villager_2500", "discovery.villager-att.improved-trades", 2500, 1000); - } - - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("discovery.villager.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getEffectiveness(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("discovery.villager.lore2")); - v.addLore(C.GREEN + "+ " + getXpTaken(level) + " " + C.GRAY + Localizer.dLocalize("discovery.villager.lore3")); - } - - private double getEffectiveness(double multiplier) { - return Math.min(getConfig().maxEffectiveness, multiplier * multiplier + getConfig().effectivenessBase); - } - - private int getXpTaken(double level) { - double d = (getConfig().levelCostAdd * getConfig().amplifier) - (level * getConfig().levelDrain); - return (int) d; - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerInteractEntityEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (e.getRightClicked() instanceof Villager v && hasAdaptation(p)) { - if (ThreadLocalRandom.current().nextDouble() <= getEffectiveness(getLevelPercent(getLevel(p)))) { - if (p.getLevel() - getXpTaken(getLevel(p)) > 0) { - BleedEffect blood = new BleedEffect(Adapt.instance.adaptEffectManager); // Enemy gets blood - blood.material = Material.EMERALD; - blood.setEntity(v); - p.setLevel((p.getLevel() - getXpTaken(getLevel(p)))); - sp.play(p.getLocation(), Sound.ENTITY_VILLAGER_CELEBRATE, 1f, 1f); - sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f); - int level = getLevel(p); - active.put(p.getUniqueId(), level); - p.addPotionEffect(new PotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE, 60, level, true, true)); - getPlayer(p).getData().addStat("discovery.villager-att.improved-trades", 1); - } else { - BleedEffect blood = new BleedEffect(Adapt.instance.adaptEffectManager); // Enemy gets blood - blood.material = Material.STONE; - v.shakeHead(); - blood.setEntity(v); - sp.play(p.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f); - } - } - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(InventoryOpenEvent event) { - if (!(event.getPlayer() instanceof Player p)) { - return; - } - int level = active.getOrDefault(p.getUniqueId(), 0); - if (level == 0) return; - - if (event.isCancelled()) { - active.remove(p.getUniqueId()); - p.removePotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE); - } else { - p.addPotionEffect(new PotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE, 60, level, true, true)); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(InventoryCloseEvent event) { - if (!(event.getPlayer() instanceof Player p) || !active.containsKey(p.getUniqueId())) { - return; - } - - active.remove(p.getUniqueId()); - p.removePotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE); - } - - @EventHandler - public void on(PlayerQuitEvent event) { - active.remove(event.getPlayer().getUniqueId()); - } - - @Override - public void onTick() { - J.s(() -> active.forEach((p, lvl) -> { - var player = Bukkit.getPlayer(p); - if (player == null) return; - player.addPotionEffect(new PotionEffect(PotionEffectType.HERO_OF_THE_VILLAGE, 60, lvl, true, true)); - })); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Get better villager trades at the cost of XP per interaction.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effectiveness Base for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double effectivenessBase = 0.005; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Effectiveness for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxEffectiveness = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Level Drain for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int levelDrain = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Level Cost Add for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int levelCostAdd = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Amplifier for the Discovery Villager Att adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double amplifier = 1.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryXpResist.java b/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryXpResist.java deleted file mode 100644 index 1917f6f8b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/discovery/DiscoveryXpResist.java +++ /dev/null @@ -1,197 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.discovery; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class DiscoveryXpResist extends SimpleAdaptation { - private static final long COOLDOWN_MILLIS = 15000L; - private final Map cooldowns; - - public DiscoveryXpResist() { - super("discovery-xp-resist"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("discovery.resist.description")); - setDisplayName(Localizer.dLocalize("discovery.resist.name")); - setIcon(Material.TOTEM_OF_UNDYING); - setInterval(5215); - setBaseCost(getConfig().baseCost); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TOTEM_OF_UNDYING) - .key("challenge_discovery_xp_resist_25") - .title(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_25.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENCHANTED_GOLDEN_APPLE) - .key("challenge_discovery_xp_resist_250") - .title(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_250.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_250.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TOTEM_OF_UNDYING) - .key("challenge_discovery_xp_resist_clutch") - .title(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_clutch.title")) - .description(Localizer.dLocalize("advancement.challenge_discovery_xp_resist_clutch.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_discovery_xp_resist_25", "discovery.xp-resist.saves", 25, 500); - registerMilestone("challenge_discovery_xp_resist_250", "discovery.xp-resist.saves", 250, 2000); - } - - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("discovery.resist.lore0")); - v.addLore(C.GREEN + "+ " + Form.pc(getEffectiveness(getLevelPercent(level)), 0) + C.GRAY + Localizer.dLocalize("discovery.resist.lore1")); - v.addLore(C.GREEN + "+ " + getXpTaken(level) + " " + C.GRAY + Localizer.dLocalize("discovery.resist.lore2")); - } - - private double getEffectiveness(double factor) { - return Math.min(getConfig().maxEffectiveness, factor * factor + getConfig().effectivenessBase); - } - - private int getXpTaken(double level) { - double d = (getConfig().levelCostAdd * getConfig().amplifier) - (level * getConfig().levelDrain); - return Math.max(1, (int) Math.round(d)); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - if (!isCriticalHealthDamage(p, e)) { - return; - } - - SoundPlayer sp = SoundPlayer.of(p); - int level = getLevel(p); - int xpCost = getXpTaken(level); - if (p.getLevel() < xpCost) { - vfxFastRing(p.getLocation().add(0, 0.05, 0), 1, Color.RED); - sp.play(p.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 15, 0.01f); - return; - } - UUID id = p.getUniqueId(); - Long cooldown = cooldowns.get(id); - if (cooldown == null || M.ms() - cooldown > COOLDOWN_MILLIS) { - double effectiveness = getEffectiveness(getLevelPercent(level)); - double originalDamage = e.getDamage(); - e.setDamage(Math.max(0D, e.getDamage() * (1D - effectiveness))); - xp(p, 5); - cooldowns.put(id, M.ms()); - p.setLevel(p.getLevel() - xpCost); - vfxFastRing(p.getLocation().add(0, 0.05, 0), 1, Color.LIME); - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_REPAIR, 3, 0.01f); - sp.play(p.getLocation(), Sound.BLOCK_SHROOMLIGHT_HIT, 15, 0.01f); - getPlayer(p).getData().addStat("discovery.xp-resist.saves", 1); - if (originalDamage >= 30.0 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_discovery_xp_resist_clutch")) { - getPlayer(p).getAdvancementHandler().grant("challenge_discovery_xp_resist_clutch"); - } - } else { - vfxFastRing(p.getLocation().add(0, 0.05, 0), 1, Color.RED); - sp.play(p.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 15, 0.01f); - } - } - - private boolean isCriticalHealthDamage(Player p, EntityDamageEvent e) { - double threshold = Math.max(0D, getConfig().triggerHealthThreshold); - double absorption = Math.max(0D, p.getAbsorptionAmount()); - double rawDamage = Math.max(0D, e.getDamage()); - double finalDamage = Math.max(0D, e.getFinalDamage()); - double healthAfterRaw = p.getHealth() - Math.max(0D, rawDamage - absorption); - double healthAfterFinal = p.getHealth() - Math.max(0D, finalDamage - absorption); - double predictedHealth = Math.min(healthAfterRaw, healthAfterFinal); - return predictedHealth <= 0D || predictedHealth <= threshold || p.getHealth() <= threshold; - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Consume experience to mitigate damage when a hit would drop you below 5 hearts.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effectiveness Base for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double effectivenessBase = 0.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Effectiveness for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxEffectiveness = 0.95; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Level Drain for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int levelDrain = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Level Cost Add for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int levelCostAdd = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Amplifier for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double amplifier = 1.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Trigger Health Threshold for the Discovery Xp Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double triggerHealthThreshold = 10.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingAnvilSavant.java b/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingAnvilSavant.java deleted file mode 100644 index 6de772086..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingAnvilSavant.java +++ /dev/null @@ -1,163 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.enchanting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.PrepareAnvilEvent; -import org.bukkit.inventory.AnvilInventory; - -public class EnchantingAnvilSavant extends SimpleAdaptation { - public EnchantingAnvilSavant() { - super("enchanting-anvil-savant"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("enchanting.anvil_savant.description")); - setDisplayName(Localizer.dLocalize("enchanting.anvil_savant.name")); - setIcon(Material.ANVIL); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2200); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ANVIL) - .key("challenge_enchanting_anvil_200") - .title(Localizer.dLocalize("advancement.challenge_enchanting_anvil_200.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_anvil_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ANVIL) - .key("challenge_enchanting_anvil_5k") - .title(Localizer.dLocalize("advancement.challenge_enchanting_anvil_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_anvil_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchanting_anvil_200", "enchanting.anvil-savant.levels-saved", 200, 400); - registerMilestone("challenge_enchanting_anvil_5k", "enchanting.anvil-savant.levels-saved", 5000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getCostReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("enchanting.anvil_savant.lore1")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PrepareAnvilEvent e) { - if (!(e.getView().getPlayer() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - if (!(e.getInventory() instanceof AnvilInventory inventory)) { - return; - } - - Integer current = readRepairCost(inventory); - if (current == null || current <= 0) { - return; - } - - int reduced = Math.max(getConfig().minimumCost, (int) Math.ceil(current * (1D - getCostReduction(getLevel(p))))); - writeRepairCost(inventory, reduced); - int saved = current - reduced; - if (saved > 0) { - getPlayer(p).getData().addStat("enchanting.anvil-savant.levels-saved", saved); - } - } - - private Integer readRepairCost(AnvilInventory inventory) { - try { - Object value = inventory.getClass().getMethod("getRepairCost").invoke(inventory); - if (value instanceof Number number) { - return number.intValue(); - } - } catch (Throwable ignored) { - - } - - return null; - } - - private void writeRepairCost(AnvilInventory inventory, int cost) { - try { - inventory.getClass().getMethod("setRepairCost", int.class).invoke(inventory, cost); - } catch (Throwable ignored) { - - } - } - - private double getCostReduction(int level) { - return Math.min(getConfig().maximumReduction, getConfig().reductionBase + (getLevelPercent(level) * getConfig().reductionFactor)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Reduce anvil XP cost when combining, repairing, and renaming.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reduction Base for the Enchanting Anvil Savant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reductionBase = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reduction Factor for the Enchanting Anvil Savant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reductionFactor = 0.37; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Maximum Reduction for the Enchanting Anvil Savant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maximumReduction = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Cost for the Enchanting Anvil Savant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int minimumCost = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingBookshelfAttunement.java b/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingBookshelfAttunement.java deleted file mode 100644 index 665340f55..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingBookshelfAttunement.java +++ /dev/null @@ -1,135 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.enchanting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.enchantments.EnchantmentOffer; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.enchantment.PrepareItemEnchantEvent; - -public class EnchantingBookshelfAttunement extends SimpleAdaptation { - public EnchantingBookshelfAttunement() { - super("enchanting-bookshelf-attunement"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("enchanting.bookshelf_attunement.description")); - setDisplayName(Localizer.dLocalize("enchanting.bookshelf_attunement.name")); - setIcon(Material.BOOKSHELF); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BOOKSHELF) - .key("challenge_enchanting_bookshelf_100") - .title(Localizer.dLocalize("advancement.challenge_enchanting_bookshelf_100.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_bookshelf_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_enchanting_bookshelf_100", "enchanting.bookshelf-attunement.enchants-boosted", 100, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getVirtualPower(level) + C.GRAY + " " + Localizer.dLocalize("enchanting.bookshelf_attunement.lore1")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PrepareItemEnchantEvent e) { - Player p = e.getEnchanter(); - if (!hasAdaptation(p)) { - return; - } - - int power = getVirtualPower(getLevel(p)); - EnchantmentOffer[] offers = e.getOffers(); - if (offers == null) { - return; - } - - boolean boosted = false; - for (EnchantmentOffer offer : offers) { - if (offer == null) { - continue; - } - - int newCost = Math.min(30, offer.getCost() + power); - int newLevel = Math.min(offer.getEnchantment().getMaxLevel(), offer.getEnchantmentLevel() + Math.max(0, power / 3)); - offer.setCost(newCost); - offer.setEnchantmentLevel(Math.max(1, newLevel)); - boosted = true; - } - if (boosted) { - getPlayer(p).getData().addStat("enchanting.bookshelf-attunement.enchants-boosted", 1); - } - } - - private int getVirtualPower(int level) { - return Math.max(1, (int) Math.round(getConfig().powerBase + (getLevelPercent(level) * getConfig().powerFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain virtual bookshelf power to improve enchanting table offer quality.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Power Base for the Enchanting Bookshelf Attunement adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double powerBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Power Factor for the Enchanting Bookshelf Attunement adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double powerFactor = 5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingGrindstoneRecovery.java b/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingGrindstoneRecovery.java deleted file mode 100644 index 13597dce8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingGrindstoneRecovery.java +++ /dev/null @@ -1,222 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.enchanting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.EnchantmentStorageMeta; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; - -public class EnchantingGrindstoneRecovery extends SimpleAdaptation { - public EnchantingGrindstoneRecovery() { - super("enchanting-grindstone-recovery"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("enchanting.grindstone_recovery.description")); - setDisplayName(Localizer.dLocalize("enchanting.grindstone_recovery.name")); - setIcon(Material.GRINDSTONE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1700); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GRINDSTONE) - .key("challenge_enchanting_grindstone_50") - .title(Localizer.dLocalize("advancement.challenge_enchanting_grindstone_50.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_grindstone_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GRINDSTONE) - .key("challenge_enchanting_grindstone_500") - .title(Localizer.dLocalize("advancement.challenge_enchanting_grindstone_500.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_grindstone_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchanting_grindstone_50", "enchanting.grindstone-recovery.enchants-recovered", 50, 300); - registerMilestone("challenge_enchanting_grindstone_500", "enchanting.grindstone-recovery.enchants-recovered", 500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getRecoverChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("enchanting.grindstone_recovery.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(getBonusXp(level), 1) + C.GRAY + " " + Localizer.dLocalize("enchanting.grindstone_recovery.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("enchanting.grindstone_recovery.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(InventoryClickEvent e) { - if (!(e.getWhoClicked() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - if (e.getView().getTopInventory().getType() != InventoryType.GRINDSTONE || e.getRawSlot() != 2 || p.hasCooldown(Material.GRINDSTONE)) { - return; - } - - ItemStack source = getEnchantedSource(e.getView().getTopInventory().getItem(0), e.getView().getTopInventory().getItem(1)); - if (source == null) { - return; - } - - int level = getLevel(p); - if (ThreadLocalRandom.current().nextDouble() > getRecoverChance(level)) { - return; - } - - ItemStack recovered = makeBook(source.getEnchantments()); - if (recovered == null) { - return; - } - - Map overflow = p.getInventory().addItem(recovered); - overflow.values().forEach(item -> p.getWorld().dropItemNaturally(p.getLocation(), item)); - int xp = Math.max(0, (int) Math.round(getBonusXp(level))); - if (xp > 0) { - p.giveExp(xp); - } - - p.setCooldown(Material.GRINDSTONE, getCooldownTicks(level)); - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.BLOCK_GRINDSTONE_USE, 0.95f, 1.15f); - sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.8f, 1.45f); - xp(p, getConfig().skillXpOnRecovery); - getPlayer(p).getData().addStat("enchanting.grindstone-recovery.enchants-recovered", 1); - } - - private ItemStack getEnchantedSource(ItemStack a, ItemStack b) { - if (isEnchanted(a)) { - return a; - } - - if (isEnchanted(b)) { - return b; - } - - return null; - } - - private boolean isEnchanted(ItemStack item) { - return isItem(item) && !item.getEnchantments().isEmpty(); - } - - private ItemStack makeBook(Map source) { - if (source.isEmpty()) { - return null; - } - - List> entries = new ArrayList<>(source.entrySet()); - Map.Entry picked = entries.get(ThreadLocalRandom.current().nextInt(entries.size())); - ItemStack book = new ItemStack(Material.ENCHANTED_BOOK); - EnchantmentStorageMeta meta = (EnchantmentStorageMeta) book.getItemMeta(); - if (meta == null) { - return null; - } - - int safeLevel = Math.max(1, Math.min(picked.getValue(), picked.getKey().getMaxLevel())); - meta.addStoredEnchant(picked.getKey(), safeLevel, true); - book.setItemMeta(meta); - return book; - } - - private double getRecoverChance(int level) { - return Math.min(getConfig().maxRecoverChance, getConfig().recoverChanceBase + (getLevelPercent(level) * getConfig().recoverChanceFactor)); - } - - private double getBonusXp(int level) { - return getConfig().bonusXpBase + (getLevelPercent(level) * getConfig().bonusXpFactor); - } - - private int getCooldownTicks(int level) { - return Math.max(10, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Using a grindstone can recover one removed enchant on a book with bonus XP.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.74; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Recover Chance Base for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double recoverChanceBase = 0.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Recover Chance Factor for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double recoverChanceFactor = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Recover Chance for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxRecoverChance = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Xp Base for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusXpBase = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Xp Factor for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusXpFactor = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 120; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 70; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Skill Xp On Recovery for the Enchanting Grindstone Recovery adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double skillXpOnRecovery = 13; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingLapisReturn.java b/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingLapisReturn.java deleted file mode 100644 index 7c3313059..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingLapisReturn.java +++ /dev/null @@ -1,146 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.enchanting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.enchantment.EnchantItemEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class EnchantingLapisReturn extends SimpleAdaptation { - private final Map cooldown = new HashMap<>(); - - public EnchantingLapisReturn() { - super("enchanting-lapis-return"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("enchanting.lapis_return.description")); - setDisplayName(Localizer.dLocalize("enchanting.lapis_return.name")); - setIcon(Material.LAPIS_LAZULI); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(20999); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LAPIS_LAZULI) - .key("challenge_enchanting_lapis_100") - .title(Localizer.dLocalize("advancement.challenge_enchanting_lapis_100.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_lapis_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.LAPIS_BLOCK) - .key("challenge_enchanting_lapis_2500") - .title(Localizer.dLocalize("advancement.challenge_enchanting_lapis_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_lapis_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchanting_lapis_100", "enchanting.lapis-return.lapis-saved", 100, 300); - registerMilestone("challenge_enchanting_lapis_2500", "enchanting.lapis-return.lapis-saved", 2500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("enchanting.lapis_return.lore1")); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - cooldown.remove(e.getPlayer().getUniqueId()); - } - - - @EventHandler(priority = EventPriority.HIGH) - public void on(EnchantItemEvent e) { - if (e.isCancelled()) { - return; - } - - Player p = e.getEnchanter(); - if (!hasAdaptation(p)) { - return; - } - - - if (ThreadLocalRandom.current().nextDouble(100D) > 80D) { - long now = System.currentTimeMillis(); - UUID playerId = p.getUniqueId(); - Long nextAllowedAt = cooldown.get(playerId); - if (nextAllowedAt != null && nextAllowedAt > now) { - return; - } - - cooldown.put(playerId, now + 20000L); - p.getWorld().dropItemNaturally(p.getLocation(), new ItemStack(Material.LAPIS_LAZULI, getLevel(p))); - getPlayer(p).getData().addStat("enchanting.lapis-return.lapis-saved", getLevel(p)); - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Chance to return free lapis when enchanting at the cost of 1 extra level.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.9; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingOfferReroll.java b/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingOfferReroll.java deleted file mode 100644 index 537730f81..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingOfferReroll.java +++ /dev/null @@ -1,193 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.enchanting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; - -import java.util.concurrent.ThreadLocalRandom; - -public class EnchantingOfferReroll extends SimpleAdaptation { - public EnchantingOfferReroll() { - super("enchanting-offer-reroll"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("enchanting.offer_reroll.description")); - setDisplayName(Localizer.dLocalize("enchanting.offer_reroll.name")); - setIcon(Material.ENCHANTING_TABLE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1800); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENCHANTING_TABLE) - .key("challenge_enchanting_reroll_100") - .title(Localizer.dLocalize("advancement.challenge_enchanting_reroll_100.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_reroll_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENCHANTING_TABLE) - .key("challenge_enchanting_reroll_1k") - .title(Localizer.dLocalize("advancement.challenge_enchanting_reroll_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_reroll_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchanting_reroll_100", "enchanting.offer-reroll.rerolls", 100, 300); - registerMilestone("challenge_enchanting_reroll_1k", "enchanting.offer-reroll.rerolls", 1000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("enchanting.offer_reroll.lore1")); - v.addLore(C.YELLOW + "* " + getLapisCost(level) + C.GRAY + " " + Localizer.dLocalize("enchanting.offer_reroll.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - if (e.isCancelled() || e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getHand() != EquipmentSlot.HAND || e.getClickedBlock() == null) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking() || e.getClickedBlock().getType() != Material.ENCHANTING_TABLE) { - return; - } - - int level = getLevel(p); - int lapisCost = getLapisCost(level); - if (p.getFoodLevel() < 0) { - return; - } - - if (!consumeLapis(p, lapisCost) || p.getLevel() < getConfig().xpLevelCost) { - return; - } - - if (!setSeed(p, ThreadLocalRandom.current().nextInt())) { - return; - } - - p.setLevel(Math.max(0, p.getLevel() - getConfig().xpLevelCost)); - p.setCooldown(Material.ENCHANTING_TABLE, getCooldownTicks(level)); - e.setCancelled(true); - - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.2f); - sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.5f, 0.85f); - xp(p, getConfig().xpGainOnReroll); - getPlayer(p).getData().addStat("enchanting.offer-reroll.rerolls", 1); - } - - private boolean consumeLapis(Player p, int amount) { - int need = amount; - for (ItemStack stack : p.getInventory().getContents()) { - if (stack == null || stack.getType() != Material.LAPIS_LAZULI || need <= 0) { - continue; - } - - int used = Math.min(stack.getAmount(), need); - stack.setAmount(stack.getAmount() - used); - need -= used; - } - return need <= 0; - } - - private boolean setSeed(Player p, int seed) { - try { - p.getClass().getMethod("setEnchantmentSeed", int.class).invoke(p, seed); - return true; - } catch (Throwable ignored) { - return false; - } - } - - private int getLapisCost(int level) { - return Math.max(1, (int) Math.round(getConfig().lapisCostBase - (getLevelPercent(level) * getConfig().lapisCostFactor))); - } - - private int getCooldownTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-right-click an enchanting table to reroll offers for lapis and XP.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Lapis Cost Base for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double lapisCostBase = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Lapis Cost Factor for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double lapisCostFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 320; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 220; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Level Cost for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int xpLevelCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Gain On Reroll for the Enchanting Offer Reroll adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpGainOnReroll = 15; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingQuickEnchant.java b/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingQuickEnchant.java deleted file mode 100644 index eb3dfb431..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingQuickEnchant.java +++ /dev/null @@ -1,212 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.enchanting; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryAction; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.EnchantmentStorageMeta; -import org.bukkit.inventory.meta.ItemMeta; - -public class EnchantingQuickEnchant extends SimpleAdaptation { - public EnchantingQuickEnchant() { - super("enchanting-quick-enchant"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("enchanting.quick_enchant.description")); - setDisplayName(Localizer.dLocalize("enchanting.quick_enchant.name")); - setIcon(Material.WRITABLE_BOOK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(15100); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENCHANTED_BOOK) - .key("challenge_enchanting_quick_100") - .title(Localizer.dLocalize("advancement.challenge_enchanting_quick_100.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_quick_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BOOKSHELF) - .key("challenge_enchanting_quick_1k") - .title(Localizer.dLocalize("advancement.challenge_enchanting_quick_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_quick_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchanting_quick_100", "enchanting.quick-enchant.books-applied", 100, 300); - registerMilestone("challenge_enchanting_quick_1k", "enchanting.quick-enchant.books-applied", 1000, 1000); - } - - private int getTotalLevelCount(int level) { - return level + (level > getConfig().maxPowerBonusLimit ? level / getConfig().maxPowerBonus1PerLevels : 0); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getTotalLevelCount(level) + C.GRAY + " " + Localizer.dLocalize("enchanting.quick_enchant.lore1")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(InventoryClickEvent e) { - if (e.getClickedInventory() == null || e.isCancelled()) { - return; - } - if (e.getWhoClicked() instanceof Player p - && hasAdaptation(p) - && e.getAction().equals(InventoryAction.SWAP_WITH_CURSOR) - && e.getClick().equals(ClickType.LEFT) - && (e.getSlotType().equals(InventoryType.SlotType.CONTAINER) - || e.getSlotType().equals(InventoryType.SlotType.ARMOR) - || e.getSlotType().equals(InventoryType.SlotType.QUICKBAR)) - && e.getCursor() != null - && e.getCurrentItem() != null - && e.getCursor().getType().equals(Material.ENCHANTED_BOOK) - && !e.getCurrentItem().getType().equals(Material.BOOK) - && !e.getCurrentItem().getType().equals(Material.ENCHANTED_BOOK) - && e.getCursor().getItemMeta() != null - && e.getCursor().getItemMeta() instanceof EnchantmentStorageMeta eb - && e.getCurrentItem().getItemMeta() != null - && e.getCurrentItem().getAmount() == 1 - && e.getCursor().getAmount() == 1) { - ItemStack item = e.getCurrentItem(); - ItemStack book = e.getCursor(); - KMap itemEnchants = new KMap<>(item.getType().equals(Material.ENCHANTED_BOOK) - ? ((EnchantmentStorageMeta) item.getItemMeta()).getStoredEnchants() - : item.getEnchantments()); - KMap bookEnchants = new KMap<>(eb.getStoredEnchants()); - KMap newEnchants = itemEnchants.copy(); - KMap addEnchants = new KMap<>(); - int power = itemEnchants.values().stream().mapToInt(i -> i).sum(); - - if (bookEnchants.isEmpty()) { - return; - } - - for (Enchantment i : bookEnchants.k()) { - if (itemEnchants.containsKey(i)) { - continue; - } - - power += bookEnchants.get(i); - newEnchants.put(i, bookEnchants.get(i)); - addEnchants.put(i, bookEnchants.get(i)); - bookEnchants.remove(i); - } - - SoundPlayer sp = SoundPlayer.of(p); - if (power > getTotalLevelCount(getLevel(p))) { - Adapt.actionbar(p, C.RED + Localizer.dLocalize("enchanting.quick_enchant.lore2") + getTotalLevelCount(getLevel(p)) + " " + Localizer.dLocalize("enchanting.quick_enchant.lore3")); - sp.play(p.getLocation(), Sound.BLOCK_CONDUIT_DEACTIVATE, 0.5f, 1.7f); - return; - } - - if (!itemEnchants.equals(newEnchants)) { - ItemMeta im = item.getItemMeta(); - - if (im instanceof EnchantmentStorageMeta sm) { - sm.getStoredEnchants().keySet().forEach(sm::removeStoredEnchant); - newEnchants.forEach((ec, l) -> sm.addStoredEnchant(ec, l, true)); - Adapt.messagePlayer(p, "---"); - sm.getStoredEnchants().forEach((k, v) -> Adapt.messagePlayer(p, k.getKey().getKey() + " " + v)); - } else { - im.getEnchants().keySet().forEach(im::removeEnchant); - newEnchants.forEach((ec, l) -> im.addEnchant(ec, l, true)); - } - - xp(p, 50); - item.setItemMeta(im); - e.setCurrentItem(item); - e.setCancelled(true); - getPlayer(p).getData().addStat("enchanting.quick-enchant.books-applied", 1); - sp.play(p.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.7f); - sp.play(p.getLocation(), Sound.BLOCK_DEEPSLATE_TILES_BREAK, 0.5f, 0.7f); - xp(p, 320 * addEnchants.values().stream().mapToInt((i) -> i).sum(), "quick-apply"); - - if (bookEnchants.isEmpty()) { - e.setCursor(null); - } else if (!eb.getStoredEnchants().equals(bookEnchants)) { - eb.getStoredEnchants().keySet().forEach(eb::removeStoredEnchant); - bookEnchants.forEach((ec, l) -> eb.addStoredEnchant(ec, l, true)); - book.setItemMeta(eb); - e.setCursor(book); - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Enchant items by clicking enchant books directly on them.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Power Bonus Limit for the Enchanting Quick Enchant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxPowerBonusLimit = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Power Bonus1Per Levels for the Enchanting Quick Enchant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxPowerBonus1PerLevels = 3; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingXPReturn.java b/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingXPReturn.java deleted file mode 100644 index 5637cc8e3..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/enchanting/EnchantingXPReturn.java +++ /dev/null @@ -1,134 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.enchanting; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.enchantment.EnchantItemEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -import java.util.HashMap; -import java.util.Map; - -public class EnchantingXPReturn extends SimpleAdaptation { - private final Map cooldown = new HashMap<>(); - - public EnchantingXPReturn() { - super("enchanting-xp-return"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("enchanting.return.description")); - setDisplayName(Localizer.dLocalize("enchanting.return.name")); - setIcon(Material.EXPERIENCE_BOTTLE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(13001); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.EXPERIENCE_BOTTLE) - .key("challenge_enchanting_xp_100") - .title(Localizer.dLocalize("advancement.challenge_enchanting_xp_100.title")) - .description(Localizer.dLocalize("advancement.challenge_enchanting_xp_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_enchanting_xp_100", "enchanting.xp-return.levels-saved", 100, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("enchanting.return.lore1")); - v.addLore(C.GREEN + "" + getConfig().xpReturn * (level * level) + Localizer.dLocalize("enchanting.return.lore2")); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - cooldown.remove(p); - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EnchantItemEvent e) { - if (e.isCancelled()) { - return; - } - int level = getLevel(e.getEnchanter()); - Player p = e.getEnchanter(); - if (!hasAdaptation(p)) { - return; - } - - if (cooldown.containsKey(p) && cooldown.get(p) + 20000 < System.currentTimeMillis()) { - cooldown.remove(p); - } else if (cooldown.containsKey(p) && cooldown.get(p) + 20000 > System.currentTimeMillis()) { - return; - } - cooldown.put(p, System.currentTimeMillis()); - int xpAmount = getConfig().xpReturn * (level * level); - p.getWorld().spawn(p.getLocation(), org.bukkit.entity.ExperienceOrb.class).setExperience(xpAmount); - getPlayer(p).getData().addStat("enchanting.xp-return.levels-saved", xpAmount); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Enchanting XP is partially refunded when you enchant an item.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Return for the Enchanting XPReturn adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public int xpReturn = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.9; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationDropToInventory.java b/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationDropToInventory.java deleted file mode 100644 index 2f144a596..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationDropToInventory.java +++ /dev/null @@ -1,137 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.excavation; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockDropItemEvent; - -import java.util.List; - -public class ExcavationDropToInventory extends SimpleAdaptation { - public ExcavationDropToInventory() { - super("excavation-drop-to-inventory"); - registerConfiguration(ExcavationDropToInventory.Config.class); - setDescription(Localizer.dLocalize("pickaxe.drop_to_inventory.description")); - setDisplayName(Localizer.dLocalize("excavation.drop_to_inventory.name")); - setIcon(Material.CHEST); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(11777); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_excavation_dti_10k") - .title(Localizer.dLocalize("advancement.challenge_excavation_dti_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavation_dti_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_excavation_dti_10k", "excavation.drop-to-inv.items-caught", 10000, 500); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("pickaxe.drop_to_inventory.lore1")); - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockDropItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - if (p.getGameMode() != GameMode.SURVIVAL) { - return; - } - if (!canInteract(p, e.getBlock().getLocation())) { - return; - } - if (!canBlockBreak(p, e.getBlock().getLocation())) { - return; - } - if (ItemListings.toolShovels.contains(p.getInventory().getItemInMainHand().getType())) { - List items = new KList<>(e.getItems()); - e.getItems().clear(); - for (Item i : items) { - sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); - xp(p, 2); - getPlayer(p).getData().addStat("excavation.drop-to-inv.items-caught", 1); - if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { - p.getWorld().dropItem(p.getLocation(), i.getItemStack()); - } - } - } - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Excavated blocks drop directly into your inventory.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationHaste.java b/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationHaste.java deleted file mode 100644 index e083cf316..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationHaste.java +++ /dev/null @@ -1,125 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.excavation; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockDamageEvent; -import org.bukkit.potion.PotionEffect; - -public class ExcavationHaste extends SimpleAdaptation { - public ExcavationHaste() { - super("excavation-haste"); - registerConfiguration(ExcavationHaste.Config.class); - setDisplayName(Localizer.dLocalize("excavation.haste.name")); - setDescription(Localizer.dLocalize("excavation.haste.description")); - setIcon(Material.GOLDEN_PICKAXE); - setInterval(4388); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SHOVEL) - .key("challenge_excavation_haste_5k") - .title(Localizer.dLocalize("advancement.challenge_excavation_haste_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavation_haste_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SHOVEL) - .key("challenge_excavation_haste_50k") - .title(Localizer.dLocalize("advancement.challenge_excavation_haste_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavation_haste_50k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_excavation_haste_5k", "excavation.haste.blocks-while-hasted", 5000, 400); - registerMilestone("challenge_excavation_haste_50k", "excavation.haste.blocks-while-hasted", 50000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("excavation.haste.lore1")); - v.addLore(C.GREEN + "" + (level) + C.GRAY + Localizer.dLocalize("excavation.haste.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockDamageEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - if (!canInteract(p, e.getBlock().getLocation())) { - return; - } - p.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, 15, getLevel(p), false, false, true)); - getPlayer(p).getData().addStat("excavation.haste.blocks-while-hasted", 1); - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain Haste while excavating blocks.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationOmniTool.java b/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationOmniTool.java deleted file mode 100644 index a5acb2e30..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationOmniTool.java +++ /dev/null @@ -1,394 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.excavation; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.content.item.multiItems.OmniTool; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockDamageEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryAction; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.List; -import java.util.Map; - - -public class ExcavationOmniTool extends SimpleAdaptation { - private static final OmniTool omniTool = new OmniTool(); - - public ExcavationOmniTool() { - super("excavation-omnitool"); - registerConfiguration(ExcavationOmniTool.Config.class); - setDisplayName(Localizer.dLocalize("excavation.omni_tool.name")); - setDescription(Localizer.dLocalize("excavation.omni_tool.description")); - setIcon(Material.DISC_FRAGMENT_5); - setInterval(20202); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_PICKAXE) - .key("challenge_excavation_omni_1k") - .title(Localizer.dLocalize("advancement.challenge_excavation_omni_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavation_omni_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_PICKAXE) - .key("challenge_excavation_omni_25k") - .title(Localizer.dLocalize("advancement.challenge_excavation_omni_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavation_omni_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_excavation_omni_1k", "excavation.omni-tool.auto-swaps", 1000, 400); - registerMilestone("challenge_excavation_omni_25k", "excavation.omni-tool.auto-swaps", 25000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("excavation.omni_tool.lore1")); - v.addLore(C.GRAY + Localizer.dLocalize("excavation.omni_tool.lore2")); - v.addLore(C.GREEN + Localizer.dLocalize("excavation.omni_tool.lore3")); - v.addLore(C.RED + Localizer.dLocalize("excavation.omni_tool.lore4")); - v.addLore(C.GRAY + Localizer.dLocalize("excavation.omni_tool.lore5")); - v.addLore(C.GREEN + "" + (level + getConfig().startingSlots) + C.GRAY + " " + Localizer.dLocalize("excavation.omni_tool.lore6")); - v.addLore(C.UNDERLINE + Localizer.dLocalize("excavation.omni_tool.lore7")); - - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public void onTick() { - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(EntityDamageByEntityEvent e) { - if (e.getDamager() instanceof Player p && validateTool(p.getInventory().getItemInMainHand())) { - //deny if the tool durability is about to break - if (p.getInventory().getItemInMainHand().getType().getMaxDurability() - p.getInventory().getItemInMainHand().getDurability() <= 2) { - e.setCancelled(true); - return; - } - - if (e.isCancelled()) { - return; - } - if (!hasAdaptation(p) && validateTool(p.getInventory().getItemInMainHand())) { - e.setCancelled(true); - return; - } - if (!hasAdaptation(p)) { - if (validateTool(p.getInventory().getItemInMainHand())) { - e.setCancelled(true); - } - return; - } - ItemStack hand = p.getInventory().getItemInMainHand(); - Damageable inHand = (Damageable) hand.getItemMeta(); - - if (!validateTool(hand)) { - return; - } - J.s(() -> p.getInventory().setItemInMainHand(omniTool.nextSword(hand))); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - if (inHand != null && inHand.hasDamage()) { - if ((hand.getType().getMaxDurability() - inHand.getDamage() - 2) <= 2) { - e.setCancelled(true); - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.77f); - } - } - - } - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(BlockBreakEvent e) { - Player p = e.getPlayer(); - if (validateTool(p.getInventory().getItemInMainHand())) { - //deny if the tool durability is about to break - if (p.getInventory().getItemInMainHand().getType().getMaxDurability() - p.getInventory().getItemInMainHand().getDurability() <= 2) { - e.setCancelled(true); - return; - } - - - //deny if they dont have the adaptation - if (!hasAdaptation(p)) { - e.setCancelled(true); - return; - } - } - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - if (validateTool(p.getInventory().getItemInMainHand())) { - //deny if the tool durability is about to break - if (p.getInventory().getItemInMainHand().getType().getMaxDurability() - p.getInventory().getItemInMainHand().getDurability() <= 2) { - e.setCancelled(true); - return; - } - - if (!hasAdaptation(p)) { - return; - } - if (e.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { - ItemStack hand = p.getInventory().getItemInMainHand(); - Damageable imHand = (Damageable) hand.getItemMeta(); - Block block = e.getClickedBlock(); - if (block != null) { - SoundPlayer sp = SoundPlayer.of(p); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - if (ItemListings.farmable.contains(block.getType())) { - if (isShovel(hand)) { - J.s(() -> p.getInventory().setItemInMainHand(omniTool.nextHoe(hand))); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - } else { - J.s(() -> p.getInventory().setItemInMainHand(omniTool.nextShovel(hand))); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - } - if (imHand != null && imHand.hasDamage()) { - if ((hand.getType().getMaxDurability() - imHand.getDamage() - 2) <= 2) { - e.setCancelled(true); - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.77f); - } - } - } else if (ItemListings.burnable.contains(block.getType())) { - J.s(() -> p.getInventory().setItemInMainHand(omniTool.nextFnS(hand))); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - if (imHand != null && imHand.hasDamage()) { - if ((hand.getType().getMaxDurability() - imHand.getDamage() - 2) <= 2) { - e.setCancelled(true); - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.77f); - } - } - } - } - } - } - - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerDropItemEvent e) { - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - if (p.isSneaking()) { - if (validateTool(e.getItemDrop().getItemStack())) { - List drops = omniTool.explode(e.getItemDrop().getItemStack()); - for (ItemStack i : drops) { - Damageable iDmgable = (Damageable) i.getItemMeta(); - if (i.hasItemMeta()) { - ItemMeta im = i.getItemMeta().clone(); - ItemMeta im2 = im; - if (im.hasDisplayName()) { - im2.setDisplayName(im.getDisplayName()); - } - if (im.hasEnchants()) { - Map enchants = im.getEnchants(); - for (Enchantment enchant : enchants.keySet()) { - im2.addEnchant(enchant, enchants.get(enchant), true); - } - } - if (iDmgable != null && iDmgable.hasDamage()) { - ((Damageable) im2).setDamage(iDmgable.getDamage()); - } - im2.setLore(null); - i.setItemMeta(im2); - } - drops.set(drops.indexOf(i), i); - } - - J.s(() -> { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_DEATH, 0.25f, 0.77f); - for (ItemStack i : drops) { - p.getWorld().dropItem(p.getLocation(), i); - } - }); - e.getItemDrop().setItemStack(new ItemStack(Material.AIR)); - } - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockDamageEvent e) { - Player p = e.getPlayer(); - org.bukkit.block.Block b = e.getBlock(); // nms block for pref tool - ItemStack hand = p.getInventory().getItemInMainHand(); - - if (validateTool(hand)) { - if (e.isCancelled()) { - return; - } - if (!hasAdaptation(p)) { - return; - } - - Damageable imHand = (Damageable) hand.getItemMeta(); - if (ItemListings.getAxePreference().contains(b.getType())) { - if (!isAxe(hand)) { - Adapt.verbose("Omnitool for " + p.getName() + " changed to axe"); - J.s(() -> p.getInventory().setItemInMainHand(omniTool.nextAxe(hand))); - itemDelegate(e, hand, imHand); - } else { - Adapt.verbose("Omnitool for " + p.getName() + " is already axe"); - } - } else if (ItemListings.getShovelPreference().contains(b.getType())) { - if (!isShovel(hand)) { - Adapt.verbose("Omnitool for " + p.getName() + " changed to shovel"); - J.s(() -> p.getInventory().setItemInMainHand(omniTool.nextShovel(hand))); - itemDelegate(e, hand, imHand); - } else { - Adapt.verbose("Omnitool for " + p.getName() + " is already shovel"); - } - } else if (ItemListings.getSwordPreference().contains(b.getType())) { - if (!isSword(hand)) { - Adapt.verbose("Omnitool for " + p.getName() + " changed to sword"); - J.s(() -> p.getInventory().setItemInMainHand(omniTool.nextSword(hand))); - itemDelegate(e, hand, imHand); - } else { - Adapt.verbose("Omnitool for " + p.getName() + " is already sword"); - } - } else { // Default to pickaxe - if (!isPickaxe(hand)) { - Adapt.verbose("Omnitool for " + p.getName() + " changed to pickaxe"); - J.s(() -> p.getInventory().setItemInMainHand(omniTool.nextPickaxe(hand))); - itemDelegate(e, hand, imHand); - } - } - } - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(InventoryClickEvent e) { - if (!hasAdaptation((Player) e.getWhoClicked())) { - return; - } - if (e.getClickedInventory() != null && e.getClick().equals(ClickType.SHIFT_LEFT) && e.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { - ItemStack cursor = e.getWhoClicked().getItemOnCursor().clone(); - ItemStack clicked = e.getClickedInventory().getItem(e.getSlot()).clone(); - - if (omniTool.explode(cursor).size() > 1 || omniTool.explode(clicked).size() > 1) { - if (omniTool.explode(cursor).size() >= getSlots(getLevel((Player) e.getWhoClicked())) || omniTool.explode(clicked).size() >= getSlots(getLevel((Player) e.getWhoClicked()))) { - e.setCancelled(true); - SoundPlayer sp = SoundPlayer.of((Player) e.getWhoClicked()); - sp.play(e.getWhoClicked().getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 1f, 0.77f); - return; - } - } - if (ItemListings.tool.contains(cursor.getType()) && ItemListings.tool.contains(clicked.getType())) { // TOOLS ONLY - if (!cursor.getType().isAir() && !clicked.getType().isAir() && omniTool.supportsItem(cursor) && omniTool.supportsItem(clicked)) { - e.setCancelled(true); - e.getWhoClicked().setItemOnCursor(new ItemStack(Material.AIR)); - e.getClickedInventory().setItem(e.getSlot(), omniTool.build(cursor, clicked)); - SoundPlayer spw = SoundPlayer.of(e.getWhoClicked().getWorld()); - spw.play(e.getWhoClicked().getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - } - } - } - - } - - private void itemDelegate(BlockDamageEvent e, ItemStack hand, Damageable imHand) { - Player p = e.getPlayer(); - getPlayer(p).getData().addStat("excavation.omni-tool.auto-swaps", 1); - SoundPlayer sp = SoundPlayer.of(p); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_ELYTRA, 1f, 0.77f); - if (imHand != null && imHand.hasDamage()) { - if ((hand.getType().getMaxDurability() - imHand.getDamage() - 2) <= 2) { - e.setCancelled(true); - sp.play(p.getLocation(), Sound.ENTITY_IRON_GOLEM_STEP, 0.25f, 0.77f); - } - } - } - - private boolean validateTool(ItemStack item) { - return (item.getItemMeta() != null - && item.getItemMeta().getLore() != null - && item.getItemMeta().getLore().toString().contains("Leatherman")); - } - - private double getSlots(double level) { - return getConfig().startingSlots + level; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Dynamically merge and swap tools on the fly based on what you are mining.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Starting Slots for the Excavation Omni Tool adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int startingSlots = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationSeismicPing.java b/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationSeismicPing.java deleted file mode 100644 index c68450850..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationSeismicPing.java +++ /dev/null @@ -1,294 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.excavation; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class ExcavationSeismicPing extends SimpleAdaptation { - private final Map cooldowns = new HashMap<>(); - - public ExcavationSeismicPing() { - super("excavation-seismic-ping"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("excavation.seismic_ping.description")); - setDisplayName(Localizer.dLocalize("excavation.seismic_ping.name")); - setIcon(Material.GOAT_HORN); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2200); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BELL) - .key("challenge_excavation_seismic_200") - .title(Localizer.dLocalize("advancement.challenge_excavation_seismic_200.title")) - .description(Localizer.dLocalize("advancement.challenge_excavation_seismic_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_excavation_seismic_200", "excavation.seismic-ping.pings-triggered", 200, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getScanRange(level) + C.GRAY + " " + Localizer.dLocalize("excavation.seismic_ping.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getPingChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("excavation.seismic_ping.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("excavation.seismic_ping.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - cooldowns.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(BlockBreakEvent e) { - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !isExcavationTool(p.getInventory().getItemInMainHand())) { - return; - } - - if (!canBlockBreak(p, e.getBlock().getLocation())) { - return; - } - - int level = getLevel(p); - long now = System.currentTimeMillis(); - long nextReady = cooldowns.getOrDefault(p.getUniqueId(), 0L); - if (now < nextReady) { - return; - } - - cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); - if (ThreadLocalRandom.current().nextDouble() > getPingChance(level)) { - return; - } - - Block target = findNearestOre(e.getBlock().getLocation(), getScanRange(level)); - if (target == null) { - return; - } - - Location origin = p.getEyeLocation(); - Location targetCenter = target.getLocation().add(0.5, 0.5, 0.5); - Vector direction = targetCenter.toVector().subtract(origin.toVector()); - if (direction.lengthSquared() <= 0.0000001) { - return; - } - - renderDirectionHint(p, origin, direction.normalize(), getHintSegments(level)); - playPingSound(p, origin.distance(targetCenter), getScanRange(level)); - getPlayer(p).getData().addStat("excavation.seismic-ping.pings-triggered", 1); - xp(p, getConfig().xpPerPing + (getValue(target.getType()) * getConfig().targetValueXpMultiplier)); - } - - private void renderDirectionHint(Player p, Location origin, Vector direction, int segments) { - Particle.DustOptions dust = new Particle.DustOptions(Color.fromRGB(110, 230, 255), (float) getConfig().particleSize); - Location at = origin.clone(); - for (int i = 0; i < segments; i++) { - at = at.add(direction.clone().multiply(getConfig().segmentSpacing)); - if (areParticlesEnabled()) { - p.spawnParticle(Particle.DUST, at, Math.max(1, getConfig().segmentParticleCount), 0.05, 0.05, 0.05, 0, dust); - } - } - - if (areParticlesEnabled()) { - - p.spawnParticle(Particle.ELECTRIC_SPARK, at, Math.max(1, getConfig().tipParticleCount), 0.1, 0.1, 0.1, 0.04); - - } - } - - private void playPingSound(Player p, double distance, int range) { - double normalized = Math.min(1.0, distance / Math.max(1.0, range)); - float pitch = (float) Math.max(0.45, Math.min(1.95, 1.9 - (normalized * 1.1))); - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.9f, pitch); - sp.play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BIT, 0.65f, (float) Math.min(2.0, pitch + 0.2)); - } - - private Block findNearestOre(Location origin, int range) { - World world = origin.getWorld(); - if (world == null) { - return null; - } - - int ox = origin.getBlockX(); - int oy = origin.getBlockY(); - int oz = origin.getBlockZ(); - int minY = world.getMinHeight(); - int maxY = world.getMaxHeight() - 1; - int rangeSq = range * range; - - Block best = null; - double bestDistanceSq = Double.MAX_VALUE; - for (int x = -range; x <= range; x++) { - int bx = ox + x; - for (int z = -range; z <= range; z++) { - int bz = oz + z; - if (!world.isChunkLoaded(bx >> 4, bz >> 4)) { - continue; - } - - for (int y = -range; y <= range; y++) { - int by = oy + y; - if (by < minY || by > maxY) { - continue; - } - - int d2 = (x * x) + (y * y) + (z * z); - if (d2 > rangeSq || d2 >= bestDistanceSq) { - continue; - } - - Block block = world.getBlockAt(bx, by, bz); - if (!isOre(block.getType())) { - continue; - } - - best = block; - bestDistanceSq = d2; - } - } - } - - return best; - } - - private boolean isOre(Material type) { - return type == Material.ANCIENT_DEBRIS || type.name().endsWith("_ORE"); - } - - private boolean isExcavationTool(ItemStack item) { - if (!isItem(item)) { - return false; - } - - String name = item.getType().name(); - return name.endsWith("_SHOVEL") || name.endsWith("_PICKAXE"); - } - - private int getScanRange(int level) { - return Math.max(6, (int) Math.round(getConfig().scanRangeBase + (getLevelPercent(level) * getConfig().scanRangeFactor))); - } - - private double getPingChance(int level) { - return Math.min(getConfig().maxPingChance, getConfig().pingChanceBase + (getLevelPercent(level) * getConfig().pingChanceFactor)); - } - - private long getCooldownMillis(int level) { - return Math.max(350L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - private int getHintSegments(int level) { - return Math.max(4, (int) Math.round(getConfig().hintSegmentsBase + (getLevelPercent(level) * getConfig().hintSegmentsFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Mining can emit seismic pings that hint toward nearby ore direction.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.78; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Scan Range Base for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double scanRangeBase = 11; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Scan Range Factor for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double scanRangeFactor = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ping Chance Base for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pingChanceBase = 0.14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ping Chance Factor for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pingChanceFactor = 0.37; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Ping Chance for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxPingChance = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisBase = 2600; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisFactor = 1850; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hint Segments Base for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hintSegmentsBase = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hint Segments Factor for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hintSegmentsFactor = 9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Segment Spacing for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double segmentSpacing = 0.55; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Particle Size for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double particleSize = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Segment Particle Count for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int segmentParticleCount = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Tip Particle Count for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int tipParticleCount = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Ping for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerPing = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Target Value Xp Multiplier for the Excavation Seismic Ping adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double targetValueXpMultiplier = 0.5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationSpelunker.java b/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationSpelunker.java deleted file mode 100644 index 4ed87d2bf..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/excavation/ExcavationSpelunker.java +++ /dev/null @@ -1,239 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.excavation; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Particles; -import fr.skytasul.glowingentities.GlowingEntities; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.entity.Slime; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; - -public class ExcavationSpelunker extends SimpleAdaptation { - private final Map cooldowns; - - public ExcavationSpelunker() { - super("excavation-spelunker"); - registerConfiguration(ExcavationSpelunker.Config.class); - setDisplayName(Localizer.dLocalize("excavation.spelunker.name")); - setDescription(Localizer.dLocalize("excavation.spelunker.description")); - setIcon(Material.GOLDEN_HELMET); - setInterval(20388); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPYGLASS) - .key("challenge_excavation_spelunker_1k") - .title(Localizer.dLocalize("advancement.challenge_excavation_spelunker_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavation_spelunker_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_ORE) - .key("challenge_excavation_spelunker_25k") - .title(Localizer.dLocalize("advancement.challenge_excavation_spelunker_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavation_spelunker_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_excavation_spelunker_1k", "excavation.spelunker.ores-revealed", 1000, 400); - registerMilestone("challenge_excavation_spelunker_25k", "excavation.spelunker.ores-revealed", 25000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("excavation.spelunker.lore1")); - v.addLore(C.YELLOW + Localizer.dLocalize("excavation.spelunker.lore2") + getConfig().rangeMultiplier * level); - v.addLore(C.YELLOW + Localizer.dLocalize("excavation.spelunker.lore3")); - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(PlayerToggleSneakEvent e) { - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - // Check if player is sneaking, has Glowberries in main hand, and an ore in offhand - if (p.isSneaking() && hasGlowberries(p) && hasOreInOffhand(p) && hasAdaptation(p)) { - // Check if player is on cooldown - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown > System.currentTimeMillis()) { - sp.play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1); - return; - } - int radius = getConfig().rangeMultiplier * getLevel(p); - consumeGlowberry(p); - searchForOres(p, radius); - cooldowns.put(p, (long) (System.currentTimeMillis() + (1000 * getConfig().cooldown))); - } - } - - private boolean hasGlowberries(Player player) { - return player.getInventory().getItemInMainHand().getType() == Material.GLOW_BERRIES; - } - - private void consumeGlowberry(Player player) { - ItemStack berries = player.getInventory().getItemInMainHand(); - berries.setAmount(berries.getAmount() - 1); - player.getInventory().setItemInMainHand(berries); - } - - private boolean hasOreInOffhand(Player player) { - Material offhandType = player.getInventory().getItemInOffHand().getType(); - return ItemListings.ores.contains(offhandType); - } - - private void searchForOres(Player p, int radius) { - Location playerLocation = p.getLocation(); - World world = p.getWorld(); - Material targetOre = p.getInventory().getItemInOffHand().getType(); - ChatColor c = ItemListings.oreColorsChatColor.get(targetOre); - Particle.DustOptions dustOptions = new Particle.DustOptions(Color.WHITE, 1); - for (int x = -radius; x <= radius; x++) { - for (int y = -radius; y <= radius; y++) { - for (int z = -radius; z <= radius; z++) { - if (x * x + y * y + z * z <= radius * radius) { - Location blockLocation = playerLocation.clone().add(x, y, z); - Block block = world.getBlockAt(blockLocation); - GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); - - if (block.getType() == targetOre) { - getPlayer(p).getData().addStat("excavation.spelunker.ores-revealed", 1); - // Raytrace particles from player to the found ore - Vector vector = blockLocation.clone().subtract(playerLocation).toVector().normalize().multiply(0.5); - Location particleLocation = playerLocation.clone(); - - while (particleLocation.distance(blockLocation) > 0.5) { - particleLocation.add(vector); - if (areParticlesEnabled()) { - p.spawnParticle(Particles.REDSTONE, particleLocation, 1, dustOptions); - } - } - - SoundPlayer spw = SoundPlayer.of(world); - spw.play(block.getLocation().add(0.5, 0, 0.5), Sound.BLOCK_BEACON_ACTIVATE, 1, 1); - Slime slime = block.getWorld().spawn(block.getLocation().add(0.5, 0, 0.5), Slime.class, (s) -> { - s.setRotation(0, 0); - s.setInvulnerable(true); - s.setCollidable(false); - s.setGravity(false); - s.setSilent(true); - s.setAI(false); - s.setSize(2); - s.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0, false, false)); - s.setMetadata("preventSuffocation", new FixedMetadataValue(Adapt.instance, true)); - }); - - try { - glowingEntities.setGlowing(slime, p, c); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - - J.s(() -> { - try { - glowingEntities.unsetGlowing(slime, p); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - - slime.remove(); - }, 5 * 20); - } - } - } - } - } - } - - - @EventHandler - public void onEntityDamage(EntityDamageEvent e) { - if (e.getEntity() instanceof Slime && e.getCause() == EntityDamageEvent.DamageCause.SUFFOCATION) { - Slime slime = (Slime) e.getEntity(); - if (slime.hasMetadata("preventSuffocation")) { - e.setCancelled(true); - } else { - e.setCancelled(true); - slime.remove(); - } - } - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("See ores through the ground using Glowberries in your main hand.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Excavation Spelunker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldown = 6.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Range Multiplier for the Excavation Spelunker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int rangeMultiplier = 5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismBeeShepherd.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismBeeShepherd.java deleted file mode 100644 index 5328d6364..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismBeeShepherd.java +++ /dev/null @@ -1,275 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.data.Ageable; -import org.bukkit.entity.Bee; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class HerbalismBeeShepherd extends SimpleAdaptation { - private final Map lastPulse = new HashMap<>(); - - public HerbalismBeeShepherd() { - super("herbalism-bee-shepherd"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.bee_shepherd.description")); - setDisplayName(Localizer.dLocalize("herbalism.bee_shepherd.name")); - setIcon(Material.BEE_NEST); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(10); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.HONEYCOMB) - .key("challenge_herbalism_bee_100") - .title(Localizer.dLocalize("advancement.challenge_herbalism_bee_100.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_bee_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_bee_100", "herbalism.bee-shepherd.bees-attracted", 100, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("herbalism.bee_shepherd.lore1")); - v.addLore(C.GREEN + "+ " + getGrowthAttempts(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.bee_shepherd.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getPulseMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("herbalism.bee_shepherd.lore3")); - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (!hasAdaptation(p) || !isHoldingFlower(p)) { - continue; - } - - int level = getLevel(p); - if (now - lastPulse.getOrDefault(p.getUniqueId(), 0L) < getPulseMillis(level)) { - continue; - } - - int foodCost = getFoodCost(level); - if (p.getFoodLevel() < foodCost) { - continue; - } - - int grown = pulseGrowth(p, level); - int attracted = pullNearbyBees(p, level); - lastPulse.put(p.getUniqueId(), now); - if (attracted > 0) { - getPlayer(p).getData().addStat("herbalism.bee-shepherd.bees-attracted", attracted); - } - if (grown <= 0) { - continue; - } - - p.setFoodLevel(Math.max(0, p.getFoodLevel() - foodCost)); - if (areParticlesEnabled()) { - p.spawnParticle(Particle.HAPPY_VILLAGER, p.getLocation().add(0, 1, 0), 12, 0.5, 0.4, 0.5, 0.1); - } - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_BEE_POLLINATE, 0.85f, 1.25f); - xp(p, grown * getConfig().xpPerGrowth); - } - } - - private int pulseGrowth(Player p, int level) { - ThreadLocalRandom random = ThreadLocalRandom.current(); - int radius = Math.max(1, (int) Math.round(getRadius(level))); - int grown = 0; - int attempts = getGrowthAttempts(level); - for (int i = 0; i < attempts; i++) { - int dx = random.nextInt(-radius, radius + 1); - int dz = random.nextInt(-radius, radius + 1); - int dy = random.nextInt(-1, 2); - Block block = p.getLocation().getBlock().getRelative(dx, dy, dz); - if (!(block.getBlockData() instanceof Ageable ageable) || ageable.getAge() >= ageable.getMaximumAge()) { - continue; - } - - int increase = Math.max(1, getGrowthStep(level)); - ageable.setAge(Math.min(ageable.getMaximumAge(), ageable.getAge() + increase)); - block.setBlockData(ageable, true); - grown++; - if (getConfig().showGrowthParticles) { - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.COMPOSTER, block.getLocation().add(0.5, 0.5, 0.5), 3, 0.1, 0.1, 0.1, 0.01); - } - } - } - - return grown; - } - - private int pullNearbyBees(Player p, int level) { - double radius = getRadius(level); - int count = 0; - for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), radius, radius, radius)) { - if (!(entity instanceof Bee bee)) { - continue; - } - - Vector toward = p.getLocation().add(0, 0.75, 0).toVector().subtract(bee.getLocation().toVector()); - if (toward.lengthSquared() <= 0.001) { - continue; - } - - toward.normalize().multiply(getBeePullStrength(level)); - bee.setVelocity(bee.getVelocity().multiply(0.6).add(toward)); - bee.setTarget(null); - count++; - } - return count; - } - - private boolean isHoldingFlower(Player p) { - return isFlower(p.getInventory().getItemInMainHand()) || isFlower(p.getInventory().getItemInOffHand()); - } - - private boolean isFlower(ItemStack item) { - if (!isItem(item)) { - return false; - } - - Material type = item.getType(); - return type.name().endsWith("_TULIP") - || type == Material.DANDELION - || type == Material.POPPY - || type == Material.BLUE_ORCHID - || type == Material.ALLIUM - || type == Material.AZURE_BLUET - || type == Material.OXEYE_DAISY - || type == Material.CORNFLOWER - || type == Material.LILY_OF_THE_VALLEY - || type == Material.WITHER_ROSE - || type == Material.SUNFLOWER - || type == Material.LILAC - || type == Material.ROSE_BUSH - || type == Material.PEONY - || type == Material.TORCHFLOWER - || type == Material.PINK_PETALS; - } - - private double getRadius(int level) { - return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); - } - - private int getGrowthAttempts(int level) { - return Math.max(1, (int) Math.round(getConfig().growthAttemptsBase + (getLevelPercent(level) * getConfig().growthAttemptsFactor))); - } - - private int getGrowthStep(int level) { - return Math.max(1, (int) Math.round(getConfig().growthStepBase + (getLevelPercent(level) * getConfig().growthStepFactor))); - } - - private int getFoodCost(int level) { - return Math.max(1, (int) Math.round(getConfig().foodCostBase - (getLevelPercent(level) * getConfig().foodCostFactor))); - } - - private long getPulseMillis(int level) { - return Math.max(250L, (long) Math.round(getConfig().pulseMillisBase - (getLevelPercent(level) * getConfig().pulseMillisFactor))); - } - - private double getBeePullStrength(int level) { - return Math.max(0.01, getConfig().beePullStrengthBase + (getLevelPercent(level) * getConfig().beePullStrengthFactor)); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Holding flowers near crops emits growth pulses and draws nearby bees toward you.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Growth Particles for the Herbalism Bee Shepherd adaptation.", impact = "True enables this behavior and false disables it.") - boolean showGrowthParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.64; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusBase = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Growth Attempts Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double growthAttemptsBase = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Growth Attempts Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double growthAttemptsFactor = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Growth Step Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double growthStepBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Growth Step Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double growthStepFactor = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Food Cost Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double foodCostBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Food Cost Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double foodCostFactor = 1.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Pulse Millis Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pulseMillisBase = 900; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Pulse Millis Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pulseMillisFactor = 650; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bee Pull Strength Base for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double beePullStrengthBase = 0.07; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bee Pull Strength Factor for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double beePullStrengthFactor = 0.14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Growth for the Herbalism Bee Shepherd adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerGrowth = 0.9; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCompostCascade.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCompostCascade.java deleted file mode 100644 index 1a5606ba9..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCompostCascade.java +++ /dev/null @@ -1,548 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.data.Ageable; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Levelled; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; - -import java.util.Locale; -import java.util.concurrent.ThreadLocalRandom; - -public class HerbalismCompostCascade extends SimpleAdaptation { - public HerbalismCompostCascade() { - super("herbalism-compost-cascade"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.compost_cascade.description")); - setDisplayName(Localizer.dLocalize("herbalism.compost_cascade.name")); - setIcon(Material.COMPOSTER); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(600); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.COMPOSTER) - .key("challenge_herbalism_compost_1k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_compost_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_compost_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BONE_MEAL) - .key("challenge_herbalism_compost_25k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_compost_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_compost_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_herbalism_compost_1k", "herbalism.compost-cascade.items-composted", 1000, 300); - registerMilestone("challenge_herbalism_compost_25k", "herbalism.compost-cascade.items-composted", 25000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("herbalism.compost_cascade.lore1")); - v.addLore(C.GREEN + "+ " + getMaxItems(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.compost_cascade.lore2")); - v.addLore(C.GREEN + "+ " + Form.pc(getFillChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.compost_cascade.lore3")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("herbalism.compost_cascade.lore4")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - if (e.isCancelled() || e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getHand() != EquipmentSlot.HAND || e.getClickedBlock() == null) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking() || e.getClickedBlock().getType() != Material.COMPOSTER || p.hasCooldown(Material.COMPOSTER)) { - return; - } - - if (!(e.getClickedBlock().getBlockData() instanceof Levelled levelled)) { - return; - } - - int oldLevel = levelled.getLevel(); - if (oldLevel >= 8) { - return; - } - - int level = getLevel(p); - double fillChance = getFillChance(level); - int maxItems = getMaxItems(level); - double radius = getRadius(level); - Location center = e.getClickedBlock().getLocation().clone().add(0.5, 0.5, 0.5); - World world = center.getWorld(); - if (world == null) { - return; - } - - CompostState state = new CompostState(oldLevel); - - processDroppedItems(world, center, radius, state, maxItems, fillChance); - processMatureCrops(p, world, center, radius, state, maxItems, fillChance); - processLeafBlocks(p, world, center, radius, level, state, maxItems, fillChance); - processInventoryItems(p, state, maxItems, fillChance); - - if (state.consumed <= 0) { - return; - } - - Levelled updated = (Levelled) e.getClickedBlock().getBlockData(); - updated.setLevel(Math.min(8, Math.max(oldLevel, state.compostLevel))); - e.getClickedBlock().setBlockData(updated); - - p.setCooldown(Material.COMPOSTER, getCooldownTicks(level)); - e.setCancelled(true); - - getPlayer(p).getData().addStat("harvest.composted", state.consumed); - getPlayer(p).getData().addStat("herbalism.compost-cascade.items-composted", state.consumed); - xp(p, center, (state.consumed * getConfig().xpPerItemConsumed) + (state.levelGains * getConfig().xpPerLevelGain)); - - SoundPlayer sp = SoundPlayer.of(world); - sp.play(center, Sound.BLOCK_COMPOSTER_FILL, 0.8f, 1.25f); - if (updated.getLevel() >= 8) { - sp.play(center, Sound.BLOCK_COMPOSTER_READY, 1.0f, 1.12f); - } - - dropRewards(world, center, level, oldLevel, updated.getLevel(), state.consumed); - } - - private void processDroppedItems(World world, Location center, double radius, CompostState state, int maxItems, double fillChance) { - if (isComposterDone(state, maxItems)) { - return; - } - - for (Entity entity : world.getNearbyEntities(center, radius, radius, radius)) { - if (!(entity instanceof Item item) || isComposterDone(state, maxItems)) { - continue; - } - - ItemStack stack = item.getItemStack(); - if (!isItem(stack) || !isCompostable(stack.getType())) { - continue; - } - - compostStack(stack, state, maxItems, fillChance); - if (stack.getAmount() <= 0) { - item.remove(); - } else { - item.setItemStack(stack); - } - } - } - - private void processMatureCrops(Player p, World world, Location center, double radius, CompostState state, int maxItems, double fillChance) { - if (isComposterDone(state, maxItems)) { - return; - } - - int r = Math.max(1, (int) Math.ceil(radius)); - double rs = radius * radius; - for (int x = -r; x <= r; x++) { - for (int y = -r; y <= r; y++) { - for (int z = -r; z <= r; z++) { - if (isComposterDone(state, maxItems)) { - return; - } - - if ((x * x) + (y * y) + (z * z) > rs) { - continue; - } - - Block b = world.getBlockAt(center.getBlockX() + x, center.getBlockY() + y, center.getBlockZ() + z); - if (!isMatureCrop(b)) { - continue; - } - - if (!canBlockBreak(p, b.getLocation()) || !canBlockPlace(p, b.getLocation())) { - continue; - } - - ItemStack[] drops = b.getDrops().toArray(ItemStack[]::new); - if (!replantCrop(b)) { - continue; - } - - for (ItemStack drop : drops) { - if (!isItem(drop)) { - continue; - } - - if (isCompostable(drop.getType()) && !isComposterDone(state, maxItems)) { - compostStack(drop, state, maxItems, fillChance); - } - - if (drop.getAmount() > 0) { - world.dropItemNaturally(b.getLocation().add(0.5, 0.5, 0.5), drop); - } - } - } - } - } - } - - private void processLeafBlocks(Player p, World world, Location center, double radius, int level, CompostState state, int maxItems, double fillChance) { - if (isComposterDone(state, maxItems)) { - return; - } - - int r = Math.max(1, (int) Math.ceil(radius)); - double rs = radius * radius; - int bursts = getLeafCompostBursts(level); - for (int x = -r; x <= r; x++) { - for (int y = -r; y <= r; y++) { - for (int z = -r; z <= r; z++) { - if (isComposterDone(state, maxItems)) { - return; - } - - if ((x * x) + (y * y) + (z * z) > rs) { - continue; - } - - Block b = world.getBlockAt(center.getBlockX() + x, center.getBlockY() + y, center.getBlockZ() + z); - if (!isLeafBlock(b.getType()) || !canBlockBreak(p, b.getLocation())) { - continue; - } - - b.setType(Material.AIR, false); - ItemStack leafMass = new ItemStack(Material.OAK_LEAVES, bursts); - compostStack(leafMass, state, maxItems, getLeafFillChance(level, fillChance)); - } - } - } - } - - private void processInventoryItems(Player p, CompostState state, int maxItems, double fillChance) { - if (isComposterDone(state, maxItems)) { - return; - } - - ItemStack[] storage = p.getInventory().getStorageContents(); - boolean changed = false; - for (int i = 0; i < storage.length; i++) { - if (isComposterDone(state, maxItems)) { - break; - } - - ItemStack stack = storage[i]; - if (!isItem(stack) || !isCompostable(stack.getType())) { - continue; - } - - compostStack(stack, state, maxItems, fillChance); - changed = true; - if (stack.getAmount() <= 0) { - storage[i] = null; - } - } - - if (changed) { - p.getInventory().setStorageContents(storage); - } - } - - private void compostStack(ItemStack stack, CompostState state, int maxItems, double fillChance) { - while (stack.getAmount() > 0 && !isComposterDone(state, maxItems)) { - stack.setAmount(stack.getAmount() - 1); - state.processed++; - state.consumed++; - - if (ThreadLocalRandom.current().nextDouble() <= fillChance) { - state.compostLevel++; - state.levelGains++; - } - } - } - - private void dropRewards(World world, Location center, int level, int oldLevel, int newLevel, int consumed) { - int boneMeal = getBaseBoneMeal(level) + Math.max(0, consumed / getItemsPerBoneMeal(level)); - if (newLevel >= 8 && oldLevel < 8) { - boneMeal += getReadyBonusBoneMeal(level); - } - - if (boneMeal > 0) { - world.dropItemNaturally(center, new ItemStack(Material.BONE_MEAL, Math.min(64, boneMeal))); - } - - if (newLevel < 8) { - return; - } - - int rolls = getValuableRolls(level); - for (int i = 0; i < rolls; i++) { - if (ThreadLocalRandom.current().nextDouble() <= getValuableChance(level)) { - world.dropItemNaturally(center, rollValuableReward(level)); - } - } - } - - private ItemStack rollValuableReward(int level) { - double lp = getLevelPercent(level); - double r = ThreadLocalRandom.current().nextDouble(); - - if (r < 0.45) { - return new ItemStack(Material.MOSS_BLOCK, 1 + ThreadLocalRandom.current().nextInt(1 + Math.max(1, (int) Math.round(lp * 3)))); - } - - if (r < 0.7) { - return new ItemStack(Material.GLOW_BERRIES, 2 + ThreadLocalRandom.current().nextInt(2 + Math.max(1, (int) Math.round(lp * 4)))); - } - - if (r < 0.88) { - return new ItemStack(Material.AMETHYST_SHARD, 1 + ThreadLocalRandom.current().nextInt(1 + Math.max(1, (int) Math.round(lp * 4)))); - } - - if (r < 0.97) { - return new ItemStack(Material.EMERALD, 1); - } - - return new ItemStack(Material.DIAMOND, 1); - } - - private boolean isMatureCrop(Block b) { - BlockData data = b.getBlockData(); - if (!(data instanceof Ageable ageable)) { - return false; - } - - Material type = b.getType(); - if (type == Material.CHORUS_PLANT || type == Material.SUGAR_CANE || type == Material.BAMBOO) { - return false; - } - - return ageable.getAge() >= ageable.getMaximumAge(); - } - - private boolean replantCrop(Block b) { - BlockData data = b.getBlockData(); - if (!(data instanceof Ageable ageable)) { - return false; - } - - ageable.setAge(0); - b.setBlockData(ageable, true); - return true; - } - - private boolean isLeafBlock(Material type) { - return type.name().endsWith("_LEAVES"); - } - - private boolean isComposterDone(CompostState state, int maxItems) { - return state.compostLevel >= 8 || state.processed >= maxItems; - } - - private boolean isCompostable(Material type) { - String n = type.name().toUpperCase(Locale.ROOT); - return n.contains("SEEDS") - || n.contains("SAPLING") - || n.contains("LEAVES") - || n.contains("FLOWER") - || n.contains("MUSHROOM") - || n.contains("ROOTS") - || n.contains("VINE") - || n.contains("KELP") - || n.contains("DRIPLEAF") - || n.contains("MOSS") - || type == Material.WHEAT - || type == Material.BEETROOT - || type == Material.CARROT - || type == Material.POTATO - || type == Material.POISONOUS_POTATO - || type == Material.NETHER_WART - || type == Material.CACTUS - || type == Material.SUGAR_CANE - || type == Material.BAMBOO - || type == Material.SHORT_GRASS - || type == Material.TALL_GRASS - || type == Material.SEA_PICKLE; - } - - private int getMaxItems(int level) { - return Math.max(1, (int) Math.round(getConfig().maxItemsBase + (getLevelPercent(level) * getConfig().maxItemsFactor))); - } - - private double getRadius(int level) { - return Math.max(1, getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor)); - } - - private double getFillChance(int level) { - return Math.min(getConfig().maxFillChance, getConfig().fillChanceBase + (getLevelPercent(level) * getConfig().fillChanceFactor)); - } - - private double getLeafFillChance(int level, double baseFillChance) { - return Math.min(1.0, baseFillChance * (getConfig().leafFillChanceMultiplierBase + (getLevelPercent(level) * getConfig().leafFillChanceMultiplierFactor))); - } - - private int getLeafCompostBursts(int level) { - return Math.max(1, (int) Math.round(getConfig().leafCompostBurstsBase + (getLevelPercent(level) * getConfig().leafCompostBurstsFactor))); - } - - private int getCooldownTicks(int level) { - return Math.max(4, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksReduction))); - } - - private int getBaseBoneMeal(int level) { - return Math.max(0, (int) Math.round(getConfig().boneMealBase + (getLevelPercent(level) * getConfig().boneMealFactor))); - } - - private int getReadyBonusBoneMeal(int level) { - return Math.max(0, (int) Math.round(getConfig().readyBonusBoneMealBase + (getLevelPercent(level) * getConfig().readyBonusBoneMealFactor))); - } - - private int getItemsPerBoneMeal(int level) { - return Math.max(1, (int) Math.round(getConfig().itemsPerBoneMealBase - (getLevelPercent(level) * getConfig().itemsPerBoneMealReduction))); - } - - private double getValuableChance(int level) { - return Math.min(getConfig().maxValuableChance, getConfig().valuableChanceBase + (getLevelPercent(level) * getConfig().valuableChanceFactor)); - } - - private int getValuableRolls(int level) { - return Math.max(0, (int) Math.round(getConfig().valuableRollsBase + (getLevelPercent(level) * getConfig().valuableRollsFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-right-click a composter to process nearby drops, crops, leaves, and your own compostables.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusBase = 5.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 12.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Items Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxItemsBase = 80.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Items Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxItemsFactor = 240.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fill Chance Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fillChanceBase = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fill Chance Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fillChanceFactor = 0.42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Fill Chance for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxFillChance = 0.98; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Leaf Compost Bursts Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double leafCompostBurstsBase = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Leaf Compost Bursts Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double leafCompostBurstsFactor = 9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Leaf Fill Chance Multiplier Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double leafFillChanceMultiplierBase = 1.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Leaf Fill Chance Multiplier Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double leafFillChanceMultiplierFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 36.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Reduction for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksReduction = 28.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bone Meal Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double boneMealBase = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bone Meal Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double boneMealFactor = 6.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ready Bonus Bone Meal Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double readyBonusBoneMealBase = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ready Bonus Bone Meal Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double readyBonusBoneMealFactor = 8.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Items Per Bone Meal Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double itemsPerBoneMealBase = 20.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Items Per Bone Meal Reduction for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double itemsPerBoneMealReduction = 14.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Valuable Chance Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double valuableChanceBase = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Valuable Chance Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double valuableChanceFactor = 0.09; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Valuable Chance for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxValuableChance = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Valuable Rolls Base for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double valuableRollsBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Valuable Rolls Factor for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double valuableRollsFactor = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Item Consumed for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerItemConsumed = 1.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Level Gain for the Herbalism Compost Cascade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerLevelGain = 2.8; - } - - private static class CompostState { - private int compostLevel; - private int processed; - private int consumed; - private int levelGains; - - private CompostState(int compostLevel) { - this.compostLevel = compostLevel; - this.processed = 0; - this.consumed = 0; - this.levelGains = 0; - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCraftableCobweb.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCraftableCobweb.java deleted file mode 100644 index 5ab6e7315..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCraftableCobweb.java +++ /dev/null @@ -1,124 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public class HerbalismCraftableCobweb extends SimpleAdaptation { - - public HerbalismCraftableCobweb() { - super("herbalism-cobweb"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.cobweb.description")); - setDisplayName(Localizer.dLocalize("herbalism.cobweb.name")); - setIcon(Material.STRING); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(17771); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shaped() - .key("herbalism-cobwebBlock") - .ingredient(new MaterialChar('I', Material.STRING)) - .shapes(List.of( - "III", - "III", - "III")) - .result(new ItemStack(Material.COBWEB, 1)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.COBWEB) - .key("challenge_herbalism_cobweb_100") - .title(Localizer.dLocalize("advancement.challenge_herbalism_cobweb_100.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_cobweb_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_cobweb_100", "herbalism.cobweb.cobwebs-crafted", 100, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.cobweb.lore1")); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getWhoClicked() instanceof Player p) || !hasAdaptation(p)) { - return; - } - if (e.getRecipe() instanceof org.bukkit.inventory.ShapedRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && recipe.getKey().getKey().equals("herbalism-cobwebBlock")) { - getPlayer(p).getData().addStat("herbalism.cobweb.cobwebs-crafted", 1); - } - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Cobwebs from String in a Crafting Table.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCraftableMushroomBlocks.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCraftableMushroomBlocks.java deleted file mode 100644 index 03c6a6543..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismCraftableMushroomBlocks.java +++ /dev/null @@ -1,141 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public class HerbalismCraftableMushroomBlocks extends SimpleAdaptation { - - public HerbalismCraftableMushroomBlocks() { - super("herbalism-mushroom-blocks"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.mushroom_blocks.description")); - setDisplayName(Localizer.dLocalize("herbalism.mushroom_blocks.name")); - setIcon(Material.BROWN_MUSHROOM_BLOCK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(17772); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shaped() - .key("herbalism-redmushblock") - .ingredient(new MaterialChar('I', Material.RED_MUSHROOM)) - .shapes(List.of( - "II", - "II")) - .result(new ItemStack(Material.RED_MUSHROOM_BLOCK, 1)) - .build()); - registerRecipe(AdaptRecipe.shaped() - .key("herbalism-brownmushblock") - .ingredient(new MaterialChar('I', Material.BROWN_MUSHROOM)) - .shapes(List.of( - "II", - "II")) - .result(new ItemStack(Material.BROWN_MUSHROOM_BLOCK, 1)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("herbalism-mushstemred") - .ingredient(Material.RED_MUSHROOM_BLOCK) - .result(new ItemStack(Material.MUSHROOM_STEM, 1)) - .build()); - registerRecipe(AdaptRecipe.shapeless() - .key("herbalism-mushstembrown") - .ingredient(Material.BROWN_MUSHROOM_BLOCK) - .result(new ItemStack(Material.MUSHROOM_STEM, 1)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.RED_MUSHROOM_BLOCK) - .key("challenge_herbalism_mushroom_100") - .title(Localizer.dLocalize("advancement.challenge_herbalism_mushroom_100.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_mushroom_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_mushroom_100", "herbalism.mushroom-blocks.crafted", 100, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.mushroom_blocks.lore1")); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getWhoClicked() instanceof Player p) || !hasAdaptation(p)) { - return; - } - if (e.getRecipe() instanceof org.bukkit.inventory.ShapedRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && (recipe.getKey().getKey().equals("herbalism-redmushblock") || recipe.getKey().getKey().equals("herbalism-brownmushblock"))) { - getPlayer(p).getData().addStat("herbalism.mushroom-blocks.crafted", 1); - } - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Mushroom Blocks from Mushrooms in a Crafting Table.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismDropToInventory.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismDropToInventory.java deleted file mode 100644 index 5af2d3cec..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismDropToInventory.java +++ /dev/null @@ -1,130 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockDropItemEvent; - -import java.util.List; - -public class HerbalismDropToInventory extends SimpleAdaptation { - public HerbalismDropToInventory() { - super("herbalism-drop-to-inventory"); - registerConfiguration(HerbalismDropToInventory.Config.class); - setDescription(Localizer.dLocalize("pickaxe.drop_to_inventory.description")); - setDisplayName(Localizer.dLocalize("herbalism.drop_to_inventory.name")); - setIcon(Material.HOPPER); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(7999); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_herbalism_dti_10k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_dti_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_dti_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_dti_10k", "herbalism.drop-to-inv.items-caught", 10000, 500); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("pickaxe.drop_to_inventory.lore1")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockDropItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - if (p.getGameMode() != GameMode.SURVIVAL) { - return; - } - if (ItemListings.toolHoes.contains(p.getInventory().getItemInMainHand().getType())) { - List items = new KList<>(e.getItems()); - e.getItems().clear(); - for (Item i : items) { - sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); - xp(p, 2); - getPlayer(p).getData().addStat("herbalism.drop-to-inv.items-caught", 1); - if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { - p.getWorld().dropItem(p.getLocation(), i.getItemStack()); - } - } - } - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Harvested crops drop directly into your inventory.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismGrowthAura.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismGrowthAura.java deleted file mode 100644 index a07a67a25..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismGrowthAura.java +++ /dev/null @@ -1,196 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Particles; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.data.Ageable; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; - -import java.util.concurrent.ThreadLocalRandom; - -public class HerbalismGrowthAura extends SimpleAdaptation { - public HerbalismGrowthAura() { - super("herbalism-growth-aura"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.growth_aura.description")); - setDisplayName(Localizer.dLocalize("herbalism.growth_aura.name")); - setIcon(Material.BONE_MEAL); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(850); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WHEAT) - .key("challenge_herbalism_growth_1k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_growth_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_growth_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.HAY_BLOCK) - .key("challenge_herbalism_growth_25k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_growth_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_growth_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_herbalism_growth_1k", "herbalism.growth-aura.blocks-grown", 1000, 300); - registerMilestone("challenge_herbalism_growth_25k", "herbalism.growth-aura.blocks-grown", 25000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRadius(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.growth_aura.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getStrength(level), 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.growth_aura.lore2")); - v.addLore(C.YELLOW + "+ " + Form.f(getFoodCost(getLevelPercent(level)), 2) + C.GRAY + " " + Localizer.dLocalize("herbalism.growth_aura.lore3")); - } - - private double getRadius(double factor) { - return factor * getConfig().radiusFactor; - } - - private double getStrength(int level) { - return level * getConfig().strengthFactor; - } - - private double getFoodCost(double factor) { - return M.lerp(1D - factor, getConfig().maxFoodCost, getConfig().minFoodCost); - } - - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - try { - if (hasAdaptation(p)) { - double rad = getRadius(getLevelPercent(p)); - double strength = getStrength(getLevel(p)); - ThreadLocalRandom random = ThreadLocalRandom.current(); - double angle = Math.toRadians(random.nextDouble(360D)); - double foodCost = getFoodCost(getLevelPercent(p)); - - - for (int i = 0; i < Math.min(Math.min(rad * rad, 256), 3); i++) { - Location m = p.getLocation().clone().add(new Vector(Math.sin(angle), RNG.r.i(-1, 1), Math.cos(angle)).multiply(random.nextDouble(rad))); - Block a = m.getBlock(); - if (getConfig().surfaceOnly) { - int max = a.getWorld().getHighestBlockYAt(m); - - if (max + 1 != a.getY()) - continue; - } - - SoundPlayer spw = SoundPlayer.of(a.getWorld()); - if (a.getBlockData() instanceof Ageable) { - Ageable ab = (Ageable) a.getBlockData(); - int toGrowLeft = ab.getMaximumAge() - ab.getAge(); - - if (toGrowLeft > 0) { - int add = (int) Math.max(1, Math.min(strength, toGrowLeft)); - AdaptPlayer player = getPlayer(p); - if (ab.getMaximumAge() > ab.getAge() && player.canConsumeFood(foodCost, 10)) { - while (add-- > 0) { - J.s(() -> { - if (!p.isOnline() - || !player.consumeFood(foodCost, 10) - || !(a.getBlockData() instanceof Ageable aab) - || aab.getAge() == aab.getMaximumAge()) - return; - - aab.setAge(aab.getAge() + 1); - a.setBlockData(aab, true); - getPlayer(p).getData().addStat("herbalism.growth-aura.blocks-grown", 1); - spw.play(a.getLocation(), Sound.BLOCK_CHORUS_FLOWER_DEATH, 0.25f, RNG.r.f(0.3f, 0.7f)); - if (areParticlesEnabled()) { - p.spawnParticle(Particles.VILLAGER_HAPPY, a.getLocation().clone().add(0.5, 0.5, 0.5), 3, 0.3, 0.3, 0.3, 0.9); - } -// xp(p, 1); // JESUS THIS IS FUCKING BUSTED - }, RNG.r.i(30, 60)); - } - } - } - - - } - } - } - } catch (Throwable e) { - e.printStackTrace(); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Grow nature around you in an aura at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Herbalism Growth Aura adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Surface Only for the Herbalism Growth Aura adaptation.", impact = "True enables this behavior and false disables it.") - boolean surfaceOnly = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.325; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Food Cost for the Herbalism Growth Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minFoodCost = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Food Cost for the Herbalism Growth Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxFoodCost = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Herbalism Growth Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Strength Factor for the Herbalism Growth Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double strengthFactor = 0.75; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismHungryHippo.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismHungryHippo.java deleted file mode 100644 index e104b75ba..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismHungryHippo.java +++ /dev/null @@ -1,121 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerItemConsumeEvent; - -public class HerbalismHungryHippo extends SimpleAdaptation { - - public HerbalismHungryHippo() { - super("herbalism-hippo"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.hippo.description")); - setDisplayName(Localizer.dLocalize("herbalism.hippo.name")); - setIcon(Material.POTATO); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(8111); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_APPLE) - .key("challenge_herbalism_hippo_500") - .title(Localizer.dLocalize("advancement.challenge_herbalism_hippo_500.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_hippo_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_hippo_500", "herbalism.hungry-hippo.bonus-saturation", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ (" + (2 + level) + C.GRAY + " + " + Localizer.dLocalize("herbalism.hippo.lore1")); - } - - @EventHandler(priority = EventPriority.NORMAL) - public void on(PlayerItemConsumeEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - if (ItemListings.getFood().contains(e.getItem().getType())) { - p.setFoodLevel(p.getFoodLevel() + 2 + getLevel(p)); - sp.play(p.getLocation(), Sound.BLOCK_POINTED_DRIPSTONE_LAND, 1, 0.25f); - vfxFastRing(p.getLocation().add(0, 0.25, 0), 2, Color.GREEN); - getPlayer(p).getData().addStat("herbalism.hungry-hippo.bonus-saturation", 2 + getLevel(p)); - xp(p, 5); - } - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Consuming food gives you more saturation.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismHungryShield.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismHungryShield.java deleted file mode 100644 index 9e79c27b5..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismHungryShield.java +++ /dev/null @@ -1,136 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; - -public class HerbalismHungryShield extends SimpleAdaptation { - - public HerbalismHungryShield() { - super("herbalism-hungry-shield"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.hungry_shield.description")); - setDisplayName(Localizer.dLocalize("herbalism.hungry_shield.name")); - setIcon(Material.APPLE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(875); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BREAD) - .key("challenge_herbalism_shield_500") - .title(Localizer.dLocalize("advancement.challenge_herbalism_shield_500.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_shield_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GOLDEN_APPLE) - .key("challenge_herbalism_shield_5k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_shield_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_shield_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_herbalism_shield_500", "herbalism.hungry-shield.damage-absorbed", 500, 400); - registerMilestone("challenge_herbalism_shield_5k", "herbalism.hungry-shield.damage-absorbed", 5000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getEffectiveness(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.hungry_shield.lore1")); - } - - - @Override - public void onTick() { - - } - - private double getEffectiveness(double factor) { - return Math.min(getConfig().maxEffectiveness, factor * factor + getConfig().effectivenessBase); - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof Player p && hasAdaptation(p)) { - double f = getEffectiveness(getLevelPercent(p)); - double h = e.getDamage() * f; - double d = e.getDamage() - h; - - if (getPlayer(p).consumeFood(h, 6)) { - d += h; - e.setDamage(d); - getPlayer(p).getData().addStat("herbalism.hungry-shield.damage-absorbed", (int) Math.ceil(h)); - xp(p, d); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Take damage to your hunger before your health.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.78; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effectiveness Base for the Herbalism Hungry Shield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double effectivenessBase = 0.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Effectiveness for the Herbalism Hungry Shield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxEffectiveness = 0.95; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismLuck.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismLuck.java deleted file mode 100644 index 68722aec8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismLuck.java +++ /dev/null @@ -1,158 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.Materials; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockDropItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.concurrent.ThreadLocalRandom; - -public class HerbalismLuck extends SimpleAdaptation { - - public HerbalismLuck() { - super("herbalism-luck"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.luck.description")); - setDisplayName(Localizer.dLocalize("herbalism.luck.name")); - setIcon(Material.EMERALD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(8121); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.RABBIT_FOOT) - .key("challenge_herbalism_luck_100") - .title(Localizer.dLocalize("advancement.challenge_herbalism_luck_100.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_luck_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.EMERALD) - .key("challenge_herbalism_luck_2500") - .title(Localizer.dLocalize("advancement.challenge_herbalism_luck_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_luck_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_herbalism_luck_100", "herbalism.luck.lucky-drops", 100, 300); - registerMilestone("challenge_herbalism_luck_2500", "herbalism.luck.lucky-drops", 2500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.luck.lore0")); - v.addLore(C.GREEN + "+ (" + (getEffectiveness(level)) + C.GRAY + "%) + " + Localizer.dLocalize("herbalism.luck.lore1")); - v.addLore(C.GREEN + "+ (" + (getEffectiveness(level)) + C.GRAY + "%) + " + Localizer.dLocalize("herbalism.luck.lore2")); - } - - private double getEffectiveness(double factor) { - return Math.min(getConfig().highChance, factor * factor + getConfig().lowChance); - } - - @EventHandler(priority = EventPriority.NORMAL) - public void on(BlockDropItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - - Block broken = e.getBlock(); - if (broken.getType() == Materials.GRASS || broken.getType() == Material.TALL_GRASS) { - var d = ThreadLocalRandom.current().nextDouble(100D); - Material m = ItemListings.getHerbalLuckSeeds().getRandom(); - if (d < getEffectiveness(getLevel(p))) { - xp(p, 100); - getPlayer(p).getData().addStat("herbalism.luck.lucky-drops", 1); - ItemStack luckDrop = new ItemStack(m, 1); - e.getBlock().getWorld().dropItem(e.getBlock().getLocation(), luckDrop); - } - } - - if (ItemListings.getFlowers().contains(broken.getType())) { - var d = ThreadLocalRandom.current().nextDouble(100D); - Material m = ItemListings.getHerbalLuckFood().getRandom(); - if (d < getEffectiveness(getLevel(p))) { - xp(p, 100); - getPlayer(p).getData().addStat("herbalism.luck.lucky-drops", 1); - ItemStack luckDrop = new ItemStack(m, 1); - e.getBlock().getWorld().dropItem(e.getBlock().getLocation(), luckDrop); - } - } - - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Breaking Grass or Flowers has a chance to drop random items.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Low Chance for the Herbalism Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double lowChance = 0.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls High Chance for the Herbalism Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double highChance = 90; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismMyconid.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismMyconid.java deleted file mode 100644 index 9d72582a1..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismMyconid.java +++ /dev/null @@ -1,119 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -public class HerbalismMyconid extends SimpleAdaptation { - - public HerbalismMyconid() { - super("herbalism-myconid"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.myconid.description")); - setDisplayName(Localizer.dLocalize("herbalism.myconid.name")); - setIcon(Material.MYCELIUM); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(17771); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shapeless() - .key("herbalism-dirt-myconid") - .ingredient(Material.DIRT) - .ingredient(Material.RED_MUSHROOM) - .ingredient(Material.BROWN_MUSHROOM) - .result(new ItemStack(Material.MYCELIUM, 1)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.MYCELIUM) - .key("challenge_herbalism_myconid_100") - .title(Localizer.dLocalize("advancement.challenge_herbalism_myconid_100.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_myconid_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_myconid_100", "herbalism.myconid.mycelium-crafted", 100, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.myconid.lore1")); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getWhoClicked() instanceof Player p) || !hasAdaptation(p)) { - return; - } - if (e.getRecipe() instanceof org.bukkit.inventory.ShapelessRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && recipe.getKey().getKey().equals("herbalism-dirt-myconid")) { - getPlayer(p).getData().addStat("herbalism.myconid.mycelium-crafted", 1); - } - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Mycelium from Dirt and Mushrooms.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismReplant.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismReplant.java deleted file mode 100644 index d528bb723..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismReplant.java +++ /dev/null @@ -1,236 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.content.skill.SkillHerbalism; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.reflect.registries.Particles; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.data.Ageable; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.Collection; - -public class HerbalismReplant extends SimpleAdaptation { - - public HerbalismReplant() { - super("herbalism-replant"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.replant.description")); - setDisplayName(Localizer.dLocalize("herbalism.replant.name")); - setIcon(Material.PUMPKIN_SEEDS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(6090); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WHEAT_SEEDS) - .key("challenge_herbalism_replant_500") - .title(Localizer.dLocalize("advancement.challenge_herbalism_replant_500.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_replant_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.COMPOSTER) - .key("challenge_herbalism_replant_25k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_replant_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_replant_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_herbalism_replant_500", "herbalism.replant.crops-replanted", 500, 300); - registerMilestone("challenge_herbalism_replant_25k", "herbalism.replant.crops-replanted", 25000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getRadius(level) + C.GRAY + Localizer.dLocalize("herbalism.replant.lore1")); - } - - - private int getCooldown(double factor, int level) { - if (level == 1) { - return (int) getConfig().cooldownLvl1; - } - - return (int) ((getConfig().baseCooldown - (getConfig().cooldownFactor * factor)) + getConfig().bonusCooldown); - } - - private float getRadius(int lvl) { - return lvl - getConfig().radiusSub; - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - if (e.getClickedBlock() == null) { - return; - } - if (!e.getAction().equals(Action.RIGHT_CLICK_BLOCK)) { // you need to right-click to harvest! - return; - } - - if (!(e.getClickedBlock().getBlockData() instanceof Ageable)) { - return; - } - - int lvl = getLevel(p); - - if (lvl > 0) { - ItemStack right = p.getInventory().getItemInMainHand(); - ItemStack left = p.getInventory().getItemInOffHand(); - - if (isTool(left) && isHoe(left) && !p.hasCooldown(left.getType())) { - damageOffHand(p, 1 + ((lvl - 1) * 7)); - p.setCooldown(left.getType(), getCooldown(getLevelPercent(p), getLevel(p))); - } else if (isTool(right) && isHoe(right) && !p.hasCooldown(right.getType())) { - damageHand(p, 1 + ((lvl - 1) * 7)); - p.setCooldown(right.getType(), getCooldown(getLevelPercent(p), getLevel(p))); - } else { - return; - } - - if (lvl > 1) { - Cuboid c = new Cuboid(e.getClickedBlock().getLocation().clone().add(0.5, 0.5, 0.5)); - c = c.expand(Cuboid.CuboidDirection.Up, (int) Math.floor(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.Down, (int) Math.floor(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.North, Math.round(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.South, Math.round(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.East, Math.round(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.West, Math.round(getRadius(lvl))); - - for (Block i : c) { - J.s(() -> hit(p, i), M.irand(1, 6)); - } - spw.play(p.getLocation(), Sound.ITEM_SHOVEL_FLATTEN, 1f, 0.66f); - spw.play(p.getLocation(), Sound.BLOCK_BAMBOO_SAPLING_BREAK, 1f, 0.66f); - if (areParticlesEnabled()) { - p.spawnParticle(Particles.VILLAGER_HAPPY, p.getLocation().clone().add(0.5, 0.5, 0.5), getLevel(p) * 3, 0.3 * getLevel(p), 0.3 * getLevel(p), 0.3 * getLevel(p), 0.9); - } - } else { - hit(p, e.getClickedBlock()); - } - } - } - - private void hit(Player p, Block b) { - if (b != null && b.getBlockData() instanceof Ageable aa && hasAdaptation(p)) { - if (aa.getAge() != aa.getMaximumAge()) { - return; - } - - xp(p, b.getLocation().clone().add(0.5, 0.5, 0.5), ((SkillHerbalism.Config) getSkill().getConfig()).harvestPerAgeXP * aa.getAge()); - xp(p, b.getLocation().clone().add(0.5, 0.5, 0.5), ((SkillHerbalism.Config) getSkill().getConfig()).plantCropSeedsXP); - PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("herbalism"); - PlayerAdaptation adaptation = line != null ? line.getAdaptation("herbalism-drop-to-inventory") : null; - if (adaptation != null && adaptation.getLevel() > 0) { - Collection items = b.getDrops(); - SoundPlayer sp = SoundPlayer.of(p); - for (ItemStack i : items) { - sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); - i.setAmount(1); - if (!p.getInventory().addItem(i).isEmpty()) { - p.getWorld().dropItem(p.getLocation(), i); - } - } - aa.setAge(0); - J.s(() -> b.setBlockData(aa, true)); - - } else { - p.breakBlock(b); - } - - aa.setAge(0); - J.s(() -> b.setBlockData(aa, true)); - - getPlayer(p).getData().addStat("harvest.blocks", 1); - getPlayer(p).getData().addStat("harvest.planted", 1); - getPlayer(p).getData().addStat("herbalism.replant.crops-replanted", 1); - - if (M.r(1D / (double) getLevel(p))) { - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(b.getLocation(), Sound.ITEM_CROP_PLANT, 1f, 0.7f); - } - } - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Right-click a crop with a hoe to harvest and replant it.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Herbalism Replant adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.95; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Lvl1 for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownLvl1 = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Cooldown for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseCooldown = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Factor for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownFactor = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Cooldown for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusCooldown = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Sub for the Herbalism Replant adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int radiusSub = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismRootedFooting.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismRootedFooting.java deleted file mode 100644 index df33c44fd..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismRootedFooting.java +++ /dev/null @@ -1,175 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerInteractEvent; - -public class HerbalismRootedFooting extends SimpleAdaptation { - public HerbalismRootedFooting() { - super("herbalism-rooted-footing"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.rooted_footing.description")); - setDisplayName(Localizer.dLocalize("herbalism.rooted_footing.name")); - setIcon(Material.FARMLAND); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2050); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FARMLAND) - .key("challenge_herbalism_rooted_500") - .title(Localizer.dLocalize("advancement.challenge_herbalism_rooted_500.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_rooted_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_rooted_500", "herbalism.rooted-footing.farmland-saved", 500, 300); - } - - @Override - public void addStats(int level, Element v) { - double absorb = getFallAbsorb(level); - v.addLore(C.GREEN + "+ " + Form.pc(absorb, 0) + C.GRAY + " " + Localizer.dLocalize("herbalism.rooted_footing.lore1")); - v.addLore(C.YELLOW + "* " + getConfig().foodPerDamage + C.GRAY + " " + Localizer.dLocalize("herbalism.rooted_footing.lore2")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("herbalism.rooted_footing.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - if (e.isCancelled()) { - return; - } - - if (e.getAction() != Action.PHYSICAL || e.getClickedBlock() == null || !(e.getPlayer() instanceof Player p)) { - return; - } - - if (!hasAdaptation(p)) { - return; - } - - if (e.getClickedBlock().getType() == Material.FARMLAND) { - e.setCancelled(true); - getPlayer(p).getData().addStat("herbalism.rooted-footing.farmland-saved", 1); - } - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(EntityDamageEvent e) { - if (e.isCancelled() || !(e.getEntity() instanceof Player p) || e.getCause() != EntityDamageEvent.DamageCause.FALL) { - return; - } - - if (!hasAdaptation(p) || !isNatureGround(p)) { - return; - } - - double absorbCap = e.getDamage() * getFallAbsorb(getLevel(p)); - int foodRequired = (int) Math.ceil(absorbCap * getConfig().foodPerDamage); - if (foodRequired <= 0 || p.getFoodLevel() <= 0) { - return; - } - - int usableFood = Math.min(p.getFoodLevel(), foodRequired); - double absorbed = usableFood / getConfig().foodPerDamage; - if (absorbed <= 0) { - return; - } - - p.setFoodLevel(Math.max(0, p.getFoodLevel() - usableFood)); - e.setDamage(Math.max(0, e.getDamage() - absorbed)); - if (e.getDamage() <= 0.01) { - e.setCancelled(true); - } - } - - private boolean isNatureGround(Player p) { - Block under = p.getLocation().clone().add(0, -1, 0).getBlock(); - Material type = under.getType(); - return type == Material.FARMLAND - || type == Material.GRASS_BLOCK - || type == Material.MOSS_BLOCK - || type == Material.MYCELIUM - || type == Material.DIRT - || type == Material.ROOTED_DIRT; - } - - private double getFallAbsorb(int level) { - return Math.min(getConfig().maxAbsorbPercent, getConfig().absorbBase + (getLevelPercent(level) * getConfig().absorbFactor)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Protect farmland and convert fall damage into hunger on natural ground.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Absorb Base for the Herbalism Rooted Footing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double absorbBase = 0.28; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Absorb Factor for the Herbalism Rooted Footing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double absorbFactor = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Absorb Percent for the Herbalism Rooted Footing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxAbsorbPercent = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Food Per Damage for the Herbalism Rooted Footing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double foodPerDamage = 1.8; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismSeedSower.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismSeedSower.java deleted file mode 100644 index 5fd29700f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismSeedSower.java +++ /dev/null @@ -1,243 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; - -public class HerbalismSeedSower extends SimpleAdaptation { - public HerbalismSeedSower() { - super("herbalism-seed-sower"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.seed_sower.description")); - setDisplayName(Localizer.dLocalize("herbalism.seed_sower.name")); - setIcon(Material.WHEAT_SEEDS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(6920); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WHEAT_SEEDS) - .key("challenge_herbalism_seed_1k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_seed_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_seed_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.FARMLAND) - .key("challenge_herbalism_seed_25k") - .title(Localizer.dLocalize("advancement.challenge_herbalism_seed_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_seed_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_herbalism_seed_1k", "herbalism.seed-sower.seeds-planted", 1000, 300); - registerMilestone("challenge_herbalism_seed_25k", "herbalism.seed-sower.seeds-planted", 25000, 1000); - } - - @Override - public void addStats(int level, Element v) { - double factor = getLevelPercent(level); - v.addLore(C.GREEN + "+ " + getRadius(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.seed_sower.lore1")); - v.addLore(C.GREEN + "+ " + getMaxCrops(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.seed_sower.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(factor) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("herbalism.seed_sower.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - if (e.isCancelled()) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking()) { - return; - } - - if (e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getHand() != EquipmentSlot.HAND || e.getClickedBlock() == null) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (!isItem(hand)) { - return; - } - - Material seedType = hand.getType(); - Material cropType = getCropType(seedType); - if (cropType == null || p.hasCooldown(seedType)) { - return; - } - - int planted = plantNearby(p, e.getClickedBlock(), hand, seedType, cropType, getRadius(getLevel(p)), getMaxCrops(getLevel(p))); - if (planted <= 0) { - return; - } - - e.setCancelled(true); - p.setCooldown(seedType, getCooldownTicks(getLevelPercent(p))); - getPlayer(p).getData().addStat("harvest.planted", planted); - getPlayer(p).getData().addStat("herbalism.seed-sower.seeds-planted", planted); - xp(p, planted * getConfig().xpPerCrop); - - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.ITEM_CROP_PLANT, 0.6f, 1.25f); - if (planted > 4) { - sp.play(p.getLocation(), Sound.BLOCK_ROOTED_DIRT_PLACE, 0.5f, 1.35f); - } - } - - private int plantNearby(Player p, Block origin, ItemStack seeds, Material seedType, Material cropType, int radius, int maxCrops) { - int planted = 0; - int available = p.getGameMode() == GameMode.CREATIVE ? Integer.MAX_VALUE : seeds.getAmount(); - int y = origin.getY(); - - for (int x = -radius; x <= radius && planted < maxCrops; x++) { - for (int z = -radius; z <= radius && planted < maxCrops; z++) { - if (available <= 0) { - break; - } - - Block base = origin.getWorld().getBlockAt(origin.getX() + x, y, origin.getZ() + z); - if (!isValidBase(seedType, base.getType())) { - continue; - } - - Block crop = base.getRelative(0, 1, 0); - if (!crop.isEmpty() || !canBlockPlace(p, crop.getLocation())) { - continue; - } - - crop.setType(cropType); - planted++; - available--; - } - } - - if (p.getGameMode() != GameMode.CREATIVE && planted > 0) { - seeds.setAmount(Math.max(0, seeds.getAmount() - planted)); - p.getInventory().setItemInMainHand(seeds.getAmount() <= 0 ? new ItemStack(Material.AIR) : seeds); - } - - return planted; - } - - private boolean isValidBase(Material seedType, Material base) { - if (seedType == Material.NETHER_WART) { - return base == Material.SOUL_SAND; - } - - return base == Material.FARMLAND; - } - - private Material getCropType(Material seedType) { - return switch (seedType) { - case WHEAT_SEEDS -> Material.WHEAT; - case CARROT -> Material.CARROTS; - case POTATO -> Material.POTATOES; - case BEETROOT_SEEDS -> Material.BEETROOTS; - case MELON_SEEDS -> Material.MELON_STEM; - case PUMPKIN_SEEDS -> Material.PUMPKIN_STEM; - case TORCHFLOWER_SEEDS -> Material.TORCHFLOWER_CROP; - case NETHER_WART -> Material.NETHER_WART; - default -> null; - }; - } - - private int getRadius(int level) { - return Math.max(1, (int) Math.round(getConfig().baseRadius + (getLevelPercent(level) * getConfig().radiusFactor))); - } - - private int getMaxCrops(int level) { - return Math.max(1, (int) Math.round(getConfig().baseCropCount + (getLevelPercent(level) * getConfig().cropCountFactor))); - } - - private int getCooldownTicks(double factor) { - return Math.max(2, (int) Math.round(getConfig().cooldownTicksBase - (factor * getConfig().cooldownTicksReduction))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-right-click with seeds to plant nearby farmland and soul-sand plots.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.675; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Radius for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseRadius = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Crop Count for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseCropCount = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Crop Count Factor for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cropCountFactor = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 60; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Reduction for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksReduction = 42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Crop for the Herbalism Seed Sower adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerCrop = 1.45; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismSporeBloom.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismSporeBloom.java deleted file mode 100644 index 0007e8945..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismSporeBloom.java +++ /dev/null @@ -1,595 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class HerbalismSporeBloom extends SimpleAdaptation { - private final Map cooldowns = new HashMap<>(); - - public HerbalismSporeBloom() { - super("herbalism-spore-bloom"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.spore_bloom.description")); - setDisplayName(Localizer.dLocalize("herbalism.spore_bloom.name")); - setIcon(Material.RED_MUSHROOM_BLOCK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2100); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BROWN_MUSHROOM) - .key("challenge_herbalism_spore_500") - .title(Localizer.dLocalize("advancement.challenge_herbalism_spore_500.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_spore_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_spore_500", "herbalism.spore-bloom.blocks-spread", 500, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getBloomAttempts(level) + C.GRAY + " " + Localizer.dLocalize("herbalism.spore_bloom.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(getBloomRadius(level)) + C.GRAY + " " + Localizer.dLocalize("herbalism.spore_bloom.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("herbalism.spore_bloom.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(BlockPlaceEvent e) { - if (e.getHand() != EquipmentSlot.HAND) { - return; - } - - if (!hasAdaptation(e.getPlayer()) || !e.getPlayer().isSneaking()) { - return; - } - - if (!isSporeItem(e.getItemInHand())) { - return; - } - - Block floor = e.getBlockPlaced().getRelative(0, -1, 0); - if (!isBloomFloor(floor.getType())) { - return; - } - - // Place-trigger activation: sneak-place mushroom on valid floor to bloom. - e.setCancelled(true); - attemptBloom(e.getPlayer(), floor, e.getItemInHand().getType()); - } - - private void startBloom(org.bukkit.entity.Player player, Block center, Material catalyst, Material spreadSurface, int level) { - List path = buildSpiderPath(center, getBloomRadius(level), getSpokes(level), getBloomAttempts(level), getGuaranteedReach(level)); - if (path.isEmpty()) { - return; - } - - new BukkitRunnable() { - int cursor = 0; - int totalChanged = 0; - - @Override - public void run() { - if (!player.isOnline() || center.getWorld() == null) { - cancel(); - return; - } - - int pulseChanged = 0; - int batch = getBlocksPerPulse(level); - for (int i = 0; i < batch && cursor < path.size(); i++) { - pulseChanged += spreadAt(path.get(cursor++), catalyst, spreadSurface); - } - - if (pulseChanged > 0) { - totalChanged += pulseChanged; - if (areParticlesEnabled()) { - center.getWorld().spawnParticle(Particle.SPORE_BLOSSOM_AIR, center.getLocation().add(0.5, 1.0, 0.5), 8, 0.55, 0.2, 0.55, 0.02); - } - if (areParticlesEnabled()) { - center.getWorld().spawnParticle(Particle.CRIMSON_SPORE, center.getLocation().add(0.5, 1.0, 0.5), 8, 0.55, 0.2, 0.55, 0.01); - } - SoundPlayer sp = SoundPlayer.of(center.getWorld()); - sp.play(center.getLocation().add(0.5, 0.5, 0.5), Sound.BLOCK_FUNGUS_PLACE, 0.45f, 0.75f); - sp.play(center.getLocation().add(0.5, 0.5, 0.5), Sound.ENTITY_ENDERMAN_AMBIENT, 0.22f, 0.45f + ThreadLocalRandom.current().nextFloat() * 0.45f); - } - - if (cursor >= path.size()) { - if (totalChanged > 0) { - getPlayer(player).getData().addStat("herbalism.spore-bloom.blocks-spread", totalChanged); - xp(player, totalChanged * getConfig().xpPerMushroomPlaced); - } - cancel(); - } - } - }.runTaskTimer(Adapt.instance, 0L, getSpreadIntervalTicks(level)); - } - - private List buildSpiderPath(Block center, double radius, int spokes, int max, int guaranteedReach) { - int r = Math.max(1, (int) Math.ceil(radius)); - int sectors = Math.max(8, Math.min(48, Math.max(1, spokes) * 3)); - double maxDistance = radius + 0.35D; - double maxDistanceSq = maxDistance * maxDistance; - List out = new ArrayList<>(Math.max(8, max)); - Set seen = new HashSet<>(); - out.add(center); - seen.add(key(center)); - - if (out.size() >= max) { - return out; - } - - ThreadLocalRandom random = ThreadLocalRandom.current(); - double offset = random.nextDouble(Math.PI * 2D); - - int forcedReach = Math.max(0, Math.min(r, guaranteedReach)); - for (int step = 1; step <= forcedReach; step++) { - addRingSamples(center, out, seen, step, sectors, offset, maxDistanceSq, max); - if (out.size() >= max) { - return out; - } - } - - for (int step = 1; step <= r; step++) { - addRingSamples(center, out, seen, step, sectors, offset, maxDistanceSq, max); - if (out.size() >= max) { - return out; - } - - // Offset pass fills sector rounding gaps so rings look uniform. - addRingSamples(center, out, seen, step, sectors, offset + (Math.PI / sectors), maxDistanceSq, max); - if (out.size() >= max) { - return out; - } - } - - fillRemainingFromCircle(center, out, seen, r, offset, maxDistanceSq, max); - return out; - } - - private void addRingSamples(Block center, List out, Set seen, int step, int sectors, double offset, double maxDistanceSq, int max) { - for (int i = 0; i < sectors && out.size() < max; i++) { - double angle = offset + ((Math.PI * 2D) * i / sectors); - int dx = (int) Math.round(Math.cos(angle) * step); - int dz = (int) Math.round(Math.sin(angle) * step); - if (dx == 0 && dz == 0) { - continue; - } - - if ((dx * dx) + (dz * dz) > maxDistanceSq) { - continue; - } - - Block block = center.getRelative(dx, 0, dz); - if (seen.add(key(block))) { - out.add(block); - } - } - } - - private void fillRemainingFromCircle(Block center, List out, Set seen, int r, double offset, double maxDistanceSq, int max) { - List candidates = new ArrayList<>(); - for (int dx = -r; dx <= r; dx++) { - for (int dz = -r; dz <= r; dz++) { - if (dx == 0 && dz == 0) { - continue; - } - - if ((dx * dx) + (dz * dz) > maxDistanceSq) { - continue; - } - - candidates.add(new int[]{dx, dz}); - } - } - - candidates.sort(Comparator.comparingInt(v -> (v[0] * v[0]) + (v[1] * v[1])) - .thenComparingDouble(v -> normalizeAngle(Math.atan2(v[1], v[0]) - offset))); - - for (int[] c : candidates) { - if (out.size() >= max) { - return; - } - - Block block = center.getRelative(c[0], 0, c[1]); - if (seen.add(key(block))) { - out.add(block); - } - } - } - - private double normalizeAngle(double angle) { - double out = angle % (Math.PI * 2D); - return out < 0 ? out + (Math.PI * 2D) : out; - } - - private int spreadAt(Block floor, Material catalyst, Material spreadSurface) { - int changed = 0; - Block ground = resolveTopSurfaceSoil(floor); - if (ground == null) { - return 0; - } - Block above = ground.getRelative(0, 1, 0); - - if (spreadSurface != null && isConvertibleSoil(ground.getType()) && ground.getType() != spreadSurface) { - ground.setType(spreadSurface, false); - changed++; - } - - if (getConfig().swapFlowersToMushrooms && isFlower(above.getType())) { - Material replacement = getFlowerReplacement(above.getType(), catalyst); - if (replacement != null && above.getType() != replacement) { - above.setType(replacement, false); - changed++; - } - } - - return changed; - } - - private Block resolveTopSurfaceSoil(Block sample) { - int x = sample.getX(); - int z = sample.getZ(); - int highestY = sample.getWorld().getHighestBlockYAt(x, z); - int minY = sample.getWorld().getMinHeight(); - - for (int y = highestY; y >= minY; y--) { - Block block = sample.getWorld().getBlockAt(x, y, z); - if (!isConvertibleSoil(block.getType())) { - continue; - } - - Block above = block.getRelative(0, 1, 0); - if (above.getType().isAir() || isReplaceablePlant(above.getType()) || isFlower(above.getType())) { - return block; - } - } - - return null; - } - - private boolean isBloomFloor(Material type) { - return type == Material.MYCELIUM || type == Material.PODZOL; - } - - private boolean isSporeItem(ItemStack hand) { - return isItem(hand) && (hand.getType() == Material.RED_MUSHROOM || hand.getType() == Material.BROWN_MUSHROOM); - } - - private boolean consumeOne(ItemStack hand) { - if (hand.getAmount() <= 0) { - return false; - } - - hand.setAmount(hand.getAmount() - 1); - return true; - } - - private boolean attemptBloom(org.bukkit.entity.Player player, Block center, Material catalyst) { - Material spreadSurface = resolveSpreadSurface(center.getType()); - if (spreadSurface == null) { - return false; - } - - int level = getLevel(player); - long now = System.currentTimeMillis(); - long ready = cooldowns.getOrDefault(player.getUniqueId(), 0L); - if (now < ready) { - SoundPlayer.of(center.getWorld()).play(center.getLocation().add(0.5, 0.5, 0.5), Sound.BLOCK_NOTE_BLOCK_BASS, 0.5f, 0.75f); - return false; - } - - if (player.getFoodLevel() < getFoodCost(level)) { - SoundPlayer.of(center.getWorld()).play(center.getLocation().add(0.5, 0.5, 0.5), Sound.BLOCK_NOTE_BLOCK_BASS, 0.5f, 0.75f); - return false; - } - - if (!consumeCatalystFromMainHandIfPresent(player, catalyst)) { - return false; - } - - player.setFoodLevel(Math.max(0, player.getFoodLevel() - getFoodCost(level))); - cooldowns.put(player.getUniqueId(), now + getCooldownMillis(level)); - - if (areParticlesEnabled()) { - center.getWorld().spawnParticle(Particle.SPORE_BLOSSOM_AIR, center.getLocation().add(0.5, 1.0, 0.5), 30, 0.35, 0.15, 0.35, 0.01); - } - SoundPlayer.of(center.getWorld()).play(center.getLocation().add(0.5, 0.5, 0.5), Sound.ENTITY_ENDERMAN_AMBIENT, 0.45f, 0.55f); - startBloom(player, center, catalyst, spreadSurface, level); - return true; - } - - private boolean consumeCatalystFromMainHandIfPresent(org.bukkit.entity.Player player, Material catalyst) { - ItemStack held = player.getInventory().getItemInMainHand(); - if (!isItem(held)) { - // Some server flows decrement the placed stack before cancelled placement is finalized. - // In that case, allow activation without double-consuming. - return true; - } - - if (held.getType() != catalyst) { - return true; - } - - return consumeOne(held); - } - - private Material resolveSpreadSurface(Material floorType) { - if (floorType == Material.MYCELIUM) { - return Material.MYCELIUM; - } - - if (floorType == Material.PODZOL) { - return Material.PODZOL; - } - - return null; - } - - private int getGuaranteedReach(int level) { - return level >= 5 ? 6 : 0; - } - - private boolean isConvertibleSoil(Material type) { - return type == Material.DIRT - || type == Material.GRASS_BLOCK - || type == Material.COARSE_DIRT - || type == Material.ROOTED_DIRT - || type == Material.MYCELIUM - || type == Material.PODZOL; - } - - private boolean isFlower(Material type) { - String n = type.name(); - return n.endsWith("_FLOWER") - || n.endsWith("_TULIP") - || type == Material.DANDELION - || type == Material.POPPY - || type == Material.BLUE_ORCHID - || type == Material.ALLIUM - || type == Material.AZURE_BLUET - || type == Material.OXEYE_DAISY - || type == Material.CORNFLOWER - || type == Material.LILY_OF_THE_VALLEY - || type == Material.WITHER_ROSE - || type == Material.SUNFLOWER - || type == Material.LILAC - || type == Material.ROSE_BUSH - || type == Material.PEONY - || type == Material.TORCHFLOWER - || type == Material.PINK_PETALS - || type == Material.SPORE_BLOSSOM; - } - - private Material getFlowerReplacement(Material flower, Material catalyst) { - ThreadLocalRandom random = ThreadLocalRandom.current(); - if (isWarmFlower(flower)) { - // Warm flowers lean red. - return random.nextDouble() <= 0.7 ? Material.RED_MUSHROOM : Material.BROWN_MUSHROOM; - } - - if (isCoolFlower(flower)) { - // Cool flowers lean brown. - return random.nextDouble() <= 0.7 ? Material.BROWN_MUSHROOM : Material.RED_MUSHROOM; - } - - // Fallback uses catalyst flavor. - return catalyst == Material.BROWN_MUSHROOM ? Material.BROWN_MUSHROOM : Material.RED_MUSHROOM; - } - - private boolean isWarmFlower(Material flower) { - return flower == Material.DANDELION - || flower == Material.POPPY - || flower == Material.RED_TULIP - || flower == Material.ORANGE_TULIP - || flower == Material.PINK_TULIP - || flower == Material.SUNFLOWER - || flower == Material.ROSE_BUSH - || flower == Material.PEONY - || flower == Material.WITHER_ROSE - || flower == Material.TORCHFLOWER - || flower == Material.PINK_PETALS; - } - - private boolean isCoolFlower(Material flower) { - return flower == Material.BLUE_ORCHID - || flower == Material.ALLIUM - || flower == Material.AZURE_BLUET - || flower == Material.WHITE_TULIP - || flower == Material.OXEYE_DAISY - || flower == Material.CORNFLOWER - || flower == Material.LILY_OF_THE_VALLEY - || flower == Material.LILAC - || flower == Material.SPORE_BLOSSOM; - } - - private boolean isWoodLike(Material type) { - String n = type.name(); - return n.endsWith("_LOG") || n.endsWith("_WOOD") || n.endsWith("_STEM") || n.endsWith("_HYPHAE") || n.endsWith("_PLANKS"); - } - - private boolean isReplaceablePlant(Material type) { - if (type == Material.RED_MUSHROOM || type == Material.BROWN_MUSHROOM || type == Material.CRIMSON_FUNGUS || type == Material.WARPED_FUNGUS) { - return true; - } - - String n = type.name(); - return n.endsWith("_FLOWER") - || n.endsWith("_TULIP") - || n.endsWith("_SAPLING") - || n.endsWith("_SEEDS") - || n.endsWith("_ROOTS") - || n.endsWith("_CROP") - || n.equals("TALL_GRASS") - || n.equals("GRASS") - || n.equals("FERN") - || n.equals("LARGE_FERN") - || n.equals("WHEAT") - || n.equals("CARROTS") - || n.equals("POTATOES") - || n.equals("BEETROOTS") - || n.equals("NETHER_WART") - || n.equals("TORCHFLOWER_CROP") - || n.equals("PITCHER_CROP"); - } - - private String key(Block block) { - return block.getX() + ":" + block.getY() + ":" + block.getZ(); - } - - private int getBloomAttempts(int level) { - double scaled = getConfig().bloomAttemptsBase + (getLevelPercent(level) * getConfig().bloomAttemptsFactor); - double perLevel = Math.max(0, level - 1) * getConfig().bloomAttemptsPerLevel; - return Math.max(1, (int) Math.round(scaled + perLevel)); - } - - private double getBloomRadius(int level) { - double radius = getConfig().bloomRadiusBase + (getLevelPercent(level) * getConfig().bloomRadiusFactor); - if (level >= 5) { - radius = Math.max(6D, radius); - } - return radius; - } - - private int getSpokes(int level) { - return Math.max(4, (int) Math.round(getConfig().spokesBase + (getLevelPercent(level) * getConfig().spokesFactor))); - } - - private int getBlocksPerPulse(int level) { - return Math.max(1, (int) Math.round(getConfig().blocksPerPulseBase + (getLevelPercent(level) * getConfig().blocksPerPulseFactor))); - } - - private int getSpreadIntervalTicks(int level) { - return Math.max(1, (int) Math.round(getConfig().spreadIntervalTicksBase - (getLevelPercent(level) * getConfig().spreadIntervalTicksFactor))); - } - - private int getFoodCost(int level) { - return Math.max(1, (int) Math.round(getConfig().foodCostBase - (getLevelPercent(level) * getConfig().foodCostFactor))); - } - - private long getCooldownMillis(int level) { - return Math.max(250L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-right-click mycelium with mushrooms to spread an outward spore-web that mutates nearby growth.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Convert Wood To Hyphae for the Herbalism Spore Bloom adaptation.", impact = "True enables this behavior and false disables it.") - boolean convertWoodToHyphae = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows flowers hit by the bloom to be replaced with mushrooms.", impact = "Disable this to keep flowers untouched while still converting soil into mushroom blocks.") - boolean swapFlowersToMushrooms = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Branch Chance for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double branchChance = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mushroom Choices for the Herbalism Spore Bloom adaptation.", impact = "Changing this alters the identifier or text used by the feature.") - String[] mushroomChoices = {"RED_MUSHROOM", "BROWN_MUSHROOM", "CRIMSON_FUNGUS", "WARPED_FUNGUS"}; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bloom Attempts Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bloomAttemptsBase = 26; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bloom Attempts Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bloomAttemptsFactor = 58; - @com.volmit.adapt.util.config.ConfigDoc(value = "Additional bloom attempts granted each adaptation level.", impact = "Higher values make each level spread across more total blocks.") - double bloomAttemptsPerLevel = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bloom Radius Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bloomRadiusBase = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bloom Radius Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bloomRadiusFactor = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spokes Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double spokesBase = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spokes Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double spokesFactor = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Blocks Per Pulse Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double blocksPerPulseBase = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Blocks Per Pulse Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double blocksPerPulseFactor = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spread Interval Ticks Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double spreadIntervalTicksBase = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spread Interval Ticks Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double spreadIntervalTicksFactor = 1.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Food Cost Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double foodCostBase = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Food Cost Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double foodCostFactor = 1.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisBase = 1700; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisFactor = 1100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mushroom Placed for the Herbalism Spore Bloom adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerMushroomPlaced = 1.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismTerralid.java b/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismTerralid.java deleted file mode 100644 index 40e3c8f39..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/herbalism/HerbalismTerralid.java +++ /dev/null @@ -1,124 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.herbalism; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public class HerbalismTerralid extends SimpleAdaptation { - - public HerbalismTerralid() { - super("herbalism-terralid"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("herbalism.terralid.description")); - setDisplayName(Localizer.dLocalize("herbalism.terralid.name")); - setIcon(Material.GRASS_BLOCK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(17771); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shaped() - .key("herbalism-dirt-terralid") - .ingredient(new MaterialChar('S', Material.WHEAT_SEEDS)) - .ingredient(new MaterialChar('D', Material.DIRT)) - .shapes(List.of( - "SSS", - "DDD")) - .result(new ItemStack(Material.GRASS_BLOCK, 3)) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GRASS_BLOCK) - .key("challenge_herbalism_terralid_200") - .title(Localizer.dLocalize("advancement.challenge_herbalism_terralid_200.title")) - .description(Localizer.dLocalize("advancement.challenge_herbalism_terralid_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_herbalism_terralid_200", "herbalism.terralid.grass-crafted", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + Localizer.dLocalize("herbalism.terralid.lore1")); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getWhoClicked() instanceof Player p) || !hasAdaptation(p)) { - return; - } - if (e.getRecipe() instanceof org.bukkit.inventory.ShapedRecipe recipe && recipe.getKey().getNamespace().equals("adapt") && recipe.getKey().getKey().equals("herbalism-dirt-terralid")) { - getPlayer(p).getData().addStat("herbalism.terralid.grass-crafted", 1); - } - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft Grass Blocks from Seeds and Dirt.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterAdrenaline.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterAdrenaline.java deleted file mode 100644 index 1b32af82b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterAdrenaline.java +++ /dev/null @@ -1,132 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageByEntityEvent; - -public class HunterAdrenaline extends SimpleAdaptation { - public HunterAdrenaline() { - super("hunter-adrenaline"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.adrenaline.description")); - setDisplayName(Localizer.dLocalize("hunter.adrenaline.name")); - setIcon(Material.LEATHER_HELMET); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1911); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_hunter_adrenaline_100") - .title(Localizer.dLocalize("advancement.challenge_hunter_adrenaline_100.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_adrenaline_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_hunter_adrenaline_2500") - .title(Localizer.dLocalize("advancement.challenge_hunter_adrenaline_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_adrenaline_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_hunter_adrenaline_100", "hunter.adrenaline.low-health-kills", 100, 400); - registerMilestone("challenge_hunter_adrenaline_2500", "hunter.adrenaline.low-health-kills", 2500, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getDamage(level), 0) + C.GRAY + " " + Localizer.dLocalize("hunter.adrenaline.lore1")); - } - - private double getDamage(int level) { - return ((getLevelPercent(level) * getConfig().damageFactor) + getConfig().damageBase); - } - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p && hasAdaptation(p) && getLevel((Player) e.getDamager()) > 0) { - double damageMax = getDamage(getLevel(p)); - double hpp = ((Player) e.getDamager()).getHealth() / ((Player) e.getDamager()).getMaxHealth(); - - if (hpp >= 1) { - return; - } - - damageMax *= (1D - hpp); - e.setDamage(e.getDamage() * (damageMax + 1D)); - getPlayer(p).getData().addStat("hunter.adrenaline.low-health-kills", 1); - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Deal more melee damage the lower your health is.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Base for the Hunter Adrenaline adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageBase = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Hunter Adrenaline adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageFactor = 0.21; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterDropToInventory.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterDropToInventory.java deleted file mode 100644 index 175948549..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterDropToInventory.java +++ /dev/null @@ -1,165 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Item; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockDropItemEvent; -import org.bukkit.event.entity.EntityDeathEvent; - -import java.util.List; - -public class HunterDropToInventory extends SimpleAdaptation { - public HunterDropToInventory() { - super("hunter-drop-to-inventory"); - registerConfiguration(HunterDropToInventory.Config.class); - setDescription(Localizer.dLocalize("hunter.drop_to_inventory.description")); - setDisplayName(Localizer.dLocalize("hunter.drop_to_inventory.name")); - setIcon(Material.TRAPPED_CHEST); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(18440); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_hunter_dti_10k") - .title(Localizer.dLocalize("advancement.challenge_hunter_dti_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_dti_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_hunter_dti_10k", "hunter.drop-to-inv.items-caught", 10000, 500); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("hunter.drop_to_inventory.lore1")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockDropItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - if (p.getGameMode() != GameMode.SURVIVAL) { - return; - } - if (!canInteract(p, e.getBlock().getLocation())) { - return; - } - if (!canPVP(p, e.getBlock().getLocation())) { - return; - } - if (ItemListings.toolSwords.contains(p.getInventory().getItemInMainHand().getType())) { - List items = new KList<>(e.getItems()); - e.getItems().clear(); - sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); - for (Item i : items) { - if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { - p.getWorld().dropItem(p.getLocation(), i.getItemStack()); - } - } - getPlayer(p).getData().addStat("hunter.drop-to-inv.items-caught", items.size()); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDeathEvent e) { - LivingEntity k = e.getEntity(); - if (k.getKiller() == null || k.getKiller().getType() != EntityType.PLAYER) { - return; - } - Player p = k.getKiller(); - if (!hasAdaptation(p)) { - return; - } - if (e.getEntity() instanceof Player) { - return; - } - if (e.getEntity().getKiller() != null && e.getEntity().getKiller().getClass().getSimpleName().equals("CraftPlayer")) { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); - int itemCount = e.getDrops().size(); - e.getDrops().forEach(i -> { - if (!p.getInventory().addItem(i).isEmpty()) { - p.getWorld().dropItem(p.getLocation(), i); - } - }); - e.getDrops().clear(); - getPlayer(p).getData().addStat("hunter.drop-to-inv.items-caught", itemCount); - } - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Mob and block drops teleport directly into your inventory.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterInvis.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterInvis.java deleted file mode 100644 index 99d4f41eb..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterInvis.java +++ /dev/null @@ -1,161 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffectType; - -public class HunterInvis extends SimpleAdaptation { - public HunterInvis() { - super("hunter-invis"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.invisibility.description")); - setDisplayName(Localizer.dLocalize("hunter.invisibility.name")); - setIcon(Material.TROPICAL_FISH_BUCKET); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(9444); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GLASS) - .key("challenge_hunter_invis_200") - .title(Localizer.dLocalize("advancement.challenge_hunter_invis_200.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_invis_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_hunter_invis_200", "hunter.invis.activations", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("hunter.invisibility.lore1")); - v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.invisibility.lore2")); - v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.invisibility.lore3")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.invisibility.lore4")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.invisibility.lore5")); - v.addLore(C.RED + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.penalty.lore1")); - v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); - - } - - - @EventHandler - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasAdaptation(p)) { - if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { - return; - } - if (!getConfig().useConsumable) { - if (p.getFoodLevel() == 0) { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - } else { - addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); - addPotionStacks(p, PotionEffectType.INVISIBILITY, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.invis.activations", 1); - } - } else { - if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { - Material mat = Material.getMaterial(getConfig().consumable); - if (mat != null && p.getInventory().contains(mat)) { - p.getInventory().removeItem(new ItemStack(mat, 1)); - addPotionStacks(p, PotionEffectType.INVISIBILITY, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.invis.activations", 1); - } else { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - } - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain invisibility when struck, at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") - boolean useConsumable = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") - boolean poisonPenalty = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackHungerPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackPoisonPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Invis adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackBuff = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Invis adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseEffectbyLevel = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Invis adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerFromLevel = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Invis adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerDuration = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Invis adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int basePoisonFromLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Invis adaptation.", impact = "Changing this alters the identifier or text used by the feature.") - String consumable = "ROTTEN_FLESH"; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterJumpBoost.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterJumpBoost.java deleted file mode 100644 index 849a3d9c8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterJumpBoost.java +++ /dev/null @@ -1,163 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffectType; - -public class HunterJumpBoost extends SimpleAdaptation { - public HunterJumpBoost() { - super("hunter-jumpboost"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.jump_boost.description")); - setDisplayName(Localizer.dLocalize("hunter.jump_boost.name")); - setIcon(Material.PUFFERFISH_BUCKET); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(9544); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.RABBIT_FOOT) - .key("challenge_hunter_jump_200") - .title(Localizer.dLocalize("advancement.challenge_hunter_jump_200.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_jump_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_hunter_jump_200", "hunter.jump-boost.activations", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("hunter.jump_boost.lore1")); - v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.jump_boost.lore2")); - v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.jump_boost.lore3")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.jump_boost.lore4")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.jump_boost.lore5")); - v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); - - } - - - @EventHandler - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasAdaptation(p)) { - if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { - return; - } - - if (!getConfig().useConsumable) { - if (p.getFoodLevel() == 0) { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - - } else { - addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); - addPotionStacks(p, PotionEffectTypes.JUMP, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.jump-boost.activations", 1); - } - } else { - if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { - Material mat = Material.getMaterial(getConfig().consumable); - if (mat != null && p.getInventory().contains(mat)) { - p.getInventory().removeItem(new ItemStack(mat, 1)); - addPotionStacks(p, PotionEffectTypes.JUMP, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.jump-boost.activations", 1); - } else { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - } - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain jump boost when struck, at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") - boolean useConsumable = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") - boolean poisonPenalty = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackHungerPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackPoisonPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Jump Boost adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackBuff = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Jump Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseEffectbyLevel = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Jump Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerFromLevel = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Jump Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerDuration = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Jump Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int basePoisonFromLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Jump Boost adaptation.", impact = "Changing this alters the identifier or text used by the feature.") - String consumable = "ROTTEN_FLESH"; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterLuck.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterLuck.java deleted file mode 100644 index 952de97e7..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterLuck.java +++ /dev/null @@ -1,164 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffectType; - -public class HunterLuck extends SimpleAdaptation { - public HunterLuck() { - super("hunter-luck"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.luck.description")); - setDisplayName(Localizer.dLocalize("hunter.luck.name")); - setIcon(Material.TADPOLE_BUCKET); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(9644); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.EMERALD) - .key("challenge_hunter_luck_200") - .title(Localizer.dLocalize("advancement.challenge_hunter_luck_200.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_luck_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_hunter_luck_200", "hunter.luck.activations", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("hunter.luck.lore1")); - v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.luck.lore2")); - v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.luck.lore3")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.luck.lore4")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.luck.lore5")); - v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); - - } - - - @EventHandler - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasAdaptation(p)) { - if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { - return; - } - - if (!getConfig().useConsumable) { - if (p.getFoodLevel() == 0) { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - addPotionStacks(p, PotionEffectType.UNLUCK, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - - } else { - addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); - addPotionStacks(p, PotionEffectType.LUCK, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.luck.activations", 1); - } - } else { - if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { - Material mat = Material.getMaterial(getConfig().consumable); - if (mat != null && p.getInventory().contains(mat)) { - p.getInventory().removeItem(new ItemStack(mat, 1)); - addPotionStacks(p, PotionEffectType.LUCK, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.luck.activations", 1); - } else { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - addPotionStacks(p, PotionEffectType.UNLUCK, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - } - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain luck when struck, at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") - boolean useConsumable = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") - boolean poisonPenalty = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackHungerPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackPoisonPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Luck adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackBuff = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseEffectbyLevel = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerFromLevel = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerDuration = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Luck adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int basePoisonFromLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Luck adaptation.", impact = "Changing this alters the identifier or text used by the feature.") - String consumable = "ROTTEN_FLESH"; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterRegen.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterRegen.java deleted file mode 100644 index cac60be79..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterRegen.java +++ /dev/null @@ -1,162 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffectType; - -public class HunterRegen extends SimpleAdaptation { - public HunterRegen() { - super("hunter-regen"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.regen.description")); - setDisplayName(Localizer.dLocalize("hunter.regen.name")); - setIcon(Material.AXOLOTL_BUCKET); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(9744); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_APPLE) - .key("challenge_hunter_regen_500") - .title(Localizer.dLocalize("advancement.challenge_hunter_regen_500.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_regen_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_hunter_regen_500", "hunter.regen.health-regened", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("hunter.regen.lore1")); - v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.regen.lore2")); - v.addLore(C.RED + "- " + (getConfig().basePoisonFromLevel - level) + C.GRAY + Localizer.dLocalize("hunter.regen.lore3")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.regen.lore4")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.regen.lore5")); - v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); - - } - - - @EventHandler - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasAdaptation(p)) { - if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { - return; - } - - if (!getConfig().useConsumable) { - if (p.getFoodLevel() == 0) { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - - } else { - addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); - addPotionStacks(p, PotionEffectType.REGENERATION, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.regen.health-regened", 1); - } - } else { - if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { - Material mat = Material.getMaterial(getConfig().consumable); - if (mat != null && p.getInventory().contains(mat)) { - p.getInventory().removeItem(new ItemStack(mat, 1)); - addPotionStacks(p, PotionEffectType.REGENERATION, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.regen.health-regened", 1); - } else { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - } - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain regeneration when struck, at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") - boolean useConsumable = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") - boolean poisonPenalty = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackHungerPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackPoisonPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Regen adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackBuff = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Regen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseEffectbyLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Regen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerFromLevel = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Regen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerDuration = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Regen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int basePoisonFromLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Regen adaptation.", impact = "Changing this alters the identifier or text used by the feature.") - String consumable = "ROTTEN_FLESH"; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterResistance.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterResistance.java deleted file mode 100644 index 4d2ff9b16..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterResistance.java +++ /dev/null @@ -1,163 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffectType; - -public class HunterResistance extends SimpleAdaptation { - public HunterResistance() { - super("hunter-resistance"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.resistance.description")); - setDisplayName(Localizer.dLocalize("hunter.resistance.name")); - setIcon(Material.POWDER_SNOW_BUCKET); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(9844); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_CHESTPLATE) - .key("challenge_hunter_resistance_500") - .title(Localizer.dLocalize("advancement.challenge_hunter_resistance_500.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_resistance_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_hunter_resistance_500", "hunter.resistance.activations", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("hunter.resistance.lore1")); - v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.resistance.lore2")); - v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.resistance.lore3")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.resistance.lore4")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.resistance.lore5")); - v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); - - } - - - @EventHandler - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasAdaptation(p)) { - if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { - return; - } - - if (!getConfig().useConsumable) { - if (p.getFoodLevel() == 0) { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - - } else { - addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); - addPotionStacks(p, PotionEffectTypes.DAMAGE_RESISTANCE, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.resistance.activations", 1); - } - } else { - if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { - Material mat = Material.getMaterial(getConfig().consumable); - if (mat != null && p.getInventory().contains(mat)) { - p.getInventory().removeItem(new ItemStack(mat, 1)); - addPotionStacks(p, PotionEffectTypes.DAMAGE_RESISTANCE, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.resistance.activations", 1); - } else { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - } - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain resistance when struck, at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") - boolean useConsumable = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") - boolean poisonPenalty = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackHungerPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackPoisonPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Resistance adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackBuff = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Resistance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseEffectbyLevel = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Resistance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerFromLevel = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Resistance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerDuration = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Resistance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int basePoisonFromLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Resistance adaptation.", impact = "Changing this alters the identifier or text used by the feature.") - String consumable = "ROTTEN_FLESH"; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterSpeed.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterSpeed.java deleted file mode 100644 index bcd417e1d..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterSpeed.java +++ /dev/null @@ -1,326 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.VelocitySpeed; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class HunterSpeed extends SimpleAdaptation { - private final Map speedBursts = new HashMap<>(); - - public HunterSpeed() { - super("hunter-speed"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.speed.description")); - setDisplayName(Localizer.dLocalize("hunter.speed.name")); - setIcon(Material.SUGAR); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(getConfig().setInterval); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SUGAR) - .key("challenge_hunter_speed_200") - .title(Localizer.dLocalize("advancement.challenge_hunter_speed_200.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_speed_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_hunter_speed_200", "hunter.speed.activations", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("hunter.speed.lore1")); - v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.speed.lore2")); - v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.speed.lore3")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.speed.lore4")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.speed.lore5")); - v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); - - } - - - @EventHandler - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasAdaptation(p)) { - if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { - return; - } - - if (!getConfig().useConsumable) { - if (p.getFoodLevel() == 0) { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - - } else { - addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); - grantSpeedBurst(p, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.speed.activations", 1); - } - } else { - if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { - Material mat = Material.getMaterial(getConfig().consumable); - if (mat != null && p.getInventory().contains(mat)) { - p.getInventory().removeItem(new ItemStack(mat, 1)); - grantSpeedBurst(p, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.speed.activations", 1); - } else { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - } - } - } - } - } - - @EventHandler - public void on(PlayerQuitEvent e) { - speedBursts.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler - public void on(PlayerDeathEvent e) { - speedBursts.remove(e.getEntity().getUniqueId()); - } - - private void grantSpeedBurst(org.bukkit.entity.Player p, int amplifier, int durationTicks, boolean overlap) { - if (durationTicks <= 0) { - return; - } - - UUID id = p.getUniqueId(); - long now = System.currentTimeMillis(); - long durationMs = Math.max(50L, durationTicks * 50L); - SpeedBurst current = speedBursts.get(id); - if (current != null && current.expiresAt > now) { - if (!overlap) { - return; - } - - current.expiresAt += durationMs; - current.amplifier = Math.max(current.amplifier, amplifier); - return; - } - - speedBursts.put(id, new SpeedBurst(now + durationMs, amplifier)); - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - org.bukkit.entity.Player p = adaptPlayer.getPlayer(); - SpeedBurst burst = speedBursts.get(p.getUniqueId()); - if (burst == null) { - continue; - } - - if (burst.expiresAt <= now) { - invalidateBurst(p, burst, false); - speedBursts.remove(p.getUniqueId()); - continue; - } - - if (!isVelocityEligible(p)) { - invalidateBurst(p, burst, true); - continue; - } - - VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); - if (!input.hasHorizontal()) { - brakeBurst(p, burst); - continue; - } - - applyBurst(p, burst, input); - } - } - - private void applyBurst(org.bukkit.entity.Player p, SpeedBurst burst, VelocitySpeed.InputSnapshot input) { - Vector desiredDirection = VelocitySpeed.resolveHorizontalDirection(p, input); - if (desiredDirection.lengthSquared() <= VelocitySpeed.EPSILON) { - brakeBurst(p, burst); - return; - } - - double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, - Math.max(0, getConfig().baseHorizontalSpeed * VelocitySpeed.speedAmplifierScalar(burst.amplifier))); - Vector velocity = p.getVelocity(); - Vector currentHorizontal = VelocitySpeed.horizontalOnly(velocity); - Vector targetHorizontal = desiredDirection.multiply(targetSpeed); - Vector nextHorizontal = VelocitySpeed.moveTowards(currentHorizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); - nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - burst.boosting = true; - } - - private void invalidateBurst(org.bukkit.entity.Player p, SpeedBurst burst, boolean invalidState) { - if (!burst.boosting) { - return; - } - - if (invalidState && getConfig().hardStopOnInvalidState) { - VelocitySpeed.hardStopHorizontal(p); - } - - burst.boosting = false; - } - - private void brakeBurst(org.bukkit.entity.Player p, SpeedBurst burst) { - if (!burst.boosting) { - return; - } - - Vector velocity = p.getVelocity(); - Vector currentHorizontal = VelocitySpeed.horizontalOnly(velocity); - double stopThreshold = Math.max(0, getConfig().stopThreshold); - if (currentHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - burst.boosting = false; - return; - } - - Vector nextHorizontal = VelocitySpeed.moveTowards(currentHorizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); - if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - burst.boosting = false; - return; - } - - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - } - - private boolean isVelocityEligible(org.bukkit.entity.Player p) { - GameMode mode = p.getGameMode(); - if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { - return false; - } - - return !p.isDead() && !p.isFlying() && !p.isGliding() && !p.isSwimming() && p.getVehicle() == null; - } - - private static class SpeedBurst { - private long expiresAt; - private int amplifier; - private boolean boosting; - - private SpeedBurst(long expiresAt, int amplifier) { - this.expiresAt = expiresAt; - this.amplifier = amplifier; - } - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain speed when struck, at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Tick interval (ms) used to update velocity speed bursts.", impact = "Lower values feel more responsive but run updates more frequently.") - long setInterval = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") - boolean useConsumable = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") - boolean poisonPenalty = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackHungerPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackPoisonPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Speed adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackBuff = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Speed adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseEffectbyLevel = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Speed adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerDuration = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Speed adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerFromLevel = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Speed adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int basePoisonFromLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for hunter bursts before amplifier scaling.", impact = "Higher values increase movement speed while a burst is active.") - double baseHorizontalSpeed = 0.13; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent runaway momentum.") - double maxHorizontalSpeed = 0.32; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the burst target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") - double accelPerTick = 0.045; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity decays when movement input is released.", impact = "Higher values reduce carry momentum more aggressively.") - double brakePerTick = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny momentum longer.") - double stopThreshold = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "If true, burst velocity is force-cleared when entering invalid states.", impact = "Prevents retained speed when state changes skip expected flow.") - boolean hardStopOnInvalidState = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") - double fallbackInputVelocityThreshold = 0.0008; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Speed adaptation.", impact = "Changing this alters the identifier or text used by the feature.") - String consumable = "ROTTEN_FLESH"; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - - double fallbackInputVelocityThresholdSquared() { - double threshold = Math.max(0, fallbackInputVelocityThreshold); - return threshold * threshold; - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterStrength.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterStrength.java deleted file mode 100644 index e1c828c83..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterStrength.java +++ /dev/null @@ -1,163 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffectType; - -public class HunterStrength extends SimpleAdaptation { - public HunterStrength() { - super("hunter-strength"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.strength.description")); - setDisplayName(Localizer.dLocalize("hunter.strength.name")); - setIcon(Material.COD_BUCKET); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(9044); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BLAZE_POWDER) - .key("challenge_hunter_strength_200") - .title(Localizer.dLocalize("advancement.challenge_hunter_strength_200.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_strength_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_hunter_strength_200", "hunter.strength.activations", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("hunter.strength.lore1")); - v.addLore(C.GREEN + "+ " + level + C.GRAY + Localizer.dLocalize("hunter.strength.lore2")); - v.addLore(C.RED + "- " + (5 + level) + C.GRAY + Localizer.dLocalize("hunter.strength.lore3")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.strength.lore4")); - v.addLore(C.GRAY + "* " + level + C.GRAY + " " + Localizer.dLocalize("hunter.strength.lore5")); - v.addLore(C.GRAY + "- " + level + C.RED + " " + Localizer.dLocalize("hunter.penalty.lore1")); - - } - - - @EventHandler - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof org.bukkit.entity.Player p && isAdaptableDamageCause(e) && hasAdaptation(p)) { - if (AdaptConfig.get().isPreventHunterSkillsWhenHungerApplied() && p.hasPotionEffect(PotionEffectType.HUNGER)) { - return; - } - - if (!getConfig().useConsumable) { - if (p.getFoodLevel() == 0) { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - - } else { - addPotionStacks(p, PotionEffectType.HUNGER, getConfig().baseHungerFromLevel - getLevel(p), getConfig().baseHungerDuration * getLevel(p), getConfig().stackHungerPenalty); - addPotionStacks(p, PotionEffectTypes.INCREASE_DAMAGE, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.strength.activations", 1); - } - } else { - if (getConfig().consumable != null && Material.getMaterial(getConfig().consumable) != null) { - Material mat = Material.getMaterial(getConfig().consumable); - if (mat != null && p.getInventory().contains(mat)) { - p.getInventory().removeItem(new ItemStack(mat, 1)); - addPotionStacks(p, PotionEffectTypes.INCREASE_DAMAGE, getLevel(p), getConfig().baseEffectbyLevel * getLevel(p), getConfig().stackBuff); - getPlayer(p).getData().addStat("hunter.strength.activations", 1); - } else { - if (getConfig().poisonPenalty) { - addPotionStacks(p, PotionEffectType.POISON, getConfig().basePoisonFromLevel - getLevel(p), getConfig().baseHungerDuration, getConfig().stackPoisonPenalty); - } - } - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain strength when struck, at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Use Consumable for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") - boolean useConsumable = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Poison Penalty for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") - boolean poisonPenalty = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Hunger Penalty for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackHungerPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Poison Penalty for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackPoisonPenalty = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stack Buff for the Hunter Strength adaptation.", impact = "True enables this behavior and false disables it.") - boolean stackBuff = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Effectby Level for the Hunter Strength adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseEffectbyLevel = 25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger From Level for the Hunter Strength adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerFromLevel = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Poison From Level for the Hunter Strength adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int basePoisonFromLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Hunger Duration for the Hunter Strength adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseHungerDuration = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Consumable for the Hunter Strength adaptation.", impact = "Changing this alters the identifier or text used by the feature.") - String consumable = "ROTTEN_FLESH"; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterTrophySkinner.java b/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterTrophySkinner.java deleted file mode 100644 index 0d04fae21..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/hunter/HunterTrophySkinner.java +++ /dev/null @@ -1,240 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.hunter; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.concurrent.ThreadLocalRandom; - -public class HunterTrophySkinner extends SimpleAdaptation { - public HunterTrophySkinner() { - super("hunter-trophy-skinner"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("hunter.trophy_skinner.description")); - setDisplayName(Localizer.dLocalize("hunter.trophy_skinner.name")); - setIcon(Material.ZOMBIE_HEAD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SKELETON_SKULL) - .key("challenge_hunter_trophy_50") - .title(Localizer.dLocalize("advancement.challenge_hunter_trophy_50.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_trophy_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ZOMBIE_HEAD) - .key("challenge_hunter_trophy_heads_100") - .title(Localizer.dLocalize("advancement.challenge_hunter_trophy_heads_100.title")) - .description(Localizer.dLocalize("advancement.challenge_hunter_trophy_heads_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_hunter_trophy_50", "hunter.trophy-skinner.trophies-collected", 50, 400); - registerMilestone("challenge_hunter_trophy_heads_100", "hunter.trophy-skinner.heads-collected", 100, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getDropChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("hunter.trophy_skinner.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getHeadChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("hunter.trophy_skinner.lore2")); - v.addLore(C.YELLOW + "* " + Form.f(getMinimumRange(level), 1) + C.GRAY + " " + Localizer.dLocalize("hunter.trophy_skinner.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - Player killer = e.getEntity().getKiller(); - if (killer == null || !hasAdaptation(killer) || e.getEntity() instanceof Player || !canPVE(killer, e.getEntity().getLocation())) { - return; - } - - int level = getLevel(killer); - PrecisionContext precision = readPrecisionContext(e, killer, level); - if (!precision.precise()) { - return; - } - - if (ThreadLocalRandom.current().nextDouble() > getDropChance(level)) { - return; - } - - ItemStack trophy = buildTrophyDrop(e.getEntityType(), level, precision.projectileKill()); - if (trophy != null) { - e.getDrops().add(trophy); - getPlayer(killer).getData().addStat("hunter.trophy-skinner.trophies-collected", 1); - } - - if (ThreadLocalRandom.current().nextDouble() <= getHeadChance(level)) { - ItemStack head = buildHeadDrop(e.getEntityType()); - if (head != null) { - e.getDrops().add(head); - getPlayer(killer).getData().addStat("hunter.trophy-skinner.heads-collected", 1); - } - } - - SoundPlayer.of(killer).play(killer.getLocation(), Sound.ENTITY_WOLF_SHAKE, 0.55f, 1.35f); - xp(killer, getConfig().xpPerTrophy); - } - - private PrecisionContext readPrecisionContext(EntityDeathEvent e, Player killer, int level) { - boolean projectileKill = false; - double range = 0; - if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent damageByEntity) { - if (damageByEntity.getDamager() instanceof Projectile projectile && projectile.getShooter() == killer) { - projectileKill = true; - } - } - - if (killer.getWorld() == e.getEntity().getWorld()) { - range = killer.getLocation().distance(e.getEntity().getLocation()); - } - - boolean rangedPrecision = projectileKill && range >= getMinimumRange(level); - boolean stealthPrecision = killer.isSneaking(); - return new PrecisionContext(projectileKill, rangedPrecision || stealthPrecision); - } - - private ItemStack buildTrophyDrop(EntityType type, int level, boolean projectileKill) { - Material material = switch (type) { - case CREEPER -> Material.GUNPOWDER; - case SKELETON, BOGGED, WITHER_SKELETON, STRAY -> Material.BONE; - case ZOMBIE, ZOMBIFIED_PIGLIN, HUSK, DROWNED -> Material.ROTTEN_FLESH; - case SPIDER, CAVE_SPIDER -> Material.STRING; - case BLAZE -> Material.BLAZE_POWDER; - case ENDERMAN -> Material.ENDER_PEARL; - case WITCH -> Material.REDSTONE; - case PIGLIN, PIGLIN_BRUTE, HOGLIN, ZOGLIN -> Material.PORKCHOP; - default -> Material.LEATHER; - }; - - int amount = Math.max(1, (int) Math.round(getConfig().trophyAmountBase + (getLevelPercent(level) * getConfig().trophyAmountFactor))); - if (projectileKill) { - amount += 1; - } - - return new ItemStack(material, Math.min(8, amount)); - } - - private ItemStack buildHeadDrop(EntityType type) { - Material material = switch (type) { - case CREEPER -> Material.CREEPER_HEAD; - case SKELETON, STRAY, BOGGED -> Material.SKELETON_SKULL; - case WITHER_SKELETON -> Material.WITHER_SKELETON_SKULL; - case ZOMBIE, HUSK, DROWNED, ZOMBIFIED_PIGLIN -> Material.ZOMBIE_HEAD; - case PIGLIN, PIGLIN_BRUTE -> Material.PIGLIN_HEAD; - default -> null; - }; - - return material == null ? null : new ItemStack(material); - } - - private double getDropChance(int level) { - return Math.min(getConfig().maxDropChance, getConfig().dropChanceBase + (getLevelPercent(level) * getConfig().dropChanceFactor)); - } - - private double getHeadChance(int level) { - return Math.min(getConfig().maxHeadChance, getConfig().headChanceBase + (getLevelPercent(level) * getConfig().headChanceFactor)); - } - - private double getMinimumRange(int level) { - return Math.max(4, getConfig().minimumRangeBase - (getLevelPercent(level) * getConfig().minimumRangeFactor)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Precision kills can grant bonus trophy drops and occasional heads from elite targets.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Drop Chance Base for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dropChanceBase = 0.14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Drop Chance Factor for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dropChanceFactor = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Maximum Drop Chance for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxDropChance = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Head Chance Base for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double headChanceBase = 0.015; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Head Chance Factor for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double headChanceFactor = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Maximum Head Chance for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxHeadChance = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Trophy Amount Base for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double trophyAmountBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Trophy Amount Factor for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double trophyAmountFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Range Base for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minimumRangeBase = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Range Factor for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minimumRangeFactor = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP Per Trophy for the Hunter Trophy Skinner adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerTrophy = 16; - } - - private record PrecisionContext(boolean projectileKill, boolean precise) { - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherBlazeLeech.java b/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherBlazeLeech.java deleted file mode 100644 index d07bc4f1f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherBlazeLeech.java +++ /dev/null @@ -1,239 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.nether; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.concurrent.ThreadLocalRandom; - -public class NetherBlazeLeech extends SimpleAdaptation { - public NetherBlazeLeech() { - super("nether-blaze-leech"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("nether.blaze_leech.description")); - setDisplayName(Localizer.dLocalize("nether.blaze_leech.name")); - setIcon(Material.BLAZE_POWDER); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(900); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BLAZE_ROD) - .key("challenge_nether_blaze_200") - .title(Localizer.dLocalize("advancement.challenge_nether_blaze_200.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_blaze_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BLAZE_POWDER) - .key("challenge_nether_blaze_2500") - .title(Localizer.dLocalize("advancement.challenge_nether_blaze_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_blaze_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_nether_blaze_200", "nether.blaze-leech.health-from-fire", 200, 300); - registerMilestone("challenge_nether_blaze_2500", "nether.blaze-leech.health-from-fire", 2500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getTriggerChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.blaze_leech.lore1")); - v.addLore(C.GREEN + "+ " + Form.duration(getRegenTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("nether.blaze_leech.lore2")); - v.addLore(C.GREEN + "+ " + Form.f(getFoodRestore(level)) + C.GRAY + " " + Localizer.dLocalize("nether.blaze_leech.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageEvent e) { - if (e.isCancelled() || !(e.getEntity() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - if (!isFireCause(e.getCause()) || !isReady(p, getLevel(p))) { - return; - } - - if (ThreadLocalRandom.current().nextDouble() > getTriggerChance(getLevel(p))) { - return; - } - - applyLeech(p, getLevel(p), true); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Player p) || !hasAdaptation(p) || !(e.getEntity() instanceof LivingEntity target)) { - return; - } - - if (target instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - return; - } - } else if (!canPVE(p, target.getLocation())) { - return; - } - - if (target.getFireTicks() <= 0 || !isReady(p, getLevel(p))) { - return; - } - - if (ThreadLocalRandom.current().nextDouble() > getTriggerChance(getLevel(p))) { - return; - } - - applyLeech(p, getLevel(p), false); - } - - private boolean isFireCause(EntityDamageEvent.DamageCause cause) { - return cause == EntityDamageEvent.DamageCause.FIRE - || cause == EntityDamageEvent.DamageCause.FIRE_TICK - || cause == EntityDamageEvent.DamageCause.LAVA - || cause == EntityDamageEvent.DamageCause.HOT_FLOOR; - } - - private boolean isReady(Player p, int level) { - long now = System.currentTimeMillis(); - long next = getStorageLong(p, "blazeLeechNext", 0L); - if (next > now) { - return false; - } - - setStorage(p, "blazeLeechNext", now + getCooldownMillis(level)); - return true; - } - - private void applyLeech(Player p, int level, boolean defensive) { - int newFood = Math.min(20, p.getFoodLevel() + (int) Math.round(getFoodRestore(level))); - p.setFoodLevel(newFood); - float sat = Math.min(20f, p.getSaturation() + (float) getConfig().saturationRestore); - p.setSaturation(sat); - - int amp = Math.max(0, (int) Math.floor(getRegenAmplifier(level))); - p.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, getRegenTicks(level), amp, true, false, true), true); - - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.ENTITY_BLAZE_AMBIENT, 0.45f, defensive ? 1.4f : 1.7f); - xp(p, defensive ? getConfig().xpOnDefensiveProc : getConfig().xpOnOffensiveProc); - getPlayer(p).getData().addStat("nether.blaze-leech.health-from-fire", 1); - } - - private double getTriggerChance(int level) { - return Math.min(getConfig().maxTriggerChance, getConfig().triggerChanceBase + (getLevelPercent(level) * getConfig().triggerChanceFactor)); - } - - private int getRegenTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().regenTicksBase + (getLevelPercent(level) * getConfig().regenTicksFactor))); - } - - private double getRegenAmplifier(int level) { - return getConfig().regenAmplifierBase + (getLevelPercent(level) * getConfig().regenAmplifierFactor); - } - - private double getFoodRestore(int level) { - return getConfig().foodRestoreBase + (getLevelPercent(level) * getConfig().foodRestoreFactor); - } - - private long getCooldownMillis(int level) { - return Math.max(100L, Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Fire interactions can grant hunger and regeneration in short bursts.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.62; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Trigger Chance Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double triggerChanceBase = 0.16; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Trigger Chance Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double triggerChanceFactor = 0.34; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Trigger Chance for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxTriggerChance = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Regen Ticks Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double regenTicksBase = 28; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Regen Ticks Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double regenTicksFactor = 42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Regen Amplifier Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double regenAmplifierBase = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Regen Amplifier Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double regenAmplifierFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Food Restore Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double foodRestoreBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Food Restore Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double foodRestoreFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Saturation Restore for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double saturationRestore = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisBase = 1400; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisFactor = 900; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Defensive Proc for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnDefensiveProc = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Offensive Proc for the Nether Blaze Leech adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnOffensiveProc = 5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherFireResist.java b/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherFireResist.java deleted file mode 100644 index 40e184e9c..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherFireResist.java +++ /dev/null @@ -1,139 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ -package com.volmit.adapt.content.adaptation.nether; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; - -import java.util.concurrent.ThreadLocalRandom; - -public class NetherFireResist extends SimpleAdaptation { - public NetherFireResist() { - super("nether-fire-resist"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("nether.fire_resist.description")); - setDisplayName(Localizer.dLocalize("nether.fire_resist.name")); - setIcon(Material.FIRE_CHARGE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(4333); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FIRE_CHARGE) - .key("challenge_nether_fire_200") - .title(Localizer.dLocalize("advancement.challenge_nether_fire_200.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_fire_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.MAGMA_CREAM) - .key("challenge_nether_fire_5k") - .title(Localizer.dLocalize("advancement.challenge_nether_fire_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_fire_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_nether_fire_200", "nether.fire-resist.negated", 200, 300); - registerMilestone("challenge_nether_fire_5k", "nether.fire-resist.negated", 5000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.RED + "+ " + Form.pc(getFireResist(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.fire_resist.lore1")); - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - - if (!(e.getEntity() instanceof Player p)) { - return; - } - - if (!hasAdaptation(p)) { - return; - } - - if (e.getCause() != EntityDamageEvent.DamageCause.FIRE && e.getCause() != EntityDamageEvent.DamageCause.FIRE_TICK) { - return; - } - - - if (ThreadLocalRandom.current().nextDouble() < getFireResist(getLevel(p))) { - e.setCancelled(true); - getPlayer(p).getData().addStat("nether.fire-resist.negated", 1); - } - } - - public double getFireResist(double level) { - return getConfig().fireResistBase + (getConfig().fireResistFactor * level); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @Data - @NoArgsConstructor - @ConfigDescription("Chance to negate the burning effect.") - public static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fire Resist Base for the Nether Fire Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fireResistBase = 0.10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fire Resist Factor for the Nether Fire Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fireResistFactor = 0.25; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherGhastWard.java b/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherGhastWard.java deleted file mode 100644 index 698b45417..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherGhastWard.java +++ /dev/null @@ -1,197 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.nether; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.AbstractArrow; -import org.bukkit.entity.Fireball; -import org.bukkit.entity.Ghast; -import org.bukkit.entity.Player; -import org.bukkit.entity.WitherSkeleton; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; - -public class NetherGhastWard extends SimpleAdaptation { - public NetherGhastWard() { - super("nether-ghast-ward"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("nether.ghast_ward.description")); - setDisplayName(Localizer.dLocalize("nether.ghast_ward.name")); - setIcon(Material.GHAST_TEAR); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GHAST_TEAR) - .key("challenge_nether_ghast_500") - .title(Localizer.dLocalize("advancement.challenge_nether_ghast_500.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_ghast_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_nether_ghast_500", "nether.ghast-ward.damage-reduced", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getGhastProjectileReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.ghast_ward.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getExplosionReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.ghast_ward.lore2")); - v.addLore(C.GREEN + "+ " + Form.pc(getWitherSkeletonReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.ghast_ward.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getEntity() instanceof Player p) || !hasAdaptation(p) || !isNether(p)) { - return; - } - - int level = getLevel(p); - if (e.getDamager() instanceof Fireball fireball && fireball.getShooter() instanceof Ghast) { - double before = e.getDamage(); - e.setDamage(Math.max(0, e.getDamage() * (1D - getGhastProjectileReduction(level)))); - p.setFireTicks(Math.min(p.getFireTicks(), getMaxFireTicks(level))); - xp(p, e.getDamage() * getConfig().xpPerMitigatedDamage); - int reduced = (int) Math.round(before - e.getDamage()); - if (reduced > 0) { - getPlayer(p).getData().addStat("nether.ghast-ward.damage-reduced", reduced); - } - return; - } - - if (e.getDamager() instanceof AbstractArrow arrow && arrow.getShooter() instanceof WitherSkeleton) { - double before = e.getDamage(); - e.setDamage(Math.max(0, e.getDamage() * (1D - getWitherSkeletonReduction(level)))); - xp(p, e.getDamage() * getConfig().xpPerMitigatedDamage); - int reduced = (int) Math.round(before - e.getDamage()); - if (reduced > 0) { - getPlayer(p).getData().addStat("nether.ghast-ward.damage-reduced", reduced); - } - } - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(EntityDamageEvent e) { - if (e.isCancelled() || e instanceof EntityDamageByEntityEvent || !(e.getEntity() instanceof Player p) || !hasAdaptation(p) || !isNether(p)) { - return; - } - - if (e.getCause() != EntityDamageEvent.DamageCause.ENTITY_EXPLOSION && e.getCause() != EntityDamageEvent.DamageCause.BLOCK_EXPLOSION) { - return; - } - - double before = e.getDamage(); - e.setDamage(Math.max(0, e.getDamage() * (1D - getExplosionReduction(getLevel(p))))); - xp(p, e.getDamage() * getConfig().xpPerMitigatedDamage); - int reduced = (int) Math.round(before - e.getDamage()); - if (reduced > 0) { - getPlayer(p).getData().addStat("nether.ghast-ward.damage-reduced", reduced); - } - } - - private boolean isNether(Player p) { - return p.getWorld().getEnvironment().name().contains("NETHER"); - } - - private double getGhastProjectileReduction(int level) { - return Math.min(getConfig().maxGhastProjectileReduction, getConfig().ghastProjectileReductionBase + (getLevelPercent(level) * getConfig().ghastProjectileReductionFactor)); - } - - private double getExplosionReduction(int level) { - return Math.min(getConfig().maxExplosionReduction, getConfig().explosionReductionBase + (getLevelPercent(level) * getConfig().explosionReductionFactor)); - } - - private double getWitherSkeletonReduction(int level) { - return Math.min(getConfig().maxWitherSkeletonReduction, getConfig().witherSkeletonReductionBase + (getLevelPercent(level) * getConfig().witherSkeletonReductionFactor)); - } - - private int getMaxFireTicks(int level) { - return Math.max(0, (int) Math.round(getConfig().maxFireTicksBase - (getLevelPercent(level) * getConfig().maxFireTicksFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Harden against ghast blasts and wither-skeleton pressure in the Nether.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.73; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ghast Projectile Reduction Base for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double ghastProjectileReductionBase = 0.14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ghast Projectile Reduction Factor for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double ghastProjectileReductionFactor = 0.54; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Ghast Projectile Reduction for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxGhastProjectileReduction = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Explosion Reduction Base for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double explosionReductionBase = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Explosion Reduction Factor for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double explosionReductionFactor = 0.42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Explosion Reduction for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxExplosionReduction = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Wither Skeleton Reduction Base for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double witherSkeletonReductionBase = 0.1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Wither Skeleton Reduction Factor for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double witherSkeletonReductionFactor = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Wither Skeleton Reduction for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxWitherSkeletonReduction = 0.55; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Fire Ticks Base for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxFireTicksBase = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Fire Ticks Factor for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxFireTicksFactor = 70; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mitigated Damage for the Nether Ghast Ward adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerMitigatedDamage = 4.2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherLavaWalker.java b/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherLavaWalker.java deleted file mode 100644 index 32446d608..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherLavaWalker.java +++ /dev/null @@ -1,179 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.nether; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -public class NetherLavaWalker extends SimpleAdaptation { - public NetherLavaWalker() { - super("nether-lava-walker"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("nether.lava_walker.description")); - setDisplayName(Localizer.dLocalize("nether.lava_walker.name")); - setIcon(Material.MAGMA_BLOCK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.MAGMA_BLOCK) - .key("challenge_nether_lava_1k") - .title(Localizer.dLocalize("advancement.challenge_nether_lava_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_lava_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_INGOT) - .key("challenge_nether_lava_25k") - .title(Localizer.dLocalize("advancement.challenge_nether_lava_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_lava_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_nether_lava_1k", "nether.lava-walker.blocks-walked", 1000, 300); - registerMilestone("challenge_nether_lava_25k", "nether.lava-walker.blocks-walked", 25000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getStride(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.lava_walker.lore1")); - v.addLore(C.YELLOW + "* " + getHungerCost(level) + C.GRAY + " " + Localizer.dLocalize("nether.lava_walker.lore2")); - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(PlayerMoveEvent e) { - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.getWorld().getEnvironment().name().contains("NETHER")) { - return; - } - - if (p.isFlying() || p.isGliding() || p.isInsideVehicle() || p.getFoodLevel() <= 0) { - return; - } - - Block feet = p.getLocation().getBlock(); - Block below = p.getLocation().clone().add(0, -1, 0).getBlock(); - if (!(isLava(feet) || isLava(below))) { - return; - } - - int level = getLevel(p); - if (getStorageLong(p, "lavaWalkerCooldown", 0L) > System.currentTimeMillis()) { - return; - } - - Vector velocity = p.getVelocity(); - Vector dir = p.getLocation().getDirection().setY(0).normalize().multiply(getStride(level)); - p.setVelocity(new Vector(dir.getX(), Math.max(0.16, velocity.getY()), dir.getZ())); - p.setFallDistance(0); - p.setFireTicks(0); - p.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, getConfig().fireResistTicks, 0, false, false)); - - int hungerCost = getHungerCost(level); - p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); - setStorage(p, "lavaWalkerCooldown", System.currentTimeMillis() + getCooldownMillis(level)); - xp(p, getConfig().xpPerStride); - getPlayer(p).getData().addStat("nether.lava-walker.blocks-walked", 1); - } - - private boolean isLava(Block b) { - return b.getType() == Material.LAVA; - } - - private double getStride(int level) { - return getConfig().strideBase + (getLevelPercent(level) * getConfig().strideFactor); - } - - private int getHungerCost(int level) { - return Math.max(1, (int) Math.round(getConfig().hungerCostBase - (getLevelPercent(level) * getConfig().hungerCostFactor))); - } - - private long getCooldownMillis(int level) { - return Math.max(100, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Stride over lava in the Nether at the cost of hunger.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stride Base for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double strideBase = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Stride Factor for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double strideFactor = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost Base for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hungerCostBase = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost Factor for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hungerCostFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Base for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisBase = 900; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Millis Factor for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownMillisFactor = 700; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fire Resist Ticks for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int fireResistTicks = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Stride for the Nether Lava Walker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerStride = 3.5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherPiglinBroker.java b/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherPiglinBroker.java deleted file mode 100644 index 996c76123..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherPiglinBroker.java +++ /dev/null @@ -1,221 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.nether; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Piglin; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.PiglinBarterEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; - -public class NetherPiglinBroker extends SimpleAdaptation { - public NetherPiglinBroker() { - super("nether-piglin-broker"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("nether.piglin_broker.description")); - setDisplayName(Localizer.dLocalize("nether.piglin_broker.name")); - setIcon(Material.GOLD_INGOT); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2300); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLD_INGOT) - .key("challenge_nether_piglin_100") - .title(Localizer.dLocalize("advancement.challenge_nether_piglin_100.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_piglin_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GOLD_BLOCK) - .key("challenge_nether_piglin_2500") - .title(Localizer.dLocalize("advancement.challenge_nether_piglin_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_piglin_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_nether_piglin_100", "nether.piglin-broker.improved-barters", 100, 300); - registerMilestone("challenge_nether_piglin_2500", "nether.piglin-broker.improved-barters", 2500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getExtraRollChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.piglin_broker.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getRareBonusChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("nether.piglin_broker.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PiglinBarterEvent e) { - Piglin piglin = e.getEntity(); - Player broker = findBroker(piglin, getConfig().brokerRange); - if (broker == null) { - return; - } - - int level = getLevel(broker); - List outcome = e.getOutcome(); - if (outcome.isEmpty()) { - return; - } - - ThreadLocalRandom random = ThreadLocalRandom.current(); - boolean changed = false; - if (random.nextDouble() <= getExtraRollChance(level)) { - ItemStack bonus = outcome.get(random.nextInt(outcome.size())).clone(); - int amount = Math.max(1, (int) Math.round(bonus.getAmount() * getAmountMultiplier(level))); - bonus.setAmount(Math.min(bonus.getMaxStackSize(), amount)); - outcome.add(bonus); - changed = true; - } - - if (random.nextDouble() <= getRareBonusChance(level)) { - outcome.add(getRareBonusRoll()); - changed = true; - } - - if (!changed) { - return; - } - - SoundPlayer.of(broker.getWorld()).play(broker.getLocation(), Sound.ENTITY_PIGLIN_ADMIRING_ITEM, 0.9f, 1.25f); - xp(broker, getConfig().xpOnBoostedBarter); - getPlayer(broker).getData().addStat("nether.piglin-broker.improved-barters", 1); - } - - private Player findBroker(Piglin piglin, double range) { - Player best = null; - double bestDist = Double.MAX_VALUE; - double rangeSquared = range * range; - var piglinLocation = piglin.getLocation(); - for (Entity nearby : piglin.getNearbyEntities(range, range, range)) { - if (!(nearby instanceof Player p)) { - continue; - } - if (!hasAdaptation(p)) { - continue; - } - - double d = p.getLocation().distanceSquared(piglinLocation); - if (d > rangeSquared) { - continue; - } - if (d < bestDist) { - best = p; - bestDist = d; - } - } - - return best; - } - - private ItemStack getRareBonusRoll() { - return switch (ThreadLocalRandom.current().nextInt(5)) { - case 0 -> new ItemStack(Material.ENDER_PEARL, 1); - case 1 -> new ItemStack(Material.OBSIDIAN, 2); - case 2 -> new ItemStack(Material.STRING, 4); - case 3 -> new ItemStack(Material.IRON_NUGGET, 6); - default -> new ItemStack(Material.SPECTRAL_ARROW, 2); - }; - } - - private double getExtraRollChance(int level) { - return Math.min(getConfig().maxExtraRollChance, getConfig().extraRollChanceBase + (getLevelPercent(level) * getConfig().extraRollChanceFactor)); - } - - private double getRareBonusChance(int level) { - return Math.min(getConfig().maxRareBonusChance, getConfig().rareBonusChanceBase + (getLevelPercent(level) * getConfig().rareBonusChanceFactor)); - } - - private double getAmountMultiplier(int level) { - return Math.max(1.0, getConfig().amountMultiplierBase + (getLevelPercent(level) * getConfig().amountMultiplierFactor)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Nearby piglin bartering can yield extra or improved rolls.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Broker Range for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double brokerRange = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Extra Roll Chance Base for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double extraRollChanceBase = 0.1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Extra Roll Chance Factor for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double extraRollChanceFactor = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Extra Roll Chance for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxExtraRollChance = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Rare Bonus Chance Base for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rareBonusChanceBase = 0.03; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Rare Bonus Chance Factor for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rareBonusChanceFactor = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Rare Bonus Chance for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxRareBonusChance = 0.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Amount Multiplier Base for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double amountMultiplierBase = 1.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Amount Multiplier Factor for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double amountMultiplierFactor = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Boosted Barter for the Nether Piglin Broker adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnBoostedBarter = 12; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherSkullYeet.java b/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherSkullYeet.java deleted file mode 100644 index fe36ab367..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherSkullYeet.java +++ /dev/null @@ -1,220 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.nether; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.WitherSkull; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.block.Action; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; - -public class NetherSkullYeet extends SimpleAdaptation { - - private final Map lastJump = new HashMap<>(); - - public NetherSkullYeet() { - super("nether-skull-toss"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("nether.skull_toss.description1") + C.ITALIC + " " + Localizer.dLocalize("nether.skull_toss.description2") + " " + C.GRAY + Localizer.dLocalize("nether.skull_toss.description3")); - setDisplayName(Localizer.dLocalize("nether.skull_toss.name")); - setIcon(Material.WITHER_SKELETON_SKULL); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(2314); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WITHER_SKELETON_SKULL) - .key("challenge_nether_skull_100") - .title(Localizer.dLocalize("advancement.challenge_nether_skull_100.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_skull_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WITHER_SKELETON_SKULL) - .key("challenge_nether_skull_kills_50") - .title(Localizer.dLocalize("advancement.challenge_nether_skull_kills_50.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_skull_kills_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WITHER_SKELETON_SKULL) - .key("challenge_nether_skull_long_bomb") - .title(Localizer.dLocalize("advancement.challenge_nether_skull_long_bomb.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_skull_long_bomb.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.HIDDEN) - .build()); - registerMilestone("challenge_nether_skull_100", "nether.skull-yeet.skulls-thrown", 100, 300); - registerMilestone("challenge_nether_skull_kills_50", "nether.skull-yeet.skull-kills", 50, 500); - } - - @Override - public void addStats(int level, Element v) { - int chance = getConfig().getBaseCooldown() - getConfig().getLevelCooldown() * level; - v.addLore(C.GREEN + String.valueOf(chance) + C.GRAY + " " + Localizer.dLocalize("nether.skull_toss.lore1")); - v.addLore(C.GRAY + Localizer.dLocalize("nether.skull_toss.lore2") + C.DARK_GRAY + Localizer.dLocalize("nether.skull_toss.lore3") + C.GRAY + ", " + Localizer.dLocalize("nether.skull_toss.lore4")); - } - - private int getCooldownDuration(Player p) { - return (getConfig().getBaseCooldown() - getConfig().getLevelCooldown() * getLevel(p)) * 20; - } - - @EventHandler - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - lastJump.remove(p); - } - - @EventHandler - public void onRightClick(PlayerInteractEvent e) { - if (!hasAdaptation(e.getPlayer())) { - return; - } - if (e.useItemInHand() == Event.Result.DENY) { - return; - } - - if (e.getAction() != Action.LEFT_CLICK_AIR && e.getAction() != Action.LEFT_CLICK_BLOCK) { - return; - } - if (e.getHand() != EquipmentSlot.HAND || e.getItem() == null || e.getMaterial() != Material.WITHER_SKELETON_SKULL) { - return; - } - - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - - if (lastJump.get(p) != null && M.ms() - lastJump.get(p) <= getCooldownDuration(p)) { - sp.play(p, Sound.BLOCK_CONDUIT_DEACTIVATE, 1F, 1F); - return; - } - - if (lastJump.get(p) != null && M.ms() - lastJump.get(p) <= getCooldownDuration(p)) { - return; - } - - if (p.hasCooldown(p.getInventory().getItemInMainHand().getType())) { - e.setCancelled(true); - sp.play(p, Sound.BLOCK_CONDUIT_DEACTIVATE, 1F, 1F); - return; - } else { - p.setCooldown(Material.WITHER_SKELETON_SKULL, getCooldownDuration(p)); - } - - - if (p.getGameMode() != GameMode.CREATIVE) { - e.getItem().setAmount(e.getItem().getAmount() - 1); - lastJump.put(p, M.ms()); - } - - Vector dir = p.getEyeLocation().getDirection(); - Location spawn = p.getEyeLocation().add(new Vector(.5, -.5, .5)).add(dir); - p.getWorld().spawn(spawn, WitherSkull.class, entity -> { - sp.play(entity, Sound.ENTITY_WITHER_SHOOT, 1, 1); - entity.setRotation(p.getEyeLocation().getYaw(), p.getEyeLocation().getPitch()); - entity.setCharged(false); - entity.setBounce(false); - entity.setDirection(dir); - entity.setShooter(p); - xp(p, 100); - }); - getPlayer(p).getData().addStat("nether.skull-yeet.skulls-thrown", 1); - } - - @EventHandler - public void onEntityDeath(EntityDeathEvent e) { - LivingEntity dead = e.getEntity(); - if (dead.getLastDamageCause() instanceof EntityDamageByEntityEvent dbe - && dbe.getDamager() instanceof WitherSkull skull - && skull.getShooter() instanceof Player p - && hasAdaptation(p)) { - getPlayer(p).getData().addStat("nether.skull-yeet.skull-kills", 1); - - double distance = p.getLocation().distance(dead.getLocation()); - if (distance >= 40 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_nether_skull_long_bomb")) { - getPlayer(p).getAdvancementHandler().grant("challenge_nether_skull_long_bomb"); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().isEnabled(); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - - @Data - @NoArgsConstructor - @ConfigDescription("Throw Wither Skulls that explode on impact.") - public static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - public boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - private boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Cooldown for the Nether Skull Yeet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private int baseCooldown = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Level Cooldown for the Nether Skull Yeet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private int levelCooldown = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - private int baseCost = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - private double costFactor = 0.92; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - private int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - private int initialCost = 5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherWitherResist.java b/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherWitherResist.java deleted file mode 100644 index 8698e9966..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/nether/NetherWitherResist.java +++ /dev/null @@ -1,145 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.nether; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; - -import java.util.concurrent.ThreadLocalRandom; - -public class NetherWitherResist extends SimpleAdaptation { - - public NetherWitherResist() { - super("nether-wither-resist"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("nether.wither_resist.description")); - setDisplayName(Localizer.dLocalize("nether.wither_resist.name")); - setIcon(Material.NETHERITE_CHESTPLATE); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(9283); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WITHER_ROSE) - .key("challenge_nether_wither_100") - .title(Localizer.dLocalize("advancement.challenge_nether_wither_100.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_wither_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHER_STAR) - .key("challenge_nether_wither_1k") - .title(Localizer.dLocalize("advancement.challenge_nether_wither_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_wither_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_nether_wither_100", "nether.wither-resist.negated", 100, 300); - registerMilestone("challenge_nether_wither_1k", "nether.wither-resist.negated", 1000, 1000); - } - - @Override - public void addStats(int level, Element v) { - int chance = (int) (getConfig().basePieceChance + getConfig().getChanceAddition() * level); - v.addLore(C.GREEN + "+ " + chance + "%" + C.GRAY + Localizer.dLocalize("nether.wither_resist.lore1")); - v.addLore(C.GRAY + " " + Localizer.dLocalize("nether.wither_resist.lore1") + C.DARK_GRAY + Localizer.dLocalize("nether.wither_resist.lore2")); - } - - @EventHandler - public void onEntityDamage(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getCause() == EntityDamageEvent.DamageCause.WITHER && e.getEntity() instanceof Player p) { - if (!hasAdaptation(p)) - return; - double chance = getTotalChange(p); - if (ThreadLocalRandom.current().nextInt(101) <= chance) { - e.setCancelled(true); - getPlayer(p).getData().addStat("nether.wither-resist.negated", 1); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().isEnabled(); - } - - @Override - public void onTick() { - } - - private double getTotalChange(Player p) { - return getChance(p, EquipmentSlot.HEAD) + getChance(p, EquipmentSlot.CHEST) + getChance(p, EquipmentSlot.LEGS) + getChance(p, EquipmentSlot.FEET); - } - - private double getChance(Player p, EquipmentSlot slot) { - if (p.getEquipment() == null) - return 0.0; - ItemStack item = p.getEquipment().getItem(slot); - if (item.getType() == Material.NETHERITE_HELMET || item.getType() == Material.NETHERITE_CHESTPLATE || item.getType() == Material.NETHERITE_LEGGINGS || item.getType() == Material.NETHERITE_BOOTS) - return getConfig().basePieceChance + getConfig().chanceAddition * getLevel(p); - return 0.0D; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @Data - @NoArgsConstructor - @ConfigDescription("Wearing Netherite Armor has a chance to negate the wither effect.") - public static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - public boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - private boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - private int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - private double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - private int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - private int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Piece Chance for the Nether Wither Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double basePieceChance = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Chance Addition for the Nether Wither Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double chanceAddition = 5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeAutosmelt.java b/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeAutosmelt.java deleted file mode 100644 index d7d67f6b2..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeAutosmelt.java +++ /dev/null @@ -1,271 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.pickaxe; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.reflect.registries.Enchantments; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.concurrent.ThreadLocalRandom; - -public class PickaxeAutosmelt extends SimpleAdaptation { - public PickaxeAutosmelt() { - super("pickaxe-autosmelt"); - registerConfiguration(PickaxeAutosmelt.Config.class); - setDescription(Localizer.dLocalize("pickaxe.auto_smelt.description")); - setDisplayName(Localizer.dLocalize("pickaxe.auto_smelt.name")); - setIcon(Material.RAW_GOLD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(7444); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FURNACE) - .key("challenge_pickaxe_autosmelt_1k") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_autosmelt_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_autosmelt_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BLAST_FURNACE) - .key("challenge_pickaxe_autosmelt_25k") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_autosmelt_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_autosmelt_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_pickaxe_autosmelt_1k", "pickaxe.autosmelt.ores-smelted", 1000, 400); - registerMilestone("challenge_pickaxe_autosmelt_25k", "pickaxe.autosmelt.ores-smelted", 25000, 1500); - } - - static void autosmeltBlockDTI(Block b, Player p) { - int fortune = getFortuneOreMultiplier(p.getInventory().getItemInMainHand() - .getEnchantments().get(Enchantments.LOOT_BONUS_BLOCKS)); - SoundPlayer spw = SoundPlayer.of(b.getWorld()); - switch (b.getType()) { - case IRON_ORE, DEEPSLATE_IRON_ORE -> { - if (b.getLocation().getWorld() == null) { - return; - } - - b.setType(Material.AIR); - HashMap excessItems = p.getInventory().addItem(new ItemStack(Material.IRON_INGOT, fortune)); - excessItems.values().forEach(itemStack -> b.getLocation().getWorld().dropItemNaturally(b.getLocation(), itemStack)); - if (soundsEnabled()) { - spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); - } - if (particlesEnabled()) { - b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); - } - } - case GOLD_ORE, DEEPSLATE_GOLD_ORE -> { - if (b.getLocation().getWorld() == null) { - return; - } - - b.setType(Material.AIR); - HashMap excessItems = p.getInventory().addItem(new ItemStack(Material.GOLD_INGOT, fortune)); - excessItems.values().forEach(itemStack -> b.getLocation().getWorld().dropItemNaturally(b.getLocation(), itemStack)); - if (soundsEnabled()) { - spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); - } - if (particlesEnabled()) { - b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); - } - } - case COPPER_ORE, DEEPSLATE_COPPER_ORE -> { - if (b.getLocation().getWorld() == null) { - return; - } - b.setType(Material.AIR); - HashMap excessItems = p.getInventory().addItem(new ItemStack(Material.COPPER_INGOT, fortune)); - excessItems.values().forEach(itemStack -> b.getLocation().getWorld().dropItemNaturally(b.getLocation(), itemStack)); - if (soundsEnabled()) { - spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); - } - if (particlesEnabled()) { - b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); - } - } - - } - } - - static void autosmeltBlock(Block b, Player p) { - int fortune = getFortuneOreMultiplier(p.getInventory().getItemInMainHand() - .getEnchantments().get(Enchantments.LOOT_BONUS_BLOCKS)); - SoundPlayer spw = SoundPlayer.of(b.getWorld()); - switch (b.getType()) { - case IRON_ORE, DEEPSLATE_IRON_ORE -> { - - if (b.getLocation().getWorld() == null) { - return; - } - - b.setType(Material.AIR); - b.getLocation().getWorld().dropItemNaturally(b.getLocation(), new ItemStack(Material.IRON_INGOT, fortune)); - if (soundsEnabled()) { - spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); - } - if (particlesEnabled()) { - b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); - } - } - case GOLD_ORE, DEEPSLATE_GOLD_ORE -> { - if (b.getLocation().getWorld() == null) { - return; - } - - b.setType(Material.AIR); - b.getLocation().getWorld().dropItemNaturally(b.getLocation(), new ItemStack(Material.GOLD_INGOT, fortune)); - if (soundsEnabled()) { - spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); - } - if (particlesEnabled()) { - b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); - } - } - case COPPER_ORE, DEEPSLATE_COPPER_ORE -> { - if (b.getLocation().getWorld() == null) { - return; - } - b.setType(Material.AIR); - b.getLocation().getWorld().dropItemNaturally(b.getLocation(), new ItemStack(Material.COPPER_INGOT, fortune)); - if (soundsEnabled()) { - spw.play(b.getLocation(), Sound.BLOCK_LAVA_POP, 1, 1); - } - if (particlesEnabled()) { - b.getWorld().spawnParticle(Particle.LAVA, b.getLocation(), 3, 0.5, 0.5, 0.5); - } - } - - } - } - - // https://minecraft.fandom.com/wiki/Fortune?oldid=2359015#Ore - private static int getFortuneOreMultiplier(Integer fortuneLevel) { - if (fortuneLevel == null || fortuneLevel < 1) return 1; - - double averageBonusMultiplier = (1.0/(fortuneLevel+2) + (fortuneLevel+1)/2.0) - 1; - int sumOfBonusMultipliers = (fortuneLevel*(fortuneLevel+1))/2; - double chancePerMultiplier = averageBonusMultiplier/sumOfBonusMultipliers; - - int bonusMultiplier = ((int) (ThreadLocalRandom.current().nextDouble()/chancePerMultiplier)) + 1; - - return bonusMultiplier <= fortuneLevel ? bonusMultiplier+1 : 1; - } - - private static boolean particlesEnabled() { - AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); - return effects == null || effects.isParticlesEnabled(); - } - - private static boolean soundsEnabled() { - AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); - return effects == null || effects.isSoundsEnabled(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.auto_smelt.lore1")); - v.addLore(C.GREEN + "" + (level * 1.25) + C.GRAY + Localizer.dLocalize("pickaxe.auto_smelt.lore2")); - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(BlockBreakEvent e) { - Player p = e.getPlayer(); - if (e.isCancelled()) { - return; - } - if (!hasAdaptation(p)) { - return; - } - if (!e.getBlock().getBlockData().getMaterial().name().endsWith("_ORE") && !ItemListings.getSmeltOre().contains(e.getBlock().getType())) { - return; - } - if (!canBlockBreak(p, e.getBlock().getLocation())) { - return; - } - - PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("pickaxe"); - PlayerAdaptation adaptation = line != null ? line.getAdaptation("pickaxe-drop-to-inventory") : null; - if (adaptation != null && adaptation.getLevel() > 0) { - PickaxeAutosmelt.autosmeltBlockDTI(e.getBlock(), p); - } else { - PickaxeAutosmelt.autosmeltBlock(e.getBlock(), p); - } - getPlayer(p).getData().addStat("pickaxe.autosmelt.ores-smelted", 1); - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Automatically smelt mined ores with a chance for extra drops.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.95; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeChisel.java b/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeChisel.java deleted file mode 100644 index 6abfda941..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeChisel.java +++ /dev/null @@ -1,199 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.pickaxe; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.Particles; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.data.BlockData; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; - -public class PickaxeChisel extends SimpleAdaptation { - public PickaxeChisel() { - super("pickaxe-chisel"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("pickaxe.chisel.description")); - setDisplayName(Localizer.dLocalize("pickaxe.chisel.name")); - setIcon(Material.IRON_NUGGET); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(7433); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_PICKAXE) - .key("challenge_pickaxe_chisel_500") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_chisel_500.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_chisel_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_pickaxe_chisel_500", "pickaxe.chisel.extra-ores", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getDropChance(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("pickaxe.chisel.lore1")); - v.addLore(C.RED + "- " + getDamagePerBlock(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("pickaxe.chisel.lore2")); - } - - private int getCooldownTime(double levelPercent) { - return getConfig().cooldownTime; - } - - private double getDropChance(double levelPercent) { - return ((levelPercent) * getConfig().dropChanceFactor) + getConfig().dropChanceBase; - } - - private double getBreakChance(double levelPercent) { - return getConfig().breakChance; - } - - private int getDamagePerBlock(double levelPercent) { - return (int) (getConfig().damagePerBlockBase + (getConfig().damageFactorInverseMultiplier * ((1D - levelPercent)))); - } - - - @EventHandler - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - - if (e.getClickedBlock() != null && e.getAction().equals(Action.RIGHT_CLICK_BLOCK) && isPickaxe(p.getInventory().getItemInMainHand()) && hasAdaptation(p)) { - if (p.getInventory().getItemInMainHand().getEnchantments().containsKey(Enchantment.SILK_TOUCH) || p.getInventory().getItemInMainHand().getEnchantments().containsKey(Enchantment.MENDING)) { - return; - } - if (p.getCooldown(p.getInventory().getItemInMainHand().getType()) > 0) { - return; - } - if (!canBlockBreak(p, e.getClickedBlock().getLocation())) { - return; - } - BlockData b = e.getClickedBlock().getBlockData(); - if (isOre(b)) { - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 1.25f, 1.4f); - spw.play(p.getLocation(), Sound.BLOCK_METAL_HIT, 1.25f, 1.7f); - - p.setCooldown(p.getInventory().getItemInMainHand().getType(), getCooldownTime(getLevelPercent(p))); - damageHand(p, getDamagePerBlock(getLevelPercent(p))); - - Location c = p.rayTraceBlocks(8).getHitPosition().toLocation(p.getWorld()); - - ItemStack is = getDropFor(b); - if (M.r(getDropChance(getLevelPercent(p)))) { - if (areParticlesEnabled()) { - e.getClickedBlock().getWorld().spawnParticle(Particles.ITEM_CRACK, c, 14, 0.10, 0.01, 0.01, 0.1, is); - } - spw.play(p.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 1.25f, 0.787f); - spw.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_PLACE, 0.55f, 1.89f); - e.getClickedBlock().getWorld().dropItemNaturally(c.clone().subtract(p.getLocation().getDirection().clone().multiply(0.1)), is); - getPlayer(p).getData().addStat("pickaxe.chisel.extra-ores", 1); - } else { - if (areParticlesEnabled()) { - e.getClickedBlock().getWorld().spawnParticle(Particles.ITEM_CRACK, c, 3, 0.01, 0.01, 0.01, 0.1, is); - e.getClickedBlock().getWorld().spawnParticle(Particles.BLOCK_CRACK, c, 9, 0.1, 0.1, 0.1, e.getClickedBlock().getBlockData()); - } - } - - if (M.r(getBreakChance(getLevelPercent(p)))) { - spw.play(p.getLocation(), Sound.BLOCK_BASALT_BREAK, 1.25f, 0.4f); - spw.play(p.getLocation(), Sound.BLOCK_DEEPSLATE_PLACE, 1.25f, 0.887f); - e.getClickedBlock().breakNaturally(p.getInventory().getItemInMainHand()); - } - } - - } - } - - private ItemStack getDropFor(BlockData b) { - return switch (b.getMaterial()) { - case COAL_ORE, DEEPSLATE_COAL_ORE -> new ItemStack(Material.COAL); - case COPPER_ORE, DEEPSLATE_COPPER_ORE -> new ItemStack(Material.RAW_COPPER); - case GOLD_ORE, DEEPSLATE_GOLD_ORE, NETHER_GOLD_ORE -> new ItemStack(Material.RAW_GOLD); - case IRON_ORE, DEEPSLATE_IRON_ORE -> new ItemStack(Material.RAW_IRON); - case DIAMOND_ORE, DEEPSLATE_DIAMOND_ORE -> new ItemStack(Material.DIAMOND); - case LAPIS_ORE, DEEPSLATE_LAPIS_ORE -> new ItemStack(Material.LAPIS_LAZULI); - case EMERALD_ORE, DEEPSLATE_EMERALD_ORE -> new ItemStack(Material.EMERALD); - case NETHER_QUARTZ_ORE -> new ItemStack(Material.QUARTZ); - case REDSTONE_ORE -> new ItemStack(Material.REDSTONE); - - default -> new ItemStack(Material.AIR); - }; - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Right-click ores to chisel extra ore at a severe durability cost.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Pickaxe Chisel adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Time for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int cooldownTime = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Drop Chance Base for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dropChanceBase = 0.07; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Drop Chance Factor for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dropChanceFactor = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Break Chance for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double breakChance = 0.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Per Block Base for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damagePerBlockBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Factor Inverse Multiplier for the Pickaxe Chisel adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageFactorInverseMultiplier = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeDropToInventory.java b/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeDropToInventory.java deleted file mode 100644 index 929c62683..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeDropToInventory.java +++ /dev/null @@ -1,136 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.pickaxe; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockDropItemEvent; - -import java.util.List; - -public class PickaxeDropToInventory extends SimpleAdaptation { - public PickaxeDropToInventory() { - super("pickaxe-drop-to-inventory"); - registerConfiguration(PickaxeDropToInventory.Config.class); - setDescription(Localizer.dLocalize("pickaxe.drop_to_inventory.description")); - setDisplayName(Localizer.dLocalize("pickaxe.drop_to_inventory.name")); - setIcon(Material.MINECART); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(7944); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_pickaxe_dti_25k") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_dti_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_dti_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_pickaxe_dti_25k", "pickaxe.drop-to-inv.items-caught", 25000, 500); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("pickaxe.drop_to_inventory.lore1")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockDropItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - if (p.getGameMode() != GameMode.SURVIVAL) { - return; - } - if (!canBlockBreak(p, e.getBlock().getLocation())) { - return; - } - if (ItemListings.toolPickaxes.contains(p.getInventory().getItemInMainHand().getType())) { - List items = new KList<>(e.getItems()); - e.getItems().clear(); - int caught = 0; - for (Item i : items) { - sp.play(p.getLocation(), Sound.BLOCK_CALCITE_HIT, 0.05f, 0.01f); - if (!p.getInventory().addItem(i.getItemStack()).isEmpty()) { - p.getWorld().dropItem(p.getLocation(), i.getItemStack()); - } - caught++; - } - if (caught > 0) { - getPlayer(p).getData().addStat("pickaxe.drop-to-inv.items-caught", caught); - } - } - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Mined blocks drop directly into your inventory.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeQuarrySense.java b/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeQuarrySense.java deleted file mode 100644 index 5477a3ced..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeQuarrySense.java +++ /dev/null @@ -1,390 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.pickaxe; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import fr.skytasul.glowingentities.GlowingEntities; -import lombok.NoArgsConstructor; -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.entity.Slime; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; - -public class PickaxeQuarrySense extends SimpleAdaptation { - private static final String MARKER_META = "adapt-quarry-sense-marker"; - - public PickaxeQuarrySense() { - super("pickaxe-quarry-sense"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("pickaxe.quarry_sense.description")); - setDisplayName(Localizer.dLocalize("pickaxe.quarry_sense.name")); - setIcon(Material.MAP); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1200); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPYGLASS) - .key("challenge_pickaxe_quarry_200") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_quarry_200.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_quarry_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_pickaxe_quarry_200", "pickaxe.quarry-sense.scans", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getScanRadius(level)) + C.GRAY + " " + Localizer.dLocalize("pickaxe.quarry_sense.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getDurabilityCostPercent(level), 2) + C.GRAY + " " + Localizer.dLocalize("pickaxe.quarry_sense.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("pickaxe.quarry_sense.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - if (e.isCancelled()) { - return; - } - - Action action = e.getAction(); - if (action != Action.RIGHT_CLICK_BLOCK || e.getClickedBlock() == null) { - return; - } - - if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking()) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (!isEligiblePickaxe(hand) || p.hasCooldown(hand.getType())) { - return; - } - - int level = getLevel(p); - if (areParticlesEnabled()) { - p.spawnParticle(Particle.ENCHANT, p.getEyeLocation(), 14, 0.2, 0.25, 0.2, 0.15); - } - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.8f, 1.35f); - - int durabilityCost = getDurabilityCost(hand, level); - if (!applyPickaxeCost(p, hand, durabilityCost)) { - if (areParticlesEnabled()) { - p.spawnParticle(Particle.SMOKE, p.getEyeLocation(), 8, 0.2, 0.2, 0.2, 0.03); - } - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_ANVIL_PLACE, 0.5f, 0.65f); - return; - } - - List ores = findNearbyOres(p.getLocation(), getScanRadius(level), getMaxHighlights(level)); - if (ores.isEmpty()) { - if (areParticlesEnabled()) { - p.spawnParticle(Particle.SMOKE, p.getEyeLocation(), 12, 0.22, 0.22, 0.22, 0.02); - } - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.6f, 0.75f); - p.setCooldown(hand.getType(), getCooldownTicks(level)); - e.setCancelled(true); - return; - } - - for (Block ore : ores) { - showOreMarker(p, ore, getHighlightTicks(level)); - } - - p.setCooldown(hand.getType(), getCooldownTicks(level)); - if (areParticlesEnabled()) { - p.spawnParticle(Particle.GLOW, p.getEyeLocation(), 8, 0.15, 0.15, 0.15, 0.01); - } - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.9f, 1.6f); - xp(p, ores.size() * getConfig().xpPerFoundOre); - getPlayer(p).getData().addStat("pickaxe.quarry-sense.scans", 1); - e.setCancelled(true); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageEvent e) { - if (e.getEntity() instanceof Slime slime && slime.hasMetadata(MARKER_META)) { - e.setCancelled(true); - } - } - - private List findNearbyOres(Location origin, int radius, int maxResults) { - List ores = new ArrayList<>(); - for (int x = -radius; x <= radius; x++) { - for (int y = -radius; y <= radius; y++) { - for (int z = -radius; z <= radius; z++) { - Block b = origin.getWorld().getBlockAt(origin.getBlockX() + x, origin.getBlockY() + y, origin.getBlockZ() + z); - if (!isOre(b.getBlockData())) { - continue; - } - - ores.add(b); - } - } - } - - ores.sort(Comparator.comparingDouble(b -> b.getLocation().distanceSquared(origin))); - if (ores.size() > maxResults) { - return new ArrayList<>(ores.subList(0, maxResults)); - } - - return ores; - } - - private void showOreMarker(Player p, Block ore, int durationTicks) { - GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); - if (glowingEntities == null) { - showFallbackMarker(p, ore, durationTicks); - return; - } - - Slime slime = ore.getWorld().spawn(ore.getLocation().add(0.5, 0.5, 0.5), Slime.class, s -> { - s.setInvulnerable(true); - s.setCollidable(false); - s.setGravity(false); - s.setSilent(true); - s.setAI(false); - s.setSize(2); - s.setRotation(0, 0); - s.setMetadata(MARKER_META, new FixedMetadataValue(Adapt.instance, true)); - s.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 0, false, false)); - }); - - try { - glowingEntities.setGlowing(slime, p, ChatColor.AQUA); - } catch (ReflectiveOperationException ignored) { - slime.remove(); - showFallbackMarker(p, ore, durationTicks); - return; - } - - if (areParticlesEnabled()) { - - p.spawnParticle(Particle.GLOW, ore.getLocation().add(0.5, 0.5, 0.5), 20, 0.25, 0.25, 0.25, 0.001); - - } - if (areParticlesEnabled()) { - p.spawnParticle(Particle.END_ROD, ore.getLocation().add(0.5, 0.5, 0.5), 8, 0.15, 0.15, 0.15, 0.003); - - } - J.s(() -> { - try { - glowingEntities.unsetGlowing(slime, p); - } catch (ReflectiveOperationException ignored) { - } - slime.remove(); - }, durationTicks); - } - - private void showFallbackMarker(Player p, Block ore, int durationTicks) { - Location loc = ore.getLocation().add(0.5, 0.5, 0.5); - for (int t = 0; t <= durationTicks; t += 8) { - J.s(() -> { - if (!p.isOnline()) { - return; - } - - if (areParticlesEnabled()) { - - p.spawnParticle(Particle.GLOW, loc, 14, 0.22, 0.22, 0.22, 0.001); - - } - if (areParticlesEnabled()) { - p.spawnParticle(Particle.END_ROD, loc, 4, 0.12, 0.12, 0.12, 0.001); - } - }, t); - } - } - - private boolean isEligiblePickaxe(ItemStack hand) { - if (!isItem(hand)) { - return false; - } - - return switch (hand.getType()) { - case IRON_PICKAXE, DIAMOND_PICKAXE, NETHERITE_PICKAXE -> true; - default -> false; - }; - } - - private boolean applyPickaxeCost(Player p, ItemStack hand, int durabilityCost) { - if (getConfig().costsReduceMaxDurability) { - return tryReduceMaxDurability(p, hand, durabilityCost); - } - - return tryDamagePickaxe(p, hand, durabilityCost); - } - - private boolean tryDamagePickaxe(Player p, ItemStack hand, int durabilityCost) { - if (!(hand.getItemMeta() instanceof Damageable damageable)) { - return false; - } - - int maxDurability = hand.getType().getMaxDurability(); - int currentDamage = damageable.getDamage(); - if (currentDamage + durabilityCost >= maxDurability) { - return false; - } - - damageable.setDamage(currentDamage + durabilityCost); - hand.setItemMeta(damageable); - p.getInventory().setItemInMainHand(hand); - return true; - } - - private boolean tryReduceMaxDurability(Player p, ItemStack hand, int durabilityCost) { - if (!(hand.getItemMeta() instanceof Damageable damageable)) { - return false; - } - - int fallbackMax = Math.max(1, hand.getType().getMaxDurability()); - int currentMax = damageable.hasMaxDamage() ? Math.max(1, damageable.getMaxDamage()) : fallbackMax; - int currentDamage = Math.max(0, damageable.getDamage()); - int newMax = currentMax - durabilityCost; - if (newMax <= currentDamage + 1) { - return false; - } - - damageable.setMaxDamage(Math.max(1, newMax)); - hand.setItemMeta(damageable); - p.getInventory().setItemInMainHand(hand); - return true; - } - - private int getScanRadius(int level) { - return Math.max(4, (int) Math.round(getConfig().scanRadiusBase + (getLevelPercent(level) * getConfig().scanRadiusFactor))); - } - - private int getMaxHighlights(int level) { - return Math.max(1, (int) Math.round(getConfig().maxHighlightsBase + (getLevelPercent(level) * getConfig().maxHighlightsFactor))); - } - - private int getHighlightTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().highlightTicksBase + (getLevelPercent(level) * getConfig().highlightTicksFactor))); - } - - private int getCooldownTicks(int level) { - return Math.max(10, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - private int getDurabilityCost(ItemStack hand, int level) { - int maxDurability = Math.max(1, hand.getType().getMaxDurability()); - return Math.max(1, (int) Math.round(maxDurability * getDurabilityCostPercent(level))); - } - - private double getDurabilityCostPercent(int level) { - return Math.max(getConfig().minDurabilityCostPercent, - getConfig().durabilityCostPercentBase - (getLevelPercent(level) * getConfig().durabilityCostPercentFactor)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-right-click a block with an iron+ pickaxe to highlight nearby ores.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Costs Reduce Max Durability for the Pickaxe Quarry Sense adaptation.", impact = "True reduces max durability instead of adding normal damage.") - boolean costsReduceMaxDurability = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Scan Radius Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double scanRadiusBase = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Scan Radius Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double scanRadiusFactor = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Highlights Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxHighlightsBase = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Highlights Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxHighlightsFactor = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Highlight Ticks Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double highlightTicksBase = 90; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Highlight Ticks Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double highlightTicksFactor = 90; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 60; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 40; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Percent Base for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durabilityCostPercentBase = 0.006; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Percent Factor for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durabilityCostPercentFactor = 0.0045; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Durability Cost Percent for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minDurabilityCostPercent = 0.001; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Found Ore for the Pickaxe Quarry Sense adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerFoundOre = 6; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeSilkSpawner.java b/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeSilkSpawner.java deleted file mode 100644 index e8dc1690b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeSilkSpawner.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.volmit.adapt.content.adaptation.pickaxe; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.RNG; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Item; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockDropItemEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.BlockStateMeta; - -public class PickaxeSilkSpawner extends SimpleAdaptation { - private final RNG rng = new RNG(); - - public PickaxeSilkSpawner() { - super("pickaxe-silk-spawner"); - registerConfiguration(PickaxeSilkSpawner.Config.class); - setDescription(Localizer.dLocalize("pickaxe.silk_spawner.description")); - setDisplayName(Localizer.dLocalize("pickaxe.silk_spawner.name")); - setIcon(Material.SPAWNER); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(8444); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPAWNER) - .key("challenge_pickaxe_spawner_10") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_spawner_10.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_spawner_10.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SPAWNER) - .key("challenge_pickaxe_spawner_50") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_spawner_50.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_spawner_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_pickaxe_spawner_10", "pickaxe.silk-spawner.spawners-collected", 10, 500); - registerMilestone("challenge_pickaxe_spawner_50", "pickaxe.silk-spawner.spawners-collected", 50, 2000); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onBlockBreak(BlockBreakEvent event) { - var player = event.getPlayer(); - var block = event.getBlock(); - if (!event.isDropItems() || !hasAdaptation(player) || block.getType() != Material.SPAWNER || !canBlockBreak(player, event.getBlock().getLocation())) - return; - var level = getLevel(player); - if (level == 1 && !player.getInventory().getItemInMainHand().getEnchantments().containsKey(Enchantment.SILK_TOUCH)) { - return; - } else if (level > 1 && !player.isSneaking()) { - return; - } - - event.setDropItems(false); - var spawner = new ItemStack(Material.SPAWNER); - var state = block.getState(); - if (spawner.getItemMeta() instanceof BlockStateMeta meta) { - meta.setBlockState(state); - spawner.setItemMeta(meta); - } - - var loc = block.getLocation().add( - rng.d(-0.25D, 0.25D), - rng.d(-0.25D, 0.25D) - 0.125D, - rng.d(-0.25D, 0.25D) - ); - var item = block.getWorld().createEntity(loc, Item.class); - item.setItemStack(spawner); - item.setOwner(player.getUniqueId()); - - var dropEvent = new BlockDropItemEvent(block, state, player, new KList().qadd(item)); - Bukkit.getPluginManager().callEvent(dropEvent); - if (dropEvent.isCancelled()) { - for (Item i : dropEvent.getItems()) { - if (i.isValid()) i.remove(); - } - } else { - for (Item i : dropEvent.getItems()) { - if (!i.isValid()) block.getWorld().addEntity(i); - } - getPlayer(player).getData().addStat("pickaxe.silk-spawner.spawners-collected", 1); - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.silk_spawner.lore" + (level < 2 ? 1 : 2))); - } - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Spawners drop when broken with silk touch or while sneaking.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.95; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeVeinminer.java b/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeVeinminer.java deleted file mode 100644 index 315ef44a9..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/pickaxe/PickaxeVeinminer.java +++ /dev/null @@ -1,215 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.pickaxe; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -import static com.volmit.adapt.util.data.Metadata.VEIN_MINED; - -public class PickaxeVeinminer extends SimpleAdaptation { - public PickaxeVeinminer() { - super("pickaxe-veinminer"); - registerConfiguration(PickaxeVeinminer.Config.class); - setDescription(Localizer.dLocalize("pickaxe.vein_miner.description")); - setDisplayName(Localizer.dLocalize("pickaxe.vein_miner.name")); - setIcon(Material.IRON_PICKAXE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(8484); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_PICKAXE) - .key("challenge_pickaxe_veinminer_2500") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_veinminer_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_veinminer_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_PICKAXE) - .key("challenge_pickaxe_veinminer_20") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_veinminer_20.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_veinminer_20.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_pickaxe_veinminer_2500", "pickaxe.veinminer.ores-veinmined", 2500, 500); - } - - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("pickaxe.vein_miner.lore1")); - v.addLore(C.GREEN + "" + (level + getConfig().baseRange) + C.GRAY + " " + Localizer.dLocalize("pickaxe.vein_miner.lore2")); - v.addLore(C.ITALIC + Localizer.dLocalize("pickaxe.vein_miner.lore3")); - } - - private int getRadius(int lvl) { - return lvl + getConfig().baseRange; - } - - @EventHandler - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - if (VEIN_MINED.get(e.getBlock())) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - if (!p.isSneaking()) { - return; - } - - if (!e.getBlock().getBlockData().getMaterial().name().endsWith("_ORE")) { - if (!e.getBlock().getType().equals(Material.OBSIDIAN)) { - return; - } - } - VEIN_MINED.add(e.getBlock()); - - Block block = e.getBlock(); - Map blockMap = new HashMap<>(); - blockMap.put(block.getLocation(), block); - - int radius = getRadius(getLevel(p)); - for (int i = 0; i < radius; i++) { - for (int x = -i; x <= i; x++) { - for (int y = -i; y <= i; y++) { - for (int z = -i; z <= i; z++) { - Block b = block.getRelative(x, y, z); - if (b.getType() == block.getType()) { - if (!canBlockBreak(p, e.getBlock().getLocation())) { - continue; - } - blockMap.put(b.getLocation(), b); - } - } - } - } - } - - int veinSize = blockMap.size(); - getPlayer(p).getData().addStat("pickaxe.veinminer.ores-veinmined", veinSize); - if (veinSize >= 20 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_pickaxe_veinminer_20")) { - getPlayer(p).getAdvancementHandler().grant("challenge_pickaxe_veinminer_20"); - } - - J.s(() -> { - for (Location l : blockMap.keySet()) { - if (!canBlockBreak(p, l)) { - Adapt.verbose("Player " + p.getName() + " doesn't have permission."); - continue; - } - Block b = block.getWorld().getBlockAt(l); - PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("pickaxe"); - PlayerAdaptation autoSmelt = line != null ? line.getAdaptation("pickaxe-autosmelt") : null; - PlayerAdaptation drop2Inv = line != null ? line.getAdaptation("pickaxe-drop-to-inventory") : null; - VEIN_MINED.add(b); - if (autoSmelt != null && autoSmelt.getLevel() > 0 && ItemListings.getSmeltOre().contains(b.getType())) { - if (drop2Inv != null && drop2Inv.getLevel() > 0) { - PickaxeAutosmelt.autosmeltBlockDTI(b, p); - } else { - PickaxeAutosmelt.autosmeltBlock(b, p); - } - } else { - if (drop2Inv != null && drop2Inv.getLevel() > 0) { - b.getDrops(p.getInventory().getItemInMainHand(), p).forEach(item -> { - HashMap extra = p.getInventory().addItem(item); - extra.forEach((k, v) -> p.getWorld().dropItem(p.getLocation(), v)); - }); - b.setType(Material.AIR); - } else { - b.breakNaturally(p.getItemInUse()); - SoundPlayer spw = SoundPlayer.of(block.getWorld()); - spw.play(block.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 0.4f, 0.25f); - if (areParticlesEnabled()) { - block.getWorld().spawnParticle(Particle.ASH, b.getLocation().add(0.5, 0.5, 0.5), 25, 0.5, 0.5, 0.5, 0.1); - } - } - } - VEIN_MINED.remove(b); - } - VEIN_MINED.remove(block); - }); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Break connected ore veins at once while sneaking.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Pickaxe Veinminer adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.95; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Range for the Pickaxe Veinminer adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int baseRange = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedArrowRecovery.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedArrowRecovery.java deleted file mode 100644 index 19e5adabd..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedArrowRecovery.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.Enchantments; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Arrow; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityShootBowEvent; -import org.bukkit.event.entity.ProjectileHitEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -import static xyz.xenondevs.particle.utils.MathUtils.RANDOM; - -public class RangedArrowRecovery extends SimpleAdaptation { - private final Map shotArrows; - - public RangedArrowRecovery() { - super("ranged-recovery"); - registerConfiguration(RangedArrowRecovery.Config.class); - setDescription(Localizer.dLocalize("ranged.arrow_recovery.description")); - setDisplayName(Localizer.dLocalize("ranged.arrow_recovery.name")); - setIcon(Material.ARROW); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - shotArrows = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ARROW) - .key("challenge_ranged_arrow_500") - .title(Localizer.dLocalize("advancement.challenge_ranged_arrow_500.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_arrow_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_ranged_arrow_10k") - .title(Localizer.dLocalize("advancement.challenge_ranged_arrow_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_arrow_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_ranged_arrow_500", "ranged.arrow-recovery.arrows-recovered", 500, 300); - registerMilestone("challenge_ranged_arrow_10k", "ranged.arrow-recovery.arrows-recovered", 10000, 1000); - } - - @EventHandler - public void onEntityShootBow(EntityShootBowEvent event) { - if (event.getEntity() instanceof Player player && hasAdaptation(player)) { - if (!event.getBow().containsEnchantment(Enchantments.ARROW_INFINITE)) { - if (event.getProjectile() instanceof Arrow arrow) { - shotArrows.put(arrow, player); - } - } - } - } - - @EventHandler - public void onProjectileHit(ProjectileHitEvent event) { - if (event.getEntity() instanceof Arrow arrow) { - Player shooter = shotArrows.get(arrow); - if (shooter != null && hasAdaptation(shooter)) { - int level = getLevel(shooter); - double chance = getConfig().hitChance[level - 1] / 100.0; - if (RANDOM.nextDouble() < chance) { - ItemStack arrowStack = new ItemStack(Material.ARROW, 1); - shooter.getInventory().addItem(arrowStack); - getPlayer(shooter).getData().addStat("ranged.arrow-recovery.arrows-recovered", 1); - Adapt.info("Arrow added to inventory."); - } - } - shotArrows.remove(arrow); - } - } - - private double chancePerLevel(int level) { - return (getConfig().hitChance[level - 1] / 100.0); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("ranged.arrow_recovery.lore1")); - v.addLore(C.GREEN + Localizer.dLocalize("ranged.arrow_recovery.lore2") + chancePerLevel(level)); - } - - @NoArgsConstructor - @ConfigDescription("Chance to recover arrows after hitting or killing an enemy.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.78; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hit Chance for the Ranged Arrow Recovery adaptation.", impact = "Add or remove entries to control which values are included.") - double[] hitChance = {10, 20, 30, 40, 50, 60, 70, 80}; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedFloaters.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedFloaters.java deleted file mode 100644 index 4449a6c87..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedFloaters.java +++ /dev/null @@ -1,177 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.concurrent.ThreadLocalRandom; - -public class RangedFloaters extends SimpleAdaptation { - public RangedFloaters() { - super("ranged-floaters"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("ranged.floaters.description")); - setDisplayName(Localizer.dLocalize("ranged.floaters.name")); - setIcon(Material.SHULKER_SHELL); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHULKER_SHELL) - .key("challenge_ranged_floaters_200") - .title(Localizer.dLocalize("advancement.challenge_ranged_floaters_200.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_floaters_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_ranged_floaters_200", "ranged.floaters.targets-levitated", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getProcChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.floaters.lore1")); - v.addLore(C.GREEN + "+ " + Form.duration(getDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("ranged.floaters.lore2")); - v.addLore(C.GREEN + "+ " + (1 + getAmplifier(level)) + C.GRAY + " " + Localizer.dLocalize("ranged.floaters.lore3")); - } - - @EventHandler(priority = EventPriority.HIGH) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - - if (!(e.getDamager() instanceof Projectile projectile)) { - return; - } - - if (!(projectile.getShooter() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - if (!(e.getEntity() instanceof LivingEntity target)) { - return; - } - - int level = getLevel(p); - if (ThreadLocalRandom.current().nextDouble() > getProcChance(level)) { - return; - } - - target.addPotionEffect(new PotionEffect( - PotionEffectType.LEVITATION, - getDurationTicks(level), - getAmplifier(level), - true, - true, - true - ), true); - getPlayer(p).getData().addStat("ranged.floaters.targets-levitated", 1); - - if (areParticlesEnabled()) { - target.getWorld().spawnParticle(Particle.END_ROD, target.getLocation().add(0, 1, 0), 10, 0.2, 0.5, 0.2, 0.02); - } - - SoundPlayer.of(target.getWorld()).play(target.getLocation(), Sound.ENTITY_SHULKER_SHOOT, 0.6f, 1.45f); - xp(p, getConfig().skillXpOnProc); - } - - private double getProcChance(int level) { - return Math.min(getConfig().maxChance, getConfig().chanceBase + (getLevelPercent(level) * getConfig().chanceFactor)); - } - - private int getDurationTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().durationTicksBase + (getLevelPercent(level) * getConfig().durationTicksFactor))); - } - - private int getAmplifier(int level) { - return Math.max(0, (int) Math.floor(getLevelPercent(level) * getConfig().maxAmplifier)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Projectiles have a chance to apply levitation to targets.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Ranged Floaters adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.78; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Chance Base for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double chanceBase = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Chance Factor for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double chanceFactor = 0.58; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Chance for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxChance = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Ticks Base for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durationTicksBase = 26.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Ticks Factor for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durationTicksFactor = 110.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Amplifier for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxAmplifier = 1.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Skill Xp On Proc for the Ranged Floaters adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double skillXpOnProc = 8.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedForce.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedForce.java deleted file mode 100644 index 11c2027b8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedForce.java +++ /dev/null @@ -1,154 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; - -public class RangedForce extends SimpleAdaptation { - - public RangedForce() { - super("ranged-force"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("ranged.force_shot.description")); - setDisplayName(Localizer.dLocalize("ranged.force_shot.name")); - setIcon(Material.TIPPED_ARROW); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(4900); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_force_30") - .title(Localizer.dLocalize("ranged.force_shot.advancementname")) - .description(Localizer.dLocalize("ranged.force_shot.advancementlore")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_ranged_force_500") - .title(Localizer.dLocalize("advancement.challenge_ranged_force_500.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_force_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_ranged_force_500", "ranged.force.long-range-hits", 500, 500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getSpeed(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.force_shot.lore1")); - } - - private double getSpeed(double factor) { - return (factor * getConfig().speedFactor); - } - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Projectile r && r.getShooter() instanceof Player p && hasAdaptation(p)) { - Location a = e.getEntity().getLocation().clone(); - Location b = p.getLocation().clone(); - a.setY(0); - b.setY(0); - xp(p, 5); - double distSq = a.distanceSquared(b); - - if (distSq > 10 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_force_30")) { - getPlayer(p).getAdvancementHandler().grant("challenge_force_30"); - xp(p, getConfig().challengeRewardLongShotReward, "challenge-long-shot"); - } - - if (distSq > 900) { - getPlayer(p).getData().addStat("ranged.force.long-range-hits", 1); - } - } - } - - @EventHandler - public void on(ProjectileLaunchEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity().getShooter() instanceof Player p) { - if (hasAdaptation(p)) { - double factor = getLevelPercent(p); - e.getEntity().setVelocity(e.getEntity().getVelocity().clone().multiply(1 + getSpeed(factor))); - SoundPlayer spw = SoundPlayer.of(e.getEntity().getWorld()); - spw.play(e.getEntity().getLocation(), Sound.ENTITY_SNOWBALL_THROW, 0.5f + ((float) factor * 0.25f), 0.7f + (float) (factor / 2f)); - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Shoot projectiles further and faster.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.225; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Reward Long Shot Reward for the Ranged Force adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeRewardLongShotReward = 2000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Factor for the Ranged Force adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedFactor = 1.135; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedLungeShot.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedLungeShot.java deleted file mode 100644 index 9da5c66ee..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedLungeShot.java +++ /dev/null @@ -1,143 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.AbstractArrow; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.util.Vector; - -public class RangedLungeShot extends SimpleAdaptation { - public RangedLungeShot() { - super("ranged-lunge-shot"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("ranged.lunge_shot.description")); - setDisplayName(Localizer.dLocalize("ranged.lunge_shot.name")); - setIcon(Material.RABBIT_HIDE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(4859); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ARROW) - .key("challenge_ranged_lunge_200") - .title(Localizer.dLocalize("advancement.challenge_ranged_lunge_200.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_lunge_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.FEATHER) - .key("challenge_ranged_lunge_2500") - .title(Localizer.dLocalize("advancement.challenge_ranged_lunge_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_lunge_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_ranged_lunge_200", "ranged.lunge-shot.lunges", 200, 300); - registerMilestone("challenge_ranged_lunge_2500", "ranged.lunge-shot.lunges", 2500, 1000); - } - - private double getSpeed(double factor) { - return (factor * getConfig().factor); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getSpeed(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.lunge_shot.lore1")); - } - - @EventHandler - public void on(ProjectileLaunchEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity().getShooter() instanceof Player p) { - if (e.getEntity() instanceof AbstractArrow a) { - if (hasAdaptation(p)) { - if (!p.isOnGround()) { - Vector velocity = p.getPlayer().getLocation().getDirection().normalize().multiply(getSpeed(getLevelPercent(p))); - p.setVelocity(p.getVelocity().subtract(velocity)); - getPlayer(p).getData().addStat("ranged.lunge-shot.lunges", 1); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_TURTLE, 1f, 0.75f); - spw.play(p.getLocation(), Sound.ITEM_CROSSBOW_SHOOT, 1f, 1.95f); - - for (int i = 0; i < 9; i++) { - Vector v = velocity.clone().add(Vector.getRandom().subtract(Vector.getRandom()).multiply(0.3)).normalize(); - if (areParticlesEnabled()) { - - p.getWorld().spawnParticle(Particle.CLOUD, p.getLocation().clone().add(0, 1, 0), 0, v.getX(), v.getY(), v.getZ(), 0.2); - } - } - } - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("While falling, firing arrows launches you in a random direction.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Ranged Lunge Shot adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Factor for the Ranged Lunge Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double factor = 0.935; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedPiercing.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedPiercing.java deleted file mode 100644 index 8e08aaf8b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedPiercing.java +++ /dev/null @@ -1,148 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.AbstractArrow; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class RangedPiercing extends SimpleAdaptation { - private final Map arrowHitCounts = new HashMap<>(); - - public RangedPiercing() { - super("ranged-piercing"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("ranged.arrow_piercing.description")); - setDisplayName(Localizer.dLocalize("ranged.arrow_piercing.name")); - setIcon(Material.FLETCHING_TABLE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(4791); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_ranged_piercing_500") - .title(Localizer.dLocalize("advancement.challenge_ranged_piercing_500.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_piercing_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_ranged_piercing_4") - .title(Localizer.dLocalize("advancement.challenge_ranged_piercing_4.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_piercing_4.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_ranged_piercing_500", "ranged.piercing.extra-hits", 500, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + level + C.GRAY + " " + Localizer.dLocalize("ranged.arrow_piercing.lore1")); - } - - @EventHandler - public void on(ProjectileLaunchEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity().getShooter() instanceof Player p) { - if (e.getEntity() instanceof AbstractArrow a) { - xp(p, 5); - if (hasAdaptation(p)) { - a.setPierceLevel(((AbstractArrow) e.getEntity()).getPierceLevel() + getLevel(p)); - } - } - } - } - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Projectile projectile && projectile instanceof AbstractArrow arrow && arrow.getShooter() instanceof Player p && hasAdaptation(p)) { - if (arrow.getPierceLevel() > 0) { - UUID arrowId = arrow.getUniqueId(); - int hits = arrowHitCounts.getOrDefault(arrowId, 0) + 1; - arrowHitCounts.put(arrowId, hits); - if (hits > 1) { - getPlayer(p).getData().addStat("ranged.piercing.extra-hits", 1); - } - if (hits >= 4 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_ranged_piercing_4")) { - getPlayer(p).getAdvancementHandler().grant("challenge_ranged_piercing_4"); - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Projectiles pierce through multiple targets.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedPinningShot.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedPinningShot.java deleted file mode 100644 index e6e944b14..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedPinningShot.java +++ /dev/null @@ -1,217 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class RangedPinningShot extends SimpleAdaptation { - private final Map targetProcTimes = new HashMap<>(); - - public RangedPinningShot() { - super("ranged-pinning-shot"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("ranged.pinning_shot.description")); - setDisplayName(Localizer.dLocalize("ranged.pinning_shot.name")); - setIcon(Material.TRIPWIRE_HOOK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2200); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ARROW) - .key("challenge_ranged_pinning_300") - .title(Localizer.dLocalize("advancement.challenge_ranged_pinning_300.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_pinning_300.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_ranged_pinning_300", "ranged.pinning-shot.targets-pinned", 300, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getProcChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.pinning_shot.lore1")); - v.addLore(C.GREEN + "+ " + Form.duration(getDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("ranged.pinning_shot.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getReapplyCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("ranged.pinning_shot.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Projectile projectile) || !(projectile.getShooter() instanceof Player p) || !hasAdaptation(p) || !(e.getEntity() instanceof LivingEntity target)) { - return; - } - - if (target instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - return; - } - } else if (!canPVE(p, target.getLocation())) { - return; - } - - long now = System.currentTimeMillis(); - cleanupExpired(now); - int level = getLevel(p); - long reapply = getReapplyCooldownMillis(level); - Long last = targetProcTimes.get(target.getUniqueId()); - if (last != null && last + reapply > now) { - return; - } - - if (ThreadLocalRandom.current().nextDouble() > getProcChance(level)) { - return; - } - - targetProcTimes.put(target.getUniqueId(), now); - target.addPotionEffect(new PotionEffect(PotionEffectType.SLOWNESS, getDurationTicks(level), getAmplifier(level), true, true, true), true); - getPlayer(p).getData().addStat("ranged.pinning-shot.targets-pinned", 1); - - if (getConfig().dampenVelocityOnProc) { - Vector v = target.getVelocity(); - target.setVelocity(new Vector(v.getX() * getConfig().horizontalVelocityFactor, v.getY(), v.getZ() * getConfig().horizontalVelocityFactor)); - } - - if (areParticlesEnabled()) { - - target.getWorld().spawnParticle(Particle.CRIT, target.getLocation().add(0, 0.9, 0), 18, 0.3, 0.45, 0.3, 0.08); - - } - if (areParticlesEnabled()) { - target.getWorld().spawnParticle(Particle.ENCHANT, target.getLocation().add(0, 1.0, 0), 28, 0.35, 0.5, 0.35, 0.35); - - } - SoundPlayer sp = SoundPlayer.of(target.getWorld()); - sp.play(target.getLocation(), Sound.BLOCK_BELL_USE, 1.1f, 0.48f); - sp.play(target.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.7f, 0.55f); - xp(p, getConfig().xpOnProc); - } - - private void cleanupExpired(long now) { - if (targetProcTimes.size() < getConfig().cleanupThreshold) { - return; - } - - targetProcTimes.entrySet().removeIf(entry -> entry.getValue() + getConfig().entryTtlMillis < now); - } - - private double getProcChance(int level) { - return Math.min(getConfig().maxProcChance, getConfig().procChanceBase + (getLevelPercent(level) * getConfig().procChanceFactor)); - } - - private int getDurationTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().durationTicksBase + (getLevelPercent(level) * getConfig().durationTicksFactor))); - } - - private int getAmplifier(int level) { - return Math.max(0, (int) Math.floor(getConfig().amplifierBase + (getLevelPercent(level) * getConfig().amplifierFactor))); - } - - private long getReapplyCooldownMillis(int level) { - return Math.max(1000, (long) Math.round(getConfig().reapplyCooldownMillisBase - (getLevelPercent(level) * getConfig().reapplyCooldownMillisFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Projectiles can pin targets with heavy slowness.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Dampen Velocity On Proc for the Ranged Pinning Shot adaptation.", impact = "True enables this behavior and false disables it.") - boolean dampenVelocityOnProc = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.74; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Proc Chance Base for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double procChanceBase = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Proc Chance Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double procChanceFactor = 0.42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Proc Chance for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxProcChance = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Ticks Base for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durationTicksBase = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration Ticks Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durationTicksFactor = 90; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Amplifier Base for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double amplifierBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Amplifier Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double amplifierFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reapply Cooldown Millis Base for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reapplyCooldownMillisBase = 5000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Reapply Cooldown Millis Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double reapplyCooldownMillisFactor = 2800; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Horizontal Velocity Factor for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double horizontalVelocityFactor = 0.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cleanup Threshold for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int cleanupThreshold = 128; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Entry Ttl Millis for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long entryTtlMillis = 60000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Proc for the Ranged Pinning Shot adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnProc = 12; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedRicochetBolt.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedRicochetBolt.java deleted file mode 100644 index c501fdb4d..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedRicochetBolt.java +++ /dev/null @@ -1,447 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.AbstractArrow; -import org.bukkit.entity.Arrow; -import org.bukkit.entity.Egg; -import org.bukkit.entity.EnderPearl; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.entity.Snowball; -import org.bukkit.entity.SpectralArrow; -import org.bukkit.entity.ThrownExpBottle; -import org.bukkit.entity.ThrownPotion; -import org.bukkit.entity.Trident; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.ProjectileHitEvent; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.metadata.MetadataValue; -import org.bukkit.util.Vector; - -public class RangedRicochetBolt extends SimpleAdaptation { - private static final String RICOCHET_COUNT_META = "adapt-ricochet-count"; - private static final String RICOCHET_MAX_META = "adapt-ricochet-max"; - private static final String BONUS_DAMAGE_META = "adapt-ricochet-bonus-damage"; - - public RangedRicochetBolt() { - super("ranged-ricochet-bolt"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("ranged.ricochet_bolt.description")); - setDisplayName(Localizer.dLocalize("ranged.ricochet_bolt.name")); - setIcon(Material.SPECTRAL_ARROW); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_ranged_ricochet_kills_50") - .title(Localizer.dLocalize("advancement.challenge_ranged_ricochet_kills_50.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_ricochet_kills_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_ranged_ricochet_kills_500") - .title(Localizer.dLocalize("advancement.challenge_ranged_ricochet_kills_500.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_ricochet_kills_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_ranged_ricochet_kills_50", "ranged.ricochet-bolt.ricochet-kills", 50, 500); - registerMilestone("challenge_ranged_ricochet_kills_500", "ranged.ricochet-bolt.ricochet-kills", 500, 2000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getMaxRicochets(level) + C.GRAY + " " + Localizer.dLocalize("ranged.ricochet_bolt.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getSpeedBonusPerRicochet(level), 0) + C.GRAY + " " + Localizer.dLocalize("ranged.ricochet_bolt.lore2")); - v.addLore(C.GREEN + "+ " + Form.f(getDamageBonusPerRicochet(level), 2) + C.GRAY + " " + Localizer.dLocalize("ranged.ricochet_bolt.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(ProjectileHitEvent e) { - if (!(e.getEntity() instanceof Projectile projectile) || !(projectile.getShooter() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - if (e.getHitBlock() == null || !supportsRicochet(projectile)) { - return; - } - - int level = getLevel(p); - int ricochetCount = Math.max(0, getMetadataInt(projectile, RICOCHET_COUNT_META, 0)); - int maxRicochets = Math.max(1, getMetadataInt(projectile, RICOCHET_MAX_META, getMaxRicochets(level))); - if (ricochetCount >= maxRicochets) { - return; - } - - Vector incoming = resolveIncomingVector(projectile); - if (incoming.lengthSquared() < getConfig().minRicochetVelocitySquared) { - return; - } - - BlockFace hitFace = resolveHitFace(e, incoming); - if (hitFace == null) { - return; - } - - Vector reflectedDir = reflect(incoming.clone().normalize(), hitFace); - if (reflectedDir.lengthSquared() <= 0.0000001) { - return; - } - - reflectedDir.normalize(); - double nextSpeed = Math.max(getConfig().minimumPostBounceSpeed, incoming.length()) * (1D + getSpeedBonusPerRicochet(level)); - if (nextSpeed <= 0) { - return; - } - - Vector ricochetVelocity = reflectedDir.clone().multiply(nextSpeed); - int nextRicochetCount = ricochetCount + 1; - double bonusDamage = getMetadataDouble(projectile, BONUS_DAMAGE_META, 0D) + getDamageBonusPerRicochet(level); - - Location bounceLocation = projectile.getLocation().clone() - .add(hitFace.getDirection().normalize().multiply(getConfig().spawnOffsetFromSurface)) - .add(reflectedDir.clone().multiply(getConfig().spawnOffsetAlongDirection)); - Projectile ricochet = spawnRicochetProjectile(projectile, bounceLocation, ricochetVelocity, p); - if (ricochet == null) { - return; - } - - ricochet.setMetadata(RICOCHET_COUNT_META, new FixedMetadataValue(Adapt.instance, nextRicochetCount)); - ricochet.setMetadata(RICOCHET_MAX_META, new FixedMetadataValue(Adapt.instance, maxRicochets)); - ricochet.setMetadata(BONUS_DAMAGE_META, new FixedMetadataValue(Adapt.instance, bonusDamage)); - - Location fx = e.getHitBlock().getLocation().add(0.5, 0.5, 0.5); - if (areParticlesEnabled()) { - projectile.getWorld().spawnParticle(Particle.ELECTRIC_SPARK, fx, Math.max(1, getConfig().sparkParticleCount), - getConfig().sparkSpread, getConfig().sparkSpread, getConfig().sparkSpread, 0.02); - projectile.getWorld().spawnParticle(Particle.CRIT, fx, Math.max(1, getConfig().critParticleCount), - getConfig().critSpread, getConfig().critSpread, getConfig().critSpread, 0.08); - } - if (areSoundsEnabled()) { - SoundPlayer sp = SoundPlayer.of(projectile.getWorld()); - sp.play(fx, Sound.BLOCK_ANVIL_HIT, 0.85f, (float) Math.max(0.4, getConfig().bouncePitchBase - (nextRicochetCount * getConfig().bouncePitchDropPerRicochet))); - sp.play(fx, Sound.BLOCK_AMETHYST_BLOCK_HIT, 0.9f, (float) Math.min(2.0, getConfig().sparkPitchBase + (nextRicochetCount * getConfig().sparkPitchRaisePerRicochet))); - } - xp(p, getConfig().xpPerRicochet + (nextRicochetCount * getConfig().xpPerRicochetStep)); - getPlayer(p).getData().addStat("ranged.ricochet-bolt.total-ricochets", 1); - projectile.remove(); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Projectile projectile) || !(projectile.getShooter() instanceof Player p) || !projectile.hasMetadata(BONUS_DAMAGE_META)) { - return; - } - - if (e.getEntity() instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - return; - } - } else if (!canPVE(p, e.getEntity().getLocation())) { - return; - } - - double bonusDamage = getMetadataDouble(projectile, BONUS_DAMAGE_META, 0D); - if (bonusDamage > 0 && e.getDamage() > 0) { - e.setDamage(e.getDamage() + bonusDamage); - } - } - - @EventHandler - public void on(EntityDeathEvent e) { - if (e.getEntity().getKiller() instanceof Player p && hasAdaptation(p)) { - if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent dmg - && dmg.getDamager() instanceof Projectile projectile - && projectile.hasMetadata(RICOCHET_COUNT_META) - && projectile.getShooter() instanceof Player) { - getPlayer(p).getData().addStat("ranged.ricochet-bolt.ricochet-kills", 1); - } - } - } - - private Projectile spawnRicochetProjectile(Projectile original, Location spawnLocation, Vector velocity, Player shooter) { - Vector dir = velocity.clone().normalize(); - float speed = (float) Math.max(0.2, velocity.length()); - if (original instanceof SpectralArrow sourceSpectral && sourceSpectral instanceof AbstractArrow sourceAbstract) { - SpectralArrow spectral = original.getWorld().spawn(spawnLocation, SpectralArrow.class); - copyArrowState(sourceAbstract, spectral, shooter, velocity); - spectral.setGlowingTicks(sourceSpectral.getGlowingTicks()); - return spectral; - } - - if (original instanceof Trident sourceTrident) { - Trident trident = original.getWorld().spawn(spawnLocation, Trident.class); - copyArrowState(sourceTrident, trident, shooter, velocity); - trident.setItem(sourceTrident.getItem()); - return trident; - } - - if (original instanceof Arrow sourceArrow) { - Arrow arrow = original.getWorld().spawnArrow(spawnLocation, dir, speed, 0f); - copyArrowState(sourceArrow, arrow, shooter, velocity); - arrow.setBasePotionType(sourceArrow.getBasePotionType()); - sourceArrow.getCustomEffects().forEach(effect -> arrow.addCustomEffect(effect, true)); - return arrow; - } - - if (original instanceof Snowball) { - Snowball snowball = original.getWorld().spawn(spawnLocation, Snowball.class); - copyProjectileState(original, snowball, shooter, velocity); - return snowball; - } - - if (original instanceof Egg) { - Egg egg = original.getWorld().spawn(spawnLocation, Egg.class); - copyProjectileState(original, egg, shooter, velocity); - return egg; - } - - if (original instanceof EnderPearl) { - EnderPearl pearl = original.getWorld().spawn(spawnLocation, EnderPearl.class); - copyProjectileState(original, pearl, shooter, velocity); - return pearl; - } - - if (original instanceof ThrownPotion sourcePotion) { - ThrownPotion potion = original.getWorld().spawn(spawnLocation, ThrownPotion.class); - copyProjectileState(sourcePotion, potion, shooter, velocity); - potion.setItem(sourcePotion.getItem().clone()); - return potion; - } - - if (original instanceof ThrownExpBottle) { - ThrownExpBottle bottle = original.getWorld().spawn(spawnLocation, ThrownExpBottle.class); - copyProjectileState(original, bottle, shooter, velocity); - return bottle; - } - - return null; - } - - private void copyArrowState(AbstractArrow source, AbstractArrow target, Player shooter, Vector velocity) { - copyProjectileState(source, target, shooter, velocity); - target.setDamage(source.getDamage()); - target.setCritical(source.isCritical()); - target.setKnockbackStrength(source.getKnockbackStrength()); - target.setPierceLevel(source.getPierceLevel()); - target.setPickupStatus(AbstractArrow.PickupStatus.CREATIVE_ONLY); - } - - private void copyProjectileState(Projectile source, Projectile target, Player shooter, Vector velocity) { - target.setShooter(shooter); - target.setVelocity(velocity); - target.setBounce(source.doesBounce()); - target.setGravity(source.hasGravity()); - target.setFireTicks(source.getFireTicks()); - } - - private Vector reflect(Vector incoming, BlockFace face) { - Vector normal = face.getDirection().normalize(); - double dot = incoming.dot(normal); - return incoming.clone().subtract(normal.multiply(2D * dot)); - } - - private Vector resolveIncomingVector(Projectile projectile) { - Vector liveVelocity = projectile.getVelocity().clone(); - if (liveVelocity.lengthSquared() >= getConfig().minimumLiveVelocitySquared) { - return liveVelocity; - } - - Vector facing = projectile.getLocation().getDirection().clone(); - if (facing.lengthSquared() > 0.0000001) { - return facing.normalize().multiply(Math.max(getConfig().minimumPostBounceSpeed, liveVelocity.length())); - } - - return liveVelocity; - } - - private boolean supportsRicochet(Projectile projectile) { - if (projectile instanceof AbstractArrow) { - return true; - } - - return getConfig().applyToAllProjectiles - && (projectile instanceof Snowball - || projectile instanceof Egg - || projectile instanceof EnderPearl - || projectile instanceof ThrownPotion - || projectile instanceof ThrownExpBottle); - } - - private BlockFace resolveHitFace(ProjectileHitEvent e, Vector incoming) { - if (e.getHitBlockFace() != null) { - return e.getHitBlockFace(); - } - - double ax = Math.abs(incoming.getX()); - double ay = Math.abs(incoming.getY()); - double az = Math.abs(incoming.getZ()); - - if (ay >= ax && ay >= az) { - return incoming.getY() > 0 ? BlockFace.DOWN : BlockFace.UP; - } - - if (ax >= az) { - return incoming.getX() > 0 ? BlockFace.WEST : BlockFace.EAST; - } - - return incoming.getZ() > 0 ? BlockFace.NORTH : BlockFace.SOUTH; - } - - private int getMetadataInt(Projectile projectile, String key, int fallback) { - for (MetadataValue value : projectile.getMetadata(key)) { - if (value.getOwningPlugin() == Adapt.instance) { - return value.asInt(); - } - } - - return fallback; - } - - private double getMetadataDouble(Projectile projectile, String key, double fallback) { - for (MetadataValue value : projectile.getMetadata(key)) { - if (value.getOwningPlugin() == Adapt.instance) { - return value.asDouble(); - } - } - - return fallback; - } - - private int getMaxRicochets(int level) { - return Math.max(1, (int) Math.round(getConfig().maxRicochetsBase + (getLevelPercent(level) * getConfig().maxRicochetsFactor))); - } - - private double getSpeedBonusPerRicochet(int level) { - return Math.min(getConfig().maxSpeedBonusPerRicochet, - getConfig().speedBonusPerRicochetBase + (getLevelPercent(level) * getConfig().speedBonusPerRicochetFactor)); - } - - private double getDamageBonusPerRicochet(int level) { - return Math.min(getConfig().maxDamageBonusPerRicochet, - getConfig().damageBonusPerRicochetBase + (getLevelPercent(level) * getConfig().damageBonusPerRicochetFactor)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Arrows ricochet from block impacts with chained bounces, scaling speed, and bonus damage.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.74; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Ricochets Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxRicochetsBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Ricochets Factor for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxRicochetsFactor = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Bonus Per Ricochet Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedBonusPerRicochetBase = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Bonus Per Ricochet Factor for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedBonusPerRicochetFactor = 0.27; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Speed Bonus Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxSpeedBonusPerRicochet = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Per Ricochet Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageBonusPerRicochetBase = 0.55; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Per Ricochet Factor for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageBonusPerRicochetFactor = 2.55; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Damage Bonus Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxDamageBonusPerRicochet = 3.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Ricochet Velocity Squared for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minRicochetVelocitySquared = 0.09; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Live Velocity Squared for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minimumLiveVelocitySquared = 0.0004; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Post Bounce Speed for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minimumPostBounceSpeed = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spawn Offset From Surface for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double spawnOffsetFromSurface = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spawn Offset Along Direction for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double spawnOffsetAlongDirection = 0.14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spark Particle Count for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int sparkParticleCount = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spark Spread for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sparkSpread = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Crit Particle Count for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int critParticleCount = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Crit Spread for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double critSpread = 0.14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bounce Pitch Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bouncePitchBase = 1.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bounce Pitch Drop Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bouncePitchDropPerRicochet = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spark Pitch Base for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sparkPitchBase = 1.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spark Pitch Raise Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sparkPitchRaisePerRicochet = 0.07; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Ricochet for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerRicochet = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Ricochet Step for the Ranged Ricochet Bolt adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerRicochetStep = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allow ricochet behavior to apply to throwables (snowballs, eggs, pearls, potions, exp bottles) in addition to arrows/tridents.", impact = "True enables universal ricochet across most player-thrown projectiles.") - boolean applyToAllProjectiles = true; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedTrajectorySight.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedTrajectorySight.java deleted file mode 100644 index 1a429331c..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedTrajectorySight.java +++ /dev/null @@ -1,801 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import fr.skytasul.glowingentities.GlowingEntities; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Color; -import org.bukkit.FluidCollisionMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.EntityShootBowEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.RayTraceResult; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class RangedTrajectorySight extends SimpleAdaptation { - private static final double EPSILON = 0.0000001D; - private final Map drawStartedMillis = new HashMap<>(); - private final Map previewGlowTargets = new HashMap<>(); - private volatile RangedForce cachedRangedForce; - private volatile RangedRicochetBolt cachedRicochetBolt; - - public RangedTrajectorySight() { - super("ranged-trajectory-sight"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("ranged.trajectory_sight.description")); - setDisplayName(Localizer.dLocalize("ranged.trajectory_sight.name")); - setIcon(Material.SPYGLASS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPYGLASS) - .key("challenge_ranged_trajectory_100") - .title(Localizer.dLocalize("advancement.challenge_ranged_trajectory_100.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_trajectory_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_ranged_trajectory_100", "ranged.trajectory-sight.kills-while-aiming", 100, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getVelocityMultiplier(level)) + C.GRAY + " " + Localizer.dLocalize("ranged.trajectory_sight.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(getSegments(level)) + C.GRAY + " " + Localizer.dLocalize("ranged.trajectory_sight.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - drawStartedMillis.remove(p.getUniqueId()); - clearPreviewGlow(p); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(PlayerInteractEvent e) { - if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (!isItem(hand)) { - return; - } - - Material type = hand.getType(); - if (type != Material.BOW && type != Material.CROSSBOW) { - return; - } - - switch (e.getAction()) { - case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> drawStartedMillis.put(p.getUniqueId(), System.currentTimeMillis()); - default -> { - } - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityShootBowEvent e) { - if (e.getEntity() instanceof Player p) { - drawStartedMillis.remove(p.getUniqueId()); - } - } - - @EventHandler - public void on(EntityDeathEvent e) { - if (e.getEntity().getKiller() instanceof Player p && hasAdaptation(p)) { - if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent dmg - && dmg.getDamager() instanceof Projectile projectile - && projectile.getShooter() instanceof Player) { - getPlayer(p).getData().addStat("ranged.trajectory-sight.kills-while-aiming", 1); - } - } - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (!hasAdaptation(p)) { - drawStartedMillis.remove(p.getUniqueId()); - clearPreviewGlow(p); - continue; - } - - PreviewContext context = resolvePreviewContext(p); - if (context == null) { - drawStartedMillis.remove(p.getUniqueId()); - clearPreviewGlow(p); - continue; - } - - if (context.trigger() != PreviewTrigger.DRAWING_BOW) { - drawStartedMillis.remove(p.getUniqueId()); - } - - ShotPreview shot = getShotPreview(p, context); - if (shot == null) { - clearPreviewGlow(p); - continue; - } - - UUID predictedHit = renderTrajectory(p, getSegments(getLevel(p)), shot); - updatePreviewGlow(p, predictedHit); - } - } - - private PreviewContext resolvePreviewContext(Player p) { - ItemStack main = p.getInventory().getItemInMainHand(); - ItemStack off = p.getInventory().getItemInOffHand(); - - if (isDrawingBow(p, main)) { - return new PreviewContext(main, PreviewTrigger.DRAWING_BOW); - } - - if (!p.isSneaking()) { - return null; - } - - if (isSneakProjectile(main)) { - return new PreviewContext(main, PreviewTrigger.SNEAK_PROJECTILE); - } - - if (isSneakProjectile(off)) { - return new PreviewContext(off, PreviewTrigger.SNEAK_PROJECTILE); - } - - return null; - } - - private ShotPreview getShotPreview(Player p, PreviewContext context) { - Material launchType = context.item().getType(); - double launchVelocity = getLaunchVelocity(p, launchType); - if (launchVelocity <= 0.01) { - return null; - } - - BallisticsProfile profile = resolveBallisticsProfile(launchType); - Vector direction = applyLaunchDirectionTuning(launchType, p.getEyeLocation().getDirection().clone()); - if (direction.lengthSquared() <= EPSILON) { - return null; - } - - Vector velocity = direction.normalize().multiply(launchVelocity); - velocity.multiply(getRangedForceLaunchMultiplier(p)); - RicochetPreview ricochet = getRicochetPreview(p); - if (!supportsRicochet(launchType, ricochet)) { - ricochet = RicochetPreview.disabled(); - } - return new ShotPreview(velocity, ricochet, profile, context.trigger()); - } - - private double getLaunchVelocity(Player p, Material type) { - if (type == Material.SNOWBALL || type == Material.EGG || type == Material.ENDER_PEARL) { - return getConfig().thrownProjectileVelocity; - } - - if (type == Material.SPLASH_POTION || type == Material.LINGERING_POTION || type == Material.EXPERIENCE_BOTTLE) { - return getConfig().thrownPotionVelocity; - } - - if (type == Material.TRIDENT) { - return getConfig().tridentVelocity; - } - - if (type == Material.BOW) { - double force = getBowForce(p); - if (force <= 0) { - return 0; - } - - return force * 3.0; - } - - if (type == Material.CROSSBOW) { - return getConfig().crossbowVelocity; - } - - return getConfig().fallbackVelocity; - } - - private double getBowForce(Player p) { - UUID id = p.getUniqueId(); - long now = System.currentTimeMillis(); - long start = drawStartedMillis.computeIfAbsent(id, k -> now); - double chargeTicks = Math.max(0, (now - start) / 50.0); - - if (!p.isHandRaised() && p.isSneaking()) { - chargeTicks = getConfig().sneakPreviewChargeTicks; - } - - double force = chargeTicks / 20.0; - force = (force * force + force * 2.0) / 3.0; - return Math.min(1.0, force); - } - - private UUID renderTrajectory(Player p, int segments, ShotPreview shot) { - Location eye = p.getEyeLocation().clone(); - Location current = eye.clone().add(p.getEyeLocation().getDirection().normalize().multiply(getConfig().previewStartOffset)); - Vector velocity = shot.initialVelocity().clone(); - Color trailColor = shot.trigger() == PreviewTrigger.DRAWING_BOW - ? Color.fromRGB(120, 225, 255) - : Color.fromRGB(255, 190, 125); - int every = Math.max(1, getConfig().trailParticleEvery); - double minDistanceSq = Math.max(0, getConfig().minPreviewDistanceFromEye); - minDistanceSq *= minDistanceSq; - int ricochets = 0; - Location lastVisiblePoint = null; - UUID hitEntityId = null; - - for (int i = 0; i < segments; i++) { - Vector step = velocity.clone(); - double stepLength = step.length(); - if (stepLength <= EPSILON) { - return hitEntityId; - } - - Vector stepDirection = step.clone().normalize(); - Location from = current.clone(); - RayTraceResult entityHit = p.getWorld().rayTraceEntities(current, stepDirection, stepLength, entity -> isValidPreviewTarget(p, entity)); - RayTraceResult hit = p.getWorld().rayTraceBlocks(current, stepDirection, stepLength, FluidCollisionMode.NEVER, true); - if (isEntityFirstHit(current, hit, entityHit)) { - Entity target = entityHit.getHitEntity(); - if (target != null) { - hitEntityId = target.getUniqueId(); - } - - if (entityHit.getHitPosition() != null) { - current = entityHit.getHitPosition().toLocation(p.getWorld()); - } else if (target != null) { - current = target.getLocation().clone(); - } - - if (areParticlesEnabled() && eye.distanceSquared(current) >= minDistanceSq) { - float impactSize = getScaledParticleSize(eye.distance(current), 1.15D); - Particle.DustOptions impact = new Particle.DustOptions(Color.fromRGB(255, 236, 128), impactSize); - p.spawnParticle(Particle.DUST, current, Math.max(1, getConfig().impactParticleCount + 1), 0.02, 0.02, 0.02, 0.0, impact); - } - break; - } - - if (hit != null && hit.getHitBlock() != null) { - current = hit.getHitPosition().toLocation(p.getWorld()); - if (areParticlesEnabled()) { - if (eye.distanceSquared(current) >= minDistanceSq) { - float impactSize = getScaledParticleSize(eye.distance(current), 1.1D); - Particle.DustOptions impact = new Particle.DustOptions(Color.fromRGB(255, 236, 128), impactSize); - p.spawnParticle(Particle.DUST, current, Math.max(1, getConfig().impactParticleCount), 0.02, 0.02, 0.02, 0.0, impact); - } - } - - RicochetPreview ricochet = shot.ricochetPreview(); - if (canRicochet(ricochet, ricochets, velocity)) { - BlockFace hitFace = hit.getHitBlockFace() == null ? resolveHitFace(velocity) : hit.getHitBlockFace(); - if (hitFace == null) { - break; - } - - Vector reflectedDir = reflect(velocity.clone().normalize(), hitFace); - if (reflectedDir.lengthSquared() <= EPSILON) { - break; - } - - reflectedDir.normalize(); - double nextSpeed = Math.max(ricochet.minimumPostBounceSpeed(), velocity.length()) * (1D + ricochet.speedBonusPerRicochet()); - velocity = reflectedDir.clone().multiply(nextSpeed); - current.add(hitFace.getDirection().normalize().multiply(ricochet.spawnOffsetFromSurface())) - .add(reflectedDir.clone().multiply(ricochet.spawnOffsetAlongDirection())); - ricochets++; - if (areParticlesEnabled()) { - if (eye.distanceSquared(current) >= minDistanceSq) { - float bounceSize = getScaledParticleSize(eye.distance(current), 1.15D); - Particle.DustOptions bounce = new Particle.DustOptions(Color.fromRGB(170, 200, 255), bounceSize); - p.spawnParticle(Particle.DUST, current, Math.max(1, getConfig().impactParticleCount), 0.02, 0.02, 0.02, 0.0, bounce); - } - } - continue; - } - - break; - } - - current.add(step); - - if (i % every == 0) { - if (areParticlesEnabled()) { - if (eye.distanceSquared(current) >= minDistanceSq) { - Location delta = current.clone().subtract(from); - int subSteps = Math.max(1, getConfig().trailSubSteps); - for (int s = 1; s <= subSteps; s++) { - double f = (double) s / (double) subSteps; - Location point = from.clone().add(delta.clone().multiply(f)); - if (eye.distanceSquared(point) < minDistanceSq) { - continue; - } - - float trailSize = getScaledParticleSize(eye.distance(point), 1D); - Particle.DustOptions trail = new Particle.DustOptions(trailColor, trailSize); - p.spawnParticle(Particle.DUST, point, Math.max(1, getConfig().trailParticleCount), 0.0, 0.0, 0.0, 0.0, trail); - lastVisiblePoint = point; - } - } - } - } - - velocity.multiply(shot.profile().dragFactor()); - velocity.setY(velocity.getY() - shot.profile().gravityStep()); - } - - if (areParticlesEnabled() && lastVisiblePoint != null) { - float tipSize = getScaledParticleSize(eye.distance(lastVisiblePoint), 1.2D); - Particle.DustOptions impact = new Particle.DustOptions(Color.fromRGB(255, 236, 128), tipSize); - p.spawnParticle(Particle.DUST, lastVisiblePoint, 1, 0.0, 0.0, 0.0, 0.0, impact); - } - return hitEntityId; - } - - private BallisticsProfile resolveBallisticsProfile(Material type) { - if (type == Material.SNOWBALL || type == Material.EGG || type == Material.ENDER_PEARL) { - return new BallisticsProfile(getConfig().lightProjectileDragFactor, getConfig().lightProjectileGravityStep); - } - - if (type == Material.SPLASH_POTION || type == Material.LINGERING_POTION || type == Material.EXPERIENCE_BOTTLE) { - return new BallisticsProfile(getConfig().heavyProjectileDragFactor, getConfig().heavyProjectileGravityStep); - } - - return new BallisticsProfile(getConfig().dragFactor, getConfig().gravityStep); - } - - private Vector applyLaunchDirectionTuning(Material type, Vector direction) { - if (type == Material.SPLASH_POTION || type == Material.LINGERING_POTION || type == Material.EXPERIENCE_BOTTLE) { - direction.setY(direction.getY() - getConfig().heavyProjectilePitchDrop); - } - - return direction; - } - - private double getRangedForceLaunchMultiplier(Player p) { - RangedForce force = getRangedForceAdaptation(); - if (force == null || !force.isEnabled() || !force.getSkill().isEnabled()) { - return 1D; - } - - int level = getAdaptationLevel(p, force.getName()); - if (level <= 0) { - return 1D; - } - - double levelPercent = Math.min(1D, Math.max(0D, (double) level / (double) Math.max(1, force.getMaxLevel()))); - double speedBonus = levelPercent * force.getConfig().speedFactor; - return Math.max(0.1D, 1D + speedBonus); - } - - private RicochetPreview getRicochetPreview(Player p) { - RangedRicochetBolt ricochet = getRicochetAdaptation(); - if (ricochet == null || !ricochet.isEnabled() || !ricochet.getSkill().isEnabled()) { - return RicochetPreview.disabled(); - } - - int level = getAdaptationLevel(p, ricochet.getName()); - if (level <= 0) { - return RicochetPreview.disabled(); - } - - RangedRicochetBolt.Config cfg = ricochet.getConfig(); - double levelPercent = Math.min(1D, Math.max(0D, (double) level / (double) Math.max(1, ricochet.getMaxLevel()))); - int maxRicochets = Math.max(1, (int) Math.round(cfg.maxRicochetsBase + (levelPercent * cfg.maxRicochetsFactor))); - double speedBonus = Math.min(cfg.maxSpeedBonusPerRicochet, - cfg.speedBonusPerRicochetBase + (levelPercent * cfg.speedBonusPerRicochetFactor)); - return new RicochetPreview( - true, - maxRicochets, - speedBonus, - cfg.minRicochetVelocitySquared, - cfg.minimumPostBounceSpeed, - cfg.spawnOffsetFromSurface, - cfg.spawnOffsetAlongDirection, - cfg.applyToAllProjectiles - ); - } - - private int getAdaptationLevel(Player p, String adaptationId) { - PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("ranged"); - return line == null ? 0 : line.getAdaptationLevel(adaptationId); - } - - private RangedForce getRangedForceAdaptation() { - RangedForce cached = cachedRangedForce; - if (cached != null) { - return cached; - } - - RangedForce found = resolveRangedAdaptation(RangedForce.class); - if (found != null) { - cachedRangedForce = found; - } - return found; - } - - private RangedRicochetBolt getRicochetAdaptation() { - RangedRicochetBolt cached = cachedRicochetBolt; - if (cached != null) { - return cached; - } - - RangedRicochetBolt found = resolveRangedAdaptation(RangedRicochetBolt.class); - if (found != null) { - cachedRicochetBolt = found; - } - return found; - } - - private T resolveRangedAdaptation(Class type) { - Skill ranged = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill("ranged"); - if (ranged == null) { - return null; - } - - for (Adaptation adaptation : ranged.getAdaptations()) { - if (type.isInstance(adaptation)) { - return type.cast(adaptation); - } - } - - return null; - } - - private boolean canRicochet(RicochetPreview ricochet, int ricochetCount, Vector incomingVelocity) { - return ricochet.enabled() - && ricochetCount < ricochet.maxRicochets() - && incomingVelocity.lengthSquared() >= ricochet.minRicochetVelocitySquared(); - } - - private float getScaledParticleSize(double distance, double multiplier) { - double size = getConfig().particleSize + (Math.max(0D, distance) * getConfig().particleSizePerBlock); - size *= multiplier; - size = Math.max(0.05D, Math.min(getConfig().maxParticleSize, size)); - return (float) size; - } - - private boolean isEntityFirstHit(Location from, RayTraceResult blockHit, RayTraceResult entityHit) { - if (entityHit == null || entityHit.getHitEntity() == null) { - return false; - } - - if (blockHit == null || blockHit.getHitPosition() == null) { - return true; - } - - if (entityHit.getHitPosition() == null) { - return false; - } - - double blockDistSq = blockHit.getHitPosition().distanceSquared(from.toVector()); - double entityDistSq = entityHit.getHitPosition().distanceSquared(from.toVector()); - return entityDistSq <= blockDistSq; - } - - private boolean isValidPreviewTarget(Player shooter, Entity entity) { - return entity instanceof LivingEntity - && entity.isValid() - && !entity.isDead() - && entity.getUniqueId() != shooter.getUniqueId(); - } - - private void updatePreviewGlow(Player p, UUID targetId) { - if (!getConfig().glowPredictedTarget) { - clearPreviewGlow(p); - return; - } - - UUID viewerId = p.getUniqueId(); - UUID current = previewGlowTargets.get(viewerId); - if (current != null && current.equals(targetId)) { - return; - } - - GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); - if (glowingEntities == null) { - return; - } - - if (current != null) { - Entity stale = Bukkit.getEntity(current); - if (stale != null) { - try { - glowingEntities.unsetGlowing(stale, p); - } catch (ReflectiveOperationException ignored) { - // Ignore and continue; preview should never hard-fail from packet glow. - } - } - previewGlowTargets.remove(viewerId); - } - - if (targetId == null) { - return; - } - - Entity target = Bukkit.getEntity(targetId); - if (target == null || !target.isValid()) { - return; - } - - try { - glowingEntities.setGlowing(target, p, ChatColor.GOLD); - previewGlowTargets.put(viewerId, targetId); - } catch (ReflectiveOperationException ignored) { - // Ignore and continue; preview should never hard-fail from packet glow. - } - } - - private void clearPreviewGlow(Player p) { - UUID viewerId = p.getUniqueId(); - UUID targetId = previewGlowTargets.remove(viewerId); - if (targetId == null) { - return; - } - - GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); - if (glowingEntities == null) { - return; - } - - Entity entity = Bukkit.getEntity(targetId); - if (entity == null) { - return; - } - - try { - glowingEntities.unsetGlowing(entity, p); - } catch (ReflectiveOperationException ignored) { - // Ignore and continue; preview should never hard-fail from packet glow. - } - } - - private boolean supportsRicochet(Material launchType, RicochetPreview ricochet) { - if (!ricochet.enabled()) { - return false; - } - - if (launchType == Material.BOW || launchType == Material.CROSSBOW || launchType == Material.TRIDENT) { - return true; - } - - return ricochet.applyToAllProjectiles() - && (launchType == Material.SNOWBALL - || launchType == Material.EGG - || launchType == Material.ENDER_PEARL - || launchType == Material.SPLASH_POTION - || launchType == Material.LINGERING_POTION - || launchType == Material.EXPERIENCE_BOTTLE); - } - - private boolean isDrawingBow(Player p, ItemStack main) { - if (!isItem(main)) { - return false; - } - - Material type = main.getType(); - if (type != Material.BOW && type != Material.CROSSBOW) { - return false; - } - - if (!p.isHandRaised()) { - return false; - } - - ItemStack active = p.getItemInUse(); - return isItem(active) && active.getType() == type; - } - - private boolean isSneakProjectile(ItemStack item) { - if (!isItem(item)) { - return false; - } - - Material type = item.getType(); - return type == Material.BOW - || type == Material.CROSSBOW - || type == Material.TRIDENT - || type == Material.SNOWBALL - || type == Material.EGG - || type == Material.ENDER_PEARL - || type == Material.SPLASH_POTION - || type == Material.LINGERING_POTION - || type == Material.EXPERIENCE_BOTTLE; - } - - private Vector reflect(Vector incoming, BlockFace face) { - Vector normal = face.getDirection().normalize(); - double dot = incoming.dot(normal); - return incoming.clone().subtract(normal.multiply(2D * dot)); - } - - private BlockFace resolveHitFace(Vector incoming) { - double ax = Math.abs(incoming.getX()); - double ay = Math.abs(incoming.getY()); - double az = Math.abs(incoming.getZ()); - - if (ay >= ax && ay >= az) { - return incoming.getY() > 0 ? BlockFace.DOWN : BlockFace.UP; - } - - if (ax >= az) { - return incoming.getX() > 0 ? BlockFace.WEST : BlockFace.EAST; - } - - return incoming.getZ() > 0 ? BlockFace.NORTH : BlockFace.SOUTH; - } - - private record PreviewContext(ItemStack item, PreviewTrigger trigger) { - } - - private enum PreviewTrigger { - SNEAK_PROJECTILE, - DRAWING_BOW - } - - private record BallisticsProfile(double dragFactor, double gravityStep) { - } - - private record ShotPreview(Vector initialVelocity, RicochetPreview ricochetPreview, BallisticsProfile profile, PreviewTrigger trigger) { - } - - private record RicochetPreview(boolean enabled, int maxRicochets, double speedBonusPerRicochet, - double minRicochetVelocitySquared, double minimumPostBounceSpeed, - double spawnOffsetFromSurface, double spawnOffsetAlongDirection, - boolean applyToAllProjectiles) { - private static RicochetPreview disabled() { - return new RicochetPreview(false, 0, 0D, Double.MAX_VALUE, 0D, 0D, 0D, false); - } - } - - private int getSegments(int level) { - return Math.max(10, (int) Math.round(getConfig().segmentsBase + (getLevelPercent(level) * getConfig().segmentsFactor))); - } - - private double getVelocityMultiplier(int level) { - return Math.max(0.1, getConfig().velocityBase + (getLevelPercent(level) * getConfig().velocityFactor)); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Preview ranged projectile flight while sneaking or drawing your shot.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Segments Base for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double segmentsBase = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Segments Factor for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double segmentsFactor = 26; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Velocity Base for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double velocityBase = 1.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Velocity Factor for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double velocityFactor = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Gravity Step for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double gravityStep = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Step Scale for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double stepScale = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Drag Factor for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dragFactor = 0.99; - @com.volmit.adapt.util.config.ConfigDoc(value = "Drag factor used for lighter thrown projectiles (snowballs, eggs, pearls).", impact = "Higher values keep thrown arcs flatter and longer; lower values make them lose speed faster.") - double lightProjectileDragFactor = 0.99; - @com.volmit.adapt.util.config.ConfigDoc(value = "Drag factor used for heavier thrown projectiles (potions, experience bottles).", impact = "Higher values keep heavier throws moving faster; lower values shorten their travel.") - double heavyProjectileDragFactor = 0.99; - @com.volmit.adapt.util.config.ConfigDoc(value = "Gravity step used for lighter thrown projectiles (snowballs, eggs, pearls).", impact = "Higher values produce steeper arcs for light projectiles.") - double lightProjectileGravityStep = 0.03; - @com.volmit.adapt.util.config.ConfigDoc(value = "Gravity step used for heavier thrown projectiles (potions, experience bottles).", impact = "Higher values cause potions and bottles to drop faster.") - double heavyProjectileGravityStep = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Crossbow Velocity for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double crossbowVelocity = 3.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Launch velocity used for trident previews while sneaking.", impact = "Higher values extend trident prediction distance.") - double tridentVelocity = 2.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Launch velocity used for light thrown projectile previews.", impact = "Higher values extend snowball, egg, and pearl prediction distance.") - double thrownProjectileVelocity = 1.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Launch velocity used for potion and experience bottle previews.", impact = "Higher values extend heavy throw prediction distance.") - double thrownPotionVelocity = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Additional downward launch offset for heavy thrown projectile previews.", impact = "Higher values tilt potion and bottle trajectories downward more strongly.") - double heavyProjectilePitchDrop = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fallback Velocity for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fallbackVelocity = 1.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sneak Preview Charge Ticks for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sneakPreviewChargeTicks = 16; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Particle Size for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double particleSize = 0.13; - @com.volmit.adapt.util.config.ConfigDoc(value = "How much particle size grows per block of distance from the viewer.", impact = "Higher values make far trajectory points easier to see.") - double particleSizePerBlock = 0.008; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum particle size used for the trajectory preview.", impact = "Caps distance scaling to prevent oversized particles.") - double maxParticleSize = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Trail Particle Count for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int trailParticleCount = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Impact Particle Count for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int impactParticleCount = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Trail Particle Every for the Ranged Trajectory Sight adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int trailParticleEvery = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum distance from the player's eye before preview particles are shown.", impact = "Higher values keep particles out of your sightline and reduce visual obstruction.") - double minPreviewDistanceFromEye = 1.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "How many interpolated points are drawn between each simulated physics step.", impact = "Higher values smooth the line while increasing particle density.") - int trailSubSteps = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Offset forward from the eye where trajectory simulation begins.", impact = "Higher values start the preview further from your face.") - double previewStartOffset = 0.55; - @com.volmit.adapt.util.config.ConfigDoc(value = "Highlights the predicted hit target entity with per-player glow.", impact = "Enable to glow whichever entity the preview would hit first.") - boolean glowPredictedTarget = true; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedWebBomb.java b/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedWebBomb.java deleted file mode 100644 index d8ade1949..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/ranged/RangedWebBomb.java +++ /dev/null @@ -1,287 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.ranged; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.recipe.MaterialChar; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.BoundSnowBall; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.Particles; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Snowball; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockExplodeEvent; -import org.bukkit.event.block.BlockPistonExtendEvent; -import org.bukkit.event.block.BlockPistonRetractEvent; -import org.bukkit.event.entity.EntityExplodeEvent; -import org.bukkit.event.entity.ProjectileHitEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; - -import java.util.*; - -public class RangedWebBomb extends SimpleAdaptation { - private static final BlockData AIR = Material.AIR.createBlockData(); - private static final BlockData BLOCK = Material.COBWEB.createBlockData(); - private final Map activeSnowballs; - private final Set activeBlocks; - - public RangedWebBomb() { - super("ranged-webshot"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("ranged.web_shot.description")); - setDisplayName(Localizer.dLocalize("ranged.web_shot.name")); - setIcon(Material.COBWEB); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(4900); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerRecipe(AdaptRecipe.shaped() - .key("ranged-web-bomb") - .ingredient(new MaterialChar('I', Material.COBWEB)) - .ingredient(new MaterialChar('S', Material.SNOWBALL)) - .shapes(List.of( - "III", - "ISI", - "III")) - .result(BoundSnowBall.io.withData(new BoundSnowBall.Data(null))) - .build()); - activeBlocks = new HashSet<>(); - activeSnowballs = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.COBWEB) - .key("challenge_ranged_web_200") - .title(Localizer.dLocalize("advancement.challenge_ranged_web_200.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_web_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_ranged_web_200", "ranged.web-bomb.mobs-trapped", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("ranged.web_shot.lore1")); - v.addLore(C.YELLOW + "+ " + level + C.GRAY + " " + Localizer.dLocalize("ranged.web_shot.lore2")); - } - - - @EventHandler - public void on(ProjectileHitEvent e) { - if (e.isCancelled()) { - return; - } - Block block; - - if (e.getHitEntity() != null) { - block = e.getHitEntity().getLocation().add(0, 1, 0).getBlock(); - } else if (e.getHitBlock() != null) { - block = e.getHitBlock().getLocation().add(0, 1, 0).getBlock(); - } else { - block = e.getEntity().getLocation().add(0, 1, 0).getBlock(); - } - - if (e.getEntity().getShooter() instanceof Player p && e.getEntity() instanceof Snowball snowball && hasAdaptation(p)) { - vfxCuboidOutline(block, Particle.REVERSE_PORTAL); - Adapt.verbose("Snowball Got: " + snowball.getEntityId() + " " + snowball.getUniqueId()); - if (activeSnowballs.containsKey(Bukkit.getEntity(snowball.getUniqueId()))) { - Adapt.verbose("Detected snowball hit"); - if (e.getHitEntity() != null) { - getPlayer(p).getData().addStat("ranged.web-bomb.mobs-trapped", 1); - } - activeSnowballs.remove(snowball); - snowball.remove(); - Set locs = new HashSet<>(); - locs.add(block.getLocation().add(0, 1, 0).getBlock()); - locs.add(block.getLocation().add(0, -1, 0).getBlock()); - locs.add(block.getLocation().add(0, 0, 1).getBlock()); - locs.add(block.getLocation().add(0, 0, -1).getBlock()); - locs.add(block.getLocation().add(1, 0, 0).getBlock()); - locs.add(block.getLocation().add(-1, 0, 0).getBlock()); - - for (Block i : locs) { - addWebFoundation(i, getLevel(p)); - } - - } - } - } - - - @EventHandler - public void on(ProjectileLaunchEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity().getShooter() instanceof Player p && e.getEntity() instanceof Snowball snowball && hasAdaptation(p)) { - Adapt.verbose("Snowball Launched: " + snowball.getEntityId() + " " + snowball.getUniqueId()); - if (BoundSnowBall.isBindableItem(snowball.getItem())) { - Adapt.verbose("Snowball is bound"); - activeSnowballs.put(snowball, p); - } else { - Adapt.verbose("Snowball is not bound"); - } - } - } - - public void addWebFoundation(Block block, int seconds) { - if (!block.getType().isAir()) { - return; - } - - J.s(() -> { - block.setBlockData(BLOCK); - activeBlocks.add(block); - }); - SoundPlayer spw = SoundPlayer.of(block.getWorld()); - spw.play(block.getLocation(), Sound.BLOCK_ROOTED_DIRT_PLACE, 1.0f, 1.0f); - if (areParticlesEnabled()) { - - vfxCuboidOutline(block, Particle.CLOUD); - vfxCuboidOutline(block, Particle.WHITE_ASH); - } - J.s(() -> removeFoundation(block), seconds * 16); - } - - public void removeFoundation(Block block) { - if (!block.getBlockData().equals(BLOCK)) { - return; - } - - J.s(() -> { - block.setBlockData(AIR); - activeBlocks.remove(block); - }); - SoundPlayer spw = SoundPlayer.of(block.getWorld()); - spw.play(block.getLocation(), Sound.BLOCK_ROOTED_DIRT_BREAK, 1.0f, 1.0f); - if (areParticlesEnabled()) { - vfxCuboidOutline(block, Particles.ENCHANTMENT_TABLE); - } - } - - - //prevent piston from moving blocks // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockPistonExtendEvent e) { - if (e.isCancelled()) { - return; - } - e.getBlocks().forEach(b -> { - if (activeBlocks.contains(b)) { - Adapt.verbose("Cancelled Piston Extend on Adaptation Foundation Block"); - e.setCancelled(true); - } - }); - } - - //prevent piston from pulling blocks // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockPistonRetractEvent e) { - if (e.isCancelled()) { - return; - } - e.getBlocks().forEach(b -> { - if (activeBlocks.contains(b)) { - Adapt.verbose("Cancelled Piston Retract on Adaptation Foundation Block"); - e.setCancelled(true); - } - }); - } - - //prevent TNT from destroying blocks // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockExplodeEvent e) { - if (e.isCancelled()) { - return; - } - if (activeBlocks.contains(e.getBlock())) { - Adapt.verbose("Cancelled Block Explosion on Adaptation Foundation Block"); - e.setCancelled(true); - } - } - - //prevent block from being destroyed // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(BlockBreakEvent e) { - if (activeBlocks.contains(e.getBlock())) { - e.setCancelled(true); - } - } - - //prevent Entities from destroying blocks // Dupe fix - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityExplodeEvent e) { - if (e.isCancelled()) { - return; - } - e.blockList().removeIf(activeBlocks::contains); - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Throw a crafted web snare to trap targets in cobwebs.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Ranged Web Bomb adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.9; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftAccess.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftAccess.java deleted file mode 100644 index 3314b79cf..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftAccess.java +++ /dev/null @@ -1,356 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.BoundEnderPearl; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import manifold.rt.api.util.Pair; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.*; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.InventoryView; -import org.bukkit.inventory.ItemStack; -import us.lynuxcraft.deadsilenceiv.advancedchests.AdvancedChestsAPI; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; - -import static com.volmit.adapt.api.adaptation.chunk.ChunkLoading.loadChunkAsync; - -public class RiftAccess extends SimpleAdaptation { - private final Map, List> activeViewsMap = new ConcurrentHashMap<>(); - private final Map tickets = new ConcurrentHashMap<>(); - - public RiftAccess() { - super("rift-access"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.remote_access.description")); - setDisplayName(Localizer.dLocalize("rift.remote_access.name")); - setMaxLevel(1); - setIcon(Material.NETHER_STAR); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setInitialCost(getConfig().initialCost); - setInterval(1000); - registerRecipe(AdaptRecipe.shapeless() - .key("rift-remote-access") - .ingredient(Material.ENDER_PEARL) - .ingredient(Material.COMPASS) - .result(BoundEnderPearl.io.withData(new BoundEnderPearl.Data(null))) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_rift_access_100") - .title(Localizer.dLocalize("advancement.challenge_rift_access_100.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_access_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_CHEST) - .key("challenge_rift_access_2500") - .title(Localizer.dLocalize("advancement.challenge_rift_access_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_access_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_access_100", "rift.access.remote-opens", 100, 300); - registerMilestone("challenge_rift_access_2500", "rift.access.remote-opens", 2500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.ITALIC + Localizer.dLocalize("rift.remote_access.lore1")); - v.addLore(C.ITALIC + Localizer.dLocalize("rift.remote_access.lore2")); - v.addLore(C.ITALIC + Localizer.dLocalize("rift.remote_access.lore3")); - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - ItemStack mainHand = p.getInventory().getItemInMainHand(); - ItemStack offHand = p.getInventory().getItemInOffHand(); - Block block = e.getClickedBlock(); - - boolean mainHandBound = BoundEnderPearl.isBindableItem(mainHand); - boolean offHandBound = BoundEnderPearl.isBindableItem(offHand); - - // Cancel event if the enderpearl is in the offhand - if (offHandBound && e.getHand() != null && e.getHand().equals(EquipmentSlot.OFF_HAND)) { - e.setCancelled(true); - return; - } - - // If the main hand is holding a bound enderpearl - if (mainHandBound) { - e.setCancelled(true); - if (hasAdaptation(p)) { - Adapt.verbose("Player using bound enderpearl."); - handleEnderPearlInteraction(e, p, block); - } - } - } - - private void handleEnderPearlInteraction(PlayerInteractEvent event, Player player, Block block) { - boolean canUseInCreative = AdaptConfig.get().allowAdaptationsInCreative; - boolean isCreative = player.getGameMode() == GameMode.CREATIVE; - boolean sneaking = player.isSneaking(); - boolean allowed = canUseInCreative || !isCreative; - - - // Check if the player is allowed to use the bound item in creative - if (!allowed) { - Adapt.info("Player " + player.getName() + " tried to use the bound item in creative mode."); - return; - } - - switch (event.getAction()) { - case LEFT_CLICK_BLOCK -> { - // If player is sneaking and left-clicking a container - if (sneaking && isStorage(block.getBlockData())) { - if (canAccessChest(player, block.getLocation())) { - linkPearl(player, block, event); - } else { - Adapt.verbose("Player " + player.getName() + " doesn't have permission."); - } - } - } - case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> - // If player right-clicks on air or any block - openPearl(player); - default -> { - } - } - } - - private void linkPearl(Player p, Block block, PlayerInteractEvent event) { - event.setCancelled(true); - if (areParticlesEnabled()) { - vfxCuboidOutline(block, Particle.REVERSE_PORTAL); - } - ItemStack hand = p.getInventory().getItemInMainHand(); - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_CLOSE, 0.5f, 0.8f); - - if (hand.getAmount() == 1) { - BoundEnderPearl.setData(hand, block); - } else { - hand.setAmount(hand.getAmount() - 1); - ItemStack pearl = BoundEnderPearl.withData(block); - p.getInventory().addItem(pearl).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); - } - } - - private void openPearl(Player p) { - SoundPlayer sp = SoundPlayer.of(p); - Block b = BoundEnderPearl.getBlock(p.getInventory().getItemInMainHand()); - if (b == null || !canAccessChest(p, b.getLocation())) { - sp.play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f); - return; - } - loadChunkAsync(b.getLocation(), chunk -> { - if (Bukkit.getPluginManager().isPluginEnabled("AdvancedChests") && - AdvancedChestsAPI.getChestManager().getAdvancedChest(b.getLocation()) != null) { - AdvancedChestsAPI.getChestManager().getAdvancedChest(b.getLocation()).openPage(p, 1); - Adapt.verbose("Opening AdvancedChests GUI"); - } else if (b.getState() instanceof InventoryHolder holder) { - InventoryView view = p.openInventory(holder.getInventory()); - if (view == null) return; - activeViewsMap.computeIfAbsent(Pair.make(new ChunkPos(chunk).add(), b.getLocation()), k -> new ArrayList<>()).add(view); - } - sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); - getPlayer(p).getData().addStat("rift.access.remote-opens", 1); - }); - } - - @Override - public void onTick() { - checkActiveViews(); - } - - private void checkActiveViews() { - Iterator, List>> mapIterator = activeViewsMap.entrySet().iterator(); - while (mapIterator.hasNext()) { - Map.Entry, List> entry = mapIterator.next(); - removeInvalidViews(entry); - removeEntryIfViewsEmpty(mapIterator, entry); - } - } - - private void removeInvalidViews(Map.Entry, List> entry) { - List views = entry.getValue(); - for (int ii = views.size() - 1; ii >= 0; ii--) { - InventoryView i = views.get(ii); - if (shouldRemoveView(i)) { - views.remove(ii); - } - } - } - - private boolean shouldRemoveView(InventoryView i) { - Location location = i.getTopInventory().getLocation(); - return !i.getPlayer().getOpenInventory().equals(i) || (location == null || !isStorage(location.getBlock().getBlockData())); - } - - private void removeEntryIfViewsEmpty(Iterator, List>> mapIterator, Map.Entry, List> entry) { - List views = entry.getValue(); - if (views.isEmpty()) { - mapIterator.remove(); - entry.getKey().getFirst().remove(); - } - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBurnEvent event) { - if (event.isCancelled()) return; - invClose(event.getBlock()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockPistonRetractEvent event) { - if (event.isCancelled()) return; - for (Block b : event.getBlocks()) { - invClose(b); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockPistonExtendEvent event) { - if (event.isCancelled()) return; - for (Block b : event.getBlocks()) { - invClose(b); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockExplodeEvent event) { - if (event.isCancelled()) return; - for (Block b : event.blockList()) { - invClose(b); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent event) { - if (event.isCancelled()) return; - invClose(event.getBlock()); - } - - - private void invClose(Block block) { - List views = activeViewsMap.get(block.getLocation()); - if (views != null) { - for (InventoryView view : views) { - view.getPlayer().closeInventory(); - } - activeViewsMap.remove(block.getLocation()); - } - } - - - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft a Reliquary Portkey to access marked containers remotely.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Rift Access adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 15; - } - - @EqualsAndHashCode - private class ChunkPos { - @EqualsAndHashCode.Exclude - private final WeakReference world; - private final String name; - private final int x, z; - - private ChunkPos(Chunk chunk) { - this.world = new WeakReference<>(chunk.getWorld()); - this.name = chunk.getWorld().getName(); - this.x = chunk.getX(); - this.z = chunk.getZ(); - } - - public ChunkPos add() { - World world = this.world.get(); - if (world == null) return this; - if (tickets.computeIfAbsent(this, k -> new AtomicInteger()).getAndIncrement() == 0) - world.addPluginChunkTicket(x, z, Adapt.instance); - return this; - } - - public void remove() { - World world = this.world.get(); - if (world == null) { - tickets.remove(this); - return; - } - if (tickets.computeIfAbsent(this, k -> new AtomicInteger()).decrementAndGet() <= 0) { - world.removePluginChunkTicket(x, z, Adapt.instance); - world.unloadChunkRequest(x, z); - tickets.remove(this); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftBlink.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftBlink.java deleted file mode 100644 index 1aed565f8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftBlink.java +++ /dev/null @@ -1,576 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.content.event.AdaptAdaptationTeleportEvent; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import org.bukkit.event.block.Action; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.event.player.PlayerToggleFlightEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static com.volmit.adapt.api.adaptation.chunk.ChunkLoading.loadChunkAsync; - - -public class RiftBlink extends SimpleAdaptation { - private final Map lastBlink = new HashMap<>(); - private final Map jumpArmUntil = new HashMap<>(); - private final Map lastOnGround = new HashMap<>(); - - public RiftBlink() { - super("rift-blink"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.blink.description")); - setDisplayName(Localizer.dLocalize("rift.blink.name")); - setIcon(Material.FEATHER); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(9288); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_blink_500") - .title(Localizer.dLocalize("advancement.challenge_rift_blink_500.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_blink_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_rift_blink_5k") - .title(Localizer.dLocalize("advancement.challenge_rift_blink_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_blink_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_blink_500", "rift.blink.blinks", 500, 400); - registerMilestone("challenge_rift_blink_5k", "rift.blink.distance-blinked", 5000, 1500); - } - - private double getBlinkDistance(int level) { - return getConfig().baseDistance + (getLevelPercent(level) * getConfig().distanceFactor); - } - - private long getCooldownDuration() { - return Math.max(0L, getConfig().cooldownMillis); - } - - private boolean isBlinkEligible(Player p) { - return hasAdaptation(p) && p.getGameMode() == GameMode.SURVIVAL; - } - - private boolean isOnCooldown(UUID id) { - return M.ms() - lastBlink.getOrDefault(id, 0L) <= getCooldownDuration(); - } - - private void clearDoubleJumpArm(Player p, UUID id) { - if (jumpArmUntil.remove(id) == null) { - return; - } - - if (p.getGameMode() == GameMode.SURVIVAL) { - p.setAllowFlight(false); - p.setFlying(false); - } - } - - private void armDoubleJump(Player p, UUID id) { - int triggerWindowMillis = Math.max(150, getConfig().doubleJumpWindowMillis); - long expires = M.ms() + triggerWindowMillis; - jumpArmUntil.put(id, expires); - p.setAllowFlight(true); - J.s(() -> { - if (!p.isOnline()) { - return; - } - - Long armUntil = jumpArmUntil.get(id); - if (armUntil != null && armUntil <= M.ms()) { - clearDoubleJumpArm(p, id); - } - }, Math.max(1, (int) Math.ceil(triggerWindowMillis / 50D))); - } - - private boolean isClickAction(Action action) { - return action == Action.LEFT_CLICK_AIR - || action == Action.LEFT_CLICK_BLOCK - || action == Action.RIGHT_CLICK_AIR - || action == Action.RIGHT_CLICK_BLOCK; - } - - private boolean isLeftClick(Action action) { - return action == Action.LEFT_CLICK_AIR || action == Action.LEFT_CLICK_BLOCK; - } - - private boolean isRightClick(Action action) { - return action == Action.RIGHT_CLICK_AIR || action == Action.RIGHT_CLICK_BLOCK; - } - - private boolean isBlockClick(Action action) { - return action == Action.LEFT_CLICK_BLOCK || action == Action.RIGHT_CLICK_BLOCK; - } - - private boolean isActionAllowed(Action action) { - if (!getConfig().allowAirClicks && !isBlockClick(action)) { - return false; - } - - if (!getConfig().allowBlockClicks && isBlockClick(action)) { - return false; - } - - return true; - } - - private boolean shouldTriggerSprintClick(PlayerInteractEvent e) { - if (!getConfig().enableSprintClickTrigger || !e.getPlayer().isSprinting()) { - return false; - } - - if (e.getHand() != null && e.getHand() != EquipmentSlot.HAND) { - return false; - } - - Action action = e.getAction(); - if (!isClickAction(action) || !isActionAllowed(action)) { - return false; - } - - if (isLeftClick(action) && !getConfig().sprintClickLeftClick) { - return false; - } - - return !isRightClick(action) || getConfig().sprintClickRightClick; - } - - private boolean shouldTriggerPearlClick(PlayerInteractEvent e) { - if (!getConfig().enableEnderPearlClickTrigger || e.getItem() == null || e.getItem().getType() != Material.ENDER_PEARL) { - return false; - } - - Action action = e.getAction(); - if (!isClickAction(action) || !isActionAllowed(action)) { - return false; - } - - if (isLeftClick(action) && !getConfig().enderPearlClickLeftClick) { - return false; - } - - return !isRightClick(action) || getConfig().enderPearlClickRightClick; - } - - private Location findBlinkGround(Player player) { - Location start = player.getLocation().clone(); - Vector direction = start.getDirection().clone().setY(0); - if (direction.lengthSquared() <= 0.0001) { - double yawRadians = Math.toRadians(start.getYaw()); - direction = new Vector(-Math.sin(yawRadians), 0, Math.cos(yawRadians)); - } - direction.normalize(); - - int maxVerticalAdjustment = Math.max(0, getConfig().maxVerticalAdjustment); - double step = Math.max(0.25, getConfig().distanceSearchStep); - double maxDistance = getBlinkDistance(getLevel(player)); - - for (double distance = maxDistance; distance >= 1; distance -= step) { - Location horizontalTarget = start.clone().add(direction.clone().multiply(distance)); - Location safe = findSafeGroundNear(horizontalTarget, maxVerticalAdjustment); - if (safe != null) { - return safe; - } - } - - return null; - } - - private Location findSafeGroundNear(Location base, int maxVerticalAdjustment) { - if (isSafe(base)) { - return base; - } - - for (int y = 1; y <= maxVerticalAdjustment; y++) { - Location down = base.clone().subtract(0, y, 0); - if (isSafe(down)) { - return down; - } - - Location up = base.clone().add(0, y, 0); - if (isSafe(up)) { - return up; - } - } - - return null; - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + (getBlinkDistance(level)) + C.GRAY + " " + Localizer.dLocalize("rift.blink.lore1")); - java.util.List combos = getTriggerCombos(); - if (combos.isEmpty()) { - v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + "none"); - return; - } - - for (String combo : combos) { - v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + combo); - } - } - - @Override - public String getDescription() { - return "Short-ranged instant teleportation to safe ground. " + summarizeTriggerDescription(); - } - - private String summarizeTriggerDescription() { - java.util.List combos = getTriggerCombos(); - if (combos.isEmpty()) { - return "No active triggers are currently enabled."; - } - - if (combos.size() == 1) { - return "Trigger: " + combos.get(0) + "."; - } - - if (combos.size() == 2) { - return "Triggers: " + combos.get(0) + " or " + combos.get(1) + "."; - } - - return "Triggers: " + combos.get(0) + ", " + combos.get(1) + ", +" + (combos.size() - 2) + " more."; - } - - private java.util.List getTriggerCombos() { - java.util.List triggers = new java.util.ArrayList<>(); - String clickSurface = getClickSurfaceLabel(); - if (getConfig().enableDoubleJumpTrigger) { - triggers.add(getConfig().doubleJumpRequiresSprint ? "Double Jump + Sprint" : "Double Jump"); - } - - if (getConfig().enableSprintClickTrigger) { - appendClickCombos(triggers, "Sprint", getConfig().sprintClickLeftClick, getConfig().sprintClickRightClick, clickSurface); - } - - if (getConfig().enableSingleSneakTrigger) { - triggers.add(getConfig().singleSneakRequiresSprint ? "Sprint + Sneak" : "Sneak"); - } - - if (getConfig().enableEnderPearlClickTrigger) { - appendClickCombos(triggers, "Ender Pearl", getConfig().enderPearlClickLeftClick, getConfig().enderPearlClickRightClick, clickSurface); - } - - return triggers; - } - - private void appendClickCombos(java.util.List triggers, String prefix, boolean allowLeft, boolean allowRight, String clickSurface) { - if (clickSurface.isBlank()) { - return; - } - - if (allowLeft) { - triggers.add(prefix + " + Left Click" + clickSurface); - } - - if (allowRight) { - triggers.add(prefix + " + Right Click" + clickSurface); - } - } - - private String getClickSurfaceLabel() { - if (getConfig().allowAirClicks && getConfig().allowBlockClicks) { - return " (air/block)"; - } - - if (getConfig().allowAirClicks) { - return " (air)"; - } - - if (getConfig().allowBlockClicks) { - return " (block)"; - } - - return ""; - } - - @EventHandler - public void on(PlayerQuitEvent e) { - UUID id = e.getPlayer().getUniqueId(); - lastBlink.remove(id); - jumpArmUntil.remove(id); - lastOnGround.remove(id); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerToggleFlightEvent e) { - Player p = e.getPlayer(); - UUID id = p.getUniqueId(); - if (!isBlinkEligible(p) || !getConfig().enableDoubleJumpTrigger) { - return; - } - - Long armUntil = jumpArmUntil.get(id); - if (armUntil == null) { - return; - } - - e.setCancelled(true); - p.setFlying(false); - clearDoubleJumpArm(p, id); - if (armUntil > M.ms()) { - attemptBlink(p); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - if (!isBlinkEligible(p)) { - return; - } - - if (shouldTriggerPearlClick(e)) { - e.setCancelled(true); - attemptBlink(p); - return; - } - - if (shouldTriggerSprintClick(e)) { - attemptBlink(p); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerToggleSneakEvent e) { - Player p = e.getPlayer(); - if (!e.isSneaking() || !isBlinkEligible(p) || !getConfig().enableSingleSneakTrigger) { - return; - } - - if (getConfig().singleSneakRequiresSprint && !p.isSprinting()) { - return; - } - - attemptBlink(p); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerMoveEvent e) { - Player p = e.getPlayer(); - UUID id = p.getUniqueId(); - boolean wasOnGround = lastOnGround.getOrDefault(id, true); - boolean onGround = p.isOnGround(); - lastOnGround.put(id, onGround); - - if (!isBlinkEligible(p) || !getConfig().enableDoubleJumpTrigger) { - clearDoubleJumpArm(p, id); - return; - } - - if (!wasOnGround && onGround) { - clearDoubleJumpArm(p, id); - return; - } - - if (isOnCooldown(id)) { - clearDoubleJumpArm(p, id); - return; - } - - if (isDoubleJumpStart(wasOnGround, onGround, p)) { - if (getConfig().doubleJumpRequiresSprint && !p.isSprinting()) { - return; - } - - armDoubleJump(p, id); - return; - } - - Long armUntil = jumpArmUntil.get(id); - if (armUntil != null && armUntil <= M.ms()) { - clearDoubleJumpArm(p, id); - } - } - - private boolean isDoubleJumpStart(boolean wasOnGround, boolean onGround, Player p) { - return wasOnGround - && !onGround - && p.getVelocity().getY() >= getConfig().doubleJumpMinVerticalVelocity; - } - - private boolean attemptBlink(Player p) { - UUID id = p.getUniqueId(); - if (isOnCooldown(id)) { - return false; - } - - Location locOG = p.getLocation().clone(); - SoundPlayer spw = SoundPlayer.of(p); - Location destinationGround = findBlinkGround(p); - if (destinationGround == null) { - spw.play(p.getLocation(), Sound.BLOCK_CONDUIT_DEACTIVATE, 1f, 1.24f); - lastBlink.put(id, M.ms()); - return false; - } - - PlayerSkillLine line = getPlayer(p).getData().getSkillLineNullable("rift"); - PlayerAdaptation adaptation = line != null ? line.getAdaptation("rift-resist") : null; - if (adaptation != null && adaptation.getLevel() > 0) { - RiftResist.riftResistStackAdd(p, 10, 5); - } - - if (areParticlesEnabled()) { - vfxParticleLine(locOG, destinationGround, Particle.REVERSE_PORTAL, 50, 8, 0.1D, 1D, 0.1D, 0D, null, false, l -> l.getBlock().isPassable()); - } - - Vector v = p.getVelocity().clone(); - loadChunkAsync(destinationGround, chunk -> J.s(() -> { - Location toLoc = destinationGround.clone().add(0, 1, 0); - - AdaptAdaptationTeleportEvent event = new AdaptAdaptationTeleportEvent(false, getPlayer(p), this, locOG, destinationGround.clone()); - Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - - p.teleport(toLoc, PlayerTeleportEvent.TeleportCause.PLUGIN); - p.setVelocity(v.multiply(3)); - })); - - getPlayer(p).getData().addStat("rift.teleports", 1); - getPlayer(p).getData().addStat("rift.blink.blinks", 1); - getPlayer(p).getData().addStat("rift.blink.distance-blinked", (int) locOG.distance(destinationGround)); - lastBlink.put(id, M.ms()); - spw.play(p.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.50f, 1.0f); - vfxLevelUp(p); - return true; - } - - private boolean isSafe(Location l) { - return l.getBlock().getType().isSolid() - && !l.getBlock().getRelative(BlockFace.UP).getType().isSolid() - && !l.getBlock().getRelative(BlockFace.UP).getRelative(BlockFace.UP).getType().isSolid(); - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Short-ranged instant teleportation by double-tapping jump while sprinting.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Rift Blink adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Cooldown between successful Rift Blink triggers in milliseconds.", impact = "Higher values reduce blink frequency; lower values allow faster reuse.") - int cooldownMillis = 2000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables double-tap jump detection for Rift Blink.", impact = "True allows jump-based activation; false disables jump activation.") - boolean enableDoubleJumpTrigger = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Require sprinting for the double-tap jump trigger.", impact = "True requires sprinting while double-tapping jump; false allows it without sprint.") - boolean doubleJumpRequiresSprint = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum time window between jump taps in milliseconds.", impact = "Higher values make double-tap detection easier; lower values make it stricter.") - int doubleJumpWindowMillis = 450; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum upward velocity required to arm double-jump blink.", impact = "Higher values reduce accidental arming; lower values make detection more sensitive.") - double doubleJumpMinVerticalVelocity = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables sprint + click activation for Rift Blink.", impact = "True allows clicking while sprinting to blink; false disables this trigger.") - boolean enableSprintClickTrigger = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables single-sneak activation for Rift Blink.", impact = "True allows pressing sneak once to trigger blink.") - boolean enableSingleSneakTrigger = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Require sprinting for single-sneak trigger.", impact = "True requires sprint state when using single-sneak activation.") - boolean singleSneakRequiresSprint = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows left-click as a sprint-click trigger.", impact = "True allows left-click activation while sprinting; false disables left-click activation.") - boolean sprintClickLeftClick = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows right-click as a sprint-click trigger.", impact = "True allows right-click activation while sprinting; false disables right-click activation.") - boolean sprintClickRightClick = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables click-with-ender-pearl activation for Rift Blink.", impact = "True allows pearl-click activation; false disables pearl-click activation.") - boolean enableEnderPearlClickTrigger = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows left-click with an ender pearl to trigger Rift Blink.", impact = "True enables left-click pearl activation; false disables it.") - boolean enderPearlClickLeftClick = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows right-click with an ender pearl to trigger Rift Blink.", impact = "True enables right-click pearl activation; false disables it.") - boolean enderPearlClickRightClick = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows air-click interactions to trigger Rift Blink.", impact = "True lets air clicks trigger enabled click modes; false blocks air-click triggers.") - boolean allowAirClicks = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows block-click interactions to trigger Rift Blink.", impact = "True lets block clicks trigger enabled click modes; false blocks block-click triggers.") - boolean allowBlockClicks = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Distance for the Rift Blink adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseDistance = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Distance Factor for the Rift Blink adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double distanceFactor = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Vertical Adjustment for the Rift Blink adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxVerticalAdjustment = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Distance Search Step for the Rift Blink adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double distanceSearchStep = 0.5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftDescent.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftDescent.java deleted file mode 100644 index f5380c3d4..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftDescent.java +++ /dev/null @@ -1,145 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.ArrayList; -import java.util.List; - -public class RiftDescent extends SimpleAdaptation { - private final List cooldown = new ArrayList<>(); - - public RiftDescent() { - super("rift-descent"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.descent.description")); - setDisplayName(Localizer.dLocalize("rift.descent.name")); - setMaxLevel(1); - setIcon(Material.SHULKER_BOX); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setInitialCost(getConfig().initialCost); - setInterval(9544); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_descent_100") - .title(Localizer.dLocalize("advancement.challenge_rift_descent_100.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_descent_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SHULKER_SHELL) - .key("challenge_rift_descent_1k") - .title(Localizer.dLocalize("advancement.challenge_rift_descent_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_descent_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_descent_100", "rift.descent.levitation-cancelled", 100, 300); - registerMilestone("challenge_rift_descent_1k", "rift.descent.levitation-cancelled", 1000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.YELLOW + Localizer.dLocalize("rift.descent.lore1")); - v.addLore(C.GREEN + Localizer.dLocalize("rift.descent.lore2") + " " + C.WHITE + getConfig().cooldown + "s"); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerToggleSneakEvent e) { - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (p.getPotionEffect(PotionEffectType.LEVITATION) == null) { - return; - } - if (!hasAdaptation(p)) { - return; - } - if (cooldown.contains(p)) { - return; - } - - PotionEffect levi = p.getPotionEffect(PotionEffectType.LEVITATION); - - if (!e.isSneaking() && (levi != null)) { - p.removePotionEffect(PotionEffectType.LEVITATION); - getPlayer(p).getData().addStat("rift.descent.levitation-cancelled", 1); - cooldown.add(p); - J.s(() -> { - sp.play(p.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f); - cooldown.remove(p); - }, Math.max(1, (int) Math.round(getConfig().cooldown * 20D))); - - J.s(() -> { - p.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, (int) (20 * getConfig().cooldown), 0)); - sp.play(p.getLocation(), Sound.ENTITY_ENDER_DRAGON_FLAP, 1f, 1f); - }); - } - } - - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak to descend and negate levitation effects.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Rift Descent adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldown = 5.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.95; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - } - -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftEnderTaglock.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftEnderTaglock.java deleted file mode 100644 index ac3339307..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftEnderTaglock.java +++ /dev/null @@ -1,477 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.BoundEnderPearl; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Animals; -import org.bukkit.entity.Ambient; -import org.bukkit.entity.EnderPearl; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Mob; -import org.bukkit.entity.Monster; -import org.bukkit.entity.Player; -import org.bukkit.entity.Slime; -import org.bukkit.entity.Villager; -import org.bukkit.entity.WaterMob; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.ProjectileHitEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.metadata.MetadataValue; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.util.BoundingBox; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -public class RiftEnderTaglock extends SimpleAdaptation { - private static final String PROJECTILE_TARGET_META = "adapt-rift-taglock-target"; - private final NamespacedKey targetKey; - private final Map suppressPearlTeleportUntil = new ConcurrentHashMap<>(); - - public RiftEnderTaglock() { - super("rift-ender-taglock"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.ender_taglock.description")); - setDisplayName(Localizer.dLocalize("rift.ender_taglock.name")); - setIcon(Material.ENDER_PEARL); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1200); - targetKey = new NamespacedKey(Adapt.instance, "rift_taglock_target_uuid"); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_taglock_100") - .title(Localizer.dLocalize("advancement.challenge_rift_taglock_100.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_taglock_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_rift_taglock_500") - .title(Localizer.dLocalize("advancement.challenge_rift_taglock_500.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_taglock_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_taglock_100", "rift.ender-taglock.entities-tagged", 100, 400); - registerMilestone("challenge_rift_taglock_500", "rift.ender-taglock.taglocked-teleports", 500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.ender_taglock.lore1")); - if (level >= 2) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.ender_taglock.lore2")); - } - if (level >= 3) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.ender_taglock.lore3")); - } - v.addLore(C.YELLOW + "* " + Form.duration(getThrowCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("rift.ender_taglock.lore4")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - suppressPearlTeleportUntil.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Player p) || !(e.getEntity() instanceof LivingEntity target) || !hasAdaptation(p)) { - return; - } - - if (!p.isSneaking()) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (!isItem(hand) || hand.getType() != Material.ENDER_PEARL || BoundEnderPearl.isBindableItem(hand)) { - return; - } - - int level = getLevel(p); - if (!isTaggable(target, level)) { - return; - } - - if (target instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - return; - } - } else if (!canPVE(p, target.getLocation())) { - return; - } - - e.setCancelled(true); - tagIntoPearl(p, hand, target); - SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.55f, 1.4f); - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.PORTAL, target.getLocation().add(0, 1, 0), 14, 0.25, 0.4, 0.25, 0.04); - } - getPlayer(p).getData().addStat("rift.ender-taglock.entities-tagged", 1); - xp(p, getConfig().xpOnTag); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerInteractEvent e) { - EquipmentSlot slot = e.getHand(); - if (slot == null) { - return; - } - - Action action = e.getAction(); - if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - - ItemStack hand = slot == EquipmentSlot.HAND - ? p.getInventory().getItemInMainHand() - : p.getInventory().getItemInOffHand(); - - UUID target = getTaggedTarget(hand); - if (target == null) { - return; - } - - e.setCancelled(true); - if (p.hasCooldown(Material.ENDER_PEARL)) { - SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_DIDGERIDOO, 0.55f, 0.6f); - return; - } - - decrementTaggedPearl(p, slot, hand); - - EnderPearl pearl = p.launchProjectile(EnderPearl.class); - pearl.setMetadata(PROJECTILE_TARGET_META, new FixedMetadataValue(Adapt.instance, target.toString())); - suppressPearlTeleportUntil.put(p.getUniqueId(), System.currentTimeMillis() + getSuppressPearlTeleportWindowMillis()); - p.setCooldown(Material.ENDER_PEARL, getThrowCooldownTicks(getLevel(p))); - SoundPlayer.of(p).play(p.getLocation(), Sound.ENTITY_ENDER_EYE_LAUNCH, 0.65f, 1.25f); - xp(p, getConfig().xpOnThrow); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerTeleportEvent e) { - if (e.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { - return; - } - - UUID id = e.getPlayer().getUniqueId(); - long until = suppressPearlTeleportUntil.getOrDefault(id, 0L); - if (until <= System.currentTimeMillis()) { - suppressPearlTeleportUntil.remove(id); - return; - } - - e.setCancelled(true); - e.getPlayer().setFallDistance(0f); - suppressPearlTeleportUntil.remove(id); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(ProjectileHitEvent e) { - if (!(e.getEntity() instanceof EnderPearl pearl) || !(pearl.getShooter() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - String raw = getMetadataString(pearl, PROJECTILE_TARGET_META); - if (raw == null) { - return; - } - - UUID targetId; - try { - targetId = UUID.fromString(raw); - } catch (IllegalArgumentException ex) { - return; - } - - Entity entity = Bukkit.getEntity(targetId); - if (!(entity instanceof LivingEntity target) || !target.isValid() || target.isDead()) { - return; - } - - Location destination = resolveDestination(e); - if (target instanceof Player victim) { - if (!canPVP(p, victim.getLocation()) || !canPVP(p, destination)) { - return; - } - } else if (!canPVE(p, target.getLocation()) || !canPVE(p, destination)) { - return; - } - - destination.getChunk().load(); - target.teleport(destination); - if (areParticlesEnabled()) { - target.getWorld().spawnParticle(Particle.REVERSE_PORTAL, destination.clone().add(0, 0.75, 0), 18, 0.3, 0.35, 0.3, 0.05); - } - SoundPlayer.of(target.getWorld()).play(destination, Sound.ENTITY_ENDERMAN_TELEPORT, 0.75f, 1.35f); - SoundPlayer.of(target.getWorld()).play(destination, Sound.ENTITY_ELDER_GUARDIAN_CURSE, 0.5f, 1.9f); - if (target instanceof Player victim && areSoundsEnabled()) { - victim.playSound(victim.getLocation(), Sound.ENTITY_ELDER_GUARDIAN_CURSE, 0.75f, 1.9f); - } - getPlayer(p).getData().addStat("rift.ender-taglock.taglocked-teleports", 1); - xp(p, getConfig().xpOnTeleport); - J.s(() -> suppressPearlTeleportUntil.remove(p.getUniqueId()), 2); - } - - private Location resolveDestination(ProjectileHitEvent e) { - Location base = e.getEntity().getLocation().clone(); - if (e.getHitBlock() != null && e.getHitBlockFace() != null) { - Vector n = e.getHitBlockFace().getDirection().clone().normalize(); - base = e.getHitBlock().getLocation().add(0.5, 0.5, 0.5).add(n.multiply(0.6)); - } - - return base.add(0, 0.05, 0); - } - - private void decrementTaggedPearl(Player p, EquipmentSlot slot, ItemStack stack) { - if (!isItem(stack)) { - return; - } - - if (stack.getAmount() <= 1) { - if (slot == EquipmentSlot.HAND) { - p.getInventory().setItemInMainHand(new ItemStack(Material.AIR)); - } else { - p.getInventory().setItemInOffHand(new ItemStack(Material.AIR)); - } - return; - } - - stack.setAmount(stack.getAmount() - 1); - if (slot == EquipmentSlot.HAND) { - p.getInventory().setItemInMainHand(stack); - } else { - p.getInventory().setItemInOffHand(stack); - } - } - - private void tagIntoPearl(Player p, ItemStack hand, LivingEntity target) { - ItemStack tagged = makeTaggedPearl(target); - - if (hand.getAmount() <= 1) { - p.getInventory().setItemInMainHand(tagged); - return; - } - - hand.setAmount(hand.getAmount() - 1); - p.getInventory().addItem(tagged).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); - } - - private ItemStack makeTaggedPearl(LivingEntity target) { - ItemStack item = new ItemStack(Material.ENDER_PEARL, 1); - ItemMeta meta = item.getItemMeta(); - if (meta == null) { - return item; - } - - PersistentDataContainer pdc = meta.getPersistentDataContainer(); - pdc.set(targetKey, PersistentDataType.STRING, target.getUniqueId().toString()); - - meta.setDisplayName(C.LIGHT_PURPLE + "Taglocked Ender Pearl"); - List lore = new ArrayList<>(); - lore.add(C.GRAY + "Target: " + C.WHITE + getTargetName(target)); - lore.add(C.DARK_GRAY + "Right click to throw"); - meta.setLore(lore); - item.setItemMeta(meta); - return item; - } - - private UUID getTaggedTarget(ItemStack item) { - if (!isItem(item) || item.getType() != Material.ENDER_PEARL || item.getItemMeta() == null) { - return null; - } - - String raw = item.getItemMeta().getPersistentDataContainer().get(targetKey, PersistentDataType.STRING); - if (raw == null || raw.isEmpty()) { - return null; - } - - try { - return UUID.fromString(raw); - } catch (IllegalArgumentException e) { - return null; - } - } - - private String getTargetName(LivingEntity target) { - if (target instanceof Player player) { - return player.getName(); - } - - if (target.getCustomName() != null && !target.getCustomName().isEmpty()) { - return target.getCustomName(); - } - - return Form.capitalizeWords(target.getType().name().toLowerCase().replaceAll("\\Q_\\E", " ")); - } - - private boolean isTaggable(LivingEntity target, int level) { - if (target instanceof Player) { - return level >= 3; - } - - if (level >= 3) { - return true; - } - - if (level == 2) { - return isLevel1Taggable(target) || target instanceof Villager || isLargeTarget(target); - } - - return isLevel1Taggable(target); - } - - private boolean isLevel1Taggable(LivingEntity target) { - if (!(target instanceof Mob)) { - return false; - } - - if (target instanceof Villager) { - return false; - } - - if (isLargeTarget(target)) { - return false; - } - - return target instanceof Animals - || target instanceof Monster - || target instanceof WaterMob - || target instanceof Ambient - || target instanceof Slime; - } - - private boolean isLargeTarget(LivingEntity target) { - BoundingBox box = target.getBoundingBox(); - double width = Math.max(box.getWidthX(), box.getWidthZ()); - double height = box.getHeight(); - return width >= getConfig().largeWidthThreshold || height >= getConfig().largeHeightThreshold; - } - - private String getMetadataString(Entity entity, String key) { - for (MetadataValue value : entity.getMetadata(key)) { - if (value.getOwningPlugin() == Adapt.instance) { - return value.asString(); - } - } - - return null; - } - - private int getThrowCooldownTicks(int level) { - return Math.max(4, (int) Math.round(getConfig().throwCooldownTicksBase - (getLevelPercent(level) * getConfig().throwCooldownTicksFactor))); - } - - private long getSuppressPearlTeleportWindowMillis() { - return Math.max(1000L, getConfig().suppressPearlTeleportWindowMillis); - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - suppressPearlTeleportUntil.entrySet().removeIf(i -> i.getValue() <= now); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Tag entities into ender pearls and throw those pearls to reposition the tagged target.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.95; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Throw Cooldown Ticks Base for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double throwCooldownTicksBase = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Throw Cooldown Ticks Factor for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double throwCooldownTicksFactor = 14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Suppress Pearl Teleport Window Millis for the Rift Ender Taglock adaptation.", impact = "This should be long enough to catch the teleport event from a taglocked throw.") - long suppressPearlTeleportWindowMillis = 180000L; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Large Width Threshold for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double largeWidthThreshold = 1.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Large Height Threshold for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double largeHeightThreshold = 2.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP On Tag for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnTag = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP On Throw for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnThrow = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP On Teleport for the Rift Ender Taglock adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnTeleport = 14; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftEnderchest.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftEnderchest.java deleted file mode 100644 index c988bc449..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftEnderchest.java +++ /dev/null @@ -1,124 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; - - -public class RiftEnderchest extends SimpleAdaptation { - public RiftEnderchest() { - super("rift-enderchest"); - setDescription(Localizer.dLocalize("rift.chest.description")); - setDisplayName(Localizer.dLocalize("rift.chest.name")); - setIcon(Material.ENDER_CHEST); - setBaseCost(0); - setCostFactor(0); - setMaxLevel(1); - setInitialCost(10); - setInterval(9248); - registerConfiguration(Config.class); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_CHEST) - .key("challenge_rift_enderchest_200") - .title(Localizer.dLocalize("advancement.challenge_rift_enderchest_200.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_enderchest_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_rift_enderchest_200", "rift.enderchest.opens", 200, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.ITALIC + Localizer.dLocalize("rift.chest.lore1")); - } - - @EventHandler - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - ItemStack hand = p.getInventory().getItemInMainHand(); - - if (hand.getType() != Material.ENDER_CHEST || !hasAdaptation(p)) { - return; - } - - if (p.hasCooldown(hand.getType())) { - e.setCancelled(true); - } else { - p.setCooldown(Material.ENDER_CHEST, 100); - - if ((e.getAction() == Action.RIGHT_CLICK_AIR) || (e.getAction() == Action.LEFT_CLICK_AIR) || (e.getAction() == Action.LEFT_CLICK_BLOCK)) { - PlayerSkillLine line = getPlayer(p).getData().getSkillLine("rift"); - PlayerAdaptation adaptation = line != null ? line.getAdaptation("rift-resist") : null; - if (adaptation != null && adaptation.getLevel() > 0) { - RiftResist.riftResistStackAdd(p, 10, 2); - } - sp.play(p.getLocation(), Sound.PARTICLE_SOUL_ESCAPE, 1f, 0.10f); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 1f, 0.10f); - p.openInventory(p.getEnderChest()); - getPlayer(p).getData().addStat("rift.enderchest.opens", 1); - } - } - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Open an enderchest by left-clicking it in your hand.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftGate.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftGate.java deleted file mode 100644 index e06564a6d..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftGate.java +++ /dev/null @@ -1,287 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.recipe.AdaptRecipe; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.event.AdaptAdaptationTeleportEvent; -import com.volmit.adapt.content.item.BoundEyeOfEnder; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitRunnable; - - -public class RiftGate extends SimpleAdaptation { - public RiftGate() { - super("rift-gate"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.gate.description")); - setDisplayName(Localizer.dLocalize("rift.gate.name")); - setIcon(Material.RESPAWN_ANCHOR); - setBaseCost(0); - setCostFactor(0); - setMaxLevel(1); - setInitialCost(30); - setInterval(1322); - registerRecipe(AdaptRecipe.shapeless() - .key("rift-recall-gate") - .ingredient(Material.ENDER_PEARL) - .ingredient(Material.AMETHYST_SHARD) - .ingredient(Material.EMERALD) - .result(BoundEyeOfEnder.io.withData(new BoundEyeOfEnder.Data(null))) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_gate_100") - .title(Localizer.dLocalize("advancement.challenge_rift_gate_100.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_gate_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_rift_gate_50k_dist") - .title(Localizer.dLocalize("advancement.challenge_rift_gate_50k_dist.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_gate_50k_dist.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_gate_100", "rift.gate.teleports", 100, 400); - registerMilestone("challenge_rift_gate_50k_dist", "rift.gate.total-distance", 50000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.YELLOW + Localizer.dLocalize("rift.gate.lore1")); - v.addLore(C.RED + Localizer.dLocalize("rift.gate.lore2")); - v.addLore(C.ITALIC + Localizer.dLocalize("rift.gate.lore3") + C.UNDERLINE + C.RED + Localizer.dLocalize("rift.gate.lore4")); - } - - - @EventHandler - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - ItemStack hand = p.getInventory().getItemInMainHand(); - ItemStack offHand = p.getInventory().getItemInOffHand(); - Location location = e.getClickedBlock() == null ? p.getLocation() : e.getClickedBlock().getLocation(); - - // Deny usage if the offhand contains a bindable item - if (BoundEyeOfEnder.isBindableItem(offHand) && e.getHand() != null && e.getHand().equals(EquipmentSlot.OFF_HAND)) { - e.setCancelled(true); - return; - } - - if (p.getInventory().getItemInMainHand().getType().equals(Material.ENDER_EYE) - && !p.hasCooldown(Material.ENDER_EYE) - && hasAdaptation(p) - && BoundEyeOfEnder.isBindableItem(hand)) { - - e.setCancelled(true); - Adapt.verbose(" - Player Main hand: " + hand.getType()); - switch (e.getAction()) { - case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> { - if (p.isSneaking()) { - Adapt.verbose("Linking eye"); - linkEye(p, location); - } - } - case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> // use - { - if (isBound(hand)) { - openEye(p); - } - } - } - } - } - - - private void handleEyeOfEnderInteraction(PlayerInteractEvent event, Player player, Block block) { - boolean sneaking = player.isSneaking(); - ItemStack mainHand = player.getInventory().getItemInMainHand(); - Location location = block == null ? player.getLocation() : block.getLocation(); - - switch (event.getAction()) { - case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> { - if (sneaking) { - if (isBound(mainHand)) { - unlinkEye(player); - } else { - linkEye(player, location); - } - } - } - case RIGHT_CLICK_AIR, RIGHT_CLICK_BLOCK -> { - if (isBound(mainHand)) { - openEye(player); - } - } - default -> { - } - } - } - - private boolean isBound(ItemStack stack) { - return stack.getType().equals(Material.ENDER_EYE) && BoundEyeOfEnder.getLocation(stack) != null; - } - - private void unlinkEye(Player p) { - ItemStack hand = p.getInventory().getItemInMainHand(); - decrementItemstack(hand, p); - ItemStack eye = new ItemStack(Material.ENDER_EYE); - p.getInventory().addItem(eye).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); - } - - private void linkEye(Player p, Location location) { - if (areParticlesEnabled()) { - vfxCuboidOutline(location.getBlock(), location.add(0, 1, 0).getBlock(), Particle.REVERSE_PORTAL); - } - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.ENTITY_ENDER_EYE_DEATH, 0.50f, 0.22f); - ItemStack hand = p.getInventory().getItemInMainHand(); - - if (hand.getAmount() == 1) { - BoundEyeOfEnder.setData(hand, location); - } else { - hand.setAmount(hand.getAmount() - 1); - ItemStack eye = BoundEyeOfEnder.withData(location); - p.getInventory().addItem(eye).values().forEach(i -> p.getWorld().dropItemNaturally(p.getLocation(), i)); - } - } - - - private void openEye(Player p) { - Adapt.verbose("Using eye"); - SoundPlayer sp = SoundPlayer.of(p); - Location l = BoundEyeOfEnder.getLocation(p.getInventory().getItemInMainHand()); - ItemStack hand = p.getInventory().getItemInMainHand(); - - if (getConfig().consumeOnUse) { - xp(p, 75); - decrementItemstack(hand, p); - } else { - if (p.getCooldown(Material.ENDER_EYE) > 0) { - sp.play(p.getLocation(), Sound.BLOCK_REDSTONE_TORCH_BURNOUT, 1, 1); - return; - } - } - p.setCooldown(Material.ENDER_EYE, 150); - - - if (RiftResist.hasRiftResistPerk(getPlayer(p))) { - RiftResist.riftResistStackAdd(p, 150, 3); - } - - p.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 100, 10, true, false, false)); - p.addPotionEffect(new PotionEffect(PotionEffectType.LEVITATION, 85, 0, true, false, false)); - sp.play(l, Sound.BLOCK_LODESTONE_PLACE, 1f, 0.1f); - sp.play(l, Sound.BLOCK_BELL_RESONATE, 1f, 0.1f); - - new BukkitRunnable() { - private long dur = 4000; - private double radius = 2.0; - private double adder = 0.0; - private final Color color = Color.fromBGR(0, 0, 0); - private boolean initialRingShown = false; - - @Override - public void run() { - if (!p.isOnline()) { - cancel(); - return; - } - - if (!initialRingShown) { - vfxFastRing(p.getLocation(), radius, color); - initialRingShown = true; - } - - dur -= 50; - if (dur <= 0) { - cancel(); - return; - } - - adder += 0.02; - radius *= 0.9; - vfxFastRing(p.getLocation().add(0, adder, 0), radius, color); - } - }.runTaskTimer(Adapt.instance, 0L, 1L); - vfxLevelUp(p); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 5.35f, 0.1f); - J.s(() -> { - AdaptAdaptationTeleportEvent event = new AdaptAdaptationTeleportEvent(!Bukkit.isPrimaryThread(), getPlayer(p), this, p.getLocation(), l); - Bukkit.getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - - getPlayer(p).getData().addStat("rift.teleports", 1); - getPlayer(p).getData().addStat("rift.gate.teleports", 1); - getPlayer(p).getData().addStat("rift.gate.total-distance", (int) p.getLocation().distance(l)); - p.teleport(l, PlayerTeleportEvent.TeleportCause.PLUGIN); - vfxLevelUp(p); - sp.play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 5.35f, 0.1f); - }, 85); - } - - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Craft a gate item to teleport to a marked location.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Consume On Use for the Rift Gate adaptation.", impact = "True enables this behavior and false disables it.") - boolean consumeOnUse = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Rift Gate adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftInflatedPocketDimension.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftInflatedPocketDimension.java deleted file mode 100644 index a8e194c6c..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftInflatedPocketDimension.java +++ /dev/null @@ -1,316 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; - -import java.util.Map; - -public class RiftInflatedPocketDimension extends SimpleAdaptation { - public RiftInflatedPocketDimension() { - super("rift-inflated-pocket-dimension"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.inflated_pocket_dimension.description")); - setDisplayName(Localizer.dLocalize("rift.inflated_pocket_dimension.name")); - setIcon(Material.ENDER_EYE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(600); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_CHEST) - .key("challenge_rift_pocket_5k") - .title(Localizer.dLocalize("advancement.challenge_rift_pocket_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_pocket_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_CHEST) - .key("challenge_rift_pocket_store_10k") - .title(Localizer.dLocalize("advancement.challenge_rift_pocket_store_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_pocket_store_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_pocket_5k", "rift.inflated-pocket.items-pulled", 5000, 400); - registerMilestone("challenge_rift_pocket_store_10k", "rift.inflated-pocket.items-stored", 10000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.inflated_pocket_dimension.lore1")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.inflated_pocket_dimension.lore2")); - v.addLore(C.GREEN + "+ " + Localizer.dLocalize("rift.inflated_pocket_dimension.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerInteractEvent e) { - if (e.getHand() != EquipmentSlot.HAND) { - return; - } - - if (e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getClickedBlock() == null) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (!hand.getType().isAir()) { - return; - } - - Material requested = e.getClickedBlock().getType(); - if (!requested.isItem() || requested.isAir()) { - return; - } - - int moved = moveFromEnderToPlayer(p, requested, getConfig().rightClickPullAmount, true); - if (moved <= 0) { - return; - } - - e.setCancelled(true); - SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 0.4f, 1.7f); - getPlayer(p).getData().addStat("rift.inflated-pocket.items-pulled", moved); - xp(p, moved * getConfig().xpPerTransferredItem, "rift:inflated-pocket:pull"); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(BlockPlaceEvent e) { - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - - Material placed = e.getBlockPlaced().getType(); - if (!placed.isItem() || placed.isAir()) { - return; - } - - J.s(() -> { - ItemStack hand = p.getInventory().getItemInMainHand(); - int needed = 0; - if (hand.getType().isAir()) { - needed = Math.min(getConfig().buildRefillAmount, placed.getMaxStackSize()); - } else if (hand.getType() == placed && hand.getAmount() < placed.getMaxStackSize()) { - needed = Math.min(getConfig().buildRefillAmount, placed.getMaxStackSize() - hand.getAmount()); - } - - if (needed <= 0) { - return; - } - - int moved = moveFromEnderToPlayer(p, placed, needed, true); - if (moved <= 0) { - return; - } - - SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 0.3f, 1.9f); - getPlayer(p).getData().addStat("rift.inflated-pocket.items-pulled", moved); - xp(p, moved * getConfig().xpPerTransferredItem, "rift:inflated-pocket:build-refill"); - }, 1); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerDropItemEvent e) { - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking()) { - return; - } - - ItemStack dropped = e.getItemDrop().getItemStack().clone(); - if (!canFullyFitInInventory(p.getEnderChest().getContents(), dropped, p.getEnderChest().getMaxStackSize())) { - e.setCancelled(true); - e.getItemDrop().remove(); - SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 0.8f, 0.8f); - return; - } - - e.getItemDrop().remove(); - p.getEnderChest().addItem(dropped); - - SoundPlayer.of(p).play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_CLOSE, 0.5f, 1.4f); - getPlayer(p).getData().addStat("rift.inflated-pocket.items-stored", dropped.getAmount()); - xp(p, dropped.getAmount() * getConfig().xpPerTransferredItem, "rift:inflated-pocket:store"); - } - - private int moveFromEnderToPlayer(Player p, Material type, int amount, boolean preferMainHand) { - if (amount <= 0) { - return 0; - } - - int moved = 0; - ItemStack[] chest = p.getEnderChest().getContents(); - for (int slot = 0; slot < chest.length && moved < amount; slot++) { - ItemStack stack = chest[slot]; - if (!isItem(stack) || stack.getType() != type) { - continue; - } - - int take = Math.min(amount - moved, stack.getAmount()); - ItemStack transfer = stack.clone(); - transfer.setAmount(take); - - int inserted = insertIntoPlayerInventory(p, transfer, preferMainHand); - if (inserted <= 0) { - continue; - } - - moved += inserted; - if (stack.getAmount() <= inserted) { - chest[slot] = null; - } else { - stack.setAmount(stack.getAmount() - inserted); - chest[slot] = stack; - } - } - - p.getEnderChest().setContents(chest); - return moved; - } - - private int insertIntoPlayerInventory(Player p, ItemStack transfer, boolean preferMainHand) { - int requested = transfer.getAmount(); - if (requested <= 0) { - return 0; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (preferMainHand && (hand == null || hand.getType().isAir())) { - p.getInventory().setItemInMainHand(transfer); - return requested; - } - - if (preferMainHand && hand.getType() == transfer.getType() && hand.getAmount() < hand.getMaxStackSize()) { - int room = hand.getMaxStackSize() - hand.getAmount(); - int moved = Math.min(room, transfer.getAmount()); - hand.setAmount(hand.getAmount() + moved); - p.getInventory().setItemInMainHand(hand); - if (moved >= requested) { - return requested; - } - - transfer.setAmount(requested - moved); - Map overflow = p.getInventory().addItem(transfer); - int remaining = overflow.values().stream().mapToInt(ItemStack::getAmount).sum(); - return moved + Math.max(0, transfer.getAmount() - remaining); - } - - Map overflow = p.getInventory().addItem(transfer); - int remaining = overflow.values().stream().mapToInt(ItemStack::getAmount).sum(); - return Math.max(0, requested - remaining); - } - - private boolean canFullyFitInInventory(ItemStack[] contents, ItemStack stack, int inventoryMaxStackSize) { - if (!isItem(stack)) { - return false; - } - - int remaining = stack.getAmount(); - int maxPerSlot = Math.min(stack.getMaxStackSize(), inventoryMaxStackSize); - - for (ItemStack existing : contents) { - if (!isItem(existing) || !existing.isSimilar(stack)) { - continue; - } - - remaining -= Math.max(0, maxPerSlot - existing.getAmount()); - if (remaining <= 0) { - return true; - } - } - - for (ItemStack existing : contents) { - if (existing == null || existing.getType().isAir()) { - remaining -= maxPerSlot; - if (remaining <= 0) { - return true; - } - } - } - - return false; - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Building and empty-hand block targeting can fetch materials from your ender chest, and sneak-drop stores items into it.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Build Refill Amount for the Rift Inflated Pocket Dimension adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int buildRefillAmount = 64; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Right Click Pull Amount for the Rift Inflated Pocket Dimension adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int rightClickPullAmount = 64; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP Per Transferred Item for the Rift Inflated Pocket Dimension adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerTransferredItem = 0.08; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftResist.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftResist.java deleted file mode 100644 index f3ec5dfaf..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftResist.java +++ /dev/null @@ -1,144 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; - - -public class RiftResist extends SimpleAdaptation { - public RiftResist() { - super("rift-resist"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.resist.description")); - setDisplayName(Localizer.dLocalize("rift.resist.name")); - setIcon(Material.SCULK_VEIN); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(10288); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_resist_200") - .title(Localizer.dLocalize("advancement.challenge_rift_resist_200.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_resist_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_rift_resist_200", "rift.resist.activations", 200, 300); - } - - static void riftResistStackAdd(Player p, int duration, int amplifier) { - if (p.getLocation().getWorld() == null) { - return; - } - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - spw.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_IRON, 1f, 1.24f); - spw.play(p.getLocation(), Sound.BLOCK_CONDUIT_AMBIENT_SHORT, 1f, 0.01f); - spw.play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 1f, 0.01f); - p.addPotionEffect(new PotionEffect(PotionEffectTypes.DAMAGE_RESISTANCE, duration, amplifier, true, false, false)); - } - - public static boolean hasRiftResistPerk(AdaptPlayer p) { - return p.getData().getLevel() > 0; - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.ITALIC + Localizer.dLocalize("rift.resist.lore1")); - v.addLore(C.UNDERLINE + Localizer.dLocalize("rift.resist.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - ItemStack hand = p.getInventory().getItemInMainHand(); - - if (e.getAction() == Action.RIGHT_CLICK_AIR) { - switch (hand.getType()) { - case ENDER_EYE, ENDER_PEARL -> { - xp(p, 3); - riftResistStackAdd(p, getConfig().duration, getConfig().amplitude); - getPlayer(p).getData().addStat("rift.resist.activations", 1); - } - } - } - - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain resistance when using Ender items and abilities.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Amplitude for the Rift Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int amplitude = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Duration for the Rift Resist adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int duration = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftVisage.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftVisage.java deleted file mode 100644 index 75115c6ef..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftVisage.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Enderman; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityTargetEvent; -import org.bukkit.inventory.ItemStack; - -public class RiftVisage extends SimpleAdaptation { - public RiftVisage() { - super("rift-visage"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.visage.description")); - setDisplayName(Localizer.dLocalize("rift.visage.name")); - setIcon(Material.POPPED_CHORUS_FRUIT); - setBaseCost(getConfig().baseCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_rift_visage_100") - .title(Localizer.dLocalize("advancement.challenge_rift_visage_100.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_visage_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DRAGON_HEAD) - .key("challenge_rift_visage_1k") - .title(Localizer.dLocalize("advancement.challenge_rift_visage_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_visage_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_visage_100", "rift.visage.stares-survived", 100, 300); - registerMilestone("challenge_rift_visage_1k", "rift.visage.stares-survived", 1000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.ITALIC + Localizer.dLocalize("rift.visage.lore1")); - } - - @EventHandler - public void onEntityTarget(EntityTargetEvent event) { - Entity entity = event.getEntity(); - if (entity instanceof Enderman) { - if (event.getTarget() instanceof Player player) { - if (hasAdaptation(player) && hasEnderPearl(player)) { - event.setCancelled(true); - getPlayer(player).getData().addStat("rift.visage.stares-survived", 1); - } - } - } - } - - private boolean hasEnderPearl(Player player) { - for (ItemStack item : player.getInventory().getContents()) { - if (item != null && item.getType() == Material.ENDER_PEARL) { - return true; - } - } - return false; - } - - @Override - public void onTick() { - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Prevents Endermen from becoming aggressive when you carry Enderpearls.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftVoidMagnet.java b/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftVoidMagnet.java deleted file mode 100644 index a06a507a8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/rift/RiftVoidMagnet.java +++ /dev/null @@ -1,215 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.rift; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import java.util.Map; - -public class RiftVoidMagnet extends SimpleAdaptation { - public RiftVoidMagnet() { - super("rift-void-magnet"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("rift.void_magnet.description")); - setDisplayName(Localizer.dLocalize("rift.void_magnet.name")); - setIcon(Material.HOPPER_MINECART); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(20); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_void_magnet_5k") - .title(Localizer.dLocalize("advancement.challenge_rift_void_magnet_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_void_magnet_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_rift_void_magnet_50k") - .title(Localizer.dLocalize("advancement.challenge_rift_void_magnet_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_void_magnet_50k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_void_magnet_5k", "rift.void-magnet.items-pulled", 5000, 400); - registerMilestone("challenge_rift_void_magnet_50k", "rift.void-magnet.items-pulled", 50000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("rift.void_magnet.lore1")); - v.addLore(C.GREEN + "+ " + getMaxItems(level) + C.GRAY + " " + Localizer.dLocalize("rift.void_magnet.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getPulseTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("rift.void_magnet.lore3")); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking() || p.getTicksLived() % getPulseTicks(getLevel(p)) != 0) { - continue; - } - - int level = getLevel(p); - int moved = collectNearbyItems(p, level); - if (moved <= 0) { - continue; - } - - if (areParticlesEnabled()) { - - p.spawnParticle(Particle.PORTAL, p.getLocation().add(0, 1, 0), 8, 0.3, 0.5, 0.3, 0.05); - - } - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_ENDER_CHEST_OPEN, 0.45f, 1.6f); - getPlayer(p).getData().addStat("rift.void-magnet.items-pulled", moved); - xp(p, moved * getConfig().xpPerMovedItem, "rift:void-magnet:item-pull"); - } - } - - private int collectNearbyItems(Player p, int level) { - int moved = 0; - int max = getMaxItems(level); - double r = getRadius(level); - for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), r, r, r)) { - if (!(entity instanceof Item item)) { - continue; - } - - if (moved >= max || item.isDead() || !item.isValid()) { - continue; - } - - ItemStack stack = item.getItemStack(); - if (stack == null || stack.getType().isAir()) { - continue; - } - - int requestAmount = Math.min(stack.getAmount(), max - moved); - if (requestAmount <= 0) { - continue; - } - - ItemStack toChest = stack.clone(); - toChest.setAmount(requestAmount); - Map chestOverflow = p.getEnderChest().addItem(toChest); - int chestRemaining = chestOverflow.values().stream().mapToInt(ItemStack::getAmount).sum(); - int movedAmount = Math.max(0, requestAmount - chestRemaining); - - if (chestRemaining > 0 && getConfig().allowEnderChestOverflow) { - ItemStack toInventory = stack.clone(); - toInventory.setAmount(chestRemaining); - Map inventoryOverflow = p.getInventory().addItem(toInventory); - int inventoryRemaining = inventoryOverflow.values().stream().mapToInt(ItemStack::getAmount).sum(); - movedAmount += Math.max(0, chestRemaining - inventoryRemaining); - } - - if (movedAmount <= 0) { - continue; - } - - if (movedAmount >= stack.getAmount()) { - item.remove(); - } else { - stack.setAmount(stack.getAmount() - movedAmount); - item.setItemStack(stack); - } - moved += movedAmount; - } - - return moved; - } - - private double getRadius(int level) { - return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); - } - - private int getMaxItems(int level) { - return Math.max(1, (int) Math.round(getConfig().maxItemsBase + (getLevelPercent(level) * getConfig().maxItemsFactor))); - } - - private int getPulseTicks(int level) { - return Math.max(2, (int) Math.round(getConfig().pulseTicksBase - (getLevelPercent(level) * getConfig().pulseTicksFactor))); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak to periodically pull nearby dropped items into your ender chest first.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Allow Ender Chest Overflow for the Rift Void Magnet adaptation.", impact = "When true, leftovers that do not fit in ender chest can spill into player inventory.") - boolean allowEnderChestOverflow = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusBase = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Items Base for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxItemsBase = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Items Factor for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxItemsFactor = 22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Pulse Ticks Base for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pulseTicksBase = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Pulse Ticks Factor for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pulseTicksFactor = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Moved Item for the Rift Void Magnet adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerMovedItem = 0.7; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneFishersFantasy.java b/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneFishersFantasy.java deleted file mode 100644 index 95fc8e4f1..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneFishersFantasy.java +++ /dev/null @@ -1,136 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.seaborrne; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.ExperienceOrb; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerFishEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.concurrent.ThreadLocalRandom; - -public class SeaborneFishersFantasy extends SimpleAdaptation { - - public SeaborneFishersFantasy() { - super("seaborne-fishers-fantasy"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("seaborn.fishers_fantasy.description")); - setDisplayName(Localizer.dLocalize("seaborn.fishers_fantasy.name")); - setIcon(Material.FISHING_ROD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(8080); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FISHING_ROD) - .key("challenge_seaborne_fish_500") - .title(Localizer.dLocalize("advancement.challenge_seaborne_fish_500.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_fish_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TROPICAL_FISH) - .key("challenge_seaborne_fish_5k") - .title(Localizer.dLocalize("advancement.challenge_seaborne_fish_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_fish_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_seaborne_fish_500", "seaborne.fishers-fantasy.fish-caught", 500, 300); - registerMilestone("challenge_seaborne_fish_5k", "seaborne.fishers-fantasy.fish-caught", 5000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("seaborn.fishers_fantasy.lore1")); - } - - @EventHandler - public void on(PlayerFishEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - if (e.getState() == PlayerFishEvent.State.CAUGHT_FISH) { - getPlayer(p).getData().addStat("seaborne.fishers-fantasy.fish-caught", 1); - int level = getLevel(p); - ThreadLocalRandom random = ThreadLocalRandom.current(); - for (int i = 0; i < level; i++) { - ItemStack item = new ItemStack(ItemListings.getFishingDrops().getRandom(), 1); - if (random.nextBoolean()) { - p.getWorld().dropItemNaturally(p.getLocation(), item); - p.getWorld().spawn(p.getLocation(), ExperienceOrb.class); - Adapt.verbose("Fishing Gift Donated!"); - xp(p, 15 * level); - } - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Earn more XP from fishing and catch more fish.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.9; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneOxygen.java b/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneOxygen.java deleted file mode 100644 index b75b1dd41..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneOxygen.java +++ /dev/null @@ -1,108 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.seaborrne; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -public class SeaborneOxygen extends SimpleAdaptation { - - public SeaborneOxygen() { - super("seaborne-oxygen"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("seaborn.oxygen.description")); - setDisplayName(Localizer.dLocalize("seaborn.oxygen.name")); - setIcon(Material.GLASS_PANE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(3750); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TURTLE_HELMET) - .key("challenge_seaborne_oxygen_12k") - .title(Localizer.dLocalize("advancement.challenge_seaborne_oxygen_12k.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_oxygen_12k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_seaborne_oxygen_12k", "seaborne.oxygen.bonus-air-ticks", 12000, 300); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getAirBoost(level), 0) + C.GRAY + Localizer.dLocalize("seaborn.oxygen.lore1")); - } - - public double getAirBoost(int level) { - return getLevelPercent(level); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - if (i.getLocation().getBlock().getType() == Material.WATER && hasAdaptation(i)) { - int airTicks = getLevel(i) * getConfig().airPerLevelTics; - i.addPotionEffect(new PotionEffect(PotionEffectType.WATER_BREATHING, airTicks, getLevel(i))); - getPlayer(i).getData().addStat("seaborne.oxygen.bonus-air-ticks", airTicks); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Hold more oxygen underwater.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.525; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Air Per Level Tics for the Seaborne Oxygen adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int airPerLevelTics = 15; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeabornePressureDiver.java b/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeabornePressureDiver.java deleted file mode 100644 index cdc9d3c16..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeabornePressureDiver.java +++ /dev/null @@ -1,244 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.seaborrne; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class SeabornePressureDiver extends SimpleAdaptation { - private final Map xpCooldowns = new HashMap<>(); - - public SeabornePressureDiver() { - super("seaborne-pressure-diver"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("seaborn.pressure_diver.description")); - setDisplayName(Localizer.dLocalize("seaborn.pressure_diver.name")); - setIcon(Material.NAUTILUS_SHELL); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(20); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_PICKAXE) - .key("challenge_seaborne_pressure_1k") - .title(Localizer.dLocalize("advancement.challenge_seaborne_pressure_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_pressure_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_seaborne_pressure_1k", "seaborne.pressure-diver.deep-blocks-mined", 1000, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getDepthThreshold(level), 1) + C.GRAY + " " + Localizer.dLocalize("seaborn.pressure_diver.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getDamageReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("seaborn.pressure_diver.lore2")); - v.addLore(C.GREEN + "+ " + Form.pc(getFatigueTrimChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("seaborn.pressure_diver.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - xpCooldowns.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Player p) || !hasAdaptation(p) || !p.isInWater()) { - return; - } - - int level = getLevel(p); - if (!isDeepEnough(p, level)) { - return; - } - - e.setDamage(e.getDamage() * (1D - getDamageReduction(level))); - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (!hasAdaptation(p) || !p.isInWater()) { - continue; - } - - int level = getLevel(p); - if (!isDeepEnough(p, level)) { - continue; - } - - applyDepthBuffs(p, level); - awardDepthXp(p, now); - getPlayer(p).getData().addStat("seaborne.pressure-diver.deep-blocks-mined", 1); - } - } - - private void applyDepthBuffs(Player p, int level) { - int resistanceAmp = getResistanceAmplifier(level, p); - p.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, getConfig().effectTicks, resistanceAmp, false, false, true), true); - p.addPotionEffect(new PotionEffect(PotionEffectType.WATER_BREATHING, getConfig().effectTicks, 0, false, false, true), true); - - PotionEffect fatigue = p.getPotionEffect(PotionEffectType.MINING_FATIGUE); - if (fatigue == null) { - return; - } - - if (ThreadLocalRandom.current().nextDouble() > getFatigueTrimChance(level)) { - return; - } - - int reducedAmp = Math.max(0, fatigue.getAmplifier() - getFatigueTrimAmount(level)); - p.addPotionEffect(new PotionEffect(PotionEffectType.MINING_FATIGUE, - Math.max(20, Math.min(fatigue.getDuration(), getConfig().fatigueReplaceTicks)), - reducedAmp, - false, - true, - true), true); - } - - private void awardDepthXp(Player p, long now) { - UUID id = p.getUniqueId(); - long next = xpCooldowns.getOrDefault(id, 0L); - if (now < next) { - return; - } - - xp(p, getConfig().xpPerDepthPulse); - xpCooldowns.put(id, now + getConfig().xpPulseCooldownMillis); - } - - private boolean isDeepEnough(Player p, int level) { - double seaLevel = p.getWorld().getSeaLevel(); - double depth = seaLevel - p.getEyeLocation().getY(); - return depth >= getDepthThreshold(level); - } - - private int getResistanceAmplifier(int level, Player p) { - double seaLevel = p.getWorld().getSeaLevel(); - double depth = seaLevel - p.getEyeLocation().getY(); - if (depth >= getDeepThreshold(level)) { - return 1; - } - - return 0; - } - - private double getDepthThreshold(int level) { - return Math.max(2, getConfig().depthThresholdBase - (getLevelPercent(level) * getConfig().depthThresholdFactor)); - } - - private double getDeepThreshold(int level) { - return Math.max(4, getConfig().deepThresholdBase - (getLevelPercent(level) * getConfig().deepThresholdFactor)); - } - - private double getDamageReduction(int level) { - return Math.min(getConfig().maxDamageReduction, getConfig().damageReductionBase + (getLevelPercent(level) * getConfig().damageReductionFactor)); - } - - private double getFatigueTrimChance(int level) { - return Math.min(1.0, getConfig().fatigueTrimChanceBase + (getLevelPercent(level) * getConfig().fatigueTrimChanceFactor)); - } - - private int getFatigueTrimAmount(int level) { - return Math.max(1, (int) Math.round(getConfig().fatigueTrimAmountBase + (getLevelPercent(level) * getConfig().fatigueTrimAmountFactor))); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain depth scaling protection underwater and partially counter mining fatigue in deep ocean play.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Depth Threshold Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double depthThresholdBase = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Depth Threshold Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double depthThresholdFactor = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Deep Threshold Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double deepThresholdBase = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Deep Threshold Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double deepThresholdFactor = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Reduction Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageReductionBase = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Reduction Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageReductionFactor = 0.26; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Maximum Damage Reduction for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxDamageReduction = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fatigue Trim Chance Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fatigueTrimChanceBase = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fatigue Trim Chance Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fatigueTrimChanceFactor = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fatigue Trim Amount Base for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fatigueTrimAmountBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fatigue Trim Amount Factor for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double fatigueTrimAmountFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effect Ticks for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int effectTicks = 60; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Fatigue Replace Ticks for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int fatigueReplaceTicks = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP Per Depth Pulse for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerDepthPulse = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP Pulse Cooldown Millis for the Seaborne Pressure Diver adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long xpPulseCooldownMillis = 3000; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneSpeed.java b/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneSpeed.java deleted file mode 100644 index 9dad3bc15..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneSpeed.java +++ /dev/null @@ -1,120 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.seaborrne; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -public class SeaborneSpeed extends SimpleAdaptation { - - public SeaborneSpeed() { - super("seaborne-speed"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("seaborn.dolphin_grace.description")); - setDisplayName(Localizer.dLocalize("seaborn.dolphin_grace.name")); - setIcon(Material.PRISMARINE_CRYSTALS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(1020); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.HEART_OF_THE_SEA) - .key("challenge_seaborne_speed_10k") - .title(Localizer.dLocalize("advancement.challenge_seaborne_speed_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_speed_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TRIDENT) - .key("challenge_seaborne_speed_100k") - .title(Localizer.dLocalize("advancement.challenge_seaborne_speed_100k.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_speed_100k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_seaborne_speed_10k", "seaborne.speed.blocks-swum", 10000, 300); - registerMilestone("challenge_seaborne_speed_100k", "seaborne.speed.blocks-swum", 100000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("seaborn.dolphin_grace.lore1") + C.GREEN + (level) + C.GRAY + Localizer.dLocalize("seaborn.dolphin_grace.lore2")); - v.addLore(C.ITALIC + Localizer.dLocalize("seaborn.dolphin_grace.lore3")); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player player = adaptPlayer.getPlayer(); - if (player.isInWater() && hasAdaptation(player)) { - if (player.getLocation().getBlock().isLiquid()) { - if (player.getInventory().getBoots() != null && player.getInventory().getBoots().containsEnchantment(Enchantment.DEPTH_STRIDER)) { - continue; - } else { - player.addPotionEffect(new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 62, getLevel(player))); - getPlayer(player).getData().addStat("seaborne.speed.blocks-swum", 1); - } - } - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Swim faster with dolphin-like grace.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.525; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTidecaller.java b/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTidecaller.java deleted file mode 100644 index c28c2e896..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTidecaller.java +++ /dev/null @@ -1,468 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.seaborrne; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.FluidCollisionMode; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerAnimationEvent; -import org.bukkit.event.player.PlayerAnimationType; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.util.Vector; - -public class SeaborneTidecaller extends SimpleAdaptation { - public SeaborneTidecaller() { - super("seaborne-tidecaller"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("seaborn.tidecaller.description")); - setDisplayName(Localizer.dLocalize("seaborn.tidecaller.name")); - setIcon(Material.HEART_OF_THE_SEA); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1600); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TRIDENT) - .key("challenge_seaborne_tidecaller_200") - .title(Localizer.dLocalize("advancement.challenge_seaborne_tidecaller_200.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_tidecaller_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.HEART_OF_THE_SEA) - .key("challenge_seaborne_tidecaller_5k") - .title(Localizer.dLocalize("advancement.challenge_seaborne_tidecaller_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_tidecaller_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_seaborne_tidecaller_200", "seaborne.tidecaller.dashes", 200, 300); - registerMilestone("challenge_seaborne_tidecaller_5k", "seaborne.tidecaller.dashes", 5000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getDashDistance(level), 1) + C.GRAY + " " + Localizer.dLocalize("seaborn.tidecaller.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("seaborn.tidecaller.lore2")); - java.util.List combos = getTriggerCombos(); - if (combos.isEmpty()) { - v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + "none"); - } else { - for (String combo : combos) { - v.addLore(C.AQUA + "* " + C.GRAY + "Trigger: " + C.WHITE + combo); - } - } - v.addLore(C.AQUA + "* " + C.GRAY + "Environment: " + C.WHITE + getEnvironmentSummary()); - } - - @Override - public String getDescription() { - return "Surge forward with a water burst in valid environments. " + summarizeTriggerDescription(); - } - - private String summarizeTriggerDescription() { - java.util.List combos = getTriggerCombos(); - if (combos.isEmpty()) { - return "No active triggers are currently enabled."; - } - - if (combos.size() == 1) { - return "Trigger: " + combos.get(0) + "."; - } - - if (combos.size() == 2) { - return "Triggers: " + combos.get(0) + " or " + combos.get(1) + "."; - } - - return "Triggers: " + combos.get(0) + ", " + combos.get(1) + ", +" + (combos.size() - 2) + " more."; - } - - private java.util.List getTriggerCombos() { - java.util.List triggers = new java.util.ArrayList<>(); - String env = getEnvironmentSummary(); - if (getConfig().enableSneakTrigger) { - triggers.add("Sneak" + (env.equals("none") ? "" : " (" + env + ")")); - } - - if (getConfig().enableAttackTrigger) { - String combo = getConfig().attackTriggerRequiresSneak ? "Sneak + Attack" : "Attack"; - if (getConfig().attackTriggerWaterOnly) { - combo += " (water only)"; - } else if (!env.equals("none")) { - combo += " (" + env + ")"; - } - triggers.add(combo); - } - - return triggers; - } - - private String getEnvironmentSummary() { - java.util.List modes = new java.util.ArrayList<>(); - if (getConfig().allowWaterTrigger) { - modes.add("water"); - } - - if (getConfig().allowRainTrigger) { - modes.add("rain"); - } - - if (modes.isEmpty()) { - return "none"; - } - - return String.join("/", modes); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerToggleSneakEvent e) { - Player p = e.getPlayer(); - if (!e.isSneaking()) { - return; - } - - tryDash(p, TriggerType.SNEAK); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerAnimationEvent e) { - if (e.getAnimationType() != PlayerAnimationType.ARM_SWING) { - return; - } - - tryDash(e.getPlayer(), TriggerType.ATTACK); - } - - private void tryDash(Player p, TriggerType triggerType) { - if (!hasAdaptation(p) || p.hasCooldown(Material.HEART_OF_THE_SEA)) { - return; - } - - if (triggerType == TriggerType.SNEAK && !getConfig().enableSneakTrigger) { - return; - } - - if (triggerType == TriggerType.ATTACK) { - if (!getConfig().enableAttackTrigger) { - return; - } - - if (getConfig().attackTriggerRequiresSneak && !p.isSneaking()) { - return; - } - - if (getConfig().attackTriggerWaterOnly && !isInWaterDashState(p)) { - return; - } - } - - if (!isDashEnvironmentValid(p)) { - return; - } - - int level = getLevel(p); - Vector direction = resolveDashDirection(p); - if (direction.lengthSquared() <= 0.000001) { - return; - } - - if (isBlockedAhead(p, direction)) { - return; - } - - boolean wasSwimming = p.isSwimming(); - org.bukkit.Location target = null; - org.bukkit.Location origin = null; - - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.SPLASH, p.getLocation().add(0, 1, 0), 20, 0.25, 0.35, 0.25, 0.08); - } - - if (getConfig().useVelocityDash) { - applyVelocityDash(p, direction, level); - target = p.getLocation().clone().add(direction.clone().multiply(Math.max(0.35, getDashDistance(level) * 0.35))); - } else { - origin = p.getLocation().clone(); - target = findSafeDashTarget(p, getDashDistance(level)); - if (target == null) { - return; - } - p.teleport(target); - applyDashMomentum(p, origin, target); - } - - preserveSwimStateAfterDash(p, wasSwimming); - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.SPLASH, target.clone().add(0, 1, 0), 30, 0.35, 0.45, 0.35, 0.08); - } - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.BUBBLE, target.clone().add(0, 0.9, 0), 18, 0.4, 0.35, 0.4, 0.05); - } - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.ITEM_TRIDENT_RIPTIDE_2, 0.75f, 1.2f); - sp.play(target, Sound.ENTITY_DOLPHIN_SPLASH, 0.65f, 1.15f); - p.setCooldown(Material.HEART_OF_THE_SEA, getCooldownTicks(level)); - xp(p, getConfig().xpPerBurst); - getPlayer(p).getData().addStat("seaborne.tidecaller.dashes", 1); - } - - private void applyVelocityDash(Player p, Vector direction, int level) { - Vector velocity = direction.clone().normalize(); - double scalar = Math.max(0, getConfig().velocityStrengthBase + (getLevelPercent(level) * getConfig().velocityStrengthFactor)); - velocity.multiply(scalar); - - double y = getConfig().velocityVerticalBase + (getLevelPercent(level) * getConfig().velocityVerticalFactor); - if (getConfig().velocityAdditive) { - velocity = p.getVelocity().clone().add(velocity).add(new Vector(0, y, 0)); - } else { - velocity.setY(y); - } - - double max = Math.max(0, getConfig().maxResultingVelocity); - if (max > 0 && velocity.lengthSquared() > max * max) { - velocity = velocity.normalize().multiply(max); - } - - p.setVelocity(velocity); - } - - private void applyDashMomentum(Player p, org.bukkit.Location origin, org.bukkit.Location target) { - if (!getConfig().applyForwardMomentumAfterDash) { - return; - } - - Vector momentum = target.toVector().subtract(origin.toVector()); - if (momentum.lengthSquared() <= 0.000001) { - momentum = origin.getDirection().clone(); - } - - momentum.normalize().multiply(Math.max(0, getConfig().forwardMomentum)); - double y = p.getVelocity().getY(); - if (getConfig().replaceVerticalMomentum) { - y = getConfig().verticalMomentum; - } else { - y += getConfig().verticalMomentum; - } - - momentum.setY(y); - p.setVelocity(momentum); - } - - private Vector resolveDashDirection(Player p) { - Vector direction = p.getLocation().getDirection().clone(); - if (getConfig().flattenVelocityDashDirection) { - direction.setY(0); - } - - if (direction.lengthSquared() <= 0.000001) { - return new Vector(); - } - - return direction.normalize(); - } - - private boolean isBlockedAhead(Player p, Vector direction) { - if (!getConfig().blockDashWhenWallAhead) { - return false; - } - - double distance = Math.max(0.1, getConfig().wallCheckDistance); - var hit = p.getWorld().rayTraceBlocks(p.getEyeLocation(), direction, distance, FluidCollisionMode.NEVER, true); - return hit != null && hit.getHitBlock() != null; - } - - private void preserveSwimStateAfterDash(Player p, boolean wasSwimming) { - if (!getConfig().preserveSwimmingAfterDash || !wasSwimming) { - return; - } - - if (!isInWaterDashState(p)) { - return; - } - - p.setSwimming(true); - J.s(() -> { - if (p.isOnline() && isInWaterDashState(p)) { - p.setSwimming(true); - } - }, 1); - } - - private boolean isDashEnvironmentValid(Player p) { - if (getConfig().allowWaterTrigger && isInWaterDashState(p)) { - return true; - } - - return getConfig().allowRainTrigger && isRainingAt(p); - } - - private boolean isInWaterDashState(Player p) { - return p.isInWater() || p.isSwimming() || p.getLocation().getBlock().isLiquid() || p.getEyeLocation().getBlock().isLiquid(); - } - - private boolean isRainingAt(Player p) { - if (!p.getWorld().hasStorm()) { - return false; - } - - int topY = p.getWorld().getHighestBlockYAt(p.getLocation()); - return p.getLocation().getY() >= topY - 1; - } - - private org.bukkit.Location findSafeDashTarget(Player p, double maxDistance) { - Vector direction = p.getLocation().getDirection().clone(); - if (direction.lengthSquared() <= 0.0001) { - return null; - } - - direction.normalize(); - for (double d = maxDistance; d >= 1.0; d -= 0.5) { - org.bukkit.Location c = p.getLocation().clone().add(direction.clone().multiply(d)); - if (isSafe(c)) { - return c; - } - } - - return null; - } - - private boolean isSafe(org.bukkit.Location location) { - Block feet = location.getBlock(); - Block head = location.clone().add(0, 1, 0).getBlock(); - Block floor = location.clone().subtract(0, 1, 0).getBlock(); - return feet.isPassable() && head.isPassable() && (floor.getType().isSolid() || floor.isLiquid()); - } - - private double getDashDistance(int level) { - return getConfig().dashDistanceBase + (getLevelPercent(level) * getConfig().dashDistanceFactor); - } - - private int getCooldownTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - private enum TriggerType { - SNEAK, - ATTACK - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak while it is raining to dash like a water blink through the storm.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Dash Distance Base for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dashDistanceBase = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Dash Distance Factor for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dashDistanceFactor = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 140; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Burst for the Seaborne Tidecaller adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerBurst = 11; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows the original rain-based trigger for Tidecaller dashes.", impact = "Disable this to make dashes water-only.") - boolean allowRainTrigger = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows Tidecaller dashes while the player is in water.", impact = "Enable this to make sneak/attack water woosh work consistently.") - boolean allowWaterTrigger = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables sneak-to-dash trigger.", impact = "Disable this if you only want attack-based trigger behavior.") - boolean enableSneakTrigger = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables attack-swing trigger (any item or empty hand).", impact = "Enable this for left-click water flings without requiring special items.") - boolean enableAttackTrigger = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Requires sneaking for attack-swing trigger.", impact = "True makes attack trigger only fire while sneaking; false allows it anytime in valid dash environments.") - boolean attackTriggerRequiresSneak = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Restricts attack-swing trigger to water states only.", impact = "True prevents accidental attack-trigger dashes on land even if rain trigger is enabled.") - boolean attackTriggerWaterOnly = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Uses velocity-based dash movement instead of teleporting to a target.", impact = "True makes tidecaller behave like a movement burst and prevents blink-style wall bypass.") - boolean useVelocityDash = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "If true, removes pitch from velocity dash direction.", impact = "True keeps movement mostly horizontal; false follows exact look direction including up/down.") - boolean flattenVelocityDashDirection = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base forward velocity strength of a velocity dash.", impact = "Higher values produce faster bursts.") - double velocityStrengthBase = 1.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Additional forward velocity strength gained by adaptation level.", impact = "Higher values make higher levels burst farther/faster.") - double velocityStrengthFactor = 0.85; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base vertical velocity contribution for velocity dashes.", impact = "Small positive values keep water movement fluid; lower values stay flatter.") - double velocityVerticalBase = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "Additional vertical velocity contribution gained by adaptation level.", impact = "Higher values increase upward kick at higher levels.") - double velocityVerticalFactor = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Adds dash velocity on top of current velocity when true.", impact = "True preserves momentum chains; false applies a fresh velocity vector.") - boolean velocityAdditive = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Hard cap on resulting velocity magnitude after dash.", impact = "Lower values are safer for anticheat and collisions.") - double maxResultingVelocity = 2.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Cancels dash if a solid block is detected directly ahead.", impact = "Prevents wall clipping and blink-like wall bypass.") - boolean blockDashWhenWallAhead = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Distance ahead used to detect blocking walls.", impact = "Higher values are safer but can block dashes near tight spaces.") - double wallCheckDistance = 1.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Applies forward velocity after the dash teleport.", impact = "True makes the dash feel fluid instead of instantly stopping at the target.") - boolean applyForwardMomentumAfterDash = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal forward momentum applied after the dash teleport.", impact = "Higher values create a stronger forward burst after each dash.") - double forwardMomentum = 1.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Vertical momentum added or set after the dash teleport.", impact = "Small positive values help keep water movement smooth; negative values push downward.") - double verticalMomentum = 0.02; - @com.volmit.adapt.util.config.ConfigDoc(value = "If true, replaces current vertical velocity with verticalMomentum.", impact = "False adds verticalMomentum on top of existing vertical motion.") - boolean replaceVerticalMomentum = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Re-applies swimming pose after dash when the player started swimming and remains in water.", impact = "Prevents dash teleports from popping swimmers out of swim posture.") - boolean preserveSwimmingAfterDash = true; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTurtlesMiningSpeed.java b/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTurtlesMiningSpeed.java deleted file mode 100644 index 668d85255..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTurtlesMiningSpeed.java +++ /dev/null @@ -1,115 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.seaborrne; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.PotionEffectTypes; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffect; - -public class SeaborneTurtlesMiningSpeed extends SimpleAdaptation { - - public SeaborneTurtlesMiningSpeed() { - super("seaborne-turtles-mining-speed"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("seaborn.haste.description")); - setDisplayName(Localizer.dLocalize("seaborn.haste.name")); - setIcon(Material.PRISMARINE_SHARD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(3000); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_PICKAXE) - .key("challenge_seaborne_mining_2500") - .title(Localizer.dLocalize("advancement.challenge_seaborne_mining_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_mining_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_PICKAXE) - .key("challenge_seaborne_mining_25k") - .title(Localizer.dLocalize("advancement.challenge_seaborne_mining_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_mining_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_seaborne_mining_2500", "seaborne.turtles-mining.blocks-underwater", 2500, 300); - registerMilestone("challenge_seaborne_mining_25k", "seaborne.turtles-mining.blocks-underwater", 25000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("seaborn.haste.lore1")); - } - - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player player = adaptPlayer.getPlayer(); - if (player.isInWater() && hasAdaptation(player)) { - if (player.getLocation().getBlock().isLiquid()) { - player.addPotionEffect(new PotionEffect(PotionEffectTypes.FAST_DIGGING, 62, 1, false, false)); - getPlayer(player).getData().addStat("seaborne.turtles-mining.blocks-underwater", 1); - } - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain haste while mining underwater.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTurtlesVision.java b/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTurtlesVision.java deleted file mode 100644 index b69df523b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/seaborrne/SeaborneTurtlesVision.java +++ /dev/null @@ -1,106 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.seaborrne; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -public class SeaborneTurtlesVision extends SimpleAdaptation { - - public SeaborneTurtlesVision() { - super("seaborne-turtles-vision"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("seaborn.night_vision.description")); - setDisplayName(Localizer.dLocalize("seaborn.night_vision.name")); - setIcon(Material.DIAMOND_HORSE_ARMOR); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(3000); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TURTLE_HELMET) - .key("challenge_seaborne_vision_72k") - .title(Localizer.dLocalize("advancement.challenge_seaborne_vision_72k.title")) - .description(Localizer.dLocalize("advancement.challenge_seaborne_vision_72k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_seaborne_vision_72k", "seaborne.turtles-vision.time-underwater", 72000, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("seaborn.night_vision.lore1")); - } - - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player player = adaptPlayer.getPlayer(); - if (player.isInWater() && hasAdaptation(player)) { - if (player.getLocation().getBlock().isLiquid()) { - player.addPotionEffect(new PotionEffect(PotionEffectType.NIGHT_VISION, 62, 0, false, false)); - getPlayer(player).getData().addStat("seaborne.turtles-vision.time-underwater", 1); - } - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain night vision while underwater.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthEnderVeil.java b/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthEnderVeil.java deleted file mode 100644 index cd18471a1..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthEnderVeil.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.volmit.adapt.content.adaptation.stealth; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.events.api.ReflectiveHandler; -import com.volmit.adapt.util.reflect.events.api.entity.EndermanAttackPlayerEvent; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityTargetLivingEntityEvent; - -public class StealthEnderVeil extends SimpleAdaptation { - - public StealthEnderVeil() { - super("stealth-enderveil"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("stealth.ender_veil.description")); - setDisplayName(Localizer.dLocalize("stealth.ender_veil.name")); - setIcon(Material.CARVED_PUMPKIN); - setBaseCost(getConfig().baseCost); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - setInterval(9182); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_stealth_ender_veil_200") - .title(Localizer.dLocalize("advancement.challenge_stealth_ender_veil_200.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_ender_veil_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_stealth_ender_veil_200", "stealth.ender-veil.stares-survived", 200, 300); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("stealth.ender_veil.lore" + (level < 2 ? 1 : 2))); - } - - @Override - public void onTick() { - - } - - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onTarget(EntityTargetLivingEntityEvent event) { - var target = event.getTarget(); - if (target == null - || target.getType() != EntityType.PLAYER - || event.getEntityType() != EntityType.ENDERMAN - || !(event.getTarget() instanceof Player player) - || !hasAdaptation(player)) { - return; - } - - if (getLevel(player) > 1 || player.isSneaking()) { - event.setCancelled(true); - getPlayer(player).getData().addStat("stealth.ender-veil.stares-survived", 1); - } - } - - @ReflectiveHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) - public void onTarget(EndermanAttackPlayerEvent event) { - var player = event.getPlayer(); - if (!hasAdaptation(player)) { - return; - } - - if (getLevel(player) > 1 || player.isSneaking()) { - event.setCancelled(true); - getPlayer(player).getData().addStat("stealth.ender-veil.stares-survived", 1); - } - } - - @NoArgsConstructor - @ConfigDescription("Prevent Enderman aggression without wearing a pumpkin.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 1.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthGhostArmor.java b/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthGhostArmor.java deleted file mode 100644 index 38a66aaae..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthGhostArmor.java +++ /dev/null @@ -1,171 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.stealth; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.version.IAttribute; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.Attributes; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.attribute.AttributeModifier; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; - -import java.util.UUID; - -public class StealthGhostArmor extends SimpleAdaptation { - private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-ghost-armor".getBytes()); - private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString( "adapt:ghost-armor"); - - public StealthGhostArmor() { - super("stealth-ghost-armor"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("stealth.ghost_armor.description")); - setDisplayName(Localizer.dLocalize("stealth.ghost_armor.name")); - setIcon(Material.CHAINMAIL_HELMET); - setInterval(5353); - setBaseCost(getConfig().baseCost); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEATHER_CHESTPLATE) - .key("challenge_stealth_ghost_100") - .title(Localizer.dLocalize("advancement.challenge_stealth_ghost_100.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_ghost_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CHAINMAIL_CHESTPLATE) - .key("challenge_stealth_ghost_500") - .title(Localizer.dLocalize("advancement.challenge_stealth_ghost_500.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_ghost_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_stealth_ghost_100", "stealth.ghost-armor.armor-consumed", 100, 300); - registerMilestone("challenge_stealth_ghost_500", "stealth.ghost-armor.armor-consumed", 500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getMaxArmorPoints(getLevelPercent(level)), 0) + C.GRAY + " " + Localizer.dLocalize("stealth.ghost_armor.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(getMaxArmorPerTick(getLevelPercent(level)), 1) + C.GRAY + " " + Localizer.dLocalize("stealth.ghost_armor.lore2")); - } - - public double getMaxArmorPoints(double factor) { - return M.lerp(getConfig().minArmor, getConfig().maxArmor, factor); - } - - public double getMaxArmorPerTick(double factor) { - return M.lerp(getConfig().minArmorPerTick, getConfig().maxArmorPerTick, factor); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - var attribute = Version.get().getAttribute(p, Attributes.GENERIC_ARMOR); - - if (!hasAdaptation(p)) { - attribute.removeModifier(MODIFIER, MODIFIER_KEY); - continue; - } - double oldArmor = attribute.getModifier(MODIFIER, MODIFIER_KEY) - .stream() - .mapToDouble(IAttribute.Modifier::getAmount) - .filter(d -> !Double.isNaN(d)) - .max() - .orElse(0);; - double armor = getMaxArmorPoints(getLevelPercent(p)); - armor = Double.isNaN(armor) ? 0 : armor; - - if (oldArmor < armor) { - attribute.setModifier(MODIFIER, MODIFIER_KEY, Math.min(armor, oldArmor + getMaxArmorPerTick(getLevelPercent(p))), AttributeModifier.Operation.ADD_NUMBER); - } else if (oldArmor > armor) { - attribute.setModifier(MODIFIER, MODIFIER_KEY, armor, AttributeModifier.Operation.ADD_NUMBER); - } - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof Player p && hasAdaptation(p) && !e.isCancelled() && e.getDamage() > 0) { - // Check if 2.5 * e.getDamage() is greater than 10 if so just set it to 10 otherwise use the value of 2.5 * e.getDamage() - int damageXP = (int) Math.min(10, 2.5 * e.getDamage()); - xp(p,damageXP ); - getPlayer(p).getData().addStat("stealth.ghost-armor.armor-consumed", 1); - J.s(() -> { - var attribute = Version.get().getAttribute(p, Attributes.GENERIC_ARMOR); - if (attribute == null) return; - attribute.removeModifier(MODIFIER, MODIFIER_KEY); - }); - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Slowly build armor when not taking damage, consumed on the next hit.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Armor for the Stealth Ghost Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxArmor = 16; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Armor for the Stealth Ghost Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int minArmor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Armor Per Tick for the Stealth Ghost Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int maxArmorPerTick = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Armor Per Tick for the Stealth Ghost Armor adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int minArmorPerTick = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.335; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthShadowDecoy.java b/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthShadowDecoy.java deleted file mode 100644 index 5ec7695a3..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthShadowDecoy.java +++ /dev/null @@ -1,1299 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.stealth; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.World; -import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Mob; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.player.PlayerAnimationEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.inventory.EntityEquipment; -import org.bukkit.inventory.ItemStack; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.RayTraceResult; -import org.bukkit.util.Vector; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -public class StealthShadowDecoy extends SimpleAdaptation { - private static final PacketDecoyBridge PACKET_DECOY = PacketDecoyBridge.create(); - - private final Map cooldowns = new HashMap<>(); - private final Map activeDecoys = new HashMap<>(); - private final Map anchorOwners = new HashMap<>(); - private final Map ownerEquipmentMaskSync = new HashMap<>(); - - public StealthShadowDecoy() { - super("stealth-shadow-decoy"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("stealth.shadow_decoy.description")); - setDisplayName(Localizer.dLocalize("stealth.shadow_decoy.name")); - setIcon(Material.PLAYER_HEAD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(5); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ARMOR_STAND) - .key("challenge_stealth_decoy_100") - .title(Localizer.dLocalize("advancement.challenge_stealth_decoy_100.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_decoy_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ARMOR_STAND) - .key("challenge_stealth_decoy_distract_500") - .title(Localizer.dLocalize("advancement.challenge_stealth_decoy_distract_500.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_decoy_distract_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_stealth_decoy_100", "stealth.shadow-decoy.decoys-spawned", 100, 300); - registerMilestone("challenge_stealth_decoy_distract_500", "stealth.shadow-decoy.mobs-distracted", 500, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.duration(getDecoyTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("stealth.shadow_decoy.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(getDecoyRadius(level)) + C.GRAY + " " + Localizer.dLocalize("stealth.shadow_decoy.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("stealth.shadow_decoy.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - UUID id = e.getPlayer().getUniqueId(); - cooldowns.remove(id); - ownerEquipmentMaskSync.remove(id); - DecoyState state = activeDecoys.remove(id); - if (state != null) { - removeDecoy(state, null); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageEvent e) { - if (anchorOwners.containsKey(e.getEntity().getUniqueId())) { - e.setCancelled(true); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageByEntityEvent e) { - UUID ownerId = anchorOwners.get(e.getEntity().getUniqueId()); - if (ownerId == null) { - return; - } - - e.setCancelled(true); - DecoyState state = activeDecoys.get(ownerId); - if (state == null) { - return; - } - - if (!(e.getEntity() instanceof ArmorStand stand) || !(e.getDamager() instanceof LivingEntity attacker)) { - return; - } - - reactToDecoyHit(state, stand, attacker); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerAnimationEvent e) { - Player attacker = e.getPlayer(); - if (activeDecoys.isEmpty()) { - return; - } - - RayTraceResult hit = attacker.getWorld().rayTraceEntities( - attacker.getEyeLocation(), - attacker.getEyeLocation().getDirection(), - Math.max(1.0, getConfig().decoySwingDetectionReach), - entity -> anchorOwners.containsKey(entity.getUniqueId()) - ); - - if (hit == null || !(hit.getHitEntity() instanceof ArmorStand stand)) { - return; - } - - UUID ownerId = anchorOwners.get(stand.getUniqueId()); - if (ownerId == null) { - return; - } - - DecoyState state = activeDecoys.get(ownerId); - if (state == null) { - return; - } - - reactToDecoyHit(state, stand, attacker); - } - - private void reactToDecoyHit(DecoyState state, ArmorStand stand, LivingEntity attacker) { - if (state.packetDecoy() != null) { - state.packetDecoy().hitFrom(attacker.getLocation()); - } - - Vector push = stand.getLocation().toVector().subtract(attacker.getLocation().toVector()); - if (push.lengthSquared() < 0.0001) { - push = attacker.getLocation().getDirection().multiply(-1); - } - - push.setY(0); - push.normalize().multiply(Math.max(0, getConfig().decoyHitKnockback)); - push.setY(Math.max(0, getConfig().decoyHitLift)); - stand.setVelocity(push); - SoundPlayer.of(stand.getWorld()).play(stand.getLocation(), Sound.ENTITY_PLAYER_HURT, 0.8f, 1.2f); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(PlayerToggleSneakEvent e) { - Player p = e.getPlayer(); - if (e.isSneaking() || !hasAdaptation(p)) { - return; - } - - int level = getLevel(p); - long now = System.currentTimeMillis(); - if (now < cooldowns.getOrDefault(p.getUniqueId(), 0L)) { - return; - } - - spawnDecoy(p, level); - cooldowns.put(p.getUniqueId(), now + getCooldownMillis(level)); - xp(p, getConfig().xpOnDecoy); - getPlayer(p).getData().addStat("stealth.shadow-decoy.decoys-spawned", 1); - } - - private void spawnDecoy(Player owner, int level) { - DecoyState previous = activeDecoys.remove(owner.getUniqueId()); - if (previous != null) { - removeDecoy(previous, owner); - } - - ArmorStand anchor = spawnAnchor(owner.getLocation()); - anchorOwners.put(anchor.getUniqueId(), owner.getUniqueId()); - PacketPlayerDecoy packetDecoy = PACKET_DECOY.spawnDecoy(owner, anchor, getConfig().tabListRemoveDelayTicks, getConfig().decoySkinLayerMask); - - if (packetDecoy == null && getConfig().legacyFallbackEnabled) { - configureLegacyVisual(anchor, owner); - } - - long expiresAt = System.currentTimeMillis() + (getDecoyTicks(level) * 50L); - activeDecoys.put(owner.getUniqueId(), new DecoyState(anchor.getUniqueId(), packetDecoy, expiresAt, level)); - - redirectAggro(owner, anchor, level); - if (areParticlesEnabled()) { - anchor.getWorld().spawnParticle(Particle.SMOKE, anchor.getLocation().add(0, 1, 0), 18, 0.2, 0.4, 0.2, 0.03); - } - SoundPlayer.of(owner.getWorld()).play(owner.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.6f, 1.7f); - } - - private ArmorStand spawnAnchor(Location location) { - return location.getWorld().spawn(location, ArmorStand.class, stand -> { - stand.setMarker(false); - stand.setVisible(false); - stand.setInvisible(true); - stand.setGravity(true); - stand.setInvulnerable(false); - stand.setSilent(true); - stand.setBasePlate(false); - stand.setSmall(false); - stand.setArms(false); - stand.setCollidable(true); - }); - } - - private void configureLegacyVisual(ArmorStand stand, Player owner) { - stand.setMarker(false); - stand.setVisible(true); - stand.setInvisible(false); - stand.setSmall(false); - stand.setArms(true); - stand.setCustomNameVisible(true); - stand.setCustomName(C.GRAY + owner.getName()); - - EntityEquipment equipment = stand.getEquipment(); - if (equipment == null) { - return; - } - - equipment.setHelmet(owner.getInventory().getHelmet()); - equipment.setChestplate(owner.getInventory().getChestplate()); - equipment.setLeggings(owner.getInventory().getLeggings()); - equipment.setBoots(owner.getInventory().getBoots()); - equipment.setItemInMainHand(owner.getInventory().getItemInMainHand()); - equipment.setItemInOffHand(owner.getInventory().getItemInOffHand()); - } - - private void redirectAggro(Player owner, LivingEntity target, int level) { - double radius = getDecoyRadius(level); - Location center = target.getLocation(); - for (Entity entity : owner.getWorld().getNearbyEntities(center, radius, radius, radius)) { - if (!(entity instanceof Mob mob)) { - continue; - } - - if (mob.getTarget() == owner || mob.hasLineOfSight(owner)) { - mob.setTarget(target); - getPlayer(owner).getData().addStat("stealth.shadow-decoy.mobs-distracted", 1); - } - } - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - Iterator> it = activeDecoys.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - UUID ownerId = entry.getKey(); - DecoyState state = entry.getValue(); - Player owner = Bukkit.getPlayer(ownerId); - - if (owner == null || !owner.isOnline() || state.expiresAt() <= now) { - removeDecoy(state, owner); - it.remove(); - continue; - } - - Entity entity = Bukkit.getEntity(state.anchorId()); - if (!(entity instanceof ArmorStand anchor) || !anchor.isValid()) { - removeDecoy(state, owner); - it.remove(); - continue; - } - - PacketPlayerDecoy packetDecoy = state.packetDecoy(); - if (packetDecoy != null) { - packetDecoy.tick(); - packetDecoy.syncToAnchor(anchor.getLocation(), anchor.isOnGround()); - packetDecoy.lookAtViewers(anchor.getLocation().add(0, getConfig().decoyEyeHeight, 0)); - } - - applyOwnerInvisibility(owner); - syncOwnerEquipmentHidden(owner); - spawnOwnerTrail(owner); - redirectAggro(owner, anchor, state.level()); - } - } - - private void applyOwnerInvisibility(Player owner) { - int duration = Math.max(20, getConfig().ownerInvisibilityRefreshTicks); - PotionEffect current = owner.getPotionEffect(PotionEffectType.INVISIBILITY); - if (current != null && current.getDuration() > duration + 5) { - return; - } - - owner.addPotionEffect(new PotionEffect(PotionEffectType.INVISIBILITY, duration, getConfig().ownerInvisibilityAmplifier, false, false, false), true); - } - - private void spawnOwnerTrail(Player owner) { - owner.getWorld().spawnParticle( - Particle.SMOKE, - owner.getLocation().add(0, getConfig().ownerTrailYOffset, 0), - Math.max(1, getConfig().ownerTrailParticles), - Math.max(0, getConfig().ownerTrailHorizontalSpread), - Math.max(0, getConfig().ownerTrailVerticalSpread), - Math.max(0, getConfig().ownerTrailHorizontalSpread), - Math.max(0, getConfig().ownerTrailSpeed) - ); - } - - private void syncOwnerEquipmentHidden(Player owner) { - long now = System.currentTimeMillis(); - long nextAt = ownerEquipmentMaskSync.getOrDefault(owner.getUniqueId(), 0L); - if (now < nextAt) { - return; - } - - PACKET_DECOY.sendOwnerEquipment(owner, true); - ownerEquipmentMaskSync.put(owner.getUniqueId(), now + Math.max(100L, getConfig().ownerEquipmentHideResendMillis)); - } - - private void restoreOwnerEquipment(Player owner) { - if (owner == null || !owner.isOnline()) { - return; - } - - ownerEquipmentMaskSync.remove(owner.getUniqueId()); - PACKET_DECOY.sendOwnerEquipment(owner, false); - } - - private void removeDecoy(DecoyState state, Player owner) { - if (state.packetDecoy() != null) { - state.packetDecoy().destroy(); - } - - Entity entity = Bukkit.getEntity(state.anchorId()); - anchorOwners.remove(state.anchorId()); - if (entity instanceof ArmorStand stand && stand.isValid()) { - stand.remove(); - } - - restoreOwnerEquipment(owner); - } - - private long getCooldownMillis(int level) { - return Math.max(1000L, (long) Math.round(getConfig().cooldownMillisBase - (getLevelPercent(level) * getConfig().cooldownMillisFactor))); - } - - private int getDecoyTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().decoyTicksBase + (getLevelPercent(level) * getConfig().decoyTicksFactor))); - } - - private double getDecoyRadius(int level) { - return getConfig().decoyRadiusBase + (getLevelPercent(level) * getConfig().decoyRadiusFactor); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Stopping sneak spawns a short-lived shadow decoy that pulls your current aggro.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base cooldown after creating a decoy, in milliseconds.", impact = "Higher values mean longer time between activations.") - double cooldownMillisBase = 18000; - @com.volmit.adapt.util.config.ConfigDoc(value = "How much cooldown is reduced by leveling.", impact = "Higher values reduce cooldown more at higher levels.") - double cooldownMillisFactor = 12000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base active duration in ticks.", impact = "Higher values keep decoys active longer.") - double decoyTicksBase = 60; - @com.volmit.adapt.util.config.ConfigDoc(value = "Duration scaling from level, in ticks.", impact = "Higher values extend duration more per level.") - double decoyTicksFactor = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base aggro redirect radius.", impact = "Higher values pull aggro from farther away.") - double decoyRadiusBase = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Aggro radius scaling from level.", impact = "Higher values expand pull range more per level.") - double decoyRadiusFactor = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Visual eye height used for fake player facing.", impact = "Adjust if head rotation appears too high or too low.") - double decoyEyeHeight = 1.62; - @com.volmit.adapt.util.config.ConfigDoc(value = "Delay before removing the fake player from tab list, in ticks.", impact = "Small values hide tab entries faster; larger values help skins load.") - int tabListRemoveDelayTicks = -1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows armor stand visual fallback if packet NPC creation fails.", impact = "Turn off to disable fallback visuals on incompatible server builds.") - boolean legacyFallbackEnabled = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Refresh duration for owner invisibility while a decoy is active.", impact = "Higher values keep invisibility active longer between refreshes.") - int ownerInvisibilityRefreshTicks = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Amplifier for the temporary invisibility effect.", impact = "Most servers should leave this at 0.") - int ownerInvisibilityAmplifier = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Smoke particles emitted around the invisible owner each tick while decoy is active.", impact = "Higher values create a stronger visible trail.") - int ownerTrailParticles = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal spread for owner smoke trail.", impact = "Higher values make the trail wider.") - double ownerTrailHorizontalSpread = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Vertical spread for owner smoke trail.", impact = "Higher values make the trail taller.") - double ownerTrailVerticalSpread = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Vertical offset for smoke trail spawn location.", impact = "Adjust to move trail closer to feet or torso.") - double ownerTrailYOffset = 0.1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Particle speed for owner smoke trail.", impact = "Higher values make trail movement more turbulent.") - double ownerTrailSpeed = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "How often owner equipment-hide packets are resent while invisible, in milliseconds.", impact = "Lower values keep visuals tighter for joining viewers, higher values reduce packet traffic.") - long ownerEquipmentHideResendMillis = 250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal knockback applied when the decoy is hit.", impact = "Higher values make the decoy react more dramatically when struck.") - double decoyHitKnockback = 0.28; - @com.volmit.adapt.util.config.ConfigDoc(value = "Vertical lift applied when the decoy is hit.", impact = "Higher values make impacts pop the decoy upward more.") - double decoyHitLift = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Swing ray distance used to detect decoy hits.", impact = "Higher values make swings connect from farther away.") - double decoySwingDetectionReach = 4.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Bitmask for visible skin layers on the fake player decoy.", impact = "127 enables all standard skin layers (hat, jacket, sleeves, pants).") - int decoySkinLayerMask = 127; - @com.volmit.adapt.util.config.ConfigDoc(value = "Experience granted on each decoy spawn.", impact = "Higher values level the adaptation faster.") - double xpOnDecoy = 18; - } - - private record DecoyState(UUID anchorId, PacketPlayerDecoy packetDecoy, long expiresAt, int level) { - } - - private static final class PacketPlayerDecoy { - private final PacketDecoyBridge bridge; - private final World world; - private final UUID profileId; - private final int entityId; - private final Object nmsEntity; - private final long removeTabAt; - private final Set knownViewers; - private boolean removedFromTab; - private Object spawnPlayerInfoPacket; - private Object spawnAddEntityPacket; - private Object spawnMetadataPacket; - private Object spawnEquipmentPacket; - private long lastPositionSyncAt; - private double lastX; - private double lastY; - private double lastZ; - private float lastYaw; - private float lastPitch; - - private PacketPlayerDecoy(PacketDecoyBridge bridge, World world, UUID profileId, int entityId, Object nmsEntity, int tabListRemoveDelayTicks) { - this.bridge = bridge; - this.world = world; - this.profileId = profileId; - this.entityId = entityId; - this.nmsEntity = nmsEntity; - this.removeTabAt = System.currentTimeMillis() + Math.max(0, tabListRemoveDelayTicks) * 50L; - this.knownViewers = new HashSet<>(); - this.removedFromTab = false; - this.spawnPlayerInfoPacket = null; - this.spawnAddEntityPacket = null; - this.spawnMetadataPacket = null; - this.spawnEquipmentPacket = null; - this.lastPositionSyncAt = 0L; - this.lastX = Double.NaN; - this.lastY = Double.NaN; - this.lastZ = Double.NaN; - this.lastYaw = Float.NaN; - this.lastPitch = Float.NaN; - } - - public void spawn(Object playerInfoPacket, Object addEntityPacket, Object metadataPacket, Object equipmentPacket) { - this.spawnPlayerInfoPacket = playerInfoPacket; - this.spawnAddEntityPacket = addEntityPacket; - this.spawnMetadataPacket = metadataPacket; - this.spawnEquipmentPacket = equipmentPacket; - ensureViewerState(); - } - - public void tick() { - ensureViewerState(); - if (removeTabAt < 0 || removedFromTab || System.currentTimeMillis() < removeTabAt) { - return; - } - - Object removePacket = bridge.createPlayerInfoRemovePacket(profileId); - if (removePacket != null) { - for (Player viewer : spawnedViewerPlayers()) { - bridge.sendPacket(viewer, removePacket); - } - } - - removedFromTab = true; - } - - public void lookAtViewers(Location origin) { - ensureViewerState(); - for (Player viewer : spawnedViewerPlayers()) { - Location to = viewer.getEyeLocation(); - if (origin.getWorld() != to.getWorld()) { - continue; - } - - double dx = to.getX() - origin.getX(); - double dy = to.getY() - origin.getY(); - double dz = to.getZ() - origin.getZ(); - double horizontal = Math.sqrt(dx * dx + dz * dz); - float yaw = (float) Math.toDegrees(Math.atan2(-dx, dz)); - float pitch = (float) Math.toDegrees(-Math.atan2(dy, horizontal)); - bridge.applyLook(this, yaw, pitch, viewer); - } - } - - public void syncToAnchor(Location anchor, boolean onGround) { - ensureViewerState(); - long now = System.currentTimeMillis(); - double dx = Double.isFinite(lastX) ? anchor.getX() - lastX : 1; - double dy = Double.isFinite(lastY) ? anchor.getY() - lastY : 1; - double dz = Double.isFinite(lastZ) ? anchor.getZ() - lastZ : 1; - double distanceSq = (dx * dx) + (dy * dy) + (dz * dz); - float yawDiff = Float.isFinite(lastYaw) ? Math.abs(anchor.getYaw() - lastYaw) : 360f; - float pitchDiff = Float.isFinite(lastPitch) ? Math.abs(anchor.getPitch() - lastPitch) : 360f; - - if (distanceSq < 0.0004 && yawDiff < 0.8f && pitchDiff < 0.8f && now - lastPositionSyncAt < 45L) { - return; - } - - if (bridge.syncPosition(this, anchor, onGround, spawnedViewerPlayers())) { - lastPositionSyncAt = now; - lastX = anchor.getX(); - lastY = anchor.getY(); - lastZ = anchor.getZ(); - lastYaw = anchor.getYaw(); - lastPitch = anchor.getPitch(); - } - } - - public void hitFrom(Location source) { - ensureViewerState(); - if (!Double.isFinite(lastX) || !Double.isFinite(lastZ)) { - bridge.sendHurtAnimation(this, 0f, spawnedViewerPlayers()); - return; - } - - double dx = source.getX() - lastX; - double dz = source.getZ() - lastZ; - float yaw = (float) Math.toDegrees(Math.atan2(-dx, dz)); - bridge.sendHurtAnimation(this, yaw, spawnedViewerPlayers()); - } - - public void destroy() { - Object removeEntityPacket = bridge.createRemoveEntityPacket(entityId); - Object removePlayerInfoPacket = bridge.createPlayerInfoRemovePacket(profileId); - - for (Player viewer : spawnedViewerPlayers()) { - if (removeEntityPacket != null) { - bridge.sendPacket(viewer, removeEntityPacket); - } - if (removePlayerInfoPacket != null) { - bridge.sendPacket(viewer, removePlayerInfoPacket); - } - } - - knownViewers.clear(); - } - - private void ensureViewerState() { - Set online = new HashSet<>(); - for (Player viewer : world.getPlayers()) { - if (!viewer.isOnline()) { - continue; - } - - UUID id = viewer.getUniqueId(); - online.add(id); - if (!knownViewers.contains(id)) { - spawnFor(viewer); - knownViewers.add(id); - } - } - - knownViewers.retainAll(online); - } - - private void spawnFor(Player viewer) { - if (spawnPlayerInfoPacket != null) { - bridge.sendPacket(viewer, spawnPlayerInfoPacket); - } - - if (spawnAddEntityPacket != null) { - bridge.sendPacket(viewer, spawnAddEntityPacket); - } - - if (spawnMetadataPacket != null) { - bridge.sendPacket(viewer, spawnMetadataPacket); - } - - if (spawnEquipmentPacket != null) { - bridge.sendPacket(viewer, spawnEquipmentPacket); - } - - if (removedFromTab) { - Object removePacket = bridge.createPlayerInfoRemovePacket(profileId); - if (removePacket != null) { - bridge.sendPacket(viewer, removePacket); - } - } - } - - private List spawnedViewerPlayers() { - List viewers = new ArrayList<>(); - for (Player viewer : world.getPlayers()) { - if (viewer.isOnline() && knownViewers.contains(viewer.getUniqueId())) { - viewers.add(viewer); - } - } - - return viewers; - } - } - - private static final class PacketDecoyBridge { - private final boolean supported; - - private final Method craftServerGetServer; - private final Method craftWorldGetHandle; - private final Method craftPlayerGetHandle; - - private final Constructor serverPlayerConstructor; - private final Method clientInformationCreateDefault; - - private final Constructor gameProfileBasicConstructor; - private final Constructor gameProfileWithPropertiesConstructor; - private final Method gameProfilePropertiesAccessor; - private final Method playerGetGameProfile; - - private final Method entitySetPos; - private final Method entitySetRot; - private final Method entitySetOnGround; - private final Method livingSetYHeadRot; - private final Method livingSetYBodyRot; - private final Method entityGetId; - private final Method entityGetType; - private final Method entityGetEntityData; - private final Method synchedEntityDataGetNonDefaultValues; - private final Method synchedEntityDataPackAll; - private final Method synchedEntityDataSet; - - private final Method playerInfoCreateSingleInitializing; - private final Constructor playerInfoActionConstructor; - private final Constructor playerInfoFromEntriesConstructor; - private final Constructor playerInfoEntryExplicitConstructor; - private final Class playerInfoActionClass; - private final Object defaultGameType; - - private final Constructor addEntityConstructor; - private final Constructor addEntityExplicitConstructor; - private final Field blockPosZero; - private final Field vec3Zero; - private final Constructor setEntityDataConstructor; - private final Constructor moveEntityRotConstructor; - private final Constructor rotateHeadConstructor; - private final Constructor removeEntitiesConstructor; - private final Constructor playerInfoRemoveConstructor; - private final Method entityPositionSyncOf; - private final Constructor hurtAnimationConstructor; - private final Constructor setEquipmentConstructor; - private final Method pairOfMethod; - private final Method craftItemStackAsNmsCopy; - private final Class equipmentSlotClass; - private final Field avatarModelCustomizationAccessor; - - private final Field serverPlayerConnectionField; - private final Method connectionSendPacket; - - private PacketDecoyBridge() throws ReflectiveOperationException { - String craftPackage = Bukkit.getServer().getClass().getPackage().getName(); - if (!craftPackage.startsWith("org.bukkit.craftbukkit")) { - throw new ClassNotFoundException("CraftBukkit package not detected: " + craftPackage); - } - - Class craftServerClass = Class.forName(craftPackage + ".CraftServer"); - Class craftWorldClass = Class.forName(craftPackage + ".CraftWorld"); - Class craftPlayerClass = Class.forName(craftPackage + ".entity.CraftPlayer"); - - Class minecraftServerClass = Class.forName("net.minecraft.server.MinecraftServer"); - Class serverLevelClass = Class.forName("net.minecraft.server.level.ServerLevel"); - Class serverPlayerClass = Class.forName("net.minecraft.server.level.ServerPlayer"); - Class clientInformationClass = Class.forName("net.minecraft.server.level.ClientInformation"); - Class nmsPlayerClass = Class.forName("net.minecraft.world.entity.player.Player"); - Class livingEntityClass = Class.forName("net.minecraft.world.entity.LivingEntity"); - Class entityClass = Class.forName("net.minecraft.world.entity.Entity"); - Class entityTypeClass = Class.forName("net.minecraft.world.entity.EntityType"); - Class avatarClass = Class.forName("net.minecraft.world.entity.Avatar"); - Class equipmentSlotClass = Class.forName("net.minecraft.world.entity.EquipmentSlot"); - Class entityDataAccessorClass = Class.forName("net.minecraft.network.syncher.EntityDataAccessor"); - Class synchedEntityDataClass = Class.forName("net.minecraft.network.syncher.SynchedEntityData"); - Class gameProfileClass = Class.forName("com.mojang.authlib.GameProfile"); - Class vec3Class = Class.forName("net.minecraft.world.phys.Vec3"); - Class pairClass = Class.forName("com.mojang.datafixers.util.Pair"); - Class packetClass = Class.forName("net.minecraft.network.protocol.Packet"); - Class connectionClass = Class.forName("net.minecraft.server.network.ServerCommonPacketListenerImpl"); - Class playerInfoPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket"); - Class playerInfoEntryClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry"); - Class playerInfoActionEnumClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action"); - Class gameTypeClass = Class.forName("net.minecraft.world.level.GameType"); - Class componentClass = Class.forName("net.minecraft.network.chat.Component"); - Class remoteChatSessionDataClass = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data"); - Class addEntityPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket"); - Class entityPositionSyncPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket"); - Class hurtAnimationPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket"); - Class blockPosClass = Class.forName("net.minecraft.core.BlockPos"); - Class setEntityDataPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket"); - Class moveEntityRotClass = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot"); - Class rotateHeadPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundRotateHeadPacket"); - Class removeEntitiesPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket"); - Class playerInfoRemovePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket"); - Class setEquipmentPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket"); - Class craftItemStackClass = Class.forName(craftPackage + ".inventory.CraftItemStack"); - - this.craftServerGetServer = craftServerClass.getMethod("getServer"); - this.craftWorldGetHandle = craftWorldClass.getMethod("getHandle"); - this.craftPlayerGetHandle = craftPlayerClass.getMethod("getHandle"); - - this.serverPlayerConstructor = serverPlayerClass.getConstructor(minecraftServerClass, serverLevelClass, gameProfileClass, clientInformationClass); - this.clientInformationCreateDefault = clientInformationClass.getMethod("createDefault"); - - this.gameProfileBasicConstructor = findConstructor(gameProfileClass, UUID.class, String.class); - this.gameProfileWithPropertiesConstructor = findOptionalConstructor(gameProfileClass, UUID.class, String.class, findPropertyMapClass(gameProfileClass)); - this.gameProfilePropertiesAccessor = findOptionalMethod(gameProfileClass, "properties", "getProperties"); - this.playerGetGameProfile = nmsPlayerClass.getMethod("getGameProfile"); - - this.entitySetPos = entityClass.getMethod("setPos", double.class, double.class, double.class); - this.entitySetRot = entityClass.getMethod("setRot", float.class, float.class); - this.entitySetOnGround = entityClass.getMethod("setOnGround", boolean.class); - this.livingSetYHeadRot = livingEntityClass.getMethod("setYHeadRot", float.class); - this.livingSetYBodyRot = livingEntityClass.getMethod("setYBodyRot", float.class); - this.entityGetId = entityClass.getMethod("getId"); - this.entityGetType = entityClass.getMethod("getType"); - this.entityGetEntityData = entityClass.getMethod("getEntityData"); - this.synchedEntityDataGetNonDefaultValues = synchedEntityDataClass.getMethod("getNonDefaultValues"); - this.synchedEntityDataPackAll = findOptionalMethod(synchedEntityDataClass, "packAll", new Class[0]); - this.synchedEntityDataSet = synchedEntityDataClass.getMethod("set", entityDataAccessorClass, Object.class); - - this.playerInfoCreateSingleInitializing = findOptionalMethod(playerInfoPacketClass, "createSinglePlayerInitializing", serverPlayerClass, boolean.class); - this.playerInfoActionConstructor = findOptionalConstructor(playerInfoPacketClass, playerInfoActionEnumClass, serverPlayerClass); - this.playerInfoFromEntriesConstructor = findOptionalConstructor(playerInfoPacketClass, EnumSet.class, List.class); - this.playerInfoEntryExplicitConstructor = findOptionalConstructor(playerInfoEntryClass, UUID.class, gameProfileClass, boolean.class, int.class, gameTypeClass, componentClass, boolean.class, int.class, remoteChatSessionDataClass); - this.playerInfoActionClass = playerInfoActionEnumClass; - this.defaultGameType = resolveDefaultGameType(gameTypeClass); - - this.addEntityConstructor = addEntityPacketClass.getConstructor(entityClass, int.class, blockPosClass); - this.addEntityExplicitConstructor = findOptionalConstructor(addEntityPacketClass, int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, entityTypeClass, int.class, vec3Class, double.class); - this.blockPosZero = blockPosClass.getField("ZERO"); - this.vec3Zero = vec3Class.getField("ZERO"); - this.setEntityDataConstructor = setEntityDataPacketClass.getConstructor(int.class, List.class); - this.moveEntityRotConstructor = moveEntityRotClass.getConstructor(int.class, byte.class, byte.class, boolean.class); - this.rotateHeadConstructor = rotateHeadPacketClass.getConstructor(entityClass, byte.class); - this.removeEntitiesConstructor = removeEntitiesPacketClass.getConstructor(int[].class); - this.playerInfoRemoveConstructor = playerInfoRemovePacketClass.getConstructor(List.class); - this.entityPositionSyncOf = entityPositionSyncPacketClass.getMethod("of", entityClass); - this.hurtAnimationConstructor = findOptionalConstructor(hurtAnimationPacketClass, int.class, float.class); - this.setEquipmentConstructor = findOptionalConstructor(setEquipmentPacketClass, int.class, List.class); - this.pairOfMethod = pairClass.getMethod("of", Object.class, Object.class); - this.craftItemStackAsNmsCopy = craftItemStackClass.getMethod("asNMSCopy", ItemStack.class); - this.equipmentSlotClass = equipmentSlotClass; - this.avatarModelCustomizationAccessor = avatarClass.getField("DATA_PLAYER_MODE_CUSTOMISATION"); - - this.serverPlayerConnectionField = serverPlayerClass.getField("connection"); - this.connectionSendPacket = connectionClass.getMethod("send", packetClass); - this.supported = true; - } - - private PacketDecoyBridge(boolean supported) { - this.supported = supported; - this.craftServerGetServer = null; - this.craftWorldGetHandle = null; - this.craftPlayerGetHandle = null; - this.serverPlayerConstructor = null; - this.clientInformationCreateDefault = null; - this.gameProfileBasicConstructor = null; - this.gameProfileWithPropertiesConstructor = null; - this.gameProfilePropertiesAccessor = null; - this.playerGetGameProfile = null; - this.entitySetPos = null; - this.entitySetRot = null; - this.entitySetOnGround = null; - this.livingSetYHeadRot = null; - this.livingSetYBodyRot = null; - this.entityGetId = null; - this.entityGetType = null; - this.entityGetEntityData = null; - this.synchedEntityDataGetNonDefaultValues = null; - this.synchedEntityDataPackAll = null; - this.synchedEntityDataSet = null; - this.playerInfoCreateSingleInitializing = null; - this.playerInfoActionConstructor = null; - this.playerInfoFromEntriesConstructor = null; - this.playerInfoEntryExplicitConstructor = null; - this.playerInfoActionClass = null; - this.defaultGameType = null; - this.addEntityConstructor = null; - this.addEntityExplicitConstructor = null; - this.blockPosZero = null; - this.vec3Zero = null; - this.setEntityDataConstructor = null; - this.moveEntityRotConstructor = null; - this.rotateHeadConstructor = null; - this.removeEntitiesConstructor = null; - this.playerInfoRemoveConstructor = null; - this.entityPositionSyncOf = null; - this.hurtAnimationConstructor = null; - this.setEquipmentConstructor = null; - this.pairOfMethod = null; - this.craftItemStackAsNmsCopy = null; - this.equipmentSlotClass = null; - this.avatarModelCustomizationAccessor = null; - this.serverPlayerConnectionField = null; - this.connectionSendPacket = null; - } - - public static PacketDecoyBridge create() { - try { - return new PacketDecoyBridge(); - } catch (Throwable e) { - Adapt.warn("Shadow decoy fake-player bridge unavailable: " + e.getClass().getSimpleName() + " " + e.getMessage()); - return new PacketDecoyBridge(false); - } - } - - public PacketPlayerDecoy spawnDecoy(Player owner, ArmorStand anchor, int tabListRemoveDelayTicks, int skinLayerMask) { - Location location = anchor.getLocation(); - if (!supported || location.getWorld() == null) { - return null; - } - - try { - Object ownerHandle = craftPlayerGetHandle.invoke(owner); - Object ownerProfile = playerGetGameProfile.invoke(ownerHandle); - UUID profileId = UUID.randomUUID(); - Object profile = createProfile(profileId, owner.getName(), ownerProfile); - - Object minecraftServer = craftServerGetServer.invoke(Bukkit.getServer()); - Object worldHandle = craftWorldGetHandle.invoke(location.getWorld()); - Object clientInfo = clientInformationCreateDefault.invoke(null); - Object nmsDecoy = serverPlayerConstructor.newInstance(minecraftServer, worldHandle, profile, clientInfo); - - entitySetPos.invoke(nmsDecoy, location.getX(), location.getY(), location.getZ()); - entitySetRot.invoke(nmsDecoy, location.getYaw(), location.getPitch()); - livingSetYHeadRot.invoke(nmsDecoy, location.getYaw()); - livingSetYBodyRot.invoke(nmsDecoy, location.getYaw()); - applySkinLayers(nmsDecoy, skinLayerMask); - - int entityId = (int) entityGetId.invoke(nmsDecoy); - Object playerInfoPacket = createPlayerInfoAddPacket(nmsDecoy, profileId, profile); - Object addEntityPacket = createAddEntityPacket(nmsDecoy, entityId, profileId, location); - Object metadataPacket = createMetadataPacket(nmsDecoy, entityId); - Object equipmentPacket = createEquipmentPacket(entityId, owner, false, true); - - PacketPlayerDecoy decoy = new PacketPlayerDecoy(this, location.getWorld(), profileId, entityId, nmsDecoy, tabListRemoveDelayTicks); - decoy.spawn(playerInfoPacket, addEntityPacket, metadataPacket, equipmentPacket); - return decoy; - } catch (Throwable e) { - Throwable root = unwrapRootCause(e); - Adapt.warn("Failed to spawn fake-player shadow decoy: " + root.getClass().getSimpleName() + " " + String.valueOf(root.getMessage())); - return null; - } - } - - private Object createProfile(UUID id, String ownerName, Object ownerProfile) throws ReflectiveOperationException { - String profileName = ownerName; - if (profileName.length() > 16) { - profileName = profileName.substring(0, 16); - } - - if (gameProfileWithPropertiesConstructor != null && gameProfilePropertiesAccessor != null && ownerProfile != null) { - Object properties = gameProfilePropertiesAccessor.invoke(ownerProfile); - if (properties != null) { - return gameProfileWithPropertiesConstructor.newInstance(id, profileName, properties); - } - } - - return gameProfileBasicConstructor.newInstance(id, profileName); - } - - private Object createPlayerInfoAddPacket(Object nmsDecoy, UUID profileId, Object profile) throws ReflectiveOperationException { - ReflectiveOperationException last = null; - - if (playerInfoFromEntriesConstructor != null && playerInfoEntryExplicitConstructor != null && playerInfoActionClass != null && defaultGameType != null) { - try { - Enum addAction = Enum.valueOf((Class) playerInfoActionClass, "ADD_PLAYER"); - EnumSet actions = buildInitializationActions(addAction); - Object entry = playerInfoEntryExplicitConstructor.newInstance(profileId, profile, true, 0, defaultGameType, null, true, 0, null); - return playerInfoFromEntriesConstructor.newInstance(actions, List.of(entry)); - } catch (ReflectiveOperationException e) { - last = e; - } - } - - if (playerInfoCreateSingleInitializing != null) { - try { - return playerInfoCreateSingleInitializing.invoke(null, nmsDecoy, true); - } catch (ReflectiveOperationException e) { - last = e; - } - } - - if (playerInfoActionConstructor != null && playerInfoActionClass != null) { - try { - Object addAction = Enum.valueOf((Class) playerInfoActionClass, "ADD_PLAYER"); - return playerInfoActionConstructor.newInstance(addAction, nmsDecoy); - } catch (ReflectiveOperationException e) { - last = e; - } - } - - if (last != null) { - throw last; - } - - throw new ReflectiveOperationException("No supported player-info add packet constructor found."); - } - - private Object createAddEntityPacket(Object nmsDecoy, int entityId, UUID profileId, Location location) throws ReflectiveOperationException { - if (addEntityExplicitConstructor != null && entityGetType != null && vec3Zero != null) { - Object entityType = entityGetType.invoke(nmsDecoy); - Object velocity = vec3Zero.get(null); - return addEntityExplicitConstructor.newInstance( - entityId, - profileId, - location.getX(), - location.getY(), - location.getZ(), - location.getPitch(), - location.getYaw(), - entityType, - 0, - velocity, - (double) location.getYaw() - ); - } - - return addEntityConstructor.newInstance(nmsDecoy, 0, blockPosZero.get(null)); - } - - private void applySkinLayers(Object nmsDecoy, int mask) throws ReflectiveOperationException { - if (avatarModelCustomizationAccessor == null || synchedEntityDataSet == null) { - return; - } - - Object accessor = avatarModelCustomizationAccessor.get(null); - Object synchedData = entityGetEntityData.invoke(nmsDecoy); - synchedEntityDataSet.invoke(synchedData, accessor, (byte) (mask & 0xFF)); - } - - private Object createEquipmentPacket(int entityId, Player owner, boolean hide, boolean includeEmptySlots) throws ReflectiveOperationException { - if (setEquipmentConstructor == null || pairOfMethod == null || craftItemStackAsNmsCopy == null || equipmentSlotClass == null) { - return null; - } - - List slots = new ArrayList<>(); - ItemStack air = new ItemStack(Material.AIR); - appendEquipment(slots, "HEAD", hide ? air : owner.getInventory().getHelmet(), includeEmptySlots); - appendEquipment(slots, "CHEST", hide ? air : owner.getInventory().getChestplate(), includeEmptySlots); - appendEquipment(slots, "LEGS", hide ? air : owner.getInventory().getLeggings(), includeEmptySlots); - appendEquipment(slots, "FEET", hide ? air : owner.getInventory().getBoots(), includeEmptySlots); - appendEquipment(slots, "MAINHAND", hide ? air : owner.getInventory().getItemInMainHand(), includeEmptySlots); - appendEquipment(slots, "OFFHAND", hide ? air : owner.getInventory().getItemInOffHand(), includeEmptySlots); - - if (slots.isEmpty()) { - return null; - } - - return setEquipmentConstructor.newInstance(entityId, slots); - } - - private void appendEquipment(List slots, String slotName, ItemStack stack, boolean includeEmptySlots) throws ReflectiveOperationException { - ItemStack resolved = stack == null ? new ItemStack(Material.AIR) : stack; - if (!includeEmptySlots && resolved.getType().isAir()) { - return; - } - - Object slot = Enum.valueOf((Class) equipmentSlotClass, slotName); - Object nmsStack = craftItemStackAsNmsCopy.invoke(null, resolved.clone()); - Object pair = pairOfMethod.invoke(null, slot, nmsStack); - slots.add(pair); - } - - private Object createMetadataPacket(Object nmsDecoy, int entityId) throws ReflectiveOperationException { - Object synchedData = entityGetEntityData.invoke(nmsDecoy); - Object packed = synchedEntityDataPackAll != null - ? synchedEntityDataPackAll.invoke(synchedData) - : synchedEntityDataGetNonDefaultValues.invoke(synchedData); - if (!(packed instanceof List values) || values.isEmpty()) { - return null; - } - - return setEntityDataConstructor.newInstance(entityId, values); - } - - public Object createPlayerInfoRemovePacket(UUID profileId) { - if (!supported) { - return null; - } - - try { - return playerInfoRemoveConstructor.newInstance(List.of(profileId)); - } catch (Throwable e) { - return null; - } - } - - public Object createRemoveEntityPacket(int entityId) { - if (!supported) { - return null; - } - - try { - return removeEntitiesConstructor.newInstance((Object) new int[]{entityId}); - } catch (Throwable e) { - return null; - } - } - - public boolean applyLook(PacketPlayerDecoy decoy, float yaw, float pitch, Player viewer) { - if (!supported) { - return false; - } - - try { - entitySetRot.invoke(decoy.nmsEntity, yaw, pitch); - livingSetYHeadRot.invoke(decoy.nmsEntity, yaw); - livingSetYBodyRot.invoke(decoy.nmsEntity, yaw); - - byte yawByte = toAngle(yaw); - byte pitchByte = toAngle(pitch); - Object rotatePacket = moveEntityRotConstructor.newInstance(decoy.entityId, yawByte, pitchByte, true); - Object headPacket = rotateHeadConstructor.newInstance(decoy.nmsEntity, yawByte); - - sendPacket(viewer, rotatePacket); - sendPacket(viewer, headPacket); - - return true; - } catch (Throwable e) { - return false; - } - } - - public boolean syncPosition(PacketPlayerDecoy decoy, Location location, boolean onGround, List viewers) { - if (!supported || entityPositionSyncOf == null) { - return false; - } - - try { - entitySetPos.invoke(decoy.nmsEntity, location.getX(), location.getY(), location.getZ()); - entitySetRot.invoke(decoy.nmsEntity, location.getYaw(), location.getPitch()); - if (entitySetOnGround != null) { - entitySetOnGround.invoke(decoy.nmsEntity, onGround); - } - - Object packet = entityPositionSyncOf.invoke(null, decoy.nmsEntity); - if (packet == null) { - return false; - } - - for (Player viewer : viewers) { - sendPacket(viewer, packet); - } - - return true; - } catch (Throwable e) { - return false; - } - } - - public void sendHurtAnimation(PacketPlayerDecoy decoy, float yaw, List viewers) { - if (!supported || hurtAnimationConstructor == null) { - return; - } - - try { - Object packet = hurtAnimationConstructor.newInstance(decoy.entityId, yaw); - for (Player viewer : viewers) { - sendPacket(viewer, packet); - } - } catch (Throwable ignored) { - } - } - - public void sendOwnerEquipment(Player owner, boolean hide) { - if (!supported || owner == null || !owner.isOnline()) { - return; - } - - try { - Object packet = createEquipmentPacket(owner.getEntityId(), owner, hide, true); - if (packet == null) { - return; - } - - for (Player viewer : new ArrayList<>(owner.getWorld().getPlayers())) { - sendPacket(viewer, packet); - } - } catch (Throwable ignored) { - } - } - - public void sendPacket(Player viewer, Object packet) { - if (!supported || packet == null || viewer == null || !viewer.isOnline()) { - return; - } - - try { - Object handle = craftPlayerGetHandle.invoke(viewer); - Object connection = serverPlayerConnectionField.get(handle); - if (connection == null) { - return; - } - - connectionSendPacket.invoke(connection, packet); - } catch (Throwable ignored) { - } - } - - private static byte toAngle(float degrees) { - return (byte) (degrees * 256.0F / 360.0F); - } - - private static Constructor findConstructor(Class type, Class... parameterTypes) throws NoSuchMethodException { - Constructor constructor = type.getConstructor(parameterTypes); - constructor.setAccessible(true); - return constructor; - } - - private static Constructor findOptionalConstructor(Class type, Class... parameterTypes) { - if (Arrays.stream(parameterTypes).anyMatch(c -> c == null)) { - return null; - } - - try { - Constructor constructor = type.getConstructor(parameterTypes); - constructor.setAccessible(true); - return constructor; - } catch (NoSuchMethodException e) { - return null; - } - } - - private static Method findOptionalMethod(Class type, String name, Class... parameterTypes) { - try { - Method method = type.getMethod(name, parameterTypes); - method.setAccessible(true); - return method; - } catch (NoSuchMethodException e) { - return null; - } - } - - private static Method findOptionalMethod(Class type, String... methodNames) { - for (String methodName : methodNames) { - try { - Method method = type.getMethod(methodName); - method.setAccessible(true); - return method; - } catch (NoSuchMethodException ignored) { - } - } - - return null; - } - - private static Class findPropertyMapClass(Class gameProfileClass) { - for (Constructor constructor : gameProfileClass.getConstructors()) { - Class[] params = constructor.getParameterTypes(); - if (params.length == 3 && params[0] == UUID.class && params[1] == String.class) { - return params[2]; - } - } - - return null; - } - - private static Object resolveDefaultGameType(Class gameTypeClass) throws ReflectiveOperationException { - try { - Field defaultMode = gameTypeClass.getField("DEFAULT_MODE"); - Object value = defaultMode.get(null); - if (value != null) { - return value; - } - } catch (NoSuchFieldException ignored) { - } - - if (gameTypeClass.isEnum()) { - try { - return Enum.valueOf((Class) gameTypeClass, "SURVIVAL"); - } catch (IllegalArgumentException ignored) { - } - - Object[] values = gameTypeClass.getEnumConstants(); - if (values != null && values.length > 0) { - return values[0]; - } - } - - throw new ReflectiveOperationException("No default game mode could be resolved."); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - private EnumSet buildInitializationActions(Enum addAction) { - EnumSet actions = EnumSet.noneOf((Class) playerInfoActionClass); - actions.add(addAction); - - String[] optionalActions = new String[]{ - "INITIALIZE_CHAT", - "UPDATE_GAME_MODE", - "UPDATE_LISTED", - "UPDATE_LATENCY", - "UPDATE_DISPLAY_NAME", - "UPDATE_HAT", - "UPDATE_LIST_ORDER" - }; - - for (String name : optionalActions) { - try { - actions.add(Enum.valueOf((Class) playerInfoActionClass, name)); - } catch (IllegalArgumentException ignored) { - } - } - - return actions; - } - - private static Throwable unwrapRootCause(Throwable throwable) { - Throwable cursor = throwable; - while (true) { - if (cursor instanceof InvocationTargetException invocation && invocation.getCause() != null) { - cursor = invocation.getCause(); - continue; - } - - Throwable cause = cursor.getCause(); - if (cause == null || cause == cursor) { - return cursor; - } - - cursor = cause; - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSight.java b/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSight.java deleted file mode 100644 index e2be2c775..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSight.java +++ /dev/null @@ -1,133 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.stealth; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.ArrayList; -import java.util.List; - -public class StealthSight extends SimpleAdaptation { - private final List sneaking; - - - public StealthSight() { - super("stealth-vision"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("stealth.night_vision.description")); - setDisplayName(Localizer.dLocalize("stealth.night_vision.name")); - setIcon(Material.POTION); - setBaseCost(getConfig().baseCost); - setInterval(1500); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setMaxLevel(getConfig().maxLevel); - sneaking = new ArrayList<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_stealth_sight_72k") - .title(Localizer.dLocalize("advancement.challenge_stealth_sight_72k.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_sight_72k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_stealth_sight_72k", "stealth.sight.time-in-darkness", 72000, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GRAY + Localizer.dLocalize("stealth.night_vision.lore1") + C.GREEN + Localizer.dLocalize("stealth.night_vision.lore2") + C.GRAY + Localizer.dLocalize("stealth.night_vision.lore3")); - } - - @EventHandler - public void on(PlayerToggleSneakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - SoundPlayer sp = SoundPlayer.of(p); - if (!hasAdaptation(p)) { - return; - } - sneaking.add(p); - if (!p.isSneaking()) { - sp.play(p.getLocation(), Sound.BLOCK_FUNGUS_BREAK, 1, 0.99f); - p.addPotionEffect(new PotionEffect(PotionEffectType.NIGHT_VISION, 1000, 0, false, false)); - getPlayer(p).getData().addStat("stealth.sight.time-in-darkness", 1); - } else { - p.removePotionEffect(PotionEffectType.NIGHT_VISION); - } - } - - - @Override - public void onTick() { - List toRemove = new ArrayList<>(); - for (Player p : sneaking) { - if (hasAdaptation(p) && !p.isSneaking()) { - toRemove.add(p); - J.s(() -> p.removePotionEffect(PotionEffectType.NIGHT_VISION)); - } - } - sneaking.removeAll(toRemove); - } - - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain night vision while sneaking.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSilentStep.java b/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSilentStep.java deleted file mode 100644 index ff5f47822..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSilentStep.java +++ /dev/null @@ -1,532 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.stealth; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import fr.skytasul.glowingentities.GlowingEntities; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Mob; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityTargetLivingEntityEvent; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; - -public class StealthSilentStep extends SimpleAdaptation { - private final Map dimmed = new HashMap<>(); - private final Map> recentBackstabs = new HashMap<>(); - private final Map> threatGlows = new HashMap<>(); - - public StealthSilentStep() { - super("stealth-silent-step"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("stealth.silent_step.description")); - setDisplayName(Localizer.dLocalize("stealth.silent_step.name")); - setIcon(Material.WHITE_WOOL); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(10); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_stealth_silent_200") - .title(Localizer.dLocalize("advancement.challenge_stealth_silent_200.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_silent_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_stealth_silent_5in10") - .title(Localizer.dLocalize("advancement.challenge_stealth_silent_5in10.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_silent_5in10.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_stealth_silent_200", "stealth.silent-step.backstabs", 200, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getStealthRadius(level)) + C.GRAY + " " + Localizer.dLocalize("stealth.silent_step.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getMobBackstabMultiplier(level) - 1D, 0) + C.GRAY + " " + Localizer.dLocalize("stealth.silent_step.lore2")); - v.addLore(C.GREEN + "+ " + Form.pc(getPlayerBackstabMultiplier(level) - 1D, 0) + C.GRAY + " " + Localizer.dLocalize("stealth.silent_step.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - clearDimming(e.getPlayer()); - clearThreatGlows(e.getPlayer()); - recentBackstabs.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityTargetLivingEntityEvent e) { - if (!(e.getTarget() instanceof Player p)) { - return; - } - - if (!hasAdaptation(p) || !p.isSneaking()) { - return; - } - - if (isTargetBlacklistType(e.getEntity().getType())) { - return; - } - - e.setCancelled(true); - if (e.getEntity() instanceof Mob mob && mob.getTarget() == p) { - mob.setTarget(null); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerMoveEvent e) { - if (e.isCancelled()) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking()) { - return; - } - - double radius = getStealthRadius(getLevel(p)); - for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), radius, radius, radius)) { - if (!(entity instanceof Mob mob)) { - continue; - } - - if (isTargetBlacklistType(mob.getType())) { - continue; - } - - if (mob.getTarget() == p) { - mob.setTarget(null); - xp(p, getConfig().xpPerTargetDrop); - } - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Player attacker) || !hasAdaptation(attacker)) { - return; - } - - if (!(e.getEntity() instanceof LivingEntity target)) { - return; - } - - if (target instanceof Player victim) { - if (!canPVP(attacker, victim.getLocation())) { - return; - } - } else if (!canPVE(attacker, target.getLocation())) { - return; - } - - boolean unseen = attacker.hasPotionEffect(PotionEffectType.INVISIBILITY) || !isLookingAt(target, attacker); - if (target == attacker || !unseen) { - return; - } - - int level = getLevel(attacker); - double multiplier = (target instanceof Player) ? getPlayerBackstabMultiplier(level) : getMobBackstabMultiplier(level); - e.setDamage(e.getDamage() * multiplier); - xp(attacker, e.getDamage() * getConfig().xpPerBonusDamage); - getPlayer(attacker).getData().addStat("stealth.silent-step.backstabs", 1); - - long now = System.currentTimeMillis(); - UUID uid = attacker.getUniqueId(); - recentBackstabs.computeIfAbsent(uid, k -> new ArrayList<>()).add(now); - recentBackstabs.get(uid).removeIf(t -> now - t > 10000); - if (recentBackstabs.get(uid).size() >= 5 - && AdaptConfig.get().isAdvancements() - && !getPlayer(attacker).getData().isGranted("challenge_stealth_silent_5in10")) { - getPlayer(attacker).getAdvancementHandler().grant("challenge_stealth_silent_5in10"); - } - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking()) { - clearDimming(p); - clearThreatGlows(p); - continue; - } - - int level = getLevel(p); - p.setFallDistance(Math.min(p.getFallDistance(), getConfig().maxSilentFallDistance)); - ThreatSnapshot threatSnapshot = collectThreatSnapshot(p, level); - if (threatSnapshot.canDetect.isEmpty()) { - applyDimming(p, level); - } else { - clearDimming(p); - } - updateThreatGlows(p, threatSnapshot); - } - } - - private ThreatSnapshot collectThreatSnapshot(Player p, int level) { - ThreatSnapshot snapshot = new ThreatSnapshot(); - double detectionLookDotThreshold = getDetectionLookDotThreshold(); - double mobRadius = getStealthRadius(level); - for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), mobRadius, mobRadius, mobRadius)) { - if (!(entity instanceof Mob mob)) { - continue; - } - - if (!getConfig().allMobsAffectStealthVisibility && !isTargetBlacklistType(mob.getType())) { - continue; - } - - snapshot.add(mob, getThreatLevel(mob, p, detectionLookDotThreshold)); - } - - double playerRadius = getPlayerDetectionRadius(level); - for (Entity nearby : p.getWorld().getNearbyEntities(p.getLocation(), playerRadius, playerRadius, playerRadius)) { - if (!(nearby instanceof Player other)) { - continue; - } - if (other == p || other.isDead()) { - continue; - } - - snapshot.add(other, getThreatLevel(other, p, detectionLookDotThreshold)); - } - - return snapshot; - } - - private void applyDimming(Player p, int level) { - p.addPotionEffect(new PotionEffect(PotionEffectType.DARKNESS, getDimDurationTicks(level), getConfig().dimAmplifier, false, false, false), true); - dimmed.put(p.getUniqueId(), true); - } - - private void clearDimming(Player p) { - if (dimmed.remove(p.getUniqueId()) != null) { - p.removePotionEffect(PotionEffectType.DARKNESS); - } - } - - private void updateThreatGlows(Player p, ThreatSnapshot snapshot) { - if (!getConfig().showThreatGlows) { - clearThreatGlows(p); - return; - } - - GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); - if (glowingEntities == null) { - clearThreatGlows(p); - return; - } - - UUID viewerId = p.getUniqueId(); - Map active = threatGlows.computeIfAbsent(viewerId, k -> new HashMap<>()); - - List stale = new ArrayList<>(); - for (UUID entityId : active.keySet()) { - if (!snapshot.threats.containsKey(entityId)) { - stale.add(entityId); - } - } - - for (UUID entityId : stale) { - Entity entity = Bukkit.getEntity(entityId); - if (entity != null) { - try { - glowingEntities.unsetGlowing(entity, p); - } catch (ReflectiveOperationException ignored) { - // Ignore reflective failures and continue clearing other entities. - } - } - active.remove(entityId); - } - - for (Map.Entry entry : snapshot.threats.entrySet()) { - UUID entityId = entry.getKey(); - ThreatLevel desired = entry.getValue(); - ThreatLevel current = active.get(entityId); - if (desired == current) { - continue; - } - - Entity entity = snapshot.entities.get(entityId); - if (entity == null) { - entity = Bukkit.getEntity(entityId); - } - if (entity == null || !entity.isValid()) { - continue; - } - - try { - glowingEntities.setGlowing(entity, p, getThreatColor(desired)); - active.put(entityId, desired); - } catch (ReflectiveOperationException ignored) { - // Ignore reflective failures and keep runtime behavior intact. - } - } - - if (active.isEmpty()) { - threatGlows.remove(viewerId); - } - } - - private void clearThreatGlows(Player p) { - Map active = threatGlows.remove(p.getUniqueId()); - if (active == null || active.isEmpty()) { - return; - } - - GlowingEntities glowingEntities = Adapt.instance.getGlowingEntities(); - if (glowingEntities == null) { - return; - } - - for (UUID entityId : active.keySet()) { - Entity entity = Bukkit.getEntity(entityId); - if (entity == null) { - continue; - } - - try { - glowingEntities.unsetGlowing(entity, p); - } catch (ReflectiveOperationException ignored) { - // Ignore reflective failures and continue clearing other entities. - } - } - } - - private ThreatLevel getThreatLevel(LivingEntity observer, LivingEntity target, double detectThreshold) { - if (!observer.hasLineOfSight(target)) { - return ThreatLevel.NONE; - } - - double lookDot = getLookDot(observer, target); - if (lookDot >= detectThreshold) { - return ThreatLevel.CAN_DETECT; - } - - double almostThreshold = Math.max(-1, detectThreshold - Math.max(0, getConfig().almostLookDotMargin)); - if (lookDot >= almostThreshold) { - return ThreatLevel.ALMOST_DETECT; - } - - return ThreatLevel.NONE; - } - - private double getDetectionLookDotThreshold() { - return Math.max(-1, Math.min(1, getConfig().detectionLookDotThreshold)); - } - - private ChatColor getThreatColor(ThreatLevel level) { - return switch (level) { - case CAN_DETECT -> ChatColor.RED; - case ALMOST_DETECT -> ChatColor.GRAY; - default -> ChatColor.WHITE; - }; - } - - private boolean isLookingAt(LivingEntity observer, LivingEntity target) { - return getLookDot(observer, target) >= getConfig().lookDotThreshold; - } - - private double getLookDot(LivingEntity observer, LivingEntity target) { - Vector look = observer.getEyeLocation().getDirection().normalize(); - Vector toTarget = target.getEyeLocation().toVector().subtract(observer.getEyeLocation().toVector()); - if (toTarget.lengthSquared() <= 0.0001) { - return 1; - } - - toTarget.normalize(); - return look.dot(toTarget); - } - - private boolean isTargetBlacklistType(EntityType type) { - if (type == null) { - return false; - } - - for (String raw : getConfig().targetingBlacklistTypes) { - if (raw == null || raw.isBlank()) { - continue; - } - - try { - EntityType configured = EntityType.valueOf(raw.trim().toUpperCase(Locale.ROOT)); - if (configured == type) { - return true; - } - } catch (IllegalArgumentException ignored) { - // Ignore invalid enum names in config. - } - } - - return false; - } - - private double getStealthRadius(int level) { - return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); - } - - private double getPlayerDetectionRadius(int level) { - return getConfig().playerDetectionRadiusBase + (getLevelPercent(level) * getConfig().playerDetectionRadiusFactor); - } - - private int getDimDurationTicks(int level) { - return Math.max(10, (int) Math.round(getConfig().dimDurationTicksBase + (getLevelPercent(level) * getConfig().dimDurationTicksFactor))); - } - - private double getMobBackstabMultiplier(int level) { - return getConfig().mobBackstabBase + (getLevelPercent(level) * getConfig().mobBackstabFactor); - } - - private double getPlayerBackstabMultiplier(int level) { - return getConfig().playerBackstabBase + (getLevelPercent(level) * getConfig().playerBackstabFactor); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneaking prevents hostile mob detection, and unseen hits deal backstab damage.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusBase = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Player Detection Radius Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double playerDetectionRadiusBase = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Player Detection Radius Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double playerDetectionRadiusFactor = 14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Dim Duration Ticks Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dimDurationTicksBase = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Dim Duration Ticks Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double dimDurationTicksFactor = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Dim Amplifier for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int dimAmplifier = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mob Backstab Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double mobBackstabBase = 1.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mob Backstab Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double mobBackstabFactor = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Player Backstab Base for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double playerBackstabBase = 1.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Player Backstab Factor for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double playerBackstabFactor = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Look Dot Threshold for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double lookDotThreshold = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Target Drop for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerTargetDrop = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Bonus Damage for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerBonusDamage = 3.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Silent Fall Distance for the Stealth Silent Step adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - float maxSilentFallDistance = 1.6f; - @com.volmit.adapt.util.config.ConfigDoc(value = "Shows nearby threats with per-player glowing while sneaking (red = can detect, gray = almost).", impact = "Enable to get visual awareness of entities that can or almost can spot you.") - boolean showThreatGlows = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Look-dot margin below the full detection threshold used for gray 'almost detect' glow.", impact = "Higher values make gray warnings appear earlier; lower values make warnings stricter.") - double almostLookDotMargin = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Look-dot threshold for stealth visibility checks while sneaking.", impact = "Lower values make crossing an entity's view count as seen more easily; higher values require a more direct look.") - double detectionLookDotThreshold = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "If true, all nearby mobs (including passive) can break hidden state when they have line-of-sight.", impact = "Enable to prevent stealth from feeling hidden in front of passive mobs like pigs.") - boolean allMobsAffectStealthVisibility = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Entity types that are NOT ignored by stealth targeting suppression.", impact = "Mobs listed here can still detect/target sneaking players with Silent Step.") - List targetingBlacklistTypes = new ArrayList<>(List.of("WARDEN", "WITHER", "PHANTOM", "ENDER_DRAGON")); - } - - private enum ThreatLevel { - NONE, - ALMOST_DETECT, - CAN_DETECT - } - - private static class ThreatSnapshot { - private final Map threats = new HashMap<>(); - private final Map entities = new HashMap<>(); - private final Map canDetect = new HashMap<>(); - - private void add(Entity entity, ThreatLevel level) { - if (entity == null || level == ThreatLevel.NONE) { - return; - } - - UUID id = entity.getUniqueId(); - entities.put(id, entity); - ThreatLevel existing = threats.get(id); - if (existing == null || level.ordinal() > existing.ordinal()) { - threats.put(id, level); - } - - if (level == ThreatLevel.CAN_DETECT) { - canDetect.put(id, level); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSnatch.java b/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSnatch.java deleted file mode 100644 index 07fe18889..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSnatch.java +++ /dev/null @@ -1,204 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.stealth; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerToggleSneakEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class StealthSnatch extends SimpleAdaptation { - private final Set holds; - - public StealthSnatch() { - super("stealth-snatch"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("stealth.snatch.description")); - setDisplayName(Localizer.dLocalize("stealth.snatch.name")); - setIcon(Material.CHEST_MINECART); - setBaseCost(getConfig().baseCost); - setInterval(getConfig().snatchRate); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - holds = new HashSet<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_stealth_snatch_2500") - .title(Localizer.dLocalize("advancement.challenge_stealth_snatch_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_snatch_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.HOPPER) - .key("challenge_stealth_snatch_25k") - .title(Localizer.dLocalize("advancement.challenge_stealth_snatch_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_snatch_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_stealth_snatch_2500", "stealth.snatch.items-snatched", 2500, 400); - registerMilestone("challenge_stealth_snatch_25k", "stealth.snatch.items-snatched", 25000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRange(getLevelPercent(level)), 1) + C.GRAY + " " + Localizer.dLocalize("stealth.snatch.lore1")); - } - - @EventHandler - public void on(PlayerToggleSneakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (!hasAdaptation(p)) { - return; - } - if (!canAccessChest(p, p.getLocation())) { - return; - } - if (e.isSneaking()) { - snatch(p); - } - } - - private void snatch(Player player) { - double factor = getLevelPercent(player); - - if (factor == 0) { - return; - } - - double range = getRange(factor); - HashSet items = new HashSet<>(); - for (Entity droppedItemEntity : player.getWorld().getNearbyEntities(player.getLocation(), range, range / 1.5, range)) { - if (droppedItemEntity instanceof Item droppedItem) { - if (droppedItem.getPickupDelay() <= 0 || droppedItem.getTicksLived() > 1) { - UUID owner = droppedItem.getOwner(); - if (owner == null || owner.equals(player.getUniqueId())) items.add(droppedItem); - } - } - } - - for (Item droppedItemEntity : items) { - if (!holds.contains(droppedItemEntity.getEntityId())) { - double dist = droppedItemEntity.getLocation().distanceSquared(player.getLocation()); - if (dist < range * range) { - ItemStack is = droppedItemEntity.getItemStack().clone(); - - if (Inventories.hasSpace(player.getInventory(), is)) { - holds.add(droppedItemEntity.getEntityId()); - SoundPlayer spw = SoundPlayer.of(player.getWorld()); - spw.play(player.getLocation(), Sound.BLOCK_LAVA_POP, 1f, (float) (1.0 + (ThreadLocalRandom.current().nextDouble() / 3D))); - safeGiveItem(player, droppedItemEntity, is); - getPlayer(player).getData().addStat("stealth.snatch.items-snatched", 1); - //sendCollected(player, droppedItemEntity); - int id = droppedItemEntity.getEntityId(); - J.s(() -> holds.remove(Integer.valueOf(id))); - } - } - } - } - - } - - private double getRange(double factor) { - return (factor * getConfig().radiusFactor) + 1; - } - - /* - public void sendCollected(Player p, Item item) { - try { - PacketPlayOutCollect packet = new PacketPlayOutCollect(item.getEntityId(), p.getEntityId(), item.getItemStack().getAmount()); - for (Entity i : p.getWorld().getNearbyEntities(p.getLocation(), 8, 8, 8, entity -> entity instanceof Player)) { - ((CraftPlayer) i).getHandle().c.a(packet); - } - } catch (Exception e) { - Adapt.error("Failed to send collected packet"); - e.printStackTrace(); - } - }*/ - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - if (i.isSneaking()) { - J.s(() -> snatch(i)); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - protected void onConfigReload(Config previousConfig, Config newConfig) { - super.onConfigReload(previousConfig, newConfig); - setInterval(newConfig.snatchRate); - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Snatch dropped items instantly while sneaking.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Snatch Rate for the Stealth Snatch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int snatchRate = 250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.125; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Stealth Snatch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 5.55; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSpeed.java b/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSpeed.java deleted file mode 100644 index fc903356d..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/stealth/StealthSpeed.java +++ /dev/null @@ -1,458 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.stealth; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Input; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class StealthSpeed extends SimpleAdaptation { - private static final Sound DEFAULT_ACTIVATION_SOUND = Sound.PARTICLE_SOUL_ESCAPE; - private final Map states; - - public StealthSpeed() { - super("stealth-speed"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("stealth.speed.description")); - setDisplayName(Localizer.dLocalize("stealth.speed.name")); - setIcon(Material.MUSHROOM_STEW); - setBaseCost(getConfig().baseCost); - setInterval(getConfig().setInterval); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - states = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEATHER_BOOTS) - .key("challenge_stealth_speed_5k") - .title(Localizer.dLocalize("advancement.challenge_stealth_speed_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_speed_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_stealth_speed_5k", "stealth.speed.blocks-sneak-sprinted", 5000, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getSpeed(getLevelPercent(level)), 0) + C.GRAY + Localizer.dLocalize("stealth.speed.lore1")); - } - - @EventHandler - public void on(PlayerQuitEvent e) { - states.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler - public void on(PlayerDeathEvent e) { - states.remove(e.getEntity().getUniqueId()); - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - long statIntervalMs = Math.max(50L, getConfig().statIntervalMs); - - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - RuntimeState state = states.computeIfAbsent(p.getUniqueId(), key -> new RuntimeState()); - - if (!isEligible(p)) { - clearBoost(p, state); - continue; - } - - double levelFactor = getLevelPercent(p); - if (levelFactor <= 0) { - clearBoost(p, state); - continue; - } - - boolean crawling = isCrawlingOnLand(p); - float targetWalkSpeed = computeTargetWalkSpeed(state, p, levelFactor, crawling); - applyBoost(p, state, targetWalkSpeed, now); - applyAutoStep(p, state, now); - - if (!isMovingHorizontally(p, getConfig().movementVelocityThreshold)) { - continue; - } - - if (getConfig().showSoulParticles && M.r(getConfig().soulParticleChance)) { - p.spawnParticle(Particle.SOUL, p.getLocation().clone().add(0, getConfig().soulParticleYOffset, 0), 1, 0.14, 0.02, 0.14, 0); - } - - if (now - state.lastStatMillis >= statIntervalMs) { - getPlayer(p).getData().addStat("stealth.speed.blocks-sneak-sprinted", 1); - state.lastStatMillis = now; - } - } - } - - private void applyBoost(Player p, RuntimeState state, float targetWalkSpeed, long now) { - if (!state.boosting) { - state.boosting = true; - state.originalWalkSpeed = p.getWalkSpeed(); - - long cooldown = Math.max(0, getConfig().activationSoundCooldownMs); - if (cooldown <= 0 || now - state.lastSoundMillis >= cooldown) { - p.playSound(p.getLocation(), DEFAULT_ACTIVATION_SOUND, getConfig().activationSoundVolume, getConfig().activationSoundPitch); - state.lastSoundMillis = now; - } - } - - float current = p.getWalkSpeed(); - if (Math.abs(current - targetWalkSpeed) > 0.0001f) { - p.setWalkSpeed(targetWalkSpeed); - } - } - - private void clearBoost(Player p, RuntimeState state) { - if (!state.boosting) { - return; - } - - state.boosting = false; - float restore = clampWalkSpeed(state.originalWalkSpeed); - float current = p.getWalkSpeed(); - if (Math.abs(current - restore) > 0.0001f) { - p.setWalkSpeed(restore); - } - } - - private boolean isEligible(Player p) { - if (!hasAdaptation(p)) { - return false; - } - - boolean crawlingOnLand = isCrawlingOnLand(p); - if (!p.isSneaking() && !crawlingOnLand) { - return false; - } - - GameMode mode = p.getGameMode(); - if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { - return false; - } - - if (p.isDead() || p.getVehicle() != null || p.isFlying() || p.isGliding()) { - return false; - } - - if ((p.isSwimming() || p.isInWater()) && !crawlingOnLand && !getConfig().allowWhileInWater) { - return false; - } - - return !getConfig().requireGrounded || p.isOnGround(); - } - - private boolean isCrawlingOnLand(Player p) { - if (p.getBoundingBox().getHeight() > getConfig().crawlHeightMax) { - return false; - } - - return !p.getEyeLocation().getBlock().isLiquid() && !p.getLocation().getBlock().isLiquid(); - } - - private float computeTargetWalkSpeed(RuntimeState state, Player p, double levelFactor, boolean crawling) { - float base = state.boosting ? state.originalWalkSpeed : clampWalkSpeed(p.getWalkSpeed()); - if (!state.boosting && Math.abs(base) < 0.0001f) { - base = clampWalkSpeed(getConfig().baselineWalkSpeed); - } - - double bonus = getSpeed(levelFactor); - if (crawling) { - bonus *= Math.max(0, getConfig().crawlBonusMultiplier); - } - - return clampWalkSpeed(base + (float) bonus); - } - - private void applyAutoStep(Player p, RuntimeState state, long now) { - if (!getConfig().enableAutoStep || !p.isOnGround()) { - return; - } - - long cooldown = Math.max(0, getConfig().autoStepCooldownMs); - if (cooldown > 0 && now - state.lastStepMillis < cooldown) { - return; - } - - Vector direction = resolveAutoStepDirection(p); - if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { - return; - } - - double probe = Math.max(0.1, getConfig().autoStepProbeDistance); - org.bukkit.Location feet = p.getLocation(); - org.bukkit.Location front = feet.clone().add(direction.multiply(probe)); - - if (getConfig().enableAutoStepUp && tryStepUp(p, front, direction)) { - state.lastStepMillis = now; - return; - } - - if (getConfig().enableAutoStepDown && tryStepDown(p, front, direction)) { - state.lastStepMillis = now; - } - } - - private Vector resolveAutoStepDirection(Player p) { - if (getConfig().autoStepUseInput) { - try { - Input input = p.getCurrentInput(); - if (input != null) { - VelocitySpeed.InputSnapshot snapshot = new VelocitySpeed.InputSnapshot(input.isForward(), input.isBackward(), input.isLeft(), input.isRight()); - if (snapshot.hasHorizontal()) { - Vector inputDirection = VelocitySpeed.resolveHorizontalDirection(p, snapshot); - if (inputDirection.lengthSquared() > VelocitySpeed.EPSILON) { - return inputDirection; - } - } - } - } catch (NoSuchMethodError ignored) { - // Runtime does not expose input API. Use velocity-based fallback. - } - } - - Vector movement = new Vector(p.getVelocity().getX(), 0, p.getVelocity().getZ()); - double velocityThreshold = Math.max(0, getConfig().autoStepVelocityThreshold); - if (movement.lengthSquared() <= velocityThreshold * velocityThreshold) { - return new Vector(); - } - - return movement.normalize(); - } - - private boolean tryStepUp(Player p, org.bukkit.Location front, Vector direction) { - if (!isStepObstacle(front, 0)) { - return false; - } - - if (!hasStepHeadroom(p, front)) { - return false; - } - - org.bukkit.Location destination = p.getLocation().clone() - .add(direction.clone().multiply(Math.max(0.05, getConfig().autoStepForwardPush))) - .add(0, 1, 0); - if (!isDestinationSafe(p, destination, true)) { - return false; - } - - p.teleport(destination); - return true; - } - - private boolean tryStepDown(Player p, org.bukkit.Location front, Vector direction) { - if (!isPassable(front, 0)) { - return false; - } - - if (!isPassable(front, -1)) { - return false; - } - - if (!isSolid(front, -2)) { - return false; - } - - org.bukkit.Location destination = p.getLocation().clone() - .add(direction.clone().multiply(Math.max(0.05, getConfig().autoStepForwardPush))) - .add(0, -1, 0); - if (!isDestinationSafe(p, destination, true)) { - return false; - } - - p.teleport(destination); - p.setFallDistance(0); - return true; - } - - private boolean isStepObstacle(org.bukkit.Location base, int yOffset) { - var block = base.clone().add(0, yOffset, 0).getBlock(); - if (block.isLiquid() || block.isPassable() || !block.getType().isSolid()) { - return false; - } - - double obstacleHeight = block.getBoundingBox().getHeight(); - return obstacleHeight >= Math.max(0.05, getConfig().stepObstacleMinHeight); - } - - private boolean isSolid(org.bukkit.Location base, int yOffset) { - var block = base.clone().add(0, yOffset, 0).getBlock(); - return block.getType().isSolid() && !block.isLiquid() && !block.isPassable(); - } - - private boolean isPassable(org.bukkit.Location base, int yOffset) { - var block = base.clone().add(0, yOffset, 0).getBlock(); - return block.isPassable() && !block.isLiquid(); - } - - private boolean hasStepHeadroom(Player p, org.bukkit.Location front) { - if (!isPassable(front, 1)) { - return false; - } - - if (requiresDoubleHeadroom(p) && !isPassable(front, 2)) { - return false; - } - - return true; - } - - private boolean isDestinationSafe(Player p, org.bukkit.Location destination, boolean requireFloor) { - if (!isPassable(destination, 0)) { - return false; - } - - if (requiresDoubleHeadroom(p) && !isPassable(destination, 1)) { - return false; - } - - if (requireFloor && !isSolid(destination, -1)) { - return false; - } - - return true; - } - - private boolean requiresDoubleHeadroom(Player p) { - return p.getBoundingBox().getHeight() >= Math.max(0.5, getConfig().doubleHeadroomHeightThreshold); - } - - private boolean isMovingHorizontally(Player p, double threshold) { - Vector horizontal = new Vector(p.getVelocity().getX(), 0, p.getVelocity().getZ()); - double t = Math.max(0, threshold); - return horizontal.lengthSquared() > t * t; - } - - private float clampWalkSpeed(float value) { - float min = Math.max(-1f, getConfig().minWalkSpeed); - float max = Math.min(1f, Math.max(min, getConfig().maxWalkSpeed)); - return Math.max(min, Math.min(max, value)); - } - - private double getSpeed(double factor) { - return Math.max(0, factor * getConfig().maxSpeedBonus); - } - - private static class RuntimeState { - private boolean boosting; - private float originalWalkSpeed = 0.2f; - private long lastSoundMillis; - private long lastStatMillis; - private long lastStepMillis; - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain speed while sneaking.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Tick interval (ms) used to update stealth speed.", impact = "Lower values feel more responsive but run updates more frequently.") - long setInterval = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Fallback baseline walk speed if no original speed has been captured yet.", impact = "Usually keep this at vanilla default unless another plugin changes baseline speeds globally.") - float baselineWalkSpeed = 0.2f; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum additional walk speed granted at max level.", impact = "Higher values make stealth speed more noticeable.") - double maxSpeedBonus = 0.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Multiplier applied to bonus speed while crawling on land.", impact = "Higher values make crawling keep pace with sneaking.") - double crawlBonusMultiplier = 1.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum walk speed clamp used when applying the boost.", impact = "Keep near default to avoid unexpected slowdowns from conflicting systems.") - float minWalkSpeed = -1f; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum walk speed clamp used when applying the boost.", impact = "Lower values are safer for anticheat; higher values feel faster.") - float maxWalkSpeed = 1f; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables automatic vertical stepping while stealth speed is active.", impact = "Helps smooth sneaking over one-block terrain changes.") - boolean enableAutoStep = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows stepping up one block while moving.", impact = "Reduces sneak interruption when encountering small ledges.") - boolean enableAutoStepUp = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows stepping down one block while moving.", impact = "Only steps down when the drop is exactly one block.") - boolean enableAutoStepDown = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Forward probe distance for auto-step checks.", impact = "Higher values detect ledges earlier but can feel more aggressive.") - double autoStepProbeDistance = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal push applied during each auto-step teleport.", impact = "Higher values move farther onto/off the next block and reduce repeat stepping in place.") - double autoStepForwardPush = 0.36; - @com.volmit.adapt.util.config.ConfigDoc(value = "Uses direct movement input for auto-step direction when available.", impact = "Helps auto-step trigger while pressing into obstacles, even when velocity is near zero.") - boolean autoStepUseInput = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum horizontal velocity required before auto-step runs.", impact = "Higher values avoid accidental stepping while nearly idle.") - double autoStepVelocityThreshold = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum delay between auto-step teleports.", impact = "Higher values reduce repeated stepping in tight terrain.") - long autoStepCooldownMs = 90; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum obstacle collision height that counts as a step-up blocker.", impact = "Higher values ignore small lips/slabs; lower values step up more aggressively.") - double stepObstacleMinHeight = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Bounding-box height above which two-block headroom is required for step-up.", impact = "Lower values are stricter; higher values allow sneaking/crawling to step in tighter spaces.") - double doubleHeadroomHeightThreshold = 1.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum bounding-box height counted as crawling on land.", impact = "Higher values make crawl detection more permissive.") - double crawlHeightMax = 0.61; - @com.volmit.adapt.util.config.ConfigDoc(value = "Requires players to be grounded for stealth speed to run.", impact = "True avoids midair acceleration and keeps behavior stable.") - boolean requireGrounded = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Allows stealth speed to run while the player is in water.", impact = "False prevents stealth from overriding seaborne-style underwater movement effects.") - boolean allowWhileInWater = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum horizontal velocity used to count the player as moving for FX/stat tracking.", impact = "Higher values reduce effects while nearly stationary.") - double movementVelocityThreshold = 0.005; - @com.volmit.adapt.util.config.ConfigDoc(value = "Shows a subtle soul particle near the player's feet while stealth speed is active.", impact = "Visual feedback visible only to the boosted player.") - boolean showSoulParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Chance per tick to spawn a soul particle while moving.", impact = "Higher values make the effect denser; lower values are subtler.") - double soulParticleChance = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Vertical offset for the soul particle effect.", impact = "Small positive values keep particles around floor level.") - double soulParticleYOffset = 0.02; - @com.volmit.adapt.util.config.ConfigDoc(value = "Activation sound volume heard by the boosted player.", impact = "Higher values are louder; lower values are subtler.") - float activationSoundVolume = 1.6f; - @com.volmit.adapt.util.config.ConfigDoc(value = "Activation sound pitch heard by the boosted player.", impact = "Higher values raise tone; lower values deepen it.") - float activationSoundPitch = 0.9f; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum time between activation sounds.", impact = "Higher values reduce audio spam when repeatedly starting/stopping.") - long activationSoundCooldownMs = 250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Minimum time between progression stat increments while moving with stealth speed.", impact = "Controls how quickly the sneak-speed progression stat accumulates.") - long statIntervalMs = 200; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/stealth/util/EntityListing.java b/src/main/java/com/volmit/adapt/content/adaptation/stealth/util/EntityListing.java deleted file mode 100644 index 23e67d23c..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/stealth/util/EntityListing.java +++ /dev/null @@ -1,66 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.stealth.util; - -import lombok.Getter; -import org.bukkit.entity.EntityType; - -import java.util.List; - -public class EntityListing { - - @Getter - public static List aggroMobs = List.of( - EntityType.EVOKER, - EntityType.VINDICATOR, - EntityType.PILLAGER, - EntityType.RAVAGER, - EntityType.VEX, - EntityType.ENDERMITE, - EntityType.GUARDIAN, - EntityType.ELDER_GUARDIAN, - EntityType.SKELETON, - EntityType.SHULKER, - EntityType.SKELETON_HORSE, - EntityType.HUSK, - EntityType.STRAY, - EntityType.PHANTOM, - EntityType.BLAZE, - EntityType.CREEPER, - EntityType.GHAST, - EntityType.MAGMA_CUBE, - EntityType.SILVERFISH, - EntityType.SLIME, - EntityType.SPIDER, - EntityType.CAVE_SPIDER, - EntityType.ZOMBIE, - EntityType.ZOMBIE_HORSE, - EntityType.ZOMBIE_VILLAGER, - EntityType.DROWNED, - EntityType.WITHER_SKELETON, - EntityType.WITCH, - EntityType.HOGLIN, - EntityType.ZOGLIN, - EntityType.PIGLIN, - EntityType.PIGLIN_BRUTE, - EntityType.ENDERMAN - ); - - -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsBloodyBlade.java b/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsBloodyBlade.java deleted file mode 100644 index 751bc5488..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsBloodyBlade.java +++ /dev/null @@ -1,202 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.sword; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.sword.effects.DamagingBleedEffect; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import de.slikey.effectlib.effect.BleedEffect; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -public class SwordsBloodyBlade extends SimpleAdaptation { - private final Map cooldowns; - private final Set bleedingEntities = new HashSet<>(); - private final Map bleedSource = new HashMap<>(); - - public SwordsBloodyBlade() { - super("sword-bloody-blade"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("sword.bloody_blade.description")); - setDisplayName(Localizer.dLocalize("sword.bloody_blade.name")); - setIcon(Material.RED_DYE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(5534); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_swords_bloody_500") - .title(Localizer.dLocalize("advancement.challenge_swords_bloody_500.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_bloody_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_swords_bloody_500", "swords.bloody-blade.bleed-damage", 500, 400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_swords_bloody_kills_100") - .title(Localizer.dLocalize("advancement.challenge_swords_bloody_kills_100.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_bloody_kills_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_swords_bloody_kills_100", "swords.bloody-blade.bleed-kills", 100, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + " " + Localizer.dLocalize("sword.bloody_blade.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getDurationOfEffect(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.bloody_blade.lore2")); - v.addLore(C.RED + "* " + Form.duration(getCooldown(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.bloody_blade.lore3")); - } - - public long getCooldown(int level) { - return getConfig().cooldown * level; - } - - public long getDurationOfEffect(int level) { - return getConfig().effectDuration * level; - } - - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p && hasAdaptation(p) && ItemListings.getToolSwords().contains(p.getInventory().getItemInMainHand().getType())) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown > System.currentTimeMillis()) - return; - Entity victim = e.getEntity(); - cooldowns.put(p, System.currentTimeMillis() + getCooldown(getLevel(p))); - if (victim instanceof Player pvic) { - if (!canPVP(p, pvic.getLocation())) return; - } else { - if (!canPVE(p, victim.getLocation())) return; - } - if (areParticlesEnabled()) { - BleedEffect blood = victim instanceof LivingEntity l ? new DamagingBleedEffect(Adapt.instance.adaptEffectManager, getConfig().damagePerBleedProc, l) : new BleedEffect(Adapt.instance.adaptEffectManager); - blood.setEntity(victim); - blood.material = Material.CRIMSON_ROOTS; - blood.height = -1; - blood.iterations = Math.toIntExact(2 * (3 + (getDurationOfEffect(getLevel(p)) / 1000))); - blood.period = 5; //5 Every second, make a proc - blood.hurt = false; -// blood.callback = () -> { -// Adapt.mAdapt.msgp(sender.player(),(p,"You bled out.."); -// p.setHealth(1d); -// }; - blood.start(); - } else { - BleedEffect blood = victim instanceof LivingEntity l ? new DamagingBleedEffect(Adapt.instance.adaptEffectManager, getConfig().damagePerBleedProc, l) : new BleedEffect(Adapt.instance.adaptEffectManager); - blood.setEntity(victim); - blood.material = Material.VOID_AIR; - blood.height = -1; - blood.iterations = Math.toIntExact(2 * (3 + (getDurationOfEffect(getLevel(p)) / 1000))); - blood.period = 5; //5 Every second, make a proc - blood.hurt = false; - blood.start(); - } - bleedingEntities.add(victim.getUniqueId()); - bleedSource.put(victim.getUniqueId(), p); - getPlayer(p).getData().addStat("swords.bloody-blade.bleed-damage", 1); - - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - UUID victimId = e.getEntity().getUniqueId(); - if (bleedingEntities.remove(victimId)) { - Player source = bleedSource.remove(victimId); - if (source != null && source.isOnline()) { - getPlayer(source).getData().addStat("swords.bloody-blade.bleed-kills", 1); - } - } - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sword strikes cause bleeding over time.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Swords Bloody Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public long cooldown = 5000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Per Bleed Proc for the Swords Bloody Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double damagePerBleedProc = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effect Duration for the Swords Bloody Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public long effectDuration = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Swords Bloody Blade adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.325; - - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsCrimsonCyclone.java b/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsCrimsonCyclone.java deleted file mode 100644 index bd96e5d6e..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsCrimsonCyclone.java +++ /dev/null @@ -1,319 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.sword; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.sword.effects.DamagingBleedEffect; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import de.slikey.effectlib.effect.BleedEffect; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.Damageable; - -public class SwordsCrimsonCyclone extends SimpleAdaptation { - public SwordsCrimsonCyclone() { - super("sword-crimson-cyclone"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("sword.crimson_cyclone.description")); - setDisplayName(Localizer.dLocalize("sword.crimson_cyclone.name")); - setIcon(Material.NETHERITE_SWORD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_swords_cyclone_500") - .title(Localizer.dLocalize("advancement.challenge_swords_cyclone_500.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_cyclone_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_swords_cyclone_5k") - .title(Localizer.dLocalize("advancement.challenge_swords_cyclone_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_cyclone_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_swords_cyclone_500", "swords.crimson-cyclone.mobs-hit", 500, 400); - registerMilestone("challenge_swords_cyclone_5k", "swords.crimson-cyclone.mobs-hit", 5000, 1500); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_swords_cyclone_6") - .title(Localizer.dLocalize("advancement.challenge_swords_cyclone_6.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_cyclone_6.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("sword.crimson_cyclone.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(getBaseDamage(level), 2) + C.GRAY + " " + Localizer.dLocalize("sword.crimson_cyclone.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("sword.crimson_cyclone.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageByEntityEvent e) { - if (!(e.getDamager() instanceof Player p) || !(e.getEntity() instanceof LivingEntity primaryTarget)) { - return; - } - - if (!hasAdaptation(p)) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (!isSword(hand) || p.hasCooldown(hand.getType()) || !isCritTrigger(p)) { - return; - } - - if (primaryTarget instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - return; - } - } else if (!canPVE(p, primaryTarget.getLocation())) { - return; - } - - int level = getLevel(p); - int hungerCost = getHungerCost(level); - if (p.getFoodLevel() < hungerCost) { - return; - } - - if (!applyDurabilityCost(hand, getDurabilityCost(level))) { - return; - } - - int hits = 0; - double radius = getRadius(level); - double damage = getBaseDamage(level); - p.setFoodLevel(Math.max(0, p.getFoodLevel() - hungerCost)); - p.setCooldown(hand.getType(), getCooldownTicks(level)); - - e.setDamage(e.getDamage() + damage); - applyBleed(primaryTarget, level); - if (areParticlesEnabled()) { - primaryTarget.getWorld().spawnParticle(Particle.CRIMSON_SPORE, primaryTarget.getLocation().add(0, 0.8, 0), 8, 0.2, 0.35, 0.2, 0.01); - } - hits++; - - for (Entity entity : primaryTarget.getWorld().getNearbyEntities(primaryTarget.getLocation(), radius, radius, radius)) { - if (!(entity instanceof LivingEntity target)) { - continue; - } - - if (target == p || target == primaryTarget) { - continue; - } - - if (target instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - continue; - } - } else if (!canPVE(p, target.getLocation())) { - continue; - } - - target.damage(damage, p); - applyBleed(target, level); - if (areParticlesEnabled()) { - target.getWorld().spawnParticle(Particle.CRIMSON_SPORE, target.getLocation().add(0, 0.8, 0), 8, 0.2, 0.35, 0.2, 0.01); - } - hits++; - } - - if (hits <= 0) { - return; - } - - if (areParticlesEnabled()) { - - p.getWorld().spawnParticle(Particle.SWEEP_ATTACK, primaryTarget.getLocation().add(0, 1, 0), 2, 0.4, 0.1, 0.4, 0.02); - - } - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.CRIMSON_SPORE, primaryTarget.getLocation().add(0, 1, 0), 36, 0.8, 0.4, 0.8, 0.02); - } - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(primaryTarget.getLocation(), Sound.ENTITY_PLAYER_ATTACK_SWEEP, 1f, 0.7f); - sp.play(primaryTarget.getLocation(), Sound.ENTITY_WITHER_HURT, 0.65f, 1.45f); - xp(p, hits * getConfig().xpPerTargetHit); - getPlayer(p).getData().addStat("swords.crimson-cyclone.mobs-hit", hits); - - // Special achievement: hit 6+ mobs with one activation - if (hits >= 6 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_swords_cyclone_6")) { - getPlayer(p).getAdvancementHandler().grant("challenge_swords_cyclone_6"); - } - } - - private boolean isCritTrigger(Player p) { - return p.getFallDistance() >= getConfig().minFallDistanceForCrit - && !p.isOnGround() - && !p.isInWater() - && !p.isInsideVehicle() - && !p.isClimbing(); - } - - private boolean applyDurabilityCost(ItemStack hand, int durabilityCost) { - if (!(hand.getItemMeta() instanceof Damageable damageable) || hand.getType().getMaxDurability() <= 0) { - return true; - } - - int max = hand.getType().getMaxDurability(); - int next = damageable.getDamage() + durabilityCost; - if (next >= max) { - return false; - } - - damageable.setDamage(next); - hand.setItemMeta(damageable); - return true; - } - - private double getRadius(int level) { - return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); - } - - private double getBaseDamage(int level) { - return getConfig().baseDamage + (getLevelPercent(level) * getConfig().damageFactor); - } - - private void applyBleed(LivingEntity target, int level) { - BleedEffect bleed = new DamagingBleedEffect(Adapt.instance.adaptEffectManager, getBleedDamagePerProc(level), target); - bleed.setEntity(target); - bleed.material = getConfig().showBleedParticles ? Material.CRIMSON_ROOTS : Material.VOID_AIR; - bleed.height = -1; - bleed.period = 5; - bleed.hurt = false; - bleed.iterations = Math.max(4, (int) Math.ceil(getBleedTicks(level) / 5D)); - bleed.start(); - } - - private int getBleedTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().bleedTicksBase + (getLevelPercent(level) * getConfig().bleedTicksFactor))); - } - - private double getBleedDamagePerProc(int level) { - return Math.max(0.01, getConfig().bleedDamagePerProcBase + (getLevelPercent(level) * getConfig().bleedDamagePerProcFactor)); - } - - private int getHungerCost(int level) { - return Math.max(1, (int) Math.round(getConfig().hungerCostBase - (getLevelPercent(level) * getConfig().hungerCostFactor))); - } - - private int getDurabilityCost(int level) { - return Math.max(1, (int) Math.round(getConfig().durabilityCostBase - (getLevelPercent(level) * getConfig().durabilityCostFactor))); - } - - private int getCooldownTicks(int level) { - return Math.max(40, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Land a sword crit while falling to unleash a bleeding crimson cyclone around your target.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Bleed Particles for the Swords Crimson Cyclone adaptation.", impact = "True enables this behavior and false disables it.") - boolean showBleedParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.76; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusBase = 2.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 2.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Damage for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseDamage = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageFactor = 4.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bleed Ticks Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bleedTicksBase = 40; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bleed Ticks Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bleedTicksFactor = 90; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bleed Damage Per Proc Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bleedDamagePerProcBase = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bleed Damage Per Proc Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bleedDamagePerProcFactor = 0.45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hungerCostBase = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hunger Cost Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hungerCostFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durabilityCostBase = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Durability Cost Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double durabilityCostFactor = 1.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 320; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 160; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Fall Distance For Crit for the Swords Crimson Cyclone adaptation.", impact = "Minimum fall distance required to trigger the cyclone on hit.") - float minFallDistanceForCrit = 0.08f; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Target Hit for the Swords Crimson Cyclone adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerTargetHit = 10; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsDualWield.java b/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsDualWield.java deleted file mode 100644 index f5e955d97..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsDualWield.java +++ /dev/null @@ -1,147 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.sword; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.inventory.ItemStack; - -public class SwordsDualWield extends SimpleAdaptation { - public SwordsDualWield() { - super("sword-dual-wield"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("sword.dual_wield.description")); - setDisplayName(Localizer.dLocalize("sword.dual_wield.name")); - setIcon(Material.GOLDEN_SWORD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1800); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_swords_dual_1k") - .title(Localizer.dLocalize("advancement.challenge_swords_dual_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_dual_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_swords_dual_25k") - .title(Localizer.dLocalize("advancement.challenge_swords_dual_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_dual_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_swords_dual_1k", "swords.dual-wield.bonus-damage", 1000, 400); - registerMilestone("challenge_swords_dual_25k", "swords.dual-wield.bonus-damage", 25000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getSameMultiplier(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.dual_wield.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getMixedMultiplier(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.dual_wield.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - ItemStack main = p.getInventory().getItemInMainHand(); - ItemStack off = p.getInventory().getItemInOffHand(); - if (!isItem(main) || !isItem(off) || main.getType() == Material.AIR || off.getType() == Material.AIR) { - return; - } - - boolean sameWeapon = main.getType() == off.getType(); - double multiplier = sameWeapon ? getSameMultiplier(getLevel(p)) : getMixedMultiplier(getLevel(p)); - double originalDamage = e.getDamage(); - e.setDamage(originalDamage * multiplier); - double bonusDamage = e.getDamage() - originalDamage; - xp(p, e.getDamage() * getConfig().xpPerDamage); - getPlayer(p).getData().addStat("swords.dual-wield.bonus-damage", bonusDamage); - } - - private double getSameMultiplier(int level) { - return getConfig().sameWeaponBase + (getLevelPercent(level) * getConfig().sameWeaponFactor); - } - - private double getMixedMultiplier(int level) { - return getConfig().mixedWeaponBase + (getLevelPercent(level) * getConfig().mixedWeaponFactor); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Wield a sword in both hands for increased damage output.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Same Weapon Base for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sameWeaponBase = 1.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Same Weapon Factor for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sameWeaponFactor = 0.43; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mixed Weapon Base for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double mixedWeaponBase = 1.06; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mixed Weapon Factor for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double mixedWeaponFactor = 0.28; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Damage for the Swords Dual Wield adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerDamage = 2.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsExecutionersEdge.java b/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsExecutionersEdge.java deleted file mode 100644 index f81c8b7b3..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsExecutionersEdge.java +++ /dev/null @@ -1,191 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.sword; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.attribute.Attribute; -import org.bukkit.attribute.AttributeInstance; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; - -public class SwordsExecutionersEdge extends SimpleAdaptation { - public SwordsExecutionersEdge() { - super("sword-executioners-edge"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("sword.executioners_edge.description")); - setDisplayName(Localizer.dLocalize("sword.executioners_edge.name")); - setIcon(Material.STONE_SWORD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1900); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_swords_execute_200") - .title(Localizer.dLocalize("advancement.challenge_swords_execute_200.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_execute_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_swords_execute_2500") - .title(Localizer.dLocalize("advancement.challenge_swords_execute_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_execute_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_swords_execute_200", "swords.executioners-edge.executions", 200, 400); - registerMilestone("challenge_swords_execute_2500", "swords.executioners-edge.executions", 2500, 1500); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.NETHERITE_AXE) - .key("challenge_swords_execute_5in10") - .title(Localizer.dLocalize("advancement.challenge_swords_execute_5in10.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_execute_5in10.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getBonusDamage(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.executioners_edge.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getThreshold(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.executioners_edge.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Player p) || !hasAdaptation(p) || !isSword(p.getInventory().getItemInMainHand()) || !(e.getEntity() instanceof LivingEntity target)) { - return; - } - - if (target instanceof Player victim) { - if (!canPVP(p, victim.getLocation())) { - return; - } - } else if (!canPVE(p, target.getLocation())) { - return; - } - - double maxHealth = getMaxHealth(target); - if (maxHealth <= 0) { - return; - } - - double hpPercent = Math.max(0, target.getHealth() / maxHealth); - double threshold = getThreshold(getLevel(p)); - if (hpPercent > threshold) { - return; - } - - double multiplier = 1D + getBonusDamage(getLevel(p)); - e.setDamage(e.getDamage() * multiplier); - xp(p, e.getDamage() * getConfig().xpPerBuffedDamage); - getPlayer(p).getData().addStat("swords.executioners-edge.executions", 1); - - // Special achievement: execute 5 in 10 seconds - long now = System.currentTimeMillis(); - long windowStart = getStorageLong(p, "executeWindowStart", 0L); - int windowCount = getStorageInt(p, "executeWindowCount", 0); - if (now - windowStart > 10000L) { - windowStart = now; - windowCount = 1; - } else { - windowCount++; - } - setStorage(p, "executeWindowStart", windowStart); - setStorage(p, "executeWindowCount", windowCount); - if (windowCount >= 5 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_swords_execute_5in10")) { - getPlayer(p).getAdvancementHandler().grant("challenge_swords_execute_5in10"); - } - } - - private double getMaxHealth(LivingEntity entity) { - AttributeInstance attr = entity.getAttribute(Attribute.MAX_HEALTH); - return attr == null ? entity.getHealth() : attr.getValue(); - } - - private double getBonusDamage(int level) { - return getConfig().bonusDamageBase + (getLevelPercent(level) * getConfig().bonusDamageFactor); - } - - private double getThreshold(int level) { - return Math.min(getConfig().maxThreshold, getConfig().thresholdBase + (getLevelPercent(level) * getConfig().thresholdFactor)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sword strikes deal bonus damage to low-health targets.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Damage Base for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusDamageBase = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Damage Factor for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusDamageFactor = 0.42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Threshold Base for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double thresholdBase = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Threshold Factor for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double thresholdFactor = 0.33; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Threshold for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxThreshold = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Buffed Damage for the Swords Executioners Edge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerBuffedDamage = 1.9; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsMachete.java b/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsMachete.java deleted file mode 100644 index c35a2bc8f..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsMachete.java +++ /dev/null @@ -1,240 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.sword; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.Materials; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import org.bukkit.inventory.ItemStack; - -import java.util.concurrent.ThreadLocalRandom; - -public class SwordsMachete extends SimpleAdaptation { - public SwordsMachete() { - super("sword-machete"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("sword.machete.description")); - setDisplayName(Localizer.dLocalize("sword.machete.name")); - setIcon(Material.IRON_SWORD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(5234); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_swords_machete_2500") - .title(Localizer.dLocalize("advancement.challenge_swords_machete_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_machete_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_swords_machete_25k") - .title(Localizer.dLocalize("advancement.challenge_swords_machete_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_machete_25k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_swords_machete_2500", "swords.machete.foliage-cut", 2500, 300); - registerMilestone("challenge_swords_machete_25k", "swords.machete.foliage-cut", 25000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getRadius(level) + C.GRAY + " " + Localizer.dLocalize("sword.machete.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTime(getLevelPercent(level)) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("sword.machete.lore2")); - v.addLore(C.RED + "- " + getDamagePerBlock(getLevelPercent(level)) + C.GRAY + " " + Localizer.dLocalize("sword.machete.lore3")); - } - - public double getRadius(int level) { - return (getLevelPercent(level) * getConfig().radiusFactor) + getConfig().radiusBase; - } - - @EventHandler - public void on(PlayerInteractEvent e) { - Player p = e.getPlayer(); - SoundPlayer spw = SoundPlayer.of(p.getWorld()); - if (e.getHand() != null && e.getHand().equals(EquipmentSlot.HAND) && e.getAction().equals(Action.LEFT_CLICK_AIR) || e.getAction().equals(Action.LEFT_CLICK_BLOCK)) { - int dmg = 0; - ItemStack is = e.getItem(); - if (isSword(is)) { - if (is != null && !p.hasCooldown(is.getType()) && hasAdaptation(p)) { - Location ctr = p.getEyeLocation().clone().add(p.getLocation().getDirection().clone().multiply(2.25)).add(0, -0.5, 0); - - int lvl = getLevel(p); - Cuboid c = new Cuboid(ctr); - c = c.expand(Cuboid.CuboidDirection.Up, (int) Math.floor(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.Down, (int) Math.floor(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.North, (int) Math.round(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.South, (int) Math.round(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.East, (int) Math.round(getRadius(lvl))); - c = c.expand(Cuboid.CuboidDirection.West, (int) Math.round(getRadius(lvl))); - - if (dmg > 0) { - return; - } - - for (Block i : c) { - if (M.r((getLevelPercent(lvl) * 2.8) / (i.getLocation().distanceSquared(ctr)))) { - if (i.getType().equals(Material.TALL_GRASS) - || i.getType().equals(Material.CACTUS) - || i.getType().equals(Material.SUGAR_CANE) - || i.getType().equals(Material.CARROT) - || i.getType().equals(Material.POTATO) - || i.getType().equals(Material.NETHER_WART) - || i.getType().equals(Materials.GRASS) - || i.getType().equals(Material.FERN) - || i.getType().equals(Material.LARGE_FERN) - || i.getType().equals(Material.VINE) - || i.getType().equals(Material.ROSE_BUSH) - || i.getType().equals(Material.WITHER_ROSE) - || i.getType().equals(Material.ACACIA_LEAVES) - || i.getType().equals(Material.BIRCH_LEAVES) - || i.getType().equals(Material.DARK_OAK_LEAVES) - || i.getType().equals(Material.JUNGLE_LEAVES) - || i.getType().equals(Material.OAK_LEAVES) - || i.getType().equals(Material.SPRUCE_LEAVES) - || i.getType().equals(Material.BROWN_MUSHROOM) - || i.getType().equals(Material.RED_MUSHROOM) - || i.getType().equals(Material.DEAD_BUSH) - || i.getType().equals(Material.DANDELION) - || i.getType().equals(Material.TALL_SEAGRASS) - || i.getType().equals(Material.SEAGRASS) - || i.getType().equals(Material.WHITE_TULIP) - || i.getType().equals(Material.RED_TULIP) - || i.getType().equals(Material.PINK_TULIP) - || i.getType().equals(Material.ORANGE_TULIP) - || i.getType().equals(Material.LILY_OF_THE_VALLEY) - || i.getType().equals(Material.ALLIUM) - || i.getType().equals(Material.AZURE_BLUET) - || i.getType().equals(Material.SUNFLOWER) - || i.getType().equals(Material.CORNFLOWER) - || i.getType().equals(Material.CHORUS_FLOWER) - || i.getType().equals(Material.BAMBOO) - || i.getType().equals(Material.BAMBOO_SAPLING) - || i.getType().equals(Material.LILAC) - || i.getType().equals(Material.PEONY) - || i.getType().equals(Material.LILY_PAD) - || i.getType().equals(Material.COCOA) - || i.getType().equals(Material.MANGROVE_LEAVES) - - ) { - if (!canBlockBreak(p, i.getLocation())) continue; - BlockBreakEvent ee = new BlockBreakEvent(i, p); - Bukkit.getPluginManager().callEvent(ee); - - if (!ee.isCancelled()) { - dmg += 1; - J.s(() -> { - i.breakNaturally(); - spw.play(i.getLocation(), Sound.BLOCK_GRASS_BREAK, 0.4f, (float) (ThreadLocalRandom.current().nextDouble() * 1.85D)); - }, RNG.r.i(0, (getMaxLevel() - lvl * 2) + 1)); - } - } - } - } - - if (dmg > 0) { - p.setCooldown(is.getType(), getCooldownTime(getLevelPercent(lvl))); -// if (areParticlesEnabled()) { -// ParticleEffect.SWEEP_ATTACK.display(p.getEyeLocation().clone().add(p.getLocation().getDirection().clone().multiply(1.25)).add(0, -0.5, 0), 0f, 0f, 0f, 0.1f, 1, null); -// } - spw.play(p.getEyeLocation(), Sound.ENTITY_PLAYER_ATTACK_SWEEP, 1f, (float) (ThreadLocalRandom.current().nextDouble() / 2D) + 0.65f); - damageHand(p, dmg * getDamagePerBlock(getLevelPercent(lvl))); - xp(p, dmg * 11.25, "foliage-cut"); - getPlayer(p).getData().addStat("swords.machete.foliage-cut", dmg); - } - } - } - } - } - - private int getCooldownTime(double levelPercent) { - return (int) (((int) ((1D - levelPercent) * getConfig().cooldownTicksSlowest)) + getConfig().cooldownTicksBase); - } - - private int getDamagePerBlock(double levelPercent) { - return (int) (getConfig().toolDamageBase + (getConfig().toolDamageInverseLevelFactor * ((1D - levelPercent)))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Cut through foliage with ease using a sword.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Swords Machete adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.225; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusBase = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 2.36; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Slowest for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksSlowest = 35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Tool Damage Base for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double toolDamageBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Tool Damage Inverse Level Factor for the Swords Machete adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double toolDamageInverseLevelFactor = 5; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsPoisonedBlade.java b/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsPoisonedBlade.java deleted file mode 100644 index b62db8f7d..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsPoisonedBlade.java +++ /dev/null @@ -1,192 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.sword; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.sword.effects.DamagingBleedEffect; -import com.volmit.adapt.content.item.ItemListings; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import de.slikey.effectlib.effect.BleedEffect; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.potion.PotionEffectType; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -public class SwordsPoisonedBlade extends SimpleAdaptation { - private final Map cooldowns; - private final Set poisonedEntities = new HashSet<>(); - private final Map poisonSource = new HashMap<>(); - - public SwordsPoisonedBlade() { - super("sword-poison-blade"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("sword.poisoned_blade.description")); - setDisplayName(Localizer.dLocalize("sword.poisoned_blade.name")); - setIcon(Material.GREEN_DYE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInterval(4984); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPIDER_EYE) - .key("challenge_swords_poison_500") - .title(Localizer.dLocalize("advancement.challenge_swords_poison_500.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_poison_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_swords_poison_500", "swords.poisoned-blade.poison-applied", 500, 400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FERMENTED_SPIDER_EYE) - .key("challenge_swords_poison_kills_50") - .title(Localizer.dLocalize("advancement.challenge_swords_poison_kills_50.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_poison_kills_50.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_swords_poison_kills_50", "swords.poisoned-blade.poison-kills", 50, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + C.GRAY + " " + Localizer.dLocalize("sword.poisoned_blade.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getDurationOfEffect(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.poisoned_blade.lore2")); - v.addLore(C.RED + "* " + Form.duration(getCooldown(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.poisoned_blade.lore3")); - } - - public long getCooldown(int level) { - return getConfig().cooldown * level; - } - - public long getDurationOfEffect(int level) { - return getConfig().effectDuration * level; - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p && hasAdaptation(p) && ItemListings.getToolSwords().contains(p.getInventory().getItemInMainHand().getType())) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown > System.currentTimeMillis()) - return; - Entity victim = e.getEntity(); - cooldowns.put(p, System.currentTimeMillis() + getCooldown(getLevel(p))); - if (victim instanceof Player pvic) { - if (!canPVP(p, pvic.getLocation())) return; - BleedEffect blood = new BleedEffect(Adapt.instance.adaptEffectManager); - blood.setEntity(pvic); - blood.material = Material.LARGE_FERN; - blood.height = -1; - blood.iterations = Math.toIntExact(2 * (3 + (getDurationOfEffect(getLevel(p)) / 1000))); - blood.period = 5; //5 Every second, make a proc - blood.hurt = false; - blood.start(); - addPotionStacks(pvic, PotionEffectType.POISON, 2, 50 * getLevel(p), true); - } else { - if (!canPVE(p, victim.getLocation())) return; - BleedEffect blood = victim instanceof LivingEntity l ? new DamagingBleedEffect(Adapt.instance.adaptEffectManager, 1, l) : new BleedEffect(Adapt.instance.adaptEffectManager); - blood.setEntity(victim); - blood.material = Material.LARGE_FERN; - blood.height = -1; - blood.iterations = Math.toIntExact(2 * (3 + (getDurationOfEffect(getLevel(p)) / 1000))); - blood.period = 5; //5 Every second, make a proc - blood.hurt = false; - blood.start(); - } - poisonedEntities.add(victim.getUniqueId()); - poisonSource.put(victim.getUniqueId(), p); - getPlayer(p).getData().addStat("swords.poisoned-blade.poison-applied", 1); - - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - UUID victimId = e.getEntity().getUniqueId(); - if (poisonedEntities.remove(victimId)) { - Player source = poisonSource.remove(victimId); - if (source != null && source.isOnline()) { - getPlayer(source).getData().addStat("swords.poisoned-blade.poison-kills", 1); - } - } - } - - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sword strikes apply poison.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Swords Poisoned Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public long cooldown = 5000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effect Duration for the Swords Poisoned Blade adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public long effectDuration = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.325; - } -} - diff --git a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsRiposteWindow.java b/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsRiposteWindow.java deleted file mode 100644 index 66d2da26b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/sword/SwordsRiposteWindow.java +++ /dev/null @@ -1,217 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.sword; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class SwordsRiposteWindow extends SimpleAdaptation { - private final Map riposteUntil = new HashMap<>(); - - public SwordsRiposteWindow() { - super("sword-riposte-window"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("sword.riposte_window.description")); - setDisplayName(Localizer.dLocalize("sword.riposte_window.name")); - setIcon(Material.GOLDEN_CHESTPLATE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2100); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_swords_riposte_200") - .title(Localizer.dLocalize("advancement.challenge_swords_riposte_200.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_riposte_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_swords_riposte_2500") - .title(Localizer.dLocalize("advancement.challenge_swords_riposte_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_riposte_2500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_swords_riposte_200", "swords.riposte.ripostes-landed", 200, 400); - registerMilestone("challenge_swords_riposte_2500", "swords.riposte.ripostes-landed", 2500, 1500); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_swords_riposte_3in5") - .title(Localizer.dLocalize("advancement.challenge_swords_riposte_3in5.title")) - .description(Localizer.dLocalize("advancement.challenge_swords_riposte_3in5.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.duration(getWindowMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("sword.riposte_window.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getDamageBonus(level), 0) + C.GRAY + " " + Localizer.dLocalize("sword.riposte_window.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - riposteUntil.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageByEntityEvent e) { - if (e.getEntity() instanceof Player defender) { - armRiposte(defender); - } - - if (!(e.getDamager() instanceof Player attacker) || !hasAdaptation(attacker) || !isSword(attacker.getInventory().getItemInMainHand())) { - return; - } - - long now = System.currentTimeMillis(); - long until = riposteUntil.getOrDefault(attacker.getUniqueId(), 0L); - if (until < now) { - return; - } - - if (!(e.getEntity() instanceof LivingEntity target)) { - return; - } - - if (target instanceof Player victim) { - if (!canPVP(attacker, victim.getLocation())) { - return; - } - } else if (!canPVE(attacker, target.getLocation())) { - return; - } - - e.setDamage(e.getDamage() * (1D + getDamageBonus(getLevel(attacker)))); - riposteUntil.remove(attacker.getUniqueId()); - if (areParticlesEnabled()) { - attacker.getWorld().spawnParticle(Particle.SWEEP_ATTACK, attacker.getLocation().add(0, 1, 0), 1, 0, 0, 0, 0); - } - SoundPlayer sp = SoundPlayer.of(attacker.getWorld()); - sp.play(attacker.getLocation(), Sound.ITEM_SHIELD_BLOCK, 0.7f, 1.6f); - sp.play(attacker.getLocation(), Sound.ENTITY_PLAYER_ATTACK_CRIT, 0.8f, 1.2f); - xp(attacker, e.getDamage() * getConfig().xpPerBuffedDamage); - getPlayer(attacker).getData().addStat("swords.riposte.ripostes-landed", 1); - - // Special achievement: 3 ripostes within 5 seconds - long riposteWindowStart = getStorageLong(attacker, "riposteWindowStart", 0L); - int riposteWindowCount = getStorageInt(attacker, "riposteWindowCount", 0); - if (now - riposteWindowStart > 5000L) { - riposteWindowStart = now; - riposteWindowCount = 1; - } else { - riposteWindowCount++; - } - setStorage(attacker, "riposteWindowStart", riposteWindowStart); - setStorage(attacker, "riposteWindowCount", riposteWindowCount); - if (riposteWindowCount >= 3 && AdaptConfig.get().isAdvancements() && !getPlayer(attacker).getData().isGranted("challenge_swords_riposte_3in5")) { - getPlayer(attacker).getAdvancementHandler().grant("challenge_swords_riposte_3in5"); - } - } - - private void armRiposte(Player defender) { - boolean hasShield = defender.getInventory().getItemInOffHand().getType() == Material.SHIELD - || defender.getInventory().getItemInMainHand().getType() == Material.SHIELD; - if (!hasAdaptation(defender) || !defender.isBlocking() || !hasShield) { - return; - } - - int level = getLevel(defender); - riposteUntil.put(defender.getUniqueId(), System.currentTimeMillis() + getWindowMillis(level)); - SoundPlayer.of(defender.getWorld()).play(defender.getLocation(), Sound.ITEM_SHIELD_BLOCK, 0.6f, 0.9f); - } - - private long getWindowMillis(int level) { - return Math.max(150L, (long) Math.round(getConfig().windowMillisBase + (getLevelPercent(level) * getConfig().windowMillisFactor))); - } - - private double getDamageBonus(int level) { - return getConfig().damageBonusBase + (getLevelPercent(level) * getConfig().damageBonusFactor); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Blocking with a shield arms a short riposte window for your next sword strike.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.71; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Window Millis Base for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windowMillisBase = 350; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Window Millis Factor for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double windowMillisFactor = 550; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Base for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageBonusBase = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Bonus Factor for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageBonusFactor = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Buffed Damage for the Swords Riposte Window adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerBuffedDamage = 1.8; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/sword/effects/DamagingBleedEffect.java b/src/main/java/com/volmit/adapt/content/adaptation/sword/effects/DamagingBleedEffect.java deleted file mode 100644 index 441aab251..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/sword/effects/DamagingBleedEffect.java +++ /dev/null @@ -1,41 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.sword.effects; - -import com.volmit.adapt.util.J; -import de.slikey.effectlib.EffectManager; -import de.slikey.effectlib.effect.BleedEffect; -import org.bukkit.entity.LivingEntity; - -public class DamagingBleedEffect extends BleedEffect { - private final double damage; - private final LivingEntity target; - - public DamagingBleedEffect(EffectManager effectManager, double damage, LivingEntity target) { - super(effectManager); - this.damage = damage; - this.target = target; - } - - @Override - public void onRun() { - super.onRun(); - J.s(() -> target.damage(damage)); - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingBeastRecall.java b/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingBeastRecall.java deleted file mode 100644 index b90076cfb..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingBeastRecall.java +++ /dev/null @@ -1,222 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.taming; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.block.Block; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Tameable; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; - -public class TamingBeastRecall extends SimpleAdaptation { - public TamingBeastRecall() { - super("tame-beast-recall"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("taming.beast_recall.description")); - setDisplayName(Localizer.dLocalize("taming.beast_recall.name")); - setIcon(Material.LEAD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2200); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEAD) - .key("challenge_taming_recall_100") - .title(Localizer.dLocalize("advancement.challenge_taming_recall_100.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_recall_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_taming_recall_1k") - .title(Localizer.dLocalize("advancement.challenge_taming_recall_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_recall_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_taming_recall_100", "taming.beast-recall.recalls", 100, 300); - registerMilestone("challenge_taming_recall_1k", "taming.beast-recall.recalls", 1000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getSearchRadius(level)) + C.GRAY + " " + Localizer.dLocalize("taming.beast_recall.lore1")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("taming.beast_recall.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(PlayerInteractEvent e) { - if (e.isCancelled() || e.getHand() != EquipmentSlot.HAND) { - return; - } - - Action action = e.getAction(); - if (action != Action.RIGHT_CLICK_AIR && action != Action.RIGHT_CLICK_BLOCK) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || !p.isSneaking() || p.getInventory().getItemInMainHand().getType() != Material.LEAD || p.hasCooldown(Material.LEAD)) { - return; - } - - int level = getLevel(p); - Tameable tameable = findNearestOwnedTameable(p, getSearchRadius(level)); - if (tameable == null) { - return; - } - - Location safe = findSafeRecallLocation(p); - if (safe == null || !canPVE(p, safe)) { - return; - } - - tameable.teleport(safe); - tameable.setFallDistance(0); - p.setCooldown(Material.LEAD, getCooldownTicks(level)); - e.setCancelled(true); - - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 0.75f, 1.45f); - sp.play(safe, Sound.ITEM_LEAD_BREAK, 0.6f, 1.2f); - xp(p, getConfig().xpOnRecall); - getPlayer(p).getData().addStat("taming.beast-recall.recalls", 1); - } - - private Tameable findNearestOwnedTameable(Player p, double radius) { - double best = Double.MAX_VALUE; - Tameable out = null; - double minDistSq = getConfig().minRecallDistanceSquared; - for (Entity entity : p.getWorld().getNearbyEntities(p.getLocation(), radius, radius, radius)) { - if (!(entity instanceof Tameable t) || !t.isTamed() || !(t.getOwner() instanceof Player owner) || !owner.getUniqueId().equals(p.getUniqueId())) { - continue; - } - - double d = t.getLocation().distanceSquared(p.getLocation()); - if (d <= minDistSq || d >= best) { - continue; - } - - out = t; - best = d; - } - - return out; - } - - private Location findSafeRecallLocation(Player p) { - Location base = p.getLocation(); - int[][] offsets = { - {0, 0}, {1, 0}, {-1, 0}, {0, 1}, {0, -1}, - {1, 1}, {1, -1}, {-1, 1}, {-1, -1}, - {2, 0}, {-2, 0}, {0, 2}, {0, -2} - }; - - for (int y = 0; y <= 2; y++) { - for (int[] offset : offsets) { - Location candidate = base.clone().add(offset[0], y, offset[1]); - if (isSafeSpot(candidate)) { - return candidate; - } - } - } - - return null; - } - - private boolean isSafeSpot(Location location) { - Block feet = location.getBlock(); - Block head = location.clone().add(0, 1, 0).getBlock(); - Block below = location.clone().add(0, -1, 0).getBlock(); - return feet.isPassable() && head.isPassable() && below.getType().isSolid(); - } - - private double getSearchRadius(int level) { - return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); - } - - private int getCooldownTicks(int level) { - return Math.max(40, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sneak-right-click with a lead to recall your nearest tamed companion.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusBase = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 38; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Recall Distance Squared for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minRecallDistanceSquared = 9.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 420; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 280; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Recall for the Taming Beast Recall adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnRecall = 26; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingDamage.java b/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingDamage.java deleted file mode 100644 index 788fc0253..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingDamage.java +++ /dev/null @@ -1,162 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.taming; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.Attributes; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.World; -import org.bukkit.attribute.AttributeModifier; -import org.bukkit.entity.Player; -import org.bukkit.entity.Tameable; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class TamingDamage extends SimpleAdaptation { - private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-tame-damage-boost".getBytes()); - private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString( "adapt:tame-damage-boost"); - - public TamingDamage() { - super("tame-damage"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("taming.damage.description")); - setDisplayName(Localizer.dLocalize("taming.damage.name")); - setIcon(Material.FLINT); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(6119); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_taming_damage_500") - .title(Localizer.dLocalize("advancement.challenge_taming_damage_500.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_damage_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_taming_damage_5k") - .title(Localizer.dLocalize("advancement.challenge_taming_damage_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_damage_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_taming_damage_500", "taming.damage.pet-kills", 500, 400); - registerMilestone("challenge_taming_damage_5k", "taming.damage.pet-kills", 5000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getDamageBoost(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.damage.lore1")); - } - - private double getDamageBoost(int level) { - return ((getLevelPercent(level) * getConfig().damageFactor) + getConfig().baseDamage); - } - - @Override - public void onTick() { - Map ownerLevels = new HashMap<>(); - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player owner = adaptPlayer.getPlayer(); - ownerLevels.put(owner.getUniqueId(), getLevel(owner)); - } - - for (World world : Bukkit.getServer().getWorlds()) { - Collection tameables = world.getEntitiesByClass(Tameable.class); - for (Tameable tameable : tameables) { - if (tameable.isTamed() && tameable.getOwner() instanceof Player p) { - update(tameable, ownerLevels.getOrDefault(p.getUniqueId(), 0)); - } - } - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent dmgEvent - && dmgEvent.getDamager() instanceof Tameable tam - && tam.isTamed() - && tam.getOwner() instanceof Player p - && hasAdaptation(p)) { - getPlayer(p).getData().addStat("taming.damage.pet-kills", 1); - } - } - - private void update(Tameable j, int level) { - var attribute = Version.get().getAttribute(j, Attributes.GENERIC_ATTACK_DAMAGE); - if (attribute == null) return; - attribute.removeModifier(MODIFIER, MODIFIER_KEY); - - if (level > 0) { - attribute.addModifier(MODIFIER, MODIFIER_KEY, getDamageBoost(level), AttributeModifier.Operation.ADD_SCALAR); - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Increase your tamed animal damage dealt.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Damage for the Taming Damage adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseDamage = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Taming Damage adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageFactor = 0.65; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingHealthBoost.java b/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingHealthBoost.java deleted file mode 100644 index 6d7068d97..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingHealthBoost.java +++ /dev/null @@ -1,146 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.taming; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.Attributes; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.World; -import org.bukkit.attribute.AttributeModifier; -import org.bukkit.entity.Player; -import org.bukkit.entity.Tameable; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class TamingHealthBoost extends SimpleAdaptation { - private static final UUID MODIFIER = UUID.nameUUIDFromBytes("adapt-tame-health-boost".getBytes()); - private static final NamespacedKey MODIFIER_KEY = NamespacedKey.fromString( "adapt:tame-health-boost"); - - public TamingHealthBoost() { - super("tame-health"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("taming.health.description")); - setDisplayName(Localizer.dLocalize("taming.health.name")); - setIcon(Material.COOKED_BEEF); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(4753); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_APPLE) - .key("challenge_taming_health_boost_1728k") - .title(Localizer.dLocalize("advancement.challenge_taming_health_boost_1728k.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_health_boost_1728k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_taming_health_boost_1728k", "taming.health-boost.ticks-active", 1728000, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getHealthBoost(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.health.lore1")); - } - - private double getHealthBoost(int level) { - return ((getLevelPercent(level) * getConfig().healthBoostFactor) + getConfig().healthBoostBase); - } - - @Override - public void onTick() { - Map ownerStates = new HashMap<>(); - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player owner = adaptPlayer.getPlayer(); - ownerStates.put(owner.getUniqueId(), new OwnerState(adaptPlayer, getLevel(owner))); - } - - for (World world : Bukkit.getServer().getWorlds()) { - Collection tameables = world.getEntitiesByClass(Tameable.class); - for (Tameable tameable : tameables) { - if (tameable.isTamed() && tameable.getOwner() instanceof Player p) { - OwnerState state = ownerStates.get(p.getUniqueId()); - int level = state == null ? 0 : state.level(); - update(tameable, level); - if (level > 0 && state != null) { - state.owner().getData().addStat("taming.health-boost.ticks-active", 1); - } - } - } - } - } - - private record OwnerState(AdaptPlayer owner, int level) { - } - - private void update(Tameable j, int level) { - var attribute = Version.get().getAttribute(j, Attributes.GENERIC_MAX_HEALTH); - if (attribute == null) return; - attribute.removeModifier(MODIFIER, MODIFIER_KEY); - - if (level > 0) { - attribute.addModifier(MODIFIER, MODIFIER_KEY, getHealthBoost(level), AttributeModifier.Operation.ADD_SCALAR); - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Increase your tamed animal maximum health.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Health Boost Factor for the Taming Health Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double healthBoostFactor = 2.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Health Boost Base for the Taming Health Boost adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double healthBoostBase = 0.57; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingHealthRegeneration.java b/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingHealthRegeneration.java deleted file mode 100644 index a4454876c..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingHealthRegeneration.java +++ /dev/null @@ -1,158 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.taming; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.Attributes; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.entity.Tameable; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.potion.PotionEffectType; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static org.bukkit.Particle.HEART; - -public class TamingHealthRegeneration extends SimpleAdaptation { - private final Map lastDamage = new HashMap<>(); - - public TamingHealthRegeneration() { - super("tame-health-regeneration"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("taming.regeneration.description")); - setDisplayName(Localizer.dLocalize("taming.regeneration.name")); - setIcon(Material.GOLDEN_APPLE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setInterval(1033); - setCostFactor(getConfig().costFactor); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GLISTERING_MELON_SLICE) - .key("challenge_taming_regen_1k") - .title(Localizer.dLocalize("advancement.challenge_taming_regen_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_regen_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_taming_regen_1k", "taming.health-regen.health-regened", 1000, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRegenSpeed(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.regeneration.lore1")); - } - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof Tameable tam - && tam.getOwner() instanceof Player p - && hasAdaptation(p)) { - if (lastDamage.containsKey(tam.getUniqueId())) { - Adapt.verbose("Tamed Entity " + tam.getUniqueId() + " last damaged " + (M.ms() - lastDamage.get(tam.getUniqueId())) + "ms ago"); - return; - } - var attribute = Version.get().getAttribute(tam, Attributes.GENERIC_MAX_HEALTH); - double mh = attribute == null ? tam.getHealth() : attribute.getValue(); - if (tam.isTamed() && tam.getOwner() instanceof Player && tam.getHealth() < mh) { - Adapt.verbose("Successfully healed tamed entity " + tam.getUniqueId()); - int level = getLevel(p); - if (level > 0) { - Adapt.verbose("[PRE] Current Health: " + tam.getHealth() + " Max Health: " + mh); - tam.addPotionEffect(PotionEffectType.REGENERATION.createEffect(25 * getLevel(p), 3)); - getPlayer(p).getData().addStat("taming.health-regen.health-regened", 1); - - if (areParticlesEnabled()) { - Adapt.verbose("Healing tamed entity " + tam.getUniqueId() + " with particles"); - tam.getWorld().spawnParticle(HEART, tam.getLocation().add(0, 1, 0), 2 * p.getLevel()); - } else { - Adapt.verbose("Healing tamed entity " + tam.getUniqueId() + " without particles"); - } - } - } - lastDamage.put(e.getEntity().getUniqueId(), M.ms()); - } - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDeathEvent e) { - lastDamage.remove(e.getEntity().getUniqueId()); - } - - - private double getRegenSpeed(int level) { - return ((getLevelPercent(level) * (getLevelPercent(level)) * getConfig().regenFactor) + getConfig().regenBase); - } - - @Override - public void onTick() { - lastDamage.entrySet().removeIf(i -> M.ms() - i.getValue() > 8000); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Increase your tamed animal regeneration rate.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Taming Health Regeneration adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Regen Factor for the Taming Health Regeneration adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double regenFactor = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Regen Base for the Taming Health Regeneration adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double regenBase = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingMountedTactics.java b/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingMountedTactics.java deleted file mode 100644 index 1210c51da..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingMountedTactics.java +++ /dev/null @@ -1,304 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.taming; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.VelocitySpeed; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.AbstractHorse; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Pig; -import org.bukkit.entity.Player; -import org.bukkit.entity.Strider; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class TamingMountedTactics extends SimpleAdaptation { - private final Map lastMountedLocation = new HashMap<>(); - - public TamingMountedTactics() { - super("tame-mounted-tactics"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("taming.mounted_tactics.description")); - setDisplayName(Localizer.dLocalize("taming.mounted_tactics.name")); - setIcon(Material.SADDLE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(10); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SADDLE) - .key("challenge_taming_mounted_200") - .title(Localizer.dLocalize("advancement.challenge_taming_mounted_200.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_mounted_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_HORSE_ARMOR) - .key("challenge_taming_mounted_50k") - .title(Localizer.dLocalize("advancement.challenge_taming_mounted_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_mounted_50k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_taming_mounted_200", "taming.mounted-tactics.mounted-kills", 200, 400); - registerMilestone("challenge_taming_mounted_50k", "taming.mounted-tactics.distance", 50000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getMountedDamageBonus(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.mounted_tactics.lore1")); - v.addLore(C.GREEN + "+ " + Form.pc(getMountedDamageReduction(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.mounted_tactics.lore2")); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (!hasAdaptation(p)) { - continue; - } - - int level = getLevel(p); - Entity vehicle = p.getVehicle(); - if (vehicle != null) { - Location last = lastMountedLocation.get(p.getUniqueId()); - Location current = p.getLocation(); - if (last != null && last.getWorld() == current.getWorld()) { - double dist = last.distance(current); - if (dist > 0.1 && dist < 100) { - getPlayer(p).getData().addStat("taming.mounted-tactics.distance", dist); - } - } - lastMountedLocation.put(p.getUniqueId(), current); - } else { - lastMountedLocation.remove(p.getUniqueId()); - } - if (vehicle instanceof AbstractHorse horse) { - if (hasForwardInput(p)) { - applyMountForwardSpeed(horse, p, getHorseTargetSpeed(level)); - } - if (p.isSprinting()) { - Vector push = p.getLocation().getDirection().clone().setY(0).normalize().multiply(getHorsePush(level)); - horse.setVelocity(horse.getVelocity().multiply(0.8).add(push)); - } - } else if (vehicle instanceof Strider strider) { - p.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 40, 0, false, false, true), true); - if (hasForwardInput(p)) { - applyMountForwardSpeed(strider, p, getStriderTargetSpeed(level)); - } - if (strider.getLocation().getBlock().getType() == Material.LAVA || strider.getLocation().clone().subtract(0, 1, 0).getBlock().getType() == Material.LAVA) { - strider.setShivering(false); - } - } else if (vehicle instanceof Pig pig) { - p.addPotionEffect(new PotionEffect(PotionEffectType.RESISTANCE, 25, getPigResistanceAmplifier(level), false, false, true), true); - if (p.isSprinting()) { - Vector forward = p.getLocation().getDirection().clone().setY(0).normalize().multiply(getPigPush(level)); - pig.setVelocity(pig.getVelocity().multiply(0.7).add(forward)); - } - } - } - } - - private void applyMountForwardSpeed(Entity mount, Player rider, double targetSpeed) { - Vector direction = rider.getLocation().getDirection().setY(0); - if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { - return; - } - - direction.normalize(); - Vector velocity = mount.getVelocity(); - Vector horizontal = VelocitySpeed.horizontalOnly(velocity); - Vector targetHorizontal = direction.multiply(Math.max(0, targetSpeed)); - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().mountAccelPerTick)); - nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().mountMaxHorizontalSpeed); - mount.setVelocity(new Vector(nextHorizontal.getX(), velocity.getY(), nextHorizontal.getZ())); - } - - private boolean hasForwardInput(Player p) { - VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); - return input.forward() && !input.backward(); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - if (e.getEntity().getKiller() instanceof Player p - && p.getVehicle() != null - && hasAdaptation(p)) { - getPlayer(p).getData().addStat("taming.mounted-tactics.mounted-kills", 1); - } - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageByEntityEvent e) { - if (e.getDamager() instanceof Player attacker && hasAdaptation(attacker) && attacker.getVehicle() != null) { - if (e.getEntity() instanceof Player victim) { - if (!canPVP(attacker, victim.getLocation())) { - return; - } - } else if (!canPVE(attacker, e.getEntity().getLocation())) { - return; - } - - e.setDamage(e.getDamage() * (1D + getMountedDamageBonus(getLevel(attacker)))); - xp(attacker, e.getDamage() * getConfig().xpPerMountedDamage); - } - - if (e.getEntity() instanceof Player defender && hasAdaptation(defender) && defender.getVehicle() != null) { - e.setDamage(e.getDamage() * (1D - getMountedDamageReduction(getLevel(defender)))); - } - } - - private double getMountedDamageBonus(int level) { - return Math.min(getConfig().maxMountedDamageBonus, getConfig().mountedDamageBonusBase + (getLevelPercent(level) * getConfig().mountedDamageBonusFactor)); - } - - private double getMountedDamageReduction(int level) { - return Math.min(getConfig().maxMountedDamageReduction, getConfig().mountedDamageReductionBase + (getLevelPercent(level) * getConfig().mountedDamageReductionFactor)); - } - - private int getHorseSpeedAmplifier(int level) { - return Math.max(0, (int) Math.round(getConfig().horseSpeedAmplifierBase + (getLevelPercent(level) * getConfig().horseSpeedAmplifierFactor))); - } - - private int getStriderSpeedAmplifier(int level) { - return Math.max(0, (int) Math.round(getConfig().striderSpeedAmplifierBase + (getLevelPercent(level) * getConfig().striderSpeedAmplifierFactor))); - } - - private int getPigResistanceAmplifier(int level) { - return Math.max(0, (int) Math.round(getConfig().pigResistanceAmplifierBase + (getLevelPercent(level) * getConfig().pigResistanceAmplifierFactor))); - } - - private double getHorseTargetSpeed(int level) { - int amplifier = getHorseSpeedAmplifier(level); - double base = Math.max(0, getConfig().horseBaseHorizontalSpeed); - return Math.min(getConfig().mountMaxHorizontalSpeed, base * VelocitySpeed.speedAmplifierScalar(amplifier)); - } - - private double getStriderTargetSpeed(int level) { - int amplifier = getStriderSpeedAmplifier(level); - double base = Math.max(0, getConfig().striderBaseHorizontalSpeed); - return Math.min(getConfig().mountMaxHorizontalSpeed, base * VelocitySpeed.speedAmplifierScalar(amplifier)); - } - - private double getHorsePush(int level) { - return getConfig().horsePushBase + (getLevelPercent(level) * getConfig().horsePushFactor); - } - - private double getPigPush(int level) { - return getConfig().pigPushBase + (getLevelPercent(level) * getConfig().pigPushFactor); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Gain mount-specific combat and control bonuses while riding.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mounted Damage Bonus Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double mountedDamageBonusBase = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mounted Damage Bonus Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double mountedDamageBonusFactor = 0.22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Mounted Damage Bonus for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxMountedDamageBonus = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mounted Damage Reduction Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double mountedDamageReductionBase = 0.06; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Mounted Damage Reduction Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double mountedDamageReductionFactor = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Mounted Damage Reduction for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxMountedDamageReduction = 0.28; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Horse Speed Amplifier Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double horseSpeedAmplifierBase = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Horse Speed Amplifier Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double horseSpeedAmplifierFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Strider Speed Amplifier Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double striderSpeedAmplifierBase = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Strider Speed Amplifier Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double striderSpeedAmplifierFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Pig Resistance Amplifier Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pigResistanceAmplifierBase = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Pig Resistance Amplifier Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pigResistanceAmplifierFactor = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base horizontal speed target used for horse mounted speed scaling.", impact = "Higher values increase steady mounted horse acceleration when moving forward.") - double horseBaseHorizontalSpeed = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base horizontal speed target used for strider mounted speed scaling.", impact = "Higher values increase steady mounted strider acceleration when moving forward.") - double striderBaseHorizontalSpeed = 0.24; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force on mounts.", impact = "Acts as a hard cap to prevent runaway mounted momentum.") - double mountMaxHorizontalSpeed = 0.78; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast mounts accelerate toward the target speed per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") - double mountAccelPerTick = 0.065; - @com.volmit.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") - double fallbackInputVelocityThreshold = 0.0008; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Horse Push Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double horsePushBase = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Horse Push Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double horsePushFactor = 0.16; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Pig Push Base for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pigPushBase = 0.05; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Pig Push Factor for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double pigPushFactor = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Mounted Damage for the Taming Mounted Tactics adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerMountedDamage = 1.5; - - double fallbackInputVelocityThresholdSquared() { - double threshold = Math.max(0, fallbackInputVelocityThreshold); - return threshold * threshold; - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingPackLeaderAura.java b/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingPackLeaderAura.java deleted file mode 100644 index 7be2714a4..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingPackLeaderAura.java +++ /dev/null @@ -1,159 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.taming; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Tameable; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.ArrayList; -import java.util.List; - -public class TamingPackLeaderAura extends SimpleAdaptation { - public TamingPackLeaderAura() { - super("tame-pack-leader-aura"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("taming.pack_leader_aura.description")); - setDisplayName(Localizer.dLocalize("taming.pack_leader_aura.name")); - setIcon(Material.BONE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(30); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_taming_pack_72k") - .title(Localizer.dLocalize("advancement.challenge_taming_pack_72k.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_pack_72k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_taming_pack_72k", "taming.pack-leader.buffed-ticks", 72000, 400); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getRadius(level)) + C.GRAY + " " + Localizer.dLocalize("taming.pack_leader_aura.lore1")); - v.addLore(C.GREEN + "+ " + (1 + getAmplifier(level)) + C.GRAY + " " + Localizer.dLocalize("taming.pack_leader_aura.lore2")); - } - - @Override - public void onTick() { - if (!Bukkit.isPrimaryThread()) { - J.s(this::onTick); - return; - } - - List owners = new ArrayList<>(); - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player owner = adaptPlayer.getPlayer(); - int level = getActiveLevel(owner); - if (level <= 0) { - continue; - } - - double radius = getRadius(level); - owners.add(new OwnerAuraState(adaptPlayer, owner, radius, radius * radius, getAmplifier(level))); - } - - for (OwnerAuraState state : owners) { - Location ownerLocation = state.owner.getLocation(); - for (Entity nearby : state.owner.getWorld().getNearbyEntities(ownerLocation, state.radius, state.radius, state.radius)) { - if (!(nearby instanceof Tameable tameable) || !tameable.isTamed()) { - continue; - } - if (!(tameable.getOwner() instanceof Player owner) || !owner.getUniqueId().equals(state.owner.getUniqueId())) { - continue; - } - if (ownerLocation.distanceSquared(tameable.getLocation()) > state.radiusSquared) { - continue; - } - - tameable.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, getConfig().effectTicks, state.amplifier, false, false)); - tameable.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, getConfig().effectTicks, state.amplifier, false, false)); - state.ownerData.getData().addStat("taming.pack-leader.buffed-ticks", 1); - } - } - } - - private record OwnerAuraState(AdaptPlayer ownerData, Player owner, double radius, double radiusSquared, int amplifier) { - } - - private double getRadius(int level) { - return getConfig().radiusBase + (getLevelPercent(level) * getConfig().radiusFactor); - } - - private int getAmplifier(int level) { - return Math.max(0, (int) Math.floor(getLevelPercent(level) * getConfig().maxAmplifier)); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Nearby tamed companions gain speed and regeneration near their owner.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Base for the Taming Pack Leader Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusBase = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Radius Factor for the Taming Pack Leader Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double radiusFactor = 14; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Amplifier for the Taming Pack Leader Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxAmplifier = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effect Ticks for the Taming Pack Leader Aura adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int effectTicks = 80; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingSharedPain.java b/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingSharedPain.java deleted file mode 100644 index 1c887df6c..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/taming/TamingSharedPain.java +++ /dev/null @@ -1,166 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.taming; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.entity.Tameable; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageEvent; - -public class TamingSharedPain extends SimpleAdaptation { - public TamingSharedPain() { - super("tame-shared-pain"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("taming.shared_pain.description")); - setDisplayName(Localizer.dLocalize("taming.shared_pain.name")); - setIcon(Material.POPPY); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1700); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHIELD) - .key("challenge_taming_shared_500") - .title(Localizer.dLocalize("advancement.challenge_taming_shared_500.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_shared_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TOTEM_OF_UNDYING) - .key("challenge_taming_shared_5k") - .title(Localizer.dLocalize("advancement.challenge_taming_shared_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_shared_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_taming_shared_500", "taming.shared-pain.damage-taken", 500, 400); - registerMilestone("challenge_taming_shared_5k", "taming.shared-pain.damage-taken", 5000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getRedirectPercent(level), 0) + C.GRAY + " " + Localizer.dLocalize("taming.shared_pain.lore1")); - v.addLore(C.YELLOW + "* " + Form.f(getOwnerHealthFloor(level), 1) + C.GRAY + " " + Localizer.dLocalize("taming.shared_pain.lore2")); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Tameable tameable) || !tameable.isTamed() || !(tameable.getOwner() instanceof Player owner) || !hasAdaptation(owner)) { - return; - } - - if (!canPVE(owner, tameable.getLocation())) { - return; - } - - int level = getLevel(owner); - double redirect = e.getDamage() * getRedirectPercent(level); - if (redirect <= 0) { - return; - } - - double floor = getOwnerHealthFloor(level); - double allowed = Math.max(0, owner.getHealth() - floor); - redirect = Math.min(redirect, allowed); - if (redirect <= 0.01) { - return; - } - - e.setDamage(Math.max(0, e.getDamage() - redirect)); - if (e.getDamage() <= 0.01) { - e.setCancelled(true); - } - - owner.damage(redirect); - getPlayer(owner).getData().addStat("taming.shared-pain.damage-taken", redirect); - SoundPlayer sp = SoundPlayer.of(owner.getWorld()); - sp.play(owner.getLocation(), Sound.BLOCK_AMETHYST_CLUSTER_HIT, 0.65f, 0.7f); - sp.play(tameable.getLocation(), Sound.ENTITY_WOLF_WHINE, 0.55f, 1.2f); - xp(owner, redirect * getConfig().xpPerRedirectedDamage); - } - - private double getRedirectPercent(int level) { - return Math.min(getConfig().maxRedirectPercent, getConfig().redirectPercentBase + (getLevelPercent(level) * getConfig().redirectPercentFactor)); - } - - private double getOwnerHealthFloor(int level) { - return Math.max(1.0, getConfig().ownerHealthFloorBase + (getLevelPercent(level) * getConfig().ownerHealthFloorFactor)); - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Redirect part of your pet's incoming damage to you, preserving companion survivability.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Redirect Percent Base for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double redirectPercentBase = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Redirect Percent Factor for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double redirectPercentFactor = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Redirect Percent for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxRedirectPercent = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Owner Health Floor Base for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double ownerHealthFloorBase = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Owner Health Floor Factor for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double ownerHealthFloorFactor = 4.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Redirected Damage for the Taming Shared Pain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerRedirectedDamage = 2.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulBloodPact.java b/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulBloodPact.java deleted file mode 100644 index ebcc19b60..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulBloodPact.java +++ /dev/null @@ -1,436 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.tragoul; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.VelocitySpeed; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class TragoulBloodPact extends SimpleAdaptation { - private static final PotionEffectType[] EFFECT_POOL = { - PotionEffectType.SPEED, - PotionEffectType.REGENERATION, - PotionEffectType.RESISTANCE, - PotionEffectType.FIRE_RESISTANCE, - PotionEffectType.ABSORPTION, - PotionEffectType.JUMP_BOOST, - PotionEffectType.NIGHT_VISION - }; - - private final Map procCooldowns = new HashMap<>(); - private final Map lowHealthProcs = new HashMap<>(); - private final Map speedBursts = new HashMap<>(); - - public TragoulBloodPact() { - super("tragoul-blood-pact"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("tragoul.blood_pact.description")); - setDisplayName(Localizer.dLocalize("tragoul.blood_pact.name")); - setIcon(Material.NETHER_WART); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(20); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.REDSTONE) - .key("challenge_tragoul_pact_200") - .title(Localizer.dLocalize("advancement.challenge_tragoul_pact_200.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_pact_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_tragoul_pact_kills_500") - .title(Localizer.dLocalize("advancement.challenge_tragoul_pact_kills_500.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_pact_kills_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_tragoul_pact_200", "tragoul.blood-pact.health-sacrificed", 200, 400); - registerMilestone("challenge_tragoul_pact_kills_500", "tragoul.blood-pact.empowered-kills", 500, 1000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.REDSTONE) - .key("challenge_tragoul_pact_all_in") - .title(Localizer.dLocalize("advancement.challenge_tragoul_pact_all_in.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_pact_all_in.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getProcChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("tragoul.blood_pact.lore1")); - v.addLore(C.GREEN + "+ " + Form.duration(getEffectDurationTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.blood_pact.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getProcCooldownMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.blood_pact.lore3")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - procCooldowns.remove(e.getPlayer().getUniqueId()); - lowHealthProcs.remove(e.getPlayer().getUniqueId()); - speedBursts.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerDeathEvent e) { - speedBursts.remove(e.getEntity().getUniqueId()); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityDamageEvent e) { - if (!(e.getEntity() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - int level = getLevel(p); - if (level <= 0 || e.getFinalDamage() < getMinTriggerDamage()) { - return; - } - - long now = System.currentTimeMillis(); - long until = procCooldowns.getOrDefault(p.getUniqueId(), 0L); - if (now < until) { - return; - } - - if (ThreadLocalRandom.current().nextDouble() > getProcChance(level)) { - return; - } - - procCooldowns.put(p.getUniqueId(), now + getProcCooldownMillis(level)); - getPlayer(p).getData().addStat("tragoul.blood-pact.health-sacrificed", (int) e.getFinalDamage()); - if (p.getHealth() - e.getFinalDamage() <= 6.0) { - lowHealthProcs.put(p.getUniqueId(), true); - } - applyRandomBuffs(p, level, e.getFinalDamage()); - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.CRIMSON_SPORE, p.getLocation().add(0, 1.0, 0), 22, 0.28, 0.42, 0.28, 0.02); - } - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.62f, 1.25f); - xp(p, getConfig().xpPerProc); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent dmgEvent) { - if (dmgEvent.getDamager() instanceof Player p && hasAdaptation(p)) { - if (p.hasPotionEffect(PotionEffectType.ABSORPTION) || p.hasPotionEffect(PotionEffectType.RESISTANCE)) { - getPlayer(p).getData().addStat("tragoul.blood-pact.empowered-kills", 1); - if (lowHealthProcs.getOrDefault(p.getUniqueId(), false) && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_tragoul_pact_all_in")) { - getPlayer(p).getAdvancementHandler().grant("challenge_tragoul_pact_all_in"); - } - } - } - } - } - - private void applyRandomBuffs(Player p, int level, double takenDamage) { - int count = getBuffCount(level); - if (takenDamage >= (getMinTriggerDamage() * 1.6)) { - count++; - } - if (ThreadLocalRandom.current().nextDouble() <= getBonusBuffChance(level)) { - count++; - } - - List pool = new ArrayList<>(List.of(EFFECT_POOL)); - Collections.shuffle(pool); - count = Math.min(count, pool.size()); - - int duration = getEffectDurationTicks(level); - for (int i = 0; i < count; i++) { - PotionEffectType type = pool.get(i); - int amplifier = getEffectAmplifier(type, level); - int d = type == PotionEffectType.ABSORPTION ? Math.max(40, duration - 20) : duration; - if (type == PotionEffectType.SPEED) { - grantSpeedBurst(p, amplifier, d); - continue; - } - p.addPotionEffect(new PotionEffect(type, d, amplifier, false, true, true), true); - } - } - - private void grantSpeedBurst(Player p, int amplifier, int durationTicks) { - if (durationTicks <= 0) { - return; - } - - UUID id = p.getUniqueId(); - long now = System.currentTimeMillis(); - long durationMs = Math.max(50L, durationTicks * 50L); - SpeedBurst burst = speedBursts.get(id); - if (burst != null && burst.expiresAt > now) { - burst.expiresAt += durationMs; - burst.amplifier = Math.max(burst.amplifier, amplifier); - return; - } - - speedBursts.put(id, new SpeedBurst(now + durationMs, amplifier)); - } - - private double getProcChance(int level) { - return Math.min(getConfig().maxProcChance, - Math.max(0, getConfig().procChanceBase + (getLevelPercent(level) * getConfig().procChanceFactor))); - } - - private long getProcCooldownMillis(int level) { - return Math.max(500L, (long) Math.round(getConfig().procCooldownMillisBase - (getLevelPercent(level) * getConfig().procCooldownMillisFactor))); - } - - private int getEffectDurationTicks(int level) { - return Math.max(40, (int) Math.round(getConfig().effectDurationTicksBase + (getLevelPercent(level) * getConfig().effectDurationTicksFactor))); - } - - private int getBuffCount(int level) { - return Math.max(1, (int) Math.round(getConfig().buffCountBase + (getLevelPercent(level) * getConfig().buffCountFactor))); - } - - private double getBonusBuffChance(int level) { - return Math.min(0.9, Math.max(0, getConfig().bonusBuffChanceBase + (getLevelPercent(level) * getConfig().bonusBuffChanceFactor))); - } - - private int getEffectAmplifier(PotionEffectType type, int level) { - double progress = getLevelPercent(level); - if (type == PotionEffectType.ABSORPTION || type == PotionEffectType.RESISTANCE || type == PotionEffectType.REGENERATION) { - return progress >= 0.85 ? 1 : 0; - } - return progress >= 0.7 ? 1 : 0; - } - - private double getMinTriggerDamage() { - return Math.max(1, getConfig().minDamageTriggerHearts * 2D); - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - procCooldowns.entrySet().removeIf(i -> i.getValue() <= now); - applySpeedBursts(now); - } - - private void applySpeedBursts(long now) { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - UUID id = p.getUniqueId(); - SpeedBurst burst = speedBursts.get(id); - if (burst == null) { - continue; - } - - if (burst.expiresAt <= now) { - invalidateSpeedBurst(p, burst, false); - speedBursts.remove(id); - continue; - } - - if (!isVelocityEligible(p)) { - invalidateSpeedBurst(p, burst, true); - continue; - } - - VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); - if (!input.hasHorizontal()) { - brakeSpeedBurst(p, burst); - continue; - } - - applySpeedBurst(p, burst, input); - } - } - - private void applySpeedBurst(Player p, SpeedBurst burst, VelocitySpeed.InputSnapshot input) { - Vector direction = VelocitySpeed.resolveHorizontalDirection(p, input); - if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { - brakeSpeedBurst(p, burst); - return; - } - - double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, - Math.max(0, getConfig().baseHorizontalSpeed * VelocitySpeed.speedAmplifierScalar(burst.amplifier))); - Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); - Vector targetHorizontal = direction.multiply(targetSpeed); - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); - nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - burst.boosting = true; - } - - private void brakeSpeedBurst(Player p, SpeedBurst burst) { - if (!burst.boosting) { - return; - } - - Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); - double stopThreshold = Math.max(0, getConfig().stopThreshold); - if (horizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - burst.boosting = false; - return; - } - - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); - if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - burst.boosting = false; - return; - } - - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - } - - private void invalidateSpeedBurst(Player p, SpeedBurst burst, boolean invalidState) { - if (!burst.boosting) { - return; - } - - if (invalidState && getConfig().hardStopOnInvalidState) { - VelocitySpeed.hardStopHorizontal(p); - } - - burst.boosting = false; - } - - private boolean isVelocityEligible(Player p) { - GameMode mode = p.getGameMode(); - if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { - return false; - } - - return !p.isDead() && !p.isFlying() && !p.isGliding() && !p.isSwimming() && p.getVehicle() == null; - } - - private static class SpeedBurst { - private long expiresAt; - private int amplifier; - private boolean boosting; - - private SpeedBurst(long expiresAt, int amplifier) { - this.expiresAt = expiresAt; - this.amplifier = amplifier; - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Taking at least 2 hearts of damage can trigger temporary beneficial effects.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.62; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Damage Trigger Hearts for the Tragoul Blood Pact adaptation.", impact = "Minimum damage taken in hearts required before the proc roll happens.") - double minDamageTriggerHearts = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Proc Chance Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double procChanceBase = 0.12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Proc Chance Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double procChanceFactor = 0.38; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Proc Chance for the Tragoul Blood Pact adaptation.", impact = "Caps chance at the requested maximum.") - double maxProcChance = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Proc Cooldown Millis Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double procCooldownMillisBase = 18000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Proc Cooldown Millis Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double procCooldownMillisFactor = 12000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effect Duration Ticks Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double effectDurationTicksBase = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Effect Duration Ticks Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double effectDurationTicksFactor = 150; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Buff Count Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double buffCountBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Buff Count Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double buffCountFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Buff Chance Base for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusBuffChanceBase = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Buff Chance Factor for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusBuffChanceFactor = 0.34; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP Per Proc for the Tragoul Blood Pact adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerProc = 24; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for blood pact speed bursts.", impact = "Higher values increase movement speed when a speed burst is active.") - double baseHorizontalSpeed = 0.13; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent runaway momentum.") - double maxHorizontalSpeed = 0.33; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the burst target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") - double accelPerTick = 0.045; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity decays when movement input is released.", impact = "Higher values reduce carry momentum more aggressively.") - double brakePerTick = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny motion longer.") - double stopThreshold = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "If true, burst velocity is force-cleared when entering invalid states.", impact = "Prevents retained speed from skipped state transitions.") - boolean hardStopOnInvalidState = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") - double fallbackInputVelocityThreshold = 0.0008; - - double fallbackInputVelocityThresholdSquared() { - double threshold = Math.max(0, fallbackInputVelocityThreshold); - return threshold * threshold; - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulBoneHarvest.java b/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulBoneHarvest.java deleted file mode 100644 index d55582d1b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulBoneHarvest.java +++ /dev/null @@ -1,415 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.tragoul; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.VelocitySpeed; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.EntityPickupItemEvent; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; - -public class TragoulBoneHarvest extends SimpleAdaptation { - private static final PotionEffectType[] BONE_EFFECT_POOL = { - PotionEffectType.SPEED, - PotionEffectType.REGENERATION, - PotionEffectType.RESISTANCE, - PotionEffectType.FIRE_RESISTANCE, - PotionEffectType.ABSORPTION, - PotionEffectType.JUMP_BOOST, - PotionEffectType.NIGHT_VISION - }; - - private final Set bloodGlobes = new HashSet<>(); - private final Set boneGlobes = new HashSet<>(); - private final Map speedBursts = new HashMap<>(); - - public TragoulBoneHarvest() { - super("tragoul-bone-harvest"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("tragoul.bone_harvest.description")); - setDisplayName(Localizer.dLocalize("tragoul.bone_harvest.name")); - setIcon(Material.BONE_BLOCK); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(2000); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_tragoul_bone_500") - .title(Localizer.dLocalize("advancement.challenge_tragoul_bone_500.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_bone_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BONE_BLOCK) - .key("challenge_tragoul_bone_5k") - .title(Localizer.dLocalize("advancement.challenge_tragoul_bone_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_bone_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_tragoul_bone_500", "tragoul.bone-harvest.orbs-collected", 500, 300); - registerMilestone("challenge_tragoul_bone_5k", "tragoul.bone-harvest.orbs-collected", 5000, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getGlobeChance(level), 0) + C.GRAY + " " + Localizer.dLocalize("tragoul.bone_harvest.lore1")); - v.addLore(C.GREEN + "+ " + Form.duration(getGlobeLifetimeTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("tragoul.bone_harvest.lore2")); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - Player killer = e.getEntity().getKiller(); - if (killer == null || !hasAdaptation(killer) || !canPVE(killer, e.getEntity().getLocation())) { - return; - } - - int level = getLevel(killer); - ThreadLocalRandom random = ThreadLocalRandom.current(); - if (random.nextDouble() > getGlobeChance(level)) { - return; - } - - spawnGlobe(killer, e, random.nextBoolean(), level); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void on(EntityPickupItemEvent e) { - if (!(e.getEntity() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - UUID id = e.getItem().getUniqueId(); - boolean blood = bloodGlobes.contains(id); - boolean bone = boneGlobes.contains(id); - if (!blood && !bone) { - return; - } - - e.setCancelled(true); - e.getItem().remove(); - bloodGlobes.remove(id); - boneGlobes.remove(id); - applyBuff(p, blood, getLevel(p)); - getPlayer(p).getData().addStat("tragoul.bone-harvest.orbs-collected", 1); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - speedBursts.remove(e.getPlayer().getUniqueId()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerDeathEvent e) { - speedBursts.remove(e.getEntity().getUniqueId()); - } - - private void spawnGlobe(Player owner, EntityDeathEvent e, boolean blood, int level) { - ItemStack item = new ItemStack(blood ? Material.MAGMA_CREAM : Material.SNOWBALL); - ItemMeta meta = item.getItemMeta(); - if (meta != null) { - meta.setDisplayName((blood ? C.RED : C.WHITE) + (blood ? "Blood Globe" : "Bone Globe")); - item.setItemMeta(meta); - } - - Item dropped = e.getEntity().getWorld().dropItemNaturally(e.getEntity().getLocation().add(0, 0.35, 0), item); - dropped.setPickupDelay(10); - if (blood) { - bloodGlobes.add(dropped.getUniqueId()); - } else { - boneGlobes.add(dropped.getUniqueId()); - } - - int life = getGlobeLifetimeTicks(level); - J.s(() -> { - bloodGlobes.remove(dropped.getUniqueId()); - boneGlobes.remove(dropped.getUniqueId()); - if (dropped.isValid()) { - dropped.remove(); - } - }, life); - xp(owner, getConfig().xpPerGlobeSpawned); - } - - private void applyBuff(Player p, boolean blood, int level) { - if (blood) { - p.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, getConfig().bloodBuffTicks, getConfig().bloodBuffAmplifier, false, true, true), true); - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 0.7f, 1.4f); - } else { - applyRandomBoneBuffs(p, level); - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.ENTITY_SKELETON_HURT, 0.7f, 1.2f); - } - } - - private void applyRandomBoneBuffs(Player p, int level) { - int buffs = Math.max(1, (int) Math.round(getConfig().boneBuffCountBase + (getLevelPercent(level) * getConfig().boneBuffCountFactor))); - List pool = new ArrayList<>(List.of(BONE_EFFECT_POOL)); - Collections.shuffle(pool); - buffs = Math.min(pool.size(), buffs); - - int duration = getConfig().boneBuffTicks; - int amp = Math.max(0, getConfig().boneBuffAmplifier); - for (int i = 0; i < buffs; i++) { - PotionEffectType type = pool.get(i); - int a = type == PotionEffectType.ABSORPTION && getLevelPercent(level) >= 0.75 ? amp + 1 : amp; - if (type == PotionEffectType.SPEED) { - grantSpeedBurst(p, a, duration); - continue; - } - p.addPotionEffect(new PotionEffect(type, duration, a, false, true, true), true); - } - } - - private void grantSpeedBurst(Player p, int amplifier, int durationTicks) { - if (durationTicks <= 0) { - return; - } - - UUID id = p.getUniqueId(); - long now = System.currentTimeMillis(); - long durationMs = Math.max(50L, durationTicks * 50L); - SpeedBurst burst = speedBursts.get(id); - if (burst != null && burst.expiresAt > now) { - burst.expiresAt += durationMs; - burst.amplifier = Math.max(burst.amplifier, amplifier); - return; - } - - speedBursts.put(id, new SpeedBurst(now + durationMs, amplifier)); - } - - private double getGlobeChance(int level) { - return Math.min(getConfig().maxGlobeChance, getConfig().globeChanceBase + (getLevelPercent(level) * getConfig().globeChanceFactor)); - } - - private int getGlobeLifetimeTicks(int level) { - return Math.max(20, (int) Math.round(getConfig().globeLifetimeTicksBase + (getLevelPercent(level) * getConfig().globeLifetimeTicksFactor))); - } - - @Override - public void onTick() { - long now = System.currentTimeMillis(); - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - UUID id = p.getUniqueId(); - SpeedBurst burst = speedBursts.get(id); - if (burst == null) { - continue; - } - - if (burst.expiresAt <= now) { - invalidateSpeedBurst(p, burst, false); - speedBursts.remove(id); - continue; - } - - if (!isVelocityEligible(p)) { - invalidateSpeedBurst(p, burst, true); - continue; - } - - VelocitySpeed.InputSnapshot input = VelocitySpeed.readInput(p, getConfig().fallbackInputVelocityThresholdSquared()); - if (!input.hasHorizontal()) { - brakeSpeedBurst(p, burst); - continue; - } - - applySpeedBurst(p, burst, input); - } - } - - private void applySpeedBurst(Player p, SpeedBurst burst, VelocitySpeed.InputSnapshot input) { - Vector direction = VelocitySpeed.resolveHorizontalDirection(p, input); - if (direction.lengthSquared() <= VelocitySpeed.EPSILON) { - brakeSpeedBurst(p, burst); - return; - } - - double targetSpeed = Math.min(getConfig().maxHorizontalSpeed, - Math.max(0, getConfig().baseHorizontalSpeed * VelocitySpeed.speedAmplifierScalar(burst.amplifier))); - Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); - Vector targetHorizontal = direction.multiply(targetSpeed); - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, targetHorizontal, Math.max(0, getConfig().accelPerTick)); - nextHorizontal = VelocitySpeed.clampHorizontal(nextHorizontal, getConfig().maxHorizontalSpeed); - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - burst.boosting = true; - } - - private void brakeSpeedBurst(Player p, SpeedBurst burst) { - if (!burst.boosting) { - return; - } - - Vector horizontal = VelocitySpeed.horizontalOnly(p.getVelocity()); - double stopThreshold = Math.max(0, getConfig().stopThreshold); - if (horizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - burst.boosting = false; - return; - } - - Vector nextHorizontal = VelocitySpeed.moveTowards(horizontal, new Vector(), Math.max(0, getConfig().brakePerTick)); - if (nextHorizontal.lengthSquared() <= stopThreshold * stopThreshold) { - VelocitySpeed.hardStopHorizontal(p); - burst.boosting = false; - return; - } - - VelocitySpeed.setHorizontalVelocity(p, nextHorizontal); - } - - private void invalidateSpeedBurst(Player p, SpeedBurst burst, boolean invalidState) { - if (!burst.boosting) { - return; - } - - if (invalidState && getConfig().hardStopOnInvalidState) { - VelocitySpeed.hardStopHorizontal(p); - } - - burst.boosting = false; - } - - private boolean isVelocityEligible(Player p) { - GameMode mode = p.getGameMode(); - if (mode != GameMode.SURVIVAL && mode != GameMode.ADVENTURE) { - return false; - } - - return !p.isDead() && !p.isFlying() && !p.isGliding() && !p.isSwimming() && p.getVehicle() == null; - } - - private static class SpeedBurst { - private long expiresAt; - private int amplifier; - private boolean boosting; - - private SpeedBurst(long expiresAt, int amplifier) { - this.expiresAt = expiresAt; - this.amplifier = amplifier; - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Kills can spawn temporary blood and bone globes that grant short buffs when picked up.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Globe Chance Base for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double globeChanceBase = 0.16; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Globe Chance Factor for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double globeChanceFactor = 0.42; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Globe Chance for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxGlobeChance = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Globe Lifetime Ticks Base for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double globeLifetimeTicksBase = 120; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Globe Lifetime Ticks Factor for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double globeLifetimeTicksFactor = 220; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Blood Buff Ticks for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int bloodBuffTicks = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Blood Buff Amplifier for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int bloodBuffAmplifier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bone Buff Ticks for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int boneBuffTicks = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bone Buff Amplifier for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int boneBuffAmplifier = 0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bone Buff Count Base for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double boneBuffCountBase = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bone Buff Count Factor for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double boneBuffCountFactor = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Globe Spawned for the Tragoul Bone Harvest adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerGlobeSpawned = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base horizontal speed used for bone-harvest speed bursts.", impact = "Higher values increase movement speed when a speed burst is active.") - double baseHorizontalSpeed = 0.13; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum horizontal speed this adaptation can force.", impact = "Acts as a hard cap to prevent runaway momentum.") - double maxHorizontalSpeed = 0.33; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity accelerates toward the burst target per tick.", impact = "Higher values accelerate faster; lower values feel smoother.") - double accelPerTick = 0.045; - @com.volmit.adapt.util.config.ConfigDoc(value = "How fast velocity decays when movement input is released.", impact = "Higher values reduce carry momentum more aggressively.") - double brakePerTick = 0.08; - @com.volmit.adapt.util.config.ConfigDoc(value = "Horizontal velocity threshold considered fully stopped.", impact = "Higher values stop sooner; lower values preserve tiny motion longer.") - double stopThreshold = 0.01; - @com.volmit.adapt.util.config.ConfigDoc(value = "If true, burst velocity is force-cleared when entering invalid states.", impact = "Prevents retained speed from skipped state transitions.") - boolean hardStopOnInvalidState = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Fallback movement threshold used when direct input API is unavailable.", impact = "Only used on runtimes without Player input access.") - double fallbackInputVelocityThreshold = 0.0008; - - double fallbackInputVelocityThresholdSquared() { - double threshold = Math.max(0, fallbackInputVelocityThreshold); - return threshold * threshold; - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulGlobe.java b/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulGlobe.java deleted file mode 100644 index c06fb0cf0..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulGlobe.java +++ /dev/null @@ -1,174 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.tragoul; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; - -import java.util.HashMap; -import java.util.Map; - -public class TragoulGlobe extends SimpleAdaptation { - private final Map cooldowns; - - public TragoulGlobe() { - super("tragoul-globe"); - registerConfiguration(TragoulGlobe.Config.class); - setDescription(Localizer.dLocalize("tragoul.globe.description")); - setDisplayName(Localizer.dLocalize("tragoul.globe.name")); - setIcon(Material.CRYING_OBSIDIAN); - setInterval(25000); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GLASS) - .key("challenge_tragoul_globe_1k") - .title(Localizer.dLocalize("advancement.challenge_tragoul_globe_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_globe_1k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_tragoul_globe_1k", "tragoul.globe.mobs-shared-with", 1000, 400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GLASS) - .key("challenge_tragoul_globe_5") - .title(Localizer.dLocalize("advancement.challenge_tragoul_globe_5.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_globe_5.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("tragoul.globe.lore1")); - v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.globe.lore2") + ((getConfig().rangePerLevel * level) + getConfig().initalRange)); - v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.globe.lore3") + (getConfig().bonusDamagePerLevel * level)); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - Long cooldownTime = cooldowns.get(p); - if (cooldownTime != null && cooldownTime + (1000 * getConfig().cooldown) > System.currentTimeMillis()) { - return; - } - - cooldowns.put(p, System.currentTimeMillis()); - double range = (getConfig().rangePerLevel * getLevel(p)) + getConfig().initalRange; - - int entitiesCount = 0; - for (Entity entity : p.getNearbyEntities(range, range, range)) { - if (entity instanceof LivingEntity && !entity.equals(p)) { - entitiesCount++; - } - } - - if (entitiesCount <= 1) { - return; - } - - double damagePerEntity = e.getDamage() / entitiesCount + (getConfig().bonusDamagePerLevel * getLevel(p)); - e.setDamage(damagePerEntity); - - int mobsSharedWith = 0; - for (Entity entity : p.getNearbyEntities(range, range, range)) { - if (entity instanceof LivingEntity && !entity.equals(p)) { - ((LivingEntity) entity).damage(damagePerEntity, p); - mobsSharedWith++; - } - } - - getPlayer(p).getData().addStat("tragoul.globe.mobs-shared-with", mobsSharedWith); - if (mobsSharedWith >= 5 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_tragoul_globe_5")) { - getPlayer(p).getAdvancementHandler().grant("challenge_tragoul_globe_5"); - } - - if (areParticlesEnabled()) { - J.s(() -> vfxFastSphere(p.getLocation(), range, Color.BLACK, 400)); - } - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - - @NoArgsConstructor - @ConfigDescription("Spread your damage among all nearby enemies.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Tragoul Globe adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown for the Tragoul Globe adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldown = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Range Per Level for the Tragoul Globe adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double rangePerLevel = 3.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Inital Range for the Tragoul Globe adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double initalRange = 5.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Bonus Damage Per Level for the Tragoul Globe adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bonusDamagePerLevel = 1; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulHealing.java b/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulHealing.java deleted file mode 100644 index d85c1157b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulHealing.java +++ /dev/null @@ -1,169 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.tragoul; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import com.volmit.adapt.util.reflect.registries.Attributes; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageByEntityEvent; - -import java.util.HashMap; -import java.util.Map; - - -public class TragoulHealing extends SimpleAdaptation { - private final Map cooldowns; - private final Map healingWindow; - - public TragoulHealing() { - super("tragoul-healing"); - registerConfiguration(TragoulHealing.Config.class); - setDescription(Localizer.dLocalize("tragoul.healing.description")); - setDisplayName(Localizer.dLocalize("tragoul.healing.name")); - setIcon(Material.GLISTERING_MELON_SLICE); - setInterval(25000); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - cooldowns = new HashMap<>(); - healingWindow = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.REDSTONE) - .key("challenge_tragoul_healing_500") - .title(Localizer.dLocalize("advancement.challenge_tragoul_healing_500.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_healing_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.RED_DYE) - .key("challenge_tragoul_healing_10k") - .title(Localizer.dLocalize("advancement.challenge_tragoul_healing_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_healing_10k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_tragoul_healing_500", "tragoul.healing.health-stolen", 500, 400); - registerMilestone("challenge_tragoul_healing_10k", "tragoul.healing.health-stolen", 10000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("tragoul.healing.lore1")); - v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.healing.lore2")); - v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.healing.lore3") + (getConfig().minHealPercent + (getConfig().maxHealPercent - getConfig().minHealPercent) * (level - 1) / (getConfig().maxLevel - 1)) + "%"); - } - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.getDamager() instanceof Player p && hasAdaptation(p)) { - if (isOnCooldown(p)) { - return; - } - - if (!healingWindow.containsKey(p)) { - Adapt.verbose("Starting healing window for " + p.getName()); - startHealingWindow(p); - } - - if (areParticlesEnabled()) { - vfxParticleLine(p.getLocation(), e.getEntity().getLocation(), 25, Particle.WHITE_ASH); - } - - double healPercentage = getConfig().minHealPercent + (getConfig().maxHealPercent - getConfig().minHealPercent) * (getLevel(p) - 1) / (getConfig().maxLevel - 1); - double healAmount = e.getDamage() * healPercentage; - Adapt.verbose("Healing " + p.getName() + " for " + healAmount + " (" + healPercentage * 100 + "% of " + e.getDamage() + " damage)"); - var attribute = Version.get().getAttribute(p, Attributes.GENERIC_MAX_HEALTH); - p.setHealth(Math.min(attribute == null ? p.getHealth() : attribute.getValue(), p.getHealth() + healAmount)); - getPlayer(p).getData().addStat("tragoul.healing.health-stolen", (int) healAmount); - - } - } - - private boolean isOnCooldown(Player p) { - Long cooldown = cooldowns.get(p); - return cooldown != null && cooldown > System.currentTimeMillis(); - } - - private void startHealingWindow(Player p) { - long currentTime = System.currentTimeMillis(); - healingWindow.put(p, currentTime + getConfig().windowDuration); - Bukkit.getScheduler().runTaskLater(Adapt.instance, () -> { - healingWindow.remove(p); - cooldowns.put(p, currentTime + getConfig().windowDuration + getConfig().cooldownDuration); - }, getConfig().windowDuration / 50); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Regain health based on the damage you deal.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Tragoul Healing adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Min Heal Percent for the Tragoul Healing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minHealPercent = 0.10; // 0.10% - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Heal Percent for the Tragoul Healing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxHealPercent = 0.45; // 0.45% - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Duration for the Tragoul Healing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int cooldownDuration = 1000; // 1 second - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Window Duration for the Tragoul Healing adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int windowDuration = 3000; // 3 seconds - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulLance.java b/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulLance.java deleted file mode 100644 index f9e58e38a..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulLance.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.volmit.adapt.content.adaptation.tragoul;/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; - -import java.util.HashMap; -import java.util.Map; - -public class TragoulLance extends SimpleAdaptation { - private final Map cooldowns; - - public TragoulLance() { - super("tragoul-lance"); - registerConfiguration(TragoulLance.Config.class); - setDescription(Localizer.dLocalize("tragoul.lance.description")); - setDisplayName(Localizer.dLocalize("tragoul.lance.name")); - setIcon(Material.TRIDENT); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_tragoul_lance_200") - .title(Localizer.dLocalize("advancement.challenge_tragoul_lance_200.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_lance_200.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_tragoul_lance_kills_100") - .title(Localizer.dLocalize("advancement.challenge_tragoul_lance_kills_100.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_lance_kills_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_tragoul_lance_200", "tragoul.lance.lances-spawned", 200, 400); - registerMilestone("challenge_tragoul_lance_kills_100", "tragoul.lance.lance-kills", 100, 1000); - } - - - @EventHandler (priority = EventPriority.LOWEST) - public void onEntityDeath(EntityDeathEvent event) { - if (event.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent e) { - if (e.getDamager() instanceof Player p && hasAdaptation(p)) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + 5000 > System.currentTimeMillis()) - return; - - cooldowns.put(p, System.currentTimeMillis()); - int level = getLevel(p); - double baseSeekerRange = 5 + 4 * level; - double damageDealt = e.getDamage(); - double seekerDamage = getConfig().seekerDamageMultiplier * damageDealt; - - triggerSeeker(p, event.getEntity(), seekerDamage, level, baseSeekerRange); - getPlayer(p).getData().addStat("tragoul.lance.lance-kills", 1); - } - } - } - - private void triggerSeeker(Player p, Entity origin, double damage, int remainingSeekers, double range) { - if (remainingSeekers <= 0) { - return; - } - - LivingEntity nearest = null; - double minDistance = range; - - for (Entity e : origin.getNearbyEntities(range, range, range)) { - if (e instanceof LivingEntity le && le != p) { - double distance = origin.getLocation().distance(le.getLocation()); - if (distance < minDistance) { - nearest = le; - minDistance = distance; - } - } - } - - if (nearest != null) { - getPlayer(p).getData().addStat("tragoul.lance.lances-spawned", 1); - vfxMovingSphere(origin.getLocation(), nearest.getLocation(), getConfig().seekerDelay, Color.MAROON, 0.25, 4); - double seekerDamage = getConfig().seekerDamageMultiplier * damage; - double selfDamage = getConfig().selfDamageMultiplier * seekerDamage; - Adapt.verbose("Seeker damage: " + seekerDamage + " Self damage: " + selfDamage); - - p.damage(selfDamage, p); - - LivingEntity finalNearest = nearest; - Bukkit.getScheduler().runTaskLater(Adapt.instance, () -> { - double remainingHealth = finalNearest.getHealth() - damage; - finalNearest.damage(damage, p); - if (remainingHealth <= 0) { - triggerSeeker(p, finalNearest, damage * 0.5, remainingSeekers - 1, range); - } - }, getConfig().seekerDelay); - } - } - - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + Localizer.dLocalize("tragoul.lance.lore1")); - v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.lance.lore2") ); - v.addLore(C.YELLOW + Localizer.dLocalize("tragoul.lance.lore3") + level); - } - - @NoArgsConstructor - @ConfigDescription("Killing an enemy spawns a lance that seeks and damages a nearby enemy.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Seeker Delay for the Tragoul Lance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int seekerDelay = 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Seeker Damage Multiplier for the Tragoul Lance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double seekerDamageMultiplier = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Self Damage Multiplier for the Tragoul Lance adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double selfDamageMultiplier = 0.5; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulThorns.java b/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulThorns.java deleted file mode 100644 index 1760adad8..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/TragoulThorns.java +++ /dev/null @@ -1,169 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.tragoul; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import de.slikey.effectlib.effect.BleedEffect; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.entity.EntityDamageByEntityEvent; - -import java.util.HashMap; -import java.util.Map; - - -public class TragoulThorns extends SimpleAdaptation { - private final Map cooldowns; - - public TragoulThorns() { - super("tragoul-thorns"); - registerConfiguration(TragoulThorns.Config.class); - setDescription(Localizer.dLocalize("tragoul.thorns.description")); - setDisplayName(Localizer.dLocalize("tragoul.thorns.name")); - setIcon(Material.CACTUS); - setInterval(25000); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CACTUS) - .key("challenge_tragoul_thorns_500") - .title(Localizer.dLocalize("advancement.challenge_tragoul_thorns_500.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_thorns_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_CHESTPLATE) - .key("challenge_tragoul_thorns_5k") - .title(Localizer.dLocalize("advancement.challenge_tragoul_thorns_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_thorns_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_tragoul_thorns_500", "tragoul.thorns.damage-reflected", 500, 400); - registerMilestone("challenge_tragoul_thorns_5k", "tragoul.thorns.damage-reflected", 5000, 1500); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CACTUS) - .key("challenge_tragoul_thorns_kill") - .title(Localizer.dLocalize("advancement.challenge_tragoul_thorns_kill.title")) - .description(Localizer.dLocalize("advancement.challenge_tragoul_thorns_kill.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "" + getConfig().damageMultiplierPerLevel * level + "x " + Localizer.dLocalize("tragoul.thorns.lore1")); - } - - - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof Player p && hasAdaptation(p)) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + 1500 > System.currentTimeMillis()) - return; - - cooldowns.put(p, System.currentTimeMillis()); - - LivingEntity le = null; - - if (e.getDamager() instanceof LivingEntity) { - le = (LivingEntity) e.getDamager(); - } else if (e.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof LivingEntity) { - le = (LivingEntity) projectile.getShooter(); - } - - if (le != null) { - if (areParticlesEnabled()) { - BleedEffect blood = new BleedEffect(Adapt.instance.adaptEffectManager); // Enemy gets blood - blood.setEntity(le); - blood.height = -1; - blood.iterations = 1; - blood.start(); - } - double reflectedDamage = getConfig().damageMultiplierPerLevel * getLevel(p); - double healthBefore = le.getHealth(); - le.damage(reflectedDamage, p); - getPlayer(p).getData().addStat("tragoul.thorns.damage-reflected", (int) reflectedDamage); - if (healthBefore <= reflectedDamage && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_tragoul_thorns_kill")) { - getPlayer(p).getAdvancementHandler().grant("challenge_tragoul_thorns_kill"); - } - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - - @Override - public void onTick() { - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Reflect damage back to your attacker.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Tragoul Thorns adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Multiplier Per Level for the Tragoul Thorns adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageMultiplierPerLevel = 1.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.72; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/utils/EntityThings.java b/src/main/java/com/volmit/adapt/content/adaptation/tragoul/utils/EntityThings.java deleted file mode 100644 index dec9b6337..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/tragoul/utils/EntityThings.java +++ /dev/null @@ -1,49 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.tragoul.utils; - -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; - -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - -public class EntityThings { - - public static LivingEntity findNearestEntity(Entity referenceEntity, double range, Player player) { - Location location = referenceEntity.getLocation(); - List nearbyEntities = referenceEntity.getNearbyEntities(range, range, range); - - // Filter out non-living entities and the player - List livingEntities = nearbyEntities.stream() - .filter(entity -> entity instanceof LivingEntity && !entity.equals(player)) - .map(entity -> (LivingEntity) entity) - .collect(Collectors.toList()); - - // Sort by distance to the reference entity - livingEntities.sort(Comparator.comparingDouble(entity -> entity.getLocation().distance(location))); - - // Return the nearest living entity, or null if no valid entity is found - return livingEntities.isEmpty() ? null : livingEntities.get(0); - } -} - diff --git a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedBatteringCharge.java b/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedBatteringCharge.java deleted file mode 100644 index 8a1c72a24..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedBatteringCharge.java +++ /dev/null @@ -1,272 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.unarmed; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class UnarmedBatteringCharge extends SimpleAdaptation { - private final Map primedState = new HashMap<>(); - - public UnarmedBatteringCharge() { - super("unarmed-battering-charge"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("unarmed.battering_charge.description")); - setDisplayName(Localizer.dLocalize("unarmed.battering_charge.name")); - setIcon(Material.BLAZE_ROD); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(8); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_INGOT) - .key("challenge_unarmed_charge_300") - .title(Localizer.dLocalize("advancement.challenge_unarmed_charge_300.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_charge_300.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_unarmed_charge_300", "unarmed.battering-charge.charges", 300, 400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND) - .key("challenge_unarmed_charge_kills_100") - .title(Localizer.dLocalize("advancement.challenge_unarmed_charge_kills_100.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_charge_kills_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_unarmed_charge_kills_100", "unarmed.battering-charge.charge-kills", 100, 1000); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.f(getDamageBonus(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.battering_charge.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(getKnockback(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.battering_charge.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getCooldownTicks(level) * 50D, 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.battering_charge.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Player p) || !hasAdaptation(p) || p.isInsideVehicle()) { - return; - } - - if (!isChargeLoadout(p) || !p.isSprinting()) { - return; - } - - ItemStack cooldownItem = getCooldownItem(p); - if (cooldownItem != null && p.hasCooldown(cooldownItem.getType())) { - return; - } - - Vector v = p.getVelocity(); - if (v.lengthSquared() < getConfig().minimumVelocitySquared) { - return; - } - - int level = getLevel(p); - e.setDamage(e.getDamage() + getDamageBonus(level)); - Entity target = e.getEntity(); - target.setVelocity(target.getVelocity().add(p.getLocation().getDirection().normalize().multiply(getKnockback(level)))); - - if (cooldownItem != null) { - p.setCooldown(cooldownItem.getType(), getCooldownTicks(level)); - } - - SoundPlayer sp = SoundPlayer.of(p.getWorld()); - sp.play(p.getLocation(), Sound.ENTITY_PLAYER_ATTACK_KNOCKBACK, 1f, 0.95f); - sp.play(target.getLocation(), Sound.ENTITY_ZOGLIN_ATTACK, 0.5f, 0.8f); - if (areParticlesEnabled()) { - target.getWorld().spawnParticle(Particle.EXPLOSION, target.getLocation().add(0, 0.8, 0), 1, 0.06, 0.06, 0.06, 0.02); - } - if (areParticlesEnabled()) { - target.getWorld().spawnParticle(Particle.CLOUD, target.getLocation().add(0, 0.6, 0), 14, 0.25, 0.25, 0.25, 0.05); - } - primedState.put(p.getUniqueId(), false); - xp(p, e.getDamage() * getConfig().xpPerDamage); - getPlayer(p).getData().addStat("unarmed.battering-charge.charges", 1); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - if (!(e.getEntity() instanceof LivingEntity victim)) { - return; - } - if (victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg - && dmg.getDamager() instanceof Player p - && hasAdaptation(p) - && isChargeLoadout(p)) { - getPlayer(p).getData().addStat("unarmed.battering-charge.charge-kills", 1); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - primedState.remove(e.getPlayer().getUniqueId()); - } - - private boolean isChargeLoadout(Player p) { - ItemStack main = p.getInventory().getItemInMainHand(); - ItemStack off = p.getInventory().getItemInOffHand(); - boolean fists = (!isItem(main) || main.getType() == Material.AIR) && (!isItem(off) || off.getType() == Material.AIR); - boolean shieldLoadout = (isItem(main) && main.getType() == Material.SHIELD) || (isItem(off) && off.getType() == Material.SHIELD); - return fists || shieldLoadout; - } - - private ItemStack getCooldownItem(Player p) { - ItemStack main = p.getInventory().getItemInMainHand(); - ItemStack off = p.getInventory().getItemInOffHand(); - if (isItem(main) && main.getType() == Material.SHIELD) { - return main; - } - - if (isItem(off) && off.getType() == Material.SHIELD) { - return off; - } - - return null; - } - - private boolean isChargeReady(Player p) { - if (p.isInsideVehicle() || !isChargeLoadout(p) || !p.isSprinting()) { - return false; - } - - ItemStack cooldownItem = getCooldownItem(p); - if (cooldownItem != null && p.hasCooldown(cooldownItem.getType())) { - return false; - } - - Vector v = p.getVelocity(); - return v.lengthSquared() >= getConfig().minimumVelocitySquared; - } - - private double getDamageBonus(int level) { - return getConfig().damageBase + (getLevelPercent(level) * getConfig().damageFactor); - } - - private double getKnockback(int level) { - return getConfig().knockbackBase + (getLevelPercent(level) * getConfig().knockbackFactor); - } - - private int getCooldownTicks(int level) { - return Math.max(10, (int) Math.round(getConfig().cooldownTicksBase - (getLevelPercent(level) * getConfig().cooldownTicksFactor))); - } - - @Override - public void onTick() { - for (com.volmit.adapt.api.world.AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (!hasAdaptation(p)) { - primedState.remove(p.getUniqueId()); - continue; - } - - boolean primed = isChargeReady(p); - boolean wasPrimed = primedState.getOrDefault(p.getUniqueId(), false); - - if (primed) { - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.CLOUD, p.getLocation().add(0, 0.2, 0), 2, 0.2, 0.05, 0.2, 0.02); - } - if (!wasPrimed) { - SoundPlayer.of(p.getWorld()).play(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASEDRUM, 0.55f, 1.15f); - if (areParticlesEnabled()) { - p.getWorld().spawnParticle(Particle.CRIT, p.getLocation().add(0, 1.0, 0), 8, 0.2, 0.3, 0.2, 0.1); - } - } - } - - primedState.put(p.getUniqueId(), primed); - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sprint into enemies with fists or a shield to deal impact damage.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Base for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageBase = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageFactor = 4.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Knockback Base for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double knockbackBase = 0.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Knockback Factor for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double knockbackFactor = 1.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Base for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksBase = 80; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Ticks Factor for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double cooldownTicksFactor = 50; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Velocity Squared for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minimumVelocitySquared = 0.18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Damage for the Unarmed Battering Charge adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerDamage = 3.3; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedComboChain.java b/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedComboChain.java deleted file mode 100644 index 634660e13..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedComboChain.java +++ /dev/null @@ -1,244 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.unarmed; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class UnarmedComboChain extends SimpleAdaptation { - private final Map combos = new HashMap<>(); - - public UnarmedComboChain() { - super("unarmed-combo-chain"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("unarmed.combo_chain.description")); - setDisplayName(Localizer.dLocalize("unarmed.combo_chain.name")); - setIcon(Material.CHAINMAIL_BOOTS); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(1800); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_INGOT) - .key("challenge_unarmed_combo_5k") - .title(Localizer.dLocalize("advancement.challenge_unarmed_combo_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_combo_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_unarmed_combo_5k", "unarmed.combo-chain.total-combo-hits", 5000, 400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BLAZE_POWDER) - .key("challenge_unarmed_combo_10") - .title(Localizer.dLocalize("advancement.challenge_unarmed_combo_10.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_combo_10.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BLAZE_ROD) - .key("challenge_unarmed_combo_25") - .title(Localizer.dLocalize("advancement.challenge_unarmed_combo_25.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_combo_25.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + getMaxStacks(level) + C.GRAY + " " + Localizer.dLocalize("unarmed.combo_chain.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(getDamagePerStack(level)) + C.GRAY + " " + Localizer.dLocalize("unarmed.combo_chain.lore2")); - v.addLore(C.YELLOW + "* " + Form.duration(getComboWindowMillis(level), 1) + C.GRAY + " " + Localizer.dLocalize("unarmed.combo_chain.lore3")); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getDamager() instanceof Player p) || !hasAdaptation(p)) { - return; - } - - ItemStack hand = p.getInventory().getItemInMainHand(); - if (isMelee(hand)) { - combos.remove(p.getUniqueId()); - return; - } - - long now = System.currentTimeMillis(); - int level = getLevel(p); - ComboState state = combos.computeIfAbsent(p.getUniqueId(), id -> new ComboState()); - - if (now - state.lastHitMillis > getComboWindowMillis(level)) { - state.stacks = 0; - } - - state.lastHitMillis = now; - state.stacks = Math.min(getMaxStacks(level), state.stacks + 1); - - double bonus = state.stacks * getDamagePerStack(level); - e.setDamage(e.getDamage() + bonus); - playComboFeedback(p, e.getEntity().getLocation(), state.stacks, getMaxStacks(level)); - xp(p, bonus * getConfig().xpPerBonusDamage); - getPlayer(p).getData().addStat("unarmed.combo-chain.total-combo-hits", 1); - - // Special achievements: reach a 10-hit or 25-hit combo - if (state.stacks >= 10 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_unarmed_combo_10")) { - getPlayer(p).getAdvancementHandler().grant("challenge_unarmed_combo_10"); - } - if (state.stacks >= 25 && AdaptConfig.get().isAdvancements() && !getPlayer(p).getData().isGranted("challenge_unarmed_combo_25")) { - getPlayer(p).getAdvancementHandler().grant("challenge_unarmed_combo_25"); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerInteractEvent e) { - Action action = e.getAction(); - if (action != Action.LEFT_CLICK_AIR && action != Action.LEFT_CLICK_BLOCK) { - return; - } - - Player p = e.getPlayer(); - if (!hasAdaptation(p) || isMelee(p.getInventory().getItemInMainHand())) { - return; - } - - ComboState state = combos.get(p.getUniqueId()); - if (state == null) { - return; - } - - long now = System.currentTimeMillis(); - if (now - state.lastHitMillis > getConfig().missResetGraceMillis) { - combos.remove(p.getUniqueId()); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - combos.remove(e.getPlayer().getUniqueId()); - } - - private int getMaxStacks(int level) { - return Math.max(1, (int) Math.round(getConfig().maxStacksBase + (getLevelPercent(level) * getConfig().maxStacksFactor))); - } - - private double getDamagePerStack(int level) { - return getConfig().damagePerStackBase + (getLevelPercent(level) * getConfig().damagePerStackFactor); - } - - private long getComboWindowMillis(int level) { - return Math.max(250, (long) Math.round(getConfig().comboWindowMillisBase + (getLevelPercent(level) * getConfig().comboWindowMillisFactor))); - } - - private void playComboFeedback(Player p, org.bukkit.Location hitLocation, int stacks, int maxStacks) { - float pitch = Math.min(2.0f, 0.85f + (stacks * 0.09f)); - if (stacks >= maxStacks) { - SoundPlayer.of(p.getWorld()).play(hitLocation, Sound.BLOCK_ANVIL_PLACE, 0.55f, 1.7f); - if (areParticlesEnabled()) { - p.spawnParticle(Particle.TOTEM_OF_UNDYING, hitLocation.clone().add(0, 1, 0), 5, 0.2, 0.4, 0.2, 0.05); - } - return; - } - - SoundPlayer.of(p.getWorld()).play(hitLocation, Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 0.55f, pitch); - if (areParticlesEnabled()) { - p.spawnParticle(Particle.CRIT, hitLocation.clone().add(0, 0.9, 0), 6 + Math.min(16, stacks * 2), 0.22, 0.34, 0.22, 0.1); - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Consecutive unarmed hits build combo stacks for increased punch damage.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Stacks Base for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxStacksBase = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Stacks Factor for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxStacksFactor = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Per Stack Base for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damagePerStackBase = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Per Stack Factor for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damagePerStackFactor = 0.85; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Combo Window Millis Base for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double comboWindowMillisBase = 1300; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Combo Window Millis Factor for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double comboWindowMillisFactor = 1400; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Miss Reset Grace Millis for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long missResetGraceMillis = 280; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Per Bonus Damage for the Unarmed Combo Chain adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpPerBonusDamage = 4.1; - } - - private static class ComboState { - private int stacks = 0; - private long lastHitMillis = 0L; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedGlassCannon.java b/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedGlassCannon.java deleted file mode 100644 index f49ea776d..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedGlassCannon.java +++ /dev/null @@ -1,158 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.unarmed; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; - -public class UnarmedGlassCannon extends SimpleAdaptation { - public UnarmedGlassCannon() { - super("unarmed-glass-cannon"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("unarmed.glass_cannon.description")); - setDisplayName(Localizer.dLocalize("unarmed.glass_cannon.name")); - setIcon(Material.POINTED_DRIPSTONE); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(4544); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GLASS) - .key("challenge_unarmed_glass_100") - .title(Localizer.dLocalize("advancement.challenge_unarmed_glass_100.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_glass_100.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GLASS_PANE) - .key("challenge_unarmed_glass_500") - .title(Localizer.dLocalize("advancement.challenge_unarmed_glass_500.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_glass_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_unarmed_glass_100", "unarmed.glass-cannon.naked-kills", 100, 300); - registerMilestone("challenge_unarmed_glass_500", "unarmed.glass-cannon.naked-kills", 500, 1000); - } - - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + (getConfig().maxDamageFactor + (level * getConfig().maxDamagePerLevelMultiplier)) + C.GRAY + " " + Localizer.dLocalize("unarmed.glass_cannon.lore1")); - v.addLore(C.GREEN + "+ " + Form.f(level * getConfig().perLevelBonusMultiplier) + C.GRAY + " " + Localizer.dLocalize("unarmed.glass_cannon.lore2")); - } - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p) { - if (!hasAdaptation(p)) { - return; - } - - - if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { - return; - } - - double armor = getArmorValue(p); - double damage = e.getDamage(); - - if (armor == 0) { - e.setDamage(damage * getConfig().maxDamageFactor); - } else { - e.setDamage(damage - (damage * armor)); - } - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - if (!(e.getEntity() instanceof LivingEntity victim)) { - return; - } - if (victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg - && dmg.getDamager() instanceof Player p - && hasAdaptation(p) - && !isTool(p.getInventory().getItemInMainHand()) - && !isTool(p.getInventory().getItemInOffHand()) - && getArmorValue(p) == 0) { - getPlayer(p).getData().addStat("unarmed.glass-cannon.naked-kills", 1); - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Bonus unarmed damage the lower your armor value is.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.425; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Per Level Bonus Multiplier for the Unarmed Glass Cannon adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double perLevelBonusMultiplier = 0.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Damage Factor for the Unarmed Glass Cannon adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxDamageFactor = 4.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Damage Per Level Multiplier for the Unarmed Glass Cannon adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxDamagePerLevelMultiplier = 0.15; - } - -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedPower.java b/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedPower.java deleted file mode 100644 index 22898771b..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedPower.java +++ /dev/null @@ -1,152 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.unarmed; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; - -public class UnarmedPower extends SimpleAdaptation { - public UnarmedPower() { - super("unarmed-power"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("unarmed.power.description")); - setDisplayName(Localizer.dLocalize("unarmed.power.name")); - setIcon(Material.IRON_INGOT); - setBaseCost(getConfig().baseCost); - setMaxLevel(getConfig().maxLevel); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(4444); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_INGOT) - .key("challenge_unarmed_power_500") - .title(Localizer.dLocalize("advancement.challenge_unarmed_power_500.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_power_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND) - .key("challenge_unarmed_power_5k") - .title(Localizer.dLocalize("advancement.challenge_unarmed_power_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_power_5k.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_unarmed_power_500", "unarmed.power.unarmed-kills", 500, 400); - registerMilestone("challenge_unarmed_power_5k", "unarmed.power.unarmed-kills", 5000, 1500); - } - - @Override - public void addStats(int level, Element v) { - v.addLore(C.GREEN + "+ " + Form.pc(getUnarmedDamage(level), 0) + C.GRAY + Localizer.dLocalize("unarmed.power.lore1")); - } - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p) { - if (!hasAdaptation(p)) { - return; - } - - if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { - return; - } - double factor = getLevelPercent(p); - - if (factor <= 0) { - return; - } - e.setDamage(e.getDamage() * (1 + getUnarmedDamage(getLevel(p)))); - xp(p, 0.321 * factor * e.getDamage(), "unarmed-hit"); - - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - if (!(e.getEntity() instanceof LivingEntity victim)) { - return; - } - if (victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg - && dmg.getDamager() instanceof Player p - && hasAdaptation(p) - && !isTool(p.getInventory().getItemInMainHand()) - && !isTool(p.getInventory().getItemInOffHand())) { - getPlayer(p).getData().addStat("unarmed.power.unarmed-kills", 1); - } - } - - private double getUnarmedDamage(int level) { - return getLevelPercent(level) * getConfig().damageFactor; - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Improved base unarmed damage.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Maximum level a player can reach for this adaptation.", impact = "Higher values allow more levels; lower values cap progression sooner.") - int maxLevel = 7; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.425; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Unarmed Power adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageFactor = 2.57; - } -} diff --git a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedSuckerPunch.java b/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedSuckerPunch.java deleted file mode 100644 index 5d31739b4..000000000 --- a/src/main/java/com/volmit/adapt/content/adaptation/unarmed/UnarmedSuckerPunch.java +++ /dev/null @@ -1,178 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.adaptation.unarmed; - -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.config.ConfigDescription; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; - -public class UnarmedSuckerPunch extends SimpleAdaptation { - public UnarmedSuckerPunch() { - super("unarmed-sucker-punch"); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("unarmed.sucker_punch.description")); - setDisplayName(Localizer.dLocalize("unarmed.sucker_punch.name")); - setIcon(Material.OBSIDIAN); - setBaseCost(getConfig().baseCost); - setInitialCost(getConfig().initialCost); - setCostFactor(getConfig().costFactor); - setInterval(4944); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_INGOT) - .key("challenge_unarmed_sucker_500") - .title(Localizer.dLocalize("advancement.challenge_unarmed_sucker_500.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_sucker_500.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_unarmed_sucker_500", "unarmed.sucker-punch.sucker-punches", 500, 400); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND) - .key("challenge_unarmed_knockout") - .title(Localizer.dLocalize("advancement.challenge_unarmed_knockout.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_knockout.description")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()); - registerMilestone("challenge_unarmed_knockout", "unarmed.sucker-punch.one-punch-kills", 50, 1000); - } - - - @Override - public void addStats(int level, Element v) { - double f = getLevelPercent(level); - double d = getDamage(f); - v.addLore(C.GREEN + "+ " + Form.pc(d, 0) + C.GRAY + " " + Localizer.dLocalize("unarmed.sucker_punch.lore1")); - v.addLore(C.GRAY + Localizer.dLocalize("unarmed.sucker_punch.lore2")); - } - - private double getDamage(double f) { - return getConfig().baseDamage + (f * getConfig().damageFactor); - } - - @EventHandler - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - - if (e.getDamager() instanceof Player p) { - if (!hasAdaptation(p)) { - return; - } - if (p.getInventory().getItemInMainHand().getType() != Material.AIR && p.getInventory().getItemInOffHand().getType() != Material.AIR) { - return; - } - double factor = getLevelPercent(p); - - if (!p.isSprinting()) { - return; - } - - if (factor <= 0) { - return; - } - - if (isTool(p.getInventory().getItemInMainHand()) || isTool(p.getInventory().getItemInOffHand())) { - return; - } - - e.setDamage(e.getDamage() * getDamage(factor)); - SoundPlayer spw = SoundPlayer.of(e.getEntity().getWorld()); - spw.play(e.getEntity().getLocation(), Sound.ENTITY_PLAYER_ATTACK_STRONG, 1f, 1.8f); - spw.play(e.getEntity().getLocation(), Sound.BLOCK_BASALT_BREAK, 1f, 0.6f); - xp(p, 6.221 * e.getDamage(), "sucker-punch"); - getPlayer(p).getData().addStat("unarmed.sucker-punch.sucker-punches", 1); - if (e.getDamage() > 5) { - xp(p, 0.42 * e.getDamage(), "bonus-damage"); - if (areParticlesEnabled()) { - e.getEntity().getWorld().spawnParticle(Particle.FLASH, e.getEntity().getLocation(), 1); - } - } - } - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void on(EntityDeathEvent e) { - if (!(e.getEntity() instanceof LivingEntity victim)) { - return; - } - if (victim.getLastDamageCause() instanceof EntityDamageByEntityEvent dmg - && dmg.getDamager() instanceof Player p - && hasAdaptation(p) - && p.isSprinting() - && !isTool(p.getInventory().getItemInMainHand()) - && !isTool(p.getInventory().getItemInOffHand())) { - // One-punch kill: entity was at full health before the killing blow - if (victim.getMaxHealth() <= dmg.getFinalDamage()) { - getPlayer(p).getData().addStat("unarmed.sucker-punch.one-punch-kills", 1); - } - } - } - - @Override - public void onTick() { - - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - public boolean isPermanent() { - return getConfig().permanent; - } - - @NoArgsConstructor - @ConfigDescription("Sprint punches deal extra damage based on your speed.") - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Keeps this adaptation permanently active once learned.", impact = "True removes the normal learn/unlearn flow and treats it as always learned.") - boolean permanent = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Unarmed Sucker Punch adaptation.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Base knowledge cost used when learning this adaptation.", impact = "Higher values make each level cost more knowledge.") - int baseCost = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Knowledge cost required to purchase level 1.", impact = "Higher values make unlocking the first level more expensive.") - int initialCost = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Scaling factor applied to higher adaptation levels.", impact = "Higher values increase level-to-level cost growth.") - double costFactor = 0.225; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Damage for the Unarmed Sucker Punch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseDamage = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Factor for the Unarmed Sucker Punch adaptation.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageFactor = 0.55; - } -} diff --git a/src/main/java/com/volmit/adapt/content/block/ScaffoldMatter.java b/src/main/java/com/volmit/adapt/content/block/ScaffoldMatter.java deleted file mode 100644 index 4f3a15188..000000000 --- a/src/main/java/com/volmit/adapt/content/block/ScaffoldMatter.java +++ /dev/null @@ -1,61 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.block; - -import art.arcane.spatial.matter.slices.RawMatter; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.UUID; - -public class ScaffoldMatter extends RawMatter { - public ScaffoldMatter(int width, int height, int depth) { - super(width, height, depth, ScaffoldData.class); - } - - @Override - public void writeNode(ScaffoldData scaffoldData, DataOutputStream dos) throws IOException { - dos.writeLong(scaffoldData.getUuid().getMostSignificantBits()); - dos.writeLong(scaffoldData.getUuid().getLeastSignificantBits()); - dos.writeLong(scaffoldData.getTime()); - } - - @Override - public ScaffoldData readNode(DataInputStream din) throws IOException { - return ScaffoldData.builder() - .uuid(new UUID(din.readLong(), din.readLong())) - .time(din.readLong()) - .build(); - } - - @Data - @Builder - @AllArgsConstructor - @NoArgsConstructor - public static class ScaffoldData { - private UUID uuid; - @Builder.Default - private long time = 0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/event/AdaptAdaptationEvent.java b/src/main/java/com/volmit/adapt/content/event/AdaptAdaptationEvent.java deleted file mode 100644 index 11ed5b17f..000000000 --- a/src/main/java/com/volmit/adapt/content/event/AdaptAdaptationEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.event; - -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.PlayerSkillLine; - -public class AdaptAdaptationEvent extends AdaptPlayerEvent { - private final Skill skill; - private final PlayerSkillLine playerSkill; - private final Adaptation adaptation; - - public AdaptAdaptationEvent(boolean async, AdaptPlayer player, Adaptation adaptation) { - super(async, player); - this.adaptation = adaptation; - this.playerSkill = player.getSkillLine(adaptation.getSkill().getId()); - this.skill = adaptation.getSkill(); - } - - public Skill getSkill() { - return skill; - } - - public Adaptation getAdaptation() { - return adaptation; - } - - public PlayerSkillLine getPlayerSkill() { - return playerSkill; - } -} diff --git a/src/main/java/com/volmit/adapt/content/event/AdaptAdaptationTeleportEvent.java b/src/main/java/com/volmit/adapt/content/event/AdaptAdaptationTeleportEvent.java deleted file mode 100644 index 917639d99..000000000 --- a/src/main/java/com/volmit/adapt/content/event/AdaptAdaptationTeleportEvent.java +++ /dev/null @@ -1,35 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.event; - -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.world.AdaptPlayer; -import lombok.Getter; -import org.bukkit.Location; - -public class AdaptAdaptationTeleportEvent extends AdaptAdaptationEvent { - @Getter - Location fromLocation, toLocation; - - public AdaptAdaptationTeleportEvent(boolean async, AdaptPlayer player, Adaptation adaptation, Location fromLocation, Location toLocation) { - super(async, player, adaptation); - this.fromLocation = fromLocation; - this.toLocation = toLocation; - } -} diff --git a/src/main/java/com/volmit/adapt/content/event/AdaptEvent.java b/src/main/java/com/volmit/adapt/content/event/AdaptEvent.java deleted file mode 100644 index 72919a140..000000000 --- a/src/main/java/com/volmit/adapt/content/event/AdaptEvent.java +++ /dev/null @@ -1,60 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.event; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.world.AdaptServer; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -import org.jetbrains.annotations.NotNull; - -public class AdaptEvent extends Event implements Cancellable { - private static final HandlerList HANDLERS = new HandlerList(); - private boolean canceled; - - - public AdaptEvent(boolean async) { - super(async); - canceled = false; - } - - public AdaptServer getServer() { - return Adapt.instance.getAdaptServer(); - } - - @Override - public boolean isCancelled() { - return canceled; - } - - @Override - public void setCancelled(boolean b) { - canceled = b; - } - - public static HandlerList getHandlerList() { - return HANDLERS; - } - - @Override - public HandlerList getHandlers() { - return HANDLERS; - } -} diff --git a/src/main/java/com/volmit/adapt/content/gui/ConfigGui.java b/src/main/java/com/volmit/adapt/content/gui/ConfigGui.java deleted file mode 100644 index 99334c04c..000000000 --- a/src/main/java/com/volmit/adapt/content/gui/ConfigGui.java +++ /dev/null @@ -1,1713 +0,0 @@ -package com.volmit.adapt.content.gui; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.service.ConfigInputSVC; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Element; -import com.volmit.adapt.util.GuiEffects; -import com.volmit.adapt.util.GuiLayout; -import com.volmit.adapt.util.GuiTheme; -import com.volmit.adapt.util.IO; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.MaterialBlock; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.UIElement; -import com.volmit.adapt.util.UIWindow; -import com.volmit.adapt.util.Window; -import com.volmit.adapt.util.config.ConfigDocumentation; -import com.volmit.adapt.util.config.TomlCodec; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.InventoryType; - -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BooleanSupplier; - -public final class ConfigGui { - private static final String TAG_PREFIX = "config/adapt"; - private static final String SOURCE_TAG_CORE = "core-config"; - private static final String ROOT_CORE = "core"; - private static final String ROOT_CORE_GENERAL = ROOT_CORE + ".$general"; - private static final String ROOT_SKILLS = "skills"; - private static final String ROOT_ADAPTATIONS = "adaptations"; - private static final String ROOT_ADAPTATIONS_SKILLS = ROOT_ADAPTATIONS + ".$skills"; - private static final String ROOT_ADAPTATIONS_ALL = ROOT_ADAPTATIONS + ".$all"; - private static final Object WRITE_LOCK = new Object(); - private static final int MAX_VALUE_PREVIEW = 64; - private static final int PAGE_JUMP = 5; - private static final int CONFIG_CONTENT_ROWS = GuiLayout.MAX_ROWS - 1; - private static final long CLOSE_SUPPRESS_MS = 1200L; - private static final int CLOSE_SUPPRESS_CLEAR_TICKS = 4; - private static final Map CLOSE_SUPPRESS_UNTIL = new ConcurrentHashMap<>(); - - private ConfigGui() { - } - - public static boolean canConfigure(Player player) { - return player != null && (player.isOp() || player.hasPermission("adapt.configurator")); - } - - public static void open(Player player) { - open(player, "", 0); - } - - public static void open(Player player, String sectionPath) { - open(player, sectionPath, 0); - } - - public static void open(Player player, String sectionPath, int page) { - if (player == null) { - return; - } - - if (!canConfigure(player)) { - Adapt.messagePlayer(player, C.RED + "You do not have permission to use the config menu."); - return; - } - - if (!Bukkit.isPrimaryThread()) { - String path = sectionPath; - int targetPage = page; - J.s(() -> open(player, path, targetPage)); - return; - } - - playPageTurn(player); - String safePath = normalizePath(sectionPath); - if (safePath.isBlank()) { - openRoot(player, page); - return; - } - - if (safePath.equals(ROOT_SKILLS)) { - openSkillIndex(player, page); - return; - } - - if (safePath.equals(ROOT_CORE)) { - openCoreIndex(player, page); - return; - } - - if (safePath.equals(ROOT_CORE_GENERAL)) { - openCoreGeneral(player, page); - return; - } - - if (safePath.equals(ROOT_ADAPTATIONS) || safePath.equals(ROOT_ADAPTATIONS_SKILLS)) { - openAdaptationSkillIndex(player, page); - return; - } - - if (safePath.equals(ROOT_ADAPTATIONS_ALL)) { - openAdaptationIndex(player, page); - return; - } - - if (safePath.startsWith(ROOT_ADAPTATIONS_SKILLS + ".")) { - String skillName = safePath.substring(ROOT_ADAPTATIONS_SKILLS.length() + 1); - openAdaptationIndexForSkill(player, skillName, page); - return; - } - - SectionTarget target = resolveSectionTarget(safePath, false); - if (target == null || target.sectionObject() == null) { - Adapt.messagePlayer(player, C.RED + "Unable to open config section: " + C.WHITE + safePath); - return; - } - - List entries = buildEntries(safePath, target.sectionObject(), target.sourceTag()); - openFieldEntries(player, safePath, entries, page); - } - - public static void reopenFromTag(Player player, String tag) { - if (player == null) { - return; - } - - if (tag == null || tag.isBlank()) { - open(player); - return; - } - - if (!tag.startsWith(TAG_PREFIX)) { - open(player); - return; - } - - String path = ""; - if (tag.length() > TAG_PREFIX.length()) { - path = tag.substring(TAG_PREFIX.length()); - if (path.startsWith("/")) { - path = path.substring(1); - } - } - - navigateTo(player, path, 0); - } - - public static ParseResult parseInputValue(Class type, String raw) { - if (type == null) { - return ParseResult.fail("Unknown target type."); - } - - Class normalized = normalizeType(type); - String trimmed = raw == null ? "" : raw.trim(); - - try { - if (normalized == String.class) { - return ParseResult.ok(raw == null ? "" : raw); - } - - if (normalized == Character.class) { - if (trimmed.length() != 1) { - return ParseResult.fail("Expected exactly one character."); - } - return ParseResult.ok(trimmed.charAt(0)); - } - - if (normalized == Boolean.class) { - if (trimmed.equalsIgnoreCase("true") || trimmed.equalsIgnoreCase("yes") || trimmed.equalsIgnoreCase("on")) { - return ParseResult.ok(true); - } - if (trimmed.equalsIgnoreCase("false") || trimmed.equalsIgnoreCase("no") || trimmed.equalsIgnoreCase("off")) { - return ParseResult.ok(false); - } - return ParseResult.fail("Expected boolean value: true/false."); - } - - if (normalized.isEnum()) { - Object constant = parseEnumConstant(normalized, trimmed); - if (constant == null) { - return ParseResult.fail("Expected one of: " + enumConstants(normalized)); - } - return ParseResult.ok(constant); - } - - if (normalized == Integer.class) { - return ParseResult.ok(Integer.parseInt(trimmed)); - } - if (normalized == Long.class) { - return ParseResult.ok(Long.parseLong(trimmed)); - } - if (normalized == Double.class) { - double v = Double.parseDouble(trimmed); - if (!Double.isFinite(v)) { - return ParseResult.fail("Expected a finite number."); - } - return ParseResult.ok(v); - } - if (normalized == Float.class) { - float v = Float.parseFloat(trimmed); - if (!Float.isFinite(v)) { - return ParseResult.fail("Expected a finite number."); - } - return ParseResult.ok(v); - } - if (normalized == Short.class) { - return ParseResult.ok(Short.parseShort(trimmed)); - } - if (normalized == Byte.class) { - return ParseResult.ok(Byte.parseByte(trimmed)); - } - } catch (Throwable e) { - return ParseResult.fail("Invalid value for type " + typeName(type) + "."); - } - - return ParseResult.fail("Unsupported type: " + typeName(type) + "."); - } - - public static boolean applyAndSave(Player actor, String valuePath, Object value) { - String path = normalizePath(valuePath); - if (path.isBlank()) { - return false; - } - - synchronized (WRITE_LOCK) { - EditTarget target = resolveEditTarget(path); - if (target == null) { - if (actor != null) { - Adapt.messagePlayer(actor, C.RED + "Failed to set config value at " + C.WHITE + path); - } - return false; - } - - Object before = readPathValue(target.rootObject(), target.objectPath()); - String beforeToml = TomlCodec.toToml(target.rootObject(), target.sourceTag()); - - if (!setPathValue(target.rootObject(), target.objectPath(), value, true)) { - if (actor != null) { - Adapt.messagePlayer(actor, C.RED + "Failed to set config value at " + C.WHITE + path); - } - return false; - } - - try { - String updatedToml = TomlCodec.toToml(target.rootObject(), target.sourceTag()); - IO.writeAll(target.file(), updatedToml); - } catch (Throwable e) { - J.attempt(() -> IO.writeAll(target.file(), beforeToml)); - target.reload().getAsBoolean(); - if (actor != null) { - Adapt.messagePlayer(actor, C.RED + "Failed to persist config update: " + C.WHITE + e.getMessage()); - } - return false; - } - - if (!target.reload().getAsBoolean()) { - J.attempt(() -> IO.writeAll(target.file(), beforeToml)); - target.reload().getAsBoolean(); - if (actor != null) { - Adapt.messagePlayer(actor, C.RED + "Config reload failed. Reverted file changes."); - } - return false; - } - - target.afterReload().run(); - if (actor != null) { - Adapt.messagePlayer(actor, C.GREEN + "Updated " + C.WHITE + path - + C.GRAY + " [" + summarizeValue(before) + C.GRAY + " -> " + summarizeValue(value) + C.GRAY + "]"); - } - return true; - } - } - - public static void confirmAndApply(Player actor, String returnSectionPath, String valuePath, Object value) { - confirmAndApply(actor, returnSectionPath, 0, valuePath, value); - } - - public static void confirmAndApply(Player actor, String returnSectionPath, int returnPage, String valuePath, Object value) { - String path = normalizePath(valuePath); - if (path.isBlank()) { - return; - } - - String section = normalizePath(returnSectionPath); - applyAndSave(actor, path, value); - navigateTo(actor, section, Math.max(0, returnPage)); - } - - public static String typeName(Class type) { - if (type == null) { - return "unknown"; - } - - Class normalized = normalizeType(type); - if (normalized.isEnum()) { - return "enum"; - } - return normalized.getSimpleName().toLowerCase(Locale.ROOT); - } - - private static ElementDescriptor describe(Field field, Object value) { - Class type = normalizeType(field.getType()); - if (type == Boolean.class) { - return new ElementDescriptor(ElementKind.BOOLEAN, true); - } - - if (isNumericType(type)) { - return new ElementDescriptor(ElementKind.NUMBER, true); - } - - if (type == String.class || type == Character.class) { - return new ElementDescriptor(ElementKind.STRING, true); - } - - if (type.isEnum()) { - return new ElementDescriptor(ElementKind.ENUM, true); - } - - if (Map.class.isAssignableFrom(type)) { - return new ElementDescriptor(ElementKind.MAP, false); - } - - if (Collection.class.isAssignableFrom(type) || type.isArray()) { - return new ElementDescriptor(ElementKind.LIST, false); - } - - if (value != null || isSectionType(type)) { - return new ElementDescriptor(ElementKind.SECTION, false); - } - - return new ElementDescriptor(ElementKind.UNSUPPORTED, false); - } - - private static UIElement createElementForEntry(Player player, String sectionPath, int currentPage, FieldEntry entry) { - Material material = materialFor(entry); - String typePrefix = switch (entry.descriptor().kind()) { - case BOOLEAN -> C.GREEN + "[Boolean] "; - case NUMBER -> C.AQUA + "[Number] "; - case STRING -> C.YELLOW + "[Text] "; - case ENUM -> C.LIGHT_PURPLE + "[Enum] "; - case SECTION -> C.BLUE + "[Section] "; - case MAP -> C.GOLD + "[Map] "; - case LIST -> C.GOLD + "[List] "; - case UNSUPPORTED -> C.RED + "[Unsupported] "; - }; - String name = displayName(entry.field().getName()); - String value = summarizeValue(entry.value()); - - UIElement element = new UIElement("cfg-" + entry.path()) - .setMaterial(new MaterialBlock(material)) - .setName(typePrefix + C.WHITE + name); - element.addLore(C.GRAY + "Value: " + C.AQUA + value); - element.addLore(C.DARK_GRAY + "Path: " + entry.path()); - element.setProgress(1D); - if (entry.descriptor().kind() == ElementKind.BOOLEAN && Boolean.TRUE.equals(entry.value())) { - element.setEnchanted(true); - } - - if (entry.descriptor().kind() == ElementKind.SECTION && entry.value() != null) { - int nested = getSerializableFields(entry.value().getClass()).size(); - element.addLore(C.GRAY + "Contains " + C.WHITE + nested + C.GRAY + " setting" + (nested == 1 ? "" : "s")); - element.addLore(C.DARK_GRAY + "Category: " + sectionCategory(entry.field().getName())); - } - - int docsShown = 0; - for (String line : entry.docs()) { - if (line == null || line.isBlank()) { - continue; - } - if (docsShown >= 3) { - element.addLore(C.DARK_GRAY + "..."); - break; - } - element.addLore(C.GRAY + line); - docsShown++; - } - - switch (entry.descriptor().kind()) { - case BOOLEAN -> { - element.addLore(Boolean.TRUE.equals(entry.value()) - ? C.GREEN + "State: Enabled" - : C.RED + "State: Disabled"); - element.addLore(C.GREEN + "Left click: toggle"); - element.onLeftClick((e) -> { - boolean toggled = !Boolean.TRUE.equals(entry.value()); - confirmAndApply(player, sectionPath, currentPage, entry.path(), toggled); - }); - } - case ENUM -> { - element.addLore(C.GREEN + "Left click: next value"); - element.addLore(C.GREEN + "Right click: previous value"); - element.onLeftClick((e) -> { - Object next = cycleEnum(entry.field().getType(), entry.value(), 1); - if (next != null) { - confirmAndApply(player, sectionPath, currentPage, entry.path(), next); - } - }); - element.onRightClick((e) -> { - Object previous = cycleEnum(entry.field().getType(), entry.value(), -1); - if (previous != null) { - confirmAndApply(player, sectionPath, currentPage, entry.path(), previous); - } - }); - } - case NUMBER, STRING -> { - element.addLore(C.YELLOW + "Left click: edit in chat"); - element.onLeftClick((e) -> { - ConfigInputSVC service = Adapt.service(ConfigInputSVC.class); - if (service == null) { - Adapt.messagePlayer(player, C.RED + "Config input service is unavailable."); - return; - } - - service.beginSession(player, entry.path(), sectionPath, currentPage, entry.field().getType(), displayName(entry.field().getName())); - }); - } - case SECTION -> { - element.addLore(C.GREEN + "Left click: open section"); - element.onLeftClick((e) -> navigateTo(player, entry.path(), 0)); - } - case MAP, LIST -> element.addLore(C.RED + "Read-only in Phase 1"); - case UNSUPPORTED -> element.addLore(C.RED + "Unsupported type"); - } - - return element; - } - - private static Material materialFor(FieldEntry entry) { - return switch (entry.descriptor().kind()) { - case BOOLEAN -> Boolean.TRUE.equals(entry.value()) ? Material.LIME_DYE : Material.GRAY_DYE; - case NUMBER -> Material.CLOCK; - case STRING -> Material.NAME_TAG; - case ENUM -> Material.BOOK; - case SECTION -> materialForSection(entry.field().getName()); - case MAP -> Material.CHEST_MINECART; - case LIST -> Material.BARREL; - case UNSUPPORTED -> Material.BARRIER; - }; - } - - private static Material materialForSection(String name) { - String key = normalizeSortKey(name); - if (key.contains("gui") || key.contains("menu") || key.contains("display") || key.contains("hud")) { - return Material.BOOKSHELF; - } - if (key.contains("sound") || key.contains("audio")) { - return Material.JUKEBOX; - } - if (key.contains("lang") || key.contains("locale") || key.contains("translation")) { - return Material.WRITABLE_BOOK; - } - if (key.contains("sql") || key.contains("database") || key.contains("storage") || key.contains("mysql")) { - return Material.ENDER_CHEST; - } - if (key.contains("xp") || key.contains("level") || key.contains("knowledge") || key.contains("power")) { - return Material.EXPERIENCE_BOTTLE; - } - if (key.contains("world") || key.contains("biome") || key.contains("dimension") || key.contains("region")) { - return Material.GRASS_BLOCK; - } - if (key.contains("thread") || key.contains("tick") || key.contains("async") || key.contains("performance") || key.contains("cache")) { - return Material.REDSTONE; - } - if (key.contains("permission") || key.contains("blacklist") || key.contains("whitelist") || key.contains("security")) { - return Material.SHIELD; - } - if (key.contains("debug") || key.contains("dev") || key.contains("test") || key.contains("verbose")) { - return Material.SPYGLASS; - } - return Material.CHEST; - } - - private static String sectionCategory(String name) { - String key = normalizeSortKey(name); - if (key.contains("gui") || key.contains("menu") || key.contains("display") || key.contains("hud")) { - return "UI"; - } - if (key.contains("sound") || key.contains("audio")) { - return "Audio"; - } - if (key.contains("lang") || key.contains("locale") || key.contains("translation")) { - return "Localization"; - } - if (key.contains("sql") || key.contains("database") || key.contains("storage") || key.contains("mysql")) { - return "Storage"; - } - if (key.contains("xp") || key.contains("level") || key.contains("knowledge") || key.contains("power")) { - return "Progression"; - } - if (key.contains("world") || key.contains("biome") || key.contains("dimension") || key.contains("region")) { - return "World"; - } - if (key.contains("thread") || key.contains("tick") || key.contains("async") || key.contains("performance") || key.contains("cache")) { - return "Performance"; - } - if (key.contains("permission") || key.contains("blacklist") || key.contains("whitelist") || key.contains("security")) { - return "Access"; - } - if (key.contains("debug") || key.contains("dev") || key.contains("test") || key.contains("verbose")) { - return "Debug"; - } - return "General"; - } - - private static List buildEntries(String sectionPath, Object sectionObject, String sourceTag) { - List sections = new ArrayList<>(); - List values = new ArrayList<>(); - for (Field field : getSerializableFields(sectionObject.getClass())) { - Object value = getFieldValue(field, sectionObject); - String childPath = joinPath(sectionPath, field.getName()); - ElementDescriptor descriptor = describe(field, value); - List docs = ConfigDocumentation.buildFieldComments(sourceTag, childPath, field, value); - FieldEntry entry = new FieldEntry(field, childPath, value, descriptor, docs); - if (descriptor.kind() == ElementKind.SECTION) { - sections.add(entry); - } else { - values.add(entry); - } - } - - sections.sort(Comparator.comparing(e -> normalizeSortKey(e.field().getName()))); - values.sort(Comparator.comparing(e -> normalizeSortKey(e.field().getName()))); - sections.addAll(values); - return sections; - } - - private static List buildCoreGeneralEntries() { - List values = new ArrayList<>(); - Object root = AdaptConfig.get(); - for (Field field : getSerializableFields(root.getClass())) { - Object value = getFieldValue(field, root); - ElementDescriptor descriptor = describe(field, value); - if (descriptor.kind() == ElementKind.SECTION) { - continue; - } - - String childPath = joinPath(ROOT_CORE, field.getName()); - List docs = ConfigDocumentation.buildFieldComments(SOURCE_TAG_CORE, childPath, field, value); - values.add(new FieldEntry(field, childPath, value, descriptor, docs)); - } - - values.sort(Comparator.comparing(e -> normalizeSortKey(e.field().getName()))); - return values; - } - - private static void openFieldEntries(Player player, String safePath, List entries, int page) { - GuiLayout.PagePlan plan = configPagePlan(entries.size()); - int currentPage = GuiLayout.clampPage(page, plan.pageCount()); - int start = currentPage * plan.itemsPerPage(); - int end = Math.min(entries.size(), start + plan.itemsPerPage()); - - Window w = new UIWindow(player); - GuiTheme.apply(w, tagForSection(safePath)); - w.setViewportHeight(plan.rows()); - - if (entries.isEmpty()) { - w.setElement(0, 0, new UIElement("cfg-empty") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.GRAY + "No settings in this section")); - } else { - List reveal = new ArrayList<>(); - for (int row = 0; row < plan.contentRows(); row++) { - int rowStart = start + (row * GuiLayout.WIDTH); - if (rowStart >= end) { - break; - } - - int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); - for (int i = 0; i < rowCount; i++) { - FieldEntry entry = entries.get(rowStart + i); - int pos = GuiLayout.centeredPosition(i, rowCount); - Element element = createElementForEntry(player, safePath, currentPage, entry); - reveal.add(new GuiEffects.Placement(pos, row, element)); - } - } - GuiEffects.applyReveal(w, reveal); - } - - int navRow = plan.rows() - 1; - applyPageControls(w, player, safePath, navRow, currentPage, plan.pageCount(), entries.size(), start, end); - if (!safePath.isBlank()) { - String parent = parentPath(safePath); - w.setElement(0, navRow, new UIElement("cfg-back") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.GRAY + "Back") - .onLeftClick((e) -> navigateTo(player, parent, 0))); - } - addSectionOverview(w, navRow, safePath, entries, currentPage, plan.pageCount()); - - String titlePath = safePath.isBlank() ? "root" : safePath; - if (safePath.equals(ROOT_CORE_GENERAL)) { - titlePath = "core.general"; - } - if (titlePath.length() > 24) { - titlePath = "..." + titlePath.substring(titlePath.length() - 21); - } - String pageSuffix = plan.pageCount() > 1 ? " [" + (currentPage + 1) + "/" + plan.pageCount() + "]" : ""; - w.setTitle(C.GRAY + "Configure: " + C.WHITE + titlePath + pageSuffix); - w.onClosed((window) -> onGuiClosed(player, safePath)); - w.open(); - Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); - } - - private static void openCoreIndex(Player player, int page) { - Object root = AdaptConfig.get(); - List entries = new ArrayList<>(); - int generalValues = 0; - for (Field field : getSerializableFields(root.getClass())) { - Object value = getFieldValue(field, root); - ElementDescriptor descriptor = describe(field, value); - if (descriptor.kind() == ElementKind.SECTION) { - int nested = value == null ? 0 : getSerializableFields(value.getClass()).size(); - entries.add(new SectionIndexEntry( - ROOT_CORE + "." + field.getName(), - displayName(field.getName()), - materialForSection(field.getName()), - "Open " + nested + " setting" + (nested == 1 ? "" : "s") - )); - } else { - generalValues++; - } - } - - if (generalValues > 0) { - entries.add(new SectionIndexEntry( - ROOT_CORE_GENERAL, - "General Settings", - Material.COMPARATOR, - "Open " + generalValues + " global option" + (generalValues == 1 ? "" : "s") - )); - } - - entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); - openSectionIndex(player, ROOT_CORE, page, "Configure: core", entries); - } - - private static void openCoreGeneral(Player player, int page) { - openFieldEntries(player, ROOT_CORE_GENERAL, buildCoreGeneralEntries(), page); - } - - private static void openRoot(Player player, int page) { - List entries = new ArrayList<>(); - entries.add(new SectionIndexEntry(ROOT_ADAPTATIONS_SKILLS, "Adaptations", Material.NETHER_STAR, "Configure adaptation settings")); - entries.add(new SectionIndexEntry(ROOT_CORE, "Core", Material.COMPARATOR, "Configure global Adapt settings")); - entries.add(new SectionIndexEntry(ROOT_SKILLS, "Skills", Material.ENCHANTED_BOOK, "Configure skill settings")); - entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); - openSectionIndex(player, "", page, "Configure Adapt", entries); - } - - private static void openSkillIndex(Player player, int page) { - List> skills = getLoadedSkills(); - List entries = new ArrayList<>(); - for (Skill skill : skills) { - if (skill == null) { - continue; - } - - entries.add(new SectionIndexEntry( - ROOT_SKILLS + "." + skill.getName(), - C.stripColor(skill.getDisplayName()), - skill.getIcon(), - "Configure " + skill.getName() - )); - } - - entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); - openSectionIndex(player, ROOT_SKILLS, page, "Configure: skills", entries); - } - - private static void openAdaptationSkillIndex(Player player, int page) { - List entries = new ArrayList<>(); - entries.add(new SectionIndexEntry( - ROOT_ADAPTATIONS_ALL, - "All Adaptations (A-Z)", - Material.NETHER_STAR, - "Browse every adaptation alphabetically" - )); - - for (Skill skill : getLoadedSkills()) { - if (skill == null) { - continue; - } - - int adaptationCount = skill.getAdaptations() == null ? 0 : skill.getAdaptations().size(); - if (adaptationCount <= 0) { - continue; - } - - entries.add(new SectionIndexEntry( - ROOT_ADAPTATIONS_SKILLS + "." + skill.getName(), - C.stripColor(skill.getDisplayName()), - skill.getIcon(), - "Browse " + adaptationCount + " adaptation" + (adaptationCount == 1 ? "" : "s") - )); - } - - List head = new ArrayList<>(); - List skillEntries = new ArrayList<>(); - for (SectionIndexEntry entry : entries) { - if (ROOT_ADAPTATIONS_ALL.equals(entry.path())) { - head.add(entry); - } else { - skillEntries.add(entry); - } - } - skillEntries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); - head.addAll(skillEntries); - openSectionIndex(player, ROOT_ADAPTATIONS_SKILLS, page, "Configure: adaptations", head); - } - - private static void openAdaptationIndexForSkill(Player player, String skillName, int page) { - Skill skill = resolveSkill(skillName); - if (skill == null) { - Adapt.messagePlayer(player, C.RED + "Unknown skill for adaptation config: " + C.WHITE + skillName); - navigateTo(player, ROOT_ADAPTATIONS_SKILLS, 0); - return; - } - - List entries = new ArrayList<>(); - for (Adaptation adaptation : skill.getAdaptations()) { - if (adaptation == null) { - continue; - } - entries.add(new SectionIndexEntry( - ROOT_ADAPTATIONS + "." + adaptation.getName(), - C.stripColor(adaptation.getDisplayName()), - adaptation.getIcon(), - "Open " + adaptation.getName() - )); - } - - entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); - openSectionIndex(player, ROOT_ADAPTATIONS_SKILLS + "." + skill.getName(), page, "Configure: " + C.stripColor(skill.getDisplayName()), entries); - } - - private static void openAdaptationIndex(Player player, int page) { - List entries = new ArrayList<>(); - for (Adaptation adaptation : getLoadedAdaptations()) { - if (adaptation == null || adaptation.getSkill() == null) { - continue; - } - - entries.add(new SectionIndexEntry( - ROOT_ADAPTATIONS + "." + adaptation.getName(), - C.stripColor(adaptation.getDisplayName()), - adaptation.getIcon(), - "Skill: " + adaptation.getSkill().getName() - )); - } - - entries.sort(Comparator.comparing(e -> normalizeSortKey(e.displayName()))); - openSectionIndex(player, ROOT_ADAPTATIONS_ALL, page, "Configure: all adaptations", entries); - } - - private static void openSectionIndex(Player player, String sectionPath, int page, String title, List entries) { - String safePath = normalizePath(sectionPath); - GuiLayout.PagePlan plan = configPagePlan(entries.size()); - int currentPage = GuiLayout.clampPage(page, plan.pageCount()); - int start = currentPage * plan.itemsPerPage(); - int end = Math.min(entries.size(), start + plan.itemsPerPage()); - - Window w = new UIWindow(player); - GuiTheme.apply(w, tagForSection(safePath)); - w.setViewportHeight(plan.rows()); - - if (entries.isEmpty()) { - w.setElement(0, 0, new UIElement("cfg-empty") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.GRAY + "No entries")); - } else { - List reveal = new ArrayList<>(); - for (int row = 0; row < plan.contentRows(); row++) { - int rowStart = start + (row * GuiLayout.WIDTH); - if (rowStart >= end) { - break; - } - - int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); - for (int i = 0; i < rowCount; i++) { - SectionIndexEntry entry = entries.get(rowStart + i); - int pos = GuiLayout.centeredPosition(i, rowCount); - Element element = new UIElement("cfg-index-" + entry.path()) - .setMaterial(new MaterialBlock(entry.material())) - .setName(C.WHITE + entry.displayName()) - .addLore(C.GRAY + entry.lore()) - .addLore(C.DARK_GRAY + "Path: " + entry.path()) - .setProgress(1D) - .onLeftClick((e) -> navigateTo(player, entry.path(), 0)); - reveal.add(new GuiEffects.Placement(pos, row, element)); - } - } - GuiEffects.applyReveal(w, reveal); - } - - int navRow = plan.rows() - 1; - applyPageControls(w, player, safePath, navRow, currentPage, plan.pageCount(), entries.size(), start, end); - if (!safePath.isBlank()) { - w.setElement(0, navRow, new UIElement("cfg-back") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.GRAY + "Back") - .onLeftClick((e) -> navigateTo(player, parentPath(safePath), 0))); - } - addIndexOverview(w, navRow, safePath, entries.size(), currentPage, plan.pageCount(), title); - - String pageSuffix = plan.pageCount() > 1 ? " [" + (currentPage + 1) + "/" + plan.pageCount() + "]" : ""; - w.setTitle(C.GRAY + title + pageSuffix); - w.onClosed((window) -> onGuiClosed(player, safePath)); - w.open(); - Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); - } - - private static GuiLayout.PagePlan configPagePlan(int totalEntries) { - int items = Math.max(0, totalEntries); - int rows = GuiLayout.MAX_ROWS; - int contentRows = CONFIG_CONTENT_ROWS; - int itemsPerPage = contentRows * GuiLayout.WIDTH; - int pageCount = Math.max(1, (int) Math.ceil(items / (double) itemsPerPage)); - return new GuiLayout.PagePlan(rows, contentRows, true, itemsPerPage, pageCount); - } - - private static void applyPageControls( - Window window, - Player player, - String safePath, - int navRow, - int currentPage, - int pageCount, - int totalEntries, - int start, - int end - ) { - if (pageCount <= 1) { - return; - } - - int jumpBack = Math.max(0, currentPage - PAGE_JUMP); - int jumpForward = Math.min(pageCount - 1, currentPage + PAGE_JUMP); - - if (currentPage > 0) { - window.setElement(-4, navRow, new UIElement("cfg-prev") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.WHITE + "Previous") - .addLore(C.GRAY + "Left click: previous page") - .addLore(C.GRAY + "Right click: jump -" + PAGE_JUMP + " pages") - .onLeftClick((e) -> navigateTo(player, safePath, currentPage - 1)) - .onRightClick((e) -> navigateTo(player, safePath, jumpBack))); - window.setElement(-3, navRow, new UIElement("cfg-first") - .setMaterial(new MaterialBlock(Material.LECTERN)) - .setName(C.GRAY + "First") - .onLeftClick((e) -> navigateTo(player, safePath, 0))); - } - - if (currentPage < pageCount - 1) { - window.setElement(4, navRow, new UIElement("cfg-next") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.WHITE + "Next") - .addLore(C.GRAY + "Left click: next page") - .addLore(C.GRAY + "Right click: jump +" + PAGE_JUMP + " pages") - .onLeftClick((e) -> navigateTo(player, safePath, currentPage + 1)) - .onRightClick((e) -> navigateTo(player, safePath, jumpForward))); - window.setElement(3, navRow, new UIElement("cfg-last") - .setMaterial(new MaterialBlock(Material.LECTERN)) - .setName(C.GRAY + "Last") - .onLeftClick((e) -> navigateTo(player, safePath, pageCount - 1))); - } - - int from = totalEntries <= 0 ? 0 : (start + 1); - int to = totalEntries <= 0 ? 0 : end; - window.setElement(-1, navRow, new UIElement("cfg-page-info") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.AQUA + "Page " + (currentPage + 1) + "/" + pageCount) - .addLore(C.GRAY + "Showing " + from + "-" + to + " of " + totalEntries) - .setProgress(1D)); - } - - private static void addSectionOverview( - Window window, - int navRow, - String path, - List entries, - int currentPage, - int pageCount - ) { - int sections = 0; - int editable = 0; - for (FieldEntry entry : entries) { - if (entry.descriptor().kind() == ElementKind.SECTION) { - sections++; - } - if (entry.descriptor().editable()) { - editable++; - } - } - - String safePath = path == null || path.isBlank() ? "root" : path; - window.setElement(1, navRow, new UIElement("cfg-overview") - .setMaterial(new MaterialBlock(Material.BOOK)) - .setName(C.AQUA + "Overview") - .addLore(C.GRAY + "Path: " + C.WHITE + safePath) - .addLore(C.GRAY + "Sections: " + C.WHITE + sections) - .addLore(C.GRAY + "Editable: " + C.WHITE + editable) - .addLore(C.GRAY + "Entries: " + C.WHITE + entries.size()) - .setProgress(1D)); - - window.setElement(2, navRow, new UIElement("cfg-help") - .setMaterial(new MaterialBlock(Material.KNOWLEDGE_BOOK)) - .setName(C.GRAY + "Help") - .addLore(C.GRAY + "LMB: open/edit/toggle") - .addLore(C.GRAY + "RMB: enum prev / page jump") - .addLore(C.GRAY + "ESC: back to parent page") - .addLore(C.DARK_GRAY + "Page " + (currentPage + 1) + "/" + pageCount) - .setProgress(1D)); - } - - private static void addIndexOverview( - Window window, - int navRow, - String path, - int totalEntries, - int currentPage, - int pageCount, - String title - ) { - String safePath = path == null || path.isBlank() ? "root" : path; - window.setElement(1, navRow, new UIElement("cfg-index-overview") - .setMaterial(new MaterialBlock(Material.BOOK)) - .setName(C.AQUA + "Directory") - .addLore(C.GRAY + "Path: " + C.WHITE + safePath) - .addLore(C.GRAY + "Entries: " + C.WHITE + totalEntries) - .addLore(C.GRAY + "Page: " + C.WHITE + (currentPage + 1) + "/" + pageCount) - .addLore(C.DARK_GRAY + title) - .setProgress(1D)); - - window.setElement(2, navRow, new UIElement("cfg-index-help") - .setMaterial(new MaterialBlock(Material.KNOWLEDGE_BOOK)) - .setName(C.GRAY + "Navigation") - .addLore(C.GRAY + "LMB: open section") - .addLore(C.GRAY + "RMB on arrows: jump pages") - .addLore(C.GRAY + "ESC: back to parent page") - .setProgress(1D)); - } - - private static String tagForSection(String sectionPath) { - String path = normalizePath(sectionPath); - if (path.isBlank()) { - return TAG_PREFIX; - } - return TAG_PREFIX + "/" + path; - } - - private static String normalizePath(String path) { - if (path == null) { - return ""; - } - - String normalized = path.trim(); - while (normalized.startsWith(".")) { - normalized = normalized.substring(1); - } - while (normalized.endsWith(".")) { - normalized = normalized.substring(0, normalized.length() - 1); - } - return normalized; - } - - private static String parentPath(String path) { - String normalized = normalizePath(path); - if (normalized.isBlank()) { - return ""; - } - - if (normalized.equals(ROOT_ADAPTATIONS) || normalized.equals(ROOT_ADAPTATIONS_SKILLS)) { - return ""; - } - - if (normalized.equals(ROOT_CORE_GENERAL)) { - return ROOT_CORE; - } - - if (normalized.equals(ROOT_ADAPTATIONS_ALL)) { - return ROOT_ADAPTATIONS_SKILLS; - } - - if (normalized.startsWith(ROOT_ADAPTATIONS_SKILLS + ".")) { - return ROOT_ADAPTATIONS_SKILLS; - } - - int dot = normalized.lastIndexOf('.'); - if (dot < 0) { - return ""; - } - return normalized.substring(0, dot); - } - - private static String joinPath(String base, String child) { - String left = normalizePath(base); - if (left.isBlank()) { - return child; - } - return left + "." + child; - } - - private static Object resolveSectionObject(Object root, String sectionPath, boolean createMissing) { - if (root == null) { - return null; - } - - String normalized = normalizePath(sectionPath); - if (normalized.isBlank()) { - return root; - } - - Object current = root; - for (String segment : normalized.split("\\Q.\\E")) { - Field field = findField(current.getClass(), segment); - if (field == null) { - return null; - } - - Object next = getFieldValue(field, current); - if (next == null && createMissing) { - next = instantiate(field.getType()); - if (next == null) { - return null; - } - if (!setFieldValue(field, current, next)) { - return null; - } - } - - if (next == null) { - return null; - } - - current = next; - } - - return current; - } - - private static boolean setPathValue(Object root, String path, Object value, boolean createMissing) { - String normalized = normalizePath(path); - if (normalized.isBlank()) { - return false; - } - - String[] segments = normalized.split("\\Q.\\E"); - if (segments.length == 0) { - return false; - } - - StringBuilder parentPath = new StringBuilder(); - for (int i = 0; i < segments.length - 1; i++) { - if (i > 0) { - parentPath.append('.'); - } - parentPath.append(segments[i]); - } - - Object section = resolveSectionObject(root, parentPath.toString(), createMissing); - if (section == null) { - return false; - } - - Field targetField = findField(section.getClass(), segments[segments.length - 1]); - if (targetField == null) { - return false; - } - - Object typedValue = coerceValue(value, targetField.getType()); - return setFieldValue(targetField, section, typedValue); - } - - private static Object readPathValue(Object root, String path) { - String normalized = normalizePath(path); - if (normalized.isBlank()) { - return null; - } - - String[] segments = normalized.split("\\Q.\\E"); - if (segments.length == 0) { - return null; - } - - StringBuilder parentPath = new StringBuilder(); - for (int i = 0; i < segments.length - 1; i++) { - if (i > 0) { - parentPath.append('.'); - } - parentPath.append(segments[i]); - } - - Object section = resolveSectionObject(root, parentPath.toString(), false); - if (section == null) { - return null; - } - - Field field = findField(section.getClass(), segments[segments.length - 1]); - if (field == null) { - return null; - } - - return getFieldValue(field, section); - } - - private static Field findField(Class type, String name) { - Class current = type; - while (current != null && current != Object.class) { - try { - Field field = current.getDeclaredField(name); - field.setAccessible(true); - return field; - } catch (NoSuchFieldException ignored) { - current = current.getSuperclass(); - } - } - - return null; - } - - private static List getSerializableFields(Class type) { - List fields = new ArrayList<>(); - collectFields(type, fields); - return fields; - } - - private static void collectFields(Class type, List out) { - if (type == null || type == Object.class) { - return; - } - - collectFields(type.getSuperclass(), out); - for (Field field : type.getDeclaredFields()) { - if (field.isSynthetic()) { - continue; - } - - int modifiers = field.getModifiers(); - if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) { - continue; - } - - field.setAccessible(true); - out.add(field); - } - } - - private static Object instantiate(Class type) { - Class normalized = normalizeType(type); - if (normalized.isPrimitive() || normalized.isEnum() || normalized == String.class || isNumericType(normalized) || normalized == Boolean.class) { - return null; - } - - try { - return normalized.getDeclaredConstructor().newInstance(); - } catch (Throwable ignored) { - return null; - } - } - - private static boolean setFieldValue(Field field, Object target, Object value) { - try { - field.setAccessible(true); - field.set(target, value); - return true; - } catch (Throwable ignored) { - return false; - } - } - - private static Object getFieldValue(Field field, Object target) { - try { - field.setAccessible(true); - return field.get(target); - } catch (Throwable ignored) { - return null; - } - } - - private static Object coerceValue(Object value, Class targetType) { - if (value == null) { - return null; - } - - Class normalizedTarget = normalizeType(targetType); - Class valueType = value.getClass(); - if (normalizedTarget.isAssignableFrom(valueType)) { - return value; - } - - ParseResult parsed = parseInputValue(targetType, String.valueOf(value)); - return parsed.success() ? parsed.value() : value; - } - - private static Class normalizeType(Class type) { - if (type == null || !type.isPrimitive()) { - return type; - } - - if (type == int.class) return Integer.class; - if (type == long.class) return Long.class; - if (type == double.class) return Double.class; - if (type == float.class) return Float.class; - if (type == short.class) return Short.class; - if (type == byte.class) return Byte.class; - if (type == boolean.class) return Boolean.class; - if (type == char.class) return Character.class; - return type; - } - - private static boolean isNumericType(Class type) { - return type == Integer.class - || type == Long.class - || type == Double.class - || type == Float.class - || type == Short.class - || type == Byte.class; - } - - private static boolean isSectionType(Class type) { - Class normalized = normalizeType(type); - if (normalized == null) { - return false; - } - - if (normalized.isPrimitive() || normalized.isEnum()) { - return false; - } - - if (normalized == String.class || normalized == Character.class || normalized == Boolean.class || isNumericType(normalized)) { - return false; - } - - if (Map.class.isAssignableFrom(normalized) || Collection.class.isAssignableFrom(normalized) || normalized.isArray()) { - return false; - } - - return true; - } - - private static Object cycleEnum(Class enumType, Object current, int direction) { - Class normalized = normalizeType(enumType); - if (normalized == null || !normalized.isEnum()) { - return null; - } - - Object[] constants = normalized.getEnumConstants(); - if (constants == null || constants.length == 0) { - return null; - } - - int currentIndex = 0; - if (current != null) { - for (int i = 0; i < constants.length; i++) { - if (Objects.equals(constants[i], current)) { - currentIndex = i; - break; - } - } - } - - int nextIndex = currentIndex + direction; - if (nextIndex < 0) { - nextIndex = constants.length - 1; - } else if (nextIndex >= constants.length) { - nextIndex = 0; - } - return constants[nextIndex]; - } - - private static Object parseEnumConstant(Class enumType, String value) { - if (enumType == null || !enumType.isEnum() || value == null) { - return null; - } - - for (Object constant : enumType.getEnumConstants()) { - if (constant == null) { - continue; - } - - if (constant.toString().equalsIgnoreCase(value)) { - return constant; - } - } - - return null; - } - - private static String enumConstants(Class enumType) { - if (enumType == null || !enumType.isEnum()) { - return ""; - } - - List values = new ArrayList<>(); - for (Object constant : enumType.getEnumConstants()) { - if (constant == null) { - continue; - } - values.add(constant.toString()); - } - return String.join(", ", values); - } - - private static String displayName(String key) { - if (key == null || key.isBlank()) { - return "Unnamed"; - } - - String spaced = key - .replace('_', ' ') - .replace('-', ' ') - .replaceAll("([a-z])([A-Z])", "$1 $2") - .trim(); - if (spaced.isBlank()) { - return key; - } - return Character.toUpperCase(spaced.charAt(0)) + spaced.substring(1); - } - - private static String summarizeValue(Object value) { - if (value == null) { - return "null"; - } - - if (value instanceof Map map) { - return "map(" + map.size() + ")"; - } - if (value instanceof Collection collection) { - return "list(" + collection.size() + ")"; - } - if (value.getClass().isArray()) { - return "array"; - } - - String text = String.valueOf(value) - .replace("\n", "\\n") - .replace("\r", "\\r"); - if (text.length() > MAX_VALUE_PREVIEW) { - return text.substring(0, MAX_VALUE_PREVIEW - 3) + "..."; - } - return text; - } - - private static void refreshGlobalRuntimeSettings() { - Adapt.wordKey.clear(); - if (AdaptConfig.get().isAutoUpdateLanguage()) { - Localizer.updateLanguageFile(); - } - - if (AdaptConfig.get().isCustomModels()) { - CustomModel.reloadFromDisk(); - } else { - CustomModel.clear(); - } - } - - private static SectionTarget resolveSectionTarget(String path, boolean createMissing) { - String normalized = normalizePath(path); - if (normalized.isBlank()) { - return new SectionTarget(SOURCE_TAG_CORE, resolveSectionObject(AdaptConfig.get(), "", createMissing)); - } - - if (normalized.equals(ROOT_CORE) || normalized.startsWith(ROOT_CORE + ".")) { - String objectPath = stripPrefix(normalized, ROOT_CORE); - return new SectionTarget(SOURCE_TAG_CORE, resolveSectionObject(AdaptConfig.get(), objectPath, createMissing)); - } - - if (normalized.equals(ROOT_SKILLS) || normalized.startsWith(ROOT_SKILLS + ".")) { - String payload = stripPrefix(normalized, ROOT_SKILLS); - if (payload.isBlank()) { - return null; - } - - String[] parts = payload.split("\\Q.\\E", 2); - Skill skill = resolveSkill(parts[0]); - if (skill == null || skill.getConfig() == null) { - return null; - } - - String objectPath = parts.length > 1 ? parts[1] : ""; - return new SectionTarget("skill:" + skill.getName(), resolveSectionObject(skill.getConfig(), objectPath, createMissing)); - } - - if (normalized.equals(ROOT_ADAPTATIONS) || normalized.startsWith(ROOT_ADAPTATIONS + ".")) { - String payload = stripPrefix(normalized, ROOT_ADAPTATIONS); - if (payload.isBlank()) { - return null; - } - - String[] parts = payload.split("\\Q.\\E", 2); - Adaptation adaptation = resolveAdaptation(parts[0]); - if (adaptation == null || adaptation.getConfig() == null) { - return null; - } - - String objectPath = parts.length > 1 ? parts[1] : ""; - return new SectionTarget("adaptation:" + adaptation.getName(), resolveSectionObject(adaptation.getConfig(), objectPath, createMissing)); - } - - return new SectionTarget(SOURCE_TAG_CORE, resolveSectionObject(AdaptConfig.get(), normalized, createMissing)); - } - - private static EditTarget resolveEditTarget(String path) { - String normalized = normalizePath(path); - if (normalized.isBlank()) { - return null; - } - - if (normalized.equals(ROOT_CORE) || normalized.startsWith(ROOT_CORE + ".")) { - String objectPath = stripPrefix(normalized, ROOT_CORE); - if (objectPath.isBlank()) { - return null; - } - return new EditTarget( - SOURCE_TAG_CORE, - AdaptConfig.get(), - objectPath, - Adapt.instance.getDataFile("adapt", "adapt.toml"), - AdaptConfig::reload, - ConfigGui::refreshGlobalRuntimeSettings - ); - } - - if (normalized.equals(ROOT_SKILLS) || normalized.startsWith(ROOT_SKILLS + ".")) { - String payload = stripPrefix(normalized, ROOT_SKILLS); - if (payload.isBlank()) { - return null; - } - - String[] parts = payload.split("\\Q.\\E", 2); - Skill skill = resolveSkill(parts[0]); - if (skill == null || skill.getConfig() == null || Adapt.instance == null || Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { - return null; - } - - String objectPath = parts.length > 1 ? parts[1] : ""; - if (objectPath.isBlank()) { - return null; - } - - return new EditTarget( - "skill:" + skill.getName(), - skill.getConfig(), - objectPath, - Adapt.instance.getDataFile("adapt", "skills", skill.getName() + ".toml"), - () -> Adapt.instance.getAdaptServer().getSkillRegistry().hotReloadSkillConfig(skill.getName()), - () -> { - } - ); - } - - if (normalized.equals(ROOT_ADAPTATIONS) || normalized.startsWith(ROOT_ADAPTATIONS + ".")) { - String payload = stripPrefix(normalized, ROOT_ADAPTATIONS); - if (payload.isBlank()) { - return null; - } - - String[] parts = payload.split("\\Q.\\E", 2); - Adaptation adaptation = resolveAdaptation(parts[0]); - if (adaptation == null || adaptation.getConfig() == null || !(adaptation instanceof SimpleAdaptation simpleAdaptation)) { - return null; - } - - String objectPath = parts.length > 1 ? parts[1] : ""; - if (objectPath.isBlank()) { - return null; - } - - return new EditTarget( - "adaptation:" + adaptation.getName(), - adaptation.getConfig(), - objectPath, - Adapt.instance.getDataFile("adapt", "adaptations", adaptation.getName() + ".toml"), - () -> simpleAdaptation.reloadConfigFromDisk(false), - () -> { - } - ); - } - - return new EditTarget( - SOURCE_TAG_CORE, - AdaptConfig.get(), - normalized, - Adapt.instance.getDataFile("adapt", "adapt.toml"), - AdaptConfig::reload, - ConfigGui::refreshGlobalRuntimeSettings - ); - } - - private static String stripPrefix(String path, String prefix) { - String normalized = normalizePath(path); - if (normalized.equals(prefix)) { - return ""; - } - - String withDot = prefix + "."; - if (normalized.startsWith(withDot)) { - return normalized.substring(withDot.length()); - } - return normalized; - } - - private static Skill resolveSkill(String skillName) { - if (skillName == null || skillName.isBlank()) { - return null; - } - if (Adapt.instance == null || Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { - return null; - } - return Adapt.instance.getAdaptServer().getSkillRegistry().getAnySkill(skillName); - } - - private static Adaptation resolveAdaptation(String adaptationName) { - if (adaptationName == null || adaptationName.isBlank()) { - return null; - } - - for (Skill skill : getLoadedSkills()) { - if (skill == null) { - continue; - } - - for (Adaptation adaptation : skill.getAdaptations()) { - if (adaptation == null || adaptation.getName() == null) { - continue; - } - - if (adaptation.getName().equalsIgnoreCase(adaptationName)) { - return adaptation; - } - } - } - - return null; - } - - private static List> getLoadedSkills() { - if (Adapt.instance == null || Adapt.instance.getAdaptServer() == null || Adapt.instance.getAdaptServer().getSkillRegistry() == null) { - return List.of(); - } - - List> skills = new ArrayList<>(Adapt.instance.getAdaptServer().getSkillRegistry().getAllSkills()); - skills.sort(Comparator.comparing(skill -> normalizeSortKey(C.stripColor(skill.getDisplayName())))); - return skills; - } - - private static List> getLoadedAdaptations() { - List> adaptations = new ArrayList<>(); - for (Skill skill : getLoadedSkills()) { - if (skill == null) { - continue; - } - for (Adaptation adaptation : skill.getAdaptations()) { - if (adaptation == null) { - continue; - } - adaptations.add(adaptation); - } - } - - adaptations.sort(Comparator.comparing(adaptation -> normalizeSortKey(C.stripColor(adaptation.getDisplayName())))); - return adaptations; - } - - private static String normalizeSortKey(String value) { - if (value == null) { - return ""; - } - - String normalized = C.stripColor(value).toLowerCase(Locale.ROOT).trim(); - return normalized.replaceFirst("^[^\\p{L}\\p{N}]+", ""); - } - - private static void playPageTurn(Player player) { - SoundPlayer spw = SoundPlayer.of(player.getWorld()); - spw.play(player.getLocation(), org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 1.1f, 1.255f); - spw.play(player.getLocation(), org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 0.7f, 1.455f); - spw.play(player.getLocation(), org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 0.3f, 1.855f); - } - - private static void navigateTo(Player player, String path, int page) { - if (player == null) { - return; - } - suppressClose(player); - open(player, path, page); - } - - public static void suppressClose(Player player) { - if (player == null) { - return; - } - - UUID playerId = player.getUniqueId(); - long suppressUntil = M.ms() + CLOSE_SUPPRESS_MS; - CLOSE_SUPPRESS_UNTIL.put(playerId, suppressUntil); - J.s(() -> { - Long current = CLOSE_SUPPRESS_UNTIL.get(playerId); - if (current != null && current == suppressUntil) { - CLOSE_SUPPRESS_UNTIL.remove(playerId); - } - }, CLOSE_SUPPRESS_CLEAR_TICKS); - } - - private static boolean consumeCloseSuppression(Player player) { - if (player == null) { - return false; - } - - Long until = CLOSE_SUPPRESS_UNTIL.get(player.getUniqueId()); - if (until == null) { - return false; - } - - if (until >= M.ms()) { - CLOSE_SUPPRESS_UNTIL.remove(player.getUniqueId()); - return true; - } - - CLOSE_SUPPRESS_UNTIL.remove(player.getUniqueId()); - return false; - } - - private static void onGuiClosed(Player player, String currentPath) { - if (player == null) { - return; - } - - Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString()); - if (consumeCloseSuppression(player)) { - return; - } - - if (AdaptConfig.get().isEscClosesAllGuis()) { - return; - } - - String safePath = normalizePath(currentPath); - if (safePath.isBlank()) { - return; - } - - String parent = parentPath(safePath); - J.s(() -> { - if (player.isOnline() && player.getOpenInventory().getTopInventory().getType() == InventoryType.CRAFTING) { - open(player, parent, 0); - } - }, 1); - } - - private record FieldEntry(Field field, String path, Object value, ElementDescriptor descriptor, List docs) { - } - - private record ElementDescriptor(ElementKind kind, boolean editable) { - } - - private record SectionIndexEntry(String path, String displayName, Material material, String lore) { - } - - private record SectionTarget(String sourceTag, Object sectionObject) { - } - - private record EditTarget( - String sourceTag, - Object rootObject, - String objectPath, - File file, - BooleanSupplier reload, - Runnable afterReload - ) { - } - - private enum ElementKind { - BOOLEAN, - NUMBER, - STRING, - ENUM, - SECTION, - MAP, - LIST, - UNSUPPORTED - } - - public record ParseResult(boolean success, Object value, String error) { - public static ParseResult ok(Object value) { - return new ParseResult(true, value, ""); - } - - public static ParseResult fail(String error) { - return new ParseResult(false, null, error == null ? "Invalid value." : error); - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/gui/SkillsGui.java b/src/main/java/com/volmit/adapt/content/gui/SkillsGui.java deleted file mode 100644 index f548b2b43..000000000 --- a/src/main/java/com/volmit/adapt/content/gui/SkillsGui.java +++ /dev/null @@ -1,198 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.gui; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.api.xp.XP; -import com.volmit.adapt.util.*; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; - -public class SkillsGui { - private static final int PAGE_JUMP = 5; - - public static void open(Player player) { - open(player, 0); - } - - public static void open(Player player, int page) { - if (!Bukkit.isPrimaryThread()) { - int targetPage = page; - J.s(() -> open(player, targetPage)); - return; - } - - AdaptPlayer adaptPlayer = Adapt.instance.getAdaptServer().getPlayer(player); - if (adaptPlayer == null) { - Adapt.error("Failed to open skills gui for " + player.getName() + " because they are not Online, Were Kicked, Or are a fake player."); - return; - } - - List entries = new ArrayList<>(); - for (PlayerSkillLine line : adaptPlayer.getData().getSkillLines().sortV()) { - Skill skill = line.getRawSkill(adaptPlayer); - if (skill == null) { - continue; - } - if (!skill.isEnabled()) { - continue; - } - if (skill.hasBlacklistPermission(adaptPlayer.getPlayer(), skill) || line.getLevel() < 0) { - continue; - } - - int adaptationLevel = 0; - for (PlayerAdaptation adaptation : line.getAdaptations().sortV()) { - adaptationLevel += adaptation.getLevel(); - } - - entries.add(new SkillPageEntry(skill, line, adaptationLevel)); - } - - entries.sort(Comparator.comparing(entry -> normalizeSortKey(entry.skill().getDisplayName()))); - - boolean reserveNavigation = false; - GuiLayout.PagePlan plan = GuiLayout.plan(entries.size(), reserveNavigation); - int currentPage = GuiLayout.clampPage(page, plan.pageCount()); - int start = currentPage * plan.itemsPerPage(); - int end = Math.min(entries.size(), start + plan.itemsPerPage()); - - Window w = new UIWindow(player); - GuiTheme.apply(w, "/"); - w.setViewportHeight(plan.rows()); - - if (entries.isEmpty()) { - w.setElement(0, 0, new UIElement("skills-empty") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.GRAY + "No skills available") - .addLore(C.DARK_GRAY + "No eligible skills were found for this player.")); - } else { - List reveal = new ArrayList<>(); - for (int row = 0; row < plan.contentRows(); row++) { - int rowStart = start + (row * GuiLayout.WIDTH); - if (rowStart >= end) { - break; - } - - int rowCount = Math.min(GuiLayout.WIDTH, end - rowStart); - for (int i = 0; i < rowCount; i++) { - SkillPageEntry entry = entries.get(rowStart + i); - int pos = GuiLayout.centeredPosition(i, rowCount); - Element element = new UIElement("skill-" + entry.skill().getName()) - .setMaterial(new MaterialBlock(entry.skill().getIcon())) - .setModel(entry.skill().getModel()) - .setName(entry.skill().getDisplayName(entry.line().getLevel())) - .setProgress(1D) - .addLore(C.ITALIC + "" + C.GRAY + entry.skill().getDescription()) - .addLore(C.UNDERLINE + "" + C.WHITE + entry.line().getKnowledge() + C.RESET + " " + C.GRAY + Localizer.dLocalize("snippets.gui.knowledge")) - .addLore(C.ITALIC + "" + C.GRAY + Localizer.dLocalize("snippets.gui.power_used") + " " + C.DARK_GREEN + entry.adaptationLevel()) - .onLeftClick((e) -> entry.skill().openGui(player)); - reveal.add(new GuiEffects.Placement(pos, row, element)); - } - } - GuiEffects.applyReveal(w, reveal); - } - - if (plan.hasNavigationRow()) { - int navRow = plan.rows() - 1; - applyPageControls(w, player, navRow, currentPage, plan.pageCount(), entries.size(), start, end); - - } - - String pageSuffix = plan.pageCount() > 1 ? " [" + (currentPage + 1) + "/" + plan.pageCount() + "]" : ""; - w.setTitle(Localizer.dLocalize("snippets.gui.level") + " " + (int) XP.getLevelForXp(adaptPlayer.getData().getMasterXp()) + " (" + adaptPlayer.getData().getUsedPower() + "/" + adaptPlayer.getData().getMaxPower() + " " + Localizer.dLocalize("snippets.gui.power_used") + ")" + pageSuffix); - w.open(); - w.onClosed((e) -> Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString())); - Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); - } - - private record SkillPageEntry(Skill skill, PlayerSkillLine line, int adaptationLevel) { - } - - private static String normalizeSortKey(String value) { - if (value == null) { - return ""; - } - - String normalized = C.stripColor(value).toLowerCase(Locale.ROOT).trim(); - return normalized.replaceFirst("^[^\\p{L}\\p{N}]+", ""); - } - - private static void applyPageControls( - Window window, - Player player, - int navRow, - int currentPage, - int pageCount, - int totalEntries, - int start, - int end - ) { - if (pageCount <= 1) { - return; - } - - int jumpBack = Math.max(0, currentPage - PAGE_JUMP); - int jumpForward = Math.min(pageCount - 1, currentPage + PAGE_JUMP); - - if (currentPage > 0) { - window.setElement(-4, navRow, new UIElement("skills-prev") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.WHITE + "Previous") - .addLore(C.GRAY + "Right click: jump -" + PAGE_JUMP + " pages") - .onLeftClick((e) -> open(player, currentPage - 1)) - .onRightClick((e) -> open(player, jumpBack))); - window.setElement(-3, navRow, new UIElement("skills-first") - .setMaterial(new MaterialBlock(Material.LECTERN)) - .setName(C.GRAY + "First") - .onLeftClick((e) -> open(player, 0))); - } - - if (currentPage < pageCount - 1) { - window.setElement(4, navRow, new UIElement("skills-next") - .setMaterial(new MaterialBlock(Material.ARROW)) - .setName(C.WHITE + "Next") - .addLore(C.GRAY + "Right click: jump +" + PAGE_JUMP + " pages") - .onLeftClick((e) -> open(player, currentPage + 1)) - .onRightClick((e) -> open(player, jumpForward))); - window.setElement(3, navRow, new UIElement("skills-last") - .setMaterial(new MaterialBlock(Material.LECTERN)) - .setName(C.GRAY + "Last") - .onLeftClick((e) -> open(player, pageCount - 1))); - } - - int from = totalEntries <= 0 ? 0 : (start + 1); - int to = totalEntries <= 0 ? 0 : end; - window.setElement(-1, navRow, new UIElement("skills-page-info") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.AQUA + "Page " + (currentPage + 1) + "/" + pageCount) - .addLore(C.GRAY + "Showing " + from + "-" + to + " of " + totalEntries) - .setProgress(1D)); - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/BoundEnderPearl.java b/src/main/java/com/volmit/adapt/content/item/BoundEnderPearl.java deleted file mode 100644 index 0553838b3..000000000 --- a/src/main/java/com/volmit/adapt/content/item/BoundEnderPearl.java +++ /dev/null @@ -1,103 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.item.DataItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Localizer; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.List; - -@AllArgsConstructor -@Data -public class BoundEnderPearl implements DataItem { - public static BoundEnderPearl io = new BoundEnderPearl(); - - public static Block getBlock(ItemStack stack) { - if (io.getData(stack) != null) { - return io.getData(stack).getBlock(); - } - - return null; - } - - public static void setData(ItemStack item, Block t) { - io.setData(item, new Data(t)); - } - - public static ItemStack withData(Block t) { - return io.withData(new Data(t)); - } - - public static boolean isBindableItem(ItemStack t) { - if (t.getType().equals(Material.ENDER_PEARL)) { - if (t.getItemMeta() != null && t.getItemMeta().getLore() != null) { - if (t.getItemMeta().getLore().get(0).contains(Localizer.dLocalize("items.bound_ender_peral.name"))) { - Adapt.verbose("Enderpearl is bindable: " + t.getType().name()); - return true; - } - } - } - return false; - } - - @Override - public Material getMaterial() { - return Material.ENDER_PEARL; - } - - @Override - public Class getType() { - return BoundEnderPearl.Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - lore.add(C.WHITE + Localizer.dLocalize("items.bound_ender_peral.name")); - lore.add(C.GRAY + Localizer.dLocalize("items.bound_ender_peral.usage1")); - lore.add(C.GRAY + Localizer.dLocalize("items.bound_ender_peral.usage2")); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS); - meta.setDisplayName(Localizer.dLocalize("items.bound_ender_peral.name")); - - } - - @AllArgsConstructor - @lombok.Data - public static class Data { - private Block block; - - public static BoundEnderPearl.Data at(Block l) { - return new BoundEnderPearl.Data(l); - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/BoundEyeOfEnder.java b/src/main/java/com/volmit/adapt/content/item/BoundEyeOfEnder.java deleted file mode 100644 index 33d11589e..000000000 --- a/src/main/java/com/volmit/adapt/content/item/BoundEyeOfEnder.java +++ /dev/null @@ -1,102 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.item.DataItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Localizer; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.List; - -@AllArgsConstructor -@Data -public class BoundEyeOfEnder implements DataItem { - public static BoundEyeOfEnder io = new BoundEyeOfEnder(); - - public static Location getLocation(ItemStack stack) { - if (io.getData(stack) != null) { - return io.getData(stack).getLocation(); - } - - return null; - } - - public static void setData(ItemStack item, Location t) { - io.setData(item, new Data(t)); - } - - public static ItemStack withData(Location t) { - return io.withData(new Data(t)); - } - - public static boolean isBindableItem(ItemStack t) { - if (t.getType().equals(Material.ENDER_EYE)) { - if (t.getItemMeta() != null && t.getItemMeta().getLore() != null) { - if (t.getItemMeta().getLore().get(0).contains(Localizer.dLocalize("items.bound_eye_of_ender.name"))) { - Adapt.verbose("Eye of ender is bindable: " + t.getType().name()); - return true; - } - } - } - return false; - } - - @Override - public Material getMaterial() { - return Material.ENDER_EYE; - } - - @Override - public Class getType() { - return BoundEyeOfEnder.Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - lore.add(C.WHITE + Localizer.dLocalize("items.bound_eye_of_ender.name")); - lore.add(C.GRAY + Localizer.dLocalize("items.bound_eye_of_ender.usage1")); - lore.add(C.GRAY + Localizer.dLocalize("items.bound_eye_of_ender.usage2")); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_DYE); - meta.setDisplayName(Localizer.dLocalize("items.bound_eye_of_ender.name")); - } - - @AllArgsConstructor - @lombok.Data - public static class Data { - private Location location; - - public static BoundEyeOfEnder.Data at(Location l) { - return new BoundEyeOfEnder.Data(l); - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/BoundRedstoneTorch.java b/src/main/java/com/volmit/adapt/content/item/BoundRedstoneTorch.java deleted file mode 100644 index a3df6f2bc..000000000 --- a/src/main/java/com/volmit/adapt/content/item/BoundRedstoneTorch.java +++ /dev/null @@ -1,109 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.item.DataItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Localizer; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.List; - -@AllArgsConstructor -@Data -public class BoundRedstoneTorch implements DataItem { - public static BoundRedstoneTorch io = new BoundRedstoneTorch(); - - public static Location getLocation(ItemStack stack) { - if (io.getData(stack) != null) { - return io.getData(stack).getLocation(); - } - - return null; - } - - /* - renamed from hasData as the types are the same (ItemStack -> boolean), but this is static - */ - public static boolean hasItemData(ItemStack stack) { - return io.hasData(stack); - } - - public static void setData(ItemStack item, Location t) { - io.setData(item, new Data(t)); - } - - public static ItemStack withData(Location t) { - return io.withData(new Data(t)); - } - - public static boolean isBindableItem(ItemStack t) { - if (t.getType().equals(Material.REDSTONE_TORCH)) { - if (t.getItemMeta() != null && t.getItemMeta().getLore() != null) { - if (t.getItemMeta().getLore().get(0).contains(Localizer.dLocalize("items.bound_redstone_torch.name"))) { - Adapt.verbose("Torch is bindable: " + t.getType().name()); - return true; - } - } - } - return false; - } - - @Override - public Material getMaterial() { - return Material.REDSTONE_TORCH; - } - - @Override - public Class getType() { - return BoundRedstoneTorch.Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - lore.add(C.WHITE + Localizer.dLocalize("items.bound_redstone_torch.name")); - lore.add(C.GRAY + Localizer.dLocalize("items.bound_redstone_torch.usage1")); - lore.add(C.GRAY + Localizer.dLocalize("items.bound_redstone_torch.usage2")); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_DYE); - meta.setDisplayName(Localizer.dLocalize("items.bound_redstone_torch.name")); - } - - @AllArgsConstructor - @lombok.Data - public static class Data { - private Location location; - - public static BoundRedstoneTorch.Data at(Location l) { - return new BoundRedstoneTorch.Data(l); - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/BoundSnowBall.java b/src/main/java/com/volmit/adapt/content/item/BoundSnowBall.java deleted file mode 100644 index 276db93e5..000000000 --- a/src/main/java/com/volmit/adapt/content/item/BoundSnowBall.java +++ /dev/null @@ -1,102 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.item.DataItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Localizer; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.List; - -@AllArgsConstructor -@Data -public class BoundSnowBall implements DataItem { - public static BoundSnowBall io = new BoundSnowBall(); - - public static Player getPlayer(ItemStack stack) { - if (io.getData(stack) != null) { - return io.getData(stack).getPlayer(); - } - - return null; - } - - public static void setData(ItemStack item, Player t) { - io.setData(item, new Data(t)); - } - - public static ItemStack withData(Player t) { - return io.withData(new Data(t)); - } - - public static boolean isBindableItem(ItemStack t) { - if (t.getType().equals(Material.SNOWBALL)) { - if (t.getItemMeta() != null && t.getItemMeta().getLore() != null) { - if (t.getItemMeta().getLore().get(0).contains(Localizer.dLocalize("items.bound_snowball.name"))) { - Adapt.verbose("Snowball is bindable: " + t.getType().name()); - return true; - } - } - } - return false; - } - - - @Override - public Material getMaterial() { - return Material.SNOWBALL; - } - - @Override - public Class getType() { - return BoundSnowBall.Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - lore.add(C.WHITE + Localizer.dLocalize("items.bound_snowball.name")); - lore.add(C.GRAY + Localizer.dLocalize("items.bound_snowball.usage1")); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlag.HIDE_DYE); - meta.setDisplayName(Localizer.dLocalize("items.bound_snowball.name")); - } - - @AllArgsConstructor - @lombok.Data - public static class Data { - private Player player; - - public static BoundSnowBall.Data at(Player p) { - return new BoundSnowBall.Data(p); - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/ChronoTimeBombItem.java b/src/main/java/com/volmit/adapt/content/item/ChronoTimeBombItem.java deleted file mode 100644 index aec278856..000000000 --- a/src/main/java/com/volmit/adapt/content/item/ChronoTimeBombItem.java +++ /dev/null @@ -1,103 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.item.DataItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.ItemFlags; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.persistence.PersistentDataType; -import org.bukkit.potion.PotionType; - -import java.util.List; - -@AllArgsConstructor -@Data -public class ChronoTimeBombItem implements DataItem { - public static ChronoTimeBombItem io = new ChronoTimeBombItem(); - - public static boolean isBindableItem(ItemStack stack) { - if (stack == null || stack.getItemMeta() == null) { - return false; - } - - if (stack.getType() == Material.LINGERING_POTION) { - return io.hasData(stack); - } - - if (stack.getType() == Material.CLOCK) { - if (Adapt.instance == null) { - return false; - } - - NamespacedKey key = new NamespacedKey(Adapt.instance, Data.class.getCanonicalName().hashCode() + ""); - return stack.getItemMeta().getPersistentDataContainer().has(key, PersistentDataType.STRING); - } - - return false; - } - - public static ItemStack withData() { - return io.withData(new Data(System.currentTimeMillis())); - } - - @Override - public Material getMaterial() { - return Material.LINGERING_POTION; - } - - @Override - public Class getType() { - return Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - lore.add(C.WHITE + Localizer.dLocalize("items.chrono_time_bomb.name")); - lore.add(C.GRAY + Localizer.dLocalize("items.chrono_time_bomb.usage1")); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - if (meta instanceof PotionMeta potionMeta) { - potionMeta.setBasePotionType(PotionType.WEAKNESS); - meta = potionMeta; - } - - meta.addEnchant(Enchantment.BINDING_CURSE, 1, true); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlags.HIDE_POTION_EFFECTS); - meta.setDisplayName(Localizer.dLocalize("items.chrono_time_bomb.name")); - } - - @AllArgsConstructor - @lombok.Data - public static class Data { - private long created; - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/ChronoTimeBottle.java b/src/main/java/com/volmit/adapt/content/item/ChronoTimeBottle.java deleted file mode 100644 index 4b8968adb..000000000 --- a/src/main/java/com/volmit/adapt/content/item/ChronoTimeBottle.java +++ /dev/null @@ -1,97 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.api.item.DataItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.ItemFlags; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Color; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.potion.PotionType; - -import java.util.List; - -@AllArgsConstructor -@Data -public class ChronoTimeBottle implements DataItem { - public static ChronoTimeBottle io = new ChronoTimeBottle(); - - public static boolean isBindableItem(ItemStack stack) { - return stack != null && stack.getType() == Material.POTION && io.hasData(stack); - } - - public static double getStoredSeconds(ItemStack stack) { - Data data = io.getData(stack); - return data == null ? 0 : Math.max(0, data.getStoredSeconds()); - } - - public static void setStoredSeconds(ItemStack stack, double seconds) { - io.setData(stack, new Data(Math.max(0, seconds))); - } - - public static ItemStack withStoredSeconds(double seconds) { - return io.withData(new Data(Math.max(0, seconds))); - } - - @Override - public Material getMaterial() { - return Material.POTION; - } - - @Override - public Class getType() { - return Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - lore.add(C.WHITE + Localizer.dLocalize("items.chrono_time_bottle.name")); - lore.add(C.GRAY + Localizer.dLocalize("items.chrono_time_bottle.usage1")); - lore.add(C.GRAY + Localizer.dLocalize("items.chrono_time_bottle.usage2")); - lore.add(C.AQUA + Localizer.dLocalize("items.chrono_time_bottle.stored") + ": " + C.WHITE + Form.duration((long) (Math.max(0, data.getStoredSeconds()) * 1000D), 1)); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - if (meta instanceof PotionMeta potionMeta) { - potionMeta.setBasePotionType(PotionType.WATER); - potionMeta.setColor(Color.fromRGB(235, 245, 255)); - meta = potionMeta; - } - - meta.addEnchant(Enchantment.BINDING_CURSE, 1, true); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS, ItemFlags.HIDE_POTION_EFFECTS); - meta.setDisplayName(Localizer.dLocalize("items.chrono_time_bottle.name")); - } - - @AllArgsConstructor - @lombok.Data - public static class Data { - private double storedSeconds; - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/ExperienceOrb.java b/src/main/java/com/volmit/adapt/content/item/ExperienceOrb.java deleted file mode 100644 index a90879b30..000000000 --- a/src/main/java/com/volmit/adapt/content/item/ExperienceOrb.java +++ /dev/null @@ -1,114 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.item.DataItem; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.Localizer; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@AllArgsConstructor -@Data -public class ExperienceOrb implements DataItem { - public static ExperienceOrb io = new ExperienceOrb(); - - public static Data get(ItemStack is) { - return io.getData(is); - } - - public static void set(ItemStack item, String skill, double xp) { - io.setData(item, new Data(skill, xp)); - } - - public static ItemStack with(String skill, double xp) { - return io.withData(new Data(skill, xp)); - } - - public static ItemStack with(Map experienceMap) { - return io.withData(new Data(experienceMap)); - } - - @Override - public Material getMaterial() { - return Material.SNOWBALL; - } - - @Override - public Class getType() { - return ExperienceOrb.Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - for (Map.Entry entry : data.getExperienceMap().entrySet()) { - String skill = entry.getKey(); - double experience = entry.getValue(); - lore.add(C.WHITE + Form.capitalize(Localizer.dLocalize("snippets.experience_orb.contains")) + " " + C.UNDERLINE + C.WHITE + Form.f(experience, 0) + " " + Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skill).getDisplayName() + C.GRAY + " " + Localizer.dLocalize("snippets.experience_orb.xp")); - } - lore.add(C.LIGHT_PURPLE + Localizer.dLocalize("snippets.experience_orb.rightclick") + " " + C.GRAY + Localizer.dLocalize("snippets.experience_orb.togainxp")); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS); - meta.setDisplayName(Localizer.dLocalize("snippets.experience_orb.xporb")); - } - - @AllArgsConstructor - @lombok.Data - public static class Data { - private Map experienceMap; - - public Data(String skill, double experience) { - this.experienceMap = new HashMap<>(); - this.experienceMap.put(skill, experience); - } - - public String getSkill() { - return experienceMap.keySet().iterator().next(); - } - - public double getExperience() { - return experienceMap.values().iterator().next(); - } - - public void apply(Player p) { - for (Map.Entry entry : experienceMap.entrySet()) { - String skill = entry.getKey(); - double experience = entry.getValue(); - Adapt.instance.getAdaptServer().getPlayer(p).getSkillLine(skill).giveXPFresh(Adapt.instance.getAdaptServer().getPlayer(p).getNot(), experience); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/ItemListings.java b/src/main/java/com/volmit/adapt/content/item/ItemListings.java deleted file mode 100644 index 223ddead5..000000000 --- a/src/main/java/com/volmit/adapt/content/item/ItemListings.java +++ /dev/null @@ -1,760 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.reflect.registries.Materials; -import lombok.Getter; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.entity.EntityType; - -import java.util.HashMap; -import java.util.List; - -public class ItemListings { - - @Getter - public static final List shearList = new KList<>( - Material.ACACIA_LEAVES, - Material.AZALEA_LEAVES, - Material.BIRCH_LEAVES, - Material.DARK_OAK_LEAVES, - Material.JUNGLE_LEAVES, - Material.OAK_LEAVES, - Material.SPRUCE_LEAVES, - Material.MANGROVE_LEAVES, - Materials.CHERRY_LEAVES, - Materials.PALE_OAK_LEAVES - ).nonNull(); - - @Getter - public static final List invalidDamageableEntities = Version.get().getInvalidDamageableEntities(); - - @Getter - public static final List smeltOre = List.of( - Material.IRON_ORE, - Material.GOLD_ORE, - Material.COPPER_ORE, - Material.DEEPSLATE_IRON_ORE, - Material.DEEPSLATE_GOLD_ORE, - Material.DEEPSLATE_COPPER_ORE - ); - - @Getter - public static final List ores = List.of( - Material.IRON_ORE, - Material.GOLD_ORE, - Material.COPPER_ORE, - Material.LAPIS_ORE, - Material.REDSTONE_ORE, - Material.EMERALD_ORE, - Material.DIAMOND_ORE, - Material.COAL_ORE, - Material.DEEPSLATE_IRON_ORE, - Material.DEEPSLATE_GOLD_ORE, - Material.DEEPSLATE_COPPER_ORE, - Material.DEEPSLATE_LAPIS_ORE, - Material.DEEPSLATE_REDSTONE_ORE, - Material.DEEPSLATE_EMERALD_ORE, - Material.DEEPSLATE_DIAMOND_ORE, - Material.DEEPSLATE_COAL_ORE, - Material.NETHER_GOLD_ORE, - Material.NETHER_QUARTZ_ORE, - Material.ANCIENT_DEBRIS - ); - - @Getter - public static HashMap oreColorsChatColor = new HashMap<>() {{ - put(Material.IRON_ORE, ChatColor.GRAY); - put(Material.GOLD_ORE, ChatColor.YELLOW); - put(Material.COPPER_ORE, ChatColor.GOLD); - put(Material.LAPIS_ORE, ChatColor.BLUE); - put(Material.REDSTONE_ORE, ChatColor.RED); - put(Material.EMERALD_ORE, ChatColor.GREEN); - put(Material.DIAMOND_ORE, ChatColor.AQUA); - put(Material.COAL_ORE, ChatColor.DARK_GRAY); - put(Material.DEEPSLATE_IRON_ORE, ChatColor.GRAY); - put(Material.DEEPSLATE_GOLD_ORE, ChatColor.YELLOW); - put(Material.DEEPSLATE_COPPER_ORE, ChatColor.GOLD); - put(Material.DEEPSLATE_LAPIS_ORE, ChatColor.BLUE); - put(Material.DEEPSLATE_REDSTONE_ORE, ChatColor.RED); - put(Material.DEEPSLATE_EMERALD_ORE, ChatColor.GREEN); - put(Material.DEEPSLATE_DIAMOND_ORE, ChatColor.AQUA); - put(Material.DEEPSLATE_COAL_ORE, ChatColor.DARK_GRAY); - put(Material.NETHER_GOLD_ORE, ChatColor.YELLOW); - put(Material.NETHER_QUARTZ_ORE, ChatColor.WHITE); - put(Material.ANCIENT_DEBRIS, ChatColor.DARK_PURPLE); - }}; - - @Getter - public static HashMap oreColorColor = new HashMap<>() {{ - put(Material.IRON_ORE, C.GRAY); - put(Material.GOLD_ORE, C.YELLOW); - put(Material.COPPER_ORE, C.GOLD); - put(Material.LAPIS_ORE, C.BLUE); - put(Material.REDSTONE_ORE, C.RED); - put(Material.EMERALD_ORE, C.GREEN); - put(Material.DIAMOND_ORE, C.AQUA); - put(Material.COAL_ORE, C.DARK_GRAY); - put(Material.DEEPSLATE_IRON_ORE, C.GRAY); - put(Material.DEEPSLATE_GOLD_ORE, C.YELLOW); - put(Material.DEEPSLATE_COPPER_ORE, C.GOLD); - put(Material.DEEPSLATE_LAPIS_ORE, C.BLUE); - put(Material.DEEPSLATE_REDSTONE_ORE, C.RED); - put(Material.DEEPSLATE_EMERALD_ORE, C.GREEN); - put(Material.DEEPSLATE_DIAMOND_ORE, C.AQUA); - put(Material.DEEPSLATE_COAL_ORE, C.DARK_GRAY); - put(Material.NETHER_GOLD_ORE, C.YELLOW); - put(Material.NETHER_QUARTZ_ORE, C.WHITE); - put(Material.ANCIENT_DEBRIS, C.DARK_PURPLE); - }}; - - @Getter - public static KList herbalLuckSeeds = new KList<>( - Material.MELON_SEEDS, - Material.PUMPKIN_SEEDS, - Material.COCOA_BEANS - ); - - @Getter - public static List swordPreference = List.of( - Material.COBWEB, - Material.CAVE_VINES, - Material.CAVE_VINES_PLANT, - Material.BAMBOO, - Material.COCOA, - Material.COCOA_BEANS, - Material.HAY_BLOCK - ); - - @Getter - public static KList herbalLuckFood = new KList<>( - Material.POTATOES, - Material.CARROTS, - Material.BEETROOTS, - Material.APPLE - ); - - @Getter - public static List flowers = List.of( - Material.DANDELION, - Material.POPPY, - Material.BLUE_ORCHID, - Material.ALLIUM, - Material.AZURE_BLUET, - Material.RED_TULIP, - Material.ORANGE_TULIP, - Material.WHITE_TULIP, - Material.PINK_TULIP, - Material.OXEYE_DAISY, - Material.CORNFLOWER, - Material.LILY_OF_THE_VALLEY, - Material.LILAC, - Material.ROSE_BUSH, - Material.PEONY, - Material.WITHER_ROSE - ); - - @Getter - public static List food = List.of( - Material.APPLE, - Material.BAKED_POTATO, - Material.BEETROOT, - Material.BEETROOT_SOUP, - Material.BREAD, - Material.CARROT, - Material.CHORUS_FRUIT, - Material.COOKED_CHICKEN, - Material.COOKED_COD, - Material.COOKED_MUTTON, - Material.COOKED_PORKCHOP, - Material.COOKED_RABBIT, - Material.COOKED_SALMON, - Material.COOKIE, - Material.DRIED_KELP, - Material.GOLDEN_APPLE, - Material.GLOW_BERRIES, - Material.GOLDEN_CARROT, - Material.HONEY_BLOCK, - Material.MELON_SLICE, - Material.MUSHROOM_STEW, - Material.POISONOUS_POTATO, - Material.POTATO, - Material.PUFFERFISH, - Material.PUMPKIN_PIE, - Material.RABBIT_STEW, - Material.BEEF, - Material.CHICKEN, - Material.COD, - Material.MUTTON, - Material.PORKCHOP, - Material.SALMON, - Material.ROTTEN_FLESH, - Material.SPIDER_EYE, - Material.COOKED_BEEF, - Material.SUSPICIOUS_STEW, - Material.SWEET_BERRIES, - Material.TROPICAL_FISH - - ); - - @Getter - public static List stripList = new KList<>( - Material.ACACIA_LOG, - Material.ACACIA_WOOD, - Material.STRIPPED_ACACIA_LOG, - Material.STRIPPED_ACACIA_WOOD, - Material.BIRCH_LOG, - Material.BIRCH_WOOD, - Material.STRIPPED_BIRCH_LOG, - Material.STRIPPED_BIRCH_WOOD, - Material.DARK_OAK_LOG, - Material.DARK_OAK_WOOD, - Material.STRIPPED_DARK_OAK_LOG, - Material.STRIPPED_DARK_OAK_WOOD, - Material.JUNGLE_LOG, - Material.JUNGLE_WOOD, - Material.STRIPPED_JUNGLE_LOG, - Material.STRIPPED_JUNGLE_WOOD, - Material.OAK_LOG, - Material.OAK_WOOD, - Material.STRIPPED_OAK_LOG, - Material.STRIPPED_OAK_WOOD, - Material.SPRUCE_LOG, - Material.SPRUCE_WOOD, - Material.STRIPPED_SPRUCE_LOG, - Material.STRIPPED_SPRUCE_WOOD, - Material.MANGROVE_LOG, - Material.MANGROVE_WOOD, - Material.STRIPPED_MANGROVE_LOG, - Material.STRIPPED_MANGROVE_WOOD, - Material.CRIMSON_STEM, - Material.CRIMSON_HYPHAE, - Materials.CHERRY_LOG, - Materials.CHERRY_WOOD, - Materials.STRIPPED_CHERRY_LOG, - Materials.STRIPPED_CHERRY_WOOD, - Materials.BAMBOO_BLOCK, - Materials.STRIPPED_BAMBOO_BLOCK, - Materials.PALE_OAK_LOG, - Materials.PALE_OAK_WOOD, - Materials.STRIPPED_PALE_OAK_LOG, - Materials.STRIPPED_PALE_OAK_WOOD - ).nonNull(); - - - @Getter - public static List ignitable = List.of( - Material.OBSIDIAN, - Material.NETHERRACK, - Material.SOUL_SAND, - Material.TNT - ); - - @Getter - public static List multiArmorable = List.of( - Material.ELYTRA, - Material.CHAINMAIL_CHESTPLATE, - Material.DIAMOND_CHESTPLATE, - Material.GOLDEN_CHESTPLATE, - Material.IRON_CHESTPLATE, - Material.LEATHER_CHESTPLATE, - Material.NETHERITE_CHESTPLATE - ); - - @Getter - public static List farmable = List.of( - Material.GRASS_BLOCK, - Material.DIRT, - Material.COARSE_DIRT, - Material.ROOTED_DIRT, - Material.WHEAT, - Material.ATTACHED_MELON_STEM, - Material.ATTACHED_PUMPKIN_STEM, - Material.MELON_STEM, - Material.PUMPKIN_STEM, - Material.POTATOES, - Material.SWEET_BERRY_BUSH, - Material.CARROTS, - Material.BEETROOTS, - Material.DIRT_PATH - - ); - - @Getter - public static List tillable = List.of( - ); - - @Getter - public static List burnable = new KList<>( - Material.OBSIDIAN, - Material.NETHERRACK, - Material.SOUL_SAND, - Material.ACACIA_LEAVES, - Material.BIRCH_LEAVES, - Material.DARK_OAK_LEAVES, - Material.JUNGLE_LEAVES, - Material.OAK_LEAVES, - Material.SPRUCE_LEAVES, - Material.MANGROVE_LEAVES, - Materials.CHERRY_LEAVES, - Materials.PALE_OAK_LEAVES, - Material.WHITE_WOOL, - Material.ORANGE_WOOL, - Material.MAGENTA_WOOL, - Material.LIGHT_BLUE_WOOL, - Material.YELLOW_WOOL, - Material.LIME_WOOL, - Material.PINK_WOOL, - Material.GRAY_WOOL, - Material.LIGHT_GRAY_WOOL, - Material.CYAN_WOOL, - Material.PURPLE_WOOL, - Material.BLUE_WOOL, - Material.BROWN_WOOL, - Material.GREEN_WOOL, - Material.RED_WOOL, - Material.BLACK_WOOL - ).nonNull(); - - @Getter - public static List toolPickaxes = List.of( - Material.WOODEN_PICKAXE, - Material.STONE_PICKAXE, - Material.IRON_PICKAXE, - Material.GOLDEN_PICKAXE, - Material.DIAMOND_PICKAXE, - Material.NETHERITE_PICKAXE - ); - - @Getter - public static List toolAxes = List.of( - Material.WOODEN_AXE, - Material.STONE_AXE, - Material.IRON_AXE, - Material.GOLDEN_AXE, - Material.DIAMOND_AXE, - Material.NETHERITE_AXE - ); - - @Getter - public static List toolSwords = List.of( - Material.WOODEN_SWORD, - Material.STONE_SWORD, - Material.IRON_SWORD, - Material.GOLDEN_SWORD, - Material.DIAMOND_SWORD, - Material.NETHERITE_SWORD - ); - - @Getter - public static List toolShovels = List.of( - Material.WOODEN_SHOVEL, - Material.STONE_SHOVEL, - Material.IRON_SHOVEL, - Material.GOLDEN_SHOVEL, - Material.DIAMOND_SHOVEL, - Material.NETHERITE_SHOVEL - ); - - @Getter - public static List toolHoes = List.of( - Material.WOODEN_HOE, - Material.STONE_HOE, - Material.IRON_HOE, - Material.GOLDEN_HOE, - Material.DIAMOND_HOE, - Material.NETHERITE_HOE - ); - - @Getter - public static List tool = List.of( - Material.WOODEN_PICKAXE, - Material.STONE_PICKAXE, - Material.IRON_PICKAXE, - Material.GOLDEN_PICKAXE, - Material.DIAMOND_PICKAXE, - Material.NETHERITE_PICKAXE, - //AXE - Material.WOODEN_AXE, - Material.STONE_AXE, - Material.IRON_AXE, - Material.GOLDEN_AXE, - Material.DIAMOND_AXE, - Material.NETHERITE_AXE, - //SWORD - Material.WOODEN_SWORD, - Material.STONE_SWORD, - Material.IRON_SWORD, - Material.GOLDEN_SWORD, - Material.DIAMOND_SWORD, - Material.NETHERITE_SWORD, - //SHOVEL - Material.WOODEN_SHOVEL, - Material.STONE_SHOVEL, - Material.IRON_SHOVEL, - Material.GOLDEN_SHOVEL, - Material.DIAMOND_SHOVEL, - Material.NETHERITE_SHOVEL, - //HOE - Material.WOODEN_HOE, - Material.STONE_HOE, - Material.IRON_HOE, - Material.GOLDEN_HOE, - Material.DIAMOND_HOE, - Material.NETHERITE_HOE, - - //EXTRA - Material.SHEARS - ); - - @Getter - public static List shovelPreference = List.of( - Material.CLAY, - Material.DIRT, - Material.FARMLAND, - Material.GRASS_BLOCK, - Material.GRAVEL, - Material.MYCELIUM, - Material.SAND, - Material.SOUL_SAND, - Material.SOUL_SOIL, - Material.SNOW, - Material.SNOW_BLOCK, - Material.POWDER_SNOW, - Material.PODZOL, - Material.RED_SAND, - Material.MUD, - Material.MUDDY_MANGROVE_ROOTS - ); - - @Getter - public static KList fishingDrops = new KList<>( - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.PUFFERFISH, - Material.TROPICAL_FISH, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.PUFFERFISH, - Material.TROPICAL_FISH, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.PUFFERFISH, - Material.TROPICAL_FISH, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.PUFFERFISH, - Material.TROPICAL_FISH, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.COD, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.SALMON, - Material.PUFFERFISH, - Material.TROPICAL_FISH, - - Material.BOW, - Material.FISHING_ROD, - Material.NAME_TAG, - Material.NAUTILUS_SHELL, - Material.SADDLE, - - Material.LILY_PAD, - Material.BOWL, - Material.LEATHER, - Material.STICK, - Material.ROTTEN_FLESH, - Material.STRING, - Material.GLASS_BOTTLE, - Material.BONE, - Material.INK_SAC, - Material.TRIPWIRE_HOOK - ); - - @Getter - public static List axePreference = new KList<>( - //FENCES - Material.ACACIA_FENCE, - Material.BIRCH_FENCE, - Material.DARK_OAK_FENCE, - Material.JUNGLE_FENCE, - Material.SPRUCE_FENCE, - Material.MANGROVE_FENCE, - Material.OAK_FENCE, - Material.CRIMSON_FENCE, - Material.WARPED_FENCE, - Materials.CHERRY_FENCE, - Materials.BAMBOO_FENCE, - Materials.PALE_OAK_FENCE, - //GATES - Material.ACACIA_FENCE_GATE, - Material.BIRCH_FENCE_GATE, - Material.DARK_OAK_FENCE_GATE, - Material.JUNGLE_FENCE_GATE, - Material.SPRUCE_FENCE_GATE, - Material.MANGROVE_FENCE_GATE, - Material.OAK_FENCE_GATE, - Material.CRIMSON_FENCE_GATE, - Material.WARPED_FENCE_GATE, - Materials.CHERRY_FENCE_GATE, - Materials.BAMBOO_FENCE_GATE, - Materials.PALE_OAK_FENCE_GATE, - //WOODS - Material.ACACIA_LOG, - Material.ACACIA_WOOD, - Material.STRIPPED_ACACIA_LOG, - Material.BIRCH_LOG, - Material.BIRCH_WOOD, - Material.STRIPPED_BIRCH_LOG, - Material.DARK_OAK_LOG, - Material.DARK_OAK_WOOD, - Material.STRIPPED_DARK_OAK_LOG, - Material.JUNGLE_LOG, - Material.JUNGLE_WOOD, - Material.STRIPPED_JUNGLE_LOG, - Material.OAK_LOG, - Material.OAK_WOOD, - Material.STRIPPED_OAK_LOG, - Material.SPRUCE_LOG, - Material.SPRUCE_WOOD, - Material.STRIPPED_SPRUCE_LOG, - Material.MANGROVE_LOG, - Material.MANGROVE_WOOD, - Material.STRIPPED_MANGROVE_LOG, - Material.CRIMSON_STEM, - Material.CRIMSON_HYPHAE, - Materials.CHERRY_LOG, - Materials.CHERRY_WOOD, - Materials.STRIPPED_CHERRY_LOG, - Materials.STRIPPED_CHERRY_WOOD, - Materials.BAMBOO_BLOCK, - Materials.STRIPPED_BAMBOO_BLOCK, - Materials.PALE_OAK_LOG, - Materials.PALE_OAK_WOOD, - Materials.STRIPPED_PALE_OAK_LOG, - Materials.STRIPPED_PALE_OAK_WOOD, - //SIGNS - Material.ACACIA_SIGN, - Material.ACACIA_WALL_SIGN, - Materials.ACACIA_HANGING_SIGN, - Materials.ACACIA_WALL_HANGING_SIGN, - Material.BIRCH_SIGN, - Material.BIRCH_WALL_SIGN, - Materials.BIRCH_HANGING_SIGN, - Materials.BIRCH_WALL_HANGING_SIGN, - Material.DARK_OAK_SIGN, - Material.DARK_OAK_WALL_SIGN, - Materials.DARK_OAK_HANGING_SIGN, - Materials.DARK_OAK_WALL_HANGING_SIGN, - Material.JUNGLE_SIGN, - Material.JUNGLE_WALL_SIGN, - Materials.JUNGLE_HANGING_SIGN, - Materials.JUNGLE_WALL_HANGING_SIGN, - Material.OAK_SIGN, - Material.OAK_WALL_SIGN, - Materials.OAK_HANGING_SIGN, - Materials.OAK_WALL_HANGING_SIGN, - Material.SPRUCE_SIGN, - Material.SPRUCE_WALL_SIGN, - Materials.SPRUCE_HANGING_SIGN, - Materials.SPRUCE_WALL_HANGING_SIGN, - Material.MANGROVE_SIGN, - Material.MANGROVE_WALL_SIGN, - Materials.MANGROVE_HANGING_SIGN, - Materials.MANGROVE_WALL_HANGING_SIGN, - Material.CRIMSON_SIGN, - Material.CRIMSON_WALL_SIGN, - Materials.CRIMSON_HANGING_SIGN, - Materials.CRIMSON_WALL_HANGING_SIGN, - Material.WARPED_SIGN, - Material.WARPED_WALL_SIGN, - Materials.WARPED_HANGING_SIGN, - Materials.WARPED_WALL_HANGING_SIGN, - Materials.CHERRY_SIGN, - Materials.CHERRY_WALL_SIGN, - Materials.CHERRY_HANGING_SIGN, - Materials.CHERRY_WALL_HANGING_SIGN, - Materials.BAMBOO_SIGN, - Materials.BAMBOO_WALL_SIGN, - Materials.BAMBOO_HANGING_SIGN, - Materials.BAMBOO_WALL_HANGING_SIGN, - Materials.PALE_OAK_SIGN, - Materials.PALE_OAK_WALL_SIGN, - Materials.PALE_OAK_HANGING_SIGN, - Materials.PALE_OAK_WALL_HANGING_SIGN, - //WOODEN_BUTTONS - Material.ACACIA_BUTTON, - Material.BIRCH_BUTTON, - Material.DARK_OAK_BUTTON, - Material.JUNGLE_BUTTON, - Material.OAK_BUTTON, - Material.SPRUCE_BUTTON, - Material.MANGROVE_BUTTON, - Material.CRIMSON_BUTTON, - Material.WARPED_BUTTON, - Materials.CHERRY_BUTTON, - Materials.BAMBOO_BUTTON, - Materials.PALE_OAK_BUTTON, - //WOODEN_DOORS - Material.ACACIA_DOOR, - Material.BIRCH_DOOR, - Material.DARK_OAK_DOOR, - Material.JUNGLE_DOOR, - Material.OAK_DOOR, - Material.SPRUCE_DOOR, - Material.MANGROVE_DOOR, - Material.CRIMSON_DOOR, - Material.WARPED_DOOR, - Materials.CHERRY_DOOR, - Materials.BAMBOO_DOOR, - Materials.PALE_OAK_DOOR, - //WOODEN_PRESSURE_PLATES - Material.ACACIA_PRESSURE_PLATE, - Material.BIRCH_PRESSURE_PLATE, - Material.DARK_OAK_PRESSURE_PLATE, - Material.JUNGLE_PRESSURE_PLATE, - Material.OAK_PRESSURE_PLATE, - Material.SPRUCE_PRESSURE_PLATE, - Material.MANGROVE_PRESSURE_PLATE, - Material.CRIMSON_PRESSURE_PLATE, - Material.WARPED_PRESSURE_PLATE, - Materials.CHERRY_PRESSURE_PLATE, - Materials.BAMBOO_PRESSURE_PLATE, - Materials.PALE_OAK_PRESSURE_PLATE, - //WOODEN_TRAPDOORS - Material.ACACIA_TRAPDOOR, - Material.BIRCH_TRAPDOOR, - Material.DARK_OAK_TRAPDOOR, - Material.JUNGLE_TRAPDOOR, - Material.OAK_TRAPDOOR, - Material.SPRUCE_TRAPDOOR, - Material.MANGROVE_TRAPDOOR, - Material.CRIMSON_TRAPDOOR, - Material.WARPED_TRAPDOOR, - Materials.CHERRY_TRAPDOOR, - Materials.BAMBOO_TRAPDOOR, - Materials.PALE_OAK_TRAPDOOR, - //WOODEN_STAIRS - Material.ACACIA_STAIRS, - Material.BIRCH_STAIRS, - Material.DARK_OAK_STAIRS, - Material.JUNGLE_STAIRS, - Material.OAK_STAIRS, - Material.SPRUCE_STAIRS, - Material.MANGROVE_STAIRS, - Material.CRIMSON_STAIRS, - Material.WARPED_STAIRS, - Materials.CHERRY_STAIRS, - Materials.BAMBOO_STAIRS, - Materials.BAMBOO_MOSAIC_STAIRS, - Materials.PALE_OAK_STAIRS, - //WOODEN_SLABS - Material.ACACIA_SLAB, - Material.BIRCH_SLAB, - Material.DARK_OAK_SLAB, - Material.JUNGLE_SLAB, - Material.OAK_SLAB, - Material.SPRUCE_SLAB, - Material.MANGROVE_SLAB, - Material.CRIMSON_SLAB, - Material.WARPED_SLAB, - Materials.CHERRY_SLAB, - Materials.BAMBOO_SLAB, - Materials.BAMBOO_MOSAIC_SLAB, - Materials.PALE_OAK_SLAB, - //PLANKS - Material.ACACIA_PLANKS, - Material.BIRCH_PLANKS, - Material.DARK_OAK_PLANKS, - Material.JUNGLE_PLANKS, - Material.OAK_PLANKS, - Material.SPRUCE_PLANKS, - Material.MANGROVE_PLANKS, - Material.CRIMSON_PLANKS, - Material.WARPED_PLANKS, - Materials.CHERRY_PLANKS, - Materials.BAMBOO_PLANKS, - Materials.BAMBOO_MOSAIC, - Materials.PALE_OAK_PLANKS, - //MISC - Material.BEE_NEST, - Material.DRIED_KELP_BLOCK, - Material.BEEHIVE, - Material.CHEST, - Material.CRAFTING_TABLE, - Material.JUKEBOX, - Material.LADDER, - Material.LOOM, - Material.NOTE_BLOCK, - Material.BARREL, - Material.BOOKSHELF, - Material.CARTOGRAPHY_TABLE, - Material.FLETCHING_TABLE, - Material.CAMPFIRE, - Material.SOUL_CAMPFIRE, - Material.HONEYCOMB, - Material.LECTERN, - Material.COCOA, - Material.JACK_O_LANTERN, - Material.PUMPKIN, - Material.MELON, - Material.TRAPPED_CHEST - ).nonNull(); -} diff --git a/src/main/java/com/volmit/adapt/content/item/KnowledgeOrb.java b/src/main/java/com/volmit/adapt/content/item/KnowledgeOrb.java deleted file mode 100644 index ba65f5ed6..000000000 --- a/src/main/java/com/volmit/adapt/content/item/KnowledgeOrb.java +++ /dev/null @@ -1,128 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.item.DataItem; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.Localizer; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@AllArgsConstructor -@Data -public class KnowledgeOrb implements DataItem { - public static KnowledgeOrb io = new KnowledgeOrb(); - - public static Data get(ItemStack is) { - return io.getData(is); - } - - public static String getSkill(ItemStack stack) { - if (io.getData(stack) != null) { - return io.getData(stack).getSkill(); - } - - return null; - } - - public static long getKnowledge(ItemStack stack) { - if (io.getData(stack) != null) { - return io.getData(stack).getKnowledge(); - } - - return 0; - } - - public static void set(ItemStack item, String skill, int knowledge) { - io.setData(item, new Data(skill, knowledge)); - } - - public static ItemStack with(String skill, int knowledge) { - return io.withData(new Data(skill, knowledge)); - } - - public static ItemStack with(Map knowledgeMap) { - return io.withData(new Data(knowledgeMap)); - } - - @Override - public Material getMaterial() { - return Material.SNOWBALL; - } - - @Override - public Class getType() { - return KnowledgeOrb.Data.class; - } - - @Override - public void applyLore(Data data, List lore) { - for (Map.Entry entry : data.getKnowledgeMap().entrySet()) { - String skill = entry.getKey(); - int knowledge = entry.getValue(); - lore.add(C.WHITE + Localizer.dLocalize("snippets.knowledge_orb.contains") + " " + C.UNDERLINE + C.WHITE + "" + knowledge + " " + Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(skill).getDisplayName() + " " + Localizer.dLocalize("snippets.knowledge_orb.knowledge")); - } - lore.add(C.LIGHT_PURPLE + Localizer.dLocalize("snippets.knowledge_orb.rightclick") + " " + C.GRAY + Localizer.dLocalize("snippets.knowledge_orb.togainknowledge")); - } - - @Override - public void applyMeta(Data data, ItemMeta meta) { - meta.addEnchant(Enchantment.BINDING_CURSE, 10, true); - meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_ENCHANTS); - meta.setDisplayName(Localizer.dLocalize("snippets.knowledge_orb.knowledge_orb")); - } - - @AllArgsConstructor - @lombok.Data - public static class Data { - private Map knowledgeMap; - - public Data(String skill, int knowledge) { - this.knowledgeMap = new HashMap<>(); - this.knowledgeMap.put(skill, knowledge); - } - - public String getSkill() { - return knowledgeMap.keySet().iterator().next(); - } - - public int getKnowledge() { - return knowledgeMap.values().iterator().next(); - } - - public void apply(Player p) { - for (Map.Entry entry : knowledgeMap.entrySet()) { - String skill = entry.getKey(); - int knowledge = entry.getValue(); - Adapt.instance.getAdaptServer().getPlayer(p).getSkillLine(skill).giveKnowledge(knowledge); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/multiItems/MultiArmor.java b/src/main/java/com/volmit/adapt/content/item/multiItems/MultiArmor.java deleted file mode 100644 index d3252c92d..000000000 --- a/src/main/java/com/volmit/adapt/content/item/multiItems/MultiArmor.java +++ /dev/null @@ -1,62 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item.multiItems; - -import com.volmit.adapt.util.Form; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.ArrayList; -import java.util.List; - -public class MultiArmor implements MultiItem { - @Override - public boolean supportsItem(ItemStack itemStack) { - return true; - } - - @Override - public String getKey() { - return "multiarmor"; - } - - @Override - public void onApplyMeta(ItemStack item, ItemMeta meta, List otherItems) { - List lore = new ArrayList<>(); - lore.add("MultiArmor (" + (otherItems.size() + 1) + " Items)"); - lore.add("-> " + Form.capitalizeWords(item.getType().name().toLowerCase().replaceAll("\\Q_\\E", " "))); - - for (ItemStack i : otherItems) { - lore.add("- " + Form.capitalizeWords(i.getType().name().toLowerCase().replaceAll("\\Q_\\E", " "))); - } - - meta.setLore(lore); - } - - public ItemStack nextElytra(ItemStack item) { - return nextMatching(item, i -> i.getType().equals(Material.ELYTRA)); - } - - public ItemStack nextChestplate(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("_CHESTPLATE")); - } - - -} diff --git a/src/main/java/com/volmit/adapt/content/item/multiItems/MultiItem.java b/src/main/java/com/volmit/adapt/content/item/multiItems/MultiItem.java deleted file mode 100644 index 3a1ff1372..000000000 --- a/src/main/java/com/volmit/adapt/content/item/multiItems/MultiItem.java +++ /dev/null @@ -1,182 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item.multiItems; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.nms.NMS; -import com.volmit.adapt.util.BukkitGson; -import com.volmit.adapt.util.WindowResolution; -import com.volmit.adapt.util.collection.KList; -import lombok.*; -import org.bukkit.NamespacedKey; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.persistence.PersistentDataType; - -import java.util.List; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -public interface MultiItem { - boolean supportsItem(ItemStack itemStack); - - String getKey(); - - default WindowResolution getWindowType() { - return WindowResolution.W5_H1; - } - - default ItemStack build(ItemStack... stacks) { - ItemStack s = stacks[0]; - - for (int i = 1; i < stacks.length; i++) { - add(s, stacks[i]); - } - - return s; - } - - default boolean remove(ItemStack multi, ItemStack toRemove) { - int ind = getItems(multi).indexOf(toRemove); - if (ind == -1) { - return false; - } - - remove(multi, ind); - return true; - } - - default void remove(ItemStack multi, int index) { - List it = getItems(multi); - it.remove(index); - setItems(multi, it); - } - - default void add(ItemStack multi, ItemStack item) { - if (isMultiItem(item)) { - explode(item).forEach(i -> add(multi, i)); - } else { - setItems(multi, getItems(multi).qadd(item)); - } - } - - default ItemStack nextMatching(ItemStack item, Predicate predicate) { - KList items = getItems(item); - for (int i = 0; i < items.size(); i++) { - if (predicate.test(items.get(i))) { - return switchTo(item, i); - } - } - - return item; - } - - default ItemStack nextTool(ItemStack multi) { - return switchTo(multi, 0); - } - - default ItemStack switchTo(ItemStack multi, int index) { - List items = getItems(multi); - ItemStack next = items.remove(index); - items.add(getRealItem(multi)); - setItems(next, items); - return next; - } - - default void setItems(ItemStack multi, List itemStacks) { - setMultiItemData(multi, MultiItemData.builder() - .rawItems(itemStacks.stream().filter(this::supportsItem) - .map(NMS::serializeStack) - .collect(Collectors.toList())) - .build()); - } - - default KList getItems(ItemStack multi) { - MultiItemData d = getMultiItemData(multi); - - if (d == null) { - return new KList<>(); - } - - return d.getItems(); - } - - default KList explode(ItemStack multi) { - KList it = new KList<>(); - it.add(getRealItem(multi)); - it.add(getItems(multi)); - return it; - } - - void onApplyMeta(ItemStack item, ItemMeta meta, List otherItems); - - default boolean isMultiItem(ItemStack item) { - return supportsItem(item) && getMultiItemData(item) != null; - } - - default ItemStack getRealItem(ItemStack multi) { - ItemStack c = multi.clone(); - if (c.hasItemMeta()) { - ItemMeta meta = c.getItemMeta(); - meta.getPersistentDataContainer().remove(new NamespacedKey(Adapt.instance, getKey())); - c.setItemMeta(meta); - } - - return c; - } - - default MultiItemData getMultiItemData(ItemStack multi) { - try { - ItemMeta meta = multi.getItemMeta(); - String st = meta.getPersistentDataContainer() - .get(new NamespacedKey(Adapt.instance, getKey()), PersistentDataType.STRING); - return BukkitGson.gson.fromJson(st, MultiItemData.class); - } catch (Throwable e) { - return null; - } - } - - default void setMultiItemData(ItemStack multi, MultiItemData data) { - String s = BukkitGson.gson.toJson(data); - ItemMeta meta = multi.getItemMeta(); - meta.getPersistentDataContainer() - .set(new NamespacedKey(Adapt.instance, getKey()), PersistentDataType.STRING, s); - multi.setItemMeta(meta); - meta = multi.getItemMeta(); - onApplyMeta(multi, meta, getItems(multi)); - multi.setItemMeta(meta); - } - - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - class MultiItemData { - @Singular - List rawItems; - - KList getItems() { - return rawItems.stream().map(NMS::deserializeStack).collect(KList.collector()); - } - - void setItems(List is) { - rawItems = is.stream().map(NMS::serializeStack).collect(KList.collector()); - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/item/multiItems/OmniTool.java b/src/main/java/com/volmit/adapt/content/item/multiItems/OmniTool.java deleted file mode 100644 index 7d995101f..000000000 --- a/src/main/java/com/volmit/adapt/content/item/multiItems/OmniTool.java +++ /dev/null @@ -1,104 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.item.multiItems; - -import com.volmit.adapt.util.Form; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import java.util.ArrayList; -import java.util.List; - -public class OmniTool implements MultiItem { - @Override - public boolean supportsItem(ItemStack itemStack) { - return true; - } - - @Override - public String getKey() { - return "omnitool"; - } - - @Override - public void onApplyMeta(ItemStack item, ItemMeta meta, List otherItems) { - List lore = new ArrayList<>(); - lore.add("Leatherman (" + (otherItems.size() + 1) + " Items)"); - lore.add("-> " + Form.capitalizeWords(item.getType().name().toLowerCase().replaceAll("\\Q_\\E", " "))); - - for (ItemStack i : otherItems) { - lore.add("- " + Form.capitalizeWords(i.getType().name().toLowerCase().replaceAll("\\Q_\\E", " "))); - } - - meta.setLore(lore); - } - - public ItemStack nextPickaxe(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("_PICKAXE")); - } - - public ItemStack nextAxe(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("_AXE")); - } - - public ItemStack nextSword(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("_SWORD")); - } - - public ItemStack nextShovel(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("_SHOVEL")); - } - - public ItemStack nextHoe(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("_HOE")); - } - - public ItemStack nextShears(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("SHEARS")); - } - - public ItemStack nextFnS(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("FLINT_AND_STEEL")); - } - - public ItemStack nextItem(ItemStack item) { - return nextMatching(item, i -> i.getType().name().endsWith("_PICKAXE") || i.getType().name().endsWith("_AXE") || i.getType().name().endsWith("_SWORD") || i.getType().name().endsWith("_SHOVEL") || i.getType().name().endsWith("_HOE") || i.getType().name().endsWith("SHEARS")); - } - - public ItemStack nextNonMatchingItem(ItemStack item, Material material) { - if (material.toString().contains("_PICKAXE")) { - return nextAxe(item); - } else if (material.toString().contains("_AXE")) { - return nextSword(item); - } else if (material.toString().contains("_SWORD")) { - return nextShovel(item); - } else if (material.toString().contains("_SHOVEL")) { - return nextHoe(item); - } else if (material.toString().contains("_HOE")) { - return nextShears(item); - } else if (material.toString().contains("SHEARS")) { - return nextPickaxe(item); - } else if (material.toString().contains("FLINT_AND_STEEL")) { - return nextFnS(item); - } else { - return nextItem(item); - } - } -} diff --git a/src/main/java/com/volmit/adapt/content/matter/BrewingStandOwnerMatter.java b/src/main/java/com/volmit/adapt/content/matter/BrewingStandOwnerMatter.java deleted file mode 100644 index 291b5c898..000000000 --- a/src/main/java/com/volmit/adapt/content/matter/BrewingStandOwnerMatter.java +++ /dev/null @@ -1,47 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.matter; - -import art.arcane.spatial.matter.slices.RawMatter; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.UUID; - -public class BrewingStandOwnerMatter extends RawMatter { - public BrewingStandOwnerMatter(int w, int h, int d) { - super(w, h, d, BrewingStandOwner.class); - } - - public BrewingStandOwnerMatter() { - this(1, 1, 1); - } - - @Override - public void writeNode(BrewingStandOwner brewingStandOwner, DataOutputStream dataOutputStream) throws IOException { - dataOutputStream.writeLong(brewingStandOwner.getOwner().getMostSignificantBits()); - dataOutputStream.writeLong(brewingStandOwner.getOwner().getLeastSignificantBits()); - } - - @Override - public BrewingStandOwner readNode(DataInputStream dataInputStream) throws IOException { - return new BrewingStandOwner(new UUID(dataInputStream.readLong(), dataInputStream.readLong())); - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/content/protector/ChestProtectProtector.java b/src/main/java/com/volmit/adapt/content/protector/ChestProtectProtector.java deleted file mode 100644 index 01baed950..000000000 --- a/src/main/java/com/volmit/adapt/content/protector/ChestProtectProtector.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.volmit.adapt.content.protector; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.protection.Protector; -import me.angeschossen.chestprotect.api.addons.ChestProtectAddon; -import me.angeschossen.chestprotect.api.protection.block.BlockProtection; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -public class ChestProtectProtector implements Protector { - private final ChestProtectAddon chestProtect; - - public ChestProtectProtector() { - this.chestProtect = new ChestProtectAddon(Adapt.instance); - } - - @Override - public boolean canAccessChest(Player player, Location chestlocation, Adaptation adaptation) { - if (!chestProtect.isProtectable(chestlocation.getBlock())) return true; - BlockProtection blockProtection = chestProtect.getProtection(chestlocation); - if (blockProtection == null) return true; - return blockProtection.isTrusted(player.getUniqueId()); - } - - @Override - public String getName() { - return "ChestProtect"; - } - - @Override - public boolean isEnabledByDefault() { - return AdaptConfig.get().getProtectorSupport().isChestProtect(); - } -} diff --git a/src/main/java/com/volmit/adapt/content/protector/FactionsClaimProtector.java b/src/main/java/com/volmit/adapt/content/protector/FactionsClaimProtector.java deleted file mode 100644 index 8fb6fd381..000000000 --- a/src/main/java/com/volmit/adapt/content/protector/FactionsClaimProtector.java +++ /dev/null @@ -1,58 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.protector; - -import com.massivecraft.factions.*; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.protection.Protector; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -public class FactionsClaimProtector implements Protector { - - @Override - public boolean checkRegion(Player player, Location location, Adaptation adaptation) { - Faction f = Board.getInstance().getFactionAt(new FLocation(player.getLocation())); - return checkPerm(player, f, adaptation) || f.isWilderness(); - } - - @Override - public boolean canPVP(Player player, Location victimLocation, Adaptation adaptation) { - Faction f = Board.getInstance().getFactionAt(new FLocation(victimLocation)); - return checkPerm(player, f, adaptation) || !f.noPvPInTerritory(); - } - - private boolean checkPerm(Player player, Faction f, Adaptation adaptation) { - FPlayer fp = FPlayers.getInstance().getByPlayer(player); - return f == null - || fp.getFaction() == f - || fp.isAdminBypassing(); - } - - @Override - public String getName() { - return "Factions"; - } - - @Override - public boolean isEnabledByDefault() { - return AdaptConfig.get().getProtectorSupport().isFactionsClaim(); - } -} diff --git a/src/main/java/com/volmit/adapt/content/protector/GriefDefenderProtector.java b/src/main/java/com/volmit/adapt/content/protector/GriefDefenderProtector.java deleted file mode 100644 index 9d3c2e32a..000000000 --- a/src/main/java/com/volmit/adapt/content/protector/GriefDefenderProtector.java +++ /dev/null @@ -1,102 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.protector; - -import com.griefdefender.api.GriefDefender; -import com.griefdefender.api.claim.Claim; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.protection.Protector; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import java.util.UUID; - -public class GriefDefenderProtector implements Protector { - /** - * This api is garbage, and obfuscated. - * If i can get a jar ill improve it, but for now this is the best i can do. - * Or if someone wants to make a PR feel free. - * - * I as an author do not support this api, and do not recommend it, - * as they are making ME pay $15(spigot) + $5(patreon) per month to be - * able to ask questions in their discord, and get unobfuscated jars. - * - */ - - @Override - public boolean checkRegion(Player player, Location location, Adaptation adaptation) { - final Claim claim = GriefDefender.getCore().getClaimAt(location); - return checkPerm(player, claim, adaptation) || claim.isWilderness(); - } - - @Override - public boolean canPVP(Player player, Location entityLocation, Adaptation adaptation) { - final Claim claim = GriefDefender.getCore().getClaimAt(entityLocation); - if (checkPerm(player, claim, adaptation)) { - return claim.isPvpAllowed(); - } - return false; - } - - private boolean checkPerm(Player player, Claim claim, Adaptation adaptation) { - if (claim == null) { - return true; - } - UUID uuid = player.getUniqueId(); - return claim.isWilderness() - || claim.getOwnerUniqueId().equals(uuid) - || claim.getUserTrusts().contains(uuid); - } - - @Override - public boolean canPVE(Player player, Location entityLocation, Adaptation adaptation) { - return checkPerm(player, GriefDefender.getCore().getClaimAt(entityLocation), adaptation); - } - - @Override - public boolean canInteract(Player player, Location targetLocation, Adaptation adaptation) { - return checkPerm(player, GriefDefender.getCore().getClaimAt(targetLocation), adaptation); - } - - @Override - public boolean canAccessChest(Player player, Location chestLocation, Adaptation adaptation) { - return checkPerm(player, GriefDefender.getCore().getClaimAt(chestLocation), adaptation); - } - - @Override - public boolean canBlockBreak(Player player, Location blockLocation, Adaptation adaptation) { - return checkPerm(player, GriefDefender.getCore().getClaimAt(blockLocation), adaptation); - } - - @Override - public boolean canBlockPlace(Player player, Location blockLocation, Adaptation adaptation) { - return checkPerm(player, GriefDefender.getCore().getClaimAt(blockLocation), adaptation); - } - - @Override - public String getName() { - return "GriefDefender"; - } - - @Override - public boolean isEnabledByDefault() { - return AdaptConfig.get().getProtectorSupport().isFactionsClaim(); - } -} diff --git a/src/main/java/com/volmit/adapt/content/protector/GriefPreventionProtector.java b/src/main/java/com/volmit/adapt/content/protector/GriefPreventionProtector.java deleted file mode 100644 index 66181895d..000000000 --- a/src/main/java/com/volmit/adapt/content/protector/GriefPreventionProtector.java +++ /dev/null @@ -1,84 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.protector; - -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.protection.Protector; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.ClaimPermission; -import me.ryanhamshire.GriefPrevention.GriefPrevention; -import me.ryanhamshire.GriefPrevention.PlayerData; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import java.util.Objects; - -public class GriefPreventionProtector implements Protector { - - - @Override - public boolean canBlockBreak(Player player, Location location, Adaptation adaptation) { - return canEditClaim(player, location); - } - - @Override - public boolean canBlockPlace(Player player, Location location, Adaptation adaptation) { - return canEditClaim(player, location); - } - - @Override - public String getName() { - return "GriefPrevention"; - } - - @Override - public boolean isEnabledByDefault() { - return AdaptConfig.get().getProtectorSupport().isGriefprevention(); - } - - @Override - public void unregister() { - Protector.super.unregister(); - } - - - - private boolean canEditClaim(Player player, Location location) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, true, null); - PlayerData playerData = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()); - - if (claim == null) { - return true; - } - //If doesn't check is adminclaim getting ownerid return null - if (!claim.isAdminClaim() && Objects.equals(claim.getOwnerID(), player.getUniqueId())) { - return true; - } - else if (claim.getPermission(player.getUniqueId().toString()) == ClaimPermission.Build) { - return true; - } - - return playerData.ignoreClaims || claim.isAdminClaim() && player.hasPermission("griefprevention.adminclaims"); - - } - - -} - diff --git a/src/main/java/com/volmit/adapt/content/protector/LocketteProProtector.java b/src/main/java/com/volmit/adapt/content/protector/LocketteProProtector.java deleted file mode 100644 index f0613c310..000000000 --- a/src/main/java/com/volmit/adapt/content/protector/LocketteProProtector.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.volmit.adapt.content.protector; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.protection.Protector; -import me.angeschossen.chestprotect.api.addons.ChestProtectAddon; -import me.angeschossen.chestprotect.api.protection.block.BlockProtection; -import me.crafter.mc.lockettepro.LocketteProAPI; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -public class LocketteProProtector implements Protector { - @Override - public boolean canAccessChest(Player player, Location chestlocation, Adaptation adaptation) { - return LocketteProAPI.isOwner(chestlocation.getBlock(), player); - } - - @Override - public String getName() { - return "LockettePro"; - } - - @Override - public boolean isEnabledByDefault() { - return AdaptConfig.get().getProtectorSupport().isLockettePro(); - } -} diff --git a/src/main/java/com/volmit/adapt/content/protector/ResidenceProtector.java b/src/main/java/com/volmit/adapt/content/protector/ResidenceProtector.java deleted file mode 100644 index 02af9090f..000000000 --- a/src/main/java/com/volmit/adapt/content/protector/ResidenceProtector.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.volmit.adapt.content.protector; - -import com.bekvon.bukkit.residence.Residence; -import com.bekvon.bukkit.residence.containers.Flags; -import com.bekvon.bukkit.residence.protection.ClaimedResidence; -import com.bekvon.bukkit.residence.protection.FlagPermissions; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.protection.Protector; -import com.volmit.adapt.util.J; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import java.util.concurrent.atomic.AtomicBoolean; - -public class ResidenceProtector implements Protector { - - public ResidenceProtector() { - FlagPermissions.addFlag("use-adaptations"); - } - - @Override - public boolean checkRegion(Player player, Location location, Adaptation adaptation) { - return checkPerm(player, location, "use-adaptations"); - } - - @Override - public boolean canBlockBreak(Player player, Location blockLocation, Adaptation adaptation) { - return checkRegion(player, blockLocation, adaptation) && checkPerm(player, blockLocation, Flags.destroy); - } - - @Override - public boolean canBlockPlace(Player player, Location blockLocation, Adaptation adaptation) { - return checkRegion(player, blockLocation, adaptation) && checkPerm(player, blockLocation, Flags.place); - } - - @Override - public boolean canPVP(Player player, Location entityLocation, Adaptation adaptation) { - return checkRegion(player, entityLocation, adaptation) && checkPerm(player, entityLocation, Flags.pvp); - } - - @Override - public boolean canPVE(Player player, Location entityLocation, Adaptation adaptation) { - return checkRegion(player, entityLocation, adaptation) && checkPerm(player, entityLocation, Flags.damage); - } - - @Override - public boolean canInteract(Player player, Location targetLocation, Adaptation adaptation) { - return checkRegion(player, targetLocation, adaptation) && checkPerm(player, targetLocation, Flags.use); - } - - @Override - public boolean canAccessChest(Player player, Location chestLocation, Adaptation adaptation) { - return checkRegion(player, chestLocation, adaptation) && checkPerm(player, chestLocation, Flags.container); - } - - private boolean checkPerm(Player player, Location location, Flags flag) { - AtomicBoolean perm = new AtomicBoolean(true); - J.s(() -> { - if (!Residence.getInstance().isDisabledWorld(location.getWorld())) { - ClaimedResidence res = Residence.getInstance().getResidenceManager().getByLoc(location); - if (res != null) { - perm.set(res.getPermissions().playerHas(player.getName(), flag, true)); - } - } - }); - return perm.get(); - } - - private boolean checkPerm(Player player, Location location, String flag) { - AtomicBoolean perm = new AtomicBoolean(true); - J.s(() -> { - if (!Residence.getInstance().isDisabledWorld(location.getWorld())) { - ClaimedResidence res = Residence.getInstance().getResidenceManager().getByLoc(location); - if (res != null) { - perm.set(res.getPermissions().playerHas(player.getName(), flag, true)); - } - } - }); - return perm.get(); - } - - @Override - public String getName() { - return "Residence"; - } - - @Override - public boolean isEnabledByDefault() { - return AdaptConfig.get().getProtectorSupport().isResidence(); - } - -} diff --git a/src/main/java/com/volmit/adapt/content/protector/WorldGuardProtector.java b/src/main/java/com/volmit/adapt/content/protector/WorldGuardProtector.java deleted file mode 100644 index 2d70e0538..000000000 --- a/src/main/java/com/volmit/adapt/content/protector/WorldGuardProtector.java +++ /dev/null @@ -1,131 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.protector; - -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldguard.LocalPlayer; -import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.protection.flags.Flag; -import com.sk89q.worldguard.protection.flags.Flags; -import com.sk89q.worldguard.protection.flags.StateFlag; -import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; -import com.sk89q.worldguard.protection.regions.RegionContainer; -import com.sk89q.worldguard.protection.regions.RegionQuery; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.protection.Protector; -import org.bukkit.Location; -import org.bukkit.entity.Player; - -import java.lang.reflect.Field; -import java.util.concurrent.ConcurrentMap; - -public class WorldGuardProtector implements Protector { - - private final RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); - private final StateFlag flag = registerFlag(); - - - @Override - public boolean checkRegion(Player player, Location location, Adaptation adaptation) { - return checkPerm(player, location, flag); - } - - @Override - public boolean canBlockBreak(Player player, Location blockLocation, Adaptation adaptation) { - return checkRegion(player, blockLocation, adaptation) && checkPerm(player, blockLocation, Flags.BLOCK_BREAK); - } - - @Override - public boolean canBlockPlace(Player player, Location blockLocation, Adaptation adaptation) { - return checkRegion(player, blockLocation, adaptation) && checkPerm(player, blockLocation, Flags.BLOCK_PLACE); - } - - @Override - public boolean canPVP(Player player, Location entityLocation, Adaptation adaptation) { - return checkRegion(player, entityLocation, adaptation) && checkPerm(player, entityLocation, Flags.PVP); - } - - @Override - public boolean canPVE(Player player, Location entityLocation, Adaptation adaptation) { - return checkRegion(player, entityLocation, adaptation) && checkPerm(player, entityLocation, Flags.DAMAGE_ANIMALS); - } - - @Override - public boolean canInteract(Player player, Location targetLocation, Adaptation adaptation) { - return checkRegion(player, targetLocation, adaptation) && checkPerm(player, targetLocation, Flags.INTERACT); - } - - @Override - public boolean canAccessChest(Player player, Location chestLocation, Adaptation adaptation) { - return checkRegion(player, chestLocation, adaptation) && checkPerm(player, chestLocation, Flags.CHEST_ACCESS); - } - - private boolean checkPerm(Player player, Location location, StateFlag flag) { - RegionQuery regionQuery = container.createQuery(); - com.sk89q.worldedit.util.Location loc = BukkitAdapter.adapt(location); - if (!hasBypass(player, location)) - return regionQuery.queryState(loc, WorldGuardPlugin.inst().wrapPlayer(player), flag) != StateFlag.State.DENY; - return true; - } - - @Override - public String getName() { - return "WorldGuard"; - } - - @Override - public boolean isEnabledByDefault() { - return AdaptConfig.get().getProtectorSupport().isWorldguard(); - } - - private boolean hasBypass(Player p, Location l) { - LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(p); - com.sk89q.worldedit.world.World world = BukkitAdapter.adapt(l.getWorld()); - return WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, world); - } - - public static StateFlag registerFlag() { - FlagRegistry registry = WorldGuard.getInstance().getFlagRegistry(); - StateFlag flag = (StateFlag) registry.get("use-adaptations"); - if (flag != null) return flag; - flag = new StateFlag("use-adaptations", false); - - try { - registry.register(flag); - } catch (IllegalStateException ignored) { - Adapt.warn("WorldGuard flag was not registered! Injecting it now..."); - try { - // Access the flags field of the registry - Field field = registry.getClass().getDeclaredField("flags"); - // This line makes the private field accessible - field.setAccessible(true); - // Get the flags from the registry - ConcurrentMap> flags = (ConcurrentMap>) field.get(registry); - // Add it to the registry - flags.put(flag.getName().toLowerCase(), flag); - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); - } - } - return flag; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillAgility.java b/src/main/java/com/volmit/adapt/content/skill/SkillAgility.java deleted file mode 100644 index d0ca8fa82..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillAgility.java +++ /dev/null @@ -1,281 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.agility.AgilityArmorUp; -import com.volmit.adapt.content.adaptation.agility.AgilityLadderSlide; -import com.volmit.adapt.content.adaptation.agility.AgilityParkourMomentum; -import com.volmit.adapt.content.adaptation.agility.AgilityRollLanding; -import com.volmit.adapt.content.adaptation.agility.AgilitySuperJump; -import com.volmit.adapt.content.adaptation.agility.AgilityWallJump; -import com.volmit.adapt.content.adaptation.agility.AgilityWindUp; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerMoveEvent; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class SkillAgility extends SimpleSkill { - private Map lastLocations; - - public SkillAgility() { - super("agility", Localizer.dLocalize("skill.agility.icon")); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("skill.agility.description")); - setDisplayName(Localizer.dLocalize("skill.agility.name")); - setColor(C.GREEN); - setInterval(975); - setIcon(Material.FEATHER); - registerAdaptation(new AgilityWindUp()); - registerAdaptation(new AgilityWallJump()); - registerAdaptation(new AgilitySuperJump()); - registerAdaptation(new AgilityArmorUp()); - registerAdaptation(new AgilityLadderSlide()); - registerAdaptation(new AgilityParkourMomentum()); - registerAdaptation(new AgilityRollLanding()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEATHER_BOOTS) - .key("challenge_move_1k") - .title(Localizer.dLocalize("advancement.challenge_move_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_move_1k.description")) - .model(CustomModel.get(Material.LEATHER_BOOTS, "advancement", "agility", "challenge_move_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_BOOTS) - .key("challenge_sprint_5k") - .title(Localizer.dLocalize("advancement.challenge_sprint_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_sprint_5k.description")) - .model(CustomModel.get(Material.IRON_BOOTS, "advancement", "agility", "challenge_sprint_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_BOOTS) - .key("challenge_sprint_50k") - .title(Localizer.dLocalize("advancement.challenge_sprint_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_sprint_50k.description")) - .model(CustomModel.get(Material.DIAMOND_BOOTS, "advancement", "agility", "challenge_sprint_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_BOOTS) - .key("challenge_sprint_500k") - .title(Localizer.dLocalize("advancement.challenge_sprint_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_sprint_500k.description")) - .model(CustomModel.get(Material.NETHERITE_BOOTS, "advancement", "agility", "challenge_sprint_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .child(AdaptAdvancement.builder() - .icon(Material.GOLDEN_BOOTS) - .key("challenge_sprint_marathon") - .title(Localizer.dLocalize("advancement.challenge_sprint_marathon.title")) - .description(Localizer.dLocalize("advancement.challenge_sprint_marathon.description")) - .model(CustomModel.get(Material.GOLDEN_BOOTS, "advancement", "agility", "challenge_sprint_marathon")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_move_1k", "move", 1000, getConfig().challengeMove1kReward); - registerMilestone("challenge_sprint_5k", "move", 5000, getConfig().challengeSprint5kReward); - registerMilestone("challenge_sprint_50k", "move", 50000, getConfig().challengeSprint5kReward); - registerMilestone("challenge_sprint_500k", "move", 500000, getConfig().challengeSprint5kReward); - registerMilestone("challenge_sprint_marathon", "move", 42195, getConfig().challengeSprintMarathonReward); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_BOOTS).key("challenge_sprint_dist_5k") - .title(Localizer.dLocalize("advancement.challenge_sprint_dist_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_sprint_dist_5k.description")) - .model(CustomModel.get(Material.GOLDEN_BOOTS, "advancement", "agility", "challenge_sprint_dist_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_BOOTS) - .key("challenge_sprint_dist_50k") - .title(Localizer.dLocalize("advancement.challenge_sprint_dist_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_sprint_dist_50k.description")) - .model(CustomModel.get(Material.DIAMOND_BOOTS, "advancement", "agility", "challenge_sprint_dist_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_sprint_dist_5k", "move.sprint", 5000, getConfig().challengeSprint5kReward); - registerMilestone("challenge_sprint_dist_50k", "move.sprint", 50000, getConfig().challengeSprint5kReward * 2); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LILY_PAD).key("challenge_agility_swim_1k") - .title(Localizer.dLocalize("advancement.challenge_agility_swim_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_swim_1k.description")) - .model(CustomModel.get(Material.LILY_PAD, "advancement", "agility", "challenge_agility_swim_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.HEART_OF_THE_SEA) - .key("challenge_agility_swim_10k") - .title(Localizer.dLocalize("advancement.challenge_agility_swim_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_swim_10k.description")) - .model(CustomModel.get(Material.HEART_OF_THE_SEA, "advancement", "agility", "challenge_agility_swim_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_agility_swim_1k", "move.swim", 1000, getConfig().challengeSprint5kReward); - registerMilestone("challenge_agility_swim_10k", "move.swim", 10000, getConfig().challengeSprint5kReward * 2); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FEATHER).key("challenge_fly_1k") - .title(Localizer.dLocalize("advancement.challenge_fly_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_fly_1k.description")) - .model(CustomModel.get(Material.FEATHER, "advancement", "agility", "challenge_fly_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ELYTRA) - .key("challenge_fly_10k") - .title(Localizer.dLocalize("advancement.challenge_fly_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_fly_10k.description")) - .model(CustomModel.get(Material.ELYTRA, "advancement", "agility", "challenge_fly_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_fly_1k", "move.fly", 1000, getConfig().challengeSprint5kReward); - registerMilestone("challenge_fly_10k", "move.fly", 10000, getConfig().challengeSprint5kReward * 2); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEATHER_LEGGINGS).key("challenge_agility_sneak_500") - .title(Localizer.dLocalize("advancement.challenge_agility_sneak_500.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_sneak_500.description")) - .model(CustomModel.get(Material.LEATHER_LEGGINGS, "advancement", "agility", "challenge_agility_sneak_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_LEGGINGS) - .key("challenge_agility_sneak_5k") - .title(Localizer.dLocalize("advancement.challenge_agility_sneak_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_agility_sneak_5k.description")) - .model(CustomModel.get(Material.IRON_LEGGINGS, "advancement", "agility", "challenge_agility_sneak_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_agility_sneak_500", "move.sneak", 500, getConfig().challengeSprint5kReward); - registerMilestone("challenge_agility_sneak_5k", "move.sneak", 5000, getConfig().challengeSprint5kReward * 2); - lastLocations = new HashMap<>(); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerMoveEvent e) { - Player p = e.getPlayer(); - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(p, e, () -> { - if (e.getFrom().getWorld() != null && e.getTo() != null && e.getFrom().getWorld().equals(e.getTo().getWorld())) { - double d = e.getFrom().distance(e.getTo()); - AdaptPlayer adaptPlayer = getPlayer(p); - adaptPlayer.getData().addStat("move", d); - - if (p.isSneaking()) { - adaptPlayer.getData().addStat("move.sneak", d); - } else if (p.isFlying()) { - adaptPlayer.getData().addStat("move.fly", d); - } else if (p.isSwimming()) { - adaptPlayer.getData().addStat("move.swim", d); - } else if (p.isSprinting()) { - adaptPlayer.getData().addStat("move.sprint", d); - } - - // Add XP for moving - xpSilent(p, getConfig().moveXpPassive * d, "agility:move"); - } - }); - } - - - @Override - public void onTick() { - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - shouldReturnForPlayer(i, () -> { - checkStatTrackers(adaptPlayer); - - // Check for sprinting - if (i.isSprinting() && !i.isFlying() && !i.isSwimming() && !i.isSneaking()) { - xpSilent(i, getConfig().sprintXpPassive, "agility:sprint"); - } - - // Check for swimming - if (i.isSwimming() && !i.isFlying() && !i.isSprinting() && !i.isSneaking()) { - xpSilent(i, getConfig().swimXpPassive, "agility:swim"); - } - - // Check for jumping - if (i.getLocation().subtract(0, 1, 0).getBlock().getType().isAir() && !i.isFlying() && !i.isSneaking()) { - xpSilent(i, getConfig().jumpXpPassive, "agility:jump"); - } - - // Check for climbing ladders - if (i.getLocation().getBlock().getType() == Material.LADDER && !i.isFlying() && !i.isSneaking()) { - xpSilent(i, getConfig().climbXpPassive, "agility:climb"); - } - }); - } - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Move1k Reward for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeMove1kReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sprint5k Reward for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSprint5kReward = 2000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sprint Marathon Reward for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSprintMarathonReward = 6500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sprint Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sprintXpPassive = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Swim Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double swimXpPassive = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Jump Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double jumpXpPassive = 0.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Climb Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double climbXpPassive = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Move Xp Passive for the Agility skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double moveXpPassive = 0.05; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillArchitect.java b/src/main/java/com/volmit/adapt/content/skill/SkillArchitect.java deleted file mode 100644 index c5ee9cafc..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillArchitect.java +++ /dev/null @@ -1,263 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.architect.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; - -import java.util.HashMap; -import java.util.Map; - -public class SkillArchitect extends SimpleSkill { - private final Map cooldowns; - - public SkillArchitect() { - super("architect", Localizer.dLocalize("skill.architect.icon")); - registerConfiguration(Config.class); - setColor(C.AQUA); - setDescription(Localizer.dLocalize("skill.architect.description")); - setDisplayName(Localizer.dLocalize("skill.architect.name")); - setInterval(3100); - setIcon(Material.IRON_BARS); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BRICK).key("challenge_place_1k") - .title(Localizer.dLocalize("advancement.challenge_place_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_place_1k.description")) - .model(CustomModel.get(Material.BRICK, "advancement", "architect", "challenge_place_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.BRICK) - .key("challenge_place_5k") - .title(Localizer.dLocalize("advancement.challenge_place_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_place_5k.description")) - .model(CustomModel.get(Material.BRICK, "advancement", "architect", "challenge_place_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.NETHER_BRICK) - .key("challenge_place_50k") - .title(Localizer.dLocalize("advancement.challenge_place_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_place_50k.description")) - .model(CustomModel.get(Material.NETHER_BRICK, "advancement", "architect", "challenge_place_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.NETHER_BRICK) - .key("challenge_place_500k") - .title(Localizer.dLocalize("advancement.challenge_place_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_place_500k.description")) - .model(CustomModel.get(Material.NETHER_BRICK, "advancement", "architect", "challenge_place_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.IRON_INGOT) - .key("challenge_place_5m") - .title(Localizer.dLocalize("advancement.challenge_place_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_place_5m.description")) - .model(CustomModel.get(Material.IRON_INGOT, "advancement", "architect", "challenge_place_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - registerMilestone("challenge_place_1k", "blocks.placed", 1000, getConfig().challengePlace1kReward); - registerMilestone("challenge_place_5k", "blocks.placed", 5000, getConfig().challengePlace1kReward); - registerMilestone("challenge_place_50k", "blocks.placed", 50000, getConfig().challengePlace1kReward); - registerMilestone("challenge_place_500k", "blocks.placed", 500000, getConfig().challengePlace1kReward); - registerMilestone("challenge_place_5m", "blocks.placed", 5000000, getConfig().challengePlace1kReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_PICKAXE).key("challenge_demolish_500") - .title(Localizer.dLocalize("advancement.challenge_demolish_500.title")) - .description(Localizer.dLocalize("advancement.challenge_demolish_500.description")) - .model(CustomModel.get(Material.IRON_PICKAXE, "advancement", "architect", "challenge_demolish_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TNT) - .key("challenge_demolish_5k") - .title(Localizer.dLocalize("advancement.challenge_demolish_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_demolish_5k.description")) - .model(CustomModel.get(Material.TNT, "advancement", "architect", "challenge_demolish_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_demolish_500", "blocks.broken", 500, getConfig().challengePlace1kReward); - registerMilestone("challenge_demolish_5k", "blocks.broken", 5000, getConfig().challengePlace1kReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLD_INGOT).key("challenge_value_placed_10k") - .title(Localizer.dLocalize("advancement.challenge_value_placed_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_value_placed_10k.description")) - .model(CustomModel.get(Material.GOLD_INGOT, "advancement", "architect", "challenge_value_placed_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND) - .key("challenge_value_placed_100k") - .title(Localizer.dLocalize("advancement.challenge_value_placed_100k.title")) - .description(Localizer.dLocalize("advancement.challenge_value_placed_100k.description")) - .model(CustomModel.get(Material.DIAMOND, "advancement", "architect", "challenge_value_placed_100k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_value_placed_10k", "blocks.placed.value", 10000, getConfig().challengePlace1kReward); - registerMilestone("challenge_value_placed_100k", "blocks.placed.value", 100000, getConfig().challengePlace1kReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TNT_MINECART).key("challenge_demolish_val_5k") - .title(Localizer.dLocalize("advancement.challenge_demolish_val_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_demolish_val_5k.description")) - .model(CustomModel.get(Material.TNT_MINECART, "advancement", "architect", "challenge_demolish_val_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.END_CRYSTAL) - .key("challenge_demolish_val_50k") - .title(Localizer.dLocalize("advancement.challenge_demolish_val_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_demolish_val_50k.description")) - .model(CustomModel.get(Material.END_CRYSTAL, "advancement", "architect", "challenge_demolish_val_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_demolish_val_5k", "architect.demolish.value", 5000, getConfig().challengePlace1kReward); - registerMilestone("challenge_demolish_val_50k", "architect.demolish.value", 50000, getConfig().challengePlace1kReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SCAFFOLDING).key("challenge_high_build_100") - .title(Localizer.dLocalize("advancement.challenge_high_build_100.title")) - .description(Localizer.dLocalize("advancement.challenge_high_build_100.description")) - .model(CustomModel.get(Material.SCAFFOLDING, "advancement", "architect", "challenge_high_build_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.LIGHTNING_ROD) - .key("challenge_high_build_1k") - .title(Localizer.dLocalize("advancement.challenge_high_build_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_high_build_1k.description")) - .model(CustomModel.get(Material.LIGHTNING_ROD, "advancement", "architect", "challenge_high_build_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_high_build_100", "architect.builds.high", 100, getConfig().challengePlace1kReward); - registerMilestone("challenge_high_build_1k", "architect.builds.high", 1000, getConfig().challengePlace1kReward * 2); - - setIcon(Material.SMITHING_TABLE); - registerAdaptation(new ArchitectGlass()); - registerAdaptation(new ArchitectFoundation()); - registerAdaptation(new ArchitectPlacement()); - registerAdaptation(new ArchitectWirelessRedstone()); - registerAdaptation(new ArchitectElevator()); - registerAdaptation(new ArchitectSmartShape()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockPlaceEvent e) { - Player p = e.getPlayer(); - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(p, e, () -> { - if (!isStorage(e.getBlock().getType().createBlockData())) { - double v = getValue(e.getBlock()) * getConfig().xpValueMultiplier; - AdaptPlayer adaptPlayer = getPlayer(p); - adaptPlayer.getData().addStat("blocks.placed", 1); - adaptPlayer.getData().addStat("blocks.placed.value", v); - if (e.getBlock().getY() > 128) { - adaptPlayer.getData().addStat("architect.builds.high", 1); - } - - handleBlockCooldown(p, () -> { - try { - xp(p, e.getBlock().getLocation().clone().add(0.5, 0.5, 0.5), blockXP(e.getBlock(), getConfig().xpBase + v)); - } catch (Exception ignored) { - Adapt.verbose("Failed to give XP to " + p.getName() + " for placing " + e.getBlock().getType().name()); - } - }); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - Player p = e.getPlayer(); - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(p, e, () -> { - AdaptPlayer adaptPlayer = getPlayer(p); - adaptPlayer.getData().addStat("blocks.broken", 1); - adaptPlayer.getData().addStat("architect.demolish.value", getValue(e.getBlock())); - }); - } - - @Override - public void onTick() { - checkStatTrackersForOnlinePlayers(); - } - - private void handleBlockCooldown(Player p, Runnable action) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - action.run(); - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Place1k Reward for the Architect skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengePlace1kReward = 1750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Value Multiplier for the Architect skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpValueMultiplier = 1.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Architect skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp Base for the Architect skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpBase = 3; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillAxes.java b/src/main/java/com/volmit/adapt/content/skill/SkillAxes.java deleted file mode 100644 index fe586bf0d..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillAxes.java +++ /dev/null @@ -1,301 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.axe.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -public class SkillAxes extends SimpleSkill { - private final Map cooldowns; - - public SkillAxes() { - super("axes", Localizer.dLocalize("skill.axes.icon")); - registerConfiguration(Config.class); - setColor(C.YELLOW); - setDescription(Localizer.dLocalize("skill.axes.description1") + C.ITALIC + Localizer.dLocalize("skill.axes.description2") + C.GRAY + " " + Localizer.dLocalize("skill.axes.description3")); - setDisplayName(Localizer.dLocalize("skill.axes.name")); - setInterval(5251); - setIcon(Material.GOLDEN_AXE); - cooldowns = new HashMap<>(); - registerAdaptation(new AxeGroundSmash()); - registerAdaptation(new AxeChop()); - registerAdaptation(new AxeDropToInventory()); - registerAdaptation(new AxeLeafVeinminer()); - registerAdaptation(new AxeWoodVeinminer()); - registerAdaptation(new AxeCraftLogSwap()); - registerAdaptation(new AxeTimberMark()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WOODEN_AXE).key("challenge_chop_1k") - .title(Localizer.dLocalize("advancement.challenge_chop_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_chop_1k.description")) - .model(CustomModel.get(Material.WOODEN_AXE, "advancement", "axes", "challenge_chop_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.STONE_AXE) - .key("challenge_chop_5k") - .title(Localizer.dLocalize("advancement.challenge_chop_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_chop_5k.description")) - .model(CustomModel.get(Material.STONE_AXE, "advancement", "axes", "challenge_chop_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.IRON_AXE) - .key("challenge_chop_50k") - .title(Localizer.dLocalize("advancement.challenge_chop_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_chop_50k.description")) - .model(CustomModel.get(Material.IRON_AXE, "advancement", "axes", "challenge_chop_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_AXE) - .key("challenge_chop_500k") - .title(Localizer.dLocalize("advancement.challenge_chop_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_chop_500k.description")) - .model(CustomModel.get(Material.DIAMOND_AXE, "advancement", "axes", "challenge_chop_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_AXE) - .key("challenge_chop_5m") - .title(Localizer.dLocalize("advancement.challenge_chop_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_chop_5m.description")) - .model(CustomModel.get(Material.NETHERITE_AXE, "advancement", "axes", "challenge_chop_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - registerMilestone("challenge_chop_1k", "axes.blocks.broken", 1000, getConfig().challengeChopReward); - registerMilestone("challenge_chop_5k", "axes.blocks.broken", 5000, getConfig().challengeChopReward); - registerMilestone("challenge_chop_50k", "axes.blocks.broken", 50000, getConfig().challengeChopReward); - registerMilestone("challenge_chop_500k", "axes.blocks.broken", 500000, getConfig().challengeChopReward); - registerMilestone("challenge_chop_5m", "axes.blocks.broken", 5000000, getConfig().challengeChopReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WOODEN_AXE).key("challenge_axe_swing_500") - .title(Localizer.dLocalize("advancement.challenge_axe_swing_500.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_swing_500.description")) - .model(CustomModel.get(Material.WOODEN_AXE, "advancement", "axes", "challenge_axe_swing_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_AXE) - .key("challenge_axe_swing_5k") - .title(Localizer.dLocalize("advancement.challenge_axe_swing_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_swing_5k.description")) - .model(CustomModel.get(Material.IRON_AXE, "advancement", "axes", "challenge_axe_swing_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_axe_swing_500", "axes.swings", 500, getConfig().challengeChopReward); - registerMilestone("challenge_axe_swing_5k", "axes.swings", 5000, getConfig().challengeChopReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_AXE).key("challenge_axe_damage_1k") - .title(Localizer.dLocalize("advancement.challenge_axe_damage_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_damage_1k.description")) - .model(CustomModel.get(Material.GOLDEN_AXE, "advancement", "axes", "challenge_axe_damage_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_AXE) - .key("challenge_axe_damage_10k") - .title(Localizer.dLocalize("advancement.challenge_axe_damage_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_damage_10k.description")) - .model(CustomModel.get(Material.DIAMOND_AXE, "advancement", "axes", "challenge_axe_damage_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_axe_damage_1k", "axes.damage", 1000, getConfig().challengeChopReward); - registerMilestone("challenge_axe_damage_10k", "axes.damage", 10000, getConfig().challengeChopReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.OAK_LOG).key("challenge_axe_value_5k") - .title(Localizer.dLocalize("advancement.challenge_axe_value_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_value_5k.description")) - .model(CustomModel.get(Material.OAK_LOG, "advancement", "axes", "challenge_axe_value_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DARK_OAK_LOG) - .key("challenge_axe_value_50k") - .title(Localizer.dLocalize("advancement.challenge_axe_value_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_axe_value_50k.description")) - .model(CustomModel.get(Material.DARK_OAK_LOG, "advancement", "axes", "challenge_axe_value_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_axe_value_5k", "axes.blocks.value", 5000, getConfig().challengeChopReward); - registerMilestone("challenge_axe_value_50k", "axes.blocks.value", 50000, getConfig().challengeChopReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.OAK_LEAVES).key("challenge_leaves_500") - .title(Localizer.dLocalize("advancement.challenge_leaves_500.title")) - .description(Localizer.dLocalize("advancement.challenge_leaves_500.description")) - .model(CustomModel.get(Material.OAK_LEAVES, "advancement", "axes", "challenge_leaves_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.AZALEA_LEAVES) - .key("challenge_leaves_5k") - .title(Localizer.dLocalize("advancement.challenge_leaves_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_leaves_5k.description")) - .model(CustomModel.get(Material.AZALEA_LEAVES, "advancement", "axes", "challenge_leaves_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_leaves_500", "axes.leaves", 500, getConfig().challengeChopReward); - registerMilestone("challenge_leaves_5k", "axes.leaves", 5000, getConfig().challengeChopReward * 2); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p && checkValidEntity(e.getEntity().getType())) { - if (!getConfig().getXpForAttackingWithTools) { - return; - } - shouldReturnForPlayer(p, () -> { - if (e.getEntity().isDead() || e.getEntity().isInvulnerable() || p.isDead() || p.isInvulnerable()) { - return; - } - AdaptPlayer a = getPlayer(p); - ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); - - if (isAxe(hand)) { - handleCooldown(p, () -> { - a.getData().addStat("axes.swings", 1); - a.getData().addStat("axes.damage", e.getDamage()); - xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().axeDamageXPMultiplier * e.getDamage()); - }); - } - }); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(p, () -> { - if (isAxe(p.getInventory().getItemInMainHand())) { - if (isLog(new ItemStack(e.getBlock().getType()))) { - double v = getValue(e.getBlock().getType()); - AdaptPlayer a = getPlayer(p); - a.getData().addStat("axes.blocks.broken", 1); - a.getData().addStat("axes.blocks.value", getValue(e.getBlock().getBlockData())); - handleCooldown(p, () -> xp(p, e.getBlock().getLocation().clone().add(0.5, 0.5, 0.5), blockXP(e.getBlock(), v))); - } - if (e.getBlock().getType().name().endsWith("_LEAVES")) { - getPlayer(p).getData().addStat("axes.leaves", 1); - } - } - }); - } - - private void handleCooldown(Player p, Runnable action) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - action.run(); - } - - public double getValue(Material type) { - double value = super.getValue(type) * getConfig().valueXPMultiplier; - value += Math.min(getConfig().maxHardnessBonus, type.getHardness()); - value += Math.min(getConfig().maxBlastResistanceBonus, type.getBlastResistance()); - - if (type.name().endsWith("_LOG") || type.name().endsWith("_WOOD")) { - value += getConfig().logOrWoodXPMultiplier; - } - if (type.name().endsWith("_LEAVES")) { - value += getConfig().leavesMultiplier; - } - - if (type.getHardness() == 0) { - value = 0; - } - - return value; - } - - - @Override - public void onTick() { - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Get Xp For Attacking With Tools for the Axes skill.", impact = "True enables this behavior and false disables it.") - boolean getXpForAttackingWithTools = true; - - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Hardness Bonus for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxHardnessBonus = 9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Blast Resistance Bonus for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxBlastResistanceBonus = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Chop Reward for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeChopReward = 1750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Log Or Wood XPMultiplier for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double logOrWoodXPMultiplier = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Leaves Multiplier for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double leavesMultiplier = 0.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Value XPMultiplier for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double valueXPMultiplier = 0.175; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Axe Damage XPMultiplier for the Axes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double axeDamageXPMultiplier = 7.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillBlocking.java b/src/main/java/com/volmit/adapt/content/skill/SkillBlocking.java deleted file mode 100644 index 0806701e8..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillBlocking.java +++ /dev/null @@ -1,278 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.blocking.BlockingChainArmorer; -import com.volmit.adapt.content.adaptation.blocking.BlockingBastionStance; -import com.volmit.adapt.content.adaptation.blocking.BlockingBulwarkBash; -import com.volmit.adapt.content.adaptation.blocking.BlockingCounterGuard; -import com.volmit.adapt.content.adaptation.blocking.BlockingHorseArmorer; -import com.volmit.adapt.content.adaptation.blocking.BlockingMirrorBlock; -import com.volmit.adapt.content.adaptation.blocking.BlockingMultiArmor; -import com.volmit.adapt.content.adaptation.blocking.BlockingSaddlecrafter; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; - -import java.util.HashMap; -import java.util.Map; - -public class SkillBlocking extends SimpleSkill { - private final Map cooldowns; - - public SkillBlocking() { - super("blocking", Localizer.dLocalize("skill.blocking.icon")); - registerConfiguration(Config.class); - setColor(C.DARK_GRAY); - setDescription(Localizer.dLocalize("skill.blocking.description")); - setDisplayName(Localizer.dLocalize("skill.blocking.name")); - setInterval(5000); - setIcon(Material.SHIELD); - registerAdaptation(new BlockingMultiArmor()); - registerAdaptation(new BlockingChainArmorer()); - registerAdaptation(new BlockingSaddlecrafter()); - registerAdaptation(new BlockingHorseArmorer()); - registerAdaptation(new BlockingCounterGuard()); - registerAdaptation(new BlockingBastionStance()); - registerAdaptation(new BlockingMirrorBlock()); - registerAdaptation(new BlockingBulwarkBash()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEATHER_CHESTPLATE).key("challenge_block_1k") - .title(Localizer.dLocalize("advancement.challenge_block_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_block_1k.description")) - .model(CustomModel.get(Material.LEATHER_CHESTPLATE, "advancement", "blocking", "challenge_block_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.CHAINMAIL_CHESTPLATE) - .key("challenge_block_5k") - .title(Localizer.dLocalize("advancement.challenge_block_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_block_5k.description")) - .model(CustomModel.get(Material.CHAINMAIL_CHESTPLATE, "advancement", "blocking", "challenge_block_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.IRON_CHESTPLATE) - .key("challenge_block_50k") - .title(Localizer.dLocalize("advancement.challenge_block_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_block_50k.description")) - .model(CustomModel.get(Material.IRON_CHESTPLATE, "advancement", "blocking", "challenge_block_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.GOLDEN_CHESTPLATE) - .key("challenge_block_500k") - .title(Localizer.dLocalize("advancement.challenge_block_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_block_500k.description")) - .model(CustomModel.get(Material.GOLDEN_CHESTPLATE, "advancement", "blocking", "challenge_block_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_CHESTPLATE) - .key("challenge_block_5m") - .title(Localizer.dLocalize("advancement.challenge_block_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_block_5m.description")) - .model(CustomModel.get(Material.DIAMOND_CHESTPLATE, "advancement", "blocking", "challenge_block_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - registerMilestone("challenge_block_1k", "blocked.hits", 1000, getConfig().challengeBlock1kReward); - registerMilestone("challenge_block_5k", "blocked.hits", 5000, getConfig().challengeBlock1kReward); - registerMilestone("challenge_block_50k", "blocked.hits", 50000, getConfig().challengeBlock5kReward); - registerMilestone("challenge_block_500k", "blocked.hits", 500000, getConfig().challengeBlock5kReward); - registerMilestone("challenge_block_5m", "blocked.hits", 5000000, getConfig().challengeBlock5kReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_CHESTPLATE).key("challenge_block_dmg_1k") - .title(Localizer.dLocalize("advancement.challenge_block_dmg_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_block_dmg_1k.description")) - .model(CustomModel.get(Material.IRON_CHESTPLATE, "advancement", "blocking", "challenge_block_dmg_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_CHESTPLATE) - .key("challenge_block_dmg_10k") - .title(Localizer.dLocalize("advancement.challenge_block_dmg_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_block_dmg_10k.description")) - .model(CustomModel.get(Material.NETHERITE_CHESTPLATE, "advancement", "blocking", "challenge_block_dmg_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_block_dmg_1k", "blocked.damage", 1000, getConfig().challengeBlock1kReward); - registerMilestone("challenge_block_dmg_10k", "blocked.damage", 10000, getConfig().challengeBlock5kReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ARROW).key("challenge_block_proj_100") - .title(Localizer.dLocalize("advancement.challenge_block_proj_100.title")) - .description(Localizer.dLocalize("advancement.challenge_block_proj_100.description")) - .model(CustomModel.get(Material.ARROW, "advancement", "blocking", "challenge_block_proj_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_block_proj_1k") - .title(Localizer.dLocalize("advancement.challenge_block_proj_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_block_proj_1k.description")) - .model(CustomModel.get(Material.SPECTRAL_ARROW, "advancement", "blocking", "challenge_block_proj_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_block_proj_100", "blocked.projectiles", 100, getConfig().challengeBlock1kReward); - registerMilestone("challenge_block_proj_1k", "blocked.projectiles", 1000, getConfig().challengeBlock5kReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD).key("challenge_block_melee_500") - .title(Localizer.dLocalize("advancement.challenge_block_melee_500.title")) - .description(Localizer.dLocalize("advancement.challenge_block_melee_500.description")) - .model(CustomModel.get(Material.IRON_SWORD, "advancement", "blocking", "challenge_block_melee_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_block_melee_5k") - .title(Localizer.dLocalize("advancement.challenge_block_melee_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_block_melee_5k.description")) - .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "blocking", "challenge_block_melee_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_block_melee_500", "blocked.melee", 500, getConfig().challengeBlock1kReward); - registerMilestone("challenge_block_melee_5k", "blocked.melee", 5000, getConfig().challengeBlock5kReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_CHESTPLATE).key("challenge_block_heavy_50") - .title(Localizer.dLocalize("advancement.challenge_block_heavy_50.title")) - .description(Localizer.dLocalize("advancement.challenge_block_heavy_50.description")) - .model(CustomModel.get(Material.DIAMOND_CHESTPLATE, "advancement", "blocking", "challenge_block_heavy_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_CHESTPLATE) - .key("challenge_block_heavy_500") - .title(Localizer.dLocalize("advancement.challenge_block_heavy_500.title")) - .description(Localizer.dLocalize("advancement.challenge_block_heavy_500.description")) - .model(CustomModel.get(Material.NETHERITE_CHESTPLATE, "advancement", "blocking", "challenge_block_heavy_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_block_heavy_50", "blocked.heavy", 50, getConfig().challengeBlock1kReward); - registerMilestone("challenge_block_heavy_500", "blocked.heavy", 500, getConfig().challengeBlock5kReward); - - cooldowns = new HashMap<>(); - } - - private void handleCooldown(Player p, Runnable runnable) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - runnable.run(); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof Player p) { - SoundPlayer sp = SoundPlayer.of(p); - shouldReturnForPlayer(p, e, () -> { - if (p.isBlocking()) { - AdaptPlayer adaptPlayer = getPlayer(p); - adaptPlayer.getData().addStat("blocked.hits", 1); - adaptPlayer.getData().addStat("blocked.damage", e.getDamage()); - if (e.getDamager() instanceof Projectile) { - adaptPlayer.getData().addStat("blocked.projectiles", 1); - } else { - adaptPlayer.getData().addStat("blocked.melee", 1); - } - if (e.getDamage() > 5) { - adaptPlayer.getData().addStat("blocked.heavy", 1); - } - - handleCooldown(p, () -> { - xp(p, getConfig().xpOnBlockedAttack); - sp.play(p.getLocation(), Sound.BLOCK_IRON_DOOR_CLOSE, 0.5f, 0.77f); - sp.play(p.getLocation(), Sound.ITEM_ARMOR_EQUIP_LEATHER, 0.5f, 0.77f); - }); - } - }); - } - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - shouldReturnForPlayer(i, () -> { - checkStatTrackers(adaptPlayer); - if (getConfig().passiveXpForUsingShield > 0 && (i.getInventory().getItemInOffHand().getType().equals(Material.SHIELD) || i.getInventory().getItemInMainHand().getType().equals(Material.SHIELD))) { - xpSilent(i, getConfig().passiveXpForUsingShield, "blocking:shield-hold"); - } - }); - } - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Xp On Blocked Attack for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double xpOnBlockedAttack = 25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Block1k Reward for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeBlock1kReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Block5k Reward for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeBlock5kReward = 2000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Passive Xp For Using Shield for the Blocking skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long passiveXpForUsingShield = 0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillBrewing.java b/src/main/java/com/volmit/adapt/content/skill/SkillBrewing.java deleted file mode 100644 index 2c41fa9e2..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillBrewing.java +++ /dev/null @@ -1,354 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import art.arcane.spatial.matter.SpatialMatter; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.data.WorldData; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.brewing.*; -import com.volmit.adapt.content.matter.BrewingStandOwner; -import com.volmit.adapt.content.matter.BrewingStandOwnerMatter; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.entity.PotionSplashEvent; -import org.bukkit.event.inventory.InventoryOpenEvent; -import org.bukkit.event.player.PlayerItemConsumeEvent; -import org.bukkit.inventory.BrewerInventory; -import org.bukkit.inventory.meta.PotionMeta; - -import org.bukkit.Bukkit; - -import java.util.HashMap; -import java.util.Map; - -public class SkillBrewing extends SimpleSkill { - private final Map cooldowns; - - public SkillBrewing() { - super("brewing", Localizer.dLocalize("skill.brewing.icon")); - registerConfiguration(Config.class); - setColor(C.LIGHT_PURPLE); - setDescription(Localizer.dLocalize("skill.brewing.description")); - setDisplayName(Localizer.dLocalize("skill.brewing.name")); - setInterval(5851); - setIcon(Material.LINGERING_POTION); - cooldowns = new HashMap<>(); - registerAdaptation(new BrewingLingering()); // Features - registerAdaptation(new BrewingSuperHeated()); - registerAdaptation(new BrewingAbsorption()); // Brews - registerAdaptation(new BrewingBlindness()); - registerAdaptation(new BrewingDarkness()); - registerAdaptation(new BrewingDecay()); - registerAdaptation(new BrewingFatigue()); - registerAdaptation(new BrewingHaste()); - registerAdaptation(new BrewingHealthBoost()); - registerAdaptation(new BrewingHunger()); - registerAdaptation(new BrewingNausea()); - registerAdaptation(new BrewingResistance()); - registerAdaptation(new BrewingSaturation()); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.POTION).key("challenge_brew_1k") - .title(Localizer.dLocalize("advancement.challenge_brew_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_1k.description")) - .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.POTION) - .key("challenge_brew_5k") - .title(Localizer.dLocalize("advancement.challenge_brew_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_5k.description")) - .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.POTION) - .key("challenge_brew_50k") - .title(Localizer.dLocalize("advancement.challenge_brew_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_50k.description")) - .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.POTION) - .key("challenge_brew_500k") - .title(Localizer.dLocalize("advancement.challenge_brew_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_500k.description")) - .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.POTION) - .key("challenge_brew_5m") - .title(Localizer.dLocalize("advancement.challenge_brew_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_5m.description")) - .model(CustomModel.get(Material.POTION, "advancement", "brewing", "challenge_brew_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - registerMilestone("challenge_brew_1k", "brewing.consumed", 1000, getConfig().challengeBrew1k); - registerMilestone("challenge_brew_5k", "brewing.consumed", 5000, getConfig().challengeBrew1k); - registerMilestone("challenge_brew_50k", "brewing.consumed", 50000, getConfig().challengeBrew1k); - registerMilestone("challenge_brew_500k", "brewing.consumed", 500000, getConfig().challengeBrew1k); - registerMilestone("challenge_brew_5m", "brewing.consumed", 5000000, getConfig().challengeBrew1k); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPLASH_POTION).key("challenge_brewsplash_1k") - .title(Localizer.dLocalize("advancement.challenge_brewsplash_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_brewsplash_1k.description")) - .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.SPLASH_POTION) - .key("challenge_brewsplash_5k") - .title(Localizer.dLocalize("advancement.challenge_brewsplash_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_brewsplash_5k.description")) - .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.SPLASH_POTION) - .key("challenge_brewsplash_50k") - .title(Localizer.dLocalize("advancement.challenge_brewsplash_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_brewsplash_50k.description")) - .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.SPLASH_POTION) - .key("challenge_brewsplash_500k") - .title(Localizer.dLocalize("advancement.challenge_brewsplash_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_brewsplash_500k.description")) - .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.SPLASH_POTION) - .key("challenge_brewsplash_5m") - .title(Localizer.dLocalize("advancement.challenge_brewsplash_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_brewsplash_5m.description")) - .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "brewsplash_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - registerMilestone("challenge_brewsplash_1k", "brewing.splashes", 1000, getConfig().challengeBrewSplash1k); - registerMilestone("challenge_brewsplash_5k", "brewing.splashes", 5000, getConfig().challengeBrewSplash1k); - registerMilestone("challenge_brewsplash_50k", "brewing.splashes", 50000, getConfig().challengeBrewSplash1k); - registerMilestone("challenge_brewsplash_500k", "brewing.splashes", 500000, getConfig().challengeBrewSplash1k); - registerMilestone("challenge_brewsplash_5m", "brewing.splashes", 5000000, getConfig().challengeBrewSplash1k); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BREWING_STAND).key("challenge_brew_stands_10") - .title(Localizer.dLocalize("advancement.challenge_brew_stands_10.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_stands_10.description")) - .model(CustomModel.get(Material.BREWING_STAND, "advancement", "brewing", "challenge_brew_stands_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BLAZE_ROD) - .key("challenge_brew_stands_50") - .title(Localizer.dLocalize("advancement.challenge_brew_stands_50.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_stands_50.description")) - .model(CustomModel.get(Material.BLAZE_ROD, "advancement", "brewing", "challenge_brew_stands_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_brew_stands_10", "brewing.stands.placed", 10, getConfig().challengeBrew1k); - registerMilestone("challenge_brew_stands_50", "brewing.stands.placed", 50, getConfig().challengeBrew1k * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GLOWSTONE_DUST).key("challenge_brew_strong_25") - .title(Localizer.dLocalize("advancement.challenge_brew_strong_25.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_strong_25.description")) - .model(CustomModel.get(Material.GLOWSTONE_DUST, "advancement", "brewing", "challenge_brew_strong_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DRAGON_BREATH) - .key("challenge_brew_strong_250") - .title(Localizer.dLocalize("advancement.challenge_brew_strong_250.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_strong_250.description")) - .model(CustomModel.get(Material.DRAGON_BREATH, "advancement", "brewing", "challenge_brew_strong_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_brew_strong_25", "brewing.strong", 25, getConfig().challengeBrew1k); - registerMilestone("challenge_brew_strong_250", "brewing.strong", 250, getConfig().challengeBrew1k * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPLASH_POTION).key("challenge_brew_splash_hits_50") - .title(Localizer.dLocalize("advancement.challenge_brew_splash_hits_50.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_splash_hits_50.description")) - .model(CustomModel.get(Material.SPLASH_POTION, "advancement", "brewing", "challenge_brew_splash_hits_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.LINGERING_POTION) - .key("challenge_brew_splash_hits_500") - .title(Localizer.dLocalize("advancement.challenge_brew_splash_hits_500.title")) - .description(Localizer.dLocalize("advancement.challenge_brew_splash_hits_500.description")) - .model(CustomModel.get(Material.LINGERING_POTION, "advancement", "brewing", "challenge_brew_splash_hits_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_brew_splash_hits_50", "brewing.splash.hits", 50, getConfig().challengeBrewSplash1k); - registerMilestone("challenge_brew_splash_hits_500", "brewing.splash.hits", 500, getConfig().challengeBrewSplash1k * 2); - - SpatialMatter.registerSliceType(new BrewingStandOwnerMatter()); - } - - private void handleCooldown(Player p, Runnable runnable) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - runnable.run(); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerItemConsumeEvent e) { - Player p = e.getPlayer(); - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(p, e, () -> { - if (e.getItem().getItemMeta() instanceof PotionMeta o - && !e.getItem().toString().contains("potion-type=minecraft:water") - && !e.getItem().toString().contains("potion-type=minecraft:mundane") - && !e.getItem().toString().contains("potion-type=minecraft:thick") - && !e.getItem().toString().contains("potion-type=minecraft:awkward")) { - getPlayer(p).getData().addStat("brewing.consumed", 1); - if (o.getBasePotionData().isUpgraded()) { - getPlayer(p).getData().addStat("brewing.strong", 1); - } - handleCooldown(p, () -> xp(p, p.getLocation(), - getConfig().splashXP - + (getConfig().splashMultiplier * o.getCustomEffects().stream().mapToDouble(i -> (i.getAmplifier() + 1) * (i.getDuration() / 20D)).sum()) - + (getConfig().splashMultiplier * (o.getBasePotionData().isUpgraded() ? 50 : 25)))); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PotionSplashEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getPotion().getShooter() instanceof Player p) { - shouldReturnForPlayer(p, e, () -> { - AdaptPlayer a = getPlayer(p); - getPlayer(p).getData().addStat("brewing.splashes", 1); - getPlayer(p).getData().addStat("brewing.splash.hits", e.getAffectedEntities().size()); - xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().splashXP + (getConfig().splashMultiplier * e.getPotion().getEffects().stream().mapToDouble(i -> (i.getAmplifier() + 1) * (i.getDuration() / 20D)).sum())); - }); - } - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockPlaceEvent e) { - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (e.getBlock().getType().equals(Material.BREWING_STAND)) { - WorldData.of(e.getBlock().getWorld()).set(e.getBlock(), new BrewingStandOwner(e.getPlayer().getUniqueId())); - getPlayer(e.getPlayer()).getData().addStat("brewing.stands.placed", 1); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(InventoryOpenEvent e) { - if (e.isCancelled() - || !(e.getPlayer() instanceof Player player) - || !(e.getInventory() instanceof BrewerInventory inv)) { - return; - } - - var holder = inv.getHolder(); - if (holder == null) return; - var block = holder.getBlock(); - if (block.getType() != Material.BREWING_STAND) return; - - shouldReturnForPlayer(player, e, () -> { - var data = WorldData.of(block.getWorld()); - var owner = data.get(block, BrewingStandOwner.class); - if (owner != null) return; - data.set(block, new BrewingStandOwner(player.getUniqueId())); - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (!e.getBlock().getType().equals(Material.BREWING_STAND)) { - return; - } - WorldData.of(e.getBlock().getWorld()).remove(e.getBlock(), BrewingStandOwner.class); - }); - } - - @Override - public void onTick() { - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Brew1k for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeBrew1k = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Brew Splash1k for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeBrewSplash1k = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Splash XP for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double splashXP = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 2500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Splash Multiplier for the Brewing skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double splashMultiplier = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillChronos.java b/src/main/java/com/volmit/adapt/content/skill/SkillChronos.java deleted file mode 100644 index 84ea9d870..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillChronos.java +++ /dev/null @@ -1,631 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.chronos.ChronosAberrantTouch; -import com.volmit.adapt.content.adaptation.chronos.ChronosInstantRecall; -import com.volmit.adapt.content.adaptation.chronos.ChronosTemporalEcho; -import com.volmit.adapt.content.adaptation.chronos.ChronosTimeBomb; -import com.volmit.adapt.content.adaptation.chronos.ChronosTimeInABottle; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.EnderPearl; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.event.player.PlayerBedEnterEvent; -import org.bukkit.event.player.PlayerItemConsumeEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.potion.PotionType; - -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -public class SkillChronos extends SimpleSkill { - private final Map lastPositions; - private final Map> positionHistory; - private final Map> recentActionTypes; - private final Map actionTypeResetTimestamps; - private final Map lastActivityTimestamps; - private final Map sleepCooldowns; - private final Map sleepEntryWorldTime; - private final Map speedPotionTrackers; - private final Map enderPearlCooldowns; - private final Map survivalStreakStart; - private final Map lastSurvivalCheck; - - public SkillChronos() { - super("chronos", Localizer.dLocalize("skill.chronos.icon")); - registerConfiguration(Config.class); - setColor(C.AQUA); - setInterval(600000); - setDescription(Localizer.dLocalize("skill.chronos.description")); - setDisplayName(Localizer.dLocalize("skill.chronos.name")); - setInterval(getConfig().setInterval); - setIcon(Material.CLOCK); - registerAdaptation(new ChronosTimeInABottle()); - registerAdaptation(new ChronosAberrantTouch()); - registerAdaptation(new ChronosInstantRecall()); - registerAdaptation(new ChronosTimeBomb()); - registerAdaptation(new ChronosTemporalEcho()); - lastPositions = new HashMap<>(); - positionHistory = new HashMap<>(); - recentActionTypes = new HashMap<>(); - actionTypeResetTimestamps = new HashMap<>(); - lastActivityTimestamps = new HashMap<>(); - sleepCooldowns = new HashMap<>(); - sleepEntryWorldTime = new HashMap<>(); - speedPotionTrackers = new HashMap<>(); - enderPearlCooldowns = new HashMap<>(); - survivalStreakStart = new HashMap<>(); - lastSurvivalCheck = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CLOCK) - .key("challenge_chronos_1h") - .title(Localizer.dLocalize("advancement.challenge_chronos_1h.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_1h.description")) - .model(CustomModel.get(Material.CLOCK, "advancement", "chronos", "challenge_chronos_1h")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.COMPASS) - .key("challenge_chronos_24h") - .title(Localizer.dLocalize("advancement.challenge_chronos_24h.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_24h.description")) - .model(CustomModel.get(Material.COMPASS, "advancement", "chronos", "challenge_chronos_24h")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.RECOVERY_COMPASS) - .key("challenge_chronos_168h") - .title(Localizer.dLocalize("advancement.challenge_chronos_168h.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_168h.description")) - .model(CustomModel.get(Material.RECOVERY_COMPASS, "advancement", "chronos", "challenge_chronos_168h")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_chronos_1h", "minutes.online", 60, getConfig().challengeChronosReward); - registerMilestone("challenge_chronos_24h", "minutes.online", 1440, getConfig().challengeChronosReward * 2); - registerMilestone("challenge_chronos_168h", "minutes.online", 10080, getConfig().challengeChronosReward * 5); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.COMPASS).key("challenge_active_dist_1k") - .title(Localizer.dLocalize("advancement.challenge_active_dist_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_active_dist_1k.description")) - .model(CustomModel.get(Material.COMPASS, "advancement", "chronos", "challenge_active_dist_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.RECOVERY_COMPASS) - .key("challenge_active_dist_10k") - .title(Localizer.dLocalize("advancement.challenge_active_dist_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_active_dist_10k.description")) - .model(CustomModel.get(Material.RECOVERY_COMPASS, "advancement", "chronos", "challenge_active_dist_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.LODESTONE) - .key("challenge_active_dist_100k") - .title(Localizer.dLocalize("advancement.challenge_active_dist_100k.title")) - .description(Localizer.dLocalize("advancement.challenge_active_dist_100k.description")) - .model(CustomModel.get(Material.LODESTONE, "advancement", "chronos", "challenge_active_dist_100k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_active_dist_1k", "chronos.active.distance", 1000, getConfig().challengeChronosReward); - registerMilestone("challenge_active_dist_10k", "chronos.active.distance", 10000, getConfig().challengeChronosReward * 2); - registerMilestone("challenge_active_dist_100k", "chronos.active.distance", 100000, getConfig().challengeChronosReward * 5); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WHITE_BED).key("challenge_beds_10") - .title(Localizer.dLocalize("advancement.challenge_beds_10.title")) - .description(Localizer.dLocalize("advancement.challenge_beds_10.description")) - .model(CustomModel.get(Material.WHITE_BED, "advancement", "chronos", "challenge_beds_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.RED_BED) - .key("challenge_beds_100") - .title(Localizer.dLocalize("advancement.challenge_beds_100.title")) - .description(Localizer.dLocalize("advancement.challenge_beds_100.description")) - .model(CustomModel.get(Material.RED_BED, "advancement", "chronos", "challenge_beds_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_beds_10", "chronos.beds.used", 10, getConfig().challengeChronosReward); - registerMilestone("challenge_beds_100", "chronos.beds.used", 100, getConfig().challengeChronosReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL).key("challenge_chronos_tp_50") - .title(Localizer.dLocalize("advancement.challenge_chronos_tp_50.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_tp_50.description")) - .model(CustomModel.get(Material.ENDER_PEARL, "advancement", "chronos", "challenge_chronos_tp_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CHORUS_FRUIT) - .key("challenge_chronos_tp_500") - .title(Localizer.dLocalize("advancement.challenge_chronos_tp_500.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_tp_500.description")) - .model(CustomModel.get(Material.CHORUS_FRUIT, "advancement", "chronos", "challenge_chronos_tp_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_chronos_tp_50", "chronos.teleports", 50, getConfig().challengeChronosReward); - registerMilestone("challenge_chronos_tp_500", "chronos.teleports", 500, getConfig().challengeChronosReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SKELETON_SKULL).key("challenge_chronos_deaths_10") - .title(Localizer.dLocalize("advancement.challenge_chronos_deaths_10.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_deaths_10.description")) - .model(CustomModel.get(Material.SKELETON_SKULL, "advancement", "chronos", "challenge_chronos_deaths_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.WITHER_SKELETON_SKULL) - .key("challenge_chronos_deaths_100") - .title(Localizer.dLocalize("advancement.challenge_chronos_deaths_100.title")) - .description(Localizer.dLocalize("advancement.challenge_chronos_deaths_100.description")) - .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "chronos", "challenge_chronos_deaths_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_chronos_deaths_10", "chronos.deaths", 10, getConfig().challengeChronosReward); - registerMilestone("challenge_chronos_deaths_100", "chronos.deaths", 100, getConfig().challengeChronosReward * 2); - } - - private void trackAction(UUID uuid, String actionType) { - long now = System.currentTimeMillis(); - lastActivityTimestamps.put(uuid, now); - - Long resetTime = actionTypeResetTimestamps.get(uuid); - if (resetTime == null || now - resetTime > getConfig().activityWindow) { - recentActionTypes.put(uuid, new HashSet<>()); - actionTypeResetTimestamps.put(uuid, now); - } - recentActionTypes.computeIfAbsent(uuid, k -> new HashSet<>()).add(actionType); - } - - private boolean isAfk(UUID uuid) { - Deque history = positionHistory.get(uuid); - if (history == null || history.size() < 3) { - return false; - } - - double avgX = 0; - double avgZ = 0; - int count = 0; - for (Location loc : history) { - avgX += loc.getX(); - avgZ += loc.getZ(); - count++; - } - avgX /= count; - avgZ /= count; - - double variance = 0; - for (Location loc : history) { - double dx = loc.getX() - avgX; - double dz = loc.getZ() - avgZ; - variance += Math.sqrt(dx * dx + dz * dz); - } - variance /= count; - - Set actions = recentActionTypes.getOrDefault(uuid, new HashSet<>()); - return variance < getConfig().afkVarianceThreshold && actions.size() < getConfig().afkMinActionTypes; - } - - private double getAfkMultiplier(UUID uuid) { - return isAfk(uuid) ? getConfig().afkPenaltyMultiplier : 1.0; - } - - private boolean isNight(Player p) { - long time = p.getWorld().getTime(); - return time >= 12542 && time <= 23460; - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - long now = System.currentTimeMillis(); - - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player p = adaptPlayer.getPlayer(); - if (shouldReturnForPlayer(p)) { - continue; - } - - UUID uuid = p.getUniqueId(); - Location current = p.getLocation(); - Location last = lastPositions.get(uuid); - - // Update position history - Deque history = positionHistory.computeIfAbsent(uuid, k -> new ArrayDeque<>()); - history.addLast(current.clone()); - while (history.size() > getConfig().positionHistorySize) { - history.removeFirst(); - } - - double moved = (last != null && last.getWorld() != null && last.getWorld().equals(current.getWorld())) - ? last.distance(current) - : 0; - - // Track movement as an action type - if (moved >= getConfig().minimumMovementForActiveCheck) { - trackAction(uuid, "movement"); - } - - double afkMult = getAfkMultiplier(uuid); - - // Movement XP (existing behavior, now with AFK penalty) - if (moved >= getConfig().minimumMovementForActiveCheck) { - adaptPlayer.getData().addStat("minutes.online", 10); - adaptPlayer.getData().addStat("chronos.active.distance", moved); - double bonus = (moved / getConfig().distancePerBonusXP) * getConfig().activeMovementXP; - xpSilent(p, Math.min(getConfig().activeMovementXPCapPerTick, bonus) * afkMult, "chronos:movement"); - } - - // Passive active-play XP - Long lastActivity = lastActivityTimestamps.get(uuid); - if (lastActivity != null && now - lastActivity < getConfig().activityWindow) { - double passiveXP = getConfig().passiveActiveXP; - - // Night activity multiplier - if (isNight(p)) { - passiveXP *= getConfig().nightActivityMultiplier; - } - - // Activity variety bonus - Set actions = recentActionTypes.getOrDefault(uuid, new HashSet<>()); - if (actions.size() >= getConfig().activityTypesForBonus) { - passiveXP *= getConfig().activityBonusMultiplier; - } - - xpSilent(p, passiveXP * afkMult, "chronos:passive"); - } - - // Survival streak XP - survivalStreakStart.putIfAbsent(uuid, now); - Long lastCheck = lastSurvivalCheck.get(uuid); - if (lastCheck == null || now - lastCheck >= 60000) { - lastSurvivalCheck.put(uuid, now); - long aliveMs = now - survivalStreakStart.getOrDefault(uuid, now); - double aliveHours = aliveMs / 3600000.0; - double streakBonus = 1.0 + Math.min( - aliveHours * getConfig().survivalStreakBonusPerHour, - getConfig().survivalStreakHourCap * getConfig().survivalStreakBonusPerHour - ); - xpSilent(p, getConfig().survivalXPPerMinute * streakBonus * afkMult, "chronos:survival"); - } - - checkStatTrackers(adaptPlayer); - lastPositions.put(uuid, current.clone()); - } - } - - // --- Sleep XP --- - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerBedEnterEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(p, () -> { - UUID uuid = p.getUniqueId(); - long now = System.currentTimeMillis(); - - Long lastSleep = sleepCooldowns.get(uuid); - if (lastSleep != null && now - lastSleep < getConfig().sleepCooldown) { - return; - } - - trackAction(uuid, "sleep"); - long worldTime = p.getWorld().getTime(); - sleepEntryWorldTime.put(uuid, worldTime); - sleepCooldowns.put(uuid, now); - getPlayer(p).getData().addStat("chronos.beds.used", 1); - - Bukkit.getScheduler().runTaskLater(Adapt.instance, () -> { - if (!p.isOnline()) { - return; - } - long currentWorldTime = p.getWorld().getTime(); - boolean nightSkipped = currentWorldTime < 1000 || currentWorldTime < worldTime - 100; - if (nightSkipped) { - xp(p, p.getLocation(), getConfig().sleepSkipXP); - } else { - xp(p, p.getLocation(), getConfig().sleepAttemptXP); - } - }, 40L); - }); - } - - // --- Speed Potion XP --- - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerItemConsumeEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(p, () -> { - ItemStack item = e.getItem(); - if (item.getType() != Material.POTION) { - return; - } - if (!(item.getItemMeta() instanceof PotionMeta meta)) { - return; - } - - boolean isSpeedPotion = false; - boolean isSpeedII = false; - - PotionType baseType = meta.getBasePotionType(); - if (baseType == PotionType.SWIFTNESS) { - isSpeedPotion = true; - } - if (baseType == PotionType.STRONG_SWIFTNESS) { - isSpeedPotion = true; - isSpeedII = true; - } - - if (!isSpeedPotion && meta.hasCustomEffects()) { - isSpeedPotion = meta.getCustomEffects().stream() - .anyMatch(effect -> effect.getType().equals(PotionEffectType.SPEED)); - isSpeedII = meta.getCustomEffects().stream() - .anyMatch(effect -> effect.getType().equals(PotionEffectType.SPEED) && effect.getAmplifier() >= 1); - } - - if (!isSpeedPotion) { - return; - } - - UUID uuid = p.getUniqueId(); - trackAction(uuid, "potion"); - long now = System.currentTimeMillis(); - - SpeedPotionTracker tracker = speedPotionTrackers.computeIfAbsent(uuid, k -> new SpeedPotionTracker()); - - if (now - tracker.lastUseTime > getConfig().speedPotionResetWindow) { - tracker.consecutiveUses = 0; - } - - double decay = getConfig().speedPotionDiminishingDecay; - double floor = getConfig().speedPotionDiminishingFloor; - double multiplier = Math.max(floor, Math.pow(1.0 - decay, tracker.consecutiveUses)); - - double xpAmount = getConfig().speedPotionBaseXP * multiplier; - if (isSpeedII) { - xpAmount *= getConfig().speedPotionLevelMultiplier; - } - - tracker.consecutiveUses++; - tracker.lastUseTime = now; - - xp(p, p.getLocation(), xpAmount); - }); - } - - // --- Ender Pearl XP --- - - @EventHandler(priority = EventPriority.MONITOR) - public void on(ProjectileLaunchEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getEntity() instanceof EnderPearl pearl)) { - return; - } - if (!(pearl.getShooter() instanceof Player p)) { - return; - } - shouldReturnForPlayer(p, () -> { - UUID uuid = p.getUniqueId(); - long now = System.currentTimeMillis(); - - Long lastThrow = enderPearlCooldowns.get(uuid); - if (lastThrow != null && now - lastThrow < getConfig().enderPearlCooldown) { - return; - } - - trackAction(uuid, "teleport"); - enderPearlCooldowns.put(uuid, now); - xp(p, p.getLocation(), getConfig().enderPearlThrowXP); - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerTeleportEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { - return; - } - Player p = e.getPlayer(); - if (ChronosInstantRecall.isRecallTeleportSuppressed(p)) { - return; - } - shouldReturnForPlayer(p, () -> { - trackAction(p.getUniqueId(), "teleport"); - getPlayer(p).getData().addStat("chronos.teleports", 1); - xp(p, e.getTo(), getConfig().enderPearlTeleportXP); - }); - } - - // --- Death / Survival Streak Reset --- - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerDeathEvent e) { - Player p = e.getEntity(); - UUID uuid = p.getUniqueId(); - survivalStreakStart.put(uuid, System.currentTimeMillis()); - lastSurvivalCheck.remove(uuid); - trackAction(uuid, "combat"); - shouldReturnForPlayer(p, () -> getPlayer(p).getData().addStat("chronos.deaths", 1)); - } - - // --- Player Quit Cleanup --- - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - UUID uuid = e.getPlayer().getUniqueId(); - lastPositions.remove(uuid); - positionHistory.remove(uuid); - recentActionTypes.remove(uuid); - actionTypeResetTimestamps.remove(uuid); - lastActivityTimestamps.remove(uuid); - sleepCooldowns.remove(uuid); - sleepEntryWorldTime.remove(uuid); - speedPotionTrackers.remove(uuid); - enderPearlCooldowns.remove(uuid); - survivalStreakStart.remove(uuid); - lastSurvivalCheck.remove(uuid); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @Override - protected void onConfigReload(Config previousConfig, Config newConfig) { - super.onConfigReload(previousConfig, newConfig); - setInterval(newConfig.setInterval); - } - - private static class SpeedPotionTracker { - int consecutiveUses; - long lastUseTime; - } - - @NoArgsConstructor - protected static class Config { - // Existing - @com.volmit.adapt.util.config.ConfigDoc(value = "Tick interval used by this logic.", impact = "Lower values run logic more often; higher values run it less often.") - long setInterval = 5050; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Minimum Movement For Active Check for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double minimumMovementForActiveCheck = 0.35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Distance Per Bonus XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double distancePerBonusXP = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Active Movement XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double activeMovementXP = 3.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Active Movement XPCap Per Tick for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double activeMovementXPCapPerTick = 6; - - // Anti-AFK - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Position History Size for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int positionHistorySize = 12; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Afk Variance Threshold for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double afkVarianceThreshold = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Afk Min Action Types for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int afkMinActionTypes = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Afk Penalty Multiplier for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double afkPenaltyMultiplier = 0.03; - - // Passive active XP - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Passive Active XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double passiveActiveXP = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Activity Window for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long activityWindow = 15000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Activity Types For Bonus for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int activityTypesForBonus = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Activity Bonus Multiplier for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double activityBonusMultiplier = 1.5; - - // Night bonus - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Night Activity Multiplier for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double nightActivityMultiplier = 1.3; - - // Sleep - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sleep Skip XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sleepSkipXP = 150; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sleep Attempt XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sleepAttemptXP = 25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sleep Cooldown for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long sleepCooldown = 30000; - - // Speed potion - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Base XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedPotionBaseXP = 45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Level Multiplier for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedPotionLevelMultiplier = 1.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Diminishing Decay for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedPotionDiminishingDecay = 0.15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Diminishing Floor for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double speedPotionDiminishingFloor = 0.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Speed Potion Reset Window for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long speedPotionResetWindow = 300000; - - // Ender pearl - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ender Pearl Throw XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double enderPearlThrowXP = 35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ender Pearl Teleport XP for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double enderPearlTeleportXP = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Ender Pearl Cooldown for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long enderPearlCooldown = 10000; - - // Survival streak - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Survival XPPer Minute for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double survivalXPPerMinute = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Survival Streak Bonus Per Hour for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double survivalStreakBonusPerHour = 0.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Survival Streak Hour Cap for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int survivalStreakHourCap = 5; - - // Challenge rewards - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Chronos Reward for the Chronos skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeChronosReward = 500; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillCrafting.java b/src/main/java/com/volmit/adapt/content/skill/SkillCrafting.java deleted file mode 100644 index 4dc15b2a4..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillCrafting.java +++ /dev/null @@ -1,343 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.crafting.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.inventory.FurnaceSmeltEvent; -import org.bukkit.inventory.CraftingInventory; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -public class SkillCrafting extends SimpleSkill { - private final Map cooldowns; - - public SkillCrafting() { - super("crafting", Localizer.dLocalize("skill.crafting.icon")); - registerConfiguration(Config.class); - setColor(C.YELLOW); - setDescription(Localizer.dLocalize("skill.crafting.description")); - setDisplayName(Localizer.dLocalize("skill.crafting.name")); - setInterval(3789); - setIcon(Material.CRAFTING_TABLE); - registerAdaptation(new CraftingDeconstruction()); - registerAdaptation(new CraftingXP()); - registerAdaptation(new CraftingLeather()); - registerAdaptation(new CraftingSkulls()); - registerAdaptation(new CraftingBackpacks()); - registerAdaptation(new CraftingStations()); - registerAdaptation(new CraftingReconstruction()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CRAFTING_TABLE).key("challenge_craft_1k") - .title(Localizer.dLocalize("advancement.challenge_craft_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_1k.description")) - .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.CRAFTING_TABLE) - .key("challenge_craft_5k") - .title(Localizer.dLocalize("advancement.challenge_craft_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_5k.description")) - .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.CRAFTING_TABLE) - .key("challenge_craft_50k") - .title(Localizer.dLocalize("advancement.challenge_craft_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_50k.description")) - .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.CRAFTING_TABLE) - .key("challenge_craft_500k") - .title(Localizer.dLocalize("advancement.challenge_craft_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_500k.description")) - .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.CRAFTING_TABLE) - .key("challenge_craft_5m") - .title(Localizer.dLocalize("advancement.challenge_craft_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_5m.description")) - .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "crafting", "challenge_craft_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - registerMilestone("challenge_craft_1k", "crafted.items", 1000, getConfig().challengeCraft1kReward); - registerMilestone("challenge_craft_5k", "crafted.items", 5000, getConfig().challengeCraft1kReward); - registerMilestone("challenge_craft_50k", "crafted.items", 50000, getConfig().challengeCraft1kReward); - registerMilestone("challenge_craft_500k", "crafted.items", 500000, getConfig().challengeCraft1kReward); - registerMilestone("challenge_craft_5m", "crafted.items", 5000000, getConfig().challengeCraft1kReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLD_INGOT).key("challenge_craft_value_10k") - .title(Localizer.dLocalize("advancement.challenge_craft_value_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_value_10k.description")) - .model(CustomModel.get(Material.GOLD_INGOT, "advancement", "crafting", "challenge_craft_value_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND) - .key("challenge_craft_value_100k") - .title(Localizer.dLocalize("advancement.challenge_craft_value_100k.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_value_100k.description")) - .model(CustomModel.get(Material.DIAMOND, "advancement", "crafting", "challenge_craft_value_100k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_craft_value_10k", "crafted.value", 10000, getConfig().challengeCraft1kReward); - registerMilestone("challenge_craft_value_100k", "crafted.value", 100000, getConfig().challengeCraft1kReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_PICKAXE).key("challenge_craft_tools_25") - .title(Localizer.dLocalize("advancement.challenge_craft_tools_25.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_tools_25.description")) - .model(CustomModel.get(Material.IRON_PICKAXE, "advancement", "crafting", "challenge_craft_tools_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_PICKAXE) - .key("challenge_craft_tools_250") - .title(Localizer.dLocalize("advancement.challenge_craft_tools_250.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_tools_250.description")) - .model(CustomModel.get(Material.DIAMOND_PICKAXE, "advancement", "crafting", "challenge_craft_tools_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_craft_tools_25", "crafting.tools", 25, getConfig().challengeCraft1kReward); - registerMilestone("challenge_craft_tools_250", "crafting.tools", 250, getConfig().challengeCraft1kReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_CHESTPLATE).key("challenge_craft_armor_25") - .title(Localizer.dLocalize("advancement.challenge_craft_armor_25.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_armor_25.description")) - .model(CustomModel.get(Material.IRON_CHESTPLATE, "advancement", "crafting", "challenge_craft_armor_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_CHESTPLATE) - .key("challenge_craft_armor_250") - .title(Localizer.dLocalize("advancement.challenge_craft_armor_250.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_armor_250.description")) - .model(CustomModel.get(Material.DIAMOND_CHESTPLATE, "advancement", "crafting", "challenge_craft_armor_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_craft_armor_25", "crafting.armor", 25, getConfig().challengeCraft1kReward); - registerMilestone("challenge_craft_armor_250", "crafting.armor", 250, getConfig().challengeCraft1kReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.HOPPER).key("challenge_craft_mass_25k") - .title(Localizer.dLocalize("advancement.challenge_craft_mass_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_mass_25k.description")) - .model(CustomModel.get(Material.HOPPER, "advancement", "crafting", "challenge_craft_mass_25k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_craft_mass_250k") - .title(Localizer.dLocalize("advancement.challenge_craft_mass_250k.title")) - .description(Localizer.dLocalize("advancement.challenge_craft_mass_250k.description")) - .model(CustomModel.get(Material.CHEST, "advancement", "crafting", "challenge_craft_mass_250k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_craft_mass_25k", "crafted.items", 25000, getConfig().challengeCraft1kReward * 2); - registerMilestone("challenge_craft_mass_250k", "crafted.items", 250000, getConfig().challengeCraft1kReward * 5); - - cooldowns = new HashMap<>(); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = (Player) e.getWhoClicked(); - shouldReturnForPlayer(p, e, () -> { - if (!isValidCraftEvent(e)) { - return; - } - int recipeAmount = calculateRecipeAmount(e); - if (recipeAmount > 0 && !e.isCancelled()) { - double v = recipeAmount * getValue(e.getRecipe().getResult()) * getConfig().craftingValueXPMultiplier; - getPlayer(p).getData().addStat("crafted.items", recipeAmount); - getPlayer(p).getData().addStat("crafted.value", v); - Material resultType = e.getRecipe().getResult().getType(); - String typeName = resultType.name(); - if (typeName.contains("_PICKAXE") || typeName.contains("_AXE") || typeName.contains("_SHOVEL") || typeName.contains("_HOE") || typeName.contains("_SWORD")) { - getPlayer(p).getData().addStat("crafting.tools", recipeAmount); - } - if (typeName.contains("_HELMET") || typeName.contains("_CHESTPLATE") || typeName.contains("_LEGGINGS") || typeName.contains("_BOOTS")) { - getPlayer(p).getData().addStat("crafting.armor", recipeAmount); - } - xp(p, v + getConfig().baseCraftingXP); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(FurnaceSmeltEvent e) { - if (e.isCancelled()) { - return; - } - if (shouldReturnForWorld(e.getBlock().getWorld(), this)) { - return; - } - xp(e.getBlock().getLocation(), getConfig().furnaceBaseXP + (getValue(e.getResult()) * getConfig().furnaceValueXPMultiplier), getConfig().furnaceXPRadius, getConfig().furnaceXPDuration); - } - - @Override - public void onTick() { - checkStatTrackersForOnlinePlayers(); - } - - - private boolean isValidCraftEvent(CraftItemEvent e) { - Player p = (Player) e.getWhoClicked(); - - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return false; - cooldowns.put(p, System.currentTimeMillis()); - - ItemStack result = e.getInventory().getResult(); - ItemStack cursor = e.getCursor(); - - return result != null && result.getAmount() > 0 && (cursor == null || cursor.getAmount() < 64); - } - - private int calculateRecipeAmount(CraftItemEvent e) { - ItemStack test = e.getRecipe().getResult().clone(); - int recipeAmount = e.getInventory().getResult().getAmount(); - switch (e.getClick()) { - case NUMBER_KEY -> { - if (e.getWhoClicked().getInventory().getItem(e.getHotbarButton()) != null) { - recipeAmount = 0; - } - } - case DROP, CONTROL_DROP -> { - ItemStack cursor = e.getCursor(); - if (!(cursor == null || cursor.getType().isAir())) { - recipeAmount = 0; - } - } - case SHIFT_RIGHT, SHIFT_LEFT -> { - if (recipeAmount == 0) { - break; - } - int maxCraftable = getMaxCraftAmount(e.getInventory()); - int capacity = fits(test, e.getView().getBottomInventory()); - if (capacity < maxCraftable) { - maxCraftable = ((capacity + recipeAmount - 1) / recipeAmount) * recipeAmount; - } - recipeAmount = maxCraftable; - } - default -> { - } - } - return recipeAmount; - } - - private int fits(ItemStack stack, Inventory inv) { - ItemStack[] contents = inv.getContents(); - int result = 0; - - for (ItemStack is : contents) { - if (is == null) { - result += stack.getMaxStackSize(); - } else if (is.isSimilar(stack)) { - result += Math.max(stack.getMaxStackSize() - is.getAmount(), 0); - } - } - - return result; - } - - private int getMaxCraftAmount(CraftingInventory inv) { - if (inv.getResult() == null) { - return 0; - } - - int resultCount = inv.getResult().getAmount(); - int materialCount = Integer.MAX_VALUE; - - for (ItemStack is : inv.getMatrix()) { - if (is != null && is.getAmount() < materialCount) { - materialCount = is.getAmount(); - } - } - - return resultCount * materialCount; - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Furnace Base XP for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double furnaceBaseXP = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Furnace Value XPMultiplier for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double furnaceValueXPMultiplier = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Furnace XPRadius for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - int furnaceXPRadius = 32; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 3000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Furnace XPDuration for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long furnaceXPDuration = 10000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Crafting Value XPMultiplier for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double craftingValueXPMultiplier = 2.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Base Crafting XP for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double baseCraftingXP = 3.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Craft1k Reward for the Crafting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeCraft1kReward = 1200; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillDiscovery.java b/src/main/java/com/volmit/adapt/content/skill/SkillDiscovery.java deleted file mode 100644 index 33fffad85..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillDiscovery.java +++ /dev/null @@ -1,431 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.Discovery; -import com.volmit.adapt.content.adaptation.discovery.DiscoveryArmor; -import com.volmit.adapt.content.adaptation.discovery.DiscoveryArchaeologist; -import com.volmit.adapt.content.adaptation.discovery.DiscoveryBetterMending; -import com.volmit.adapt.content.adaptation.discovery.DiscoveryCartographerPulse; -import com.volmit.adapt.content.adaptation.discovery.DiscoveryUnity; -import com.volmit.adapt.content.adaptation.discovery.DiscoveryVillagerAtt; -import com.volmit.adapt.content.adaptation.discovery.DiscoveryXpResist; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.Particles; -import lombok.NoArgsConstructor; -import org.bukkit.*; -import org.bukkit.block.Biome; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityPickupItemEvent; -import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.event.player.*; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.Recipe; -import org.bukkit.potion.PotionEffect; - -import java.util.Map; - -public class SkillDiscovery extends SimpleSkill { - public SkillDiscovery() { - super("discovery", Localizer.dLocalize("skill.discovery.icon")); - registerConfiguration(Config.class); - setColor(C.AQUA); - setDescription(Localizer.dLocalize("skill.discovery.description")); - setDisplayName(Localizer.dLocalize("skill.discovery.name")); - setInterval(500); - setIcon(Material.FILLED_MAP); - registerAdaptation(new DiscoveryUnity()); - registerAdaptation(new DiscoveryArmor()); - registerAdaptation(new DiscoveryXpResist()); - registerAdaptation(new DiscoveryVillagerAtt()); - registerAdaptation(new DiscoveryBetterMending()); - registerAdaptation(new DiscoveryArchaeologist()); - registerAdaptation(new DiscoveryCartographerPulse()); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ITEM_FRAME).key("challenge_discover_items_50") - .title(Localizer.dLocalize("advancement.challenge_discover_items_50.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_items_50.description")) - .model(CustomModel.get(Material.ITEM_FRAME, "advancement", "discovery", "challenge_discover_items_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CHEST) - .key("challenge_discover_items_250") - .title(Localizer.dLocalize("advancement.challenge_discover_items_250.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_items_250.description")) - .model(CustomModel.get(Material.CHEST, "advancement", "discovery", "challenge_discover_items_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discover_items_50", "discovery.items", 50, 500); - registerMilestone("challenge_discover_items_250", "discovery.items", 250, 2500); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GRASS_BLOCK).key("challenge_discover_blocks_50") - .title(Localizer.dLocalize("advancement.challenge_discover_blocks_50.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_blocks_50.description")) - .model(CustomModel.get(Material.GRASS_BLOCK, "advancement", "discovery", "challenge_discover_blocks_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.STONE_BRICKS) - .key("challenge_discover_blocks_250") - .title(Localizer.dLocalize("advancement.challenge_discover_blocks_250.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_blocks_250.description")) - .model(CustomModel.get(Material.STONE_BRICKS, "advancement", "discovery", "challenge_discover_blocks_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discover_blocks_50", "discovery.blocks", 50, 500); - registerMilestone("challenge_discover_blocks_250", "discovery.blocks", 250, 2500); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.EGG).key("challenge_discover_mobs_25") - .title(Localizer.dLocalize("advancement.challenge_discover_mobs_25.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_mobs_25.description")) - .model(CustomModel.get(Material.EGG, "advancement", "discovery", "challenge_discover_mobs_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SPAWNER) - .key("challenge_discover_mobs_75") - .title(Localizer.dLocalize("advancement.challenge_discover_mobs_75.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_mobs_75.description")) - .model(CustomModel.get(Material.SPAWNER, "advancement", "discovery", "challenge_discover_mobs_75")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discover_mobs_25", "discovery.mobs", 25, 500); - registerMilestone("challenge_discover_mobs_75", "discovery.mobs", 75, 2500); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.MAP).key("challenge_discover_biomes_10") - .title(Localizer.dLocalize("advancement.challenge_discover_biomes_10.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_biomes_10.description")) - .model(CustomModel.get(Material.MAP, "advancement", "discovery", "challenge_discover_biomes_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.FILLED_MAP) - .key("challenge_discover_biomes_40") - .title(Localizer.dLocalize("advancement.challenge_discover_biomes_40.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_biomes_40.description")) - .model(CustomModel.get(Material.FILLED_MAP, "advancement", "discovery", "challenge_discover_biomes_40")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discover_biomes_10", "discovery.biomes", 10, 500); - registerMilestone("challenge_discover_biomes_40", "discovery.biomes", 40, 2500); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.APPLE).key("challenge_discover_foods_10") - .title(Localizer.dLocalize("advancement.challenge_discover_foods_10.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_foods_10.description")) - .model(CustomModel.get(Material.APPLE, "advancement", "discovery", "challenge_discover_foods_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GOLDEN_APPLE) - .key("challenge_discover_foods_30") - .title(Localizer.dLocalize("advancement.challenge_discover_foods_30.title")) - .description(Localizer.dLocalize("advancement.challenge_discover_foods_30.description")) - .model(CustomModel.get(Material.GOLDEN_APPLE, "advancement", "discovery", "challenge_discover_foods_30")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_discover_foods_10", "discovery.foods", 10, 500); - registerMilestone("challenge_discover_foods_30", "discovery.foods", 30, 2500); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerChangedWorldEvent e) { - shouldReturnForPlayer(e.getPlayer(), () -> scheduleSeeWorld(e.getPlayer())); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerInteractAtEntityEvent e) { - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(e.getPlayer(), e, () -> seeEntity(e.getPlayer(), e.getRightClicked())); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityPickupItemEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getEntity() instanceof Player p) { - shouldReturnForPlayer(p, e, () -> seeItem(p, e.getItem().getItemStack())); - } - - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(CraftItemEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getWhoClicked() instanceof Player p)) return; - shouldReturnForPlayer(p, e, () -> { - try { - NamespacedKey key = (NamespacedKey) Recipe.class.getDeclaredMethod("getKey()").invoke(e.getRecipe()); - if (key != null) { - seeCraftedRecipe(p, key.toString()); - } - } catch (Throwable ignored) { - Adapt.verbose("No recipe key found for " + e.getRecipe().getResult().getType().name()); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerItemConsumeEvent e) { - shouldReturnForPlayer(e.getPlayer(), e, () -> { - seeItem(e.getPlayer(), e.getItem()); - seeFood(e.getPlayer(), e.getItem().getType()); - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerInteractEvent e) { - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (e.getClickedBlock() != null) { - seeBlock(e.getPlayer(), e.getClickedBlock().getBlockData(), e.getClickedBlock().getLocation()); - } - }); - - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerExpChangeEvent e) { - shouldReturnForPlayer(e.getPlayer(), () -> { - if (e.getAmount() > 0 && getLevel(e.getPlayer()) > 0) { - xp(e.getPlayer(), e.getAmount()); - } - }); - } - - private void scheduleSeeWorld(Player p) { - try { - J.s(() -> seeWorld(p, p.getWorld()), 15); - } catch (Exception e) { - Adapt.error("Failed to discover world " + p.getWorld().getName()); - } - } - - public void seeBlock(Player p, BlockData bd, Location l) { - Discovery d = getPlayer(p).getData().getSeenBlocks(); - if (d.isNewDiscovery(bd.getAsString())) { - xp(p, getConfig().discoverBlockBaseXP + (getValue(bd) * getConfig().discoverBlockValueXPMultiplier)); - getPlayer(p).getData().addStat("discovery.blocks", 1); - if (areParticlesEnabled()) { - p.spawnParticle(Particles.TOTEM, l.clone().add(0.5, 0.5, 0.5), 9, 0, 0, 0, 0.3); - } - } - - seeItem(p, bd.getMaterial()); - } - - public void seeItem(Player p, Material bd) { - Discovery d = getPlayer(p).getData().getSeenItems(); - if (d.isNewDiscovery(bd)) { - xp(p, getConfig().discoverItemBaseXP + (getValue(bd) * getConfig().discoverItemValueXPMultiplier)); - getPlayer(p).getData().addStat("discovery.items", 1); - } - } - - public void seeItem(Player p, ItemStack bd) { - seeItem(p, bd.getType()); - Map m = bd.getEnchantments(); - - for (Enchantment i : m.keySet()) { - seeEnchant(p, i, m.get(i)); - } - } - - public void seeCraftedRecipe(Player p, String key) { - Discovery d = getPlayer(p).getData().getSeenRecipes(); - if (d.isNewDiscovery(key)) { - xp(p, getConfig().discoverRecipeBaseXP); - } - } - - public void seeFood(Player p, Material bd) { - Discovery d = getPlayer(p).getData().getSeenFoods(); - if (d.isNewDiscovery(bd)) { - xp(p, getConfig().discoverFoodTypeXP); - getPlayer(p).getData().addStat("discovery.foods", 1); - } - } - - public void seeEntity(Player p, Entity bd) { - Discovery d = getPlayer(p).getData().getSeenMobs(); - if (d.isNewDiscovery(bd.getType())) { - xp(p, getConfig().discoverEntityTypeXP); - getPlayer(p).getData().addStat("discovery.mobs", 1); - } - - if (bd instanceof Player) { - seePlayer(p, (Player) bd); - } - - if (bd instanceof LivingEntity) { - for (PotionEffect i : ((LivingEntity) bd).getActivePotionEffects()) { - seePotionEffect(p, i); - } - } - } - - public void seePlayer(Player p, Player bd) { - Discovery d = getPlayer(p).getData().getSeenPeople(); - if (d.isNewDiscovery(bd.getUniqueId().toString())) { - xp(p, getConfig().discoverPlayerXP); - } - } - - public void seeEnchant(Player p, Enchantment bd, int level) { - Discovery d = getPlayer(p).getData().getSeenEnchants(); - if (d.isNewDiscovery(bd.getName() + " " + Form.toRoman(level))) { - xp(p, getConfig().discoverEnchantBaseXP + Math.min(getConfig().discoverEnchantMaxXP, level * getConfig().discoverEnchantLevelXPMultiplier)); - } - } - - public void seeWorld(Player p, World world) { - Discovery d = getPlayer(p).getData().getSeenWorlds(); - if (d.isNewDiscovery(world.getName() + "-" + world.getSeed())) { - xp(p, getConfig().discoverWorldXP); - } - - seeEnvironment(p, world.getEnvironment()); - } - - public void seeEnvironment(Player p, World.Environment world) { - Discovery d = getPlayer(p).getData().getSeenEnvironments(); - if (d.isNewDiscovery(world)) { - xp(p, getConfig().discoverEnvironmentXP); - } - } - - public void seePotionEffect(Player p, PotionEffect e) { - Discovery d = getPlayer(p).getData().getSeenPotionEffects(); - if (d.isNewDiscovery(e.getType().getName() + " " + Form.toRoman(e.getAmplifier()).trim())) { - xp(p, getConfig().discoverPotionXP); - } - } - - public void seeBiome(Player p, Biome e) { - Discovery d = getPlayer(p).getData().getSeenBiomes(); - if (d.isNewDiscovery(e.getKey().toString())) { - xp(p, getConfig().discoverBiomeXP); - getPlayer(p).getData().addStat("discovery.biomes", 1); - } - } - - @Override - public void onTick() { - if (!this.isEnabled()) return; - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - if (shouldReturnForPlayer(i)) continue; - checkStatTrackers(adaptPlayer); - seeTargetBlock(i); - } - } - - private void seeTargetBlock(Player i) { - try { - Block b = i.getTargetBlockExact(5, FluidCollisionMode.NEVER); - if (b != null) { - seeBlock(i, b.getBlockData(), b.getLocation()); - seeBiome(i, b.getBiome()); - } - } catch (Throwable ignored) { - Adapt.verbose("Failed to get target block for " + i.getName()); - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Discovery skill.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Biome XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverBiomeXP = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Potion XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverPotionXP = 36; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Entity Type XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverEntityTypeXP = 125; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Food Type XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverFoodTypeXP = 75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Player XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverPlayerXP = 125; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Environment XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverEnvironmentXP = 750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover World XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverWorldXP = 750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Enchant Max XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverEnchantMaxXP = 250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Enchant Level XPMultiplier for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverEnchantLevelXPMultiplier = 52; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Enchant Base XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverEnchantBaseXP = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Item Base XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverItemBaseXP = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Recipe Base XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverRecipeBaseXP = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Item Value XPMultiplier for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverItemValueXPMultiplier = 1; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Block Base XP for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverBlockBaseXP = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Discover Block Value XPMultiplier for the Discovery skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double discoverBlockValueXPMultiplier = 0.333; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillEnchanting.java b/src/main/java/com/volmit/adapt/content/skill/SkillEnchanting.java deleted file mode 100644 index a6eed3e32..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillEnchanting.java +++ /dev/null @@ -1,251 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.enchanting.EnchantingLapisReturn; -import com.volmit.adapt.content.adaptation.enchanting.EnchantingAnvilSavant; -import com.volmit.adapt.content.adaptation.enchanting.EnchantingBookshelfAttunement; -import com.volmit.adapt.content.adaptation.enchanting.EnchantingGrindstoneRecovery; -import com.volmit.adapt.content.adaptation.enchanting.EnchantingOfferReroll; -import com.volmit.adapt.content.adaptation.enchanting.EnchantingQuickEnchant; -import com.volmit.adapt.content.adaptation.enchanting.EnchantingXPReturn; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.enchantment.EnchantItemEvent; - -import java.util.HashMap; -import java.util.Map; - -public class SkillEnchanting extends SimpleSkill { - private final Map cooldowns; - - public SkillEnchanting() { - super("enchanting", Localizer.dLocalize("skill.enchanting.icon")); - registerConfiguration(Config.class); - setColor(C.LIGHT_PURPLE); - setDescription(Localizer.dLocalize("skill.enchanting.description")); - setDisplayName(Localizer.dLocalize("skill.enchanting.name")); - setInterval(3909); - setIcon(Material.KNOWLEDGE_BOOK); - cooldowns = new HashMap<>(); - registerAdaptation(new EnchantingQuickEnchant()); - registerAdaptation(new EnchantingLapisReturn()); - registerAdaptation(new EnchantingXPReturn()); // - registerAdaptation(new EnchantingAnvilSavant()); - registerAdaptation(new EnchantingOfferReroll()); - registerAdaptation(new EnchantingBookshelfAttunement()); - registerAdaptation(new EnchantingGrindstoneRecovery()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CRAFTING_TABLE).key("challenge_enchant_1k") - .title(Localizer.dLocalize("advancement.challenge_enchant_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_1k.description")) - .model(CustomModel.get(Material.CRAFTING_TABLE, "advancement", "enchanting", "challenge_enchant_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.KNOWLEDGE_BOOK) - .key("challenge_enchant_5k") - .title(Localizer.dLocalize("advancement.challenge_enchant_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_5k.description")) - .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "enchanting", "challenge_enchant_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.KNOWLEDGE_BOOK) - .key("challenge_enchant_50k") - .title(Localizer.dLocalize("advancement.challenge_enchant_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_50k.description")) - .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "enchanting", "challenge_enchant_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.KNOWLEDGE_BOOK) - .key("challenge_enchant_500k") - .title(Localizer.dLocalize("advancement.challenge_enchant_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_500k.description")) - .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "enchanting", "challenge_enchant_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.KNOWLEDGE_BOOK) - .key("challenge_enchant_5m") - .title(Localizer.dLocalize("advancement.challenge_enchant_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_5m.description")) - .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "enchanting", "challenge_enchant_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - registerMilestone("challenge_enchant_1k", "enchanted.items", 1000, getConfig().challengeEnchantReward); - registerMilestone("challenge_enchant_5k", "enchanted.items", 5000, getConfig().challengeEnchantReward); - registerMilestone("challenge_enchant_50k", "enchanted.items", 50000, getConfig().challengeEnchantReward); - registerMilestone("challenge_enchant_500k", "enchanted.items", 500000, getConfig().challengeEnchantReward); - registerMilestone("challenge_enchant_5m", "enchanted.items", 5000000, getConfig().challengeEnchantReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.EXPERIENCE_BOTTLE) - .key("challenge_enchant_power_100") - .title(Localizer.dLocalize("advancement.challenge_enchant_power_100.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_power_100.description")) - .model(CustomModel.get(Material.EXPERIENCE_BOTTLE, "advancement", "enchanting", "challenge_enchant_power_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENCHANTING_TABLE) - .key("challenge_enchant_power_1k") - .title(Localizer.dLocalize("advancement.challenge_enchant_power_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_power_1k.description")) - .model(CustomModel.get(Material.ENCHANTING_TABLE, "advancement", "enchanting", "challenge_enchant_power_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchant_power_100", "enchanted.power", 100, getConfig().challengeEnchantReward); - registerMilestone("challenge_enchant_power_1k", "enchanted.power", 1000, getConfig().challengeEnchantReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LAPIS_LAZULI) - .key("challenge_enchant_levels_1k") - .title(Localizer.dLocalize("advancement.challenge_enchant_levels_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_levels_1k.description")) - .model(CustomModel.get(Material.LAPIS_LAZULI, "advancement", "enchanting", "challenge_enchant_levels_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.LAPIS_BLOCK) - .key("challenge_enchant_levels_10k") - .title(Localizer.dLocalize("advancement.challenge_enchant_levels_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_levels_10k.description")) - .model(CustomModel.get(Material.LAPIS_BLOCK, "advancement", "enchanting", "challenge_enchant_levels_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchant_levels_1k", "enchanted.levels.spent", 1000, getConfig().challengeEnchantReward); - registerMilestone("challenge_enchant_levels_10k", "enchanted.levels.spent", 10000, getConfig().challengeEnchantReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BOOKSHELF) - .key("challenge_enchant_high_25") - .title(Localizer.dLocalize("advancement.challenge_enchant_high_25.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_high_25.description")) - .model(CustomModel.get(Material.BOOKSHELF, "advancement", "enchanting", "challenge_enchant_high_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENCHANTED_BOOK) - .key("challenge_enchant_high_250") - .title(Localizer.dLocalize("advancement.challenge_enchant_high_250.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_high_250.description")) - .model(CustomModel.get(Material.ENCHANTED_BOOK, "advancement", "enchanting", "challenge_enchant_high_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchant_high_25", "enchanting.high.level", 25, getConfig().challengeEnchantReward); - registerMilestone("challenge_enchant_high_250", "enchanting.high.level", 250, getConfig().challengeEnchantReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.EXPERIENCE_BOTTLE) - .key("challenge_enchant_total_500") - .title(Localizer.dLocalize("advancement.challenge_enchant_total_500.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_total_500.description")) - .model(CustomModel.get(Material.EXPERIENCE_BOTTLE, "advancement", "enchanting", "challenge_enchant_total_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DRAGON_BREATH) - .key("challenge_enchant_total_5k") - .title(Localizer.dLocalize("advancement.challenge_enchant_total_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_enchant_total_5k.description")) - .model(CustomModel.get(Material.DRAGON_BREATH, "advancement", "enchanting", "challenge_enchant_total_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_enchant_total_500", "enchanting.total.levels", 500, getConfig().challengeEnchantReward); - registerMilestone("challenge_enchant_total_5k", "enchanting.total.levels", 5000, getConfig().challengeEnchantReward * 2); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EnchantItemEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getEnchanter(); - shouldReturnForPlayer(p, e, () -> { - handleEnchantItemEvent(p, e); - }); - - } - - private void handleEnchantItemEvent(Player p, EnchantItemEvent e) { - AdaptPlayer adaptPlayer = getPlayer(p); - adaptPlayer.getData().addStat("enchanted.items", 1); - adaptPlayer.getData().addStat("enchanted.power", e.getEnchantsToAdd().values().stream().mapToInt(i -> i).sum()); - adaptPlayer.getData().addStat("enchanted.levels.spent", e.getExpLevelCost()); - if (e.getExpLevelCost() >= 30) { - adaptPlayer.getData().addStat("enchanting.high.level", 1); - } - adaptPlayer.getData().addStat("enchanting.total.levels", e.getExpLevelCost()); - - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - xp(p, getConfig().enchantPowerXPMultiplier * e.getEnchantsToAdd().values().stream().mapToInt((i) -> i).sum()); - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Enchant Power XPMultiplier for the Enchanting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double enchantPowerXPMultiplier = 45; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Enchanting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 5250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Enchant Reward for the Enchanting skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeEnchantReward = 2500; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillExcavation.java b/src/main/java/com/volmit/adapt/content/skill/SkillExcavation.java deleted file mode 100644 index 3b1d42ccd..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillExcavation.java +++ /dev/null @@ -1,288 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.excavation.ExcavationDropToInventory; -import com.volmit.adapt.content.adaptation.excavation.ExcavationHaste; -import com.volmit.adapt.content.adaptation.excavation.ExcavationOmniTool; -import com.volmit.adapt.content.adaptation.excavation.ExcavationSpelunker; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -public class SkillExcavation extends SimpleSkill { - private final Map cooldowns; - - public SkillExcavation() { - super("excavation", Localizer.dLocalize("skill.excavation.icon")); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("skill.excavation.description")); - setDisplayName(Localizer.dLocalize("skill.excavation.name")); - setColor(C.YELLOW); - setInterval(5953); - setIcon(Material.DIAMOND_SHOVEL); - cooldowns = new HashMap<>(); - registerAdaptation(new ExcavationHaste()); - registerAdaptation(new ExcavationSpelunker()); - registerAdaptation(new ExcavationOmniTool()); - registerAdaptation(new ExcavationDropToInventory()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WOODEN_SHOVEL).key("challenge_excavate_1k") - .title(Localizer.dLocalize("advancement.challenge_excavate_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavate_1k.description")) - .model(CustomModel.get(Material.WOODEN_SHOVEL, "advancement", "excavation", "challenge_excavate_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.KNOWLEDGE_BOOK) - .key("challenge_excavate_5k") - .title(Localizer.dLocalize("advancement.challenge_excavate_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavate_5k.description")) - .model(CustomModel.get(Material.KNOWLEDGE_BOOK, "advancement", "excavation", "challenge_excavate_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.STONE_SHOVEL) - .key("challenge_excavate_50k") - .title(Localizer.dLocalize("advancement.challenge_excavate_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavate_50k.description")) - .model(CustomModel.get(Material.STONE_SHOVEL, "advancement", "excavation", "challenge_excavate_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.IRON_SHOVEL) - .key("challenge_excavate_500k") - .title(Localizer.dLocalize("advancement.challenge_excavate_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_excavate_500k.description")) - .model(CustomModel.get(Material.IRON_SHOVEL, "advancement", "excavation", "challenge_excavate_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SHOVEL) - .key("challenge_excavate_5m") - .title(Localizer.dLocalize("advancement.challenge_excavate_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_excavate_5m.description")) - .model(CustomModel.get(Material.DIAMOND_SHOVEL, "advancement", "excavation", "challenge_excavate_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - registerMilestone("challenge_excavate_1k", "excavation.blocks.broken", 1000, getConfig().challengeExcavationReward); - registerMilestone("challenge_excavate_5k", "excavation.blocks.broken", 5000, getConfig().challengeExcavationReward); - registerMilestone("challenge_excavate_50k", "excavation.blocks.broken", 50000, getConfig().challengeExcavationReward); - registerMilestone("challenge_enchant_500k", "excavation.blocks.broken", 500000, getConfig().challengeExcavationReward); - registerMilestone("challenge_excavate_5m", "excavation.blocks.broken", 5000000, getConfig().challengeExcavationReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WOODEN_SHOVEL).key("challenge_dig_swing_500") - .title(Localizer.dLocalize("advancement.challenge_dig_swing_500.title")) - .description(Localizer.dLocalize("advancement.challenge_dig_swing_500.description")) - .model(CustomModel.get(Material.WOODEN_SHOVEL, "advancement", "excavation", "challenge_dig_swing_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_SHOVEL) - .key("challenge_dig_swing_5k") - .title(Localizer.dLocalize("advancement.challenge_dig_swing_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_dig_swing_5k.description")) - .model(CustomModel.get(Material.IRON_SHOVEL, "advancement", "excavation", "challenge_dig_swing_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_dig_swing_500", "excavation.swings", 500, getConfig().challengeExcavationReward); - registerMilestone("challenge_dig_swing_5k", "excavation.swings", 5000, getConfig().challengeExcavationReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_SHOVEL).key("challenge_dig_damage_1k") - .title(Localizer.dLocalize("advancement.challenge_dig_damage_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_dig_damage_1k.description")) - .model(CustomModel.get(Material.GOLDEN_SHOVEL, "advancement", "excavation", "challenge_dig_damage_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SHOVEL) - .key("challenge_dig_damage_10k") - .title(Localizer.dLocalize("advancement.challenge_dig_damage_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_dig_damage_10k.description")) - .model(CustomModel.get(Material.DIAMOND_SHOVEL, "advancement", "excavation", "challenge_dig_damage_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_dig_damage_1k", "excavation.damage", 1000, getConfig().challengeExcavationReward); - registerMilestone("challenge_dig_damage_10k", "excavation.damage", 10000, getConfig().challengeExcavationReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CLAY_BALL).key("challenge_dig_value_5k") - .title(Localizer.dLocalize("advancement.challenge_dig_value_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_dig_value_5k.description")) - .model(CustomModel.get(Material.CLAY_BALL, "advancement", "excavation", "challenge_dig_value_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BRICK) - .key("challenge_dig_value_50k") - .title(Localizer.dLocalize("advancement.challenge_dig_value_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_dig_value_50k.description")) - .model(CustomModel.get(Material.BRICK, "advancement", "excavation", "challenge_dig_value_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_dig_value_5k", "excavation.blocks.value", 5000, getConfig().challengeExcavationReward); - registerMilestone("challenge_dig_value_50k", "excavation.blocks.value", 50000, getConfig().challengeExcavationReward * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GRAVEL).key("challenge_dig_gravel_500") - .title(Localizer.dLocalize("advancement.challenge_dig_gravel_500.title")) - .description(Localizer.dLocalize("advancement.challenge_dig_gravel_500.description")) - .model(CustomModel.get(Material.GRAVEL, "advancement", "excavation", "challenge_dig_gravel_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.FLINT) - .key("challenge_dig_gravel_5k") - .title(Localizer.dLocalize("advancement.challenge_dig_gravel_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_dig_gravel_5k.description")) - .model(CustomModel.get(Material.FLINT, "advancement", "excavation", "challenge_dig_gravel_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_dig_gravel_500", "excavation.gravel", 500, getConfig().challengeExcavationReward); - registerMilestone("challenge_dig_gravel_5k", "excavation.gravel", 5000, getConfig().challengeExcavationReward * 2); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p && checkValidEntity(e.getEntity().getType())) { - if (!getConfig().getXpForAttackingWithTools) { - return; - } - shouldReturnForPlayer(p, e, () -> handleEntityDamageByPlayer(p, e)); - } - } - - private void handleEntityDamageByPlayer(Player p, EntityDamageByEntityEvent e) { - AdaptPlayer a = getPlayer(p); - ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); - if (isShovel(hand)) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - getPlayer(p).getData().addStat("excavation.swings", 1); - getPlayer(p).getData().addStat("excavation.damage", e.getDamage()); - xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().axeDamageXPMultiplier * e.getDamage()); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(p, e, () -> { - if (isShovel(p.getInventory().getItemInMainHand())) { - handleBlockBreakWithShovel(p, e); - } - }); - - } - - private void handleBlockBreakWithShovel(Player p, BlockBreakEvent e) { - getPlayer(p).getData().addStat("excavation.blocks.broken", 1); - getPlayer(p).getData().addStat("excavation.blocks.value", getValue(e.getBlock().getBlockData())); - Material blockType = e.getBlock().getType(); - if (blockType == Material.GRAVEL || blockType == Material.SAND || blockType == Material.RED_SAND - || blockType == Material.CLAY || blockType == Material.SOUL_SAND || blockType == Material.SOUL_SOIL) { - getPlayer(p).getData().addStat("excavation.gravel", 1); - } - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - double v = getValue(e.getBlock().getType()); - xp(p, e.getBlock().getLocation().clone().add(0.5, 0.5, 0.5), blockXP(e.getBlock(), v)); - } - - public double getValue(Material type) { - double value = super.getValue(type) * getConfig().valueXPMultiplier; - value += Math.min(getConfig().maxHardnessBonus, type.getHardness()); - value += Math.min(getConfig().maxBlastResistanceBonus, type.getBlastResistance()); - return value; - } - - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Get Xp For Attacking With Tools for the Excavation skill.", impact = "True enables this behavior and false disables it.") - boolean getXpForAttackingWithTools = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Hardness Bonus for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxHardnessBonus = 9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Blast Resistance Bonus for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxBlastResistanceBonus = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Excavation Reward for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeExcavationReward = 1200; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Value XPMultiplier for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double valueXPMultiplier = 0.6; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Axe Damage XPMultiplier for the Excavation skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double axeDamageXPMultiplier = 4.0; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillHerbalism.java b/src/main/java/com/volmit/adapt/content/skill/SkillHerbalism.java deleted file mode 100644 index 0d918ff00..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillHerbalism.java +++ /dev/null @@ -1,378 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.herbalism.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.data.Ageable; -import org.bukkit.block.data.Levelled; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.player.*; -import org.bukkit.inventory.meta.PotionMeta; - -import java.util.HashMap; -import java.util.Map; - -public class SkillHerbalism extends SimpleSkill { - private final Map cooldown = new HashMap<>(); - - public SkillHerbalism() { - super("herbalism", Localizer.dLocalize("skill.herbalism.icon")); - registerConfiguration(Config.class); - setColor(C.GREEN); - setInterval(3990); - setDescription(Localizer.dLocalize("skill.herbalism.description")); - setDisplayName(Localizer.dLocalize("skill.herbalism.name")); - setIcon(Material.WHEAT); - registerAdaptation(new HerbalismGrowthAura()); - registerAdaptation(new HerbalismReplant()); - registerAdaptation(new HerbalismHungryShield()); - registerAdaptation(new HerbalismHungryHippo()); - registerAdaptation(new HerbalismDropToInventory()); - registerAdaptation(new HerbalismLuck()); - registerAdaptation(new HerbalismMyconid()); - registerAdaptation(new HerbalismTerralid()); - registerAdaptation(new HerbalismCraftableMushroomBlocks()); - registerAdaptation(new HerbalismCraftableCobweb()); - registerAdaptation(new HerbalismSeedSower()); - registerAdaptation(new HerbalismCompostCascade()); - registerAdaptation(new HerbalismRootedFooting()); - registerAdaptation(new HerbalismBeeShepherd()); - registerAdaptation(new HerbalismSporeBloom()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.COOKED_BEEF) - .key("challenge_eat_100") - .title(Localizer.dLocalize("advancement.challenge_eat_100.title")) - .description(Localizer.dLocalize("advancement.challenge_eat_100.description")) - .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "challenge_eat_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.COOKED_BEEF) - .key("challenge_eat_1000") - .title(Localizer.dLocalize("advancement.challenge_eat_1000.title")) - .description(Localizer.dLocalize("advancement.challenge_eat_1000.description")) - .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "challenge_eat_1000")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED).child(AdaptAdvancement.builder() - .icon(Material.COOKED_BEEF) - .key("challenge_eat_10000") - .title(Localizer.dLocalize("advancement.challenge_eat_10000.title")) - .description(Localizer.dLocalize("advancement.challenge_eat_10000.description")) - .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "challenge_eat_10000")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_eat_100", "food.eaten", 100, getConfig().challengeEat100Reward); - registerMilestone("challenge_eat_1000", "food.eaten", 1000, getConfig().challengeEat1kReward); - registerMilestone("challenge_eat_10000", "food.eaten", 10000, getConfig().challengeEat1kReward); - - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.COOKED_BEEF) - .key("challenge_harvest_100") - .title(Localizer.dLocalize("advancement.challenge_harvest_100.title")) - .description(Localizer.dLocalize("advancement.challenge_harvest_100.description")) - .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "harvest_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.COOKED_BEEF) - .key("challenge_harvest_1000") - .title(Localizer.dLocalize("advancement.challenge_harvest_1000.title")) - .description(Localizer.dLocalize("advancement.challenge_harvest_1000.description")) - .model(CustomModel.get(Material.COOKED_BEEF, "advancement", "herbalism", "harvest_1000")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_harvest_100", "harvest.blocks", 100, getConfig().challengeHarvest100Reward); - registerMilestone("challenge_harvest_1000", "harvest.blocks", 1000, getConfig().challengeHarvest1kReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WHEAT_SEEDS) - .key("challenge_plant_100") - .title(Localizer.dLocalize("advancement.challenge_plant_100.title")) - .description(Localizer.dLocalize("advancement.challenge_plant_100.description")) - .model(CustomModel.get(Material.WHEAT_SEEDS, "advancement", "herbalism", "challenge_plant_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BEETROOT_SEEDS) - .key("challenge_plant_1k") - .title(Localizer.dLocalize("advancement.challenge_plant_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_plant_1k.description")) - .model(CustomModel.get(Material.BEETROOT_SEEDS, "advancement", "herbalism", "challenge_plant_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GOLDEN_CARROT) - .key("challenge_plant_5k") - .title(Localizer.dLocalize("advancement.challenge_plant_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_plant_5k.description")) - .model(CustomModel.get(Material.GOLDEN_CARROT, "advancement", "herbalism", "challenge_plant_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_plant_100", "harvest.planted", 100, getConfig().challengePlant100Reward); - registerMilestone("challenge_plant_1k", "harvest.planted", 1000, getConfig().challengePlant1kReward); - registerMilestone("challenge_plant_5k", "harvest.planted", 5000, getConfig().challengePlant5kReward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.COMPOSTER) - .key("challenge_compost_50") - .title(Localizer.dLocalize("advancement.challenge_compost_50.title")) - .description(Localizer.dLocalize("advancement.challenge_compost_50.description")) - .model(CustomModel.get(Material.COMPOSTER, "advancement", "herbalism", "challenge_compost_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BONE_MEAL) - .key("challenge_compost_500") - .title(Localizer.dLocalize("advancement.challenge_compost_500.title")) - .description(Localizer.dLocalize("advancement.challenge_compost_500.description")) - .model(CustomModel.get(Material.BONE_MEAL, "advancement", "herbalism", "challenge_compost_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_compost_50", "harvest.composted", 50, getConfig().challengeCompost50Reward); - registerMilestone("challenge_compost_500", "harvest.composted", 500, getConfig().challengeCompost500Reward); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SHEARS) - .key("challenge_shear_50") - .title(Localizer.dLocalize("advancement.challenge_shear_50.title")) - .description(Localizer.dLocalize("advancement.challenge_shear_50.description")) - .model(CustomModel.get(Material.SHEARS, "advancement", "herbalism", "challenge_shear_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.WHITE_WOOL) - .key("challenge_shear_250") - .title(Localizer.dLocalize("advancement.challenge_shear_250.title")) - .description(Localizer.dLocalize("advancement.challenge_shear_250.description")) - .model(CustomModel.get(Material.WHITE_WOOL, "advancement", "herbalism", "challenge_shear_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_shear_50", "herbalism.sheared", 50, getConfig().challengeShear50Reward); - registerMilestone("challenge_shear_250", "herbalism.sheared", 250, getConfig().challengeShear250Reward); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - cooldown.remove(p); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerItemConsumeEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (e.getItem().getItemMeta() instanceof PotionMeta o) { - return; - } - - handleHerbCooldown(p, () -> { - xp(p, getConfig().foodConsumeXP); - getPlayer(p).getData().addStat("food.eaten", 1); - }); - - - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerShearEntityEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(e.getPlayer(), e, () -> { - getPlayer(p).getData().addStat("herbalism.sheared", 1); - xp(p, e.getEntity().getLocation(), getConfig().shearXP); - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerHarvestBlockEvent e) { - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(e.getPlayer(), e, () -> handleEvent(e, e.getPlayer(), e.getHarvestedBlock(), "harvest.blocks")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockPlaceEvent e) { - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(e.getPlayer(), e, () -> handleEvent(e, e.getPlayer(), e.getBlock(), "harvest.planted")); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerInteractEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (e.useItemInHand().equals(Event.Result.DENY)) { - return; - } - if (e.getClickedBlock() == null) { - return; - } - if (e.getClickedBlock().getType().equals(Material.COMPOSTER)) { - handleComposterInteraction(e, p); - } - }); - - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - shouldReturnForPlayer(e.getPlayer(), e, () -> handleEvent(e, e.getPlayer(), e.getBlock(), "harvest.blocks")); - } - - private void handleHerbCooldown(Player p, Runnable action) { - if (cooldown.containsKey(p)) { - if (cooldown.get(p) + getConfig().harvestXpCooldown > System.currentTimeMillis()) { - return; - } else { - cooldown.remove(p); - } - } - - cooldown.put(p, System.currentTimeMillis()); - action.run(); - } - - private void handleEvent(Cancellable e, Player p, Block block, String stat) { - handleHerbCooldown(p, () -> { - if (block.getBlockData() instanceof Ageable ageableBlock) { - xp(p, block.getLocation().clone().add(0.5, 0.5, 0.5), getConfig().harvestPerAgeXP * ageableBlock.getAge()); - getPlayer(p).getData().addStat(stat, 1); - } - }); - } - - private void handleComposterInteraction(PlayerInteractEvent e, Player p) { - Block b = e.getClickedBlock(); - assert b != null; - if (!(b.getBlockData() instanceof Levelled oldData)) - return; - int ol = oldData.getLevel(); - J.s(() -> { - if (!(b.getBlockData() instanceof Levelled newData)) - return; - int nl = newData.getLevel(); - if (nl > ol || (ol > 0 && nl == 0)) { - xp(p, e.getClickedBlock().getLocation().clone().add(0.5, 0.5, 0.5), getConfig().composterBaseXP + (nl * getConfig().composterLevelXPMultiplier) + (nl == 0 ? getConfig().composterNonZeroLevelBonus : 5)); - getPlayer(p).getData().addStat("harvest.composted", 1); - } - }); - } - - - @Override - public void onTick() { - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - public static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - public boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Harvest Xp Cooldown for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double harvestXpCooldown = 3500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Food Consume XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double foodConsumeXP = 35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Shear XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double shearXP = 35; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Harvest Per Age XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double harvestPerAgeXP = 5.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Plant Crop Seeds XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double plantCropSeedsXP = 4.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Composter Base XP for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double composterBaseXP = 2.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Composter Level XPMultiplier for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double composterLevelXPMultiplier = 1.25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Composter Non Zero Level Bonus for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double composterNonZeroLevelBonus = 25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Eat100Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengeEat100Reward = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Eat1k Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengeEat1kReward = 6250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Harvest100Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengeHarvest100Reward = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Harvest1k Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengeHarvest1kReward = 6250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Plant100 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengePlant100Reward = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Plant1k Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengePlant1kReward = 6250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Plant5k Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengePlant5kReward = 25000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Compost50 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengeCompost50Reward = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Compost500 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengeCompost500Reward = 6250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Shear50 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengeShear50Reward = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Shear250 Reward for the Herbalism skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double challengeShear250Reward = 6250; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillHunter.java b/src/main/java/com/volmit/adapt/content/skill/SkillHunter.java deleted file mode 100644 index 9d031135e..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillHunter.java +++ /dev/null @@ -1,329 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.hunter.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.Attributes; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.entity.CreatureSpawnEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.PlayerInteractEvent; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -public class SkillHunter extends SimpleSkill { - private final Map cooldowns; - - public SkillHunter() { - super("hunter", Localizer.dLocalize("skill.hunter.icon")); - registerConfiguration(Config.class); - setColor(C.RED); - setDescription(Localizer.dLocalize("skill.hunter.description")); - setDisplayName(Localizer.dLocalize("skill.hunter.name")); - setInterval(4150); - setIcon(Material.BONE); - cooldowns = new HashMap<>(); - registerAdaptation(new HunterAdrenaline()); - registerAdaptation(new HunterRegen()); - registerAdaptation(new HunterInvis()); - registerAdaptation(new HunterJumpBoost()); - registerAdaptation(new HunterLuck()); - registerAdaptation(new HunterSpeed()); - registerAdaptation(new HunterStrength()); - registerAdaptation(new HunterResistance()); - registerAdaptation(new HunterDropToInventory()); - registerAdaptation(new HunterTrophySkinner()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TURTLE_EGG) - .key("horrible_person") - .title(Localizer.dLocalize("advancement.horrible_person.title")) - .description(Localizer.dLocalize("advancement.horrible_person.description")) - .model(CustomModel.get(Material.TURTLE_EGG, "advancement", "hunter", "horrible_person")) - .frame(AdaptAdvancementFrame.GOAL) - .visibility(AdvancementVisibility.HIDDEN) - .build() - ); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TURTLE_EGG) - .key("challenge_turtle_egg_smasher") - .title(Localizer.dLocalize("advancement.challenge_turtle_egg_smasher.title")) - .description(Localizer.dLocalize("advancement.challenge_turtle_egg_smasher.description")) - .model(CustomModel.get(Material.TURTLE_EGG, "advancement", "hunter", "challenge_turtle_egg_smasher")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TURTLE_EGG) - .key("challenge_turtle_egg_annihilator") - .title(Localizer.dLocalize("advancement.challenge_turtle_egg_annihilator.title")) - .description(Localizer.dLocalize("advancement.challenge_turtle_egg_annihilator.description")) - .model(CustomModel.get(Material.TURTLE_EGG, "advancement", "hunter", "challenge_turtle_egg_annihilator")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_novice_hunter") - .title(Localizer.dLocalize("advancement.challenge_novice_hunter.title")) - .description(Localizer.dLocalize("advancement.challenge_novice_hunter.description")) - .model(CustomModel.get(Material.BONE, "advancement", "hunter", "challenge_novice_hunter")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_intermediate_hunter") - .title(Localizer.dLocalize("advancement.challenge_intermediate_hunter.title")) - .description(Localizer.dLocalize("advancement.challenge_intermediate_hunter.description")) - .model(CustomModel.get(Material.IRON_SWORD, "advancement", "hunter", "challenge_intermediate_hunter")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_advanced_hunter") - .title(Localizer.dLocalize("advancement.challenge_advanced_hunter.title")) - .description(Localizer.dLocalize("advancement.challenge_advanced_hunter.description")) - .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "hunter", "challenge_advanced_hunter")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CREEPER_HEAD) - .key("challenge_creeper_conqueror") - .title(Localizer.dLocalize("advancement.challenge_creeper_conqueror.title")) - .description(Localizer.dLocalize("advancement.challenge_creeper_conqueror.description")) - .model(CustomModel.get(Material.CREEPER_HEAD, "advancement", "hunter", "challenge_creeper_conqueror")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TNT) - .key("challenge_creeper_annihilator") - .title(Localizer.dLocalize("advancement.challenge_creeper_annihilator.title")) - .description(Localizer.dLocalize("advancement.challenge_creeper_annihilator.description")) - .model(CustomModel.get(Material.TNT, "advancement", "hunter", "challenge_creeper_annihilator")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_kills_500") - .title(Localizer.dLocalize("advancement.challenge_kills_500.title")) - .description(Localizer.dLocalize("advancement.challenge_kills_500.description")) - .model(CustomModel.get(Material.BONE, "advancement", "hunter", "challenge_kills_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.WITHER_SKELETON_SKULL) - .key("challenge_kills_5k") - .title(Localizer.dLocalize("advancement.challenge_kills_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_kills_5k.description")) - .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "hunter", "challenge_kills_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DRAGON_HEAD) - .key("challenge_boss_1") - .title(Localizer.dLocalize("advancement.challenge_boss_1.title")) - .description(Localizer.dLocalize("advancement.challenge_boss_1.description")) - .model(CustomModel.get(Material.DRAGON_HEAD, "advancement", "hunter", "challenge_boss_1")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHER_STAR) - .key("challenge_boss_10") - .title(Localizer.dLocalize("advancement.challenge_boss_10.title")) - .description(Localizer.dLocalize("advancement.challenge_boss_10.description")) - .model(CustomModel.get(Material.NETHER_STAR, "advancement", "hunter", "challenge_boss_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - - registerMilestone("horrible_person", "killed.turtleeggs", 1, getConfig().turtleEggKillXP); - registerMilestone("challenge_turtle_egg_smasher", "killed.turtleeggs", 100, getConfig().turtleEggKillXP*10); - registerMilestone("challenge_turtle_egg_annihilator", "killed.turtleeggs", 1000, getConfig().turtleEggKillXP*10); - registerMilestone("challenge_novice_hunter", "killed.monsters", 100, getConfig().turtleEggKillXP*3); - registerMilestone("challenge_intermediate_hunter", "killed.monsters", 1000, getConfig().turtleEggKillXP*3); - registerMilestone("challenge_advanced_hunter", "killed.monsters", 10000, getConfig().turtleEggKillXP*3); - registerMilestone("challenge_creeper_conqueror", "killed.creepers", 100, getConfig().turtleEggKillXP*3); - registerMilestone("challenge_creeper_annihilator", "killed.creepers", 1000, getConfig().turtleEggKillXP*3); - registerMilestone("challenge_kills_500", "killed.kills", 500, getConfig().killsChallengeReward); - registerMilestone("challenge_kills_5k", "killed.kills", 5000, getConfig().killsChallengeReward * 5); - registerMilestone("challenge_boss_1", "hunter.boss.kills", 1, getConfig().bossKillReward); - registerMilestone("challenge_boss_10", "hunter.boss.kills", 10, getConfig().bossKillReward * 5); - } - - private void handleCooldownAndXp(Player p, double xpAmount) { - handleCooldownAndXp(p, xpAmount, null); - } - - private void handleCooldownAndXp(Player p, double xpAmount, String rewardKey) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - xp(p, xpAmount, rewardKey); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (e.getBlock().getType().equals(Material.TURTLE_EGG)) { - handleCooldownAndXp(p, getConfig().turtleEggKillXP, "hunter:turtle-egg:break"); - getPlayer(p).getData().addStat("killed.turtleeggs", 1); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerInteractEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (e.getAction().equals(Action.PHYSICAL) && e.getClickedBlock() != null && e.getClickedBlock().getType().equals(Material.TURTLE_EGG)) { - handleCooldownAndXp(p, getConfig().turtleEggKillXP, "hunter:turtle-egg:step"); - getPlayer(p).getData().addStat("killed.turtleeggs", 1); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - if (e.getEntity().getKiller() == null) { - return; - } - Player p = e.getEntity().getKiller(); - - if (!getConfig().getXpForAttackingWithTools) { - return; - } - - shouldReturnForPlayer(p, () -> { - if (e.getEntity().getType().equals(EntityType.CREEPER)) { - double cmult = getConfig().creeperKillMultiplier; - var attribute = Version.get().getAttribute(e.getEntity(), Attributes.GENERIC_MAX_HEALTH); - double xpAmount = (attribute == null ? 1 : attribute.getValue()) * getConfig().killMaxHealthXPMultiplier * cmult; - if (e.getEntity().getPortalCooldown() > 0) { - xpAmount *= getConfig().spawnerMobReductionXpMultiplier; - } - getPlayer(p).getData().addStat("killed.kills", 1); - handleCooldownAndXp(p, xpAmount, "hunter:kill:creeper"); - } else { - handleEntityKill(p, e.getEntity()); - } - EntityType type = e.getEntity().getType(); - if (type == EntityType.ENDER_DRAGON || type == EntityType.WITHER || type == EntityType.ELDER_GUARDIAN || type == EntityType.WARDEN) { - getPlayer(p).getData().addStat("hunter.boss.kills", 1); - } - }); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(CreatureSpawnEvent e) { - if (!isEnabled() || e.isCancelled()) { - return; - } - if (e.getSpawnReason().equals(CreatureSpawnEvent.SpawnReason.SPAWNER)) { - Entity ent = e.getEntity(); - ent.setPortalCooldown(630726000); - } - } - - private void handleEntityKill(Player p, Entity entity) { - if (entity instanceof LivingEntity livingEntity) { - var attribute = Version.get().getAttribute(livingEntity, Attributes.GENERIC_MAX_HEALTH); - double xpAmount = (attribute == null ? 1 : attribute.getValue()) * getConfig().killMaxHealthXPMultiplier; - if (entity.getPortalCooldown() > 0) { - xpAmount *= getConfig().spawnerMobReductionXpMultiplier; - } - getPlayer(p).getData().addStat("killed.kills", 1); - String rewardKey = "hunter:kill:" + entity.getType().name().toLowerCase(Locale.ROOT); - handleCooldownAndXp(p, xpAmount, rewardKey); - } - } - - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Get Xp For Attacking With Tools for the Hunter skill.", impact = "True enables this behavior and false disables it.") - boolean getXpForAttackingWithTools = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Turtle Egg Kill XP for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double turtleEggKillXP = 100; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Creeper Kill Multiplier for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double creeperKillMultiplier = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Kill Max Health XPMultiplier for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double killMaxHealthXPMultiplier = 3.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Spawner Mob Reduction Xp Multiplier for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double spawnerMobReductionXpMultiplier = 0.3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Kills Challenge Reward for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double killsChallengeReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Boss Kill Reward for the Hunter skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double bossKillReward = 1000; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillNether.java b/src/main/java/com/volmit/adapt/content/skill/SkillNether.java deleted file mode 100644 index 7269bba67..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillNether.java +++ /dev/null @@ -1,290 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.nether.NetherFireResist; -import com.volmit.adapt.content.adaptation.nether.NetherBlazeLeech; -import com.volmit.adapt.content.adaptation.nether.NetherGhastWard; -import com.volmit.adapt.content.adaptation.nether.NetherLavaWalker; -import com.volmit.adapt.content.adaptation.nether.NetherPiglinBroker; -import com.volmit.adapt.content.adaptation.nether.NetherSkullYeet; -import com.volmit.adapt.content.adaptation.nether.NetherWitherResist; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.entity.EntityDamageByBlockEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.EntityDeathEvent; - -public class SkillNether extends SimpleSkill { - private int witherRoseCooldown; - - public SkillNether() { - super("nether", Localizer.dLocalize("skill.nether.icon")); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("skill.nether.description")); - setDisplayName(Localizer.dLocalize("skill.nether.name")); - setInterval(7425); - setColor(C.DARK_GRAY); - setIcon(Material.NETHER_STAR); - registerAdaptation(new NetherWitherResist()); - registerAdaptation(new NetherSkullYeet()); - registerAdaptation(new NetherFireResist()); - registerAdaptation(new NetherLavaWalker()); - registerAdaptation(new NetherGhastWard()); - registerAdaptation(new NetherBlazeLeech()); - registerAdaptation(new NetherPiglinBroker()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WITHER_SKELETON_SKULL) - .key("challenge_nether_50") - .title(Localizer.dLocalize("advancement.challenge_nether_50.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_50.description")) - .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "nether", "challenge_nether_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHER_STAR) - .key("challenge_nether_500") - .title(Localizer.dLocalize("advancement.challenge_nether_500.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_500.description")) - .model(CustomModel.get(Material.NETHER_STAR, "advancement", "nether", "challenge_nether_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BEACON) - .key("challenge_nether_5k") - .title(Localizer.dLocalize("advancement.challenge_nether_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_nether_5k.description")) - .model(CustomModel.get(Material.BEACON, "advancement", "nether", "challenge_nether_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_nether_50", "nether.kills", 50, getConfig().getChallengeNetherReward()); - registerMilestone("challenge_nether_500", "nether.kills", 500, getConfig().getChallengeNetherReward() * 2); - registerMilestone("challenge_nether_5k", "nether.kills", 5000, getConfig().getChallengeNetherReward() * 5); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WITHER_ROSE) - .key("challenge_wither_dmg_500") - .title(Localizer.dLocalize("advancement.challenge_wither_dmg_500.title")) - .description(Localizer.dLocalize("advancement.challenge_wither_dmg_500.description")) - .model(CustomModel.get(Material.WITHER_ROSE, "advancement", "nether", "challenge_wither_dmg_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SOUL_LANTERN) - .key("challenge_wither_dmg_5k") - .title(Localizer.dLocalize("advancement.challenge_wither_dmg_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_wither_dmg_5k.description")) - .model(CustomModel.get(Material.SOUL_LANTERN, "advancement", "nether", "challenge_wither_dmg_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_wither_dmg_500", "nether.wither.damage", 500, getConfig().getChallengeWitherDmgReward()); - registerMilestone("challenge_wither_dmg_5k", "nether.wither.damage", 5000, getConfig().getChallengeWitherDmgReward() * 2); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_wither_skel_25") - .title(Localizer.dLocalize("advancement.challenge_wither_skel_25.title")) - .description(Localizer.dLocalize("advancement.challenge_wither_skel_25.description")) - .model(CustomModel.get(Material.BONE, "advancement", "nether", "challenge_wither_skel_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.WITHER_SKELETON_SKULL) - .key("challenge_wither_skel_250") - .title(Localizer.dLocalize("advancement.challenge_wither_skel_250.title")) - .description(Localizer.dLocalize("advancement.challenge_wither_skel_250.description")) - .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "nether", "challenge_wither_skel_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_wither_skel_25", "nether.skeleton.kills", 25, getConfig().getChallengeWitherSkelReward()); - registerMilestone("challenge_wither_skel_250", "nether.skeleton.kills", 250, getConfig().getChallengeWitherSkelReward() * 2); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.NETHER_STAR) - .key("challenge_wither_boss_1") - .title(Localizer.dLocalize("advancement.challenge_wither_boss_1.title")) - .description(Localizer.dLocalize("advancement.challenge_wither_boss_1.description")) - .model(CustomModel.get(Material.NETHER_STAR, "advancement", "nether", "challenge_wither_boss_1")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BEACON) - .key("challenge_wither_boss_10") - .title(Localizer.dLocalize("advancement.challenge_wither_boss_10.title")) - .description(Localizer.dLocalize("advancement.challenge_wither_boss_10.description")) - .model(CustomModel.get(Material.BEACON, "advancement", "nether", "challenge_wither_boss_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_wither_boss_1", "nether.boss.kills", 1, getConfig().getChallengeWitherBossReward()); - registerMilestone("challenge_wither_boss_10", "nether.boss.kills", 10, getConfig().getChallengeWitherBossReward() * 2); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WITHER_ROSE) - .key("challenge_roses_10") - .title(Localizer.dLocalize("advancement.challenge_roses_10.title")) - .description(Localizer.dLocalize("advancement.challenge_roses_10.description")) - .model(CustomModel.get(Material.WITHER_ROSE, "advancement", "nether", "challenge_roses_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.FLOWER_POT) - .key("challenge_roses_100") - .title(Localizer.dLocalize("advancement.challenge_roses_100.title")) - .description(Localizer.dLocalize("advancement.challenge_roses_100.description")) - .model(CustomModel.get(Material.FLOWER_POT, "advancement", "nether", "challenge_roses_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_roses_10", "nether.roses.broken", 10, getConfig().getChallengeRosesReward()); - registerMilestone("challenge_roses_100", "nether.roses.broken", 100, getConfig().getChallengeRosesReward() * 2); - } - - private boolean shouldReturnForEventWithCause(Player p, EntityDamageEvent.DamageCause cause) { - return shouldReturnForPlayer(p) || cause != EntityDamageEvent.DamageCause.WITHER; - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (!this.isEnabled() || e.isCancelled() || !(e.getEntity() instanceof Player p) || shouldReturnForEventWithCause(p, e.getCause()) || e instanceof EntityDamageByBlockEvent) { - return; - } - getPlayer(p).getData().addStat("nether.wither.damage", e.getDamage()); - xp(p, getConfig().getWitherDamageXp()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (e.getBlock().getType() == Material.WITHER_ROSE && witherRoseCooldown == 0) { - witherRoseCooldown = getConfig().getWitherRoseBreakCooldown(); - getPlayer(p).getData().addStat("nether.roses.broken", 1); - xp(p, e.getBlock().getLocation().add(.5D, .5D, .5D), getConfig().getWitherRoseBreakXp()); - } - }); - - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - Player p = e.getEntity().getKiller(); - if (p == null || !p.getClass().getSimpleName().equals("CraftPlayer") || shouldReturnForPlayer(p)) { - return; - } - if (e.getEntityType() == EntityType.WITHER_SKELETON) { - getPlayer(p).getData().addStat("nether.kills", 1); - getPlayer(p).getData().addStat("nether.skeleton.kills", 1); - xp(p, getConfig().getWitherSkeletonKillXp()); - } else if (e.getEntityType() == EntityType.WITHER) { - getPlayer(p).getData().addStat("nether.kills", 1); - getPlayer(p).getData().addStat("nether.boss.kills", 1); - xp(p, getConfig().getWitherKillXp()); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getDamager() instanceof Player p) || shouldReturnForEventWithCause(p, e.getCause())) { - return; - } - xp(p, getConfig().getWitherAttackXp()); - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - if (witherRoseCooldown > 0) { - witherRoseCooldown--; - } - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - if (!shouldReturnForPlayer(i)) { - checkStatTrackers(adaptPlayer); - } - } - } - - @Override - public boolean isEnabled() { - return getConfig().isEnabled(); - } - - @Data - @NoArgsConstructor - public static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - private boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Wither Damage Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double witherDamageXp = 26.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Wither Attack Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double witherAttackXp = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Wither Skeleton Kill Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double witherSkeletonKillXp = 225; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Wither Kill Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double witherKillXp = 900; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Wither Rose Break Xp for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double witherRoseBreakXp = 125; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Wither Rose Break Cooldown for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private int witherRoseBreakCooldown = 60 * 20; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Nether Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double challengeNetherReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Wither Damage Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double challengeWitherDmgReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Wither Skeleton Kill Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double challengeWitherSkelReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Wither Boss Kill Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double challengeWitherBossReward = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Roses Broken Reward for the Nether skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - private double challengeRosesReward = 500; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillPickaxes.java b/src/main/java/com/volmit/adapt/content/skill/SkillPickaxes.java deleted file mode 100644 index 4f3be526f..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillPickaxes.java +++ /dev/null @@ -1,345 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.pickaxe.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -public class SkillPickaxes extends SimpleSkill { - private final Map cooldowns; - - public SkillPickaxes() { - super("pickaxe", Localizer.dLocalize("skill.pickaxe.icon")); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("skill.pickaxe.description")); - setDisplayName(Localizer.dLocalize("skill.pickaxe.name")); - setColor(C.GOLD); - setInterval(2750); - setIcon(Material.NETHERITE_PICKAXE); - cooldowns = new HashMap<>(); - registerAdaptation(new PickaxeChisel()); - registerAdaptation(new PickaxeVeinminer()); - registerAdaptation(new PickaxeAutosmelt()); - registerAdaptation(new PickaxeDropToInventory()); - registerAdaptation(new PickaxeSilkSpawner()); - registerAdaptation(new PickaxeQuarrySense()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WOODEN_PICKAXE) - .key("challenge_pickaxe_1k") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_1k.description")) - .model(CustomModel.get(Material.WOODEN_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.STONE_PICKAXE) - .key("challenge_pickaxe_5k") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_5k.description")) - .model(CustomModel.get(Material.STONE_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_PICKAXE) - .key("challenge_pickaxe_50k") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_50k.description")) - .model(CustomModel.get(Material.IRON_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_PICKAXE) - .key("challenge_pickaxe_500k") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_500k.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_500k.description")) - .model(CustomModel.get(Material.DIAMOND_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_500k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_PICKAXE) - .key("challenge_pickaxe_5m") - .title(Localizer.dLocalize("advancement.challenge_pickaxe_5m.title")) - .description(Localizer.dLocalize("advancement.challenge_pickaxe_5m.description")) - .model(CustomModel.get(Material.NETHERITE_PICKAXE, "advancement", "pickaxe", "challenge_pickaxe_5m")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()) - .build()) - .build()); - - registerMilestone("challenge_pickaxe_1k", "pickaxe.blocks.broken", 100, getConfig().emeraldBonus*2); - registerMilestone("challenge_pickaxe_5k", "pickaxe.blocks.broken", 500, getConfig().emeraldBonus*5); - registerMilestone("challenge_pickaxe_50k", "pickaxe.blocks.broken", 5000, getConfig().emeraldBonus*10); - registerMilestone("challenge_pickaxe_500k", "pickaxe.blocks.broken", 50000, getConfig().emeraldBonus*10); - registerMilestone("challenge_pickaxe_5m", "pickaxe.blocks.broken", 500000, getConfig().emeraldBonus*50); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WOODEN_PICKAXE).key("challenge_pick_swing_500") - .title(Localizer.dLocalize("advancement.challenge_pick_swing_500.title")) - .description(Localizer.dLocalize("advancement.challenge_pick_swing_500.description")) - .model(CustomModel.get(Material.WOODEN_PICKAXE, "advancement", "pickaxe", "challenge_pick_swing_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_PICKAXE) - .key("challenge_pick_swing_5k") - .title(Localizer.dLocalize("advancement.challenge_pick_swing_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_pick_swing_5k.description")) - .model(CustomModel.get(Material.IRON_PICKAXE, "advancement", "pickaxe", "challenge_pick_swing_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_pick_swing_500", "pickaxe.swings", 500, getConfig().emeraldBonus); - registerMilestone("challenge_pick_swing_5k", "pickaxe.swings", 5000, getConfig().emeraldBonus * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_PICKAXE).key("challenge_pick_damage_1k") - .title(Localizer.dLocalize("advancement.challenge_pick_damage_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_pick_damage_1k.description")) - .model(CustomModel.get(Material.GOLDEN_PICKAXE, "advancement", "pickaxe", "challenge_pick_damage_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_PICKAXE) - .key("challenge_pick_damage_10k") - .title(Localizer.dLocalize("advancement.challenge_pick_damage_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_pick_damage_10k.description")) - .model(CustomModel.get(Material.DIAMOND_PICKAXE, "advancement", "pickaxe", "challenge_pick_damage_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_pick_damage_1k", "pickaxe.damage", 1000, getConfig().emeraldBonus); - registerMilestone("challenge_pick_damage_10k", "pickaxe.damage", 10000, getConfig().emeraldBonus * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.RAW_IRON).key("challenge_pick_value_5k") - .title(Localizer.dLocalize("advancement.challenge_pick_value_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_pick_value_5k.description")) - .model(CustomModel.get(Material.RAW_IRON, "advancement", "pickaxe", "challenge_pick_value_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.RAW_GOLD) - .key("challenge_pick_value_50k") - .title(Localizer.dLocalize("advancement.challenge_pick_value_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_pick_value_50k.description")) - .model(CustomModel.get(Material.RAW_GOLD, "advancement", "pickaxe", "challenge_pick_value_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_pick_value_5k", "pickaxe.blocks.value", 5000, getConfig().emeraldBonus); - registerMilestone("challenge_pick_value_50k", "pickaxe.blocks.value", 50000, getConfig().emeraldBonus * 2); - - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_ORE).key("challenge_pick_ores_500") - .title(Localizer.dLocalize("advancement.challenge_pick_ores_500.title")) - .description(Localizer.dLocalize("advancement.challenge_pick_ores_500.description")) - .model(CustomModel.get(Material.IRON_ORE, "advancement", "pickaxe", "challenge_pick_ores_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_ORE) - .key("challenge_pick_ores_5k") - .title(Localizer.dLocalize("advancement.challenge_pick_ores_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_pick_ores_5k.description")) - .model(CustomModel.get(Material.DIAMOND_ORE, "advancement", "pickaxe", "challenge_pick_ores_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_pick_ores_500", "pickaxe.ores", 500, getConfig().emeraldBonus); - registerMilestone("challenge_pick_ores_5k", "pickaxe.ores", 5000, getConfig().emeraldBonus * 2); - - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getDamager() instanceof Player ? (Player) e.getDamager() : null; - if (!getConfig().getXpForAttackingWithTools || p == null) { - return; - } - - shouldReturnForPlayer(p, () -> { - if (checkValidEntity(e.getEntity().getType())) { - AdaptPlayer a = getPlayer(p); - ItemStack hand = p.getInventory().getItemInMainHand(); - if (isPickaxe(hand)) { - a.getData().addStat("pickaxe.swings", 1); - a.getData().addStat("pickaxe.damage", e.getDamage()); - handleCooldown(p, () -> xp(p, e.getEntity().getLocation(), getConfig().damageXPMultiplier * e.getDamage())); - } - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(p, () -> { - ItemStack mainHand = p.getInventory().getItemInMainHand(); - - if (isPickaxe(mainHand)) { - Material blockType = e.getBlock().getType(); - double blockValue = getValue(blockType); - AdaptPlayer adaptPlayer = getPlayer(p); - - adaptPlayer.getData().addStat("pickaxe.blocks.broken", 1); - adaptPlayer.getData().addStat("pickaxe.blocks.value", blockValue); - if (blockType.name().contains("_ORE")) { - adaptPlayer.getData().addStat("pickaxe.ores", 1); - } - - handleCooldown(p, () -> { - if (mainHand.getEnchantments().containsKey(Enchantment.SILK_TOUCH)) { - xp(p, 5); - } else { - Location blockLocation = e.getBlock().getLocation().clone().add(0.5, 0.5, 0.5); - xp(p, blockLocation, blockXP(e.getBlock(), blockValue)); - } - }); - } - }); - } - - public double getValue(Material type) { - Config c = getConfig(); - double value = super.getValue(type) * c.blockValueMultiplier; - value += Math.min(c.maxHardnessBonus, type.getHardness()); - value += Math.min(c.maxBlastResistanceBonus, type.getBlastResistance()); - - value += switch (type) { - case COAL_ORE -> c.coalBonus; - case COPPER_ORE -> c.copperBonus; - case IRON_ORE -> c.ironBonus; - case GOLD_ORE -> c.goldBonus; - case LAPIS_ORE -> c.lapisBonus; - case DIAMOND_ORE -> c.diamondBonus; - case EMERALD_ORE -> c.emeraldBonus; - case NETHER_GOLD_ORE -> c.netherGoldBonus; - case NETHER_QUARTZ_ORE -> c.netherQuartzBonus; - case REDSTONE_ORE -> c.redstoneBonus; - case ANCIENT_DEBRIS -> c.debrisBonus; - case DEEPSLATE_COAL_ORE -> c.coalBonus * c.deepslateMultiplier; - case DEEPSLATE_COPPER_ORE -> c.copperBonus * c.deepslateMultiplier; - case DEEPSLATE_IRON_ORE -> c.ironBonus * c.deepslateMultiplier; - case DEEPSLATE_GOLD_ORE -> c.goldBonus * c.deepslateMultiplier; - case DEEPSLATE_LAPIS_ORE -> c.lapisBonus * c.deepslateMultiplier; - case DEEPSLATE_DIAMOND_ORE -> c.diamondBonus * c.deepslateMultiplier; - case DEEPSLATE_EMERALD_ORE -> c.emeraldBonus * c.deepslateMultiplier; - case DEEPSLATE_REDSTONE_ORE -> c.redstoneBonus * c.deepslateMultiplier; - default -> 0; - }; - - return value * 0.48; - } - - - private void handleCooldown(Player p, Runnable action) { - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - action.run(); - } - - @Override - public void onTick() { - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Debris Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double debrisBonus = 210; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Get Xp For Attacking With Tools for the Pickaxes skill.", impact = "True enables this behavior and false disables it.") - boolean getXpForAttackingWithTools = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage XPMultiplier for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageXPMultiplier = 6.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Block Value Multiplier for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double blockValueMultiplier = 0.125; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Hardness Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxHardnessBonus = 9; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Max Blast Resistance Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double maxBlastResistanceBonus = 10; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Coal Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double coalBonus = 18; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Iron Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double ironBonus = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Redstone Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double redstoneBonus = 55; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Copper Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double copperBonus = 22; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Gold Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double goldBonus = 38; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Lapis Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double lapisBonus = 75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Diamond Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double diamondBonus = 175; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Emerald Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double emeraldBonus = 210; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Nether Gold Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double netherGoldBonus = 105; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Nether Quartz Bonus for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double netherQuartzBonus = 125; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Deepslate Multiplier for the Pickaxes skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double deepslateMultiplier = 1.35; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillRanged.java b/src/main/java/com/volmit/adapt/content/skill/SkillRanged.java deleted file mode 100644 index f570e829e..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillRanged.java +++ /dev/null @@ -1,294 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.ranged.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.FishHook; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.entity.Snowball; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -public class SkillRanged extends SimpleSkill { - private final Map cooldowns; - - public SkillRanged() { - super("ranged", Localizer.dLocalize("skill.ranged.icon")); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("skill.ranged.description")); - setDisplayName(Localizer.dLocalize("skill.ranged.name")); - setColor(C.DARK_GREEN); - setInterval(3044); - registerAdaptation(new RangedForce()); - registerAdaptation(new RangedPiercing()); - registerAdaptation(new RangedArrowRecovery()); - registerAdaptation(new RangedLungeShot()); - registerAdaptation(new RangedWebBomb()); - registerAdaptation(new RangedTrajectorySight()); - registerAdaptation(new RangedFloaters()); - registerAdaptation(new RangedPinningShot()); - registerAdaptation(new RangedRicochetBolt()); - setIcon(Material.CROSSBOW); - cooldowns = new HashMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ARROW) - .key("challenge_ranged_100") - .title(Localizer.dLocalize("advancement.challenge_ranged_100.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_100.description")) - .model(CustomModel.get(Material.ARROW, "advancement", "ranged", "challenge_ranged_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_ranged_1k") - .title(Localizer.dLocalize("advancement.challenge_ranged_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_1k.description")) - .model(CustomModel.get(Material.SPECTRAL_ARROW, "advancement", "ranged", "challenge_ranged_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CROSSBOW) - .key("challenge_ranged_10k") - .title(Localizer.dLocalize("advancement.challenge_ranged_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_10k.description")) - .model(CustomModel.get(Material.CROSSBOW, "advancement", "ranged", "challenge_ranged_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_ranged_100", "ranged.shotsfired", 100, getConfig().challengeRangedReward); - registerMilestone("challenge_ranged_1k", "ranged.shotsfired", 1000, getConfig().challengeRangedReward * 2); - registerMilestone("challenge_ranged_10k", "ranged.shotsfired", 10000, getConfig().challengeRangedReward * 5); - - // Chain 2 - Ranged Damage - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BOW) - .key("challenge_ranged_dmg_1k") - .title(Localizer.dLocalize("advancement.challenge_ranged_dmg_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_dmg_1k.description")) - .model(CustomModel.get(Material.BOW, "advancement", "ranged", "challenge_ranged_dmg_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CROSSBOW) - .key("challenge_ranged_dmg_10k") - .title(Localizer.dLocalize("advancement.challenge_ranged_dmg_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_dmg_10k.description")) - .model(CustomModel.get(Material.CROSSBOW, "advancement", "ranged", "challenge_ranged_dmg_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_ranged_dmg_1k", "ranged.damage", 1000, getConfig().challengeRangedDmgReward); - registerMilestone("challenge_ranged_dmg_10k", "ranged.damage", 10000, getConfig().challengeRangedDmgReward * 3); - - // Chain 3 - Ranged Distance - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ARROW) - .key("challenge_ranged_dist_5k") - .title(Localizer.dLocalize("advancement.challenge_ranged_dist_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_dist_5k.description")) - .model(CustomModel.get(Material.ARROW, "advancement", "ranged", "challenge_ranged_dist_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SPECTRAL_ARROW) - .key("challenge_ranged_dist_50k") - .title(Localizer.dLocalize("advancement.challenge_ranged_dist_50k.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_dist_50k.description")) - .model(CustomModel.get(Material.SPECTRAL_ARROW, "advancement", "ranged", "challenge_ranged_dist_50k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_ranged_dist_5k", "ranged.distance", 5000, getConfig().challengeRangedDistReward); - registerMilestone("challenge_ranged_dist_50k", "ranged.distance", 50000, getConfig().challengeRangedDistReward * 3); - - // Chain 4 - Ranged Kills - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TIPPED_ARROW) - .key("challenge_ranged_kills_50") - .title(Localizer.dLocalize("advancement.challenge_ranged_kills_50.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_kills_50.description")) - .model(CustomModel.get(Material.TIPPED_ARROW, "advancement", "ranged", "challenge_ranged_kills_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TARGET) - .key("challenge_ranged_kills_500") - .title(Localizer.dLocalize("advancement.challenge_ranged_kills_500.title")) - .description(Localizer.dLocalize("advancement.challenge_ranged_kills_500.description")) - .model(CustomModel.get(Material.TARGET, "advancement", "ranged", "challenge_ranged_kills_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_ranged_kills_50", "ranged.kills", 50, getConfig().challengeRangedKillsReward); - registerMilestone("challenge_ranged_kills_500", "ranged.kills", 500, getConfig().challengeRangedKillsReward * 3); - - // Chain 5 - Longshots - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SPYGLASS) - .key("challenge_longshot_25") - .title(Localizer.dLocalize("advancement.challenge_longshot_25.title")) - .description(Localizer.dLocalize("advancement.challenge_longshot_25.description")) - .model(CustomModel.get(Material.SPYGLASS, "advancement", "ranged", "challenge_longshot_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_longshot_250") - .title(Localizer.dLocalize("advancement.challenge_longshot_250.title")) - .description(Localizer.dLocalize("advancement.challenge_longshot_250.description")) - .model(CustomModel.get(Material.ENDER_EYE, "advancement", "ranged", "challenge_longshot_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_longshot_25", "ranged.longshots", 25, getConfig().challengeRangedLongshotReward); - registerMilestone("challenge_longshot_250", "ranged.longshots", 250, getConfig().challengeRangedLongshotReward * 3); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(ProjectileLaunchEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getEntity().getShooter() instanceof Player p)) { - return; - } - shouldReturnForPlayer(p, e, () -> { - if (e.getEntity() instanceof Snowball || e.getEntity().getType().name().toLowerCase(Locale.ROOT).contains("hook")) { - return; // Ignore snowballs and fishing hooks - } - - getPlayer(p).getData().addStat("ranged.shotsfired", 1); - getPlayer(p).getData().addStat("ranged.shotsfired." + e.getEntity().getType().name().toLowerCase(Locale.ROOT), 1); - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - xp(p, getConfig().shootXP); - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getDamager() instanceof Projectile) || !(((Projectile) e.getDamager()).getShooter() instanceof Player p) || !checkValidEntity(e.getEntity().getType())) { - return; - } - shouldReturnForPlayer(p, e, () -> { - if (e.getEntity() instanceof Snowball || e.getEntity() instanceof FishHook) { - return; // Ignore snowballs and fishing hooks - } - if (e.getEntity().getLocation().getWorld().equals(p.getLocation().getWorld())) { - double distance = e.getEntity().getLocation().distance(p.getLocation()); - getPlayer(p).getData().addStat("ranged.distance", distance); - getPlayer(p).getData().addStat("ranged.distance." + e.getDamager().getType().name().toLowerCase(Locale.ROOT), distance); - if (distance > 30) { - getPlayer(p).getData().addStat("ranged.longshots", 1); - } - } - getPlayer(p).getData().addStat("ranged.damage", e.getDamage()); - getPlayer(p).getData().addStat("ranged.damage." + e.getDamager().getType().name().toLowerCase(Locale.ROOT), e.getDamage()); - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - xp(p, e.getEntity().getLocation(), (getConfig().hitDamageXPMultiplier * e.getDamage()) + (e.getEntity().getLocation().distance(p.getLocation()) * getConfig().hitDistanceXPMultiplier)); - - }); - } - - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - Player p = e.getEntity().getKiller(); - if (p == null) { - return; - } - shouldReturnForPlayer(p, () -> { - ItemStack hand = p.getInventory().getItemInMainHand(); - if (hand.getType() == Material.BOW || hand.getType() == Material.CROSSBOW) { - getPlayer(p).getData().addStat("ranged.kills", 1); - } - }); - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Shoot XP for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double shootXP = 5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hit Damage XPMultiplier for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hitDamageXPMultiplier = 1.75; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Hit Distance XPMultiplier for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double hitDistanceXPMultiplier = 1.2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeRangedReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Damage Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeRangedDmgReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Distance Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeRangedDistReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Kills Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeRangedKillsReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Ranged Longshot Reward for the Ranged skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeRangedLongshotReward = 500; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillRift.java b/src/main/java/com/volmit/adapt/content/skill/SkillRift.java deleted file mode 100644 index 74d4f9437..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillRift.java +++ /dev/null @@ -1,329 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.chronos.ChronosInstantRecall; -import com.volmit.adapt.content.adaptation.rift.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.reflect.registries.Attributes; -import com.volmit.adapt.util.reflect.registries.EntityTypes; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.*; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerTeleportEvent; - -public class SkillRift extends SimpleSkill { - private final KMap lasttp; - - public SkillRift() { - super("rift", Localizer.dLocalize("skill.rift.icon")); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("skill.rift.description")); - setDisplayName(Localizer.dLocalize("skill.rift.name")); - setColor(C.DARK_PURPLE); - setInterval(1154); - setIcon(Material.ENDER_EYE); - registerAdaptation(new RiftResist()); - registerAdaptation(new RiftAccess()); - registerAdaptation(new RiftEnderchest()); - registerAdaptation(new RiftGate()); - registerAdaptation(new RiftBlink()); - registerAdaptation(new RiftDescent()); - registerAdaptation(new RiftVisage()); - registerAdaptation(new RiftEnderTaglock()); - registerAdaptation(new RiftInflatedPocketDimension()); - registerAdaptation(new RiftVoidMagnet()); - lasttp = new KMap<>(); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_50") - .title(Localizer.dLocalize("advancement.challenge_rift_50.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_50.description")) - .model(CustomModel.get(Material.ENDER_PEARL, "advancement", "rift", "challenge_rift_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_rift_500") - .title(Localizer.dLocalize("advancement.challenge_rift_500.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_500.description")) - .model(CustomModel.get(Material.ENDER_EYE, "advancement", "rift", "challenge_rift_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.END_CRYSTAL) - .key("challenge_rift_5k") - .title(Localizer.dLocalize("advancement.challenge_rift_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_5k.description")) - .model(CustomModel.get(Material.END_CRYSTAL, "advancement", "rift", "challenge_rift_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_rift_50", "rift.teleports", 50, getConfig().challengeRiftReward); - registerMilestone("challenge_rift_500", "rift.teleports", 500, getConfig().challengeRiftReward * 2); - registerMilestone("challenge_rift_5k", "rift.teleports", 5000, getConfig().challengeRiftReward * 5); - - // Chain 2 - Ender Pearl Throws - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_pearls_50") - .title(Localizer.dLocalize("advancement.challenge_rift_pearls_50.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_pearls_50.description")) - .model(CustomModel.get(Material.ENDER_PEARL, "advancement", "rift", "challenge_rift_pearls_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENDER_EYE) - .key("challenge_rift_pearls_500") - .title(Localizer.dLocalize("advancement.challenge_rift_pearls_500.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_pearls_500.description")) - .model(CustomModel.get(Material.ENDER_EYE, "advancement", "rift", "challenge_rift_pearls_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_pearls_50", "rift.ender.pearls", 50, getConfig().challengeRiftReward); - registerMilestone("challenge_rift_pearls_500", "rift.ender.pearls", 500, getConfig().challengeRiftReward * 2); - - // Chain 3 - Enderman Damage - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ENDER_PEARL) - .key("challenge_rift_enderman_50") - .title(Localizer.dLocalize("advancement.challenge_rift_enderman_50.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_enderman_50.description")) - .model(CustomModel.get(Material.ENDER_PEARL, "advancement", "rift", "challenge_rift_enderman_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.END_STONE) - .key("challenge_rift_enderman_500") - .title(Localizer.dLocalize("advancement.challenge_rift_enderman_500.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_enderman_500.description")) - .model(CustomModel.get(Material.END_STONE, "advancement", "rift", "challenge_rift_enderman_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_enderman_50", "rift.enderman.kills", 50, getConfig().challengeRiftReward); - registerMilestone("challenge_rift_enderman_500", "rift.enderman.kills", 500, getConfig().challengeRiftReward * 2); - - // Chain 4 - Dragon Damage - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DRAGON_BREATH) - .key("challenge_rift_dragon_500") - .title(Localizer.dLocalize("advancement.challenge_rift_dragon_500.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_dragon_500.description")) - .model(CustomModel.get(Material.DRAGON_BREATH, "advancement", "rift", "challenge_rift_dragon_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DRAGON_HEAD) - .key("challenge_rift_dragon_5k") - .title(Localizer.dLocalize("advancement.challenge_rift_dragon_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_dragon_5k.description")) - .model(CustomModel.get(Material.DRAGON_HEAD, "advancement", "rift", "challenge_rift_dragon_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_dragon_500", "rift.dragon.damage", 500, getConfig().challengeRiftReward); - registerMilestone("challenge_rift_dragon_5k", "rift.dragon.damage", 5000, getConfig().challengeRiftReward * 2); - - // Chain 5 - End Crystal Destruction - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.END_CRYSTAL) - .key("challenge_rift_crystal_10") - .title(Localizer.dLocalize("advancement.challenge_rift_crystal_10.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_crystal_10.description")) - .model(CustomModel.get(Material.END_CRYSTAL, "advancement", "rift", "challenge_rift_crystal_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BEACON) - .key("challenge_rift_crystal_100") - .title(Localizer.dLocalize("advancement.challenge_rift_crystal_100.title")) - .description(Localizer.dLocalize("advancement.challenge_rift_crystal_100.description")) - .model(CustomModel.get(Material.BEACON, "advancement", "rift", "challenge_rift_crystal_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_rift_crystal_10", "rift.crystals.destroyed", 10, getConfig().challengeRiftReward); - registerMilestone("challenge_rift_crystal_100", "rift.crystals.destroyed", 100, getConfig().challengeRiftReward * 2); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerTeleportEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - if (ChronosInstantRecall.isRecallTeleportSuppressed(p)) { - return; - } - - shouldReturnForPlayer(e.getPlayer(), e, () -> { - getPlayer(p).getData().addStat("rift.teleports", 1); - if (!lasttp.containsKey(p)) { - xpSilent(p, getConfig().teleportXP, "rift:teleport"); - lasttp.put(p, M.ms()); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(ProjectileLaunchEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getEntity().getShooter() instanceof Player p)) { - return; - } - shouldReturnForPlayer(p, e, () -> { - if (e.getEntity() instanceof EnderPearl) { - xp(p, getConfig().throwEnderpearlXP, "rift:throw:ender-pearl"); - getPlayer(p).getData().addStat("rift.ender.pearls", 1); - } else if (e.getEntity() instanceof EnderSignal) { - xp(p, getConfig().throwEnderEyeXP, "rift:throw:ender-eye"); - } - }); - } - - private void handleEntityDamageByEntity(Entity entity, Player p, double damage) { - if (entity instanceof LivingEntity living) { - var attribute = Version.get().getAttribute(living, Attributes.GENERIC_MAX_HEALTH); - double baseHealth = attribute == null ? 1 : attribute.getBaseValue(); - double multiplier = switch (entity.getType()) { - case ENDERMAN -> getConfig().damageEndermanXPMultiplier; - case ENDERMITE -> getConfig().damageEndermiteXPMultiplier; - case ENDER_DRAGON -> getConfig().damageEnderdragonXPMultiplier; - default -> 0; - }; - double xp = multiplier * Math.min(damage, baseHealth); - String rewardKey = switch (entity.getType()) { - case ENDERMAN -> "rift:damage:enderman"; - case ENDERMITE -> "rift:damage:endermite"; - case ENDER_DRAGON -> "rift:damage:ender-dragon"; - default -> "rift:damage:other"; - }; - if (xp > 0) xp(p, xp, rewardKey); - if (entity.getType() == EntityType.ENDERMAN) { - getPlayer(p).getData().addStat("rift.enderman.kills", 1); - } else if (entity.getType() == EntityType.ENDER_DRAGON) { - getPlayer(p).getData().addStat("rift.dragon.damage", damage); - } - } else if (entity.getType() == EntityTypes.ENDER_CRYSTAL) { - xp(p, getConfig().damageEndCrystalXP, "rift:damage:end-crystal"); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p) { - shouldReturnForPlayer(p, e, () -> handleEntityDamageByEntity(e.getEntity(), p, e.getDamage())); - } else if (e.getDamager() instanceof Projectile j && j.getShooter() instanceof Player p) { - shouldReturnForPlayer(p, e, () -> handleEntityDamageByEntity(e.getEntity(), p, e.getDamage())); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - if (e.getEntity() instanceof EnderCrystal && e.getEntity().getKiller() != null) { - Player p = e.getEntity().getKiller(); - shouldReturnForPlayer(p, () -> { - xp(e.getEntity().getKiller(), getConfig().destroyEndCrystalXP, "rift:kill:end-crystal"); - getPlayer(p).getData().addStat("rift.crystals.destroyed", 1); - }); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerQuitEvent e) { - Player p = e.getPlayer(); - lasttp.remove(p); - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - for (Player i : lasttp.k()) { - shouldReturnForPlayer(i, () -> { - if (M.ms() - lasttp.get(i) > getConfig().teleportXPCooldown) { - lasttp.remove(i); - } - }); - } - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Destroy End Crystal XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double destroyEndCrystalXP = 250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage End Crystal XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageEndCrystalXP = 110; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Enderman XPMultiplier for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageEndermanXPMultiplier = 4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Endermite XPMultiplier for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageEndermiteXPMultiplier = 2; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Enderdragon XPMultiplier for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageEnderdragonXPMultiplier = 8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Throw Enderpearl XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double throwEnderpearlXP = 65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Throw Ender Eye XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double throwEnderEyeXP = 30; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Teleport XP for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double teleportXP = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Teleport XPCooldown for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double teleportXPCooldown = 60000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Rift Reward for the Rift skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeRiftReward = 500; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillSeaborne.java b/src/main/java/com/volmit/adapt/content/skill/SkillSeaborne.java deleted file mode 100644 index 09ab57031..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillSeaborne.java +++ /dev/null @@ -1,311 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.seaborrne.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.reflect.registries.Attributes; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.*; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.PlayerFishEvent; - -import java.util.HashMap; -import java.util.Map; - -public class SkillSeaborne extends SimpleSkill { - private final Map cooldowns; - - public SkillSeaborne() { - super("seaborne", Localizer.dLocalize("skill.seaborne.icon")); - registerConfiguration(Config.class); - setColor(C.BLUE); - setDescription(Localizer.dLocalize("skill.seaborne.description")); - setDisplayName(Localizer.dLocalize("skill.seaborne.name")); - setInterval(2120); - setIcon(Material.TRIDENT); - registerAdaptation(new SeaborneOxygen()); - registerAdaptation(new SeaborneSpeed()); - registerAdaptation(new SeaborneFishersFantasy()); - registerAdaptation(new SeaborneTurtlesVision()); - registerAdaptation(new SeaborneTurtlesMiningSpeed()); - registerAdaptation(new SeaborneTidecaller()); - registerAdaptation(new SeabornePressureDiver()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TURTLE_HELMET) - .key("challenge_swim_1nm") - .title(Localizer.dLocalize("advancement.challenge_swim_1nm.title")) - .description(Localizer.dLocalize("advancement.challenge_swim_1nm.description")) - .model(CustomModel.get(Material.TURTLE_HELMET, "advancement", "seaborne", "challenge_swim_1nm")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.HEART_OF_THE_SEA) - .key("challenge_swim_5k") - .title(Localizer.dLocalize("advancement.challenge_swim_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_swim_5k.description")) - .model(CustomModel.get(Material.HEART_OF_THE_SEA, "advancement", "seaborne", "challenge_swim_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TRIDENT) - .key("challenge_swim_20k") - .title(Localizer.dLocalize("advancement.challenge_swim_20k.title")) - .description(Localizer.dLocalize("advancement.challenge_swim_20k.description")) - .model(CustomModel.get(Material.TRIDENT, "advancement", "seaborne", "challenge_swim_20k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_swim_1nm", "move.swim", 1852, getConfig().challengeSwim1nmReward); - registerMilestone("challenge_swim_5k", "move.swim", 5000, getConfig().challengeSwim5kReward); - registerMilestone("challenge_swim_20k", "move.swim", 20000, getConfig().challengeSwim20kReward); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FISHING_ROD) - .key("challenge_fish_25") - .title(Localizer.dLocalize("advancement.challenge_fish_25.title")) - .description(Localizer.dLocalize("advancement.challenge_fish_25.description")) - .model(CustomModel.get(Material.FISHING_ROD, "advancement", "seaborne", "challenge_fish_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TROPICAL_FISH) - .key("challenge_fish_250") - .title(Localizer.dLocalize("advancement.challenge_fish_250.title")) - .description(Localizer.dLocalize("advancement.challenge_fish_250.description")) - .model(CustomModel.get(Material.TROPICAL_FISH, "advancement", "seaborne", "challenge_fish_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_fish_25", "seaborne.fish.caught", 25, getConfig().challengeSwim1nmReward); - registerMilestone("challenge_fish_250", "seaborne.fish.caught", 250, getConfig().challengeSwim1nmReward); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ROTTEN_FLESH) - .key("challenge_drowned_25") - .title(Localizer.dLocalize("advancement.challenge_drowned_25.title")) - .description(Localizer.dLocalize("advancement.challenge_drowned_25.description")) - .model(CustomModel.get(Material.ROTTEN_FLESH, "advancement", "seaborne", "challenge_drowned_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.TRIDENT) - .key("challenge_drowned_250") - .title(Localizer.dLocalize("advancement.challenge_drowned_250.title")) - .description(Localizer.dLocalize("advancement.challenge_drowned_250.description")) - .model(CustomModel.get(Material.TRIDENT, "advancement", "seaborne", "challenge_drowned_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_drowned_25", "seaborne.drowned.kills", 25, getConfig().challengeSwim1nmReward); - registerMilestone("challenge_drowned_250", "seaborne.drowned.kills", 250, getConfig().challengeSwim1nmReward); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.PRISMARINE_SHARD) - .key("challenge_guardian_10") - .title(Localizer.dLocalize("advancement.challenge_guardian_10.title")) - .description(Localizer.dLocalize("advancement.challenge_guardian_10.description")) - .model(CustomModel.get(Material.PRISMARINE_SHARD, "advancement", "seaborne", "challenge_guardian_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.SEA_LANTERN) - .key("challenge_guardian_100") - .title(Localizer.dLocalize("advancement.challenge_guardian_100.title")) - .description(Localizer.dLocalize("advancement.challenge_guardian_100.description")) - .model(CustomModel.get(Material.SEA_LANTERN, "advancement", "seaborne", "challenge_guardian_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_guardian_10", "seaborne.guardian.kills", 10, getConfig().challengeSwim1nmReward); - registerMilestone("challenge_guardian_100", "seaborne.guardian.kills", 100, getConfig().challengeSwim1nmReward); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.PRISMARINE) - .key("challenge_underwater_blocks_100") - .title(Localizer.dLocalize("advancement.challenge_underwater_blocks_100.title")) - .description(Localizer.dLocalize("advancement.challenge_underwater_blocks_100.description")) - .model(CustomModel.get(Material.PRISMARINE, "advancement", "seaborne", "challenge_underwater_blocks_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CONDUIT) - .key("challenge_underwater_blocks_1k") - .title(Localizer.dLocalize("advancement.challenge_underwater_blocks_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_underwater_blocks_1k.description")) - .model(CustomModel.get(Material.CONDUIT, "advancement", "seaborne", "challenge_underwater_blocks_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_underwater_blocks_100", "seaborne.underwater.blocks", 100, getConfig().challengeSwim1nmReward); - registerMilestone("challenge_underwater_blocks_1k", "seaborne.underwater.blocks", 1000, getConfig().challengeSwim1nmReward); - cooldowns = new HashMap<>(); - } - - private boolean isOnCooldown(Player p, long cooldown) { - Long lastCooldown = cooldowns.get(p); - return lastCooldown != null && lastCooldown + cooldown > System.currentTimeMillis(); - } - - private void setCooldown(Player p) { - cooldowns.put(p, System.currentTimeMillis()); - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - shouldReturnForPlayer(i, () -> { - if (i.getWorld().getBlockAt(i.getLocation()).isLiquid() && i.isSwimming() && i.getPlayer() != null && i.getPlayer().getRemainingAir() < i.getMaximumAir()) { - Adapt.verbose("seaborne Tick"); - checkStatTrackers(adaptPlayer); - xpSilent(i, getConfig().swimXP, "seaborne:swim"); - } - }); - - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerFishEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (e.getState().equals(PlayerFishEvent.State.CAUGHT_FISH)) { - getPlayer(p).getData().addStat("seaborne.fish.caught", 1); - xp(p, 250); - } else if (e.getState().equals(PlayerFishEvent.State.CAUGHT_ENTITY)) { - xp(p, 10); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(BlockBreakEvent e) { - if (e.isCancelled()) { - return; - } - Player p = e.getPlayer(); - shouldReturnForPlayer(e.getPlayer(), e, () -> { - if (isOnCooldown(p, getConfig().seaPickleCooldown)) { - return; - } - setCooldown(p); - if (p.isSwimming() || p.isInWater()) { - getPlayer(p).getData().addStat("seaborne.underwater.blocks", 1); - } - if (e.getBlock().getType().equals(Material.SEA_PICKLE) && p.isSwimming() && p.getRemainingAir() < p.getMaximumAir()) { // BECAUSE I LIKE PICKLES - xpSilent(p, 10, "seaborne:sea-pickle"); - } else { - xpSilent(p, 3, "seaborne:underwater-block"); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - Player p = e.getEntity().getKiller(); - if (p == null || !p.getClass().getSimpleName().equals("CraftPlayer") || shouldReturnForPlayer(p)) { - return; - } - if (e.getEntityType() == EntityType.DROWNED) { - getPlayer(p).getData().addStat("seaborne.drowned.kills", 1); - } else if (e.getEntityType() == EntityType.GUARDIAN || e.getEntityType() == EntityType.ELDER_GUARDIAN) { - getPlayer(p).getData().addStat("seaborne.guardian.kills", 1); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled() || !(e.getEntity() instanceof LivingEntity entity)) - return; - - if (e.getEntity().getType() == EntityType.DROWNED && e.getDamager() instanceof Player p) { - shouldReturnForPlayer(p, e, () -> { - if (isOnCooldown(p, getConfig().seaPickleCooldown)) { - return; - } - setCooldown(p); - xp(p, getConfig().damagedrownxpmultiplier * Math.min(e.getDamage(), getBaseHealth(entity))); - }); - } else if (e.getDamager().getType() == EntityType.TRIDENT) { - var shooter = ((Trident) e.getDamager()).getShooter(); - if (shooter instanceof Player p) { - shouldReturnForPlayer(p, e, () -> xp(p, getConfig().tridentxpmultiplier * Math.min(e.getDamage(), getBaseHealth(entity)))); - } - } else if (e.getDamager() instanceof Player p) { - if (p.getInventory().getItemInMainHand().getType().equals(Material.TRIDENT)) { - shouldReturnForPlayer(p, e, () -> xp(p, getConfig().tridentxpmultiplier * Math.min(e.getDamage(), getBaseHealth(entity)))); - } - } - } - - private double getBaseHealth(LivingEntity entity) { - var attribute = Version.get().getAttribute(entity, Attributes.GENERIC_MAX_HEALTH); - return attribute == null ? 0 : attribute.getBaseValue(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sea Pickle Cooldown for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public long seaPickleCooldown = 60000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Tridentxpmultiplier for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double tridentxpmultiplier = 4.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damagedrownxpmultiplier for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damagedrownxpmultiplier = 3; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Swim1nm Reward for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSwim1nmReward = 750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Swim5k Reward for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSwim5kReward = 1500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Swim20k Reward for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSwim20kReward = 3750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Swim XP for the Seaborne skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double swimXP = 0.4; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillStealth.java b/src/main/java/com/volmit/adapt/content/skill/SkillStealth.java deleted file mode 100644 index 6e4e55d6a..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillStealth.java +++ /dev/null @@ -1,283 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.stealth.*; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.ProjectileLaunchEvent; - -import java.util.HashMap; -import java.util.Map; - -public class SkillStealth extends SimpleSkill { - private final Map cooldowns; - - public SkillStealth() { - super("stealth", Localizer.dLocalize("skill.stealth.icon")); - registerConfiguration(Config.class); - setColor(C.DARK_GRAY); - setInterval(1412); - setIcon(Material.WITHER_ROSE); - cooldowns = new HashMap<>(); - setDescription(Localizer.dLocalize("skill.stealth.description")); - setDisplayName(Localizer.dLocalize("skill.stealth.name")); - registerAdaptation(new StealthSpeed()); - registerAdaptation(new StealthSnatch()); - registerAdaptation(new StealthGhostArmor()); - registerAdaptation(new StealthSight()); - registerAdaptation(new StealthEnderVeil()); - registerAdaptation(new StealthSilentStep()); - registerAdaptation(new StealthShadowDecoy()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEATHER_LEGGINGS) - .key("challenge_sneak_1k") - .title(Localizer.dLocalize("advancement.challenge_sneak_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_sneak_1k.description")) - .model(CustomModel.get(Material.LEATHER_LEGGINGS, "advancement", "stealth", "challenge_sneak_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CHAINMAIL_LEGGINGS) - .key("challenge_sneak_5k") - .title(Localizer.dLocalize("advancement.challenge_sneak_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_sneak_5k.description")) - .model(CustomModel.get(Material.CHAINMAIL_LEGGINGS, "advancement", "stealth", "challenge_sneak_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_LEGGINGS) - .key("challenge_sneak_20k") - .title(Localizer.dLocalize("advancement.challenge_sneak_20k.title")) - .description(Localizer.dLocalize("advancement.challenge_sneak_20k.description")) - .model(CustomModel.get(Material.NETHERITE_LEGGINGS, "advancement", "stealth", "challenge_sneak_20k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_sneak_1k", "move.sneak", 1000, getConfig().challengeSneak1kReward); - registerMilestone("challenge_sneak_5k", "move.sneak", 5000, getConfig().challengeSneak5kReward); - registerMilestone("challenge_sneak_20k", "move.sneak", 20000, getConfig().challengeSneak20kReward); - - // Chain 2 - Stealth Damage While Sneaking - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.STONE_SWORD) - .key("challenge_stealth_dmg_500") - .title(Localizer.dLocalize("advancement.challenge_stealth_dmg_500.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_dmg_500.description")) - .model(CustomModel.get(Material.STONE_SWORD, "advancement", "stealth", "challenge_stealth_dmg_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_stealth_dmg_5k") - .title(Localizer.dLocalize("advancement.challenge_stealth_dmg_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_dmg_5k.description")) - .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "stealth", "challenge_stealth_dmg_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_stealth_dmg_500", "stealth.damage.sneaking", 500, getConfig().challengeStealthDmg500Reward); - registerMilestone("challenge_stealth_dmg_5k", "stealth.damage.sneaking", 5000, getConfig().challengeStealthDmg5kReward); - - // Chain 3 - Stealth Kills While Sneaking - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SKELETON_SKULL) - .key("challenge_stealth_kills_10") - .title(Localizer.dLocalize("advancement.challenge_stealth_kills_10.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_kills_10.description")) - .model(CustomModel.get(Material.SKELETON_SKULL, "advancement", "stealth", "challenge_stealth_kills_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.WITHER_ROSE) - .key("challenge_stealth_kills_100") - .title(Localizer.dLocalize("advancement.challenge_stealth_kills_100.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_kills_100.description")) - .model(CustomModel.get(Material.WITHER_ROSE, "advancement", "stealth", "challenge_stealth_kills_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_stealth_kills_10", "stealth.kills.sneaking", 10, getConfig().challengeStealthKills10Reward); - registerMilestone("challenge_stealth_kills_100", "stealth.kills.sneaking", 100, getConfig().challengeStealthKills100Reward); - - // Chain 4 - Stealth Time Spent Sneaking - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEATHER_BOOTS) - .key("challenge_stealth_time_1h") - .title(Localizer.dLocalize("advancement.challenge_stealth_time_1h.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_time_1h.description")) - .model(CustomModel.get(Material.LEATHER_BOOTS, "advancement", "stealth", "challenge_stealth_time_1h")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CHAINMAIL_BOOTS) - .key("challenge_stealth_time_10h") - .title(Localizer.dLocalize("advancement.challenge_stealth_time_10h.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_time_10h.description")) - .model(CustomModel.get(Material.CHAINMAIL_BOOTS, "advancement", "stealth", "challenge_stealth_time_10h")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_stealth_time_1h", "stealth.time", 3600, getConfig().challengeStealthTime1hReward); - registerMilestone("challenge_stealth_time_10h", "stealth.time", 36000, getConfig().challengeStealthTime10hReward); - - // Chain 5 - Stealth Arrows Fired While Sneaking - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BOW) - .key("challenge_stealth_arrows_50") - .title(Localizer.dLocalize("advancement.challenge_stealth_arrows_50.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_arrows_50.description")) - .model(CustomModel.get(Material.BOW, "advancement", "stealth", "challenge_stealth_arrows_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CROSSBOW) - .key("challenge_stealth_arrows_500") - .title(Localizer.dLocalize("advancement.challenge_stealth_arrows_500.title")) - .description(Localizer.dLocalize("advancement.challenge_stealth_arrows_500.description")) - .model(CustomModel.get(Material.CROSSBOW, "advancement", "stealth", "challenge_stealth_arrows_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_stealth_arrows_50", "stealth.arrows.sneaking", 50, getConfig().challengeStealthArrows50Reward); - registerMilestone("challenge_stealth_arrows_500", "stealth.arrows.sneaking", 500, getConfig().challengeStealthArrows500Reward); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p && p.isSneaking()) { - shouldReturnForPlayer(p, e, () -> { - getPlayer(p).getData().addStat("stealth.damage.sneaking", e.getDamage()); - xp(p, e.getEntity().getLocation(), e.getDamage() * getConfig().sneakCombatXPMultiplier); - }); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - if (e.getEntity().getKiller() == null) { - return; - } - Player p = e.getEntity().getKiller(); - if (p.isSneaking()) { - shouldReturnForPlayer(p, () -> { - getPlayer(p).getData().addStat("stealth.kills.sneaking", 1); - xp(p, e.getEntity().getLocation(), getConfig().sneakKillXP); - }); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(ProjectileLaunchEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getEntity().getShooter() instanceof Player p)) { - return; - } - if (p.isSneaking()) { - shouldReturnForPlayer(p, e, () -> { - getPlayer(p).getData().addStat("stealth.arrows.sneaking", 1); - }); - } - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - for (AdaptPlayer adaptPlayer : getServer().getOnlineAdaptPlayerSnapshot()) { - Player i = adaptPlayer.getPlayer(); - shouldReturnForPlayer(i, () -> { - checkStatTrackers(adaptPlayer); - if (i.isSneaking() && !i.isSwimming() && !i.isSprinting() && !i.isFlying() && !i.isGliding() && (i.getGameMode().equals(GameMode.SURVIVAL) || i.getGameMode().equals(GameMode.ADVENTURE))) { - xpSilent(i, getConfig().sneakXP, "stealth:sneak"); - adaptPlayer.getData().addStat("stealth.time", 1); - } - }); - - } - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sneak1k Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSneak1kReward = 1750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sneak5k Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSneak5kReward = 3500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sneak20k Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSneak20kReward = 8750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Sneak XP for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sneakXP = 0.4; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP multiplier for dealing damage while sneaking.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sneakCombatXPMultiplier = 3.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP awarded for killing while sneaking.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double sneakKillXP = 15; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Dmg 500 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeStealthDmg500Reward = 1500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Dmg 5k Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeStealthDmg5kReward = 5000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Kills 10 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeStealthKills10Reward = 1000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Kills 100 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeStealthKills100Reward = 5000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Time 1h Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeStealthTime1hReward = 2000; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Time 10h Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeStealthTime10hReward = 7500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Arrows 50 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeStealthArrows50Reward = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Stealth Arrows 500 Reward for the Stealth skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeStealthArrows500Reward = 5000; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillSwords.java b/src/main/java/com/volmit/adapt/content/skill/SkillSwords.java deleted file mode 100644 index e29e067c0..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillSwords.java +++ /dev/null @@ -1,272 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.sword.SwordsBloodyBlade; -import com.volmit.adapt.content.adaptation.sword.SwordsDualWield; -import com.volmit.adapt.content.adaptation.sword.SwordsCrimsonCyclone; -import com.volmit.adapt.content.adaptation.sword.SwordsExecutionersEdge; -import com.volmit.adapt.content.adaptation.sword.SwordsMachete; -import com.volmit.adapt.content.adaptation.sword.SwordsPoisonedBlade; -import com.volmit.adapt.content.adaptation.sword.SwordsRiposteWindow; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -public class SkillSwords extends SimpleSkill { - private final Map cooldowns; - - public SkillSwords() { - super("swords", Localizer.dLocalize("skill.swords.icon")); - registerConfiguration(Config.class); - setColor(C.YELLOW); - setDescription(Localizer.dLocalize("skill.swords.description")); - setDisplayName(Localizer.dLocalize("skill.swords.name")); - setInterval(2150); - setIcon(Material.DIAMOND_SWORD); - cooldowns = new HashMap<>(); - registerAdaptation(new SwordsMachete()); - registerAdaptation(new SwordsPoisonedBlade()); - registerAdaptation(new SwordsBloodyBlade()); - registerAdaptation(new SwordsDualWield()); - registerAdaptation(new SwordsExecutionersEdge()); - registerAdaptation(new SwordsRiposteWindow()); - registerAdaptation(new SwordsCrimsonCyclone()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.WOODEN_SWORD) - .key("challenge_sword_100") - .title(Localizer.dLocalize("advancement.challenge_sword_100.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_100.description")) - .model(CustomModel.get(Material.WOODEN_SWORD, "advancement", "swords", "challenge_sword_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_sword_1k") - .title(Localizer.dLocalize("advancement.challenge_sword_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_1k.description")) - .model(CustomModel.get(Material.IRON_SWORD, "advancement", "swords", "challenge_sword_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_sword_10k") - .title(Localizer.dLocalize("advancement.challenge_sword_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_10k.description")) - .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "swords", "challenge_sword_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_sword_100", "sword.hits", 100, getConfig().challengeSwordReward); - registerMilestone("challenge_sword_1k", "sword.hits", 1000, getConfig().challengeSwordReward * 2); - registerMilestone("challenge_sword_10k", "sword.hits", 10000, getConfig().challengeSwordReward * 5); - - // Chain 2 - sword.damage - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_SWORD) - .key("challenge_sword_dmg_1k") - .title(Localizer.dLocalize("advancement.challenge_sword_dmg_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_dmg_1k.description")) - .model(CustomModel.get(Material.GOLDEN_SWORD, "advancement", "swords", "challenge_sword_dmg_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_sword_dmg_10k") - .title(Localizer.dLocalize("advancement.challenge_sword_dmg_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_dmg_10k.description")) - .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "swords", "challenge_sword_dmg_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_sword_dmg_1k", "sword.damage", 1000, getConfig().challengeSwordDmgReward); - registerMilestone("challenge_sword_dmg_10k", "sword.damage", 10000, getConfig().challengeSwordDmgReward * 3); - - // Chain 3 - sword.kills - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.IRON_SWORD) - .key("challenge_sword_kills_50") - .title(Localizer.dLocalize("advancement.challenge_sword_kills_50.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_kills_50.description")) - .model(CustomModel.get(Material.IRON_SWORD, "advancement", "swords", "challenge_sword_kills_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_sword_kills_500") - .title(Localizer.dLocalize("advancement.challenge_sword_kills_500.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_kills_500.description")) - .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "swords", "challenge_sword_kills_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_sword_kills_50", "sword.kills", 50, getConfig().challengeSwordKillsReward); - registerMilestone("challenge_sword_kills_500", "sword.kills", 500, getConfig().challengeSwordKillsReward * 3); - - // Chain 4 - sword.critical - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_SWORD) - .key("challenge_sword_crit_50") - .title(Localizer.dLocalize("advancement.challenge_sword_crit_50.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_crit_50.description")) - .model(CustomModel.get(Material.GOLDEN_SWORD, "advancement", "swords", "challenge_sword_crit_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_sword_crit_500") - .title(Localizer.dLocalize("advancement.challenge_sword_crit_500.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_crit_500.description")) - .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "swords", "challenge_sword_crit_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_sword_crit_50", "sword.critical", 50, getConfig().challengeSwordCritReward); - registerMilestone("challenge_sword_crit_500", "sword.critical", 500, getConfig().challengeSwordCritReward * 3); - - // Chain 5 - sword.heavy.hits - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_sword_heavy_25") - .title(Localizer.dLocalize("advancement.challenge_sword_heavy_25.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_heavy_25.description")) - .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "swords", "challenge_sword_heavy_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHERITE_SWORD) - .key("challenge_sword_heavy_250") - .title(Localizer.dLocalize("advancement.challenge_sword_heavy_250.title")) - .description(Localizer.dLocalize("advancement.challenge_sword_heavy_250.description")) - .model(CustomModel.get(Material.NETHERITE_SWORD, "advancement", "swords", "challenge_sword_heavy_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_sword_heavy_25", "sword.heavy.hits", 25, getConfig().challengeSwordHeavyReward); - registerMilestone("challenge_sword_heavy_250", "sword.heavy.hits", 250, getConfig().challengeSwordHeavyReward * 3); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Player p && checkValidEntity(e.getEntity().getType())) { - shouldReturnForPlayer(p, e, () -> { - AdaptPlayer a = getPlayer(p); - ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); - if (isSword(hand)) { - getPlayer(p).getData().addStat("sword.hits", 1); - getPlayer(p).getData().addStat("sword.damage", e.getDamage()); - if (p.getFallDistance() > 0 && !p.isOnGround()) { - getPlayer(p).getData().addStat("sword.critical", 1); - } - if (e.getDamage() > 8) { - getPlayer(p).getData().addStat("sword.heavy.hits", 1); - } - if (!isOnCooldown(p)) { - setCooldown(p); - xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().damageXPMultiplier * e.getDamage()); - } - } - }); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - if (e.getEntity().getKiller() == null) { - return; - } - Player p = e.getEntity().getKiller(); - shouldReturnForPlayer(p, () -> { - ItemStack hand = p.getInventory().getItemInMainHand(); - if (isSword(hand)) { - getPlayer(p).getData().addStat("sword.kills", 1); - } - }); - } - - private boolean isOnCooldown(Player p) { - Long cooldown = cooldowns.get(p); - return cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis(); - } - - private void setCooldown(Player p) { - cooldowns.put(p, System.currentTimeMillis()); - } - - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage XPMultiplier for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageXPMultiplier = 4.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSwordReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Damage Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSwordDmgReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Kills Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSwordKillsReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Critical Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSwordCritReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Sword Heavy Hits Reward for the Swords skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeSwordHeavyReward = 500; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillTaming.java b/src/main/java/com/volmit/adapt/content/skill/SkillTaming.java deleted file mode 100644 index a34ead4d6..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillTaming.java +++ /dev/null @@ -1,292 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.taming.TamingDamage; -import com.volmit.adapt.content.adaptation.taming.TamingBeastRecall; -import com.volmit.adapt.content.adaptation.taming.TamingHealthBoost; -import com.volmit.adapt.content.adaptation.taming.TamingHealthRegeneration; -import com.volmit.adapt.content.adaptation.taming.TamingMountedTactics; -import com.volmit.adapt.content.adaptation.taming.TamingPackLeaderAura; -import com.volmit.adapt.content.adaptation.taming.TamingSharedPain; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.entity.Tameable; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityBreedEvent; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.EntityTameEvent; - -import java.util.HashMap; -import java.util.Map; - -public class SkillTaming extends SimpleSkill { - private final Map cooldowns; - - public SkillTaming() { - super("taming", Localizer.dLocalize("skill.taming.icon")); - registerConfiguration(Config.class); - setDescription(Localizer.dLocalize("skill.taming.description")); - setDisplayName(Localizer.dLocalize("skill.taming.name")); - setColor(C.GOLD); - setInterval(3480); - setIcon(Material.LEAD); - cooldowns = new HashMap<>(); - registerAdaptation(new TamingHealthBoost()); - registerAdaptation(new TamingDamage()); - registerAdaptation(new TamingHealthRegeneration()); - registerAdaptation(new TamingPackLeaderAura()); - registerAdaptation(new TamingBeastRecall()); - registerAdaptation(new TamingSharedPain()); - registerAdaptation(new TamingMountedTactics()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEAD) - .key("challenge_taming_10") - .title(Localizer.dLocalize("advancement.challenge_taming_10.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_10.description")) - .model(CustomModel.get(Material.LEAD, "advancement", "taming", "challenge_taming_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NAME_TAG) - .key("challenge_taming_50") - .title(Localizer.dLocalize("advancement.challenge_taming_50.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_50.description")) - .model(CustomModel.get(Material.NAME_TAG, "advancement", "taming", "challenge_taming_50")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GOLDEN_APPLE) - .key("challenge_taming_500") - .title(Localizer.dLocalize("advancement.challenge_taming_500.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_500.description")) - .model(CustomModel.get(Material.GOLDEN_APPLE, "advancement", "taming", "challenge_taming_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_pet_dmg_500") - .title(Localizer.dLocalize("advancement.challenge_pet_dmg_500.title")) - .description(Localizer.dLocalize("advancement.challenge_pet_dmg_500.description")) - .model(CustomModel.get(Material.BONE, "advancement", "taming", "challenge_pet_dmg_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.DIAMOND_SWORD) - .key("challenge_pet_dmg_5k") - .title(Localizer.dLocalize("advancement.challenge_pet_dmg_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_pet_dmg_5k.description")) - .model(CustomModel.get(Material.DIAMOND_SWORD, "advancement", "taming", "challenge_pet_dmg_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.LEAD) - .key("challenge_tamed_10") - .title(Localizer.dLocalize("advancement.challenge_tamed_10.title")) - .description(Localizer.dLocalize("advancement.challenge_tamed_10.description")) - .model(CustomModel.get(Material.LEAD, "advancement", "taming", "challenge_tamed_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NAME_TAG) - .key("challenge_tamed_100") - .title(Localizer.dLocalize("advancement.challenge_tamed_100.title")) - .description(Localizer.dLocalize("advancement.challenge_tamed_100.description")) - .model(CustomModel.get(Material.NAME_TAG, "advancement", "taming", "challenge_tamed_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_pet_kills_25") - .title(Localizer.dLocalize("advancement.challenge_pet_kills_25.title")) - .description(Localizer.dLocalize("advancement.challenge_pet_kills_25.description")) - .model(CustomModel.get(Material.BONE, "advancement", "taming", "challenge_pet_kills_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.GOLDEN_APPLE) - .key("challenge_pet_kills_250") - .title(Localizer.dLocalize("advancement.challenge_pet_kills_250.title")) - .description(Localizer.dLocalize("advancement.challenge_pet_kills_250.description")) - .model(CustomModel.get(Material.GOLDEN_APPLE, "advancement", "taming", "challenge_pet_kills_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.GOLDEN_CARROT) - .key("challenge_taming_2500") - .title(Localizer.dLocalize("advancement.challenge_taming_2500.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_2500.description")) - .model(CustomModel.get(Material.GOLDEN_CARROT, "advancement", "taming", "challenge_taming_2500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ENCHANTED_GOLDEN_APPLE) - .key("challenge_taming_25k") - .title(Localizer.dLocalize("advancement.challenge_taming_25k.title")) - .description(Localizer.dLocalize("advancement.challenge_taming_25k.description")) - .model(CustomModel.get(Material.ENCHANTED_GOLDEN_APPLE, "advancement", "taming", "challenge_taming_25k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_taming_10", "taming.bred", 10, getConfig().challengeTamingReward); - registerMilestone("challenge_taming_50", "taming.bred", 50, getConfig().challengeTamingReward * 2); - registerMilestone("challenge_taming_500", "taming.bred", 500, getConfig().challengeTamingReward * 5); - registerMilestone("challenge_pet_dmg_500", "taming.pet.damage", 500, getConfig().challengePetDmgReward); - registerMilestone("challenge_pet_dmg_5k", "taming.pet.damage", 5000, getConfig().challengePetDmgReward * 5); - registerMilestone("challenge_tamed_10", "taming.tamed", 10, getConfig().challengeTamedReward); - registerMilestone("challenge_tamed_100", "taming.tamed", 100, getConfig().challengeTamedReward * 5); - registerMilestone("challenge_pet_kills_25", "taming.pet.kills", 25, getConfig().challengePetKillsReward); - registerMilestone("challenge_pet_kills_250", "taming.pet.kills", 250, getConfig().challengePetKillsReward * 5); - registerMilestone("challenge_taming_2500", "taming.bred", 2500, getConfig().challengeTamingReward * 10); - registerMilestone("challenge_taming_25k", "taming.bred", 25000, getConfig().challengeTamingReward * 25); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityBreedEvent e) { - if (e.isCancelled()) { - return; - } - for (Entity nearby : e.getEntity().getNearbyEntities(15, 15, 15)) { - if (!(nearby instanceof Player p)) { - continue; - } - shouldReturnForPlayer(p, e, () -> { - getPlayer(p).getData().addStat("taming.bred", 1); - if (!isOnCooldown(p)) { - setCooldown(p); - xp(p, getConfig().tameXpBase); - } - }); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getDamager() instanceof Tameable tameable && tameable.isTamed() && tameable.getOwner() instanceof Player p) { - shouldReturnForPlayer(p, e, () -> { - getPlayer(p).getData().addStat("taming.pet.damage", e.getDamage()); - if (!isOnCooldown(p)) { - setCooldown(p); - xp(p, e.getEntity().getLocation(), e.getDamage() * getConfig().tameDamageXPMultiplier); - } - }); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityTameEvent e) { - if (e.isCancelled()) { - return; - } - if (e.getOwner() instanceof Player p) { - shouldReturnForPlayer(p, e, () -> { - getPlayer(p).getData().addStat("taming.tamed", 1); - xp(p, e.getEntity().getLocation(), getConfig().tameSuccessXP); - }); - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - if (e.getEntity().getKiller() != null) { - return; - } - if (e.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent damageEvent) { - if (damageEvent.getDamager() instanceof Tameable tameable && tameable.isTamed() && tameable.getOwner() instanceof Player p) { - shouldReturnForPlayer(p, () -> { - getPlayer(p).getData().addStat("taming.pet.kills", 1); - xp(p, e.getEntity().getLocation(), getConfig().petKillXP); - }); - } - } - } - - private boolean isOnCooldown(Player p) { - Long cooldown = cooldowns.get(p); - return cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis(); - } - - private void setCooldown(Player p) { - cooldowns.put(p, System.currentTimeMillis()); - } - - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Tame Xp Base for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double tameXpBase = 65; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Tame Damage XPMultiplier for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double tameDamageXPMultiplier = 8.0; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP awarded for successfully taming an animal.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double tameSuccessXP = 150; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP awarded when a pet kills a mob.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double petKillXP = 25; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Taming Reward for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeTamingReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Pet Damage Reward for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengePetDmgReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Tamed Reward for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeTamedReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Pet Kills Reward for the Taming skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengePetKillsReward = 500; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillTragOul.java b/src/main/java/com/volmit/adapt/content/skill/SkillTragOul.java deleted file mode 100644 index 13864ab04..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillTragOul.java +++ /dev/null @@ -1,329 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.api.world.PlayerAdaptation; -import com.volmit.adapt.api.world.PlayerSkillLine; -import com.volmit.adapt.content.adaptation.tragoul.TragoulGlobe; -import com.volmit.adapt.content.adaptation.tragoul.TragoulBloodPact; -import com.volmit.adapt.content.adaptation.tragoul.TragoulBoneHarvest; -import com.volmit.adapt.content.adaptation.tragoul.TragoulHealing; -import com.volmit.adapt.content.adaptation.tragoul.TragoulLance; -import com.volmit.adapt.content.adaptation.tragoul.TragoulThorns; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.SoundPlayer; -import com.volmit.adapt.util.reflect.registries.Particles; -import de.slikey.effectlib.effect.CloudEffect; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDamageEvent; -import org.bukkit.event.entity.PlayerDeathEvent; - -import java.util.HashMap; -import java.util.Map; - -public class SkillTragOul extends SimpleSkill { - private final Map cooldowns; - - public SkillTragOul() { - super("tragoul", Localizer.dLocalize("skill.tragoul.icon")); - registerConfiguration(Config.class); - setColor(C.AQUA); - setDescription(Localizer.dLocalize("skill.tragoul.description")); - setDisplayName(Localizer.dLocalize("skill.tragoul.name")); - setInterval(2755); - setIcon(Material.CRIMSON_ROOTS); - cooldowns = new HashMap<>(); - registerAdaptation(new TragoulThorns()); - registerAdaptation(new TragoulGlobe()); - registerAdaptation(new TragoulHealing()); - registerAdaptation(new TragoulLance()); - registerAdaptation(new TragoulBloodPact()); - registerAdaptation(new TragoulBoneHarvest()); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.CRIMSON_ROOTS) - .key("challenge_trag_1k") - .title(Localizer.dLocalize("advancement.challenge_trag_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_1k.description")) - .model(CustomModel.get(Material.CRIMSON_ROOTS, "advancement", "tragoul", "challenge_trag_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.CRIMSON_STEM) - .key("challenge_trag_10k") - .title(Localizer.dLocalize("advancement.challenge_trag_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_10k.description")) - .model(CustomModel.get(Material.CRIMSON_STEM, "advancement", "tragoul", "challenge_trag_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHER_STAR) - .key("challenge_trag_100k") - .title(Localizer.dLocalize("advancement.challenge_trag_100k.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_100k.description")) - .model(CustomModel.get(Material.NETHER_STAR, "advancement", "tragoul", "challenge_trag_100k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_trag_1k", "trag.damage", 1000, getConfig().challengeTragReward); - registerMilestone("challenge_trag_10k", "trag.damage", 10000, getConfig().challengeTragReward * 2); - registerMilestone("challenge_trag_100k", "trag.damage", 100000, getConfig().challengeTragReward * 5); - - // Chain 2 - Hits Received - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ROTTEN_FLESH) - .key("challenge_trag_hits_500") - .title(Localizer.dLocalize("advancement.challenge_trag_hits_500.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_hits_500.description")) - .model(CustomModel.get(Material.ROTTEN_FLESH, "advancement", "tragoul", "challenge_trag_hits_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BONE) - .key("challenge_trag_hits_5k") - .title(Localizer.dLocalize("advancement.challenge_trag_hits_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_hits_5k.description")) - .model(CustomModel.get(Material.BONE, "advancement", "tragoul", "challenge_trag_hits_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_trag_hits_500", "trag.hitsrecieved", 500, getConfig().challengeTragReward); - registerMilestone("challenge_trag_hits_5k", "trag.hitsrecieved", 5000, getConfig().challengeTragReward); - - // Chain 3 - Deaths - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SKELETON_SKULL) - .key("challenge_trag_deaths_10") - .title(Localizer.dLocalize("advancement.challenge_trag_deaths_10.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_deaths_10.description")) - .model(CustomModel.get(Material.SKELETON_SKULL, "advancement", "tragoul", "challenge_trag_deaths_10")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.WITHER_SKELETON_SKULL) - .key("challenge_trag_deaths_100") - .title(Localizer.dLocalize("advancement.challenge_trag_deaths_100.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_deaths_100.description")) - .model(CustomModel.get(Material.WITHER_SKELETON_SKULL, "advancement", "tragoul", "challenge_trag_deaths_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_trag_deaths_10", "trag.deaths", 10, getConfig().challengeTragReward); - registerMilestone("challenge_trag_deaths_100", "trag.deaths", 100, getConfig().challengeTragReward); - - // Chain 4 - Fire Damage - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.BLAZE_POWDER) - .key("challenge_trag_fire_500") - .title(Localizer.dLocalize("advancement.challenge_trag_fire_500.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_fire_500.description")) - .model(CustomModel.get(Material.BLAZE_POWDER, "advancement", "tragoul", "challenge_trag_fire_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.MAGMA_CREAM) - .key("challenge_trag_fire_5k") - .title(Localizer.dLocalize("advancement.challenge_trag_fire_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_fire_5k.description")) - .model(CustomModel.get(Material.MAGMA_CREAM, "advancement", "tragoul", "challenge_trag_fire_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_trag_fire_500", "trag.fire.damage", 500, getConfig().challengeTragReward); - registerMilestone("challenge_trag_fire_5k", "trag.fire.damage", 5000, getConfig().challengeTragReward); - - // Chain 5 - Fall Damage - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FEATHER) - .key("challenge_trag_fall_500") - .title(Localizer.dLocalize("advancement.challenge_trag_fall_500.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_fall_500.description")) - .model(CustomModel.get(Material.FEATHER, "advancement", "tragoul", "challenge_trag_fall_500")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.HAY_BLOCK) - .key("challenge_trag_fall_5k") - .title(Localizer.dLocalize("advancement.challenge_trag_fall_5k.title")) - .description(Localizer.dLocalize("advancement.challenge_trag_fall_5k.description")) - .model(CustomModel.get(Material.HAY_BLOCK, "advancement", "tragoul", "challenge_trag_fall_5k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_trag_fall_500", "trag.fall.damage", 500, getConfig().challengeTragReward); - registerMilestone("challenge_trag_fall_5k", "trag.fall.damage", 5000, getConfig().challengeTragReward); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getEntity() instanceof Player p)) { - return; - } - shouldReturnForPlayer(p, e, () -> { - if (e.getEntity().isDead() - || e.getEntity().isInvulnerable() - || p.isInvulnerable() - || p.isBlocking() - || !checkValidEntity(e.getEntity().getType())) { - return; - } - AdaptPlayer a = getPlayer(p); - a.getData().addStat("trag.hitsrecieved", 1); - a.getData().addStat("trag.damage", e.getDamage()); - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - xp(a.getPlayer(), getConfig().damageReceivedXpMultiplier * e.getDamage()); - if (p.getHealth() - e.getFinalDamage() > 0 && p.getHealth() - e.getFinalDamage() <= 8) { - xp(a.getPlayer(), getConfig().lowHealthSurvivalXP); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(PlayerDeathEvent e) { - Player p = e.getEntity(); - shouldReturnForPlayer(p, () -> { - AdaptPlayer a = getPlayer(p); - a.getData().addStat("trag.deaths", 1); - if (AdaptConfig.get().isHardcoreResetOnPlayerDeath()) { - Adapt.info("Resetting " + p.getName() + "'s skills due to death"); - a.delete(p.getUniqueId()); - return; - } - if (getConfig().takeAwaySkillsOnDeath) { - if (areParticlesEnabled()) { - CloudEffect ce = new CloudEffect(Adapt.instance.adaptEffectManager); - ce.mainParticle = Particle.ASH; - ce.cloudParticle = Particles.REDSTONE; - ce.duration = 10000; - ce.iterations = 1000; - ce.setEntity(p); - ce.start(); - } - - if (this.hasBlacklistPermission(p, this)) { - return; - } - - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.ENTITY_BLAZE_DEATH, 1f, 1f); - - PlayerSkillLine tragoul = a.getData().getSkillLineNullable("tragoul"); - if (tragoul != null) { - double xp = tragoul.getXp(); - if (xp > getConfig().deathXpLoss) { - xp(p, getConfig().deathXpLoss); - } else { - tragoul.setXp(0); - } - tragoul.setLastXP(xp); - - for (PlayerAdaptation adapt : tragoul.getAdaptations().values()) { - adapt.setLevel(Math.max(adapt.getLevel() - 1, 0)); - } - - recalcTotalExp(p); - a.getData().pruneAdaptationsForPowerBudget(); - } - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getEntity() instanceof Player p)) { - return; - } - shouldReturnForPlayer(p, e, () -> { - EntityDamageEvent.DamageCause cause = e.getCause(); - if (cause == EntityDamageEvent.DamageCause.FALL) { - getPlayer(p).getData().addStat("trag.fall.damage", e.getDamage()); - } else if (cause == EntityDamageEvent.DamageCause.FIRE - || cause == EntityDamageEvent.DamageCause.FIRE_TICK - || cause == EntityDamageEvent.DamageCause.LAVA) { - getPlayer(p).getData().addStat("trag.fire.damage", e.getDamage()); - } - }); - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - checkStatTrackersForOnlinePlayers(); - } - - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Death Xp Loss for the Trag Oul skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - public double deathXpLoss = -250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Take Away Skills On Death for the Trag Oul skill.", impact = "True enables this behavior and false disables it.") - boolean takeAwaySkillsOnDeath = false; - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Show Particles for the Trag Oul skill.", impact = "True enables this behavior and false disables it.") - boolean showParticles = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Trag Oul skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 450; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage Received Xp Multiplier for the Trag Oul skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageReceivedXpMultiplier = 4.8; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls XP bonus for surviving a hit below 4 hearts.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double lowHealthSurvivalXP = 28; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Trag Reward for the Trag Oul skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeTragReward = 500; - } -} diff --git a/src/main/java/com/volmit/adapt/content/skill/SkillUnarmed.java b/src/main/java/com/volmit/adapt/content/skill/SkillUnarmed.java deleted file mode 100644 index 789126411..000000000 --- a/src/main/java/com/volmit/adapt/content/skill/SkillUnarmed.java +++ /dev/null @@ -1,275 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.content.skill; - -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.volmit.adapt.api.advancement.AdaptAdvancement; -import com.volmit.adapt.api.advancement.AdvancementVisibility; -import com.volmit.adapt.api.skill.SimpleSkill; -import com.volmit.adapt.api.world.AdaptPlayer; -import com.volmit.adapt.api.world.AdaptStatTracker; -import com.volmit.adapt.content.adaptation.unarmed.UnarmedGlassCannon; -import com.volmit.adapt.content.adaptation.unarmed.UnarmedBatteringCharge; -import com.volmit.adapt.content.adaptation.unarmed.UnarmedComboChain; -import com.volmit.adapt.content.adaptation.unarmed.UnarmedPower; -import com.volmit.adapt.content.adaptation.unarmed.UnarmedSuckerPunch; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.Localizer; -import lombok.NoArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.entity.EntityDamageByEntityEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -public class SkillUnarmed extends SimpleSkill { - private final Map cooldowns; - - public SkillUnarmed() { - super("unarmed", Localizer.dLocalize("skill.unarmed.icon")); - registerConfiguration(Config.class); - cooldowns = new HashMap<>(); - setColor(C.YELLOW); - setDescription(Localizer.dLocalize("skill.unarmed.description")); - setDisplayName(Localizer.dLocalize("skill.unarmed.name")); - setInterval(2579); - registerAdaptation(new UnarmedSuckerPunch()); - registerAdaptation(new UnarmedPower()); - registerAdaptation(new UnarmedGlassCannon()); - registerAdaptation(new UnarmedBatteringCharge()); - registerAdaptation(new UnarmedComboChain()); - setIcon(Material.FIRE_CHARGE); - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FIRE_CHARGE) - .key("challenge_unarmed_100") - .title(Localizer.dLocalize("advancement.challenge_unarmed_100.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_100.description")) - .model(CustomModel.get(Material.FIRE_CHARGE, "advancement", "unarmed", "challenge_unarmed_100")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BLAZE_POWDER) - .key("challenge_unarmed_1k") - .title(Localizer.dLocalize("advancement.challenge_unarmed_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_1k.description")) - .model(CustomModel.get(Material.BLAZE_POWDER, "advancement", "unarmed", "challenge_unarmed_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.NETHER_STAR) - .key("challenge_unarmed_10k") - .title(Localizer.dLocalize("advancement.challenge_unarmed_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_10k.description")) - .model(CustomModel.get(Material.NETHER_STAR, "advancement", "unarmed", "challenge_unarmed_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()) - .build()); - registerMilestone("challenge_unarmed_100", "unarmed.hits", 100, getConfig().challengeUnarmedReward); - registerMilestone("challenge_unarmed_1k", "unarmed.hits", 1000, getConfig().challengeUnarmedReward * 2); - registerMilestone("challenge_unarmed_10k", "unarmed.hits", 10000, getConfig().challengeUnarmedReward * 5); - - // Chain 2 - Unarmed Damage - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.FIRE_CHARGE) - .key("challenge_unarmed_dmg_1k") - .title(Localizer.dLocalize("advancement.challenge_unarmed_dmg_1k.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_dmg_1k.description")) - .model(CustomModel.get(Material.FIRE_CHARGE, "advancement", "unarmed", "challenge_unarmed_dmg_1k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.BLAZE_ROD) - .key("challenge_unarmed_dmg_10k") - .title(Localizer.dLocalize("advancement.challenge_unarmed_dmg_10k.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_dmg_10k.description")) - .model(CustomModel.get(Material.BLAZE_ROD, "advancement", "unarmed", "challenge_unarmed_dmg_10k")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_unarmed_dmg_1k", "unarmed.damage", 1000, getConfig().challengeUnarmedDmgReward); - registerMilestone("challenge_unarmed_dmg_10k", "unarmed.damage", 10000, getConfig().challengeUnarmedDmgReward * 3); - - // Chain 3 - Unarmed Kills - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.ROTTEN_FLESH) - .key("challenge_unarmed_kills_25") - .title(Localizer.dLocalize("advancement.challenge_unarmed_kills_25.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_kills_25.description")) - .model(CustomModel.get(Material.ROTTEN_FLESH, "advancement", "unarmed", "challenge_unarmed_kills_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.ZOMBIE_HEAD) - .key("challenge_unarmed_kills_250") - .title(Localizer.dLocalize("advancement.challenge_unarmed_kills_250.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_kills_250.description")) - .model(CustomModel.get(Material.ZOMBIE_HEAD, "advancement", "unarmed", "challenge_unarmed_kills_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_unarmed_kills_25", "unarmed.kills", 25, getConfig().challengeUnarmedKillsReward); - registerMilestone("challenge_unarmed_kills_250", "unarmed.kills", 250, getConfig().challengeUnarmedKillsReward * 3); - - // Chain 4 - Unarmed Criticals - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.SUGAR) - .key("challenge_unarmed_crit_25") - .title(Localizer.dLocalize("advancement.challenge_unarmed_crit_25.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_crit_25.description")) - .model(CustomModel.get(Material.SUGAR, "advancement", "unarmed", "challenge_unarmed_crit_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.FERMENTED_SPIDER_EYE) - .key("challenge_unarmed_crit_250") - .title(Localizer.dLocalize("advancement.challenge_unarmed_crit_250.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_crit_250.description")) - .model(CustomModel.get(Material.FERMENTED_SPIDER_EYE, "advancement", "unarmed", "challenge_unarmed_crit_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_unarmed_crit_25", "unarmed.critical", 25, getConfig().challengeUnarmedCritReward); - registerMilestone("challenge_unarmed_crit_250", "unarmed.critical", 250, getConfig().challengeUnarmedCritReward * 3); - - // Chain 5 - Unarmed Heavy Hits - registerAdvancement(AdaptAdvancement.builder() - .icon(Material.TNT) - .key("challenge_unarmed_heavy_25") - .title(Localizer.dLocalize("advancement.challenge_unarmed_heavy_25.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_heavy_25.description")) - .model(CustomModel.get(Material.TNT, "advancement", "unarmed", "challenge_unarmed_heavy_25")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .child(AdaptAdvancement.builder() - .icon(Material.END_CRYSTAL) - .key("challenge_unarmed_heavy_250") - .title(Localizer.dLocalize("advancement.challenge_unarmed_heavy_250.title")) - .description(Localizer.dLocalize("advancement.challenge_unarmed_heavy_250.description")) - .model(CustomModel.get(Material.END_CRYSTAL, "advancement", "unarmed", "challenge_unarmed_heavy_250")) - .frame(AdaptAdvancementFrame.CHALLENGE) - .visibility(AdvancementVisibility.PARENT_GRANTED) - .build()) - .build()); - registerMilestone("challenge_unarmed_heavy_25", "unarmed.heavy", 25, getConfig().challengeUnarmedHeavyReward); - registerMilestone("challenge_unarmed_heavy_250", "unarmed.heavy", 250, getConfig().challengeUnarmedHeavyReward * 3); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDamageByEntityEvent e) { - if (e.isCancelled()) { - return; - } - if (!(e.getDamager() instanceof Player p)) { - return; - } - - shouldReturnForPlayer(p, e, () -> { - if (e.getEntity().isDead() - || e.getEntity().isInvulnerable() - || p.isInvulnerable()) { - return; - } - - if (!checkValidEntity(e.getEntity().getType())) { - return; - } - - AdaptPlayer a = getPlayer(p); - ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); - - if (!isMelee(hand)) { - a.getData().addStat("unarmed.hits", 1); - a.getData().addStat("unarmed.damage", e.getDamage()); - if (p.getFallDistance() > 0 && !p.isOnGround()) { - a.getData().addStat("unarmed.critical", 1); - } - if (e.getDamage() > 6) { - a.getData().addStat("unarmed.heavy", 1); - } - Long cooldown = cooldowns.get(p); - if (cooldown != null && cooldown + getConfig().cooldownDelay > System.currentTimeMillis()) - return; - cooldowns.put(p, System.currentTimeMillis()); - xp(a.getPlayer(), e.getEntity().getLocation(), getConfig().damageXPMultiplier * e.getDamage()); - } - }); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(EntityDeathEvent e) { - if (e.getEntity().getKiller() == null) { - return; - } - Player p = e.getEntity().getKiller(); - - shouldReturnForPlayer(p, () -> { - AdaptPlayer a = getPlayer(p); - ItemStack hand = a.getPlayer().getInventory().getItemInMainHand(); - - if (!isMelee(hand)) { - a.getData().addStat("unarmed.kills", 1); - } - }); - } - - @Override - public void onTick() { - if (!this.isEnabled()) { - return; - } - checkStatTrackersForOnlinePlayers(); - } - - @Override - public boolean isEnabled() { - return getConfig().enabled; - } - - @NoArgsConstructor - protected static class Config { - @com.volmit.adapt.util.config.ConfigDoc(value = "Enables or disables this feature.", impact = "Set to false to disable behavior without uninstalling files.") - boolean enabled = true; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Damage XPMultiplier for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double damageXPMultiplier = 4.5; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Cooldown Delay for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - long cooldownDelay = 1250; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeUnarmedReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Damage Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeUnarmedDmgReward = 500; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Kills Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeUnarmedKillsReward = 750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Critical Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeUnarmedCritReward = 750; - @com.volmit.adapt.util.config.ConfigDoc(value = "Controls Challenge Unarmed Heavy Reward for the Unarmed skill.", impact = "Higher values usually increase intensity, limits, or frequency; lower values reduce it.") - double challengeUnarmedHeavyReward = 750; - } -} diff --git a/src/main/java/com/volmit/adapt/nms/NMS.java b/src/main/java/com/volmit/adapt/nms/NMS.java deleted file mode 100644 index c5b3928b0..000000000 --- a/src/main/java/com/volmit/adapt/nms/NMS.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.volmit.adapt.nms; - -import org.bukkit.inventory.ItemStack; -import org.bukkit.util.io.BukkitObjectInputStream; -import org.bukkit.util.io.BukkitObjectOutputStream; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Base64; - -public class NMS { - - public static String serializeStack(ItemStack is) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (BukkitObjectOutputStream oos = new BukkitObjectOutputStream(out)){ - oos.writeObject(is); - return Base64.getUrlEncoder().encodeToString(out.toByteArray()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static ItemStack deserializeStack(String s) { - ByteArrayInputStream in = new ByteArrayInputStream(Base64.getUrlDecoder().decode(s)); - try (BukkitObjectInputStream ois = new BukkitObjectInputStream(in)) { - return (ItemStack) ois.readObject(); - } catch (IOException | ClassNotFoundException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/java/com/volmit/adapt/service/CommandSVC.java b/src/main/java/com/volmit/adapt/service/CommandSVC.java deleted file mode 100644 index 39bb1ae39..000000000 --- a/src/main/java/com/volmit/adapt/service/CommandSVC.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.volmit.adapt.service; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.command.CommandAdapt; -import com.volmit.adapt.util.AdaptService; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.cache.AtomicCache; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.decree.DecreeSystem; -import com.volmit.adapt.util.decree.virtual.VirtualDecreeCommand; -import org.bukkit.command.PluginCommand; -import org.bukkit.event.EventHandler; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.server.ServerCommandEvent; - -import java.util.Locale; -import java.util.concurrent.CompletableFuture; - -public class CommandSVC implements AdaptService, DecreeSystem { - private final KMap> futures = new KMap<>(); - private final transient AtomicCache commandCache = new AtomicCache<>(); - private CompletableFuture consoleFuture = null; - - @Override - public void onEnable() { - Adapt.verbose("Initializing Commands..."); - PluginCommand command = Adapt.instance.getCommand("adapt"); - if (command == null) { - Adapt.warn("Failed to find command 'adapt'"); - return; - } - command.setExecutor(this); - J.a(() -> getRoot().cacheAll()); - } - - @Override - public void onDisable() { - - } - - @EventHandler - public void on(PlayerCommandPreprocessEvent e) { - String msg = e.getMessage().startsWith("/") ? e.getMessage().substring(1) : e.getMessage(); - - if (msg.startsWith("adaptdecree ")) { - String[] args = msg.split("\\Q \\E"); - CompletableFuture future = futures.get(args[1]); - - if (future != null) { - future.complete(args[2]); - e.setCancelled(true); - } - } - } - - @EventHandler - public void on(ServerCommandEvent e) { - if (consoleFuture != null && !consoleFuture.isCancelled() && !consoleFuture.isDone()) { - if (!e.getCommand().contains(" ")) { - String pick = e.getCommand().trim().toLowerCase(Locale.ROOT); - consoleFuture.complete(pick); - e.setCancelled(true); - } - } - } - - @Override - public VirtualDecreeCommand getRoot() { - return commandCache.aquireNastyPrint(() -> VirtualDecreeCommand.createRoot(new CommandAdapt())); - } - - public void post(String password, CompletableFuture future) { - futures.put(password, future); - } - - public void postConsole(CompletableFuture future) { - consoleFuture = future; - } -} diff --git a/src/main/java/com/volmit/adapt/service/ConfigInputSVC.java b/src/main/java/com/volmit/adapt/service/ConfigInputSVC.java deleted file mode 100644 index 745e13f03..000000000 --- a/src/main/java/com/volmit/adapt/service/ConfigInputSVC.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.volmit.adapt.service; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.content.gui.ConfigGui; -import com.volmit.adapt.util.AdaptService; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.J; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerQuitEvent; - -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -public class ConfigInputSVC implements AdaptService { - private static final long SESSION_TIMEOUT_MS = 45_000L; - - private final Map sessions = new ConcurrentHashMap<>(); - private int cleanupTaskId = -1; - - @Override - public void onEnable() { - cleanupTaskId = J.sr(this::cleanupExpiredSessions, 20); - } - - @Override - public void onDisable() { - if (cleanupTaskId != -1) { - J.csr(cleanupTaskId); - cleanupTaskId = -1; - } - sessions.clear(); - } - - public void beginSession(Player player, String valuePath, String returnSectionPath, int returnPage, Class targetType, String label) { - if (player == null) { - return; - } - - PendingInput pending = new PendingInput( - player.getUniqueId(), - valuePath, - returnSectionPath == null ? "" : returnSectionPath, - Math.max(0, returnPage), - targetType, - label == null ? valuePath : label, - System.currentTimeMillis() + SESSION_TIMEOUT_MS - ); - sessions.put(player.getUniqueId(), pending); - - ConfigGui.suppressClose(player); - player.closeInventory(); - Adapt.messagePlayer(player, C.AQUA + "Enter value for " + C.WHITE + pending.label()); - Adapt.messagePlayer(player, C.AQUA + "Path: " + C.WHITE + pending.valuePath()); - Adapt.messagePlayer(player, C.AQUA + "Expected type: " + C.WHITE + ConfigGui.typeName(targetType)); - Adapt.messagePlayer(player, C.GRAY + "Type " + C.WHITE + "cancel" + C.GRAY + " to abort."); - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onAsyncChat(AsyncPlayerChatEvent event) { - Player player = event.getPlayer(); - PendingInput pending = sessions.get(player.getUniqueId()); - if (pending == null) { - return; - } - - event.setCancelled(true); - - if (pending.isExpired()) { - sessions.remove(player.getUniqueId()); - J.s(() -> { - Adapt.messagePlayer(player, C.RED + "Config input timed out."); - ConfigGui.open(player, pending.returnSectionPath(), pending.returnPage()); - }); - return; - } - - String message = event.getMessage() == null ? "" : event.getMessage(); - if (message.equalsIgnoreCase("cancel")) { - sessions.remove(player.getUniqueId()); - J.s(() -> { - Adapt.messagePlayer(player, C.YELLOW + "Config edit cancelled."); - ConfigGui.open(player, pending.returnSectionPath(), pending.returnPage()); - }); - return; - } - - ConfigGui.ParseResult parsed = ConfigGui.parseInputValue(pending.targetType(), message); - if (!parsed.success()) { - J.s(() -> { - Adapt.messagePlayer(player, C.RED + parsed.error()); - Adapt.messagePlayer(player, C.GRAY + "Try again or type " + C.WHITE + "cancel"); - }); - return; - } - - sessions.remove(player.getUniqueId()); - Object value = parsed.value(); - J.s(() -> { - ConfigGui.confirmAndApply(player, pending.returnSectionPath(), pending.returnPage(), pending.valuePath(), value); - }); - } - - @EventHandler - public void onQuit(PlayerQuitEvent event) { - sessions.remove(event.getPlayer().getUniqueId()); - } - - private void cleanupExpiredSessions() { - long now = System.currentTimeMillis(); - sessions.entrySet().removeIf(e -> e.getValue().expiresAt() <= now); - } - - private record PendingInput( - UUID playerId, - String valuePath, - String returnSectionPath, - int returnPage, - Class targetType, - String label, - long expiresAt - ) { - private boolean isExpired() { - return System.currentTimeMillis() >= expiresAt; - } - } -} diff --git a/src/main/java/com/volmit/adapt/service/HotloadSVC.java b/src/main/java/com/volmit/adapt/service/HotloadSVC.java deleted file mode 100644 index 473a0ba48..000000000 --- a/src/main/java/com/volmit/adapt/service/HotloadSVC.java +++ /dev/null @@ -1,687 +0,0 @@ -package com.volmit.adapt.service; - -import art.arcane.amulet.io.FolderWatcher; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.adaptation.SimpleAdaptation; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.skill.SkillRegistry; -import com.volmit.adapt.api.tick.TickedObject; -import com.volmit.adapt.content.gui.ConfigGui; -import com.volmit.adapt.content.gui.SkillsGui; -import com.volmit.adapt.util.AdaptService; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.ConfigRewriteReporter; -import com.volmit.adapt.util.CustomModel; -import com.volmit.adapt.util.IO; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.Json; -import com.volmit.adapt.util.Localizer; -import com.volmit.adapt.util.Window; -import com.volmit.adapt.util.config.ConfigFileSupport; -import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.entity.Player; - -import java.io.File; -import java.nio.file.Files; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; - -import static com.volmit.adapt.util.decree.context.AdaptationListingHandler.initializeAdaptationListings; - -public class HotloadSVC implements AdaptService { - private static final long WATCHER_POLL_MS = 500; - private static final int MAX_DIFF_MESSAGES_PER_FILE = 12; - private static final String MISSING = ""; - private static final String REMOVED = ""; - - private FolderWatcher configWatcher; - private TickedObject configTicker; - private File adaptFolder; - private File adaptConfigFile; - private File adaptConfigLegacyFile; - private File modelsFile; - private File modelsLegacyFile; - private File skillsFolder; - private File adaptationsFolder; - private final Map knownSignatures = new HashMap<>(); - private final Map knownContents = new HashMap<>(); - - @Override - public void onEnable() { - adaptFolder = Adapt.instance.getDataFolder("adapt"); - adaptConfigFile = Adapt.instance.getDataFile("adapt", "adapt.toml"); - adaptConfigLegacyFile = Adapt.instance.getDataFile("adapt", "adapt.json"); - modelsFile = Adapt.instance.getDataFile("adapt", "models.toml"); - modelsLegacyFile = Adapt.instance.getDataFile("adapt", "models.json"); - skillsFolder = Adapt.instance.getDataFolder("adapt", "skills"); - adaptationsFolder = Adapt.instance.getDataFolder("adapt", "adaptations"); - configWatcher = new FolderWatcher(adaptFolder); - configWatcher.checkModified(); - primeKnownSnapshots(); - Adapt.info("Config hotload watcher enabled for all /adapt/*.json and /adapt/*.toml files."); - - configTicker = new TickedObject("config", "config-hotload-service", WATCHER_POLL_MS) { - @Override - public void onTick() { - pollConfigChanges(); - } - }; - } - - @Override - public void onDisable() { - if (configTicker != null) { - configTicker.unregister(); - configTicker = null; - } - configWatcher = null; - knownSignatures.clear(); - knownContents.clear(); - } - - private void pollConfigChanges() { - if (configWatcher == null) { - return; - } - - Set touched = new HashSet<>(); - if (configWatcher.checkModified()) { - touched.addAll(configWatcher.getCreated()); - touched.addAll(configWatcher.getChanged()); - touched.addAll(configWatcher.getDeleted()); - } - touched.addAll(scanForMissedChanges()); - if (touched.isEmpty()) { - return; - } - - boolean refreshedSomething = false; - for (File file : touched) { - if (file == null || !ConfigFileSupport.isSupportedConfigFile(file)) { - continue; - } - - refreshedSomething = processConfigChange(file) || refreshedSomething; - } - - if (refreshedSomething) { - refreshOpenAdaptGuis(); - } - } - - private boolean processConfigChange(File file) { - String path = file.getAbsolutePath(); - String before = knownContents.get(path); - String nowRaw = readFileContent(file); - String now = normalizeContent(nowRaw); - - if (Objects.equals(before, now)) { - updateKnownSnapshot(file, now); - return false; - } - - boolean applied = applyConfigChange(file); - String after = normalizeContent(readFileContent(file)); - updateKnownSnapshot(file, after); - if (!applied) { - return false; - } - - if (isModelsConfigFile(file)) { - return true; - } - - notifyOps(file, before, after); - return true; - } - - private boolean applyConfigChange(File file) { - try { - if (isShadowedLegacyJson(file)) { - if (!isModelsConfigFile(file)) { - Adapt.verbose("Ignoring legacy json hotload because canonical toml exists: " + file.getPath()); - } - return false; - } - - if (isAdaptConfigFile(file)) { - boolean ok = AdaptConfig.reload(); - if (ok) { - refreshGlobalRuntimeSettings(); - } else { - Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid config."); - } - return ok; - } - - if (isSkillConfigFile(file)) { - return reloadSkillConfig(file); - } - - if (isAdaptationConfigFile(file)) { - return reloadAdaptationConfig(file); - } - - if (isModelsConfigFile(file)) { - return reloadModelsConfig(file); - } - - return validateAndCanonicalizeConfig(file); - } catch (Throwable e) { - Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid config: " + e.getMessage()); - return false; - } - } - - private boolean reloadSkillConfig(File file) { - String skillName = toConfigName(file.getName()); - if (skillName == null) { - return false; - } - - SkillRegistry registry = Adapt.instance.getAdaptServer().getSkillRegistry(); - boolean ok = registry.hotReloadSkillConfig(skillName); - if (ok) { - initializeAdaptationListings(); - } else { - Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid skill config."); - } - return ok; - } - - private boolean reloadAdaptationConfig(File file) { - String adaptationName = toConfigName(file.getName()); - if (adaptationName == null) { - return false; - } - - for (Skill skill : Adapt.instance.getAdaptServer().getSkillRegistry().getAllSkills()) { - for (Adaptation adaptation : skill.getAdaptations()) { - if (!adaptation.getName().equalsIgnoreCase(adaptationName)) { - continue; - } - - if (!(adaptation instanceof SimpleAdaptation simpleAdaptation)) { - return false; - } - - boolean ok = simpleAdaptation.reloadConfigFromDisk(false); - if (ok) { - Adapt.instance.getAdaptServer().getSkillRegistry().refreshRecipes(skill); - initializeAdaptationListings(); - } else { - Adapt.warn("Skipped hotload for " + file.getPath() + " due to invalid adaptation config."); - } - return ok; - } - } - - return validateAndCanonicalizeConfig(file); - } - - private boolean reloadModelsConfig(File file) { - return CustomModel.reloadFromDisk(true); - } - - private void refreshGlobalRuntimeSettings() { - Adapt.wordKey.clear(); - if (AdaptConfig.get().isAutoUpdateLanguage()) { - Localizer.updateLanguageFile(); - } - - if (AdaptConfig.get().isCustomModels()) { - CustomModel.reloadFromDisk(true); - } else { - CustomModel.clear(); - } - } - - private boolean validateAndCanonicalizeConfig(File file) { - if (file == null || !file.exists() || !file.isFile()) { - return true; - } - - try { - String raw = readFileContent(file); - JsonElement parsed = parseStructured(raw, file); - if (parsed == null) { - return false; - } - - if (ConfigFileSupport.isTomlFile(file)) { - return true; - } - - String canonical = Json.toJson(parsed, true); - if (!normalizeContent(raw).equals(normalizeContent(canonical))) { - ConfigRewriteReporter.reportRewrite(file, "hotload", raw, canonical); - IO.writeAll(file, canonical); - } - return true; - } catch (Throwable e) { - return false; - } - } - - private boolean isAdaptConfigFile(File file) { - return sameFile(file, adaptConfigFile) || sameFile(file, adaptConfigLegacyFile); - } - - private boolean isModelsConfigFile(File file) { - return sameFile(file, modelsFile) || sameFile(file, modelsLegacyFile); - } - - private boolean isSkillConfigFile(File file) { - return isDirectChild(skillsFolder, file) && ConfigFileSupport.isSupportedConfigFile(file); - } - - private boolean isAdaptationConfigFile(File file) { - return isDirectChild(adaptationsFolder, file) && ConfigFileSupport.isSupportedConfigFile(file); - } - - private boolean isDirectChild(File parent, File child) { - if (parent == null || child == null) { - return false; - } - - File childParent = child.getParentFile(); - return childParent != null && sameFile(parent, childParent); - } - - private boolean sameFile(File a, File b) { - return a != null && b != null && a.getAbsoluteFile().equals(b.getAbsoluteFile()); - } - - private boolean isShadowedLegacyJson(File file) { - if (file == null || !file.getName().toLowerCase(Locale.ROOT).endsWith(".json")) { - return false; - } - - if (sameFile(file, adaptConfigLegacyFile) && adaptConfigFile != null && adaptConfigFile.exists()) { - return true; - } - if (sameFile(file, modelsLegacyFile) && modelsFile != null && modelsFile.exists()) { - return true; - } - if ((isSkillConfigFile(file) || isAdaptationConfigFile(file)) && ConfigFileSupport.toTomlFile(file).exists()) { - return true; - } - - return false; - } - - private String toConfigName(String fileName) { - return ConfigFileSupport.configNameFromFileName(fileName); - } - - private void primeKnownSnapshots() { - knownSignatures.clear(); - knownContents.clear(); - for (File file : listKnownConfigFiles()) { - updateKnownSnapshot(file, normalizeContent(readFileContent(file))); - } - } - - private Set scanForMissedChanges() { - Set changed = new HashSet<>(); - Set seenPaths = new HashSet<>(); - for (File file : listKnownConfigFiles()) { - String path = file.getAbsolutePath(); - seenPaths.add(path); - String now = signature(file); - String previous = knownSignatures.put(path, now); - if (previous != null && !previous.equals(now)) { - changed.add(file); - } - } - - for (String path : new HashSet<>(knownSignatures.keySet())) { - if (seenPaths.contains(path)) { - continue; - } - - String previous = knownSignatures.put(path, "missing"); - if (previous != null && !"missing".equals(previous)) { - changed.add(new File(path)); - } - } - - return changed; - } - - private List listKnownConfigFiles() { - List files = new ArrayList<>(); - Set added = new HashSet<>(); - - addIfConfig(files, added, adaptConfigFile); - addIfConfig(files, added, adaptConfigLegacyFile); - addIfConfig(files, added, modelsFile); - addIfConfig(files, added, modelsLegacyFile); - - if (adaptFolder == null || !adaptFolder.exists() || !adaptFolder.isDirectory()) { - return files; - } - - ArrayDeque queue = new ArrayDeque<>(); - queue.add(adaptFolder); - while (!queue.isEmpty()) { - File next = queue.removeFirst(); - File[] children = next.listFiles(); - if (children == null || children.length == 0) { - continue; - } - - for (File child : children) { - if (child == null) { - continue; - } - - if (child.isDirectory()) { - queue.add(child); - continue; - } - - addIfConfig(files, added, child); - } - } - - return files; - } - - private void addIfConfig(List out, Set added, File file) { - if (file == null || !ConfigFileSupport.isSupportedConfigFile(file)) { - return; - } - - String path = file.getAbsolutePath(); - if (!added.add(path)) { - return; - } - - out.add(file); - } - - private String signature(File file) { - if (file == null || !file.exists()) { - return "missing"; - } - - return file.lastModified() + ":" + file.length(); - } - - private String readFileContent(File file) { - if (file == null || !file.exists() || !file.isFile()) { - return null; - } - - try { - return Files.readString(file.toPath()); - } catch (Throwable e) { - return null; - } - } - - private void updateKnownSnapshot(File file, String normalizedContent) { - if (file == null) { - return; - } - - String path = file.getAbsolutePath(); - knownSignatures.put(path, signature(file)); - if (normalizedContent == null) { - knownContents.remove(path); - } else { - knownContents.put(path, normalizedContent); - } - } - - private String normalizeContent(String text) { - if (text == null) { - return null; - } - return ConfigFileSupport.normalize(text); - } - - private JsonElement parseStructured(String raw, File file) { - if (raw == null || raw.isBlank()) { - return null; - } - - return ConfigFileSupport.parseToJsonElement(raw, file); - } - - private void notifyOps(File file, String before, String after) { - List diffs = computeDiff(before, after); - if (diffs.isEmpty()) { - return; - } - - String relative = relativizeToDataFolder(file); - List messages = new ArrayList<>(); - int shown = Math.min(MAX_DIFF_MESSAGES_PER_FILE, diffs.size()); - for (int i = 0; i < shown; i++) { - DiffEntry diff = diffs.get(i); - messages.add(formatHotloadMessage(relative, diff.key, diff.oldValue, diff.newValue)); - } - - if (diffs.size() > shown) { - int remaining = diffs.size() - shown; - messages.add(formatHotloadMessage(relative, "...", "+" + remaining + " more", "truncated")); - } - - J.s(() -> { - for (Player player : Adapt.instance.getAdaptServer().getOnlinePlayerSnapshot()) { - if (!player.isOp()) { - continue; - } - - player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 0.8f, 1.6f); - messages.forEach(player::sendMessage); - } - }); - } - - private List computeDiff(String before, String after) { - Map left = flattenForDiff(before); - Map right = flattenForDiff(after); - Set keys = new HashSet<>(left.keySet()); - keys.addAll(right.keySet()); - - List ordered = new ArrayList<>(keys); - ordered.sort(String::compareTo); - - List changes = new ArrayList<>(); - for (String key : ordered) { - boolean inLeft = left.containsKey(key); - boolean inRight = right.containsKey(key); - String oldValue = inLeft ? left.get(key) : MISSING; - String newValue = inRight ? right.get(key) : REMOVED; - if (Objects.equals(oldValue, newValue)) { - continue; - } - changes.add(new DiffEntry(key, oldValue, newValue)); - } - - return changes; - } - - private Map flattenForDiff(String raw) { - JsonElement element = parseStructured(raw, null); - if (element == null) { - Map fallback = new HashMap<>(); - if (raw != null && !raw.isBlank()) { - fallback.put("$", formatValue(raw)); - } - return fallback; - } - - Map out = new HashMap<>(); - flattenJson("$", element, out); - return out; - } - - private void flattenJson(String path, JsonElement element, Map out) { - if (element == null || element.isJsonNull()) { - out.put(path, "null"); - return; - } - - if (element.isJsonPrimitive()) { - out.put(path, element.toString()); - return; - } - - if (element.isJsonArray()) { - if (element.getAsJsonArray().size() == 0) { - out.put(path, "[]"); - return; - } - - for (int i = 0; i < element.getAsJsonArray().size(); i++) { - flattenJson(path + "[" + i + "]", element.getAsJsonArray().get(i), out); - } - return; - } - - JsonObject object = element.getAsJsonObject(); - if (object.entrySet().isEmpty()) { - out.put(path, "{}"); - return; - } - - for (Map.Entry entry : object.entrySet()) { - flattenJson(path + "." + entry.getKey(), entry.getValue(), out); - } - } - - private String formatHotloadMessage(String file, String key, String oldValue, String newValue) { - return C.GRAY + "[" + C.DARK_RED + "Adapt" + C.GRAY + "]: " - + C.GREEN + "Adapt Hotloaded: " - + C.WHITE + "[" + file + "] " - + C.AQUA + "[" + key + "] " - + C.GRAY + "[" + formatValue(oldValue) + " -> " + formatValue(newValue) + "]"; - } - - private String formatValue(String value) { - if (value == null) { - return "null"; - } - - String compact = value.replace("\r", "\\r").replace("\n", "\\n"); - if (compact.length() > 120) { - return compact.substring(0, 117) + "..."; - } - return compact; - } - - private String relativizeToDataFolder(File file) { - try { - return Adapt.instance.getDataFolder().toPath().relativize(file.toPath()).toString(); - } catch (Throwable e) { - return file.getName(); - } - } - - private void refreshOpenAdaptGuis() { - J.s(() -> { - Map open = new HashMap<>(Adapt.instance.getGuiLeftovers()); - for (Map.Entry entry : open.entrySet()) { - String playerKey = entry.getKey(); - Window window = entry.getValue(); - if (window == null) { - continue; - } - - UUID uuid; - try { - uuid = UUID.fromString(playerKey); - } catch (Throwable ignored) { - continue; - } - - Player player = Bukkit.getPlayer(uuid); - if (player == null || !player.isOnline()) { - Adapt.instance.getGuiLeftovers().remove(playerKey); - continue; - } - - reopenFromTag(player, window.getTag()); - } - }); - } - - private void reopenFromTag(Player player, String tag) { - if (tag == null || tag.isBlank() || "/".equals(tag)) { - SkillsGui.open(player); - return; - } - - if (tag.startsWith("config/")) { - ConfigGui.reopenFromTag(player, tag); - return; - } - - if (!tag.startsWith("skill/")) { - SkillsGui.open(player); - return; - } - - String[] parts = tag.split("/"); - if (parts.length < 2) { - SkillsGui.open(player); - return; - } - - Skill skill = Adapt.instance.getAdaptServer().getSkillRegistry().getSkill(parts[1]); - if (skill == null || !skill.isEnabled()) { - SkillsGui.open(player); - return; - } - - if (parts.length == 2) { - skill.openGui(player); - return; - } - - String adaptationName = parts[2]; - for (Adaptation adaptation : skill.getAdaptations()) { - if (!adaptation.getName().equalsIgnoreCase(adaptationName)) { - continue; - } - - if (adaptation.isEnabled()) { - adaptation.openGui(player); - } else { - skill.openGui(player); - } - return; - } - - skill.openGui(player); - } - - private static class DiffEntry { - private final String key; - private final String oldValue; - private final String newValue; - - private DiffEntry(String key, String oldValue, String newValue) { - this.key = key; - this.oldValue = oldValue; - this.newValue = newValue; - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/AR.java b/src/main/java/com/volmit/adapt/util/AR.java deleted file mode 100644 index eba27578c..000000000 --- a/src/main/java/com/volmit/adapt/util/AR.java +++ /dev/null @@ -1,40 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public abstract class AR implements Runnable, CancellableTask { - private int id = 0; - - public AR() { - this(0); - } - - public AR(int interval) { - id = J.ar(this, interval); - } - - @Override - public void cancel() { - J.car(id); - } - - public int getId() { - return id; - } -} diff --git a/src/main/java/com/volmit/adapt/util/AdvancementUtils.java b/src/main/java/com/volmit/adapt/util/AdvancementUtils.java deleted file mode 100644 index ecb55d726..000000000 --- a/src/main/java/com/volmit/adapt/util/AdvancementUtils.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.volmit.adapt.util; - -import com.fren_gor.ultimateAdvancementAPI.UltimateAdvancementAPI; -import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.MinecraftKeyWrapper; -import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementDisplayWrapper; -import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementFrameTypeWrapper; -import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.advancement.AdvancementWrapper; -import com.fren_gor.ultimateAdvancementAPI.nms.wrappers.packets.PacketPlayOutAdvancementsWrapper; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.advancement.AdaptAdvancementFrame; -import com.google.common.base.Preconditions; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -public class AdvancementUtils { - - private static volatile boolean unavailable; - private static volatile boolean warned; - - /** - * Displays a custom toast to a player. - * - * @param player A player to show the toast. - * @param icon The displayed item of the toast. - * @param title The displayed title of the toast. - * @param frame The frame type of the toast. - * @see UltimateAdvancementAPI#displayCustomToast(Player, ItemStack, String, com.fren_gor.ultimateAdvancementAPI.advancement.display.AdvancementFrameType) - */ - public static void displayToast(@NotNull Player player, @NotNull ItemStack icon, @NotNull String title, @NotNull String description, @NotNull AdaptAdvancementFrame frame) { - if (unavailable) { - return; - } - - Preconditions.checkNotNull(player, "Player is null."); - Preconditions.checkNotNull(icon, "Icon is null."); - Preconditions.checkNotNull(title, "Title is null."); - Preconditions.checkNotNull(frame, "AdvancementFrameType is null."); - Preconditions.checkArgument(icon.getType() != Material.AIR, "ItemStack is air."); - - try { - MinecraftKeyWrapper rootKey = MinecraftKeyWrapper.craft("com.fren_gor", "root"); - MinecraftKeyWrapper notificationKey = MinecraftKeyWrapper.craft("com.fren_gor", "notification"); - AdvancementDisplayWrapper rootDisplay = AdvancementDisplayWrapper.craft( - new ItemStack(Material.GRASS_BLOCK), - "§f§lNotifications§1§2§3§4§5§6§7§8§9§0", - "§7Notification page.\n§7Close and reopen advancements to hide.", - AdvancementFrameTypeWrapper.TASK, - 0, - 0, - "textures/block/stone.png" - ); - AdvancementWrapper root = AdvancementWrapper.craftRootAdvancement(rootKey, rootDisplay, 1); - AdvancementDisplayWrapper display = AdvancementDisplayWrapper.craft(icon, title, description, frame.toUaaFrame().getNMSWrapper(), 1, 0, true, false, false); - AdvancementWrapper notification = AdvancementWrapper.craftBaseAdvancement(notificationKey, root, display, 1); - PacketPlayOutAdvancementsWrapper.craftSendPacket(Map.of( - root, 1, - notification, 1 - )).sendTo(player); - PacketPlayOutAdvancementsWrapper.craftRemovePacket(Set.of(rootKey, notificationKey)).sendTo(player); - } catch (Throwable e) { - unavailable = true; - warnOnce(e); - } - } - - private static void warnOnce(Throwable throwable) { - if (warned) { - return; - } - - warned = true; - Throwable root = throwable; - while (root.getCause() != null && root.getCause() != root) { - root = root.getCause(); - } - Adapt.warn("Advancement notifications are unavailable: " + Objects.toString(root.getMessage(), root.getClass().getSimpleName())); - } -} diff --git a/src/main/java/com/volmit/adapt/util/Area.java b/src/main/java/com/volmit/adapt/util/Area.java deleted file mode 100644 index 9a3ce1d89..000000000 --- a/src/main/java/com/volmit/adapt/util/Area.java +++ /dev/null @@ -1,253 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KList; -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Random; - - -/** - * Used to Create an instance of a spherical area based on a central location - * Great for efficiently checking if an entity is within a spherical area. - * - * @author cyberpwn - */ -public class Area { - private Location location; - private Double radius; - - /** - * Used to instantiate a new "area" in which you can check if entities are - * within this area. - * - * @param location The center location of the area - * @param radius The radius used as a double. - */ - public Area(Location location, Double radius) { - this.location = location; - this.radius = radius; - } - - /** - * Used to instantiate a new "area" in which you can check if entities are - * within this area. - * - * @param location The center location of the area - * @param radius The radius used as an int. - */ - public Area(Location location, Integer radius) { - this.location = location; - this.radius = (double) radius; - } - - public static boolean within(Location center, Location target, double rad) { - return new Area(center, rad).isWithin(target); - } - - public Cuboid toCuboid() { - return new Cuboid(location.clone().add(radius, radius, radius), location.clone().subtract(radius, radius, radius)); - } - - /** - * Calculate the ESTIMATED distance from the center of this - * area, to the given location WARNING: This uses newton's method, - * be careful on how accurate you need this. As it is meant for FAST - * calculations with minimal load. - * - * @param location The given location to calculate a distance from the center. - * @return Returns the distance of location from the center. - */ - public Double distance(Location location) { - double c = this.location.distanceSquared(location); - double t = c; - - for (int i = 0; i < 3; i++) { - t = (c / t + t) / 2.0; - } - - return t; - } - - /** - * Calculate the EXACT distance from the center of this - * area, to the given location WARNING: This uses the sqrt function, - * be careful on how heavy you call this. - * - * @param location The given location to calculate a distance from the center. - * @return Returns the distance of location from the center. - */ - public Double slowDistance(Location location) { - return this.location.distance(location); - } - - /** - * Check to see weather a location is within the area - * - * @param location The location to measure from the center. - * @return Returns True if within; False if not. - */ - public boolean isWithin(Location location) { - return this.location.distance(location) <= (radius * radius); - } - - /** - * But does it have any entities? - */ - public boolean hasEntities() { - return getNearbyEntities().length > 0; - } - - /** - * Get all nearby entities matching the given entity type - * - * @param type the entity type - * @return the nearby entities matching the given type - */ - public Entity[] getNearbyEntities(EntityType type) { - KList e = new KList<>(getNearbyEntities()); - - for (Entity i : e.copy()) { - if (!i.getType().equals(type)) { - e.remove(i); - } - } - - return e.toArray(new Entity[e.size()]); - } - - /** - * Get nearby entities which match the following class - * - * @param entityClass the entity class - * @return the nearby entities assignable from the given class - */ - public Entity[] getNearbyEntities(Class entityClass) { - KList e = new KList<>(getNearbyEntities()); - - for (Entity i : e.copy()) { - if (!i.getClass().isAssignableFrom(entityClass)) { - e.remove(i); - } - } - - return e.toArray(new Entity[0]); - } - - /** - * Get ALL entities within the area. NOTE: This is EVERY entity, not - * just LivingEntities. Drops, Particles, Mobs, Players, Everything - * - * @return Returns an Entity[] array of all entities within the given area. - */ - public Entity[] getNearbyEntities() { - try { - int chunkRadius = (int) (radius < 16 ? 1 : (radius - (radius % 16)) / 16); - HashSet radiusEntities = new HashSet(); - - for (int chX = 0 - chunkRadius; chX <= chunkRadius; chX++) { - for (int chZ = 0 - chunkRadius; chZ <= chunkRadius; chZ++) { - int x = (int) location.getX(), y = (int) location.getY(), z = (int) location.getZ(); - - for (Entity e : new Location(location.getWorld(), x + (chX * 16), y, z + (chZ * 16)).getChunk().getEntities()) { - if (e.getLocation().distanceSquared(location) <= radius * radius && e.getLocation().getBlock() != location.getBlock()) { - radiusEntities.add(e); - } - } - } - } - - return radiusEntities.toArray(new Entity[radiusEntities.size()]); - } catch (Exception e) { - return new ArrayList().toArray(new Entity[0]); - } - } - - /** - * Get all players within the area. - * - * @return Returns an Player[] array of all players within the given area. - */ - public Player[] getNearbyPlayers() { - List px = new ArrayList<>(); - - for (Entity i : getNearbyEntities()) { - if (i.getType().equals(EntityType.PLAYER)) { - px.add((Player) i); - } - } - - return px.toArray(new Player[0]); - } - - /** - * Get the defined center location - * - * @return Returns the center location of the area - */ - public Location getLocation() { - return location; - } - - /** - * Set the defined center location - * - * @param location The new location to be set - */ - public void setLocation(Location location) { - this.location = location; - } - - /** - * Gets the area's radius - * - * @return Returns the area's radius - */ - public Double getRadius() { - return radius; - } - - /** - * Set the area's radius - * - * @param radius The new radius to be set - */ - public void setRadius(Double radius) { - this.radius = radius; - } - - /** - * Pick a random location in this radius - */ - public Location random() { - Random r = new Random(); - double x = radius * ((r.nextDouble() - 0.5) * 2); - double y = radius * ((r.nextDouble() - 0.5) * 2); - double z = radius * ((r.nextDouble() - 0.5) * 2); - - return location.clone().add(x, y, z); - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/AtomicAverage.java b/src/main/java/com/volmit/adapt/util/AtomicAverage.java deleted file mode 100644 index b1da46e31..000000000 --- a/src/main/java/com/volmit/adapt/util/AtomicAverage.java +++ /dev/null @@ -1,101 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.google.common.util.concurrent.AtomicDoubleArray; - -/** - * Provides an incredibly fast averaging object. It swaps values from a sum - * using an array. Averages do not use any form of looping. An average of 10,000 - * entries is the same speed as an average with 5 entries. - * - * @author cyberpwn - */ -public class AtomicAverage { - protected AtomicDoubleArray values; - protected int cursor; - private double average; - private double lastSum; - private boolean dirty; - private boolean brandNew; - - /** - * Create an average holder - * - * @param size the size of entries to keep - */ - public AtomicAverage(int size) { - values = new AtomicDoubleArray(size); - DoubleArrayUtils.fill(values, 0); - brandNew = true; - average = 0; - cursor = 0; - lastSum = 0; - dirty = false; - } - - /** - * Put a value into the average (rolls over if full) - * - * @param i the value - */ - public void put(double i) { - - dirty = true; - - if (brandNew) { - DoubleArrayUtils.fill(values, i); - lastSum = size() * i; - brandNew = false; - return; - } - - double current = values.get(cursor); - lastSum = (lastSum - current) + i; - values.set(cursor, i); - cursor = cursor + 1 < size() ? cursor + 1 : 0; - } - - /** - * Get the current average - * - * @return the average - */ - public double getAverage() { - if (dirty) { - calculateAverage(); - return getAverage(); - } - - return average; - } - - private void calculateAverage() { - average = lastSum / (double) size(); - dirty = false; - } - - public int size() { - return values.length(); - } - - public boolean isDirty() { - return dirty; - } -} diff --git a/src/main/java/com/volmit/adapt/util/AtomicRollingSequence.java b/src/main/java/com/volmit/adapt/util/AtomicRollingSequence.java deleted file mode 100644 index ebe9da578..000000000 --- a/src/main/java/com/volmit/adapt/util/AtomicRollingSequence.java +++ /dev/null @@ -1,110 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KList; - -public class AtomicRollingSequence extends AtomicAverage { - private double median; - private double max; - private double min; - private boolean dirtyMedian; - private int dirtyExtremes; - private boolean precision; - - public AtomicRollingSequence(int size) { - super(size); - median = 0; - min = 0; - max = 0; - setPrecision(false); - } - - public double addLast(int amt) { - double f = 0; - - for (int i = 0; i < Math.min(values.length(), amt); i++) { - f += values.get(i); - } - - return f; - } - - public boolean isPrecision() { - return precision; - } - - public void setPrecision(boolean p) { - this.precision = p; - } - - public double getMin() { - if (dirtyExtremes > (isPrecision() ? 0 : values.length())) { - resetExtremes(); - } - - return min; - } - - public double getMax() { - if (dirtyExtremes > (isPrecision() ? 0 : values.length())) { - resetExtremes(); - } - - return max; - } - - public double getMedian() { - if (dirtyMedian) { - recalculateMedian(); - } - - return median; - } - - private void recalculateMedian() { - double[] a = new double[values.length()]; - for (int i = 0; i < a.length; i++) { - a[i] = values.get(i); - } - median = new KList().forceAdd(a).sort().middleValue(); - dirtyMedian = false; - } - - public void resetExtremes() { - max = Integer.MIN_VALUE; - min = Integer.MAX_VALUE; - - for (int i = 0; i < values.length(); i++) { - double v = values.get(i); - max = M.max(max, v); - min = M.min(min, v); - } - - dirtyExtremes = 0; - } - - public void put(double i) { - super.put(i); - dirtyMedian = true; - dirtyExtremes++; - max = M.max(max, i); - min = M.min(min, i); - } -} diff --git a/src/main/java/com/volmit/adapt/util/Average.java b/src/main/java/com/volmit/adapt/util/Average.java deleted file mode 100644 index 408f26b6b..000000000 --- a/src/main/java/com/volmit/adapt/util/Average.java +++ /dev/null @@ -1,99 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Provides an incredibly fast averaging object. It swaps values from a sum - * using an array. Averages do not use any form of looping. An average of 10,000 - * entries is the same speed as an average with 5 entries. - * - * @author cyberpwn - */ -public class Average { - protected double[] values; - protected int cursor; - private double average; - private double lastSum; - private boolean dirty; - private boolean brandNew; - - /** - * Create an average holder - * - * @param size the size of entries to keep - */ - public Average(int size) { - values = new double[size]; - DoubleArrayUtils.fill(values, 0); - brandNew = true; - average = 0; - cursor = 0; - lastSum = 0; - dirty = false; - } - - /** - * Put a value into the average (rolls over if full) - * - * @param i the value - */ - public void put(double i) { - - dirty = true; - - if (brandNew) { - DoubleArrayUtils.fill(values, i); - lastSum = size() * i; - brandNew = false; - return; - } - - double current = values[cursor]; - lastSum = (lastSum - current) + i; - values[cursor] = i; - cursor = cursor + 1 < size() ? cursor + 1 : 0; - } - - /** - * Get the current average - * - * @return the average - */ - public double getAverage() { - if (dirty) { - calculateAverage(); - return getAverage(); - } - - return average; - } - - private void calculateAverage() { - average = lastSum / (double) size(); - dirty = false; - } - - public int size() { - return values.length; - } - - public boolean isDirty() { - return dirty; - } -} diff --git a/src/main/java/com/volmit/adapt/util/BlockPosition.java b/src/main/java/com/volmit/adapt/util/BlockPosition.java deleted file mode 100644 index e06c3dbec..000000000 --- a/src/main/java/com/volmit/adapt/util/BlockPosition.java +++ /dev/null @@ -1,67 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -@Getter -@Setter -@ToString -public class BlockPosition { - private int x; - private int y; - private int z; - - public BlockPosition(int x, int y, int z) { - this.x = x; - this.y = y; - this.z = z; - } - - public boolean equals(Object o) { - if (o == null) { - return false; - } - - if (o instanceof BlockPosition ot) { - - return ot.x == x && ot.y == y && ot.z == z; - } - - return false; - } - - public int getChunkX() { - return x >> 4; - } - - public int getChunkZ() { - return z >> 4; - } - - public boolean is(int x, int z) { - return this.x == x && this.z == z; - } - - public boolean is(int x, int y, int z) { - return this.x == x && this.y == y && this.z == z; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Board.java b/src/main/java/com/volmit/adapt/util/Board.java deleted file mode 100644 index 5b343a622..000000000 --- a/src/main/java/com/volmit/adapt/util/Board.java +++ /dev/null @@ -1,148 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import lombok.NonNull; -import lombok.Setter; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.scoreboard.DisplaySlot; -import org.bukkit.scoreboard.Objective; -import org.bukkit.scoreboard.Scoreboard; -import org.bukkit.scoreboard.Team; - -import java.util.Collections; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -/** - * @author Missionary (missionarymc@gmail.com) - * @since 3/23/2018 - */ -public class Board { - - private static final String[] CACHED_ENTRIES = new String[C.values().length]; - - private static final Function APPLY_COLOR_TRANSLATION = s -> C.translateAlternateColorCodes('&', s); - - static { - IntStream.range(0, 15).forEach(i -> CACHED_ENTRIES[i] = C.values()[i].toString() + C.RESET); - } - - private final Player player; - private final Objective objective; - private final Team team; - @Setter - private BoardSettings boardSettings; - private boolean ready; - - @SuppressWarnings("deprecation") - public Board(@NonNull final Player player, final BoardSettings boardSettings) { - this.player = player; - this.boardSettings = boardSettings; - this.objective = this.getScoreboard().getObjective("board") == null ? this.getScoreboard().registerNewObjective("board", "dummy") : this.getScoreboard().getObjective("board"); - this.objective.setDisplaySlot(DisplaySlot.SIDEBAR); - this.team = this.getScoreboard().getTeam("board") == null ? this.getScoreboard().registerNewTeam("board") : this.getScoreboard().getTeam("board"); - this.team.setAllowFriendlyFire(true); - this.team.setCanSeeFriendlyInvisibles(false); - this.team.setPrefix(""); - this.team.setSuffix(""); - this.ready = true; - } - - public Scoreboard getScoreboard() { - return (player != null) ? player.getScoreboard() : null; - } - - public void remove() { - this.resetScoreboard(); - } - - public void update() { - // Checking if we are ready to start updating the Scoreboard. - if (!ready) { - return; - } - - // Making sure the player is connected. - if (!player.isOnline()) { - remove(); - return; - } - - // Making sure the Scoreboard Provider is set. - if (boardSettings == null) { - return; - } - - // Getting their Scoreboard display from the Scoreboard Provider. - final List entries = boardSettings.getBoardProvider().getLines(player).stream().map(APPLY_COLOR_TRANSLATION).collect(Collectors.toList()); - - if (boardSettings.getScoreDirection() == ScoreDirection.UP) { - Collections.reverse(entries); - } - - // Setting the Scoreboard title - String title = boardSettings.getBoardProvider().getTitle(player); - if (title.length() > 32) { - Bukkit.getLogger().warning("The title " + title + " is over 32 characters in length, substringing to prevent errors."); - title = title.substring(0, 32); - } - objective.setDisplayName(C.translateAlternateColorCodes('&', title)); - - // Clearing previous Scoreboard values if entry sizes don't match. - if (this.getScoreboard().getEntries().size() != entries.size()) - this.getScoreboard().getEntries().forEach(this::removeEntry); - - // Setting Scoreboard lines. - for (int i = 0; i < entries.size(); i++) { - String str = entries.get(i); - BoardEntry entry = BoardEntry.translateToEntry(str); - Team team = getScoreboard().getTeam(CACHED_ENTRIES[i]); - - if (team == null) { - team = this.getScoreboard().registerNewTeam(CACHED_ENTRIES[i]); - team.addEntry(team.getName()); - } - - team.setPrefix(entry.getPrefix()); - team.setSuffix(entry.getSuffix()); - - switch (boardSettings.getScoreDirection()) { - case UP: - objective.getScore(team.getName()).setScore(1 + i); - break; - case DOWN: - objective.getScore(team.getName()).setScore(15 - i); - break; - } - } - } - - public void removeEntry(String id) { - this.getScoreboard().resetScores(id); - } - - public void resetScoreboard() { - ready = false; - player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); - } -} diff --git a/src/main/java/com/volmit/adapt/util/BoardEntry.java b/src/main/java/com/volmit/adapt/util/BoardEntry.java deleted file mode 100644 index f8cd76a54..000000000 --- a/src/main/java/com/volmit/adapt/util/BoardEntry.java +++ /dev/null @@ -1,57 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import lombok.Getter; -import org.apache.commons.lang3.StringUtils; - -/** - * @author Missionary (missionarymc@gmail.com) - * @since 3/29/2018 - */ -public class BoardEntry { - - @Getter - private final String prefix, suffix; - - private BoardEntry(final String prefix, final String suffix) { - this.prefix = prefix; - this.suffix = suffix; - } - - public static BoardEntry translateToEntry(String input) { - if (input.isEmpty()) { - return new BoardEntry("", ""); - } - if (input.length() <= 16) { - return new BoardEntry(input, ""); - } else { - String prefix = input.substring(0, 16); - String suffix = ""; - - if (prefix.endsWith("\u00a7")) { - prefix = prefix.substring(0, prefix.length() - 1); - suffix = "\u00a7" + suffix; - } - - suffix = StringUtils.left(C.getLastColors(prefix) + suffix + input.substring(16), 16); - return new BoardEntry(prefix, suffix); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/BoardManager.java b/src/main/java/com/volmit/adapt/util/BoardManager.java deleted file mode 100644 index 47156752d..000000000 --- a/src/main/java/com/volmit/adapt/util/BoardManager.java +++ /dev/null @@ -1,105 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitTask; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -@DontObfuscate -public class BoardManager { - @DontObfuscate - private final JavaPlugin plugin; - @DontObfuscate - private final Map scoreboards; - @DontObfuscate - private final BukkitTask updateTask; - @DontObfuscate - private BoardSettings boardSettings; - - @DontObfuscate - public BoardManager(JavaPlugin plugin, BoardSettings boardSettings) { - this.plugin = plugin; - this.boardSettings = boardSettings; - this.scoreboards = new ConcurrentHashMap<>(); - this.updateTask = new BoardUpdateTask(this).runTaskTimer(plugin, 2L, 2L); - for (Player player : onlinePlayers()) { - setup(player); - } - } - - @DontObfuscate - public void setBoardSettings(BoardSettings boardSettings) { - this.boardSettings = boardSettings; - scoreboards.values().forEach(board -> board.setBoardSettings(boardSettings)); - } - - @DontObfuscate - public boolean hasBoard(Player player) { - return scoreboards.containsKey(player.getUniqueId()); - } - - @DontObfuscate - public Optional getBoard(Player player) { - return Optional.ofNullable(scoreboards.get(player.getUniqueId())); - } - - @DontObfuscate - public void setup(Player player) { - Optional.ofNullable(scoreboards.remove(player.getUniqueId())).ifPresent(Board::resetScoreboard); - if (Bukkit.getScoreboardManager() != null && player.getScoreboard().equals(Bukkit.getScoreboardManager().getMainScoreboard())) { - player.setScoreboard(Bukkit.getScoreboardManager().getNewScoreboard()); - } - scoreboards.put(player.getUniqueId(), new Board(player, boardSettings)); - } - - @DontObfuscate - public void remove(Player player) { - Optional.ofNullable(scoreboards.remove(player.getUniqueId())).ifPresent(Board::remove); - } - - @DontObfuscate - public Map getScoreboards() { - return Collections.unmodifiableMap(scoreboards); - } - - @DontObfuscate - public void onDisable() { - updateTask.cancel(); - for (Player player : onlinePlayers()) { - remove(player); - } - scoreboards.clear(); - } - - private Iterable onlinePlayers() { - if (Adapt.instance != null && Adapt.instance.getAdaptServer() != null) { - return Adapt.instance.getAdaptServer().getOnlinePlayerSnapshot(); - } - return plugin.getServer().getOnlinePlayers(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/BoardProvider.java b/src/main/java/com/volmit/adapt/util/BoardProvider.java deleted file mode 100644 index 18e9d16bd..000000000 --- a/src/main/java/com/volmit/adapt/util/BoardProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.entity.Player; - -import java.util.List; - -@DontObfuscate -public interface BoardProvider { - @DontObfuscate - String getTitle(Player player); - - @DontObfuscate - List getLines(Player player); -} diff --git a/src/main/java/com/volmit/adapt/util/BoardSettings.java b/src/main/java/com/volmit/adapt/util/BoardSettings.java deleted file mode 100644 index a4b36075b..000000000 --- a/src/main/java/com/volmit/adapt/util/BoardSettings.java +++ /dev/null @@ -1,33 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import lombok.Builder; -import lombok.Getter; - -@DontObfuscate -@Getter -@Builder -public class BoardSettings { - @DontObfuscate - private BoardProvider boardProvider; - - @DontObfuscate - private ScoreDirection scoreDirection; -} diff --git a/src/main/java/com/volmit/adapt/util/BoardUpdateTask.java b/src/main/java/com/volmit/adapt/util/BoardUpdateTask.java deleted file mode 100644 index 6bf38bfe2..000000000 --- a/src/main/java/com/volmit/adapt/util/BoardUpdateTask.java +++ /dev/null @@ -1,43 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import lombok.RequiredArgsConstructor; -import org.bukkit.Bukkit; -import org.bukkit.scheduler.BukkitRunnable; - -import java.util.UUID; -import java.util.function.Predicate; - -/** - * @author Missionary (missionarymc@gmail.com) - * @since 5/31/2018 - */ -@RequiredArgsConstructor -public class BoardUpdateTask extends BukkitRunnable { - - private static final Predicate PLAYER_IS_ONLINE = uuid -> Bukkit.getPlayer(uuid) != null; - - private final BoardManager boardManager; - - @Override - public void run() { - boardManager.getScoreboards().entrySet().stream().filter(entrySet -> PLAYER_IS_ONLINE.test(entrySet.getKey())).forEach(entrySet -> entrySet.getValue().update()); - } -} diff --git a/src/main/java/com/volmit/adapt/util/BukkitGson.java b/src/main/java/com/volmit/adapt/util/BukkitGson.java deleted file mode 100644 index fc0d2c769..000000000 --- a/src/main/java/com/volmit/adapt/util/BukkitGson.java +++ /dev/null @@ -1,101 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.google.gson.*; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.data.BlockData; -import org.bukkit.util.BlockVector; -import org.bukkit.util.Vector; - -public class BukkitGson { - public static final Gson gson = new GsonBuilder() - .registerTypeAdapter(World.class, (JsonSerializer) (world, type, s) -> s.serialize(world.getName())) - .registerTypeAdapter(World.class, (JsonDeserializer) (j, type, d) -> Bukkit.getWorld(j.getAsString())) - .registerTypeAdapter(BlockData.class, (JsonSerializer) (data, type, s) -> new JsonPrimitive(data.getAsString(true))) - .registerTypeAdapter(BlockData.class, (JsonDeserializer) (j, type, d) -> Bukkit.createBlockData(j.getAsString())) - .registerTypeAdapter(Location.class, (JsonSerializer) (data, type, s) -> { - JsonArray a = new JsonArray(); - a.add(data.getWorld().getName()); - a.add(truncate(data.getX(), 1)); - a.add(truncate(data.getY(), 1)); - a.add(truncate(data.getZ(), 1)); - a.add((int) data.getYaw()); - a.add((int) data.getPitch()); - return a; - }) - .registerTypeAdapter(Location.class, (JsonDeserializer) (j, type, d) -> { - JsonArray a = j.getAsJsonArray(); - return new Location(Bukkit.getWorld(a.get(0).getAsString()), a.get(1).getAsDouble(), a.get(2).getAsDouble(), a.get(3).getAsDouble(), a.get(4).getAsFloat(), a.get(5).getAsFloat()); - }) - .registerTypeAdapter(Block.class, (JsonSerializer) (data, type, s) -> { - JsonArray a = new JsonArray(); - a.add(data.getWorld().getName()); - a.add(data.getX()); - a.add(data.getY()); - a.add(data.getZ()); - return a; - }) - .registerTypeAdapter(Block.class, (JsonDeserializer) (j, type, d) -> { - JsonArray a = j.getAsJsonArray(); - return new Location(Bukkit.getWorld(a.get(0).getAsString()), a.get(1).getAsInt(), a.get(2).getAsInt(), a.get(3).getAsInt()).getBlock(); - }) - .registerTypeAdapter(BlockVector.class, (JsonSerializer) (data, type, s) -> { - JsonArray a = new JsonArray(); - a.add(data.getBlockX()); - a.add(data.getBlockY()); - a.add(data.getBlockZ()); - return a; - }) - .registerTypeAdapter(BlockVector.class, (JsonDeserializer) (j, type, d) -> { - JsonArray a = j.getAsJsonArray(); - return new BlockVector(a.get(0).getAsInt(), a.get(1).getAsInt(), a.get(2).getAsInt()); - }) - .registerTypeAdapter(Vector.class, (JsonSerializer) (data, type, s) -> { - JsonArray a = new JsonArray(); - a.add(truncate(data.getX(), 1)); - a.add(truncate(data.getY(), 1)); - a.add(truncate(data.getZ(), 1)); - return a; - }) - .registerTypeAdapter(Vector.class, (JsonDeserializer) (j, type, d) -> { - JsonArray a = j.getAsJsonArray(); - return new BlockVector(a.get(0).getAsDouble(), a.get(1).getAsDouble(), a.get(2).getAsDouble()); - }) - .create(); - - private static double truncate(double d, int p) { - if ((int) d == d) { - return d; - } - - return Double.parseDouble(Form.f(d, p)); - } - - private static double truncate(float d, int p) { - if ((int) d == d) { - return d; - } - - return Float.parseFloat(Form.f(d, p)); - } -} diff --git a/src/main/java/com/volmit/adapt/util/BurstExecutor.java b/src/main/java/com/volmit/adapt/util/BurstExecutor.java deleted file mode 100644 index 9c312f9f3..000000000 --- a/src/main/java/com/volmit/adapt/util/BurstExecutor.java +++ /dev/null @@ -1,65 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; - -public class BurstExecutor { - private final ExecutorService executor; - private final List> futures; - - public BurstExecutor(ExecutorService executor, int burstSizeEstimate) { - this.executor = executor; - futures = new ArrayList<>(burstSizeEstimate); - } - - public CompletableFuture queue(Runnable r) { - synchronized (futures) { - CompletableFuture c = CompletableFuture.runAsync(r, executor); - futures.add(c); - return c; - } - } - - public BurstExecutor queue(Runnable[] r) { - synchronized (futures) { - for (Runnable i : r) { - CompletableFuture c = CompletableFuture.runAsync(i, executor); - futures.add(c); - } - } - - return this; - } - - public void complete() { - synchronized (futures) { - try { - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(); - futures.clear(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/ByteArrayTag.java b/src/main/java/com/volmit/adapt/util/ByteArrayTag.java deleted file mode 100644 index cb4adaae9..000000000 --- a/src/main/java/com/volmit/adapt/util/ByteArrayTag.java +++ /dev/null @@ -1,67 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * The TAG_Byte_Array tag. - * - * @author Graham Edgecombe - */ -public final class ByteArrayTag extends Tag { - - /** - * The value. - */ - private final byte[] value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public ByteArrayTag(String name, byte[] value) { - super(name); - this.value = value; - } - - @Override - public byte[] getValue() { - return value; - } - - @Override - public String toString() { - StringBuilder hex = new StringBuilder(); - for (byte b : value) { - String hexDigits = Integer.toHexString(b).toUpperCase(); - if (hexDigits.length() == 1) { - hex.append("0"); - } - hex.append(hexDigits).append(" "); - } - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_Byte_Array" + append + ": " + hex; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/ByteTag.java b/src/main/java/com/volmit/adapt/util/ByteTag.java deleted file mode 100644 index fbec382ec..000000000 --- a/src/main/java/com/volmit/adapt/util/ByteTag.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * The TAG_Byte tag. - * - * @author Graham Edgecombe - */ -public final class ByteTag extends Tag { - - /** - * The value. - */ - private final byte value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public ByteTag(String name, byte value) { - super(name); - this.value = value; - } - - @Override - public Byte getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_Byte" + append + ": " + value; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/C.java b/src/main/java/com/volmit/adapt/util/C.java deleted file mode 100644 index 13ebc920a..000000000 --- a/src/main/java/com/volmit/adapt/util/C.java +++ /dev/null @@ -1,736 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; -import org.apache.commons.lang3.Validate; -import org.bukkit.ChatColor; -import org.bukkit.Color; -import org.bukkit.DyeColor; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.regex.Pattern; - -/** - * Colors - * - * @author cyberpwn - */ -public enum C { - /** - * Represents black - */ - BLACK('0', 0x00) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.BLACK; - } - }, - /** - * Represents dark blue - */ - DARK_BLUE('1', 0x1) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.DARK_BLUE; - } - }, - /** - * Represents dark green - */ - DARK_GREEN('2', 0x2) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.DARK_GREEN; - } - }, - /** - * Represents dark blue (aqua) - */ - DARK_AQUA('3', 0x3) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.DARK_AQUA; - } - }, - /** - * Represents dark red - */ - DARK_RED('4', 0x4) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.DARK_RED; - } - }, - /** - * Represents dark red - */ - ADAPT('4', 0x4) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.DARK_RED; - } - }, - /** - * Represents dark purple - */ - DARK_PURPLE('5', 0x5) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.DARK_PURPLE; - } - }, - /** - * Represents gold - */ - GOLD('6', 0x6) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.GOLD; - } - }, - /** - * Represents gray - */ - GRAY('7', 0x7) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.GRAY; - } - }, - /** - * Represents dark gray - */ - DARK_GRAY('8', 0x8) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.DARK_GRAY; - } - }, - /** - * Represents blue - */ - BLUE('9', 0x9) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.BLUE; - } - }, - /** - * Represents green - */ - GREEN('a', 0xA) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.GREEN; - } - }, - /** - * Represents aqua - */ - AQUA('b', 0xB) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.AQUA; - } - }, - /** - * Represents red - */ - RED('c', 0xC) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.RED; - } - }, - /** - * Represents light purple - */ - LIGHT_PURPLE('d', 0xD) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.LIGHT_PURPLE; - } - }, - /** - * Represents yellow - */ - YELLOW('e', 0xE) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.YELLOW; - } - }, - /** - * Represents white - */ - WHITE('f', 0xF) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.WHITE; - } - }, - /** - * Represents magical characters that change around randomly - */ - MAGIC("", 'k', 0x10, true) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.MAGIC; - } - }, - /** - * Makes the text bold. - */ - BOLD('l', 0x11, true) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.BOLD; - } - }, - /** - * Makes a line appear through the text. - */ - STRIKETHROUGH('m', 0x12, true) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.STRIKETHROUGH; - } - }, - /** - * Makes the text appear underlined. - */ - UNDERLINE("", 'n', 0x13, true) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.UNDERLINE; - } - }, - /** - * Makes the text italic. - */ - ITALIC('o', 0x14, true) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.ITALIC; - } - }, - - /** - * Resets all previous chat colors or formats. - */ - RESET('r', 0x15) { - @Override - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.RESET; - } - }, - - - ; - /** - * The special character which prefixes all chat colour codes. Use this if you - * need to dynamically convert colour codes from your custom format. - */ - public static final char COLOR_CHAR = '\u00A7'; - public final static C[] COLORCYCLE = new C[]{C.GOLD, C.YELLOW, C.GREEN, C.AQUA, C.LIGHT_PURPLE, C.AQUA, C.GREEN, C.YELLOW, C.GOLD, C.RED}; - private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + COLOR_CHAR + "[0-9A-FK-OR]"); - private final static C[] COLORS = new C[]{C.BLACK, C.DARK_BLUE, C.DARK_GREEN, C.DARK_AQUA, C.DARK_RED, C.DARK_PURPLE, C.GOLD, C.GRAY, C.DARK_GRAY, C.BLUE, C.GREEN, C.AQUA, C.RED, C.LIGHT_PURPLE, C.YELLOW, C.WHITE}; - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") - private final static Map BY_ID = new HashMap<>(); - private final static Map BY_CHAR = new HashMap<>(); - private final static Map dyeChatMap = new HashMap<>(); - private final static Map chatHexMap = new HashMap<>(); - private final static Map dyeHexMap = new HashMap<>(); - - static { - chatHexMap.put(C.BLACK, "#000000"); - chatHexMap.put(C.DARK_BLUE, "#0000AA"); - chatHexMap.put(C.DARK_GREEN, "#00AA00"); - chatHexMap.put(C.DARK_AQUA, "#00AAAA"); - chatHexMap.put(C.DARK_RED, "#AA0000"); - chatHexMap.put(C.ADAPT, "#AA0000"); - chatHexMap.put(C.DARK_PURPLE, "#AA00AA"); - chatHexMap.put(C.GOLD, "#FFAA00"); - chatHexMap.put(C.GRAY, "#AAAAAA"); - chatHexMap.put(C.DARK_GRAY, "#555555"); - chatHexMap.put(C.BLUE, "#5555FF"); - chatHexMap.put(C.GREEN, "#55FF55"); - chatHexMap.put(C.AQUA, "#55FFFF"); - chatHexMap.put(C.RED, "#FF5555"); - chatHexMap.put(C.LIGHT_PURPLE, "#FF55FF"); - chatHexMap.put(C.YELLOW, "#FFFF55"); - chatHexMap.put(C.WHITE, "#FFFFFF"); - dyeChatMap.put(DyeColor.BLACK, C.DARK_GRAY); - dyeChatMap.put(DyeColor.BLUE, C.DARK_BLUE); - dyeChatMap.put(DyeColor.BROWN, C.GOLD); - dyeChatMap.put(DyeColor.CYAN, C.AQUA); - dyeChatMap.put(DyeColor.GRAY, C.GRAY); - dyeChatMap.put(DyeColor.GREEN, C.DARK_GREEN); - dyeChatMap.put(DyeColor.LIGHT_BLUE, C.BLUE); - dyeChatMap.put(DyeColor.LIME, C.GREEN); - dyeChatMap.put(DyeColor.MAGENTA, C.LIGHT_PURPLE); - dyeChatMap.put(DyeColor.ORANGE, C.GOLD); - dyeChatMap.put(DyeColor.PINK, C.LIGHT_PURPLE); - dyeChatMap.put(DyeColor.PURPLE, C.DARK_PURPLE); - dyeChatMap.put(DyeColor.RED, C.RED); - dyeChatMap.put(DyeColor.LIGHT_GRAY, C.GRAY); - dyeChatMap.put(DyeColor.WHITE, C.WHITE); - dyeChatMap.put(DyeColor.YELLOW, C.YELLOW); - dyeHexMap.put(DyeColor.BLACK, "#181414"); - dyeHexMap.put(DyeColor.BLUE, "#253193"); - dyeHexMap.put(DyeColor.BROWN, "#56331c"); - dyeHexMap.put(DyeColor.CYAN, "#267191"); - dyeHexMap.put(DyeColor.GRAY, "#414141"); - dyeHexMap.put(DyeColor.GREEN, "#364b18"); - dyeHexMap.put(DyeColor.LIGHT_BLUE, "#6387d2"); - dyeHexMap.put(DyeColor.LIME, "#39ba2e"); - dyeHexMap.put(DyeColor.MAGENTA, "#be49c9"); - dyeHexMap.put(DyeColor.ORANGE, "#ea7e35"); - dyeHexMap.put(DyeColor.PINK, "#d98199"); - dyeHexMap.put(DyeColor.PURPLE, "#7e34bf"); - dyeHexMap.put(DyeColor.RED, "#9e2b27"); - dyeHexMap.put(DyeColor.LIGHT_GRAY, "#a0a7a7"); - dyeHexMap.put(DyeColor.WHITE, "#a4a4a4"); - dyeHexMap.put(DyeColor.YELLOW, "#c2b51c"); - } - - static { - for (C color : values()) { - BY_ID.put(color.intCode, color); - BY_CHAR.put(color.code, color); - } - } - - private final int intCode; - private final char code; - private final String token; - private final boolean isFormat; - private final String toString; - - C(char code, int intCode) { - this("^", code, intCode, false); - } - - C(String token, char code, int intCode) { - this(token, code, intCode, false); - } - - C(char code, int intCode, boolean isFormat) { - this("^", code, intCode, isFormat); - } - - C(String token, char code, int intCode, boolean isFormat) { - this.code = code; - this.token = token.equalsIgnoreCase("^") ? "<" + name().toLowerCase(Locale.ROOT) + ">" : token; - this.intCode = intCode; - this.isFormat = isFormat; - this.toString = new String(new char[]{COLOR_CHAR, code}); - } - - public static float[] spin(float[] c, int shift) { - return new float[]{spin(c[0], shift), spinc(c[1], shift), spinc(c[2], shift)}; - } - - public static float[] spin(float[] c, int a, int b, int d) { - return new float[]{spin(c[0], a), spinc(c[1], b), spinc(c[2], d)}; - } - - public static float spin(float c, int shift) { - float g = ((((int) Math.floor(c * 360)) + shift) % 360) / 360F; - return g < 0 ? 1f - g : g; - } - - public static float spinc(float c, int shift) { - float g = ((((int) Math.floor(c * 255)) + shift)) / 255F; - return Math.max(0f, Math.min(g, 1f)); - } - - public static java.awt.Color spin(java.awt.Color c, int h, int s, int b) { - float[] hsb = java.awt.Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); - hsb = spin(hsb, h, s, b); - return java.awt.Color.getHSBColor(hsb[0], hsb[1], hsb[2]); - } - - public static String spinToHex(C color, int h, int s, int b) { - return "#" + Integer.toHexString(spin(color.awtColor(), h, s, b).getRGB()).substring(2); - } - - public static String aura(String s, int hrad, int srad, int vrad) { - return aura(s, hrad, srad, vrad, 0.3D); - } - - public static String aura(String s, int hrad, int srad, int vrad, double pulse) { - StringBuilder b = new StringBuilder(); - boolean c = false; - - for (char i : s.toCharArray()) { - if (c) { - c = false; - - C o = C.getByChar(i); - - if (o.isColor() && (hrad != 0 || srad != 0 || vrad != 0)) { - if (pulse > 0) { - b.append(VolmitSender.pulse(spinToHex(o, hrad, srad, vrad), spinToHex(o, -hrad, -srad, -vrad), pulse)); - } else { - b.append(""); - } - } else { - b.append(C.getByChar(i).token); - } - - continue; - } - - if (i == C.COLOR_CHAR) { - c = true; - continue; - } - - b.append(i); - } - - return b.toString(); - } - - public static String compress(String c) { - return BaseComponent.toLegacyText(TextComponent.fromLegacyText(c)); - } - - /** - * Gets the color represented by the specified color code - * - * @param code Code to check - * @return Associative {@link org.bukkit.ChatColor} with the given code, or null - * if it doesn't exist - */ - public static C getByChar(char code) { - try { - C c = BY_CHAR.get(code); - return c == null ? C.WHITE : c; - } catch (Exception e) { - return C.WHITE; - } - } - - /** - * Gets the color represented by the specified color code - * - * @param code Code to check - * @return Associative {@link org.bukkit.ChatColor} with the given code, or null - * if it doesn't exist - */ - public static C getByChar(String code) { - try { - Validate.notNull(code, "Code cannot be null"); - Validate.isTrue(code.length() > 0, "Code must have at least one char"); - - return BY_CHAR.get(code.charAt(0)); - } catch (Exception e) { - return C.WHITE; - } - } - - /** - * Strips the given message of all color codes - * - * @param input String to strip of color - * @return A copy of the input string, without any coloring - */ - public static String stripColor(final String input) { - if (input == null) { - return null; - } - - return STRIP_COLOR_PATTERN.matcher(input).replaceAll(""); - } - - /** - * DyeColor to ChatColor - * - * @param dclr the dye color - * @return the color - */ - public static C dyeToChat(DyeColor dclr) { - if (dyeChatMap.containsKey(dclr)) { - return dyeChatMap.get(dclr); - } - - return C.MAGIC; - } - - public static DyeColor chatToDye(ChatColor color) { - for (Map.Entry entry : dyeChatMap.entrySet()) { - if (entry.getValue().toString().equals(color.toString())) { - return entry.getKey(); - } - } - - return DyeColor.BLACK; - } - - @SuppressWarnings("unlikely-arg-type") - public static String chatToHex(C clr) { - if (chatHexMap.containsKey(clr)) { - return chatHexMap.get(clr); - } - - return "#000000"; - } - - public static String dyeToHex(DyeColor clr) { - if (dyeHexMap.containsKey(clr)) { - return dyeHexMap.get(clr); - } - - return "#000000"; - } - - public static Color hexToColor(String hex) { - if (hex.startsWith("#")) { - hex = hex.substring(1); - } - - if (hex.contains("x")) { - hex = hex.substring(hex.indexOf("x")); - } - - if (hex.length() != 6 && hex.length() != 3) { - return null; - } - int sz = hex.length() / 3, mult = 1 << ((2 - sz) * 4), x = 0; - - for (int i = 0, z = 0; z < hex.length(); ++i, z += sz) { - x |= (mult * Integer.parseInt(hex.substring(z, z + sz), 16)) << (i * 8); - } - - return Color.fromBGR(x & 0xffffff); - } - - public static Color rgbToColor(String rgb) { - String[] parts = rgb.split("[^0-9]+"); - if (parts.length < 3) { - return null; - } - - int x = 0, i; - - for (i = 0; i < 3; ++i) { - x |= Integer.parseInt(parts[i]) << (i * 8); - } - - return Color.fromBGR(x & 0xffffff); - } - - public static String generateColorTable() { - StringBuilder str = new StringBuilder(); - - str.append(""); - - for (Map.Entry e : chatHexMap.entrySet()) { - str.append(String.format("" + "", e.getKey().name(), e.getValue())); - } - - str.append("
Chat ColorColor
%1$sTest String
"); - str.append(""); - for (Map.Entry e : dyeHexMap.entrySet()) { - str.append(String.format("" + "", e.getKey().name(), e.getValue())); - } - - str.append("
Dye ColorColor
%1$sTest String
"); - - return str.toString(); - } - - /** - * Translates a string using an alternate color code character into a string - * that uses the internal ChatColor.COLOR_CODE color code character. The - * alternate color code character will only be replaced if it is immediately - * followed by 0-9, A-F, a-f, K-O, k-o, R or r. - * - * @param altColorChar The alternate color code character to replace. Ex: {@literal &} - * @param textToTranslate Text containing the alternate color code character. - * @return Text containing the ChatColor.COLOR_CODE color code character. - */ - public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { - if (textToTranslate == null) { - return null; - } - - char[] b = textToTranslate.toCharArray(); - for (int i = 0; i < b.length - 1; i++) { - if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) { - b[i] = C.COLOR_CHAR; - b[i + 1] = Character.toLowerCase(b[i + 1]); - } - } - return new String(b); - } - - public static C fromItemMeta(byte c) { - for (C i : C.values()) { - if (i.getItemMeta() == c) { - return i; - } - } - - return null; - } - - public static C randomColor() { - return COLORS[(int) (Math.random() * (COLORS.length - 1))]; - } - - /** - * Gets the ChatColors used at the end of the given input string. - * - * @param input Input string to retrieve the colors from. - * @return Any remaining ChatColors to pass onto the next line. - */ - public static String getLastColors(String input) { - StringBuilder result = new StringBuilder(); - int length = input.length(); - - // Search backwards from the end as it is faster - for (int index = length - 1; index > -1; index--) { - char section = input.charAt(index); - if (section == COLOR_CHAR && index < length - 1) { - char c = input.charAt(index + 1); - C color = getByChar(c); - - if (color != null) { - result.insert(0, color); - - // Once we find a color or reset we can stop searching - if (color.isColor() || color.equals(RESET)) { - break; - } - } - } - } - - return result.toString(); - } - - public net.md_5.bungee.api.ChatColor asBungee() { - return net.md_5.bungee.api.ChatColor.RESET; - } - - /** - * Gets the char value associated with this color - * - * @return A char value of this color code - */ - public char getChar() { - return code; - } - - @Override - public String toString() { - return intCode == -1 ? token : toString; - } - - /** - * get the dye color for the chatcolor - */ - public DyeColor dye() { - return chatToDye(chatColor()); - } - - public String hex() { - return chatToHex(this); - } - - public java.awt.Color awtColor() { - return java.awt.Color.decode(hex()); - } - - /** - * Checks if this code is a format code as opposed to a color code. - * - * @return whether this ChatColor is a format code - */ - public boolean isFormat() { - return isFormat; - } - - /** - * Checks if this code is a color code as opposed to a format code. - * - * @return whether this ChatColor is a color code - */ - public boolean isColor() { - return !isFormat && this != RESET; - } - - /** - * Get the ChatColor enum instance instead of C - */ - public ChatColor chatColor() { - return ChatColor.getByChar(code); - } - - public byte getMeta() { - return switch (this) { - case AQUA -> (byte) 11; - case BLACK -> (byte) 0; - case BLUE, DARK_AQUA -> (byte) 9; - case BOLD, UNDERLINE, STRIKETHROUGH, RESET, MAGIC, ITALIC -> (byte) -1; - case DARK_BLUE -> (byte) 1; - case DARK_GRAY -> (byte) 8; - case DARK_GREEN -> (byte) 2; - case DARK_PURPLE -> (byte) 5; - case DARK_RED -> (byte) 4; - case GOLD -> (byte) 6; - case GRAY -> (byte) 7; - case GREEN -> (byte) 10; - case LIGHT_PURPLE -> (byte) 13; - case RED -> (byte) 12; - case YELLOW -> (byte) 14; - default -> (byte) 15; - }; - } - - public byte getItemMeta() { - return switch (this) { - case AQUA, DARK_AQUA -> (byte) 9; - case BLUE -> (byte) 3; - case BOLD, UNDERLINE, RESET, STRIKETHROUGH, MAGIC, ITALIC -> (byte) -1; - case DARK_BLUE -> (byte) 11; - case DARK_GRAY -> (byte) 7; - case DARK_GREEN -> (byte) 13; - case DARK_PURPLE -> (byte) 10; - case DARK_RED, RED -> (byte) 14; - case GOLD, YELLOW -> (byte) 4; - case GRAY -> (byte) 8; - case GREEN -> (byte) 5; - case LIGHT_PURPLE -> (byte) 2; - case WHITE -> (byte) 0; - default -> (byte) 15; - }; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/CDou.java b/src/main/java/com/volmit/adapt/util/CDou.java deleted file mode 100644 index ded39c473..000000000 --- a/src/main/java/com/volmit/adapt/util/CDou.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class CDou { - private final double max; - private double number; - - public CDou(double max) { - number = 0; - this.max = max; - } - - public CDou set(double n) { - number = n; - circ(); - return this; - } - - public CDou add(double a) { - number += a; - circ(); - return this; - } - - public CDou sub(double a) { - number -= a; - circ(); - return this; - } - - public double get() { - return number; - } - - public void circ() { - if (number < 0) { - number = max - (Math.abs(number) > max ? max : Math.abs(number)); - } - - number = number % (max); - } -} diff --git a/src/main/java/com/volmit/adapt/util/Callback.java b/src/main/java/com/volmit/adapt/util/Callback.java deleted file mode 100644 index 3e2d9215a..000000000 --- a/src/main/java/com/volmit/adapt/util/Callback.java +++ /dev/null @@ -1,35 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Callback for async workers - * - * @param the type of object to be returned in the runnable - * @author cyberpwn - */ -@FunctionalInterface -public interface Callback { - /** - * Called when the callback calls back... - * - * @param t the object to be called back - */ - void run(T t); -} diff --git a/src/main/java/com/volmit/adapt/util/CancellableTask.java b/src/main/java/com/volmit/adapt/util/CancellableTask.java deleted file mode 100644 index 07742af6c..000000000 --- a/src/main/java/com/volmit/adapt/util/CancellableTask.java +++ /dev/null @@ -1,23 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public interface CancellableTask { - void cancel(); -} diff --git a/src/main/java/com/volmit/adapt/util/ChronoLatch.java b/src/main/java/com/volmit/adapt/util/ChronoLatch.java deleted file mode 100644 index 70106e5a0..000000000 --- a/src/main/java/com/volmit/adapt/util/ChronoLatch.java +++ /dev/null @@ -1,50 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class ChronoLatch { - private final long interval; - private long since; - - public ChronoLatch(long interval, boolean openedAtStart) { - this.interval = interval; - since = System.currentTimeMillis() - (openedAtStart ? interval * 2 : 0); - } - - public ChronoLatch(long interval) { - this(interval, true); - } - - public void flipDown() { - since = System.currentTimeMillis(); - } - - public boolean couldFlip() { - return System.currentTimeMillis() - since > interval; - } - - public boolean flip() { - if (System.currentTimeMillis() - since > interval) { - since = System.currentTimeMillis(); - return true; - } - - return false; - } -} diff --git a/src/main/java/com/volmit/adapt/util/ChunkPosition.java b/src/main/java/com/volmit/adapt/util/ChunkPosition.java deleted file mode 100644 index a0adf69d3..000000000 --- a/src/main/java/com/volmit/adapt/util/ChunkPosition.java +++ /dev/null @@ -1,66 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class ChunkPosition { - private int x; - private int z; - - public ChunkPosition(int x, int z) { - this.x = x; - this.z = z; - } - - public int getX() { - return x; - } - - public void setX(int x) { - this.x = x; - } - - public int getZ() { - return z; - } - - public void setZ(int z) { - this.z = z; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + x; - result = prime * result + z; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof ChunkPosition)) { - return false; - } - ChunkPosition other = (ChunkPosition) obj; - return x == other.x && z == other.z; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Chunker.java b/src/main/java/com/volmit/adapt/util/Chunker.java deleted file mode 100644 index 4d2cb7d3a..000000000 --- a/src/main/java/com/volmit/adapt/util/Chunker.java +++ /dev/null @@ -1,70 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -public class Chunker { - private final List q; - private ExecutorService executor; - private int threads; - private int workload; - - public Chunker(List q) { - this.q = q; - } - - public Chunker threads(int threads) { - this.threads = threads; - return this; - } - - public Chunker workload(int workload) { - this.workload = workload; - return this; - } - - public void execute(Consumer consumer, Callback progress, int progressInterval) { - ChronoLatch cl = new ChronoLatch(progressInterval); - Contained consumed = new Contained(0); - executor = Executors.newFixedThreadPool(threads); - int length = q.size(); - int remaining = length; - - while (remaining > 0) { - int at = remaining; - remaining -= (remaining > workload ? workload : remaining); - int to = remaining; - - executor.submit(() -> - { - J.dofor(at, (i) -> i >= to, -1, (i) -> J.attempt(() -> consumer.accept(q.get(i)))); - consumed.mod((c) -> c += workload); - J.doif(() -> progress != null && cl.flip(), () -> progress.run((double) consumed.get() / (double) length)); - }); - } - - executor.shutdown(); - J.attempt(() -> executor.awaitTermination(100, TimeUnit.HOURS)); - } -} diff --git a/src/main/java/com/volmit/adapt/util/CommandDummy.java b/src/main/java/com/volmit/adapt/util/CommandDummy.java deleted file mode 100644 index 3a905424e..000000000 --- a/src/main/java/com/volmit/adapt/util/CommandDummy.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util; - -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionAttachment; -import org.bukkit.permissions.PermissionAttachmentInfo; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Set; -import java.util.UUID; - -public class CommandDummy implements CommandSender { - @Override - public void sendMessage(@NotNull String message) { - - } - - @Override - public void sendMessage(@NotNull String... messages) { - - } - - @Override - public void sendMessage(@Nullable UUID sender, @NotNull String message) { - - } - - @Override - public void sendMessage(@Nullable UUID sender, @NotNull String... messages) { - - } - - @NotNull - @Override - public Server getServer() { - return null; - } - - @NotNull - @Override - public String getName() { - return null; - } - - @NotNull - @Override - public Spigot spigot() { - return null; - } - - @Override - public boolean isPermissionSet(@NotNull String name) { - return false; - } - - @Override - public boolean isPermissionSet(@NotNull Permission perm) { - return false; - } - - @Override - public boolean hasPermission(@NotNull String name) { - return false; - } - - @Override - public boolean hasPermission(@NotNull Permission perm) { - return false; - } - - @NotNull - @Override - public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) { - return null; - } - - @NotNull - @Override - public PermissionAttachment addAttachment(@NotNull Plugin plugin) { - return null; - } - - @Nullable - @Override - public PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) { - return null; - } - - @Nullable - @Override - public PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) { - return null; - } - - @Override - public void removeAttachment(@NotNull PermissionAttachment attachment) { - - } - - @Override - public void recalculatePermissions() { - - } - - @NotNull - @Override - public Set getEffectivePermissions() { - return null; - } - - @Override - public boolean isOp() { - return false; - } - - @Override - public void setOp(boolean value) { - - } -} diff --git a/src/main/java/com/volmit/adapt/util/CompoundTag.java b/src/main/java/com/volmit/adapt/util/CompoundTag.java deleted file mode 100644 index f2a15a28a..000000000 --- a/src/main/java/com/volmit/adapt/util/CompoundTag.java +++ /dev/null @@ -1,67 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.Map; - -/** - * The TAG_Compound tag. - * - * @author Graham Edgecombe - */ -public final class CompoundTag extends Tag { - - /** - * The value. - */ - private final Map value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public CompoundTag(String name, Map value) { - super(name); - this.value = value; - } - - @Override - public Map getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - StringBuilder bldr = new StringBuilder(); - bldr.append("TAG_Compound" + append + ": " + value.size() + " entries\r\n{\r\n"); - for (Map.Entry entry : value.entrySet()) { - bldr.append(" " + entry.getValue().toString().replaceAll("\r\n", "\r\n ") + "\r\n"); - } - bldr.append("}"); - return bldr.toString(); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/ConfigRewriteReporter.java b/src/main/java/com/volmit/adapt/util/ConfigRewriteReporter.java deleted file mode 100644 index 18b4c9b38..000000000 --- a/src/main/java/com/volmit/adapt/util/ConfigRewriteReporter.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.volmit.adapt.util; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.config.ConfigFileSupport; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -public class ConfigRewriteReporter { - private static final int MAX_KEYS_PER_CATEGORY = 8; - private static final String MISSING = ""; - private static final String REMOVED = ""; - - public static void reportRewrite(File file, String source, String beforeRaw, String afterRaw) { - String before = normalize(beforeRaw); - String after = normalize(afterRaw); - if (Objects.equals(before, after)) { - return; - } - - List changes = computeDiff(before, after); - String path = relativize(file); - String sourceTag = source == null || source.isBlank() ? "config" : source; - - if (changes.isEmpty()) { - Adapt.info("Canonicalized " + sourceTag + " [" + path + "] (format/order only)."); - return; - } - - int removed = 0; - int added = 0; - int changed = 0; - List removedKeys = new ArrayList<>(); - List addedKeys = new ArrayList<>(); - List changedKeys = new ArrayList<>(); - for (Change changeEntry : changes) { - if (changeEntry.type == ChangeType.REMOVED) { - removed++; - removedKeys.add(changeEntry.key); - } else if (changeEntry.type == ChangeType.ADDED) { - added++; - addedKeys.add(changeEntry.key); - } else { - changed++; - changedKeys.add(changeEntry.key); - } - } - - Adapt.warn("Canonicalized " + sourceTag + " [" + path + "] with schema changes (removed=" + removed + ", added=" + added + ", changed=" + changed + ")."); - if (!removedKeys.isEmpty()) { - Adapt.warn(" - removed keys: " + summarizeKeys(removedKeys)); - } - if (!addedKeys.isEmpty()) { - Adapt.info(" - added keys: " + summarizeKeys(addedKeys)); - } - if (!changedKeys.isEmpty()) { - Adapt.info(" - changed keys: " + summarizeKeys(changedKeys)); - } - } - - public static void reportFallbackRewrite(File file, String source, String reason) { - String path = relativize(file); - String sourceTag = source == null || source.isBlank() ? "config" : source; - String reasonText = reason == null || reason.isBlank() ? "invalid/unsupported content" : reason; - Adapt.warn("Rewrote " + sourceTag + " [" + path + "] using fallback defaults (" + reasonText + ")."); - } - - private static List computeDiff(String before, String after) { - Map left = flattenForDiff(before); - Map right = flattenForDiff(after); - Set keys = new HashSet<>(left.keySet()); - keys.addAll(right.keySet()); - - List ordered = new ArrayList<>(keys); - ordered.sort(String::compareTo); - - List out = new ArrayList<>(); - for (String key : ordered) { - boolean inLeft = left.containsKey(key); - boolean inRight = right.containsKey(key); - String oldValue = inLeft ? left.get(key) : MISSING; - String newValue = inRight ? right.get(key) : REMOVED; - if (Objects.equals(oldValue, newValue)) { - continue; - } - - ChangeType type; - if (inLeft && !inRight) { - type = ChangeType.REMOVED; - } else if (!inLeft && inRight) { - type = ChangeType.ADDED; - } else { - type = ChangeType.CHANGED; - } - - out.add(new Change(type, key)); - } - - return out; - } - - private static Map flattenForDiff(String raw) { - JsonElement element = parseStructured(raw); - if (element == null) { - Map fallback = new HashMap<>(); - if (raw != null && !raw.isBlank()) { - fallback.put("$", normalize(raw)); - } - return fallback; - } - - Map out = new HashMap<>(); - flattenJson("$", element, out); - return out; - } - - private static void flattenJson(String path, JsonElement element, Map out) { - if (element == null || element.isJsonNull()) { - out.put(path, "null"); - return; - } - - if (element.isJsonPrimitive()) { - out.put(path, element.toString()); - return; - } - - if (element.isJsonArray()) { - if (element.getAsJsonArray().size() == 0) { - out.put(path, "[]"); - return; - } - - for (int i = 0; i < element.getAsJsonArray().size(); i++) { - flattenJson(path + "[" + i + "]", element.getAsJsonArray().get(i), out); - } - return; - } - - JsonObject object = element.getAsJsonObject(); - if (object.entrySet().isEmpty()) { - out.put(path, "{}"); - return; - } - - for (Map.Entry entry : object.entrySet()) { - flattenJson(path + "." + entry.getKey(), entry.getValue(), out); - } - } - - private static JsonElement parseStructured(String raw) { - if (raw == null || raw.isBlank()) { - return null; - } - - return ConfigFileSupport.parseToJsonElement(raw, null); - } - - private static String summarizeKeys(List keys) { - if (keys == null || keys.isEmpty()) { - return "(none)"; - } - - int shown = Math.min(MAX_KEYS_PER_CATEGORY, keys.size()); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < shown; i++) { - if (i > 0) { - sb.append(", "); - } - sb.append(keys.get(i)); - } - - if (keys.size() > shown) { - sb.append(" (+").append(keys.size() - shown).append(" more)"); - } - - return sb.toString(); - } - - private static String normalize(String json) { - if (json == null) { - return null; - } - return ConfigFileSupport.normalize(json); - } - - private static String relativize(File file) { - if (file == null) { - return ""; - } - - try { - File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); - if (dataFolder == null) { - return file.getPath(); - } - return dataFolder.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '/'); - } catch (Throwable ignored) { - return file.getPath(); - } - } - - private enum ChangeType { - REMOVED, - ADDED, - CHANGED - } - - private static class Change { - private final ChangeType type; - private final String key; - - private Change(ChangeType type, String key) { - this.type = type; - this.key = key; - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/Consumer2.java b/src/main/java/com/volmit/adapt/util/Consumer2.java deleted file mode 100644 index 63cd93ce3..000000000 --- a/src/main/java/com/volmit/adapt/util/Consumer2.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Consumer2 { - void accept(A a, B b); -} diff --git a/src/main/java/com/volmit/adapt/util/Consumer3.java b/src/main/java/com/volmit/adapt/util/Consumer3.java deleted file mode 100644 index b76d47117..000000000 --- a/src/main/java/com/volmit/adapt/util/Consumer3.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Consumer3 { - void accept(A a, B b, C c); -} diff --git a/src/main/java/com/volmit/adapt/util/Consumer4.java b/src/main/java/com/volmit/adapt/util/Consumer4.java deleted file mode 100644 index 101000f14..000000000 --- a/src/main/java/com/volmit/adapt/util/Consumer4.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Consumer4 { - void accept(A a, B b, C c, D d); -} diff --git a/src/main/java/com/volmit/adapt/util/Consumer5.java b/src/main/java/com/volmit/adapt/util/Consumer5.java deleted file mode 100644 index 7fdfebe84..000000000 --- a/src/main/java/com/volmit/adapt/util/Consumer5.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Consumer5 { - void accept(A a, B b, C c, D d, E e); -} diff --git a/src/main/java/com/volmit/adapt/util/Consumer6.java b/src/main/java/com/volmit/adapt/util/Consumer6.java deleted file mode 100644 index 6ec98531a..000000000 --- a/src/main/java/com/volmit/adapt/util/Consumer6.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Consumer6 { - void accept(A a, B b, C c, D d, E e, F f); -} diff --git a/src/main/java/com/volmit/adapt/util/Consumer8.java b/src/main/java/com/volmit/adapt/util/Consumer8.java deleted file mode 100644 index c99dd303a..000000000 --- a/src/main/java/com/volmit/adapt/util/Consumer8.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Consumer8 { - void accept(A a, B b, C c, D d, E e, F f, G g, H h); -} diff --git a/src/main/java/com/volmit/adapt/util/Contained.java b/src/main/java/com/volmit/adapt/util/Contained.java deleted file mode 100644 index 358cd73a4..000000000 --- a/src/main/java/com/volmit/adapt/util/Contained.java +++ /dev/null @@ -1,45 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.function.Function; - -public class Contained { - private T t; - - public Contained(T t) { - set(t); - } - - public Contained() { - this(null); - } - - public void mod(Function x) { - set(x.apply(t)); - } - - public T get() { - return t; - } - - public void set(T t) { - this.t = t; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Converter.java b/src/main/java/com/volmit/adapt/util/Converter.java deleted file mode 100644 index d33e8af4c..000000000 --- a/src/main/java/com/volmit/adapt/util/Converter.java +++ /dev/null @@ -1,29 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.File; - -public interface Converter { - String getInExtension(); - - String getOutExtension(); - - void convert(File in, File out); -} diff --git a/src/main/java/com/volmit/adapt/util/Cuboid.java b/src/main/java/com/volmit/adapt/util/Cuboid.java deleted file mode 100644 index 62a820585..000000000 --- a/src/main/java/com/volmit/adapt/util/Cuboid.java +++ /dev/null @@ -1,772 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.configuration.serialization.ConfigurationSerializable; -import org.bukkit.entity.Entity; - -import java.util.*; - -/** - * Cuboids - * - * @author cyberpwn - */ -public class Cuboid implements Iterable, Cloneable, ConfigurationSerializable { - protected final String worldName; - protected int x1, y1, z1; - protected int x2, y2, z2; - - /** - * Construct a Cuboid given two Location objects which represent any two corners - * of the Cuboid. - * - * @param l1 one of the corners - * @param l2 the other corner - */ - public Cuboid(Location l1, Location l2) { - if (!l1.getWorld().equals(l2.getWorld())) { - throw new IllegalArgumentException("locations must be on the same world"); - } - - worldName = l1.getWorld().getName(); - x1 = Math.min(l1.getBlockX(), l2.getBlockX()); - y1 = Math.min(l1.getBlockY(), l2.getBlockY()); - z1 = Math.min(l1.getBlockZ(), l2.getBlockZ()); - x2 = Math.max(l1.getBlockX(), l2.getBlockX()); - y2 = Math.max(l1.getBlockY(), l2.getBlockY()); - z2 = Math.max(l1.getBlockZ(), l2.getBlockZ()); - } - - /** - * Construct a one-block Cuboid at the given Location of the Cuboid. - * - * @param l1 location of the Cuboid - */ - public Cuboid(Location l1) { - this(l1, l1); - } - - /** - * Copy constructor. - * - * @param other the Cuboid to copy - */ - public Cuboid(Cuboid other) { - this(other.getWorld().getName(), other.x1, other.y1, other.z1, other.x2, other.y2, other.z2); - } - - /** - * Construct a Cuboid in the given World and xyz co-ordinates - * - * @param world the Cuboid's world - * @param x1 X co-ordinate of corner 1 - * @param y1 Y co-ordinate of corner 1 - * @param z1 Z co-ordinate of corner 1 - * @param x2 X co-ordinate of corner 2 - * @param y2 Y co-ordinate of corner 2 - * @param z2 Z co-ordinate of corner 2 - */ - public Cuboid(World world, int x1, int y1, int z1, int x2, int y2, int z2) { - this.worldName = world.getName(); - this.x1 = Math.min(x1, x2); - this.x2 = Math.max(x1, x2); - this.y1 = Math.min(y1, y2); - this.y2 = Math.max(y1, y2); - this.z1 = Math.min(z1, z2); - this.z2 = Math.max(z1, z2); - } - - /** - * Construct a Cuboid in the given world name and xyz co-ordinates. - * - * @param worldName the Cuboid's world name - * @param x1 X co-ordinate of corner 1 - * @param y1 Y co-ordinate of corner 1 - * @param z1 Z co-ordinate of corner 1 - * @param x2 X co-ordinate of corner 2 - * @param y2 Y co-ordinate of corner 2 - * @param z2 Z co-ordinate of corner 2 - */ - private Cuboid(String worldName, int x1, int y1, int z1, int x2, int y2, int z2) { - this.worldName = worldName; - this.x1 = Math.min(x1, x2); - this.x2 = Math.max(x1, x2); - this.y1 = Math.min(y1, y2); - this.y2 = Math.max(y1, y2); - this.z1 = Math.min(z1, z2); - this.z2 = Math.max(z1, z2); - } - - public Cuboid(Map map) { - worldName = (String) map.get("worldName"); - x1 = (Integer) map.get("x1"); - x2 = (Integer) map.get("x2"); - y1 = (Integer) map.get("y1"); - y2 = (Integer) map.get("y2"); - z1 = (Integer) map.get("z1"); - z2 = (Integer) map.get("z2"); - } - - public List getEntities() { - List en = new ArrayList<>(); - - for (Chunk i : getChunks()) { - for (Entity j : i.getEntities()) { - if (contains(j.getLocation())) { - en.add(j); - } - } - } - - return en; - } - - /** - * Set the locations - * - * @param l1 a - * @param l2 b - */ - public void set(Location l1, Location l2) { - x1 = Math.min(l1.getBlockX(), l2.getBlockX()); - y1 = Math.min(l1.getBlockY(), l2.getBlockY()); - z1 = Math.min(l1.getBlockZ(), l2.getBlockZ()); - x2 = Math.max(l1.getBlockX(), l2.getBlockX()); - y2 = Math.max(l1.getBlockY(), l2.getBlockY()); - z2 = Math.max(l1.getBlockZ(), l2.getBlockZ()); - } - - @Override - public Map serialize() { - Map map = new HashMap(); - map.put("worldName", worldName); - map.put("x1", x1); - map.put("y1", y1); - map.put("z1", z1); - map.put("x2", x2); - map.put("y2", y2); - map.put("z2", z2); - return map; - } - - public Cuboid flatten(int level) { - return new Cuboid(getWorld(), x1, level, z1, x2, level, z2); - } - - /** - * Get the Location of the lower northeast corner of the Cuboid (minimum XYZ - * co-ordinates). - * - * @return Location of the lower northeast corner - */ - public Location getLowerNE() { - return new Location(getWorld(), x1, y1, z1); - } - - /** - * Get the Location of the upper southwest corner of the Cuboid (maximum XYZ - * co-ordinates). - * - * @return Location of the upper southwest corner - */ - public Location getUpperSW() { - return new Location(getWorld(), x2, y2, z2); - } - - /** - * Get the the centre of the Cuboid - * - * @return Location at the centre of the Cuboid - */ - public Location getCenter() { - int x1 = getUpperX() + 1; - int y1 = getUpperY() + 1; - int z1 = getUpperZ() + 1; - return new Location(getWorld(), getLowerX() + (x1 - getLowerX()) / 2.0, getLowerY() + (y1 - getLowerY()) / 2.0, getLowerZ() + (z1 - getLowerZ()) / 2.0); - } - - /** - * Get the Cuboid's world. - * - * @return the World object representing this Cuboid's world - * @throws IllegalStateException if the world is not loaded - */ - public World getWorld() { - World world = Bukkit.getWorld(worldName); - if (world == null) { - throw new IllegalStateException("world '" + worldName + "' is not loaded"); - } - return world; - } - - /** - * Get the size of this Cuboid along the X axis - * - * @return Size of Cuboid along the X axis - */ - public int getSizeX() { - return (x2 - x1) + 1; - } - - /** - * Get the size of this Cuboid along the Y axis - * - * @return Size of Cuboid along the Y axis - */ - public int getSizeY() { - return (y2 - y1) + 1; - } - - /** - * Get the size of this Cuboid along the Z axis - * - * @return Size of Cuboid along the Z axis - */ - public int getSizeZ() { - return (z2 - z1) + 1; - } - - /** - * Get the cuboid dimensions - * - * @return the dimensions - */ - public Dimension getDimension() { - return new Dimension(getSizeX(), getSizeY(), getSizeZ()); - } - - /** - * Get the minimum X co-ordinate of this Cuboid - * - * @return the minimum X co-ordinate - */ - public int getLowerX() { - return x1; - } - - /** - * Get the minimum Y co-ordinate of this Cuboid - * - * @return the minimum Y co-ordinate - */ - public int getLowerY() { - return y1; - } - - /** - * Get the minimum Z co-ordinate of this Cuboid - * - * @return the minimum Z co-ordinate - */ - public int getLowerZ() { - return z1; - } - - /** - * Get the maximum X co-ordinate of this Cuboid - * - * @return the maximum X co-ordinate - */ - public int getUpperX() { - return x2; - } - - /** - * Get the maximum Y co-ordinate of this Cuboid - * - * @return the maximum Y co-ordinate - */ - public int getUpperY() { - return y2; - } - - /** - * Get the maximum Z co-ordinate of this Cuboid - * - * @return the maximum Z co-ordinate - */ - public int getUpperZ() { - return z2; - } - - /** - * Get the Blocks at the eight corners of the Cuboid. - * - * @return array of Block objects representing the Cuboid corners - */ - public Block[] corners() { - Block[] res = new Block[8]; - World w = getWorld(); - res[0] = w.getBlockAt(x1, y1, z1); - res[1] = w.getBlockAt(x1, y1, z2); - res[2] = w.getBlockAt(x1, y2, z1); - res[3] = w.getBlockAt(x1, y2, z2); - res[4] = w.getBlockAt(x2, y1, z1); - res[5] = w.getBlockAt(x2, y1, z2); - res[6] = w.getBlockAt(x2, y2, z1); - res[7] = w.getBlockAt(x2, y2, z2); - return res; - } - - /** - * Expand the Cuboid in the given direction by the given amount. Negative - * amounts will shrink the Cuboid in the given direction. Shrinking a cuboid's - * face past the opposite face is not an error and will return a valid Cuboid. - * - * @param dir the direction in which to expand - * @param amount the number of blocks by which to expand - * @return a new Cuboid expanded by the given direction and amount - */ - public Cuboid expand(CuboidDirection dir, int amount) { - switch (dir) { - case North: - return new Cuboid(worldName, x1 - amount, y1, z1, x2, y2, z2); - case South: - return new Cuboid(worldName, x1, y1, z1, x2 + amount, y2, z2); - case East: - return new Cuboid(worldName, x1, y1, z1 - amount, x2, y2, z2); - case West: - return new Cuboid(worldName, x1, y1, z1, x2, y2, z2 + amount); - case Down: - return new Cuboid(worldName, x1, y1 - amount, z1, x2, y2, z2); - case Up: - return new Cuboid(worldName, x1, y1, z1, x2, y2 + amount, z2); - default: - throw new IllegalArgumentException("invalid direction " + dir); - } - } - - public Cuboid expand(Direction dir, int amount) { - int ax = dir.toVector().getBlockX() == 1 ? amount : 0; - int sx = dir.toVector().getBlockX() == -1 ? -amount : 0; - int ay = dir.toVector().getBlockY() == 1 ? amount : 0; - int sy = dir.toVector().getBlockY() == -1 ? -amount : 0; - int az = dir.toVector().getBlockZ() == 1 ? amount : 0; - int sz = dir.toVector().getBlockZ() == -1 ? -amount : 0; - return new Cuboid(worldName, x1 + sx, y1 + sy, z1 + sz, x2 + ax, y2 + ay, z2 + az); - } - - /** - * Shift the Cuboid in the given direction by the given amount. - * - * @param dir the direction in which to shift - * @param amount the number of blocks by which to shift - * @return a new Cuboid shifted by the given direction and amount - */ - public Cuboid shift(CuboidDirection dir, int amount) { - return expand(dir, amount).expand(dir.opposite(), -amount); - } - - /** - * Outset (grow) the Cuboid in the given direction by the given amount. - * - * @param dir the direction in which to outset (must be Horizontal, Vertical, or - * Both) - * @param amount the number of blocks by which to outset - * @return a new Cuboid outset by the given direction and amount - */ - public Cuboid outset(CuboidDirection dir, int amount) { - Cuboid c; - switch (dir) { - case Horizontal: - c = expand(CuboidDirection.North, amount).expand(CuboidDirection.South, amount).expand(CuboidDirection.East, amount).expand(CuboidDirection.West, amount); - break; - case Vertical: - c = expand(CuboidDirection.Down, amount).expand(CuboidDirection.Up, amount); - break; - case Both: - c = outset(CuboidDirection.Horizontal, amount).outset(CuboidDirection.Vertical, amount); - break; - default: - throw new IllegalArgumentException("invalid direction " + dir); - } - return c; - } - - /** - * Inset (shrink) the Cuboid in the given direction by the given amount. - * Equivalent to calling outset() with a negative amount. - * - * @param dir the direction in which to inset (must be Horizontal, Vertical, or - * Both) - * @param amount the number of blocks by which to inset - * @return a new Cuboid inset by the given direction and amount - */ - public Cuboid inset(CuboidDirection dir, int amount) { - return outset(dir, -amount); - } - - /** - * Return true if the point at (x,y,z) is contained within this Cuboid. - * - * @param x the X co-ordinate - * @param y the Y co-ordinate - * @param z the Z co-ordinate - * @return true if the given point is within this Cuboid, false otherwise - */ - public boolean contains(int x, int y, int z) { - return x >= x1 && x <= x2 && y >= y1 && y <= y2 && z >= z1 && z <= z2; - } - - /** - * Check if the given Block is contained within this Cuboid. - * - * @param b the Block to check for - * @return true if the Block is within this Cuboid, false otherwise - */ - public boolean contains(Block b) { - return contains(b.getLocation()); - } - - /** - * Check if the given Location is contained within this Cuboid. - * - * @param l the Location to check for - * @return true if the Location is within this Cuboid, false otherwise - */ - public boolean contains(Location l) { - return worldName.equals(l.getWorld().getName()) && contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()); - } - - /** - * Get the volume of this Cuboid. - * - * @return the Cuboid volume, in blocks - */ - public int volume() { - return getSizeX() * getSizeY() * getSizeZ(); - } - - /** - * Get the average light level of all empty (air) blocks in the Cuboid. Returns - * 0 if there are no empty blocks. - * - * @return the average light level of this Cuboid - */ - public byte averageLightLevel() { - long total = 0; - int n = 0; - for (Block b : this) { - if (b.isEmpty()) { - total += b.getLightLevel(); - ++n; - } - } - return n > 0 ? (byte) (total / n) : 0; - } - - /** - * Contract the Cuboid, returning a Cuboid with any air around the edges - * removed, just large enough to include all non-air blocks. - * - * @return a new Cuboid with no external air blocks - */ - public Cuboid contract() { - return this.contract(CuboidDirection.Down).contract(CuboidDirection.South).contract(CuboidDirection.East).contract(CuboidDirection.Up).contract(CuboidDirection.North).contract(CuboidDirection.West); - } - - /** - * Contract the Cuboid in the given direction, returning a new Cuboid which has - * no exterior empty space. E.g. a direction of Down will push the top face - * downwards as much as possible. - * - * @param dir the direction in which to contract - * @return a new Cuboid contracted in the given direction - */ - public Cuboid contract(CuboidDirection dir) { - Cuboid face = getFace(dir.opposite()); - switch (dir) { - case Down: - while (face.containsOnly(Material.AIR) && face.getLowerY() > this.getLowerY()) { - face = face.shift(CuboidDirection.Down, 1); - } - return new Cuboid(worldName, x1, y1, z1, x2, face.getUpperY(), z2); - case Up: - while (face.containsOnly(Material.AIR) && face.getUpperY() < this.getUpperY()) { - face = face.shift(CuboidDirection.Up, 1); - } - return new Cuboid(worldName, x1, face.getLowerY(), z1, x2, y2, z2); - case North: - while (face.containsOnly(Material.AIR) && face.getLowerX() > this.getLowerX()) { - face = face.shift(CuboidDirection.North, 1); - } - return new Cuboid(worldName, x1, y1, z1, face.getUpperX(), y2, z2); - case South: - while (face.containsOnly(Material.AIR) && face.getUpperX() < this.getUpperX()) { - face = face.shift(CuboidDirection.South, 1); - } - return new Cuboid(worldName, face.getLowerX(), y1, z1, x2, y2, z2); - case East: - while (face.containsOnly(Material.AIR) && face.getLowerZ() > this.getLowerZ()) { - face = face.shift(CuboidDirection.East, 1); - } - return new Cuboid(worldName, x1, y1, z1, x2, y2, face.getUpperZ()); - case West: - while (face.containsOnly(Material.AIR) && face.getUpperZ() < this.getUpperZ()) { - face = face.shift(CuboidDirection.West, 1); - } - return new Cuboid(worldName, x1, y1, face.getLowerZ(), x2, y2, z2); - default: - throw new IllegalArgumentException("Invalid direction " + dir); - } - } - - /** - * Get the Cuboid representing the face of this Cuboid. The resulting Cuboid - * will be one block thick in the axis perpendicular to the requested face. - * - * @param dir which face of the Cuboid to get - * @return the Cuboid representing this Cuboid's requested face - */ - public Cuboid getFace(CuboidDirection dir) { - switch (dir) { - case Down: - return new Cuboid(worldName, x1, y1, z1, x2, y1, z2); - case Up: - return new Cuboid(worldName, x1, y2, z1, x2, y2, z2); - case North: - return new Cuboid(worldName, x1, y1, z1, x1, y2, z2); - case South: - return new Cuboid(worldName, x2, y1, z1, x2, y2, z2); - case East: - return new Cuboid(worldName, x1, y1, z1, x2, y2, z1); - case West: - return new Cuboid(worldName, x1, y1, z2, x2, y2, z2); - default: - throw new IllegalArgumentException("Invalid direction " + dir); - } - } - - /** - * Check if the Cuboid contains only blocks of the given type - * - * @param material the material to check for - * @return true if this Cuboid contains only blocks of the given type - */ - public boolean containsOnly(Material material) { - for (Block b : this) { - if (b.getType() != material) { - return false; - } - } - return true; - } - - /** - * Get the Cuboid big enough to hold both this Cuboid and the given one. - * - * @param other the other Cuboid to include - * @return a new Cuboid large enough to hold this Cuboid and the given Cuboid - */ - public Cuboid getBoundingCuboid(Cuboid other) { - if (other == null) { - return this; - } - - int xMin = Math.min(getLowerX(), other.getLowerX()); - int yMin = Math.min(getLowerY(), other.getLowerY()); - int zMin = Math.min(getLowerZ(), other.getLowerZ()); - int xMax = Math.max(getUpperX(), other.getUpperX()); - int yMax = Math.max(getUpperY(), other.getUpperY()); - int zMax = Math.max(getUpperZ(), other.getUpperZ()); - - return new Cuboid(worldName, xMin, yMin, zMin, xMax, yMax, zMax); - } - - /** - * Get a block relative to the lower NE point of the Cuboid. - * - * @param x the X co-ordinate - * @param y the Y co-ordinate - * @param z the Z co-ordinate - * @return the block at the given position - */ - public Block getRelativeBlock(int x, int y, int z) { - return getWorld().getBlockAt(x1 + x, y1 + y, z1 + z); - } - - /** - * Get a block relative to the lower NE point of the Cuboid in the given World. - * This version of getRelativeBlock() should be used if being called many times, - * to avoid excessive calls to getWorld(). - * - * @param w the World - * @param x the X co-ordinate - * @param y the Y co-ordinate - * @param z the Z co-ordinate - * @return the block at the given position - */ - public Block getRelativeBlock(World w, int x, int y, int z) { - return w.getBlockAt(x1 + x, y1 + y, z1 + z); - } - - /** - * Get a list of the chunks which are fully or partially contained in this - * cuboid. - * - * @return a list of Chunk objects - */ - public List getChunks() { - List res = new ArrayList(); - - World w = getWorld(); - int x1 = getLowerX() & ~0xf; - int x2 = getUpperX() & ~0xf; - int z1 = getLowerZ() & ~0xf; - int z2 = getUpperZ() & ~0xf; - for (int x = x1; x <= x2; x += 16) { - for (int z = z1; z <= z2; z += 16) { - res.add(w.getChunkAt(x >> 4, z >> 4)); - } - } - return res; - } - - /** - * Set all the blocks within the Cuboid to the given MaterialData, using a - * MassBlockUpdate object for fast updates. - * - * @param mat - * the MaterialData to set - * @param mbu - * the MassBlockUpdate object - */ - - /** - * Reset the light level of all blocks within this Cuboid. - */ - - /* - * (non-Javadoc) - * - * @see java.lang.Iterable#iterator() - */ - @Override - public Iterator iterator() { - return new CuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#clone() - */ - @Override - public Cuboid clone() throws CloneNotSupportedException { - return new Cuboid(this); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "Cuboid: " + worldName + "," + x1 + "," + y1 + "," + z1 + "=>" + x2 + "," + y2 + "," + z2; - } - - public enum CuboidDirection { - - North, - East, - South, - West, - Up, - Down, - Horizontal, - Vertical, - Both, - Unknown; - - public CuboidDirection opposite() { - switch (this) { - case North: - return South; - case East: - return West; - case South: - return North; - case West: - return East; - case Horizontal: - return Vertical; - case Vertical: - return Horizontal; - case Up: - return Down; - case Down: - return Up; - case Both: - return Both; - default: - return Unknown; - } - } - } - - public class CuboidIterator implements Iterator { - private final World w; - private final int baseX; - private final int baseY; - private final int baseZ; - private final int sizeX; - private final int sizeY; - private final int sizeZ; - private int x, y, z; - - public CuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) { - this.w = w; - baseX = x1; - baseY = y1; - baseZ = z1; - sizeX = Math.abs(x2 - x1) + 1; - sizeY = Math.abs(y2 - y1) + 1; - sizeZ = Math.abs(z2 - z1) + 1; - x = y = z = 0; - } - - @Override - public boolean hasNext() { - return x < sizeX && y < sizeY && z < sizeZ; - } - - @Override - public Block next() { - Block b = w.getBlockAt(baseX + x, baseY + y, baseZ + z); - if (++x >= sizeX) { - x = 0; - if (++y >= sizeY) { - y = 0; - ++z; - } - } - return b; - } - - @Override - public void remove() { - // nop - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/CuboidException.java b/src/main/java/com/volmit/adapt/util/CuboidException.java deleted file mode 100644 index 16b5656a8..000000000 --- a/src/main/java/com/volmit/adapt/util/CuboidException.java +++ /dev/null @@ -1,32 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Represents a cuboid exception - * - * @author cyberpwn - */ -public class CuboidException extends Exception { - private static final long serialVersionUID = 1L; - - public CuboidException(String string) { - super(string); - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/CustomModel.java b/src/main/java/com/volmit/adapt/util/CustomModel.java deleted file mode 100644 index 2dbd524ff..000000000 --- a/src/main/java/com/volmit/adapt/util/CustomModel.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.volmit.adapt.util; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.version.Version; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.config.ConfigFileSupport; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.inventory.ItemStack; - -import java.io.File; -import java.io.IOException; - -import static com.volmit.adapt.Adapt.instance; - -public record CustomModel(Material material, int model, NamespacedKey modelKey) { - public static final NamespacedKey EMPTY_KEY = NamespacedKey.minecraft("empty"); - private static UpdateChecker updateChecker = null; - - public ItemStack toItemStack() { - return toItemStack(new ItemStack(material)); - } - - public ItemStack toItemStack(ItemStack itemStack) { - var meta = itemStack.getItemMeta(); - if (meta == null || model == 0) - return itemStack; - - Version.get().applyModel(this, meta); - itemStack.setItemMeta(meta); - return itemStack; - } - - public static CustomModel get(Material fallback, String... path) { - if (!AdaptConfig.get().isCustomModels()) - return new CustomModel(fallback, 0, null); - - if (updateChecker == null) - updateChecker = new UpdateChecker(); - return updateChecker.get(fallback, path); - } - - public static void clear() { - if (updateChecker == null) - return; - updateChecker = null; - } - - public static boolean reloadFromDisk() { - return reloadFromDisk(false); - } - - public static boolean reloadFromDisk(boolean quiet) { - if (updateChecker == null) - updateChecker = new UpdateChecker(); - - return updateChecker.reloadFromDisk(quiet); - } - - private static class UpdateChecker { - private final Object lock = new Object(); - private final File modelsFile; - private final File legacyModelsFile; - private final KMap cache = new KMap<>(); - private JsonObject json = new JsonObject(); - - public UpdateChecker() { - modelsFile = instance.getDataFile("adapt", "models.toml"); - legacyModelsFile = instance.getDataFile("adapt", "models.json"); - - try { - readFile(); - } catch (IOException e) { - Adapt.error("Failed to read models.toml"); - e.printStackTrace(); - } - } - - public boolean reloadFromDisk(boolean quiet) { - synchronized (lock) { - try { - readFile(); - cache.clear(); - return true; - } catch (IOException e) { - if (!quiet) { - Adapt.error("Failed to read models.toml"); - e.printStackTrace(); - } - return false; - } - } - } - - public CustomModel get(Material fallback, String... path) { - return cache.computeIfAbsent(String.join("", path), k -> { - var json = this.json; - for (var s : path) { - if (!json.has(s)) - return set(new CustomModel(fallback, 0, EMPTY_KEY), path); - var v = json.get(s); - if (!v.isJsonObject()) { - Adapt.warn("Invalid json at path: " + String.join(".", path)); - return new CustomModel(fallback, 0, EMPTY_KEY); - } - json = v.getAsJsonObject(); - } - - return new CustomModel( - json.has("material") ? Material.valueOf(json.get("material").getAsString()) : fallback, - json.has("model") ? json.get("model").getAsInt() : 0, - json.has("modelKey") ? NamespacedKey.fromString(json.get("modelKey").getAsString()) : EMPTY_KEY - ); - }); - } - - public CustomModel set(CustomModel data, String... path) { - var json = this.json; - for (var s : path) { - if (!json.has(s)) - json.add(s, new JsonObject()); - - var v = json.get(s); - if (!v.isJsonObject()) { - v = new JsonObject(); - json.add(s, v); - } - json = v.getAsJsonObject(); - } - - json.addProperty("material", data.material.name()); - json.addProperty("model", data.model); - json.addProperty("modelKey", (data.modelKey == null ? EMPTY_KEY : data.modelKey).toString()); - - try { - writeFile(); - } catch (IOException e) { - Adapt.error("Failed to write models.toml"); - e.printStackTrace(); - } - return data; - } - - public void readFile() throws IOException { - synchronized (lock) { - if (modelsFile.exists()) { - String raw = IO.readAll(modelsFile); - JsonElement parsed = ConfigFileSupport.parseToJsonElement(raw, modelsFile); - if (parsed == null || !parsed.isJsonObject()) { - throw new IOException("Invalid models.toml"); - } - - json = parsed.getAsJsonObject(); - ConfigFileSupport.deleteLegacyFileIfMigrated(modelsFile, legacyModelsFile, "models-config"); - return; - } - - if (legacyModelsFile.exists()) { - String legacyRaw = IO.readAll(legacyModelsFile); - JsonObject legacy = Json.fromJson(legacyRaw, JsonObject.class); - if (legacy == null) { - throw new IOException("Invalid models.json"); - } - json = legacy; - IO.writeAll(modelsFile, ConfigFileSupport.serializeJsonElementToToml(legacy)); - Adapt.info("Migrated legacy config [adapt/models.json] -> [adapt/models.toml]."); - ConfigFileSupport.deleteLegacyFileIfMigrated(modelsFile, legacyModelsFile, "models-config"); - return; - } - - json = new JsonObject(); - IO.writeAll(modelsFile, ConfigFileSupport.serializeJsonElementToToml(json)); - Adapt.info("Created missing models config [adapt/models.toml] from defaults."); - } - } - - public void writeFile() throws IOException { - synchronized (lock) { - IO.writeAll(modelsFile, ConfigFileSupport.serializeJsonElementToToml(json)); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/CustomOutputStream.java b/src/main/java/com/volmit/adapt/util/CustomOutputStream.java deleted file mode 100644 index 079a3c8a7..000000000 --- a/src/main/java/com/volmit/adapt/util/CustomOutputStream.java +++ /dev/null @@ -1,30 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.zip.GZIPOutputStream; - -public class CustomOutputStream extends GZIPOutputStream { - public CustomOutputStream(OutputStream out, int level) throws IOException { - super(out); - def.setLevel(level); - } -} diff --git a/src/main/java/com/volmit/adapt/util/DOP.java b/src/main/java/com/volmit/adapt/util/DOP.java deleted file mode 100644 index 1c7a84e8b..000000000 --- a/src/main/java/com/volmit/adapt/util/DOP.java +++ /dev/null @@ -1,35 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.util.Vector; - -public abstract class DOP { - private final String type; - - public DOP(String type) { - this.type = type; - } - - public abstract Vector op(Vector v); - - public String getType() { - return type; - } -} diff --git a/src/main/java/com/volmit/adapt/util/DataPalette.java b/src/main/java/com/volmit/adapt/util/DataPalette.java deleted file mode 100644 index df5367c5c..000000000 --- a/src/main/java/com/volmit/adapt/util/DataPalette.java +++ /dev/null @@ -1,124 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public abstract class DataPalette implements Writable { - private static final int DEFAULT_BITS_PER_BLOCK = 4; - private static final int CAPACITY = 4096; - private int bpb; - private NibbleArray data; - private List palette; - - public DataPalette(T defaultValue) { - palette = new ArrayList<>(); - bpb = DEFAULT_BITS_PER_BLOCK; - data = new NibbleArray(bpb, CAPACITY); - data.setAll(Byte.MIN_VALUE); - getPaletteId(defaultValue); - } - - public abstract T readType(DataInputStream i) throws IOException; - - public abstract void writeType(T t, DataOutputStream o) throws IOException; - - @Override - public void write(DataOutputStream o) throws IOException { - o.writeByte(bpb + Byte.MIN_VALUE); - o.writeByte(palette.size() + Byte.MIN_VALUE); - - for (T i : palette) { - writeType(i, o); - } - - data.write(o); - } - - @Override - public void read(DataInputStream i) throws IOException { - bpb = i.readByte() - Byte.MIN_VALUE; - palette = new ArrayList<>(); - int v = i.readByte() - Byte.MIN_VALUE; - - for (int j = 0; j < v; j++) { - palette.add(readType(i)); - } - - data = new NibbleArray(CAPACITY, i); - } - - private final void expand() { - if (bpb < 8) { - changeBitsPerBlock(bpb + 1); - } else { - throw new IndexOutOfBoundsException("The Data Palette can only handle at most 256 block types per 16x16x16 region. We cannot use more than 8 bits per block!"); - } - } - - public final void optimize() { - int targetBits = bpb; - int needed = palette.size(); - - for (int i = 1; i < bpb; i++) { - if (Math.pow(2, i) > needed) { - targetBits = i; - break; - } - } - - changeBitsPerBlock(targetBits); - } - - private final void changeBitsPerBlock(int bits) { - bpb = bits; - data = new NibbleArray(bpb, CAPACITY, data); - } - - public final void set(int x, int y, int z, T d) { - data.set(getCoordinateIndex(x, y, z), getPaletteId(d)); - } - - public final T get(int x, int y, int z) { - return palette.get(data.get(getCoordinateIndex(x, y, z))); - } - - private final int getPaletteId(T d) { - int index = palette.indexOf(d); - - if (index == -1) { - index = palette.size(); - palette.add(d); - - if (palette.size() > Math.pow(2, bpb)) { - expand(); - } - } - - return index + Byte.MIN_VALUE; - } - - private final int getCoordinateIndex(int x, int y, int z) { - return y << 8 | z << 4 | x; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Dictionary.java b/src/main/java/com/volmit/adapt/util/Dictionary.java deleted file mode 100644 index af492a9c4..000000000 --- a/src/main/java/com/volmit/adapt/util/Dictionary.java +++ /dev/null @@ -1,412 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class Dictionary { - //!\"#$%&[] - private static final Pattern wordsPattern = Pattern.compile("[A-Z][A-Z][A-Z][A-Z]*"); - private static final Pattern longCodesPattern = Pattern.compile("<[N-Z0-9!\\\\\"#$%\\[\\]& _][A-Z0-9!\\\\\"#$%\\[\\]& _]"); - private static final Pattern shortCodesPattern = Pattern.compile("<[A-M]"); - - private static final Comparator byLength = new Comparator() { - @Override - public int compare(String a, String b) { - return b.length() - a.length(); - } - }; - - - private static final String[] wordsAndCodes = { - "A", "THE", "B", "AND", "C", "FOR", "D", "YOU", "E", "NOT", "F", "ARE", - "G", "ALL", "H", "NEW", "I", "WAS", "J", "CAN", "K", "HAS", "L", "BUT", - "M", "OUR", - "NA", "THAT", "NB", "THIS", "NC", "WITH", "ND", "FROM", "NE", "YOUR", - "NF", "HAVE", "NG", "MORE", "NH", "WILL", "NI", "HOME", "NJ", "ABOUT", - "NK", "PAGE", "NL", "SEARCH", "NM", "FREE", "NN", "OTHER", "NO", "INFORMATION", - "NP", "TIME", "NQ", "THEY", "NR", "SITE", "NS", "WHAT", "NT", "WHICH", - "NU", "THEIR", "NV", "NEWS", "NW", "THERE", "NX", "ONLY", "NY", "WHEN", - "NZ", "CONTACT", "N0", "HERE", "N1", "BUSINESS", "N2", "ALSO", "N3", "HELP", - "N4", "VIEW", "N5", "ONLINE", "N6", "FIRST", "N7", "BEEN", "N8", "WOULD", - "N9", "WERE", "N!", "SERVICES", "N\"", "SOME", "N#", "THESE", "N$", "CLICK", - "N%", "LIKE", "N&", "SERVICE", "N[", "THAN", "N]", "FIND", "N ", "PRICE", - "N_", "DATE", "OA", "BACK", "OB", "PEOPLE", "OC", "LIST", "OD", "NAME", - "OE", "JUST", "OF", "OVER", "OG", "STATE", "OH", "YEAR", "OI", "INTO", - "OJ", "EMAIL", "OK", "HEALTH", "OL", "WORLD", "OM", "NEXT", "ON", "USED", - "OO", "WORK", "OP", "LAST", "OQ", "MOST", "OR", "PRODUCTS", "OS", "MUSIC", - "OT", "DATA", "OU", "MAKE", "OV", "THEM", "OW", "SHOULD", "OX", "PRODUCT", - "OY", "SYSTEM", "OZ", "POST", "O0", "CITY", "O1", "POLICY", "O2", "NUMBER", - "O3", "SUCH", "O4", "PLEASE", "O5", "AVAILABLE", "O6", "COPYRIGHT", "O7", "SUPPORT", - "O8", "MESSAGE", "O9", "AFTER", "O!", "BEST", "O\"", "SOFTWARE", "O#", "THEN", - "O$", "GOOD", "O%", "VIDEO", "O&", "WELL", "O[", "WHERE", "O]", "INFO", - "O ", "RIGHTS", "O_", "PUBLIC", "PA", "BOOKS", "PB", "HIGH", "PC", "SCHOOL", - "PD", "THROUGH", "PE", "EACH", "PF", "LINKS", "PG", "REVIEW", "PH", "YEARS", - "PI", "ORDER", "PJ", "VERY", "PK", "PRIVACY", "PL", "BOOK", "PM", "ITEMS", - "PN", "COMPANY", "PO", "READ", "PP", "GROUP", "PQ", "NEED", "PR", "MANY", - "PS", "USER", "PT", "SAID", "PU", "DOES", "PV", "UNDER", "PW", "GENERAL", - "PX", "RESEARCH", "PY", "UNIVERSITY", "PZ", "JANUARY", "P0", "MAIL", "P1", "FULL", - "P2", "REVIEWS", "P3", "PROGRAM", "P4", "LIFE", "P5", "KNOW", "P6", "GAMES", - "P7", "DAYS", "P8", "MANAGEMENT", "P9", "PART", "P!", "COULD", "P\"", "GREAT", - "P#", "UNITED", "P$", "HOTEL", "P%", "REAL", "P&", "ITEM", "P[", "INTERNATIONAL", - "P]", "CENTER", "P ", "EBAY", "P_", "MUST", "QA", "STORE", "QB", "TRAVEL", - "QC", "COMMENTS", "QD", "MADE", "QE", "DEVELOPMENT", "QF", "REPORT", "QG", "MEMBER", - "QH", "DETAILS", "QI", "LINE", "QJ", "TERMS", "QK", "BEFORE", "QL", "HOTELS", - "QM", "SEND", "QN", "RIGHT", "QO", "TYPE", "QP", "BECAUSE", "QQ", "LOCAL", - "QR", "THOSE", "QS", "USING", "QT", "RESULTS", "QU", "OFFICE", "QV", "EDUCATION", - "QW", "NATIONAL", "QX", "DESIGN", "QY", "TAKE", "QZ", "POSTED", "Q0", "INTERNET", - "Q1", "ADDRESS", "Q2", "COMMUNITY", "Q3", "WITHIN", "Q4", "STATES", "Q5", "AREA", - "Q6", "WANT", "Q7", "PHONE", "Q8", "SHIPPING", "Q9", "RESERVED", "Q!", "SUBJECT", - "Q\"", "BETWEEN", "Q#", "FORUM", "Q$", "FAMILY", "Q%", "LONG", "Q&", "BASED", - "Q[", "CODE", "Q]", "SHOW", "Q ", "EVEN", "Q_", "BLACK", "RA", "CHECK", - "RB", "SPECIAL", "RC", "PRICES", "RD", "WEBSITE", "RE", "INDEX", "RF", "BEING", - "RG", "WOMEN", "RH", "MUCH", "RI", "SIGN", "RJ", "FILE", "RK", "LINK", - "RL", "OPEN", "RM", "TODAY", "RN", "TECHNOLOGY", "RO", "SOUTH", "RP", "CASE", - "RQ", "PROJECT", "RR", "SAME", "RS", "PAGES", "RT", "VERSION", "RU", "SECTION", - "RV", "FOUND", "RW", "SPORTS", "RX", "HOUSE", "RY", "RELATED", "RZ", "SECURITY", - "R0", "BOTH", "R1", "COUNTY", "R2", "AMERICAN", "R3", "PHOTO", "R4", "GAME", - "R5", "MEMBERS", "R6", "POWER", "R7", "WHILE", "R8", "CARE", "R9", "NETWORK", - "R!", "DOWN", "R\"", "COMPUTER", "R#", "SYSTEMS", "R$", "THREE", "R%", "TOTAL", - "R&", "PLACE", "R[", "FOLLOWING", "R]", "DOWNLOAD", "R ", "WITHOUT", "R_", "ACCESS", - "SA", "THINK", "SB", "NORTH", "SC", "RESOURCES", "SD", "CURRENT", "SE", "POSTS", - "SF", "MEDIA", "SG", "CONTROL", "SH", "WATER", "SI", "HISTORY", "SJ", "PICTURES", - "SK", "SIZE", "SL", "PERSONAL", "SM", "SINCE", "SN", "INCLUDING", "SO", "GUIDE", - "SP", "SHOP", "SQ", "DIRECTORY", "SR", "BOARD", "SS", "LOCATION", "ST", "CHANGE", - "SU", "WHITE", "SV", "TEXT", "SW", "SMALL", "SX", "RATING", "SY", "RATE", - "SZ", "GOVERNMENT", "S0", "CHILDREN", "S1", "DURING", "S2", "RETURN", "S3", "STUDENTS", - "S4", "SHOPPING", "S5", "ACCOUNT", "S6", "TIMES", "S7", "SITES", "S8", "LEVEL", - "S9", "DIGITAL", "S!", "PROFILE", "S\"", "PREVIOUS", "S#", "FORM", "S$", "EVENTS", - "S%", "LOVE", "S&", "JOHN", "S[", "MAIN", "S]", "CALL", "S ", "HOURS", - "S_", "IMAGE", "TA", "DEPARTMENT", "TB", "TITLE", "TC", "DESCRIPTION", "TD", "INSURANCE", - "TE", "ANOTHER", "TF", "SHALL", "TG", "PROPERTY", "TH", "CLASS", "TI", "STILL", - "TJ", "MONEY", "TK", "QUALITY", "TL", "EVERY", "TM", "LISTING", "TN", "CONTENT", - "TO", "COUNTRY", "TP", "PRIVATE", "TQ", "LITTLE", "TR", "VISIT", "TS", "SAVE", - "TT", "TOOLS", "TU", "REPLY", "TV", "CUSTOMER", "TW", "DECEMBER", "TX", "COMPARE", - "TY", "MOVIES", "TZ", "INCLUDE", "T0", "COLLEGE", "T1", "VALUE", "T2", "ARTICLE", - "T3", "YORK", "T4", "CARD", "T5", "JOBS", "T6", "PROVIDE", "T7", "FOOD", - "T8", "SOURCE", "T9", "AUTHOR", "T!", "DIFFERENT", "T\"", "PRESS", "T#", "LEARN", - "T$", "SALE", "T%", "AROUND", "T&", "PRINT", "T[", "COURSE", "T]", "CANADA", - "T ", "PROCESS", "T_", "TEEN", "UA", "ROOM", "UB", "STOCK", "UC", "TRAINING", - "UD", "CREDIT", "UE", "POINT", "UF", "JOIN", "UG", "SCIENCE", "UH", "CATEGORIES", - "UI", "ADVANCED", "UJ", "WEST", "UK", "SALES", "UL", "LOOK", "UM", "ENGLISH", - "UN", "LEFT", "UO", "TEAM", "UP", "ESTATE", "UQ", "CONDITIONS", "UR", "SELECT", - "US", "WINDOWS", "UT", "PHOTOS", "UU", "THREAD", "UV", "WEEK", "UW", "CATEGORY", - "UX", "NOTE", "UY", "LIVE", "UZ", "LARGE", "U0", "GALLERY", "U1", "TABLE", - "U2", "REGISTER", "U3", "HOWEVER", "U4", "JUNE", "U5", "OCTOBER", "U6", "NOVEMBER", - "U7", "MARKET", "U8", "LIBRARY", "U9", "REALLY", "U!", "ACTION", "U\"", "START", - "U#", "SERIES", "U$", "MODEL", "U%", "FEATURES", "U&", "INDUSTRY", "U[", "PLAN", - "U]", "HUMAN", "U ", "PROVIDED", "U_", "REQUIRED", "VA", "SECOND", "VB", "ACCESSORIES", - "VC", "COST", "VD", "MOVIE", "VE", "FORUMS", "VF", "MARCH", "VG", "SEPTEMBER", - "VH", "BETTER", "VI", "QUESTIONS", "VJ", "JULY", "VK", "YAHOO", "VL", "GOING", - "VM", "MEDICAL", "VN", "TEST", "VO", "FRIEND", "VP", "COME", "VQ", "SERVER", - "VR", "STUDY", "VS", "APPLICATION", "VT", "CART", "VU", "STAFF", "VV", "ARTICLES", - "VW", "FEEDBACK", "VX", "AGAIN", "VY", "PLAY", "VZ", "LOOKING", "V0", "ISSUES", - "V1", "APRIL", "V2", "NEVER", "V3", "USERS", "V4", "COMPLETE", "V5", "STREET", - "V6", "TOPIC", "V7", "COMMENT", "V8", "FINANCIAL", "V9", "THINGS", "V!", "WORKING", - "V\"", "AGAINST", "V#", "STANDARD", "V$", "PERSON", "V%", "BELOW", "V&", "MOBILE", - "V[", "LESS", "V]", "BLOG", "V ", "PARTY", "V_", "PAYMENT", "WA", "EQUIPMENT", - "WB", "LOGIN", "WC", "STUDENT", "WD", "PROGRAMS", "WE", "OFFERS", "WF", "LEGAL", - "WG", "ABOVE", "WH", "RECENT", "WI", "PARK", "WJ", "STORES", "WK", "SIDE", - "WL", "PROBLEM", "WM", "GIVE", "WN", "MEMORY", "WO", "PERFORMANCE", "WP", "SOCIAL", - "WQ", "AUGUST", "WR", "QUOTE", "WS", "LANGUAGE", "WT", "STORY", "WU", "SELL", - "WV", "OPTIONS", "WW", "EXPERIENCE", "WX", "RATES", "WY", "CREATE", "WZ", "BODY", - "W0", "YOUNG", "W1", "AMERICA", "W2", "IMPORTANT", "W3", "FIELD", "W4", "EAST", - "W5", "PAPER", "W6", "SINGLE", "W7", "ACTIVITIES", "W8", "CLUB", "W9", "EXAMPLE", - "W!", "GIRLS", "W\"", "ADDITIONAL", "W#", "PASSWORD", "W$", "LATEST", "W%", "SOMETHING", - "W&", "ROAD", "W[", "GIFT", "W]", "QUESTION", "W ", "CHANGES", "W_", "NIGHT", - "XA", "HARD", "XB", "TEXAS", "XC", "FOUR", "XD", "POKER", "XE", "STATUS", - "XF", "BROWSE", "XG", "ISSUE", "XH", "RANGE", "XI", "BUILDING", "XJ", "SELLER", - "XK", "COURT", "XL", "FEBRUARY", "XM", "ALWAYS", "XN", "RESULT", "XO", "AUDIO", - "XP", "LIGHT", "XQ", "WRITE", "XR", "OFFER", "XS", "BLUE", "XT", "GROUPS", - "XU", "EASY", "XV", "GIVEN", "XW", "FILES", "XX", "EVENT", "XY", "RELEASE", - "XZ", "ANALYSIS", "X0", "REQUEST", "X1", "CHINA", "X2", "MAKING", "X3", "PICTURE", - "X4", "NEEDS", "X5", "POSSIBLE", "X6", "MIGHT", "X7", "PROFESSIONAL", "X8", "MONTH", - "X9", "MAJOR", "X!", "STAR", "X\"", "AREAS", "X#", "FUTURE", "X$", "SPACE", - "X%", "COMMITTEE", "X&", "HAND", "X[", "CARDS", "X]", "PROBLEMS", "X ", "LONDON", - "X_", "WASHINGTON", "YA", "MEETING", "YB", "BECOME", "YC", "INTEREST", "YD", "CHILD", - "YE", "KEEP", "YF", "ENTER", "YG", "CALIFORNIA", "YH", "PORN", "YI", "SHARE", - "YJ", "SIMILAR", "YK", "GARDEN", "YL", "SCHOOLS", "YM", "MILLION", "YN", "ADDED", - "YO", "REFERENCE", "YP", "COMPANIES", "YQ", "LISTED", "YR", "BABY", "YS", "LEARNING", - "YT", "ENERGY", "YU", "DELIVERY", "YV", "POPULAR", "YW", "TERM", "YX", "FILM", - "YY", "STORIES", "YZ", "COMPUTERS", "Y0", "JOURNAL", "Y1", "REPORTS", "Y2", "WELCOME", - "Y3", "CENTRAL", "Y4", "IMAGES", "Y5", "PRESIDENT", "Y6", "NOTICE", "Y7", "ORIGINAL", - "Y8", "HEAD", "Y9", "RADIO", "Y!", "UNTIL", "Y\"", "CELL", "Y#", "COLOR", - "Y$", "SELF", "Y%", "COUNCIL", "Y&", "AWAY", "Y[", "INCLUDES", "Y]", "TRACK", - "Y ", "AUSTRALIA", "Y_", "DISCUSSION", "ZA", "ARCHIVE", "ZB", "ONCE", "ZC", "OTHERS", - "ZD", "ENTERTAINMENT", "ZE", "AGREEMENT", "ZF", "FORMAT", "ZG", "LEAST", "ZH", "SOCIETY", - "ZI", "MONTHS", "ZJ", "SAFETY", "ZK", "FRIENDS", "ZL", "SURE", "ZM", "TRADE", - "ZN", "EDITION", "ZO", "CARS", "ZP", "MESSAGES", "ZQ", "MARKETING", "ZR", "TELL", - "ZS", "FURTHER", "ZT", "UPDATED", "ZU", "ASSOCIATION", "ZV", "ABLE", "ZW", "HAVING", - "ZX", "PROVIDES", "ZY", "DAVID", "ZZ", "ALREADY", "Z0", "GREEN", "Z1", "STUDIES", - "Z2", "CLOSE", "Z3", "COMMON", "Z4", "DRIVE", "Z5", "SPECIFIC", "Z6", "SEVERAL", - "Z7", "GOLD", "Z8", "LIVING", "Z9", "COLLECTION", "Z!", "CALLED", "Z\"", "SHORT", - "Z#", "ARTS", "Z$", "DISPLAY", "Z%", "LIMITED", "Z&", "POWERED", "Z[", "SOLUTIONS", - "Z]", "MEANS", "Z ", "DIRECTOR", "Z_", "DAILY", "0A", "BEACH", "0B", "PAST", - "0C", "NATURAL", "0D", "WHETHER", "0E", "ELECTRONICS", "0F", "FIVE", "0G", "UPON", - "0H", "PERIOD", "0I", "PLANNING", "0J", "DATABASE", "0K", "SAYS", "0L", "OFFICIAL", - "0M", "WEATHER", "0N", "LAND", "0O", "AVERAGE", "0P", "DONE", "0Q", "TECHNICAL", - "0R", "WINDOW", "0S", "FRANCE", "0T", "REGION", "0U", "ISLAND", "0V", "RECORD", - "0W", "DIRECT", "0X", "MICROSOFT", "0Y", "CONFERENCE", "0Z", "ENVIRONMENT", "00", "RECORDS", - "01", "DISTRICT", "02", "CALENDAR", "03", "COSTS", "04", "STYLE", "05", "FRONT", - "06", "STATEMENT", "07", "UPDATE", "08", "PARTS", "09", "EVER", "0!", "DOWNLOADS", - "0\"", "EARLY", "0#", "MILES", "0$", "SOUND", "0%", "RESOURCE", "0&", "PRESENT", - "0[", "APPLICATIONS", "0]", "EITHER", "0 ", "DOCUMENT", "0_", "WORD", "1A", "WORKS", - "1B", "MATERIAL", "1C", "BILL", "1D", "WRITTEN", "1E", "TALK", "1F", "FEDERAL", - "1G", "HOSTING", "1H", "RULES", "1I", "FINAL", "1J", "ADULT", "1K", "TICKETS", - "1L", "THING", "1M", "CENTRE", "1N", "REQUIREMENTS", "1O", "CHEAP", "1P", "NUDE", - "1Q", "KIDS", "1R", "FINANCE", "1S", "TRUE", "1T", "MINUTES", "1U", "ELSE", - "1V", "MARK", "1W", "THIRD", "1X", "ROCK", "1Y", "GIFTS", "1Z", "EUROPE", - "10", "READING", "11", "TOPICS", "12", "INDIVIDUAL", "13", "TIPS", "14", "PLUS", - "15", "AUTO", "16", "COVER", "17", "USUALLY", "18", "EDIT", "19", "TOGETHER", - "1!", "VIDEOS", "1\"", "PERCENT", "1#", "FAST", "1$", "FUNCTION", "1%", "FACT", - "1&", "UNIT", "1[", "GETTING", "1]", "GLOBAL", "1 ", "TECH", "1_", "MEET", - "2A", "ECONOMIC", "2B", "PLAYER", "2C", "PROJECTS", "2D", "LYRICS", "2E", "OFTEN", - "2F", "SUBSCRIBE", "2G", "SUBMIT", "2H", "GERMANY", "2I", "AMOUNT", "2J", "WATCH", - "2K", "INCLUDED", "2L", "FEEL", "2M", "THOUGH", "2N", "BANK", "2O", "RISK", - "2P", "THANKS", "2Q", "EVERYTHING", "2R", "DEALS", "2S", "VARIOUS", "2T", "WORDS", - "2U", "LINUX", "2V", "PRODUCTION", "2W", "COMMERCIAL", "2X", "JAMES", "2Y", "WEIGHT", - "2Z", "TOWN", "20", "HEART", "21", "ADVERTISING", "22", "RECEIVED", "23", "CHOOSE", - "24", "TREATMENT", "25", "NEWSLETTER", "26", "ARCHIVES", "27", "POINTS", "28", "KNOWLEDGE", - "29", "MAGAZINE", "2!", "ERROR", "2\"", "CAMERA", "2#", "GIRL", "2$", "CURRENTLY", - "2%", "CONSTRUCTION", "2&", "TOYS", "2[", "REGISTERED", "2]", "CLEAR", "2 ", "GOLF", - "2_", "RECEIVE", "3A", "DOMAIN", "3B", "METHODS", "3C", "CHAPTER", "3D", "MAKES", - "3E", "PROTECTION", "3F", "POLICIES", "3G", "LOAN", "3H", "WIDE", "3I", "BEAUTY", - "3J", "MANAGER", "3K", "INDIA", "3L", "POSITION", "3M", "TAKEN", "3N", "SORT", - "3O", "LISTINGS", "3P", "MODELS", "3Q", "MICHAEL", "3R", "KNOWN", "3S", "HALF", - "3T", "CASES", "3U", "STEP", "3V", "ENGINEERING", "3W", "FLORIDA", "3X", "SIMPLE", - "3Y", "QUICK", "3Z", "NONE", "30", "WIRELESS", "31", "LICENSE", "32", "PAUL", - "33", "FRIDAY", "34", "LAKE", "35", "WHOLE", "36", "ANNUAL", "37", "PUBLISHED", - "38", "LATER", "39", "BASIC", "3!", "SONY", "3\"", "SHOWS", "3#", "CORPORATE", - "3$", "GOOGLE", "3%", "CHURCH", "3&", "METHOD", "3[", "PURCHASE", "3]", "CUSTOMERS", - "3 ", "ACTIVE", "3_", "RESPONSE", "4A", "PRACTICE", "4B", "HARDWARE", "4C", "FIGURE", - "4D", "MATERIALS", "4E", "FIRE", "4F", "HOLIDAY", "4G", "CHAT", "4H", "ENOUGH", - "4I", "DESIGNED", "4J", "ALONG", "4K", "AMONG", "4L", "DEATH", "4M", "WRITING", - "4N", "SPEED", "4O", "HTML", "4P", "COUNTRIES", "4Q", "LOSS", "4R", "FACE", - "4S", "BRAND", "4T", "DISCOUNT", "4U", "HIGHER", "4V", "EFFECTS", "4W", "CREATED", - "4X", "REMEMBER", "4Y", "STANDARDS", "4Z", "YELLOW", "40", "POLITICAL", "41", "INCREASE", - "42", "ADVERTISE", "43", "KINGDOM", "44", "BASE", "45", "NEAR", "46", "ENVIRONMENTAL", - "47", "THOUGHT", "48", "STUFF", "49", "FRENCH", "4!", "STORAGE", "4\"", "JAPAN", - "4#", "DOING", "4$", "LOANS", "4%", "SHOES", "4&", "ENTRY", "4[", "STAY", - "4]", "NATURE", "4 ", "ORDERS", "4_", "AVAILABILITY", "5A", "AFRICA", "5B", "SUMMARY", - "5C", "TURN", "5D", "MEAN", "5E", "GROWTH", "5F", "NOTES", "5G", "AGENCY", - "5H", "KING", "5I", "MONDAY", "5J", "EUROPEAN", "5K", "ACTIVITY", "5L", "COPY", - "5M", "ALTHOUGH", "5N", "DRUG", "5O", "PICS", "5P", "WESTERN", "5Q", "INCOME", - "5R", "FORCE", "5S", "CASH", "5T", "EMPLOYMENT", "5U", "OVERALL", "5V", "RIVER", - "5W", "COMMISSION", "5X", "PACKAGE", "5Y", "CONTENTS", "5Z", "SEEN", "50", "PLAYERS", - "51", "ENGINE", "52", "PORT", "53", "ALBUM", "54", "REGIONAL", "55", "STOP", - "56", "SUPPLIES", "57", "STARTED", "58", "ADMINISTRATION", "59", "INSTITUTE", "5!", "VIEWS", - "5\"", "PLANS", "5#", "DOUBLE", "5$", "BUILD", "5%", "SCREEN", "5&", "EXCHANGE", - "5[", "TYPES", "5]", "SOON", "5 ", "SPONSORED", "5_", "LINES", "6A", "ELECTRONIC", - "6B", "CONTINUE", "6C", "ACROSS", "6D", "BENEFITS", "6E", "NEEDED", "6F", "SEASON", - "6G", "APPLY", "6H", "SOMEONE", "6I", "HELD", "6J", "ANYTHING", "6K", "PRINTER", - "6L", "CONDITION", "6M", "EFFECTIVE", "6N", "BELIEVE", "6O", "ORGANIZATION", "6P", "EFFECT", - "6Q", "ASKED", "6R", "MIND", "6S", "SUNDAY", "6T", "SELECTION", "6U", "CASINO", - "6V", "LOST", "6W", "TOUR", "6X", "MENU", "6Y", "VOLUME", "6Z", "CROSS", - "60", "ANYONE", "61", "MORTGAGE", "62", "HOPE", "63", "SILVER", "64", "CORPORATION", - "65", "WISH", "66", "INSIDE", "67", "SOLUTION", "68", "MATURE", "69", "ROLE", - "6!", "RATHER", "6\"", "WEEKS", "6#", "ADDITION", "6$", "CAME", "6%", "SUPPLY", - "6&", "NOTHING", "6[", "CERTAIN", "6]", "EXECUTIVE", "6 ", "RUNNING", "6_", "LOWER", - "7A", "NECESSARY", "7B", "UNION", "7C", "JEWELRY", "7D", "ACCORDING", "7E", "CLOTHING", - "7F", "PARTICULAR", "7G", "FINE", "7H", "NAMES", "7I", "ROBERT", "7J", "HOMEPAGE", - "7K", "HOUR", "7L", "SKILLS", "7M", "BUSH", "7N", "ISLANDS", "7O", "ADVICE", - "7P", "CAREER", "7Q", "MILITARY", "7R", "RENTAL", "7S", "DECISION", "7T", "LEAVE", - "7U", "BRITISH", "7V", "TEENS", "7W", "HUGE", "7X", "WOMAN", "7Y", "FACILITIES", - "7Z", "KIND", "70", "SELLERS", "71", "MIDDLE", "72", "MOVE", "73", "CABLE", - "74", "OPPORTUNITIES", "75", "TAKING", "76", "VALUES", "77", "DIVISION", "78", "COMING", - "79", "TUESDAY", "7!", "OBJECT", "7\"", "LESBIAN", "7#", "APPROPRIATE", "7$", "MACHINE", - "7%", "LOGO", "7&", "LENGTH", "7[", "ACTUALLY", "7]", "NICE", "7 ", "SCORE", - "7_", "STATISTICS", "8A", "CLIENT", "8B", "RETURNS", "8C", "CAPITAL", "8D", "FOLLOW", - "8E", "SAMPLE", "8F", "INVESTMENT", "8G", "SENT", "8H", "SHOWN", "8I", "SATURDAY", - "8J", "CHRISTMAS", "8K", "ENGLAND", "8L", "CULTURE", "8M", "BAND", "8N", "FLASH", - "8O", "LEAD", "8P", "GEORGE", "8Q", "CHOICE", "8R", "WENT", "8S", "STARTING", - "8T", "REGISTRATION", "8U", "THURSDAY", "8V", "COURSES", "8W", "CONSUMER", "8X", "AIRPORT", - "8Y", "FOREIGN", "8Z", "ARTIST", "80", "OUTSIDE", "81", "FURNITURE", "82", "LEVELS", - "83", "CHANNEL", "84", "LETTER", "85", "MODE", "86", "PHONES", "87", "IDEAS", - "88", "WEDNESDAY", "89", "STRUCTURE", "8!", "FUND", "8\"", "SUMMER", "8#", "ALLOW", - "8$", "DEGREE", "8%", "CONTRACT", "8&", "BUTTON", "8[", "RELEASES", "8]", "HOMES", - "8 ", "SUPER", "8_", "MALE", "9A", "MATTER", "9B", "CUSTOM", "9C", "VIRGINIA", - "9D", "ALMOST", "9E", "TOOK", "9F", "LOCATED", "9G", "MULTIPLE", "9H", "ASIAN", - "9I", "DISTRIBUTION", "9J", "EDITOR", "9K", "INDUSTRIAL", "9L", "CAUSE", "9M", "POTENTIAL", - "9N", "SONG", "9O", "CNET", "9P", "FOCUS", "9Q", "LATE", "9R", "FALL", - "9S", "FEATURED", "9T", "IDEA", "9U", "ROOMS", "9V", "FEMALE", "9W", "RESPONSIBLE", - "9X", "COMMUNICATIONS", "9Y", "ASSOCIATED", "9Z", "THOMAS", "90", "PRIMARY", "91", "CANCER", - "92", "NUMBERS", "93", "REASON", "94", "TOOL", "95", "BROWSER", "96", "SPRING", - "97", "FOUNDATION", "98", "ANSWER", "99", "VOICE", "9!", "FRIENDLY", "9\"", "SCHEDULE", - "9#", "DOCUMENTS", "9$", "COMMUNICATION", "9%", "PURPOSE", "9&", "FEATURE", "9[", "COMES", - "9]", "POLICE", "9 ", "EVERYONE", "9_", "INDEPENDENT", "!A", "APPROACH", "!B", "CAMERAS", - "!C", "BROWN", "!D", "PHYSICAL", "!E", "OPERATING", "!F", "HILL", "!G", "MAPS", - "!H", "MEDICINE", "!I", "DEAL", "!J", "HOLD", "!K", "RATINGS", "!L", "CHICAGO", - "!M", "FORMS", "!N", "GLASS", "!O", "HAPPY", "!P", "SMITH", "!Q", "WANTED", - "!R", "DEVELOPED", "!S", "THANK", "!T", "SAFE", "!U", "UNIQUE", "!V", "SURVEY", - "!W", "PRIOR", "!X", "TELEPHONE", "!Y", "SPORT", "!Z", "READY", "!0", "FEED", - "!1", "ANIMAL", "!2", "SOURCES", "!3", "MEXICO", "!4", "POPULATION", "!5", "REGULAR", - "!6", "SECURE", "!7", "NAVIGATION", "!8", "OPERATIONS", "!9", "THEREFORE", "!!", "SIMPLY", - "!\"", "EVIDENCE", "!#", "STATION", "!$", "CHRISTIAN", "!%", "ROUND", "!&", "PAYPAL", - "![", "FAVORITE", "!]", "UNDERSTAND", "! ", "OPTION", "!_", "MASTER", "\"A", "VALLEY", - "\"B", "RECENTLY", "\"C", "PROBABLY", "\"D", "RENTALS", "\"E", "BUILT", "\"F", "PUBLICATIONS", - "\"G", "BLOOD", "\"H", "WORLDWIDE", "\"I", "IMPROVE", "\"J", "CONNECTION", "\"K", "PUBLISHER", - "\"L", "HALL", "\"M", "LARGER", "\"N", "ANTI", "\"O", "NETWORKS", "\"P", "EARTH", - "\"Q", "PARENTS", "\"R", "NOKIA", "\"S", "IMPACT", "\"T", "TRANSFER", "\"U", "INTRODUCTION", - "\"V", "KITCHEN", "\"W", "STRONG", "\"X", "CAROLINA", "\"Y", "WEDDING", "\"Z", "PROPERTIES", - "\"0", "HOSPITAL", "\"1", "GROUND", "\"2", "OVERVIEW", "\"3", "SHIP", "\"4", "ACCOMMODATION", - "\"5", "OWNERS", "\"6", "DISEASE", "\"7", "EXCELLENT", "\"8", "PAID", "\"9", "ITALY", - "\"!", "PERFECT", "\"\"", "HAIR", "\"#", "OPPORTUNITY", "\"$", "CLASSIC", "\"%", "BASIS", - "\"&", "COMMAND", "\"[", "CITIES", "\"]", "WILLIAM", "\" ", "EXPRESS", "\"_", "ANAL", - "#A", "AWARD", "#B", "DISTANCE", "#C", "TREE", "#D", "PETER", "#E", "ASSESSMENT", - "#F", "ENSURE", "#G", "THUS", "#H", "WALL", "#I", "INVOLVED", "#J", "EXTRA", - "#K", "ESPECIALLY", "#L", "INTERFACE", "#M", "PUSSY", "#N", "PARTNERS", "#O", "BUDGET", - "#P", "RATED", "#Q", "GUIDES", "#R", "SUCCESS", "#S", "MAXIMUM", "#T", "OPERATION", - "#U", "EXISTING", "#V", "QUITE", "#W", "SELECTED", "#X", "AMAZON", "#Y", "PATIENTS", - "#Z", "RESTAURANTS", "#0", "BEAUTIFUL", "#1", "WARNING", "#2", "WINE", "#3", "LOCATIONS", - "#4", "HORSE", "#5", "VOTE", "#6", "FORWARD", "#7", "FLOWERS", "#8", "STARS", - "#9", "SIGNIFICANT", "#!", "LISTS", "#\"", "TECHNOLOGIES", "##", "OWNER", "#$", "RETAIL", - "#%", "ANIMALS", "#&", "USEFUL", "#[", "DIRECTLY", "#]", "MANUFACTURER", "# ", "WAYS", - "#_", "PROVIDING", "$A", "RULE", "$B", "HOUSING", "$C", "TAKES", "$D", "BRING", - "$E", "CATALOG", "$F", "SEARCHES", "$G", "TRYING", "$H", "MOTHER", "$I", "AUTHORITY", - "$J", "CONSIDERED", "$K", "TOLD", "$L", "TRAFFIC", "$M", "PROGRAMME", "$N", "JOINED", - "$O", "INPUT", "$P", "STRATEGY", "$Q", "FEET", "$R", "AGENT", "$S", "VALID", - "$T", "MODERN", "$U", "SENIOR", "$V", "IRELAND", "$W", "SEXY", "$X", "TEACHING", - "$Y", "DOOR", "$Z", "GRAND", "$0", "TESTING", "$1", "TRIAL", "$2", "CHARGE", - "$3", "UNITS", "$4", "INSTEAD", "$5", "CANADIAN", "$6", "COOL", "$7", "NORMAL", - "$8", "WROTE", "$9", "ENTERPRISE", "$!", "SHIPS", "$\"", "ENTIRE", "$#", "EDUCATIONAL", - "$$", "LEADING", "$%", "METAL", "$&", "POSITIVE", "$[", "FITNESS", "$]", "CHINESE", - "$ ", "OPINION", "$_", "ASIA", "%A", "FOOTBALL", "%B", "ABSTRACT", "%C", "USES", - "%D", "OUTPUT", "%E", "FUNDS", "%F", "GREATER", "%G", "LIKELY", "%H", "DEVELOP", - "%I", "EMPLOYEES", "%J", "ARTISTS", "%K", "ALTERNATIVE", "%L", "PROCESSING", "%M", "RESPONSIBILITY", - "%N", "RESOLUTION", "%O", "JAVA", "%P", "GUEST", "%Q", "SEEMS", "%R", "PUBLICATION", - "%S", "PASS", "%T", "RELATIONS", "%U", "TRUST", "%V", "CONTAINS", "%W", "SESSION", - "%X", "MULTI", "%Y", "PHOTOGRAPHY", "%Z", "REPUBLIC", "%0", "FEES", "%1", "COMPONENTS", - "%2", "VACATION", "%3", "CENTURY", "%4", "ACADEMIC", "%5", "ASSISTANCE", "%6", "COMPLETED", - "%7", "SKIN", "%8", "GRAPHICS", "%9", "INDIAN", "%!", "PREV", "%\"", "MARY", - "%#", "EXPECTED", "%$", "RING", "%%", "GRADE", "%&", "DATING", "%[", "PACIFIC", - "%]", "MOUNTAIN", "% ", "ORGANIZATIONS", "%_", "FILTER", "&A", "MAILING", "&B", "VEHICLE", - "&C", "LONGER", "&D", "CONSIDER", "&E", "NORTHERN", "&F", "BEHIND", "&G", "PANEL", - "&H", "FLOOR", "&I", "GERMAN", "&J", "BUYING", "&K", "MATCH", "&L", "PROPOSED", - "&M", "DEFAULT", "&N", "REQUIRE", "&O", "IRAQ", "&P", "BOYS", "&Q", "OUTDOOR", - "&R", "DEEP", "&S", "MORNING", "&T", "OTHERWISE", "&U", "ALLOWS", "&V", "REST", - "&W", "PROTEIN", "&X", "PLANT", "&Y", "REPORTED", "&Z", "TRANSPORTATION", "&0", "POOL", - "&1", "MINI", "&2", "POLITICS", "&3", "PARTNER", "&4", "DISCLAIMER", "&5", "AUTHORS", - "&6", "BOARDS", "&7", "FACULTY", "&8", "PARTIES", "&9", "FISH", "&!", "MEMBERSHIP", - "&\"", "MISSION", "&#", "STRING", "&$", "SENSE", "&%", "MODIFIED", "&&", "PACK", - "&[", "RELEASED", "&]", "STAGE", "& ", "INTERNAL", "&_", "GOODS", "[A", "RECOMMENDED", - "[B", "BORN", "[C", "UNLESS", "[D", "RICHARD", "[E", "DETAILED", "[F", "JAPANESE", - "[G", "RACE", "[H", "APPROVED", "[I", "BACKGROUND", "[J", "TARGET", "[K", "EXCEPT", - "[L", "CHARACTER", "[M", "MAINTENANCE", "[N", "ABILITY", "[O", "MAYBE", "[P", "FUNCTIONS", - "[Q", "MOVING", "[R", "BRANDS", "[S", "PLACES", "[T", "PRETTY", "[U", "TRADEMARKS", - "[V", "PHENTERMINE", "[W", "SPAIN", "[X", "SOUTHERN", "[Y", "YOURSELF", "[Z", "WINTER", - "[0", "RAPE", "[1", "BATTERY", "[2", "YOUTH", "[3", "PRESSURE", "[4", "SUBMITTED", - "[5", "BOSTON", "[6", "INCEST", "[7", "DEBT", "[8", "KEYWORDS", "[9", "MEDIUM", - "[!", "TELEVISION", "[\"", "INTERESTED", "[#", "CORE", "[$", "BREAK", "[%", "PURPOSES", - "[&", "THROUGHOUT", "[[", "SETS", "[]", "DANCE", "[ ", "WOOD", "[_", "ITSELF", - "]A", "DEFINED", "]B", "PAPERS", "]C", "PLAYING", "]D", "AWARDS", "]E", "STUDIO", - "]F", "READER", "]G", "VIRTUAL", "]H", "DEVICE", "]I", "ESTABLISHED", "]J", "ANSWERS", - "]K", "RENT", "]L", "REMOTE", "]M", "DARK", "]N", "PROGRAMMING", "]O", "EXTERNAL", - "]P", "APPLE", "]Q", "REGARDING", "]R", "INSTRUCTIONS", "]S", "OFFERED", "]T", "THEORY", - "]U", "ENJOY", "]V", "REMOVE", "]W", "SURFACE", "]X", "MINIMUM", "]Y", "VISUAL", - "]Z", "HOST", "]0", "VARIETY", "]1", "TEACHERS", "]2", "ISBN", "]3", "MARTIN", - "]4", "MANUAL", "]5", "BLOCK", "]6", "SUBJECTS", "]7", "AGENTS", "]8", "INCREASED", - "]9", "REPAIR", "]!", "FAIR", "]\"", "CIVIL", "]#", "STEEL", "]$", "UNDERSTANDING", - "]%", "SONGS", "]&", "FIXED", "][", "WRONG", "]]", "BEGINNING", "] ", "HANDS", - "]_", "ASSOCIATES", " A", "FINALLY", " B", "UPDATES", " C", "DESKTOP", " D", "CLASSES", - " E", "PARIS", " F", "OHIO", " G", "GETS", " H", "SECTOR", " I", "CAPACITY", - " J", "REQUIRES", " K", "JERSEY", " L", "FULLY", " M", "FATHER", " N", "ELECTRIC", - " O", "INSTRUMENTS", " P", "QUOTES", " Q", "OFFICER", " R", "DRIVER", " S", "BUSINESSES", - " T", "DEAD", " U", "RESPECT", " V", "UNKNOWN", " W", "SPECIFIED", " X", "RESTAURANT", - " Y", "MIKE", " Z", "TRIP", " 0", "WORTH", " 1", "PROCEDURES", " 2", "POOR", - " 3", "TEACHER", " 4", "EYES", " 5", "RELATIONSHIP", " 6", "WORKERS", " 7", "FARM", - " 8", "HTTP://", " 9", "GEORGIA", " !", "PEACE", " \"", "TRADITIONAL", " #", "CAMPUS", - " $", "SHOWING", " %", "CREATIVE", " &", "COAST", " [", "BENEFIT", " ]", "PROGRESS", - " ", "FUNDING", " _", "DEVICES", "_A", "LORD", "_B", "GRANT", "_C", "AGREE", - "_D", "FICTION", "_E", "HEAR", "_F", "SOMETIMES", "_G", "WATCHES", "_H", "CAREERS", - "_I", "BEYOND", "_J", "GOES", "_K", "FAMILIES", "_L", "MUSEUM", "_M", "THEMSELVES", - "_N", "TRANSPORT", "_O", "INTERESTING", "_P", "BLOGS", "_Q", "WIFE", "_R", "EVALUATION", - "_S", "ACCEPTED", "_T", "FORMER", "_U", "IMPLEMENTATION", "_V", "HITS", "_W", "ZONE", - "_X", "COMPLEX", "_Y", "GALLERIES", "_Z", "REFERENCES", "_0", "PRESENTED", "_1", "JACK", - "_2", "FLAT", "_3", "FLOW", "_4", "AGENCIES", "_5", "LITERATURE", "_6", "RESPECTIVE", - "_7", "PARENT", "_8", "SPANISH", "_9", "MICHIGAN", "_!", "COLUMBIA", "_\"", "SETTING", - "_#", "SCALE", "_$", "STAND", "_%", "ECONOMY", "_&", "HIGHEST", "_[", "HELPFUL", - "_]", "MONTHLY", "_ ", "CRITICAL", "__", "FRAME" - }; - private static final Map wordsToCodes = new HashMap(); - private static final Map codesToWords = new HashMap(); - - static { - for (int i = 0; i < wordsAndCodes.length; i = i + 2) { - String code = wordsAndCodes[i]; - String word = wordsAndCodes[i + 1]; - wordsToCodes.put(word, code); - codesToWords.put(code, word); - } - } - - public static String encode(String word) { - if (wordsToCodes.containsKey(word)) { - return "<" + wordsToCodes.get(word); - } - return word; - } - - public static String decode(String code1) { - if (!code1.startsWith("<")) { - return code1; - } - String code = code1.substring(1); - if (codesToWords.containsKey(code)) { - return codesToWords.get(code); - } - return code1; - } - - public static String lengthen(String result2) { - List shortCodesInString = wordsForPattern(shortCodesPattern, result2); - for (String w : shortCodesInString) { - String theWord = Dictionary.decode(w); - result2 = result2.replace(w, theWord); - } - List longCodesInString = wordsForPattern(longCodesPattern, result2); - for (String w : longCodesInString) { - String theWord = Dictionary.decode(w); - result2 = result2.replace(w, theWord); - } - return result2; - } - - public static String shorten(String str) { - List words = wordsForPattern(wordsPattern, str); - String result = str; - for (String w : words) { - String code = Dictionary.encode(w); - result = result.replace(w, code); - } - return result; - } - - public static List wordsForPattern(Pattern p, String str) { - Matcher m = p.matcher(str); - List words = new ArrayList(); - while (m.find()) { - words.add(m.group(0)); - } - - Collections.sort(words, byLength); - return words; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/Dimension.java b/src/main/java/com/volmit/adapt/util/Dimension.java deleted file mode 100644 index a1c2a649f..000000000 --- a/src/main/java/com/volmit/adapt/util/Dimension.java +++ /dev/null @@ -1,89 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Dimensions - * - * @author cyberpwn - */ -public class Dimension { - private final int width; - private final int height; - private final int depth; - - /** - * Make a dimension - * - * @param width width of this (X) - * @param height the height (Y) - * @param depth the depth (Z) - */ - public Dimension(int width, int height, int depth) { - this.width = width; - this.height = height; - this.depth = depth; - } - - /** - * Make a dimension - * - * @param width width of this (X) - * @param height the height (Y) - */ - public Dimension(int width, int height) { - this.width = width; - this.height = height; - this.depth = 0; - } - - /** - * Get the direction of the flat part of this dimension (null if no thin - * face) - * - * @return the direction of the flat pane or null - */ - public DimensionFace getPane() { - if (width == 1) { - return DimensionFace.X; - } - - if (height == 1) { - return DimensionFace.Y; - } - - if (depth == 1) { - return DimensionFace.Z; - } - - return null; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public int getDepth() { - return depth; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/DimensionFace.java b/src/main/java/com/volmit/adapt/util/DimensionFace.java deleted file mode 100644 index 830efe6de..000000000 --- a/src/main/java/com/volmit/adapt/util/DimensionFace.java +++ /dev/null @@ -1,41 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Represents a dimension (coordinates not worlds) - * - * @author cyberpwn - */ -public enum DimensionFace { - /** - * The X dimension (width) - */ - X, - - /** - * The Y dimension (height) - */ - Y, - - /** - * The Z dimension (depth) - */ - Z -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/Direction.java b/src/main/java/com/volmit/adapt/util/Direction.java deleted file mode 100644 index 5ec7bfe3b..000000000 --- a/src/main/java/com/volmit/adapt/util/Direction.java +++ /dev/null @@ -1,438 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.Cuboid.CuboidDirection; -import org.bukkit.Axis; -import org.bukkit.block.BlockFace; -import org.bukkit.util.Vector; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Directions - * - * @author cyberpwn - */ -public enum Direction { - U(0, 1, 0, CuboidDirection.Up), - D(0, -1, 0, CuboidDirection.Down), - N(0, 0, -1, CuboidDirection.North), - S(0, 0, 1, CuboidDirection.South), - E(1, 0, 0, CuboidDirection.East), - W(-1, 0, 0, CuboidDirection.West); - - private static Map, DOP> permute = null; - - private final int x; - private final int y; - private final int z; - private final CuboidDirection f; - - Direction(int x, int y, int z, CuboidDirection f) { - this.x = x; - this.y = y; - this.z = z; - this.f = f; - } - - public static Direction getDirection(BlockFace f) { - switch (f) { - case DOWN: - return D; - case EAST: - return E; - case EAST_NORTH_EAST: - return E; - case EAST_SOUTH_EAST: - return E; - case NORTH: - return N; - case NORTH_EAST: - return N; - case NORTH_NORTH_EAST: - return N; - case NORTH_NORTH_WEST: - return N; - case NORTH_WEST: - return N; - case SELF: - return U; - case SOUTH: - return S; - case SOUTH_EAST: - return S; - case SOUTH_SOUTH_EAST: - return S; - case SOUTH_SOUTH_WEST: - return S; - case SOUTH_WEST: - return S; - case UP: - return U; - case WEST: - return W; - case WEST_NORTH_WEST: - return W; - case WEST_SOUTH_WEST: - return W; - } - - return D; - } - - public static Direction closest(Vector v) { - double m = Double.MAX_VALUE; - Direction s = null; - - for (Direction i : values()) { - Vector x = i.toVector(); - double g = x.dot(v); - - if (g < m) { - m = g; - s = i; - } - } - - return s; - } - - public static Direction closest(Vector v, Direction... d) { - double m = Double.MAX_VALUE; - Direction s = null; - - for (Direction i : d) { - Vector x = i.toVector(); - double g = x.distance(v); - - if (g < m) { - m = g; - s = i; - } - } - - return s; - } - - public static Direction closest(Vector v, List d) { - double m = Double.MAX_VALUE; - Direction s = null; - - for (Direction i : d) { - Vector x = i.toVector(); - double g = x.distance(v); - - if (g < m) { - m = g; - s = i; - } - } - - return s; - } - - public static List news() { - return Arrays.asList(N, E, W, S); - } - - public static Direction getDirection(Vector v) { - Vector k = VectorMath.triNormalize(v.clone().normalize()); - - for (Direction i : udnews()) { - if (i.x == k.getBlockX() && i.y == k.getBlockY() && i.z == k.getBlockZ()) { - return i; - } - } - - return Direction.N; - } - - public static List udnews() { - return Arrays.asList(U, D, N, E, W, S); - } - - /** - * Get the directional value from the given byte from common directional blocks - * (MUST BE BETWEEN 0 and 5 INCLUSIVE) - * - * @param b the byte - * @return the direction or null if the byte is outside of the inclusive range - * 0-5 - */ - public static Direction fromByte(byte b) { - if (b > 5 || b < 0) { - return null; - } - - if (b == 0) { - return D; - } else if (b == 1) { - return U; - } else if (b == 2) { - return N; - } else if (b == 3) { - return S; - } else if (b == 4) { - return W; - } else { - return E; - } - } - - public static void calculatePermutations() { - if (permute != null) { - return; - } - - permute = new HashMap<>(); - - for (Direction i : udnews()) { - for (Direction j : udnews()) { - GBiset b = new GBiset(i, j); - - if (i.equals(j)) { - permute.put(b, new DOP("DIRECT") { - @Override - public Vector op(Vector v) { - return v; - } - }); - } else if (i.reverse().equals(j)) { - if (i.isVertical()) { - permute.put(b, new DOP("R180CCZ") { - @Override - public Vector op(Vector v) { - return VectorMath.rotate90CCZ(VectorMath.rotate90CCZ(v)); - } - }); - } else { - permute.put(b, new DOP("R180CCY") { - @Override - public Vector op(Vector v) { - return VectorMath.rotate90CCY(VectorMath.rotate90CCY(v)); - } - }); - } - } else if (getDirection(VectorMath.rotate90CX(i.toVector())).equals(j)) { - permute.put(b, new DOP("R90CX") { - @Override - public Vector op(Vector v) { - return VectorMath.rotate90CX(v); - } - }); - } else if (getDirection(VectorMath.rotate90CCX(i.toVector())).equals(j)) { - permute.put(b, new DOP("R90CCX") { - @Override - public Vector op(Vector v) { - return VectorMath.rotate90CCX(v); - } - }); - } else if (getDirection(VectorMath.rotate90CY(i.toVector())).equals(j)) { - permute.put(b, new DOP("R90CY") { - @Override - public Vector op(Vector v) { - return VectorMath.rotate90CY(v); - } - }); - } else if (getDirection(VectorMath.rotate90CCY(i.toVector())).equals(j)) { - permute.put(b, new DOP("R90CCY") { - @Override - public Vector op(Vector v) { - return VectorMath.rotate90CCY(v); - } - }); - } else if (getDirection(VectorMath.rotate90CZ(i.toVector())).equals(j)) { - permute.put(b, new DOP("R90CZ") { - @Override - public Vector op(Vector v) { - return VectorMath.rotate90CZ(v); - } - }); - } else if (getDirection(VectorMath.rotate90CCZ(i.toVector())).equals(j)) { - permute.put(b, new DOP("R90CCZ") { - @Override - public Vector op(Vector v) { - return VectorMath.rotate90CCZ(v); - } - }); - } else { - permute.put(b, new DOP("FAIL") { - @Override - public Vector op(Vector v) { - return v; - } - }); - } - } - } - } - - @Override - public String toString() { - switch (this) { - case D: - return "Down"; - case E: - return "East"; - case N: - return "North"; - case S: - return "South"; - case U: - return "Up"; - case W: - return "West"; - } - - return "?"; - } - - public boolean isVertical() { - return equals(D) || equals(U); - } - - public Vector toVector() { - return new Vector(x, y, z); - } - - public boolean isCrooked(Direction to) { - if (equals(to.reverse())) { - return false; - } - - return !equals(to); - } - - public Vector angle(Vector initial, Direction d) { - calculatePermutations(); - - for (GBiset i : permute.keySet()) { - if (i.getA().equals(this) && i.getB().equals(d)) { - return permute.get(i).op(initial); - } - } - - return initial; - } - - public Direction reverse() { - switch (this) { - case D: - return U; - case E: - return W; - case N: - return S; - case S: - return N; - case U: - return D; - case W: - return E; - default: - break; - } - - return null; - } - - public int x() { - return x; - } - - public int y() { - return y; - } - - public int z() { - return z; - } - - public CuboidDirection f() { - return f; - } - - /** - * Get the byte value represented in some directional blocks - * - * @return the byte value - */ - public byte byteValue() { - switch (this) { - case D: - return 0; - case E: - return 5; - case N: - return 2; - case S: - return 3; - case U: - return 1; - case W: - return 4; - default: - break; - } - - return -1; - } - - public BlockFace getFace() { - switch (this) { - case D: - return BlockFace.DOWN; - case E: - return BlockFace.EAST; - case N: - return BlockFace.NORTH; - case S: - return BlockFace.SOUTH; - case U: - return BlockFace.UP; - case W: - return BlockFace.WEST; - } - - return null; - } - - public Axis getAxis() { - switch (this) { - case D: - return Axis.Y; - case E: - return Axis.X; - case N: - return Axis.Z; - case S: - return Axis.Z; - case U: - return Axis.Y; - case W: - return Axis.X; - } - - return null; - } -} diff --git a/src/main/java/com/volmit/adapt/util/DirtyString.java b/src/main/java/com/volmit/adapt/util/DirtyString.java deleted file mode 100644 index 49db8d497..000000000 --- a/src/main/java/com/volmit/adapt/util/DirtyString.java +++ /dev/null @@ -1,49 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import org.bukkit.ChatColor; - -public class DirtyString { - - public static String write(Object data) { - return write(Json.toJson(data, false)); - } - - public static T fromJson(String data, Class t) { - return Json.fromJson(read(data), t); - } - - public static boolean has(String data) { - if (!HiddenStringUtils.hasHiddenString(data)) { - Adapt.info("Not has in " + data.replaceAll("\\Q" + ChatColor.COLOR_CHAR + "\\E", "&")); - } - return HiddenStringUtils.hasHiddenString(data); - } - - public static String write(String data) { - return HiddenStringUtils.encodeString(data); - } - - public static String read(String data) { - return HiddenStringUtils.extractHiddenString(data); - } -} - diff --git a/src/main/java/com/volmit/adapt/util/DoubleArrayUtils.java b/src/main/java/com/volmit/adapt/util/DoubleArrayUtils.java deleted file mode 100644 index 58d052dfa..000000000 --- a/src/main/java/com/volmit/adapt/util/DoubleArrayUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - - -import com.google.common.util.concurrent.AtomicDoubleArray; - -import java.util.Arrays; - -public class DoubleArrayUtils { - public static void shiftRight(double[] values, double push) { - for (int index = values.length - 2; index >= 0; index--) { - values[index + 1] = values[index]; - } - - values[0] = push; - } - - public static void wrapRight(double[] values) { - double last = values[values.length - 1]; - shiftRight(values, last); - } - - public static void fill(double[] values, double value) { - Arrays.fill(values, value); - } - - public static void fill(AtomicDoubleArray values, double value) { - for (int i = 0; i < values.length(); i++) { - values.set(i, value); - } - } - -} diff --git a/src/main/java/com/volmit/adapt/util/DoubleTag.java b/src/main/java/com/volmit/adapt/util/DoubleTag.java deleted file mode 100644 index d16cefa57..000000000 --- a/src/main/java/com/volmit/adapt/util/DoubleTag.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * The TAG_Double tag. - * - * @author Graham Edgecombe - */ -public final class DoubleTag extends Tag { - - /** - * The value. - */ - private final double value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public DoubleTag(String name, double value) { - super(name); - this.value = value; - } - - @Override - public Double getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_Double" + append + ": " + value; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Element.java b/src/main/java/com/volmit/adapt/util/Element.java deleted file mode 100644 index 6443c729a..000000000 --- a/src/main/java/com/volmit/adapt/util/Element.java +++ /dev/null @@ -1,77 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.inventory.ItemStack; - -import java.util.List; - -public interface Element { - MaterialBlock getMaterial(); - - Element setMaterial(MaterialBlock b); - - boolean isEnchanted(); - - Element setEnchanted(boolean enchanted); - - String getId(); - - String getName(); - - Element setName(String name); - - CustomModel getModel(); - - Element setModel(CustomModel model); - - double getProgress(); - - Element setProgress(double progress); - - short getEffectiveDurability(); - - int getCount(); - - Element setCount(int c); - - ItemStack computeItemStack(); - - Element setBackground(boolean bg); - - boolean isBackgrond(); - - Element addLore(String loreLine); - - List getLore(); - - Element call(ElementEvent event, Element context); - - Element onLeftClick(Callback clicked); - - Element onRightClick(Callback clicked); - - Element onShiftLeftClick(Callback clicked); - - Element onShiftRightClick(Callback clicked); - - Element onDraggedInto(Callback into); - - Element onOtherDraggedInto(Callback other); -} diff --git a/src/main/java/com/volmit/adapt/util/ElementEvent.java b/src/main/java/com/volmit/adapt/util/ElementEvent.java deleted file mode 100644 index 1412dd59c..000000000 --- a/src/main/java/com/volmit/adapt/util/ElementEvent.java +++ /dev/null @@ -1,33 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Element Event. - * - * @author cyberpwn - */ -public enum ElementEvent { - LEFT, - RIGHT, - SHIFT_LEFT, - SHIFT_RIGHT, - DRAG_INTO, - OTHER_DRAG_INTO -} diff --git a/src/main/java/com/volmit/adapt/util/FastParticle.java b/src/main/java/com/volmit/adapt/util/FastParticle.java deleted file mode 100644 index ab0ece1f8..000000000 --- a/src/main/java/com/volmit/adapt/util/FastParticle.java +++ /dev/null @@ -1,179 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Player; - -/** - * Simple Bukkit Particles API with 1.7 to 1.13.2 support ! - *

- * You can find the project on GitHub - * - * @author MrMicky - */ -public final class FastParticle { - - private static final ParticleSender PARTICLE_SENDER; - - static { - if (FastReflection.optionalClass("org.bukkit.Particle$DustOptions").isPresent()) { - PARTICLE_SENDER = new ParticleSender.ParticleSender1_13(); - } else if (FastReflection.optionalClass("org.bukkit.Particle").isPresent()) { - PARTICLE_SENDER = new ParticleSender.ParticleSenderImpl(); - } else { - PARTICLE_SENDER = new ParticleSenderLegacy(); - } - } - - private FastParticle() { - throw new UnsupportedOperationException(); - } - - /* - * Worlds methods - */ - public static void spawnParticle(World world, ParticleType particle, Location location, int count) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count) { - spawnParticle(world, particle, x, y, z, count, null); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, T data) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, data); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - T data) { - spawnParticle(world, particle, x, y, z, count, 0.0D, 0.0D, 0.0D, data); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, double offsetX, - double offsetY, double offsetZ) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ) { - spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, null); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, - double offsetX, double offsetY, double offsetZ, T data) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, - offsetZ, data); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, T data) { - spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, 1.0D, data); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, double offsetX, - double offsetY, double offsetZ, double extra) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra) { - spawnParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); - } - - public static void spawnParticle(World world, ParticleType particle, Location location, int count, - double offsetX, double offsetY, double offsetZ, double extra, T data) { - spawnParticle(world, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); - } - - public static void spawnParticle(World world, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra, T data) { - sendParticle(world, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } - - /* - * Player methods - */ - public static void spawnParticle(Player player, ParticleType particle, Location location, int count) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count) { - spawnParticle(player, particle, x, y, z, count, null); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, T data) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, data); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - T data) { - spawnParticle(player, particle, x, y, z, count, 0.0D, 0.0D, 0.0D, data); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, double offsetX, - double offsetY, double offsetZ) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ) { - spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, null); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, - double offsetX, double offsetY, double offsetZ, T data) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, data); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, T data) { - spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, 1.0D, data); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, double offsetX, - double offsetY, double offsetZ, double extra) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra) { - spawnParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null); - } - - public static void spawnParticle(Player player, ParticleType particle, Location location, int count, - double offsetX, double offsetY, double offsetZ, double extra, T data) { - spawnParticle(player, particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data); - } - - public static void spawnParticle(Player player, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra, T data) { - sendParticle(player, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } - - private static void sendParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, - double offsetX, double offsetY, double offsetZ, double extra, Object data) { - if (!particle.isSupported()) { - throw new IllegalArgumentException("The particle '" + particle + "' is not compatible with your server version"); - } - - PARTICLE_SENDER.spawnParticle(receiver, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } -} diff --git a/src/main/java/com/volmit/adapt/util/FastReflection.java b/src/main/java/com/volmit/adapt/util/FastReflection.java deleted file mode 100644 index 023a8a3c2..000000000 --- a/src/main/java/com/volmit/adapt/util/FastReflection.java +++ /dev/null @@ -1,77 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Bukkit; - -import java.util.Optional; - -/** - * Small reflection class to use CraftBukkit and NMS - * - * @author MrMicky - */ -public final class FastReflection { - - public static final String OBC_PACKAGE = "org.bukkit.craftbukkit"; - public static final String NMS_PACKAGE = "net.minecraft.server"; - - public static final String VERSION = Bukkit.getServer().getClass().getPackage().getName().substring(OBC_PACKAGE.length() + 1); - - private FastReflection() { - throw new UnsupportedOperationException(); - } - - public static String nmsClassName(String className) { - return NMS_PACKAGE + '.' + VERSION + '.' + className; - } - - public static Class nmsClass(String className) throws ClassNotFoundException { - return Class.forName(nmsClassName(className)); - } - - public static Optional> nmsOptionalClass(String className) { - return optionalClass(nmsClassName(className)); - } - - public static String obcClassName(String className) { - return OBC_PACKAGE + '.' + VERSION + '.' + className; - } - - public static Class obcClass(String className) throws ClassNotFoundException { - return Class.forName(obcClassName(className)); - } - - public static Optional> obcOptionalClass(String className) { - return optionalClass(obcClassName(className)); - } - - public static Optional> optionalClass(String className) { - try { - return Optional.of(Class.forName(className)); - } catch (ClassNotFoundException e) { - return Optional.empty(); - } - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - public static Object enumValueOf(Class enumClass, String enumName) { - return Enum.valueOf((Class) enumClass, enumName.toUpperCase()); - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/FinalInteger.java b/src/main/java/com/volmit/adapt/util/FinalInteger.java deleted file mode 100644 index 7877c923b..000000000 --- a/src/main/java/com/volmit/adapt/util/FinalInteger.java +++ /dev/null @@ -1,48 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Represents a number that can be finalized and be changed - * - * @author cyberpwn - */ -public class FinalInteger extends Wrapper { - public FinalInteger(Integer t) { - super(t); - } - - /** - * Add to this value - * - * @param i the number to add to this value (value = value + i) - */ - public void add(int i) { - set(get() + i); - } - - /** - * Subtract from this value - * - * @param i the number to subtract from this value (value = value - i) - */ - public void sub(int i) { - set(get() - i); - } -} diff --git a/src/main/java/com/volmit/adapt/util/FloatTag.java b/src/main/java/com/volmit/adapt/util/FloatTag.java deleted file mode 100644 index 303d2e20b..000000000 --- a/src/main/java/com/volmit/adapt/util/FloatTag.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * The TAG_Float tag. - * - * @author Graham Edgecombe - */ -public final class FloatTag extends Tag { - - /** - * The value. - */ - private final float value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public FloatTag(String name, float value) { - super(name); - this.value = value; - } - - @Override - public Float getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_Float" + append + ": " + value; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Form.java b/src/main/java/com/volmit/adapt/util/Form.java deleted file mode 100644 index e3d43f774..000000000 --- a/src/main/java/com/volmit/adapt/util/Form.java +++ /dev/null @@ -1,1352 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.math.BigInteger; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Form { - private static final String[] NAMES = new String[]{"Thousand", "Million", "Billion", "Trillion", "Quadrillion", "Quintillion", "Sextillion", "Septillion", "Octillion", "Nonillion", "Decillion", "Undecillion", "Duodecillion", "Tredecillion", "Quattuordecillion", "Quindecillion", "Sexdecillion", "Septendecillion", "Octodecillion", "Novemdecillion", "Vigintillion",}; - private static final BigInteger THOUSAND = BigInteger.valueOf(1000); - private static final NavigableMap MAP; - private static final LinkedHashMap roman_numerals = new LinkedHashMap(); - private static NumberFormat NF; - private static DecimalFormat DF; - - static { - MAP = new TreeMap(); - for (int i = 0; i < NAMES.length; i++) { - MAP.put(THOUSAND.pow(i + 1), NAMES[i]); - } - - roman_numerals.put("M", 1000); - roman_numerals.put("CM", 900); - roman_numerals.put("D", 500); - roman_numerals.put("CD", 400); - roman_numerals.put("C", 100); - roman_numerals.put("XC", 90); - roman_numerals.put("L", 50); - roman_numerals.put("XL", 40); - roman_numerals.put("X", 10); - roman_numerals.put("IX", 9); - roman_numerals.put("V", 5); - roman_numerals.put("IV", 4); - roman_numerals.put("I", 1); - } - - private static void instantiate() { - if (NF == null) { - NF = NumberFormat.getInstance(Locale.US); - } - } - - /** - * Scroll text - * - * @param smx the text - * @param viewport the viewport length - * @param time the timeline value - */ - public static String scroll(String smx, int viewport, long time) { - String src = Form.repeat(" ", viewport) + smx + Form.repeat(" ", viewport); - int len = src.length(); - int walk = (int) (time % (len - viewport)); - String base = src.substring(walk, M.min(walk + viewport, len - 1)); - base = base.length() < viewport ? base + Form.repeat(" ", (viewport - base.length()) - 3) : base; - - return base; - } - - /** - * Capitalize the first letter - * - * @param s the string - * @return the capitalized string - */ - public static String capitalize(String s) { - String roll = ""; - boolean f = true; - - for (Character i : s.trim().toCharArray()) { - if (f) { - roll += Character.toUpperCase(i); - f = false; - } else { - roll += i; - } - } - - return roll; - } - - /** - * Capitalize all words in the string - * - * @param s the string - * @return the capitalized string - */ - public static String capitalizeWords(String s) { - String rollx = ""; - - for (String i : s.trim().split(" ")) { - rollx += " " + capitalize(i.trim()); - } - - return rollx.substring(1); - } - - /** - * Hard word wrap - * - * @param s the words - * @param len the length per line - * @return the wrapped string - */ - public static String wrap(String s, int len) { - return wrap(s, len, null, false); - } - - /** - * Soft Word wrap - * - * @param s the string - * @param len the length to wrap - * @return the wrapped string - */ - public static String wrapWords(String s, int len) { - return wrap(s, len, null, true); - } - - public static String wrapWordsPrefixed(String s, String prefix, int len) { - return wrapPrefixed(s, prefix, len, null, true); - } - - /** - * Wrap words - * - * @param s the string - * @param len the wrap length - * @param newLineSep the new line seperator - * @param soft should it be soft wrapped or hard wrapped? - * @return the wrapped words - */ - public static String wrap(String s, int len, String newLineSep, boolean soft) { - return wrap(s, len, newLineSep, soft, " "); - } - - public static String wrapPrefixed(String s, String pref, int len, String newLineSep, boolean soft) { - return pref + wrapPrefixed(s, pref, len, newLineSep, soft, " ").replaceAll("\\Q\n\\E", "\n" + pref); - } - - /** - * Wrap words - * - * @param s the string - * @param len the length - * @param newLineSep the new line seperator - * @param soft soft or hard wrapping - * @param regex the regex - * @return the wrapped string - */ - public static String wrap(String s, int len, String newLineSep, boolean soft, String regex) { - if (s == null) { - return null; - } else { - if (newLineSep == null) { - newLineSep = "\n"; - } - - if (len < 1) { - len = 1; - } - - if (regex.trim().equals("")) { - regex = " "; - } - - Pattern arg4 = Pattern.compile(regex); - int arg5 = s.length(); - int arg6 = 0; - StringBuilder arg7 = new StringBuilder(arg5 + 32); - - while (arg6 < arg5) { - int arg8 = -1; - Matcher arg9 = arg4.matcher(s.substring(arg6, Math.min(arg6 + len + 1, arg5))); - if (arg9.find()) { - if (arg9.start() == 0) { - arg6 += arg9.end(); - continue; - } - - arg8 = arg9.start(); - } - - if (arg5 - arg6 <= len) { - break; - } - - while (arg9.find()) { - arg8 = arg9.start() + arg6; - } - - if (arg8 >= arg6) { - arg7.append(s, arg6, arg8); - arg7.append(newLineSep); - arg6 = arg8 + 1; - } else if (soft) { - arg7.append(s, arg6, len + arg6); - arg7.append(newLineSep); - arg6 += len; - } else { - arg9 = arg4.matcher(s.substring(arg6 + len)); - if (arg9.find()) { - arg8 = arg9.start() + arg6 + len; - } - - if (arg8 >= 0) { - arg7.append(s, arg6, arg8); - arg7.append(newLineSep); - arg6 = arg8 + 1; - } else { - arg7.append(s.substring(arg6)); - arg6 = arg5; - } - } - } - - arg7.append(s.substring(arg6)); - return arg7.toString(); - } - } - - public static String wrapPrefixed(String s, String pref, int len, String newLineSep, boolean soft, String regex) { - if (s == null) { - return null; - } else { - if (newLineSep == null) { - newLineSep = "\n"; - } - - if (len < 1) { - len = 1; - } - - if (regex.trim().equals("")) { - regex = " "; - } - - Pattern arg4 = Pattern.compile(regex); - int arg5 = s.length(); - int arg6 = 0; - StringBuilder arg7 = new StringBuilder(arg5 + 32); - - while (arg6 < arg5) { - int arg8 = -1; - Matcher arg9 = arg4.matcher(s.substring(arg6, Math.min(arg6 + len + 1, arg5))); - if (arg9.find()) { - if (arg9.start() == 0) { - arg6 += arg9.end(); - continue; - } - - arg8 = arg9.start(); - } - - if (arg5 - arg6 <= len) { - break; - } - - while (arg9.find()) { - arg8 = arg9.start() + arg6; - } - - if (arg8 >= arg6) { - arg7.append(s, arg6, arg8); - arg7.append(newLineSep); - arg6 = arg8 + 1; - } else if (soft) { - arg7.append(s, arg6, len + arg6); - arg7.append(newLineSep); - arg6 += len; - } else { - arg9 = arg4.matcher(s.substring(arg6 + len)); - if (arg9.find()) { - arg8 = arg9.start() + arg6 + len; - } - - if (arg8 >= 0) { - arg7.append(s, arg6, arg8); - arg7.append(newLineSep); - arg6 = arg8 + 1; - } else { - arg7.append(s.substring(arg6)); - arg6 = arg5; - } - } - } - - arg7.append(s.substring(arg6)); - return arg7.toString(); - } - } - - /** - * Returns a fancy duration up to Years - * - * @param duration the duration in ms - * @return the fancy duration - */ - public static String duration(RollingSequence rollingSequence, long duration) { - String suffix = "Millisecond"; - double phantom = duration; - int div = 1000; - - if (phantom > div) { - phantom /= div; - suffix = "Second"; - div = 60; - - if (phantom > div) { - phantom /= div; - suffix = "Minute"; - - if (phantom > div) { - phantom /= div; - suffix = "Hour"; - div = 24; - - if (phantom > 24) { - phantom /= div; - suffix = "Day"; - div = 7; - - if (phantom > div) { - phantom /= div; - suffix = "Week"; - div = 4; - - if (phantom > div) { - phantom /= div; - suffix = "Month"; - div = 12; - - if (phantom > div) { - phantom /= div; - suffix = "Year"; - return Form.fd(phantom, 0) + " " + suffix + ((int) phantom == 1 ? "" : "s"); - } else { - return Form.fd(phantom, 0) + " " + suffix + ((int) phantom == 1 ? "" : "s"); - } - } else { - return Form.fd(phantom, 0) + " " + suffix + ((int) phantom == 1 ? "" : "s"); - } - } else { - return Form.fd(phantom, 0) + " " + suffix + ((int) phantom == 1 ? "" : "s"); - } - } else { - return Form.fd(phantom, 0) + " " + suffix + ((int) phantom == 1 ? "" : "s"); - } - } else { - return Form.fd(phantom, 0) + " " + suffix + ((int) phantom == 1 ? "" : "s"); - } - } else { - return Form.fd(phantom, 0) + " " + suffix + ((int) phantom == 1 ? "" : "s"); - } - } else { - return "Under a Second"; - } - } - - /** - * Fixes the minute issue with formatting - * - * @param c the calendar - * @return the minute string - */ - public static String fmin(Calendar c) { - String s = c.get(Calendar.MINUTE) + ""; - if (s.length() == 1) { - return "0" + s; - } - - return s; - } - - /** - * Get a fancy time stamp - * - * @param time the stamp in time (ago) - * @return the fancy stamp in time (ago) - */ - public static String ago(long time) { - long current = M.ms(); - - if (time > current - TimeUnit.SECONDS.toMillis(30) && time < current) { - return "Just Now"; - } else if (time > current - TimeUnit.SECONDS.toMillis(60) && time < current) { - return "Seconds Ago"; - } else if (time > current - TimeUnit.MINUTES.toMillis(10) && time < current) { - return "Minutes Ago"; - } else { - Calendar now = Calendar.getInstance(); - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(time); - boolean sameYear = now.get(Calendar.YEAR) == c.get(Calendar.YEAR); - boolean sameDay = now.get(Calendar.DAY_OF_YEAR) == c.get(Calendar.DAY_OF_YEAR); - - if (sameDay) { - int h = c.get(Calendar.HOUR); - h = h == 0 ? 12 : h; - - return "Today at " + h + ":" + fmin(c) + " " + (c.get(Calendar.AM_PM) == Calendar.PM ? "PM" : "AM"); - } else if (sameYear) { - boolean yesterday = now.get(Calendar.DAY_OF_YEAR) - 1 == c.get(Calendar.DAY_OF_YEAR); - - if (yesterday) { - int h = c.get(Calendar.HOUR); - h = h == 0 ? 12 : h; - - return "Yesterday at " + h + ":" + fmin(c) + " " + (c.get(Calendar.AM_PM) == Calendar.PM ? "PM" : "AM"); - } else { - int h = c.get(Calendar.HOUR); - h = h == 0 ? 12 : h; - String dow = "Error Day"; - - switch (c.get(Calendar.DAY_OF_WEEK)) { - case Calendar.SUNDAY: - dow = "Sunday"; - break; - case Calendar.MONDAY: - dow = "Monday"; - break; - case Calendar.TUESDAY: - dow = "Tuesday"; - break; - case Calendar.WEDNESDAY: - dow = "Wednesday"; - break; - case Calendar.THURSDAY: - dow = "Thursday"; - break; - case Calendar.FRIDAY: - dow = "Friday"; - break; - case Calendar.SATURDAY: - dow = "Saturday"; - break; - } - - String monthName = "Error Month"; - int month = c.get(Calendar.MONTH); - - switch (month) { - case Calendar.JANUARY: - monthName = "Jan"; - break; - case Calendar.FEBRUARY: - monthName = "Feb"; - break; - case Calendar.MARCH: - monthName = "Mar"; - break; - case Calendar.APRIL: - monthName = "Apr"; - break; - case Calendar.MAY: - monthName = "May"; - break; - case Calendar.JUNE: - monthName = "Jun"; - break; - case Calendar.JULY: - monthName = "Jul"; - break; - case Calendar.AUGUST: - monthName = "Aug"; - break; - case Calendar.SEPTEMBER: - monthName = "Sep"; - break; - case Calendar.OCTOBER: - monthName = "Oct"; - break; - case Calendar.NOVEMBER: - monthName = "Nov"; - break; - case Calendar.DECEMBER: - monthName = "Dec"; - break; - } - - int dayOfMonth = c.get(Calendar.DAY_OF_MONTH); - String suffix = numberSuffix(dayOfMonth); - - return dow + ", " + monthName + " " + suffix + " at " + h + ":" + fmin(c) + " " + (c.get(Calendar.AM_PM) == Calendar.PM ? "PM" : "AM"); - } - } else { - int h = c.get(Calendar.HOUR); - h = h == 0 ? 12 : h; - String dow = "Error Day"; - - switch (c.get(Calendar.DAY_OF_WEEK)) { - case Calendar.SUNDAY: - dow = "Sunday"; - break; - case Calendar.MONDAY: - dow = "Monday"; - break; - case Calendar.TUESDAY: - dow = "Tuesday"; - break; - case Calendar.WEDNESDAY: - dow = "Wednesday"; - break; - case Calendar.THURSDAY: - dow = "Thursday"; - break; - case Calendar.FRIDAY: - dow = "Friday"; - break; - case Calendar.SATURDAY: - dow = "Saturday"; - break; - } - - String monthName = "Error Month"; - int month = c.get(Calendar.MONTH); - - switch (month) { - case Calendar.JANUARY: - monthName = "Jan"; - break; - case Calendar.FEBRUARY: - monthName = "Feb"; - break; - case Calendar.MARCH: - monthName = "Mar"; - break; - case Calendar.APRIL: - monthName = "Apr"; - break; - case Calendar.MAY: - monthName = "May"; - break; - case Calendar.JUNE: - monthName = "Jun"; - break; - case Calendar.JULY: - monthName = "Jul"; - break; - case Calendar.AUGUST: - monthName = "Aug"; - break; - case Calendar.SEPTEMBER: - monthName = "Sep"; - break; - case Calendar.OCTOBER: - monthName = "Oct"; - break; - case Calendar.NOVEMBER: - monthName = "Nov"; - break; - case Calendar.DECEMBER: - monthName = "Dec"; - break; - } - - int dayOfMonth = c.get(Calendar.DAY_OF_MONTH); - String suffix = numberSuffix(dayOfMonth); - int year = c.get(Calendar.YEAR); - - return year + ", " + dow + ", " + monthName + " " + suffix + " at " + h + ":" + fmin(c) + " " + (c.get(Calendar.AM_PM) == Calendar.PM ? "PM" : "AM"); - } - } - } - - /** - * Get the suffix for a number i.e. 1st 2nd 3rd - * - * @param i the number - * @return the suffix - */ - public static String numberSuffix(int i) { - String[] sufixes = new String[]{"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"}; - switch (i % 100) { - case 11: - case 12: - case 13: - return i + "th"; - default: - return i + sufixes[i % 10]; - - } - } - - /** - * Get a high accuracy but limited range duration (accurate up to a couple - * minutes) - * - * @param ms the milliseconds (double) - * @param prec the precision (decimal format) - * @return the formatted string - */ - public static String duration(double ms, int prec) { - if (ms < 1000.0) { - return Form.f(ms, prec) + "ms"; - } - - if (ms / 1000.0 < 60.0) { - return Form.f(ms / 1000.0, prec) + "s"; - } - - if (ms / 1000.0 / 60.0 < 60.0) { - return Form.f(ms / 1000.0 / 60.0, prec) + "m"; - } - - if (ms / 1000.0 / 60.0 / 60.0 < 24.0) { - return Form.f(ms / 1000.0 / 60.0 / 60.0, prec) + " hours"; - } - - if (ms / 1000.0 / 60.0 / 60.0 / 24.0 < 7) { - return Form.f(ms / 1000.0 / 60.0 / 24.0, prec) + " days"; - } - - return Form.f(ms, prec) + "ms"; - } - - public static String duration(long ms) { - return duration(ms, 0); - } - - /** - * Get a duration from milliseconds up to days - * - * @param ms the ms - * @param prec the precision (decimal format) - * @return the formatted string - */ - public static String duration(long ms, int prec) { - if (ms < 1000.0) { - return Form.f(ms, prec) + "ms"; - } - - if (ms / 1000.0 < 60.0) { - return Form.f(ms / 1000.0, prec) + " seconds"; - } - - if (ms / 1000.0 / 60.0 < 60.0) { - return Form.f(ms / 1000.0 / 60.0, prec) + " minutes"; - } - - if (ms / 1000.0 / 60.0 / 60.0 < 24.0) { - return Form.f(ms / 1000.0 / 60.0 / 60.0, prec) + " hours"; - } - - if (ms / 1000.0 / 60.0 / 60.0 / 24.0 < 7) { - return Form.f(ms / 1000.0 / 60.0 / 24.0, prec) + " days"; - } - - return Form.f(ms, prec) + "ms"; - } - - /** - * Format a big value - * - * @param i the number - * @return the full value in string - */ - public static String b(int i) { - return b(new BigInteger(String.valueOf(i))); - } - - /** - * Format a big value - * - * @param i the number - * @return the full value in string - */ - public static String b(long i) { - return b(new BigInteger(String.valueOf(i))); - } - - /** - * Format a big value - * - * @param i the number - * @return the full value in string - */ - public static String b(double i) { - return b(new BigInteger(String.valueOf((long) i))); - } - - /** - * Format a big number - * - * @param number the big number - * @return the value in string - */ - public static String b(BigInteger number) { - Entry entry = MAP.floorEntry(number); - if (entry == null) { - return "Nearly nothing"; - } - - BigInteger key = entry.getKey(); - BigInteger d = key.divide(THOUSAND); - BigInteger m = number.divide(d); - float f = m.floatValue() / 1000.0f; - float rounded = ((int) (f * 100.0)) / 100.0f; - - if (rounded % 1 == 0) { - return ((int) rounded) + " " + entry.getValue(); - } - - return rounded + " " + entry.getValue(); - } - - /** - * Calculate a fancy string representation of a file size. Adds a suffix of B, - * KB, MB, GB, or TB - * - * @param s the size (in bytes) - * @return the string - */ - public static String fileSize(long s) { - return ofSize(s, 1000); - } - - /** - * ":", "a", "b", "c" -> a:b:c - * - * @param splitter the splitter that goes in between - * @param strings the strings - * @return the result - */ - public static String split(String splitter, String... strings) { - StringBuilder b = new StringBuilder(); - - for (String i : strings) { - b.append(splitter); - b.append(i); - } - - return b.substring(splitter.length()); - } - - /** - * Calculate a fancy string representation of a file size. Adds a suffix of B, - * KB, MB, GB, or TB - * - * @param s the size (in bytes) - * @return the string - */ - public static String memSize(long s) { - return ofSize(s, 1024); - } - - public static String memSize(long s, int dec) { - return ofSize(s, 1024, dec); - } - - /** - * Get the timestamp of the time t (ms since 1970) - * - * @param t the time - * @return the stamp - */ - @SuppressWarnings("deprecation") - public static String stamp(long t) { - Date d = new Date(t); - return d.getMonth() + "-" + d.getDate() + "-" + (d.getYear() + 1900) + " " + d.getHours() + "h " + d.getMinutes() + "m " + d.getSeconds() + "s "; - } - - @SuppressWarnings("deprecation") - public static String stampTime(long t) { - Date d = new Date(t); - - return Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + ":" + forceDoubleDigit(d.getMinutes()) + ":" + forceDoubleDigit(d.getSeconds()); - } - - public static String forceDoubleDigit(int dig) { - if (dig < 10) { - return "0" + dig; - } - - return dig + ""; - } - - @SuppressWarnings("deprecation") - public static String stampDay(long t) { - Date d = new Date(t); - return d.getMonth() + "-" + d.getDate() + "-" + (d.getYear() + 1900); - } - - /** - * Calculate a fancy string representation of a size in B, KB, MB, GB, or TB - * with a special divisor. The divisor decides how much goes up in the suffix - * chain. - * - * @param s the size (in bytes) - * @param div the divisor - * @return the string - */ - public static String ofSize(long s, int div) { - Double d = (double) s; - String sub = "Bytes"; - - if (d > div - 1) { - d /= div; - sub = "KB"; - - if (d > div - 1) { - d /= div; - sub = "MB"; - - if (d > div - 1) { - d /= div; - sub = "GB"; - - if (d > div - 1) { - d /= div; - sub = "TB"; - } - } - } - } - - if (sub.equals("GB") || sub.equals("TB")) { - return Form.f(d, 1) + sub; - } else { - return Form.f(d, 0) + sub; - } - } - - /** - * Calculate a fancy string representation of a size in B, KB, MB, GB, or TB - * with a special divisor. The divisor decides how much goes up in the suffix - * chain. - * - * @param s the size (in bytes) - * @param div the divisor - * @param dec the decimal places - * @return the string - */ - public static String ofSize(long s, int div, int dec) { - Double d = (double) s; - String sub = "Bytes"; - - if (d > div - 1) { - d /= div; - sub = "KB"; - - if (d > div - 1) { - d /= div; - sub = "MB"; - - if (d > div - 1) { - d /= div; - sub = "GB"; - - if (d > div - 1) { - d /= div; - sub = "TB"; - } - } - } - } - - return Form.f(d, dec) + " " + sub; - } - - /** - * Calculate a fancy string representation of a size in Grams, KG, MG, GG, TG - * with a special divisor. The divisor decides how much goes up in the suffix - * chain. - * - * @param s the size (in bytes) - * @param div the divisor - * @param dec the decimal places - * @return the string - */ - public static String ofSizeMetricWeight(long s, int div, int dec) { - boolean neg = s < 0; - if (neg) { - s = -s; - } - Double d = (double) s; - String sub = "Grams"; - - if (d > div - 1) { - d /= div; - sub = "KG"; - - if (d > div - 1) { - d /= div; - sub = "MG"; - - if (d > div - 1) { - d /= div; - sub = "GG"; - - if (d > div - 1) { - d /= div; - sub = "TG"; - } - } - } - } - - return (neg ? "-" : "") + Form.f(d, dec) + " " + sub; - } - - /** - * Trim a string to a length, then append ... at the end if it extends the limit - * - * @param s the string - * @param l the limit - * @return the modified string - */ - public static String trim(String s, int l) { - if (s.length() <= l) { - return s; - } - - return s.substring(0, l) + "..."; - } - - /** - * Get a class name into a configuration/filename key For example, - * PhantomController.class is converted to phantom-controller - * - * @param clazz the class - * @return the string representation - */ - public static String cname(String clazz) { - String codeName = ""; - - for (Character i : clazz.toCharArray()) { - if (Character.isUpperCase(i)) { - codeName = codeName + "-" + Character.toLowerCase(i); - } else { - codeName = codeName + i; - } - } - - if (codeName.startsWith("-")) { - codeName = codeName.substring(1); - } - - return codeName; - } - - /** - * Get a formatted representation of the memory given in megabytes - * - * @param mb the megabytes - * @return the string representation with suffixes - */ - public static String mem(long mb) { - if (mb < 1024) { - return f(mb) + " MB"; - } else { - return f(((double) mb / (double) 1024), 1) + " GB"; - } - } - - /** - * Get a formatted representation of the memory given in kilobytes - * - * @param kb the kilobytes - * @return the string representation with suffixes - */ - public static String memx(long kb) { - if (kb < 1024) { - return fd(kb, 2) + " KB"; - } else { - double mb = (double) kb / 1024.0; - - if (mb < 1024) { - return fd(mb, 2) + " MB"; - } else { - double gb = mb / 1024.0; - - return fd(gb, 2) + " GB"; - } - } - } - - /** - * Format a long. Changes -10334 into -10,334 - * - * @param i the number - * @return the string representation of the number - */ - public static String f(long i) { - instantiate(); - return NF.format(i); - } - - /** - * Format a number. Changes -10334 into -10,334 - * - * @param i the number - * @return the string representation of the number - */ - public static String f(int i) { - instantiate(); - return NF.format(i); - } - - /** - * Formats a double's decimals to a limit - * - * @param i the double - * @param p the number of decimal places to use - * @return the formated string - */ - public static String f(double i, int p) { - String form = "#"; - - if (p > 0) { - form = form + "." + repeat("#", p); - } - - DF = new DecimalFormat(form); - - return DF.format(i); - } - - /** - * Formats a double's decimals to a limit, however, this will add zeros to the - * decimal places that dont need to be placed down. 2.4343 formatted with 6 - * decimals gets returned as 2.434300 - * - * @param i the double - * @param p the number of decimal places to use - * @return the formated string - */ - public static String fd(double i, int p) { - String form = "0"; - - if (p > 0) { - form = form + "." + repeat("0", p); - } - - DF = new DecimalFormat(form); - - return DF.format(i); - } - - /** - * Formats a float's decimals to a limit - * - * @param i the float - * @param p the number of decimal places to use - * @return the formated string - */ - public static String f(float i, int p) { - String form = "#"; - - if (p > 0) { - form = form + "." + repeat("#", p); - } - - DF = new DecimalFormat(form); - - return DF.format(i); - } - - /** - * Formats a double's decimals (one decimal point) - * - * @param i the double - */ - public static String f(double i) { - return f(i, 1); - } - - /** - * Formats a float's decimals (one decimal point) - * - * @param i the float - */ - public static String f(float i) { - return f(i, 1); - } - - /** - * Get a percent representation of a double and decimal places (0.53) would - * return 53% - * - * @param i the double - * @param p the number of decimal points - * @return a string - */ - public static String pc(double i, int p) { - return f(i * 100.0, p) + "%"; - } - - /** - * Get a percent representation of a float and decimal places (0.53) would - * return 53% - * - * @param i the float - * @param p the number of decimal points - * @return a string - */ - public static String pc(float i, int p) { - return f(i * 100, p) + "%"; - } - - /** - * Get a percent representation of a double and zero decimal places (0.53) would - * return 53% - * - * @param i the double - * @return a string - */ - public static String pc(double i) { - return f(i * 100, 0) + "%"; - } - - /** - * Get a percent representation of a float and zero decimal places (0.53) would - * return 53% - * - * @param i the double - * @return a string - */ - public static String pc(float i) { - return f(i * 100, 0) + "%"; - } - - /** - * Get a percent as the percent of i out of "of" with custom decimal places - * - * @param i the percent out of - * @param of of of - * @param p the decimal places - * @return the string - */ - public static String pc(int i, int of, int p) { - return f(100.0 * (((double) i) / ((double) of)), p) + "%"; - } - - /** - * Get a percent as the percent of i out of "of" - * - * @param i the percent out of - * @param of of of - * @return the string - */ - public static String pc(int i, int of) { - return pc(i, of, 0); - } - - /** - * Get a percent as the percent of i out of "of" with custom decimal places - * - * @param i the percent out of - * @param of of of - * @param p the decimal places - * @return the string - */ - public static String pc(long i, long of, int p) { - return f(100.0 * (((double) i) / ((double) of)), p) + "%"; - } - - /** - * Get a percent as the percent of i out of "of" - * - * @param i the percent out of - * @param of of of - * @return the string - */ - public static String pc(long i, long of) { - return pc(i, of, 0); - } - - /** - * Milliseconds to seconds (double) - * - * @param ms the milliseconds - * @return a formatted string to milliseconds - */ - public static String msSeconds(long ms) { - return f((double) ms / 1000.0); - } - - /** - * Milliseconds to seconds (double) custom decimals - * - * @param ms the milliseconds - * @param p number of decimal points - * @return a formatted string to milliseconds - */ - public static String msSeconds(long ms, int p) { - return f((double) ms / 1000.0, p); - } - - /** - * nanoseconds to seconds (double) - * - * @param ms the nanoseconds - * @return a formatted string to nanoseconds - */ - public static String nsMs(long ns) { - return f((double) ns / 1000000.0); - } - - /** - * nanoseconds to seconds (double) custom decimals - * - * @param ms the nanoseconds - * @param p number of decimal points - * @return a formatted string to nanoseconds - */ - public static String nsMs(long ns, int p) { - return f((double) ns / 1000000.0, p); - } - - /** - * nanoseconds to seconds (double) custom decimals - * - * @param ms the nanoseconds - * @param p number of decimal points - * @return a formatted string to nanoseconds - */ - public static String nsMsd(long ns, int p) { - return fd((double) ns / 1000000.0, p); - } - - /** - * Get roman numeral representation of the int - * - * @param num the int - * @return the numerals - */ - public static String toRoman(int num) { - - String res = ""; - - for (Map.Entry entry : roman_numerals.entrySet()) { - int matches = num / entry.getValue(); - - res += repeat(entry.getKey(), matches); - num = num % entry.getValue(); - } - - return res; - } - - /** - * Get the number representation from roman numerals. - * - * @param number the roman number - * @return the int representation - */ - public static int fromRoman(String number) { - if (number.isEmpty()) { - return 0; - } - - number = number.toUpperCase(); - - if (number.startsWith("M")) { - return 1000 + fromRoman(number.substring(1)); - } - - if (number.startsWith("CM")) { - return 900 + fromRoman(number.substring(2)); - } - - if (number.startsWith("D")) { - return 500 + fromRoman(number.substring(1)); - } - - if (number.startsWith("CD")) { - return 400 + fromRoman(number.substring(2)); - } - - if (number.startsWith("C")) { - return 100 + fromRoman(number.substring(1)); - } - - if (number.startsWith("XC")) { - return 90 + fromRoman(number.substring(2)); - } - - if (number.startsWith("L")) { - return 50 + fromRoman(number.substring(1)); - } - - if (number.startsWith("XL")) { - return 40 + fromRoman(number.substring(2)); - } - - if (number.startsWith("X")) { - return 10 + fromRoman(number.substring(1)); - } - - if (number.startsWith("IX")) { - return 9 + fromRoman(number.substring(2)); - } - - if (number.startsWith("V")) { - return 5 + fromRoman(number.substring(1)); - } - - if (number.startsWith("IV")) { - return 4 + fromRoman(number.substring(2)); - } - - if (number.startsWith("I")) { - return 1 + fromRoman(number.substring(1)); - } - - return 0; - } - - /** - * Repeat a string - * - * @param s the string - * @param n the amount of times to repeat - * @return the repeated string - */ - public static String repeat(String s, int n) { - if (s == null) { - return null; - } - - final StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < n; i++) { - sb.append(s); - } - - return sb.toString(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/Function2.java b/src/main/java/com/volmit/adapt/util/Function2.java deleted file mode 100644 index c62dd142b..000000000 --- a/src/main/java/com/volmit/adapt/util/Function2.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Function2 { - R apply(A a, B b); -} diff --git a/src/main/java/com/volmit/adapt/util/Function3.java b/src/main/java/com/volmit/adapt/util/Function3.java deleted file mode 100644 index 518b825ad..000000000 --- a/src/main/java/com/volmit/adapt/util/Function3.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Function3 { - R apply(A a, B b, C c); -} diff --git a/src/main/java/com/volmit/adapt/util/Function4.java b/src/main/java/com/volmit/adapt/util/Function4.java deleted file mode 100644 index 5ec74050c..000000000 --- a/src/main/java/com/volmit/adapt/util/Function4.java +++ /dev/null @@ -1,25 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@SuppressWarnings("hiding") -@FunctionalInterface -public interface Function4 { - R apply(A a, B b, C c, D d); -} diff --git a/src/main/java/com/volmit/adapt/util/GBiset.java b/src/main/java/com/volmit/adapt/util/GBiset.java deleted file mode 100644 index b64d8435f..000000000 --- a/src/main/java/com/volmit/adapt/util/GBiset.java +++ /dev/null @@ -1,81 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - - -import java.io.Serializable; - -/** - * A Biset - * - * @param the first object type - * @param the second object type - * @author cyberpwn - */ -@SuppressWarnings("hiding") -public class GBiset implements Serializable { - private static final long serialVersionUID = 1L; - private A a; - private B b; - - /** - * Create a new Biset - * - * @param a the first object - * @param b the second object - */ - public GBiset(A a, B b) { - this.a = a; - this.b = b; - } - - /** - * Get the object of the type A - * - * @return the first object - */ - public A getA() { - return a; - } - - /** - * Set the first object - * - * @param a the first object A - */ - public void setA(A a) { - this.a = a; - } - - /** - * Get the second object - * - * @return the second object - */ - public B getB() { - return b; - } - - /** - * Set the second object - */ - public void setB(B b) { - this.b = b; - } -} diff --git a/src/main/java/com/volmit/adapt/util/GListAdapter.java b/src/main/java/com/volmit/adapt/util/GListAdapter.java deleted file mode 100644 index 65e1f8c50..000000000 --- a/src/main/java/com/volmit/adapt/util/GListAdapter.java +++ /dev/null @@ -1,60 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - - -import java.util.ArrayList; -import java.util.List; - -/** - * Adapts a list of objects into a list of other objects - * - * @param the from object in lists (the item INSIDE the list) - * @param the to object in lists (the item INSIDE the list) - * @author cyberpwn - */ -public abstract class GListAdapter { - /** - * Adapts a list of FROM to a list of TO - * - * @param from the from list - * @return the to list - */ - public List adapt(List from) { - List adapted = new ArrayList<>(); - - for (FROM i : from) { - TO t = onAdapt(i); - - if (t != null) { - adapted.add(onAdapt(i)); - } - } - - return adapted; - } - - /** - * Adapts a list object FROM to TO for use with the adapt method - * - * @param from the from object - * @return the to object - */ - public abstract TO onAdapt(FROM from); -} diff --git a/src/main/java/com/volmit/adapt/util/GroupedExecutor.java b/src/main/java/com/volmit/adapt/util/GroupedExecutor.java deleted file mode 100644 index d1fadbff5..000000000 --- a/src/main/java/com/volmit/adapt/util/GroupedExecutor.java +++ /dev/null @@ -1,113 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; -import java.util.concurrent.ForkJoinWorkerThread; - -public class GroupedExecutor { - private final ExecutorService service; - private final Map mirror; - private int xc; - - public GroupedExecutor(int threadLimit, int priority, String name) { - xc = 1; - mirror = new HashMap<>(); - - if (threadLimit == 1) { - service = Executors.newSingleThreadExecutor((r) -> - { - Thread t = new Thread(r); - t.setName(name); - t.setPriority(priority); - - return t; - }); - } else if (threadLimit > 1) { - final ForkJoinWorkerThreadFactory factory = new ForkJoinWorkerThreadFactory() { - @Override - public ForkJoinWorkerThread newThread(ForkJoinPool pool) { - final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); - worker.setName(name + " " + xc++); - worker.setPriority(priority); - return worker; - } - }; - - service = new ForkJoinPool(threadLimit, factory, null, false); - } else { - service = Executors.newCachedThreadPool((r) -> - { - Thread t = new Thread(r); - t.setName(name + " " + xc++); - t.setPriority(priority); - - return t; - }); - } - } - - public void waitFor(String g) { - if (g == null) { - return; - } - - if (!mirror.containsKey(g)) { - return; - } - - while (true) { - if (mirror.get(g) == 0) { - break; - } - } - } - - public void queue(String q, NastyRunnable r) { - mirror.compute(q, (k, v) -> k == null || v == null ? 1 : v + 1); - - service.execute(() -> - { - try { - r.run(); - } catch (Throwable e) { - e.printStackTrace(); - } - - mirror.compute(q, (k, v) -> v - 1); - }); - } - - public void close() { - J.a(() -> - { - J.sleep(100); - service.shutdown(); - }); - } - - public void closeNow() { - service.shutdown(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/GuiConfirm.java b/src/main/java/com/volmit/adapt/util/GuiConfirm.java deleted file mode 100644 index d32329add..000000000 --- a/src/main/java/com/volmit/adapt/util/GuiConfirm.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.entity.Player; - -public final class GuiConfirm { - private GuiConfirm() { - } - - public static void open( - Player player, - String title, - String message, - Runnable onConfirm, - Runnable onCancel - ) { - if (player == null) { - return; - } - - if (!Bukkit.isPrimaryThread()) { - J.s(() -> open(player, title, message, onConfirm, onCancel)); - return; - } - - Window w = new UIWindow(player); - GuiTheme.apply(w, "confirm"); - w.setViewportHeight(3); - - w.setElement(0, 0, new UIElement("confirm-msg") - .setMaterial(new MaterialBlock(Material.PAPER)) - .setName(C.WHITE + (title == null ? "Confirm" : title)) - .addLore(C.GRAY + (message == null ? "Apply this change?" : message))); - - w.setElement(-2, 1, new UIElement("confirm-yes") - .setMaterial(new MaterialBlock(Material.LIME_STAINED_GLASS_PANE)) - .setName(C.GREEN + "Confirm") - .onLeftClick((e) -> { - w.close(); - if (onConfirm != null) { - onConfirm.run(); - } - })); - - w.setElement(2, 1, new UIElement("confirm-no") - .setMaterial(new MaterialBlock(Material.RED_STAINED_GLASS_PANE)) - .setName(C.RED + "Cancel") - .onLeftClick((e) -> { - w.close(); - if (onCancel != null) { - onCancel.run(); - } - })); - - w.setTitle(C.GRAY + "Confirm"); - w.onClosed((window) -> Adapt.instance.getGuiLeftovers().remove(player.getUniqueId().toString())); - w.open(); - Adapt.instance.getGuiLeftovers().put(player.getUniqueId().toString(), w); - } -} diff --git a/src/main/java/com/volmit/adapt/util/GuiEffects.java b/src/main/java/com/volmit/adapt/util/GuiEffects.java deleted file mode 100644 index 7d4709db5..000000000 --- a/src/main/java/com/volmit/adapt/util/GuiEffects.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.volmit.adapt.util; - -import java.util.List; - -public final class GuiEffects { - private GuiEffects() { - } - - public static void applyReveal(Window window, List placements) { - if (window == null || placements == null || placements.isEmpty()) { - return; - } - - for (Placement placement : placements) { - if (placement == null || placement.element() == null) { - continue; - } - window.setElement(placement.position(), placement.row(), placement.element()); - } - } - - public record Placement(int position, int row, Element element) { - } -} diff --git a/src/main/java/com/volmit/adapt/util/GuiLayout.java b/src/main/java/com/volmit/adapt/util/GuiLayout.java deleted file mode 100644 index eedbe4233..000000000 --- a/src/main/java/com/volmit/adapt/util/GuiLayout.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.volmit.adapt.util; - -public final class GuiLayout { - public static final int WIDTH = 9; - public static final int MAX_ROWS = 6; - - private GuiLayout() { - } - - public static PagePlan plan(int totalItems, boolean reserveNavigationRow) { - int items = Math.max(0, totalItems); - - boolean navigation = reserveNavigationRow; - int maxContentRows = MAX_ROWS - (navigation ? 1 : 0); - if (maxContentRows < 1) { - maxContentRows = 1; - } - - if (items > maxContentRows * WIDTH) { - navigation = true; - maxContentRows = MAX_ROWS - 1; - } - - int contentRows; - if (items <= 0) { - contentRows = 1; - } else if (items > maxContentRows * WIDTH) { - contentRows = maxContentRows; - } else { - contentRows = (int) Math.ceil(items / (double) WIDTH); - } - - contentRows = Math.max(1, Math.min(maxContentRows, contentRows)); - int rows = contentRows + (navigation ? 1 : 0); - rows = Math.max(1, Math.min(MAX_ROWS, rows)); - - int itemsPerPage = contentRows * WIDTH; - itemsPerPage = Math.max(WIDTH, itemsPerPage); - int pages = Math.max(1, (int) Math.ceil(items / (double) itemsPerPage)); - - return new PagePlan(rows, contentRows, navigation, itemsPerPage, pages); - } - - public static int clampPage(int page, int pageCount) { - if (pageCount <= 0) { - return 0; - } - return Math.max(0, Math.min(pageCount - 1, page)); - } - - public static int centeredPosition(int indexInRow, int rowCount) { - int count = Math.max(1, Math.min(WIDTH, rowCount)); - int index = Math.max(0, Math.min(count - 1, indexInRow)); - int start = -(count / 2); - return start + index; - } - - public record PagePlan( - int rows, - int contentRows, - boolean hasNavigationRow, - int itemsPerPage, - int pageCount - ) { - } -} diff --git a/src/main/java/com/volmit/adapt/util/GuiTheme.java b/src/main/java/com/volmit/adapt/util/GuiTheme.java deleted file mode 100644 index 7b3ec0d2a..000000000 --- a/src/main/java/com/volmit/adapt/util/GuiTheme.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.volmit.adapt.util; - -import org.bukkit.Material; - -public final class GuiTheme { - private GuiTheme() { - } - - public static void apply(Window window, String tag) { - if (window == null) { - return; - } - - window.setResolution(WindowResolution.W9_H6); - window.setViewportHeight(WindowResolution.W9_H6.getMaxHeight()); - if (tag != null) { - window.setTag(tag); - } - - window.setDecorator((w, position, row) -> new UIElement("bg-" + row + "-" + position) - .setName(" ") - .setMaterial(new MaterialBlock(background(row, position)))); - } - - public static Material background(int row, int position) { - if (row == 0) { - return position % 2 == 0 ? Material.GRAY_STAINED_GLASS_PANE : Material.LIGHT_GRAY_STAINED_GLASS_PANE; - } - - if (row == 1) { - return Material.BLACK_STAINED_GLASS_PANE; - } - - return position % 2 == 0 ? Material.BLACK_STAINED_GLASS_PANE : Material.GRAY_STAINED_GLASS_PANE; - } -} diff --git a/src/main/java/com/volmit/adapt/util/HeightMap.java b/src/main/java/com/volmit/adapt/util/HeightMap.java deleted file mode 100644 index bfa55b995..000000000 --- a/src/main/java/com/volmit/adapt/util/HeightMap.java +++ /dev/null @@ -1,38 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.Arrays; - -public class HeightMap { - private final byte[] height; - - public HeightMap() { - height = new byte[256]; - Arrays.fill(height, Byte.MIN_VALUE); - } - - public void setHeight(int x, int z, int h) { - height[x * 16 + z] = (byte) (h + Byte.MIN_VALUE); - } - - public int getHeight(int x, int z) { - return height[x * 16 + z] - Byte.MIN_VALUE; - } -} diff --git a/src/main/java/com/volmit/adapt/util/HiddenStringUtils.java b/src/main/java/com/volmit/adapt/util/HiddenStringUtils.java deleted file mode 100644 index 60da16564..000000000 --- a/src/main/java/com/volmit/adapt/util/HiddenStringUtils.java +++ /dev/null @@ -1,145 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.ChatColor; - -import java.nio.charset.StandardCharsets; - -public class HiddenStringUtils { - - // String constants. TODO Change them to something unique to avoid conflict with other plugins! - private static final String SEQUENCE_HEADER = "" + ChatColor.RESET + ChatColor.UNDERLINE + ChatColor.RESET; - private static final String SEQUENCE_FOOTER = "" + ChatColor.RESET + ChatColor.ITALIC + ChatColor.RESET; - - public static String encodeString(String hiddenString) { - return quote(stringToColors(hiddenString)); - } - - public static boolean hasHiddenString(String input) { - if (input == null) return false; - - return input.indexOf(SEQUENCE_HEADER) > -1 && input.indexOf(SEQUENCE_FOOTER) > -1; - } - - public static String extractHiddenString(String input) { - return colorsToString(extract(input)); - } - - - public static String replaceHiddenString(String input, String hiddenString) { - if (input == null) return null; - - int start = input.indexOf(SEQUENCE_HEADER); - int end = input.indexOf(SEQUENCE_FOOTER); - - if (start < 0 || end < 0) { - return null; - } - - return input.substring(0, start + SEQUENCE_HEADER.length()) + stringToColors(hiddenString) + input.substring(end); - } - - /** - * Internal stuff. - */ - private static String quote(String input) { - if (input == null) return null; - return SEQUENCE_HEADER + input + SEQUENCE_FOOTER; - } - - private static String extract(String input) { - if (input == null) return null; - - int start = input.indexOf(SEQUENCE_HEADER); - int end = input.indexOf(SEQUENCE_FOOTER); - - if (start < 0 || end < 0) { - return null; - } - - return input.substring(start + SEQUENCE_HEADER.length(), end); - } - - private static String stringToColors(String normal) { - if (normal == null) return null; - - byte[] bytes = normal.getBytes(StandardCharsets.UTF_8); - char[] chars = new char[bytes.length * 4]; - - for (int i = 0; i < bytes.length; i++) { - char[] hex = byteToHex(bytes[i]); - chars[i * 4] = ChatColor.COLOR_CHAR; - chars[i * 4 + 1] = hex[0]; - chars[i * 4 + 2] = ChatColor.COLOR_CHAR; - chars[i * 4 + 3] = hex[1]; - } - - return new String(chars); - } - - private static String colorsToString(String colors) { - if (colors == null) return null; - - colors = colors.toLowerCase().replace("" + ChatColor.COLOR_CHAR, ""); - - if (colors.length() % 2 != 0) { - colors = colors.substring(0, (colors.length() / 2) * 2); - } - - char[] chars = colors.toCharArray(); - byte[] bytes = new byte[chars.length / 2]; - - for (int i = 0; i < chars.length; i += 2) { - bytes[i / 2] = hexToByte(chars[i], chars[i + 1]); - } - - return new String(bytes, StandardCharsets.UTF_8); - } - - private static int hexToUnsignedInt(char c) { - if (c >= '0' && c <= '9') { - return c - 48; - } else if (c >= 'a' && c <= 'f') { - return c - 87; - } else { - throw new IllegalArgumentException("Invalid hex char: out of range"); - } - } - - private static char unsignedIntToHex(int i) { - if (i >= 0 && i <= 9) { - return (char) (i + 48); - } else if (i >= 10 && i <= 15) { - return (char) (i + 87); - } else { - throw new IllegalArgumentException("Invalid hex int: out of range"); - } - } - - private static byte hexToByte(char hex1, char hex0) { - return (byte) (((hexToUnsignedInt(hex1) << 4) | hexToUnsignedInt(hex0)) + Byte.MIN_VALUE); - } - - private static char[] byteToHex(byte b) { - int unsignedByte = (int) b - Byte.MIN_VALUE; - return new char[]{unsignedIntToHex((unsignedByte >> 4) & 0xf), unsignedIntToHex(unsignedByte & 0xf)}; - } - -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/ICommand.java b/src/main/java/com/volmit/adapt/util/ICommand.java deleted file mode 100644 index b3bf860e8..000000000 --- a/src/main/java/com/volmit/adapt/util/ICommand.java +++ /dev/null @@ -1,70 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.List; - -/** - * Represents a pawn command - * - * @author cyberpwn - */ -public interface ICommand { - List getRequiredPermissions(); - - /** - * Get the name of this command (node) - * - * @return the node - */ - String getNode(); - - /** - * Get all (realized) nodes of this command - * - * @return the nodes - */ - List getNodes(); - - /** - * Get all (every) node in this command - * - * @return all nodes - */ - List getAllNodes(); - - /** - * Add a node to this command - * - * @param node the node - */ - void addNode(String node); - - /** - * Handle a command. If this is a subcommand, parameters after the subcommand - * will be adapted in args for you - * - * @param sender the volume sender (pre-tagged) - * @param args the arguments after this command node - * @return return true to mark it as handled - */ - boolean handle(MortarSender sender, String[] args); - - List handleTab(MortarSender sender, String[] args); -} diff --git a/src/main/java/com/volmit/adapt/util/IO.java b/src/main/java/com/volmit/adapt/util/IO.java deleted file mode 100644 index db783400e..000000000 --- a/src/main/java/com/volmit/adapt/util/IO.java +++ /dev/null @@ -1,1509 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.security.DigestInputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.function.Consumer; -import java.util.zip.GZIPInputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -public class IO { - /** - * The Unix directory separator character. - */ - public static final char DIR_SEPARATOR_UNIX = '/'; - /** - * The Windows directory separator character. - */ - public static final char DIR_SEPARATOR_WINDOWS = '\\'; - /** - * The system directory separator character. - */ - public static final char DIR_SEPARATOR = File.separatorChar; - /** - * The Unix line separator string. - */ - public static final String LINE_SEPARATOR_UNIX = "\n"; - /** - * The Windows line separator string. - */ - public static final String LINE_SEPARATOR_WINDOWS = "\r\n"; - /** - * The system line separator string. - */ - public static final String LINE_SEPARATOR; - - /** - * The default buffer size to use. - */ - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); - - static { - // avoid security issues - StringWriter buf = new StringWriter(4); - PrintWriter out = new PrintWriter(buf); - out.println(); - LINE_SEPARATOR = buf.toString(); - } - - public static String decompress(String gz) throws IOException { - ByteArrayInputStream bin = new ByteArrayInputStream(Base64.getUrlDecoder().decode(gz)); - GZIPInputStream gzi = new GZIPInputStream(bin); - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - IO.fullTransfer(gzi, boas, 256); - gzi.close(); - - return boas.toString(StandardCharsets.UTF_8); - } - - public static byte[] sdecompress(String compressed) throws IOException { - ByteArrayInputStream bin = new ByteArrayInputStream(Base64.getUrlDecoder().decode(compressed)); - GZIPInputStream gzi = new GZIPInputStream(bin); - ByteArrayOutputStream boas = new ByteArrayOutputStream(); - IO.fullTransfer(gzi, boas, 256); - gzi.close(); - - return boas.toByteArray(); - } - - public static String encode(byte[] data) { - return Base64.getUrlEncoder().encodeToString(data); - } - - public static byte[] decode(String u) { - return Base64.getUrlDecoder().decode(u); - } - - public static String hash(String b) { - try { - MessageDigest d = MessageDigest.getInstance("SHA-256"); - return bytesToHex(d.digest(b.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - - return "¯\\_(ツ)_/¯"; - } - - public static String hash(File b) { - try { - MessageDigest d = MessageDigest.getInstance("SHA-256"); - DigestInputStream din = new DigestInputStream(new FileInputStream(b), d); - fullTransfer(din, new VoidOutputStream(), 8192); - din.close(); - return bytesToHex(din.getMessageDigest().digest()); - } catch (Throwable e) { - e.printStackTrace(); - } - - return "¯\\_(ツ)_/¯"; - } - - public static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - - return new String(hexChars).toUpperCase(); - } - - /** - * Transfers the length of the buffer amount of data from the input stream to - * the output stream - * - * @param in the input - * @param out the output - * @param buffer the buffer and size to use - * @return the actual transfered amount - * @throws IOException shit happens - */ - public static int transfer(InputStream in, OutputStream out, byte[] buffer) throws IOException { - int r = in.read(buffer); - - if (r != -1) { - out.write(buffer, 0, r); - } - - return r; - } - - /** - * Transfers the length of the buffer amount of data from the input stream to - * the output stream - * - * @param in the input - * @param out the output - * @param targetBuffer the buffer and size to use - * @param totalSize the total amount to transfer - * @return the actual transfered amount - * @throws IOException shit happens - */ - public static long transfer(InputStream in, OutputStream out, int targetBuffer, long totalSize) throws IOException { - long total = totalSize; - long wrote = 0; - byte[] buf = new byte[targetBuffer]; - int r = 0; - - while ((r = in.read(buf, 0, (int) (total < targetBuffer ? total : targetBuffer))) != -1) { - total -= r; - out.write(buf, 0, r); - wrote += r; - - if (total <= 0) { - break; - } - } - - return wrote; - } - - /** - * Fully move data from a finite inputstream to an output stream using a buffer - * size of 8192. This does NOT close streams. - * - * @return total size transfered - */ - public static long fillTransfer(InputStream in, OutputStream out) throws IOException { - return fullTransfer(in, out, 8192); - } - - public static void deleteUp(File f) { - if (f.exists()) { - f.delete(); - - if (f.getParentFile().list().length == 0) { - deleteUp(f.getParentFile()); - } - } - } - - /** - * Fully move data from a finite inputstream to an output stream using a given - * buffer size. This does NOT close streams. - * - * @param in the input stream to read from - * @param out the output stream to write to - * @param bufferSize the target buffer size - * @return total size transfered - * @throws IOException shit happens - */ - public static long fullTransfer(InputStream in, OutputStream out, int bufferSize) throws IOException { - long wrote = 0; - byte[] buf = new byte[bufferSize]; - int r = 0; - - while ((r = in.read(buf)) != -1) { - out.write(buf, 0, r); - wrote += r; - } - - return wrote; - } - - /** - * Recursive delete (deleting folders) - * - * @param f the file to delete (and subfiles if folder) - */ - public static void delete(File f) { - if (f == null || !f.exists()) { - return; - } - - if (f.isDirectory()) { - for (File i : f.listFiles()) { - delete(i); - } - } - - f.delete(); - } - - public static long size(File file) { - long s = 0; - - if (file.exists()) { - if (file.isDirectory()) { - for (File i : file.listFiles()) { - s += size(i); - } - } else { - s += file.length(); - } - } - - return s; - } - - public static long count(File file) { - long s = 0; - - if (file.exists()) { - if (file.isDirectory()) { - for (File i : file.listFiles()) { - s += count(i); - } - } else { - s++; - } - } - - return s; - } - - public static long transfer(InputStream in, OutputStream out, byte[] buf, int totalSize) throws IOException { - long total = totalSize; - long wrote = 0; - int r = 0; - - while ((r = in.read(buf, 0, (int) (total < buf.length ? total : buf.length))) != -1) { - total -= r; - out.write(buf, 0, r); - wrote += r; - - if (total <= 0) { - break; - } - } - - return wrote; - } - - public static void readEntry(File zipfile, String entryname, Consumer v) throws IOException { - ZipFile file = new ZipFile(zipfile); - Throwable x = null; - - try { - Enumeration entries = file.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - - if (entryname.equals(entry.getName())) { - InputStream in = file.getInputStream(entry); - v.accept(in); - } - } - } catch (Exception ex) { - x = ex.getCause(); - } finally { - file.close(); - } - - if (x != null) { - throw new IOException("Failed to read zip entry, however it has been closed safely.", x); - } - } - - public static void writeAll(File f, Object c) throws IOException { - f.getParentFile().mkdirs(); - PrintWriter pw = new PrintWriter(new FileWriter(f)); - pw.println(c.toString()); - pw.close(); - } - - public static String readAll(File f) throws IOException { - StringBuilder content = new StringBuilder(); - try (BufferedReader bu = new BufferedReader(new FileReader(f))) { - String line; - while ((line = bu.readLine()) != null) { - content.append(line).append('\n'); - } - } - - return content.toString(); - } - - public static String readAll(InputStream in) throws IOException { - StringBuilder content = new StringBuilder(); - try (BufferedReader bu = new BufferedReader(new InputStreamReader(in))) { - String line; - while ((line = bu.readLine()) != null) { - content.append(line).append('\n'); - } - } - - return content.toString(); - } - - /** - * Implements the same behaviour as the "touch" utility on Unix. It creates a - * new file with size 0 or, if the file exists already, it is opened and closed - * without modifying it, but updating the file date and time. - * - * @param file the File to touch - * @throws IOException If an I/O problem occurs - */ - public static void touch(File file) throws IOException { - if (!file.exists()) { - OutputStream out = new FileOutputStream(file); - out.close(); - } - file.setLastModified(System.currentTimeMillis()); - } - - /** - * Copies a file to a new location preserving the file date. - *

- * This method copies the contents of the specified source file to the specified - * destination file. The directory holding the destination file is created if it - * does not exist. If the destination file exists, then this method will - * overwrite it. - * - * @param srcFile an existing file to copy, must not be null - * @param destFile the new file, must not be null - * @throws NullPointerException if source or destination is null - * @throws IOException if source or destination is invalid - * @throws IOException if an IO error occurs during copying - * @see #copyFileToDirectory - */ - public static void copyFile(File srcFile, File destFile) throws IOException { - copyFile(srcFile, destFile, true); - } - - /** - * Copies a file to a new location. - *

- * This method copies the contents of the specified source file to the specified - * destination file. The directory holding the destination file is created if it - * does not exist. If the destination file exists, then this method will - * overwrite it. - * - * @param srcFile an existing file to copy, must not be null - * @param destFile the new file, must not be null - * @param preserveFileDate true if the file date of the copy should be the same as the - * original - * @throws NullPointerException if source or destination is null - * @throws IOException if source or destination is invalid - * @throws IOException if an IO error occurs during copying - * @see #copyFileToDirectory - */ - public static void copyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { - if (srcFile == null) { - throw new NullPointerException("Source must not be null"); - } - if (destFile == null) { - throw new NullPointerException("Destination must not be null"); - } - if (srcFile.exists() == false) { - throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); - } - if (srcFile.isDirectory()) { - throw new IOException("Source '" + srcFile + "' exists but is a directory"); - } - if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { - throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); - } - if (destFile.getParentFile() != null && destFile.getParentFile().exists() == false) { - if (destFile.getParentFile().mkdirs() == false) { - throw new IOException("Destination '" + destFile + "' directory cannot be created"); - } - } - if (destFile.exists() && destFile.canWrite() == false) { - throw new IOException("Destination '" + destFile + "' exists but is read-only"); - } - doCopyFile(srcFile, destFile, preserveFileDate); - } - - // ----------------------------------------------------------------------- - - /** - * Internal copy file method. - * - * @param srcFile the validated source file, not null - * @param destFile the validated destination file, not null - * @param preserveFileDate whether to preserve the file date - * @throws IOException if an error occurs - */ - private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { - if (destFile.exists() && destFile.isDirectory()) { - throw new IOException("Destination '" + destFile + "' exists but is a directory"); - } - - try (FileInputStream input = new FileInputStream(srcFile)) { - try (FileOutputStream output = new FileOutputStream(destFile)) { - IO.copy(input, output); - } - } - - if (srcFile.length() != destFile.length()) { - throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "'"); - } - if (preserveFileDate) { - destFile.setLastModified(srcFile.lastModified()); - } - } - - /** - * Unconditionally close an Reader. - *

- * Equivalent to {@link Reader#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - * - * @param input the Reader to close, may be null or already closed - */ - public static void closeQuietly(Reader input) { - try { - if (input != null) { - input.close(); - } - } catch (IOException ioe) { - // ignore - } - } - - /** - * Unconditionally close a Writer. - *

- * Equivalent to {@link Writer#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - * - * @param output the Writer to close, may be null or already closed - */ - public static void closeQuietly(Writer output) { - try { - if (output != null) { - output.close(); - } - } catch (IOException ioe) { - // ignore - } - } - - /** - * Unconditionally close an InputStream. - *

- * Equivalent to {@link InputStream#close()}, except any exceptions will be - * ignored. This is typically used in finally blocks. - * - * @param input the InputStream to close, may be null or already closed - */ - public static void closeQuietly(InputStream input) { - try { - if (input != null) { - input.close(); - } - } catch (IOException ioe) { - // ignore - } - } - - // read toByteArray - // ----------------------------------------------------------------------- - - /** - * Unconditionally close an OutputStream. - *

- * Equivalent to {@link OutputStream#close()}, except any exceptions will be - * ignored. This is typically used in finally blocks. - * - * @param output the OutputStream to close, may be null or already closed - */ - public static void closeQuietly(OutputStream output) { - try { - if (output != null) { - output.close(); - } - } catch (IOException ioe) { - // ignore - } - } - - /** - * Get the contents of an InputStream as a byte[]. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - */ - public static byte[] toByteArray(InputStream input) throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - copy(input, output); - return output.toByteArray(); - } - - /** - * Get the contents of a Reader as a byte[] using the - * default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - */ - public static byte[] toByteArray(Reader input) throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - copy(input, output); - return output.toByteArray(); - } - - /** - * Get the contents of a Reader as a byte[] using the - * specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @param encoding the encoding to use, null means platform default - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static byte[] toByteArray(Reader input, String encoding) throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - copy(input, output, encoding); - return output.toByteArray(); - } - - // read char[] - // ----------------------------------------------------------------------- - - /** - * Get the contents of a String as a byte[] using the - * default character encoding of the platform. - *

- * This is the same as {@link String#getBytes()}. - * - * @param input the String to convert - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs (never occurs) - * @Deprecated Use {@link String#getBytes()} - */ - public static byte[] toByteArray(String input) throws IOException { - return input.getBytes(); - } - - /** - * Get the contents of an InputStream as a character array using - * the default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param is the InputStream to read from - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static char[] toCharArray(InputStream is) throws IOException { - CharArrayWriter output = new CharArrayWriter(); - copy(is, output); - return output.toCharArray(); - } - - /** - * Get the contents of an InputStream as a character array using - * the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param is the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static char[] toCharArray(InputStream is, String encoding) throws IOException { - CharArrayWriter output = new CharArrayWriter(); - copy(is, output, encoding); - return output.toCharArray(); - } - - // read toString - // ----------------------------------------------------------------------- - - /** - * Get the contents of a Reader as a character array. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static char[] toCharArray(Reader input) throws IOException { - CharArrayWriter sw = new CharArrayWriter(); - copy(input, sw); - return sw.toCharArray(); - } - - /** - * Get the contents of an InputStream as a String using the default - * character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - */ - public static String toString(InputStream input) throws IOException { - StringWriter sw = new StringWriter(); - copy(input, sw); - return sw.toString(); - } - - /** - * Get the contents of an InputStream as a String using the - * specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested String - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - */ - public static String toString(InputStream input, String encoding) throws IOException { - StringWriter sw = new StringWriter(); - copy(input, sw, encoding); - return sw.toString(); - } - - /** - * Get the contents of a Reader as a String. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - */ - public static String toString(Reader input) throws IOException { - StringWriter sw = new StringWriter(); - copy(input, sw); - return sw.toString(); - } - - /** - * Get the contents of a byte[] as a String using the default - * character encoding of the platform. - * - * @param input the byte array to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs (never occurs) - * @Deprecated Use {@link String#String(byte[])} - */ - public static String toString(byte[] input) throws IOException { - return new String(input); - } - - // readLines - // ----------------------------------------------------------------------- - - /** - * Get the contents of a byte[] as a String using the specified - * character encoding. - *

- * Character encoding names can be found at - * IANA. - * - * @param input the byte array to read from - * @param encoding the encoding to use, null means platform default - * @return the requested String - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs (never occurs) - * @Deprecated Use {@link String#String(byte[], String)} - */ - public static String toString(byte[] input, String encoding) throws IOException { - if (encoding == null) { - return new String(input); - } else { - return new String(input, encoding); - } - } - - /** - * Get the contents of an InputStream as a list of Strings, one - * entry per line, using the default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from, not null - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static List readLines(InputStream input) throws IOException { - InputStreamReader reader = new InputStreamReader(input); - return readLines(reader); - } - - /** - * Get the contents of an InputStream as a list of Strings, one - * entry per line, using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from, not null - * @param encoding the encoding to use, null means platform default - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static List readLines(InputStream input, String encoding) throws IOException { - if (encoding == null) { - return readLines(input); - } else { - InputStreamReader reader = new InputStreamReader(input, encoding); - return readLines(reader); - } - } - - // ----------------------------------------------------------------------- - - /** - * Get the contents of a Reader as a list of Strings, one entry per - * line. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from, not null - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static List readLines(Reader input) throws IOException { - BufferedReader reader = new BufferedReader(input); - List list = new ArrayList(); - String line = reader.readLine(); - while (line != null) { - list.add(line); - line = reader.readLine(); - } - return list; - } - - /** - * Convert the specified string to an input stream, encoded as bytes using the - * default character encoding of the platform. - * - * @param input the string to convert - * @return an input stream - * @since Commons IO 1.1 - */ - public static InputStream toInputStream(String input) { - byte[] bytes = input.getBytes(); - return new ByteArrayInputStream(bytes); - } - - // write byte[] - // ----------------------------------------------------------------------- - - /** - * Convert the specified string to an input stream, encoded as bytes using the - * specified character encoding. - *

- * Character encoding names can be found at - * IANA. - * - * @param input the string to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @throws IOException if the encoding is invalid - * @since Commons IO 1.1 - */ - public static InputStream toInputStream(String input, String encoding) throws IOException { - byte[] bytes = encoding != null ? input.getBytes(encoding) : input.getBytes(); - return new ByteArrayInputStream(bytes); - } - - /** - * Writes bytes from a byte[] to an OutputStream. - * - * @param data the byte array to write, do not modify during output, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(byte[] data, OutputStream output) throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes bytes from a byte[] to chars on a Writer - * using the default character encoding of the platform. - *

- * This method uses {@link String#String(byte[])}. - * - * @param data the byte array to write, do not modify during output, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(byte[] data, Writer output) throws IOException { - if (data != null) { - output.write(new String(data)); - } - } - - // write char[] - // ----------------------------------------------------------------------- - - /** - * Writes bytes from a byte[] to chars on a Writer - * using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#String(byte[], String)}. - * - * @param data the byte array to write, do not modify during output, null ignored - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(byte[] data, Writer output, String encoding) throws IOException { - if (data != null) { - if (encoding == null) { - write(data, output); - } else { - output.write(new String(data, encoding)); - } - } - } - - /** - * Writes chars from a char[] to a Writer using the - * default character encoding of the platform. - * - * @param data the char array to write, do not modify during output, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(char[] data, Writer output) throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes chars from a char[] to bytes on an - * OutputStream. - *

- * This method uses {@link String#String(char[])} and {@link String#getBytes()}. - * - * @param data the char array to write, do not modify during output, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(char[] data, OutputStream output) throws IOException { - if (data != null) { - output.write(new String(data).getBytes()); - } - } - - // write String - // ----------------------------------------------------------------------- - - /** - * Writes chars from a char[] to bytes on an - * OutputStream using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#String(char[])} and - * {@link String#getBytes(String)}. - * - * @param data the char array to write, do not modify during output, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(char[] data, OutputStream output, String encoding) throws IOException { - if (data != null) { - if (encoding == null) { - write(data, output); - } else { - output.write(new String(data).getBytes(encoding)); - } - } - } - - /** - * Writes chars from a String to a Writer. - * - * @param data the String to write, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(String data, Writer output) throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes chars from a String to bytes on an - * OutputStream using the default character encoding of the - * platform. - *

- * This method uses {@link String#getBytes()}. - * - * @param data the String to write, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(String data, OutputStream output) throws IOException { - if (data != null) { - output.write(data.getBytes()); - } - } - - // write StringBuffer - // ----------------------------------------------------------------------- - - /** - * Writes chars from a String to bytes on an - * OutputStream using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#getBytes(String)}. - * - * @param data the String to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(String data, OutputStream output, String encoding) throws IOException { - if (data != null) { - if (encoding == null) { - write(data, output); - } else { - output.write(data.getBytes(encoding)); - } - } - } - - /** - * Writes chars from a StringBuffer to a Writer. - * - * @param data the StringBuffer to write, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(StringBuffer data, Writer output) throws IOException { - if (data != null) { - output.write(data.toString()); - } - } - - /** - * Writes chars from a StringBuffer to bytes on an - * OutputStream using the default character encoding of the - * platform. - *

- * This method uses {@link String#getBytes()}. - * - * @param data the StringBuffer to write, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(StringBuffer data, OutputStream output) throws IOException { - if (data != null) { - output.write(data.toString().getBytes()); - } - } - - // writeLines - // ----------------------------------------------------------------------- - - /** - * Writes chars from a StringBuffer to bytes on an - * OutputStream using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#getBytes(String)}. - * - * @param data the StringBuffer to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void write(StringBuffer data, OutputStream output, String encoding) throws IOException { - if (data != null) { - if (encoding == null) { - write(data, output); - } else { - output.write(data.toString().getBytes(encoding)); - } - } - } - - /** - * Writes the toString() value of each item in a collection to an - * OutputStream line by line, using the default character encoding - * of the platform and the specified line ending. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param output the OutputStream to write to, not null, not closed - * @throws NullPointerException if the output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, OutputStream output) throws IOException { - if (lines == null) { - return; - } - if (lineEnding == null) { - lineEnding = LINE_SEPARATOR; - } - for (Iterator it = lines.iterator(); it.hasNext(); ) { - Object line = it.next(); - if (line != null) { - output.write(line.toString().getBytes()); - } - output.write(lineEnding.getBytes()); - } - } - - /** - * Writes the toString() value of each item in a collection to an - * OutputStream line by line, using the specified character - * encoding and the specified line ending. - *

- * Character encoding names can be found at - * IANA. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param output the OutputStream to write to, not null, not closed - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, OutputStream output, String encoding) throws IOException { - if (encoding == null) { - writeLines(lines, lineEnding, output); - } else { - if (lines == null) { - return; - } - if (lineEnding == null) { - lineEnding = LINE_SEPARATOR; - } - for (Iterator it = lines.iterator(); it.hasNext(); ) { - Object line = it.next(); - if (line != null) { - output.write(line.toString().getBytes(encoding)); - } - output.write(lineEnding.getBytes(encoding)); - } - } - } - - // copy from InputStream - // ----------------------------------------------------------------------- - - /** - * Writes the toString() value of each item in a collection to a - * Writer line by line, using the specified line ending. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param writer the Writer to write to, not null, not closed - * @throws NullPointerException if the input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, Writer writer) throws IOException { - if (lines == null) { - return; - } - if (lineEnding == null) { - lineEnding = LINE_SEPARATOR; - } - for (Iterator it = lines.iterator(); it.hasNext(); ) { - Object line = it.next(); - if (line != null) { - writer.write(line.toString()); - } - writer.write(lineEnding); - } - } - - /** - * Copy bytes from an InputStream to an OutputStream. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * Large streams (over 2GB) will return a bytes copied value of -1 - * after the copy has completed since the correct number of bytes cannot be - * returned as an int. For large streams use the - * copyLarge(InputStream, OutputStream) method. - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @throws ArithmeticException if the byte count is too large - * @since Commons IO 1.1 - */ - public static int copy(InputStream input, OutputStream output) throws IOException { - long count = copyLarge(input, output); - if (count > Integer.MAX_VALUE) { - return -1; - } - return (int) count; - } - - /** - * Copy bytes from a large (over 2GB) InputStream to an - * OutputStream. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.3 - */ - public static long copyLarge(InputStream input, OutputStream output) throws IOException { - byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - long count = 0; - int n = 0; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - /** - * Copy bytes from an InputStream to chars on a Writer - * using the default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * This method uses {@link InputStreamReader}. - * - * @param input the InputStream to read from - * @param output the Writer to write to - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void copy(InputStream input, Writer output) throws IOException { - InputStreamReader in = new InputStreamReader(input); - copy(in, output); - } - - // copy from Reader - // ----------------------------------------------------------------------- - - /** - * Copy bytes from an InputStream to chars on a Writer - * using the specified character encoding. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link InputStreamReader}. - * - * @param input the InputStream to read from - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void copy(InputStream input, Writer output, String encoding) throws IOException { - if (encoding == null) { - copy(input, output); - } else { - InputStreamReader in = new InputStreamReader(input, encoding); - copy(in, output); - } - } - - /** - * Copy chars from a Reader to a Writer. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * Large streams (over 2GB) will return a chars copied value of -1 - * after the copy has completed since the correct number of chars cannot be - * returned as an int. For large streams use the - * copyLarge(Reader, Writer) method. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @throws ArithmeticException if the character count is too large - * @since Commons IO 1.1 - */ - public static int copy(Reader input, Writer output) throws IOException { - long count = copyLarge(input, output); - if (count > Integer.MAX_VALUE) { - return -1; - } - return (int) count; - } - - /** - * Copy chars from a large (over 2GB) Reader to a - * Writer. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.3 - */ - public static long copyLarge(Reader input, Writer output) throws IOException { - char[] buffer = new char[DEFAULT_BUFFER_SIZE]; - long count = 0; - int n = 0; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - /** - * Copy chars from a Reader to bytes on an - * OutputStream using the default character encoding of the - * platform, and calling flush. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * Due to the implementation of OutputStreamWriter, this method performs a - * flush. - *

- * This method uses {@link OutputStreamWriter}. - * - * @param input the Reader to read from - * @param output the OutputStream to write to - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void copy(Reader input, OutputStream output) throws IOException { - OutputStreamWriter out = new OutputStreamWriter(output); - copy(input, out); - // have to flush here. - out.flush(); - } - - // content equals - // ----------------------------------------------------------------------- - - /** - * Copy chars from a Reader to bytes on an - * OutputStream using the specified character encoding, and calling - * flush. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * Character encoding names can be found at - * IANA. - *

- * Due to the implementation of OutputStreamWriter, this method performs a - * flush. - *

- * This method uses {@link OutputStreamWriter}. - * - * @param input the Reader to read from - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static void copy(Reader input, OutputStream output, String encoding) throws IOException { - if (encoding == null) { - copy(input, output); - } else { - OutputStreamWriter out = new OutputStreamWriter(output, encoding); - copy(input, out); - // we have to flush here. - out.flush(); - } - } - - /** - * Compare the contents of two Streams to determine if they are equal or not. - *

- * This method buffers the input internally using - * BufferedInputStream if they are not already buffered. - * - * @param input1 the first stream - * @param input2 the second stream - * @return true if the content of the streams are equal or they both don't - * exist, false otherwise - * @throws NullPointerException if either input is null - * @throws IOException if an I/O error occurs - */ - public static boolean contentEquals(InputStream input1, InputStream input2) throws IOException { - if (!(input1 instanceof BufferedInputStream)) { - input1 = new BufferedInputStream(input1); - } - if (!(input2 instanceof BufferedInputStream)) { - input2 = new BufferedInputStream(input2); - } - - int ch = input1.read(); - while (-1 != ch) { - int ch2 = input2.read(); - if (ch != ch2) { - return false; - } - ch = input1.read(); - } - - int ch2 = input2.read(); - return (ch2 == -1); - } - - /** - * Compare the contents of two Readers to determine if they are equal or not. - *

- * This method buffers the input internally using BufferedReader if - * they are not already buffered. - * - * @param input1 the first reader - * @param input2 the second reader - * @return true if the content of the readers are equal or they both don't - * exist, false otherwise - * @throws NullPointerException if either input is null - * @throws IOException if an I/O error occurs - * @since Commons IO 1.1 - */ - public static boolean contentEquals(Reader input1, Reader input2) throws IOException { - if (!(input1 instanceof BufferedReader)) { - input1 = new BufferedReader(input1); - } - if (!(input2 instanceof BufferedReader)) { - input2 = new BufferedReader(input2); - } - - int ch = input1.read(); - while (-1 != ch) { - int ch2 = input2.read(); - if (ch != ch2) { - return false; - } - ch = input1.read(); - } - - int ch2 = input2.read(); - return (ch2 == -1); - } -} diff --git a/src/main/java/com/volmit/adapt/util/IObjectPlacer.java b/src/main/java/com/volmit/adapt/util/IObjectPlacer.java deleted file mode 100644 index be25db2c5..000000000 --- a/src/main/java/com/volmit/adapt/util/IObjectPlacer.java +++ /dev/null @@ -1,41 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.block.data.BlockData; - -public interface IObjectPlacer { - int getHighest(int x, int z); - - int getHighest(int x, int z, boolean ignoreFluid); - - void set(int x, int y, int z, BlockData d); - - BlockData get(int x, int y, int z); - - boolean isPreventingDecay(); - - boolean isSolid(int x, int y, int z); - - boolean isUnderwater(int x, int z); - - int getFluidHeight(); - - boolean isDebugSmartBore(); -} diff --git a/src/main/java/com/volmit/adapt/util/IPostBlockAccess.java b/src/main/java/com/volmit/adapt/util/IPostBlockAccess.java deleted file mode 100644 index afac7a871..000000000 --- a/src/main/java/com/volmit/adapt/util/IPostBlockAccess.java +++ /dev/null @@ -1,38 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.block.data.BlockData; -import org.bukkit.generator.ChunkGenerator.ChunkData; - -import java.util.List; - -public interface IPostBlockAccess { - BlockData getPostBlock(int x, int y, int z, int currentPostX, int currentPostZ, ChunkData currentData); - - void setPostBlock(int x, int y, int z, BlockData d, int currentPostX, int currentPostZ, ChunkData currentData); - - int highestTerrainOrFluidBlock(int x, int z); - - int highestTerrainBlock(int x, int z); - - void updateHeight(int x, int z, int h); - - List caveFloors(int x, int z); -} diff --git a/src/main/java/com/volmit/adapt/util/Impulse.java b/src/main/java/com/volmit/adapt/util/Impulse.java deleted file mode 100644 index bee490623..000000000 --- a/src/main/java/com/volmit/adapt/util/Impulse.java +++ /dev/null @@ -1,109 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.entity.LivingEntity; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.List; - -public class Impulse { - private final List ignore; - private double radius; - private double forceMax; - private double forceMin; - private double damageMin; - private double damageMax; - - public Impulse(double radius) { - ignore = new ArrayList<>(); - this.radius = radius; - this.forceMax = 1; - this.forceMin = 0; - this.damageMax = 1; - this.damageMin = 0; - } - - public Impulse radius(double radius) { - this.radius = radius; - return this; - } - - public Impulse force(double force) { - this.forceMax = force; - return this; - } - - public Impulse force(double forceMax, double forceMin) { - this.forceMax = forceMax; - this.forceMin = forceMin; - return this; - } - - public Impulse damage(double damage) { - this.damageMax = damage; - return this; - } - - public Impulse damage(double damageMax, double damageMin) { - this.damageMax = damageMax; - this.damageMin = damageMin; - return this; - } - - public void punch(Location at) { - Area a = new Area(at, radius); - - for (Entity i : a.getNearbyEntities()) { - if (ignore.contains(i)) { - continue; - } - - Vector force = VectorMath.direction(at, i.getLocation()); - double damage = 0; - double distance = i.getLocation().distance(at); - - if (forceMin < forceMax) { - force.clone().multiply(((1D - (distance / radius)) * (forceMax - forceMin)) + forceMin); - } - - if (damageMin < damageMax) { - damage = ((1D - (distance / radius)) * (damageMax - damageMin)) + damageMin; - } - - try { - if (i instanceof LivingEntity && damage > 0) { - ((LivingEntity) i).damage(damage); - } - - i.setVelocity(i.getVelocity().add(force)); - } catch (Exception e) { - - } - } - } - - public Impulse ignore(Entity player) { - ignore.add(player); - return this; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/IntArrayTag.java b/src/main/java/com/volmit/adapt/util/IntArrayTag.java deleted file mode 100644 index ffa247e96..000000000 --- a/src/main/java/com/volmit/adapt/util/IntArrayTag.java +++ /dev/null @@ -1,61 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.Arrays; - -/** - * The TAG_Int_Array tag. - * - * @author Neil Wightman - */ -public final class IntArrayTag extends Tag { - - /** - * The value. - */ - private final int[] value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public IntArrayTag(String name, int[] value) { - super(name); - this.value = value; - } - - @Override - public int[] getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_Int_Array" + append + ": " + Arrays.toString(value); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/IntTag.java b/src/main/java/com/volmit/adapt/util/IntTag.java deleted file mode 100644 index 1e3163524..000000000 --- a/src/main/java/com/volmit/adapt/util/IntTag.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * The TAG_Int tag. - * - * @author Graham Edgecombe - */ -public final class IntTag extends Tag { - - /** - * The value. - */ - private final int value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public IntTag(String name, int value) { - super(name); - this.value = value; - } - - @Override - public Integer getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_Int" + append + ": " + value; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Inventories.java b/src/main/java/com/volmit/adapt/util/Inventories.java deleted file mode 100644 index bd7edfaa5..000000000 --- a/src/main/java/com/volmit/adapt/util/Inventories.java +++ /dev/null @@ -1,106 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Material; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; - -import java.util.ArrayList; -import java.util.List; - -/** - * ItemStack & Inventory utilities - * - * @author cyberpwn - */ -public class Inventories { - /** - * Does the inventory have any space (empty slots) - * - * @param i the inventory - * @return true if it has at least one slot empty - */ - public static boolean hasSpace(Inventory i) { - return new PhantomInventory(i).hasSpace(); - } - - /** - * Does the inventory have a given amount of empty space (or more) - * - * @param i the inventory - * @param slots the slots needed empty - * @return true if it has more than or enough empty slots - */ - public static boolean hasSpace(Inventory i, int slots) { - int ex = 0; - - ItemStack[] vv = i.getContents(); - - for (ItemStack itemStack : vv) { - if (itemStack == null || itemStack.getType().equals(Material.AIR)) { - ex++; - } - } - - return ex >= slots; - } - - /** - * Get the ACTUAL contents in this inventory. Meaning no elements in the - * list are null, or just plain air - * - * @param i the inventory - * @return the ACTUAL contents - */ - public static List getActualContents(Inventory i) { - List actualItems = new ArrayList<>(); - - for (ItemStack j : i.getContents()) { - if (Items.is(j)) { - actualItems.add(j); - } - } - - return actualItems; - } - - /** - * Does the inventory have space for the given item - * - * @param i the inventory - * @param item the item - * @return returns true if either there is enough empty slots to fill it - * (amt / maxStackSize) OR the item can be merged with an existing - * item, else false. - */ - public static boolean hasSpace(Inventory i, ItemStack item) { - if (hasSpace(i, item.getAmount() / item.getMaxStackSize())) { - return true; - } else { - for (ItemStack j : getActualContents(i)) { - if (Items.isMergable(j, item)) { - return true; - } - } - } - - return false; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/Items.java b/src/main/java/com/volmit/adapt/util/Items.java deleted file mode 100644 index 943296deb..000000000 --- a/src/main/java/com/volmit/adapt/util/Items.java +++ /dev/null @@ -1,358 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Material; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.inventory.ItemStack; - -import java.util.ArrayList; - - -/** - * Itemstack utilities - * - * @author cyberpwn - */ -public class Items { - /** - * Is the item an item (not null or air) - * - * @param is the item - * @return true if it is - */ - public static boolean is(ItemStack is) { - return is != null && !is.getType().equals(Material.AIR); - } - - /** - * Is the item a certain material - * - * @param is the item - * @param material the material - * @return true if it is - */ - public static boolean is(ItemStack is, Material material) { - return is(is) && is.getType().equals(material); - } - - /** - * Is the item a certain material and metadata - * - * @param is the item - * @param mb the materialblock - * @return true if it is - */ - @SuppressWarnings("deprecation") - public static boolean is(ItemStack is, MaterialBlock mb) { - return is(is, mb.getMaterial()) && is.getData().getData() == mb.getData(); - } - - /** - * Is the item a given material and data - * - * @param is the item - * @param material the material - * @param data the data - * @return true if it is - */ - public static boolean is(ItemStack is, Material material, byte data) { - return is(is, new MaterialBlock(material, data)); - } - - /** - * Is the item a given material and data - * - * @param is the item - * @param material the material - * @param data the data - * @return true if it is - */ - public static boolean is(ItemStack is, Material material, int data) { - return is(is, material, (byte) data); - } - - /** - * Does the item have meta - * - * @param is the item - * @return true if it does - */ - public static boolean hasMeta(ItemStack is) { - return is(is) && is.hasItemMeta(); - } - - /** - * Does the item have a custom name - * - * @param is the item - * @return true if it has a name - */ - public static boolean hasName(ItemStack is) { - return hasMeta(is) && is.getItemMeta().hasDisplayName(); - } - - /** - * Does the item have any lore? - * - * @param is the item - * @return true if it does - */ - public static boolean hasLore(ItemStack is) { - return hasMeta(is) && is.getItemMeta().hasLore(); - } - - /** - * Does the item have the given name (color matters) - * - * @param is the item - * @param name the name - * @return true if it has the name - */ - public static boolean hasName(ItemStack is, String name) { - return hasName(is) && is.getItemMeta().getDisplayName().equals(name); - } - - - /** - * Does the item have the given enchantment - * - * @param is the item - * @param e the enchantment - * @return true if it does - */ - public static boolean hasEnchantment(ItemStack is, Enchantment e) { - return is(is) && is.getEnchantments().containsKey(e); - } - - /** - * Does the item have the enchantment at the given level - * - * @param is the item - * @param e the enchantment - * @param level the level - * @return true if it does - */ - public static boolean hasEnchantment(ItemStack is, Enchantment e, int level) { - if (!is(is)) { - return false; - } - - return hasEnchantment(is, e) && is.getEnchantmentLevel(e) == level; - } - - /** - * Does the item have any enchantments - * - * @param is the item - * @return true if it does - */ - public static boolean hasEnchantments(ItemStack is) { - if (!is(is)) { - return false; - } - - return !is.getEnchantments().isEmpty(); - } - - /** - * Get a materialblock representation of this item - * - * @param is the item - * @return the materialblock or null if the item is null - */ - @SuppressWarnings("deprecation") - public static MaterialBlock toMaterialBlock(ItemStack is) { - if (is != null) { - return new MaterialBlock(is.getType(), is.getData().getData()); - } - - return null; - } - - /** - * Should the itemstack be broken? - * - * @param is the itemStack - * @return true if it should be broken - */ - public static boolean isBroken(ItemStack is) { - return is(is) && getMaxDurability(is) == getDurability(is) && hasDurability(is); - } - - /** - * Does this item have durability - * - * @param is the item - * @return true if it does - */ - public static boolean hasDurability(ItemStack is) { - return is(is) && getMaxDurability(is) > 0; - } - - /** - * Get the durability percent - * - * @param is the itemstack - * @return the percent - */ - public static double getDurabilityPercent(ItemStack is) { - if (!is(is)) { - return 0.0; - } - - if (getMaxDurability(is) == 0) { - return 1.0; - } - - return 1.0 - ((double) getDurability(is) / (double) getMaxDurability(is)); - } - - /** - * Set the durability percent - * - * @param is the itemStack - * @param pc the percent - */ - public static void setDurabilityPercent(ItemStack is, double pc) { - if (!is(is)) { - return; - } - - pc = (pc > 1.0 ? 1.0 : (pc < 0.0 ? 0.0 : pc)); - - if (getDurability(is) == 0) { - return; - } - - setDurability(is, (int) ((double) getMaxDurability(is) * (1.0 - pc))); - } - - /** - * Get the max durability - * - * @param is the item - * @return the item type's max durability - */ - public static short getMaxDurability(ItemStack is) { - if (!is(is)) { - return 0; - } - - return is.getType().getMaxDurability(); - } - - /** - * Get the durability - * - * @param is the item - * @return the item durability - */ - public static short getDurability(ItemStack is) { - if (!is(is)) { - return 0; - } - - return is.getDurability(); - } - - /** - * Set the durability (if higher than max, it will be set to the max) - * - * @param is the item - * @param dmg the durability - */ - public static void setDurability(ItemStack is, short dmg) { - if (!is(is)) { - return; - } - - is.setDurability(dmg > getMaxDurability(is) ? getMaxDurability(is) : dmg); - } - - /** - * Set the durability (if higher than max, it will be set to the max) - * - * @param is the item - * @param dmg the durability - */ - public static void setDurability(ItemStack is, int dmg) { - if (!is(is)) { - return; - } - - setDurability(is, (short) dmg); - } - - /** - * Damage an itemstack - * - * @param is the item - * @param amt the amount to damage - */ - public static void damage(ItemStack is, int amt) { - if (!is(is)) { - return; - } - - setDurability(is, getDurability(is) + amt); - } - - /** - * Can the item a be stacked onto the item b (following max stack size) - * - * @param a the item a - * @param b the item b - * @return true if they can be merged - */ - @SuppressWarnings("deprecation") - public static boolean isMergable(ItemStack a, ItemStack b) { - if (is(a) && is(b)) { - if (!a.getType().equals(b.getType())) { - return false; - } - - if (a.getData().getData() != b.getData().getData()) { - return false; - } - - if (a.hasItemMeta() != b.hasItemMeta()) { - return false; - } - - if (a.getDurability() != b.getDurability()) { - return false; - } - - if (a.hasItemMeta()) { - if (!a.getItemMeta().getDisplayName().equals(b.getItemMeta().getDisplayName())) { - return false; - } - - if (!new ArrayList(a.getItemMeta().getLore()).equals(new ArrayList<>(b.getItemMeta().getLore()))) { - return false; - } - } - - return a.getMaxStackSize() >= a.getAmount() + b.getAmount(); - } - - return false; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/J.java b/src/main/java/com/volmit/adapt/util/J.java deleted file mode 100644 index 6851cbbd9..000000000 --- a/src/main/java/com/volmit/adapt/util/J.java +++ /dev/null @@ -1,283 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import org.bukkit.Bukkit; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -public class J { - private static final int tid = 0; - private static List afterStartup = new ArrayList<>(); - private static List afterStartupAsync = new ArrayList<>(); - private static boolean started = false; - - public static void dofor(int a, Function c, int ch, Consumer d) { - for (int i = a; c.apply(i); i += ch) { - c.apply(i); - } - } - - public static boolean doif(Supplier c, Runnable g) { - if (c.get()) { - g.run(); - return true; - } - - return false; - } - - public static void a(Runnable a) { - MultiBurst.burst.lazy(a); - } - - public static Future a(Callable a) { - return MultiBurst.burst.getService().submit(a); - } - - public static void attemptAsync(NastyRunnable r) { - J.a(() -> J.attempt(r)); - } - - public static R attemptResult(NastyFuture r, R onError) { - try { - return r.run(); - } catch (Throwable e) { - e.printStackTrace(); - } - - return onError; - } - - public static R attemptFunction(NastyFunction r, T param, R onError) { - try { - return r.run(param); - } catch (Throwable e) { - Adapt.verbose("Failed to run function: " + e.getMessage()); - } - - return onError; - } - - public static boolean sleep(long ms) { - return J.attempt(() -> Thread.sleep(ms)); - } - - public static boolean attempt(NastyRunnable r) { - return attemptCatch(r) == null; - } - - public static Throwable attemptCatch(NastyRunnable r) { - try { - r.run(); - } catch (Throwable e) { - return e; - } - - return null; - } - - public static T attempt(Supplier t, T i) { - try { - return t.get(); - } catch (Throwable e) { - return i; - } - } - - /** - * Dont call this unless you know what you are doing! - */ - public static void executeAfterStartupQueue() { - if (started) { - return; - } - - started = true; - - for (Runnable r : afterStartup) { - s(r); - } - - for (Runnable r : afterStartupAsync) { - a(r); - } - - afterStartup = null; - afterStartupAsync = null; - } - - /** - * Schedule a sync task to be run right after startup. If the server has already - * started ticking, it will simply run it in a sync task. - *

- * If you dont know if you should queue this or not, do so, it's pretty - * forgiving. - * - * @param r the runnable - */ - public static void ass(Runnable r) { - if (started) { - s(r); - } else { - afterStartup.add(r); - } - } - - /** - * Schedule an async task to be run right after startup. If the server has - * already started ticking, it will simply run it in an async task. - *

- * If you dont know if you should queue this or not, do so, it's pretty - * forgiving. - * - * @param r the runnable - */ - public static void asa(Runnable r) { - if (started) { - a(r); - } else { - afterStartupAsync.add(r); - } - } - - /** - * Queue a sync task - * - * @param r the runnable - */ - public static void s(Runnable r) { - Bukkit.getScheduler().scheduleSyncDelayedTask(Adapt.instance, r); - } - - /** - * Queue a sync task - * - * @param r the runnable - * @param delay the delay to wait in ticks before running - */ - public static void s(Runnable r, int delay) { - Bukkit.getScheduler().scheduleSyncDelayedTask(Adapt.instance, r, delay); - } - - /** - * Cancel a sync repeating task - * - * @param id the task id - */ - public static void csr(int id) { - Bukkit.getScheduler().cancelTask(id); - } - - /** - * Start a sync repeating task - * - * @param r the runnable - * @param interval the interval - * @return the task id - */ - public static int sr(Runnable r, int interval) { - return Bukkit.getScheduler().scheduleSyncRepeatingTask(Adapt.instance, r, 0, interval); - } - - /** - * Start a sync repeating task for a limited amount of ticks - * - * @param r the runnable - * @param interval the interval in ticks - * @param intervals the maximum amount of intervals to run - */ - public static void sr(Runnable r, int interval, int intervals) { - FinalInteger fi = new FinalInteger(0); - - new SR() { - @Override - public void run() { - fi.add(1); - r.run(); - - if (fi.get() >= intervals) { - cancel(); - } - } - }; - } - - /** - * Call an async task dealyed - * - * @param r the runnable - * @param delay the delay to wait before running - */ - @SuppressWarnings("deprecation") - public static void a(Runnable r, int delay) { - Bukkit.getScheduler().scheduleAsyncDelayedTask(Adapt.instance, r, delay); - } - - /** - * Cancel an async repeat task - * - * @param id the id - */ - public static void car(int id) { - Bukkit.getScheduler().cancelTask(id); - } - - /** - * Start an async repeat task - * - * @param r the runnable - * @param interval the interval in ticks - * @return the task id - */ - @SuppressWarnings("deprecation") - public static int ar(Runnable r, int interval) { - return Bukkit.getScheduler().scheduleAsyncRepeatingTask(Adapt.instance, r, 0, interval); - } - - /** - * Start an async repeating task for a limited time - * - * @param r the runnable - * @param interval the interval - * @param intervals the intervals to run - */ - public static void ar(Runnable r, int interval, int intervals) { - FinalInteger fi = new FinalInteger(0); - - new AR() { - @Override - public void run() { - fi.add(1); - r.run(); - - if (fi.get() >= intervals) { - cancel(); - } - } - }; - } -} diff --git a/src/main/java/com/volmit/adapt/util/JarScanner.java b/src/main/java/com/volmit/adapt/util/JarScanner.java deleted file mode 100644 index 50936793f..000000000 --- a/src/main/java/com/volmit/adapt/util/JarScanner.java +++ /dev/null @@ -1,94 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -public class JarScanner { - private final Set> classes; - private final File jar; - private final String superPackage; - - /** - * Create a scanner - * - * @param jar the path to the jar - */ - public JarScanner(File jar, String superPackage) { - this.jar = jar; - this.classes = new HashSet<>(); - this.superPackage = superPackage; - } - - /** - * Scan the jar - * - * @throws IOException bad things happen - */ - public void scan() throws IOException { - classes.clear(); - FileInputStream fin = new FileInputStream(jar); - ZipInputStream zip = new ZipInputStream(fin); - - for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) { - if (!entry.isDirectory() && entry.getName().endsWith(".class")) { - if (entry.getName().contains("$")) { - continue; - } - - String c = entry.getName().replaceAll("/", ".").replace(".class", ""); - - if (c.startsWith(superPackage)) { - try { - Class clazz = Class.forName(c); - classes.add(clazz); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - } - } - } - - zip.close(); - } - - /** - * Get the scanned clases - * - * @return a gset of classes - */ - public Set> getClasses() { - return classes; - } - - /** - * Get the file object for the jar - * - * @return a file object representing the jar - */ - public File getJar() { - return jar; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/Json.java b/src/main/java/com/volmit/adapt/util/Json.java deleted file mode 100644 index d08ac8246..000000000 --- a/src/main/java/com/volmit/adapt/util/Json.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.volmit.adapt.util; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import lombok.NonNull; - -import java.lang.reflect.Type; - -public class Json { - public static final Gson NORMAL = create(false); - public static final Gson PRETTY = create(true); - - @NonNull - public static String toJson(@NonNull Object src, boolean pretty) { - return toJson(src, src.getClass(), pretty); - } - - @NonNull - public static String toJson(@NonNull Object src, @NonNull Type type, boolean pretty) { - return (pretty ? PRETTY : NORMAL).toJson(src, type); - } - - public static T fromJson(@NonNull String json, @NonNull Class type) { - return NORMAL.fromJson(json, type); - } - - public static T fromJson(@NonNull String json, @NonNull Type type) { - return NORMAL.fromJson(json, type); - } - - private static Gson create(boolean pretty) { - var builder = new GsonBuilder() - .setLenient() - .disableHtmlEscaping(); - if (pretty) builder.setPrettyPrinting(); - return builder.create(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/KeyPair.java b/src/main/java/com/volmit/adapt/util/KeyPair.java deleted file mode 100644 index becb952b6..000000000 --- a/src/main/java/com/volmit/adapt/util/KeyPair.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Represents a keypair - * - * @param the key type - * @param the value type - * @author cyberpwn - */ -@SuppressWarnings("hiding") -public class KeyPair { - private K k; - private V v; - - /** - * Create a keypair - * - * @param k the key - * @param v the value - */ - public KeyPair(K k, V v) { - this.k = k; - this.v = v; - } - - public K getK() { - return k; - } - - public void setK(K k) { - this.k = k; - } - - public V getV() { - return v; - } - - public void setV(V v) { - this.v = v; - } -} diff --git a/src/main/java/com/volmit/adapt/util/LZString.java b/src/main/java/com/volmit/adapt/util/LZString.java deleted file mode 100644 index 0ce742628..000000000 --- a/src/main/java/com/volmit/adapt/util/LZString.java +++ /dev/null @@ -1,556 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.*; - -public class LZString { - - private static final char[] keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(); - private static final char[] keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$".toCharArray(); - private static final Map> baseReverseDic = new HashMap>(); - - private static char getBaseValue(char[] alphabet, Character character) { - Map map = baseReverseDic.get(alphabet); - if (map == null) { - map = new HashMap(); - baseReverseDic.put(alphabet, map); - for (int i = 0; i < alphabet.length; i++) { - map.put(alphabet[i], i); - } - } - return (char) map.get(character).intValue(); - } - - public static String compressToBase64(String input) { - if (input == null) - return ""; - String res = LZString._compress(input, 6, new CompressFunctionWrapper() { - @Override - public char doFunc(int a) { - return keyStrBase64[a]; - } - }); - switch (res.length() % 4) { // To produce valid Base64 - default: // When could this happen ? - case 0: - return res; - case 1: - return res + "==="; - case 2: - return res + "=="; - case 3: - return res + "="; - } - } - - public static String decompressFromBase64(final String inputStr) { - if (inputStr == null) - return ""; - if (inputStr.equals("")) - return null; - return LZString._decompress(inputStr.length(), 32, new DecompressFunctionWrapper() { - @Override - public char doFunc(int index) { - return getBaseValue(keyStrBase64, inputStr.charAt(index)); - } - }); - } - - public static String compressToUTF16(String input) { - if (input == null) - return ""; - return LZString._compress(input, 15, new CompressFunctionWrapper() { - @Override - public char doFunc(int a) { - return fc(a + 32); - } - }) + " "; - } - - public static String decompressFromUTF16(final String compressedStr) { - if (compressedStr == null) - return ""; - if (compressedStr.isEmpty()) - return null; - return LZString._decompress(compressedStr.length(), 16384, new DecompressFunctionWrapper() { - @Override - public char doFunc(int index) { - return (char) (compressedStr.charAt(index) - 32); - } - }); - } - - //TODO: java has no Uint8Array type, what can we do? - - public static String compressToEncodedURIComponent(String input) { - if (input == null) - return ""; - return LZString._compress(input, 6, new CompressFunctionWrapper() { - @Override - public char doFunc(int a) { - return keyStrUriSafe[a]; - } - }); - } - - public static String decompressFromEncodedURIComponent(String inputStr) { - if (inputStr == null) return ""; - if (inputStr.isEmpty()) return null; - final String urlEncodedInputStr = inputStr.replace(' ', '+'); - return LZString._decompress(urlEncodedInputStr.length(), 32, new DecompressFunctionWrapper() { - @Override - public char doFunc(int index) { - return getBaseValue(keyStrUriSafe, urlEncodedInputStr.charAt(index)); - } - }); - } - - public static String compress(String uncompressed) { - return LZString._compress(uncompressed, 16, new CompressFunctionWrapper() { - @Override - public char doFunc(int a) { - return fc(a); - } - }); - } - - private static String _compress(String uncompressedStr, int bitsPerChar, CompressFunctionWrapper getCharFromInt) { - if (uncompressedStr == null) return ""; - int i, value; - Map context_dictionary = new HashMap(); - Set context_dictionaryToCreate = new HashSet(); - String context_c = ""; - String context_wc = ""; - String context_w = ""; - int context_enlargeIn = 2; // Compensate for the first entry which should not count - int context_dictSize = 3; - int context_numBits = 2; - StringBuilder context_data = new StringBuilder(uncompressedStr.length() / 3); - int context_data_val = 0; - int context_data_position = 0; - int ii; - - for (ii = 0; ii < uncompressedStr.length(); ii += 1) { - context_c = String.valueOf(uncompressedStr.charAt(ii)); - if (!context_dictionary.containsKey(context_c)) { - context_dictionary.put(context_c, context_dictSize++); - context_dictionaryToCreate.add(context_c); - } - - context_wc = context_w + context_c; - if (context_dictionary.containsKey(context_wc)) { - context_w = context_wc; - } else { - if (context_dictionaryToCreate.contains(context_w)) { - if (context_w.charAt(0) < 256) { - for (i = 0; i < context_numBits; i++) { - context_data_val = (context_data_val << 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - } - value = context_w.charAt(0); - for (i = 0; i < 8; i++) { - context_data_val = (context_data_val << 1) | (value & 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = value >> 1; - } - } else { - value = 1; - for (i = 0; i < context_numBits; i++) { - context_data_val = (context_data_val << 1) | value; - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = 0; - } - value = context_w.charAt(0); - for (i = 0; i < 16; i++) { - context_data_val = (context_data_val << 1) | (value & 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = value >> 1; - } - } - context_enlargeIn--; - if (context_enlargeIn == 0) { - context_enlargeIn = powerOf2(context_numBits); - context_numBits++; - } - context_dictionaryToCreate.remove(context_w); - } else { - value = context_dictionary.get(context_w); - for (i = 0; i < context_numBits; i++) { - context_data_val = (context_data_val << 1) | (value & 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = value >> 1; - } - - } - context_enlargeIn--; - if (context_enlargeIn == 0) { - context_enlargeIn = powerOf2(context_numBits); - context_numBits++; - } - // Add wc to the dictionary. - context_dictionary.put(context_wc, context_dictSize++); - context_w = context_c; - } - } - - // Output the code for w. - if (!context_w.isEmpty()) { - if (context_dictionaryToCreate.contains(context_w)) { - if (context_w.charAt(0) < 256) { - for (i = 0; i < context_numBits; i++) { - context_data_val = (context_data_val << 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - } - value = context_w.charAt(0); - for (i = 0; i < 8; i++) { - context_data_val = (context_data_val << 1) | (value & 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = value >> 1; - } - } else { - value = 1; - for (i = 0; i < context_numBits; i++) { - context_data_val = (context_data_val << 1) | value; - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = 0; - } - value = context_w.charAt(0); - for (i = 0; i < 16; i++) { - context_data_val = (context_data_val << 1) | (value & 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = value >> 1; - } - } - context_enlargeIn--; - if (context_enlargeIn == 0) { - context_enlargeIn = powerOf2(context_numBits); - context_numBits++; - } - context_dictionaryToCreate.remove(context_w); - } else { - value = context_dictionary.get(context_w); - for (i = 0; i < context_numBits; i++) { - context_data_val = (context_data_val << 1) | (value & 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = value >> 1; - } - - } - context_enlargeIn--; - if (context_enlargeIn == 0) { - context_enlargeIn = powerOf2(context_numBits); - context_numBits++; - } - } - - // Mark the end of the stream - value = 2; - for (i = 0; i < context_numBits; i++) { - context_data_val = (context_data_val << 1) | (value & 1); - if (context_data_position == bitsPerChar - 1) { - context_data_position = 0; - context_data.append(getCharFromInt.doFunc(context_data_val)); - context_data_val = 0; - } else { - context_data_position++; - } - value = value >> 1; - } - - // Flush the last char - while (true) { - context_data_val = (context_data_val << 1); - if (context_data_position == bitsPerChar - 1) { - context_data.append(getCharFromInt.doFunc(context_data_val)); - break; - } else - context_data_position++; - } - return context_data.toString(); - } - - public static String f(int i) { - return String.valueOf((char) i); - } - - public static char fc(int i) { - return (char) i; - } - - public static String decompress(final String compressed) { - if (compressed == null) - return ""; - if (compressed.isEmpty()) - return null; - return LZString._decompress(compressed.length(), 32768, new DecompressFunctionWrapper() { - @Override - public char doFunc(int i) { - return compressed.charAt(i); - } - }); - } - - private static String _decompress(int length, int resetValue, DecompressFunctionWrapper getNextValue) { - List dictionary = new ArrayList(); - // TODO: is next an unused variable in original lz-string? - @SuppressWarnings("unused") - int next; - int enlargeIn = 4; - int dictSize = 4; - int numBits = 3; - String entry = ""; - StringBuilder result = new StringBuilder(); - String w; - int bits, resb; - int maxpower, power; - String c = null; - DecData data = new DecData(); - data.val = getNextValue.doFunc(0); - data.position = resetValue; - data.index = 1; - - for (int i = 0; i < 3; i += 1) { - dictionary.add(i, f(i)); - } - - bits = 0; - maxpower = powerOf2(2); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue.doFunc(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - - switch (next = bits) { - case 0: - bits = 0; - maxpower = powerOf2(8); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue.doFunc(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - c = f(bits); - break; - case 1: - bits = 0; - maxpower = powerOf2(16); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue.doFunc(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - c = f(bits); - break; - case 2: - return ""; - } - dictionary.add(3, c); - w = c; - result.append(w); - while (true) { - if (data.index > length) { - return ""; - } - - bits = 0; - maxpower = powerOf2(numBits); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue.doFunc(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - // TODO: very strange here, c above is as char/string, here further is a int, rename "c" in the switch as "cc" - int cc; - switch (cc = bits) { - case 0: - bits = 0; - maxpower = powerOf2(8); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue.doFunc(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - - dictionary.add(dictSize++, f(bits)); - cc = dictSize - 1; - enlargeIn--; - break; - case 1: - bits = 0; - maxpower = powerOf2(16); - power = 1; - while (power != maxpower) { - resb = data.val & data.position; - data.position >>= 1; - if (data.position == 0) { - data.position = resetValue; - data.val = getNextValue.doFunc(data.index++); - } - bits |= (resb > 0 ? 1 : 0) * power; - power <<= 1; - } - dictionary.add(dictSize++, f(bits)); - cc = dictSize - 1; - enlargeIn--; - break; - case 2: - return result.toString(); - } - - if (enlargeIn == 0) { - enlargeIn = powerOf2(numBits); - numBits++; - } - - if (cc < dictionary.size() && dictionary.get(cc) != null) { - entry = dictionary.get(cc); - } else { - if (cc == dictSize) { - entry = w + w.charAt(0); - } else { - return null; - } - } - result.append(entry); - - // Add w+entry[0] to the dictionary. - dictionary.add(dictSize++, w + entry.charAt(0)); - enlargeIn--; - - w = entry; - - if (enlargeIn == 0) { - enlargeIn = powerOf2(numBits); - numBits++; - } - - } - - } - - private static int powerOf2(int power) { - return 1 << power; - } - - private static abstract class CompressFunctionWrapper { - public abstract char doFunc(int i); - } - - private static abstract class DecompressFunctionWrapper { - public abstract char doFunc(int i); - } - - protected static class DecData { - public char val; - public int position; - public int index; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/ListTag.java b/src/main/java/com/volmit/adapt/util/ListTag.java deleted file mode 100644 index 18abf0ddd..000000000 --- a/src/main/java/com/volmit/adapt/util/ListTag.java +++ /dev/null @@ -1,84 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.Collections; -import java.util.List; - -/** - * The TAG_List tag. - * - * @author Graham Edgecombe - */ -public final class ListTag extends Tag { - - /** - * The type. - */ - private final Class type; - - /** - * The value. - */ - private final List value; - - /** - * Creates the tag. - * - * @param name The name. - * @param type The type of item in the list. - * @param value The value. - */ - public ListTag(String name, Class type, List value) { - super(name); - this.type = type; - this.value = Collections.unmodifiableList(value); - } - - /** - * Gets the type of item in this list. - * - * @return The type of item in this list. - */ - public Class getType() { - return type; - } - - @Override - public List getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - StringBuilder bldr = new StringBuilder(); - bldr.append("TAG_List" + append + ": " + value.size() + " entries of type " + NBTUtils.getTypeName(type) + "\r\n{\r\n"); - for (Tag t : value) { - bldr.append(" " + t.toString().replaceAll("\r\n", "\r\n ") + "\r\n"); - } - bldr.append("}"); - return bldr.toString(); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Localizer.java b/src/main/java/com/volmit/adapt/util/Localizer.java deleted file mode 100644 index 2eb715055..000000000 --- a/src/main/java/com/volmit/adapt/util/Localizer.java +++ /dev/null @@ -1,327 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.util.config.ConfigFileSupport; -import lombok.SneakyThrows; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; - -import java.io.File; -import java.io.InputStream; -import java.nio.file.Files; -import java.util.Objects; - -public class Localizer { - private static final Object LANGUAGE_CACHE_LOCK = new Object(); - private static String cachedPrimaryLanguage; - private static JsonObject cachedPrimaryLanguageRoot; - private static String cachedFallbackLanguage; - private static JsonObject cachedFallbackLanguageRoot; - - @SneakyThrows - public static void updateLanguageFile() { - if (AdaptConfig.get().isAutoUpdateLanguage()) { - Adapt.verbose("Attempting to update Language File"); - File langFolder = new File(Adapt.instance.getDataFolder() + "/languages"); - if (!langFolder.exists()) { - langFolder.mkdirs(); - } - - Adapt.verbose("Updating Primary Language File: " + AdaptConfig.get().getLanguage()); - syncLanguageResource(langFolder, AdaptConfig.get().getLanguage()); - Adapt.verbose("Loaded Primary Language: " + AdaptConfig.get().getLanguage()); - - if (!Objects.equals(AdaptConfig.get().getLanguage(), AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing())) { - Adapt.verbose("Updating Fallback Language File: " + AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing()); - syncLanguageResource(langFolder, AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing()); - Adapt.verbose("Loaded Fallback: " + AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing()); - } - - migrateExistingLanguageFilesToToml(); - } else { - Adapt.error("Auto Update Language is disabled, Expect Errors."); - Adapt.error("Do not disable this unless you know what you are doing, and dont expect support."); - migrateExistingLanguageFilesToToml(); - } - - invalidateLanguageCache(); - } - - - @SneakyThrows - public static String dLocalize(String s1, String s2, String s3) { - return dLocalize(s1 + "." + s2 + "." + s3); - } - - @SneakyThrows - public static String dLocalize(String key, Object... params) { - String cacheKey = key; - if (!Adapt.wordKey.containsKey(cacheKey)) { - File langFolder = new File(Adapt.instance.getDataFolder(), "languages"); - String resolved = resolveLocalizedFromRoot(getPrimaryLanguageRoot(langFolder), key); - - if (resolved == null) { - updateLanguageFile(); - resolved = resolveLocalizedFromRoot(getPrimaryLanguageRoot(langFolder), key); - } - - if (resolved == null) { - Adapt.verbose("Your Language File is missing the following key: " + key); - Adapt.verbose("Loading English Language File FallBack"); - - resolved = resolveLocalizedFromRoot(getFallbackLanguageRoot(langFolder), key); - } - - if (resolved == null) { - Adapt.wordKey.put(cacheKey, key); - Adapt.error("Your Fallback Language File is missing the following key: " + key); - Adapt.verbose("New Assignement: " + key); - Adapt.error("Please report this to the developer!"); - } else { - Adapt.wordKey.put(cacheKey, resolved); - Adapt.verbose("Loaded Localization: " + resolved + " for key: " + key); - } - } - var s = applyParameters(Adapt.wordKey.get(cacheKey), params); - if (AdaptConfig.get().isAutomaticGradients()) { - s = C.translateAlternateColorCodes('&', s); - s = C.aura(s, -20, 7, 8, 0.36); - } - - return LegacyComponentSerializer.legacySection() - .serialize(MiniMessage.miniMessage().deserialize(s)); - } - - private static void syncLanguageResource(File langFolder, String languageCode) throws Exception { - if (languageCode == null || languageCode.isBlank()) { - return; - } - - String tomlResourcePath = languageCode + ".toml"; - String jsonResourcePath = languageCode + ".json"; - - String resourcePath = tomlResourcePath; - InputStream in = Adapt.instance.getResource(tomlResourcePath); - if (in == null) { - resourcePath = jsonResourcePath; - in = Adapt.instance.getResource(jsonResourcePath); - } - - if (in == null) { - Adapt.warn("Missing bundled language resource: " + tomlResourcePath + " (and fallback " + jsonResourcePath + ")"); - return; - } - - try (InputStream stream = in) { - String raw = IO.readAll(stream); - JsonElement parsed = ConfigFileSupport.parseToJsonElement(raw, new File(resourcePath)); - if (parsed == null) { - Adapt.warn("Failed to parse bundled language resource: " + resourcePath); - return; - } - - File tomlTarget = new File(langFolder, languageCode + ".toml"); - Files.deleteIfExists(tomlTarget.toPath()); - Files.writeString(tomlTarget.toPath(), ConfigFileSupport.serializeJsonElementToToml(parsed)); - - File legacyJsonTarget = new File(langFolder, jsonResourcePath); - Files.deleteIfExists(legacyJsonTarget.toPath()); - } - } - - private static File resolveLanguageFile(File languageFolder, String languageCode) { - File toml = new File(languageFolder, languageCode + ".toml"); - if (toml.exists()) { - return toml; - } - - return new File(languageFolder, languageCode + ".json"); - } - - private static JsonObject loadLanguageRoot(File file) { - try { - if (file == null || !file.exists() || !file.isFile()) { - return null; - } - - String raw = Files.readString(file.toPath()); - JsonElement root = ConfigFileSupport.parseToJsonElement(raw, file); - if (root == null || !root.isJsonObject()) { - return null; - } - - return root.getAsJsonObject(); - } catch (Throwable ignored) { - return null; - } - } - - private static JsonObject getPrimaryLanguageRoot(File languageFolder) { - synchronized (LANGUAGE_CACHE_LOCK) { - String language = AdaptConfig.get().getLanguage(); - if (Objects.equals(language, cachedPrimaryLanguage) && cachedPrimaryLanguageRoot != null) { - return cachedPrimaryLanguageRoot; - } - - cachedPrimaryLanguage = language; - cachedPrimaryLanguageRoot = loadLanguageRoot(resolveLanguageFile(languageFolder, language)); - return cachedPrimaryLanguageRoot; - } - } - - private static JsonObject getFallbackLanguageRoot(File languageFolder) { - synchronized (LANGUAGE_CACHE_LOCK) { - String fallbackLanguage = AdaptConfig.get().getFallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing(); - if (Objects.equals(fallbackLanguage, cachedFallbackLanguage) && cachedFallbackLanguageRoot != null) { - return cachedFallbackLanguageRoot; - } - - cachedFallbackLanguage = fallbackLanguage; - cachedFallbackLanguageRoot = loadLanguageRoot(resolveLanguageFile(languageFolder, fallbackLanguage)); - return cachedFallbackLanguageRoot; - } - } - - private static String resolveLocalizedFromRoot(JsonObject root, String key) { - if (root == null) { - return null; - } - return resolveLocalizedElementValue(resolveLocalizedElement(root, key)); - } - - private static void invalidateLanguageCache() { - synchronized (LANGUAGE_CACHE_LOCK) { - cachedPrimaryLanguage = null; - cachedPrimaryLanguageRoot = null; - cachedFallbackLanguage = null; - cachedFallbackLanguageRoot = null; - } - Adapt.wordKey.clear(); - } - - private static JsonElement resolveLocalizedElement(JsonObject root, String key) { - JsonObject current = root; - JsonElement element = null; - - for (String path : key.split("\\.")) { - if (current == null || !current.has(path)) { - return null; - } - - element = current.get(path); - if (element == null || element.isJsonNull()) { - return null; - } - - if (element.isJsonObject()) { - current = element.getAsJsonObject(); - } else { - current = null; - } - } - - return element; - } - - private static String resolveLocalizedElementValue(JsonElement element) { - if (element == null || element.isJsonNull()) { - return null; - } - - if (element.isJsonPrimitive()) { - return element.getAsString(); - } - - if (element.isJsonArray()) { - StringBuilder result = new StringBuilder(); - for (JsonElement value : element.getAsJsonArray()) { - if (!value.isJsonPrimitive()) { - continue; - } - - if (result.length() > 0) { - result.append('\n'); - } - - result.append(value.getAsString()); - } - - return result.toString(); - } - - return null; - } - - private static String applyParameters(String value, Object... params) { - if (value == null || params == null || params.length == 0) { - return value; - } - - String result = value; - for (int i = 0; i < params.length; i++) { - result = result.replace("{" + i + "}", String.valueOf(params[i])); - } - - return result; - } - - private static void migrateExistingLanguageFilesToToml() { - try { - File languageFolder = new File(Adapt.instance.getDataFolder(), "languages"); - if (!languageFolder.exists() || !languageFolder.isDirectory()) { - return; - } - - File[] files = languageFolder.listFiles((dir, name) -> name.toLowerCase().endsWith(".json")); - if (files == null || files.length == 0) { - return; - } - - for (File jsonFile : files) { - if (jsonFile == null || !jsonFile.exists() || !jsonFile.isFile()) { - continue; - } - - File tomlFile = ConfigFileSupport.toTomlFile(jsonFile); - if (tomlFile.exists() && tomlFile.isFile()) { - Files.deleteIfExists(jsonFile.toPath()); - continue; - } - - String raw = Files.readString(jsonFile.toPath()); - JsonElement parsed = ConfigFileSupport.parseToJsonElement(raw, jsonFile); - if (parsed == null) { - continue; - } - - Files.writeString(tomlFile.toPath(), ConfigFileSupport.serializeJsonElementToToml(parsed)); - Adapt.info("Migrated legacy language file [" + jsonFile.getName() + "] -> [" + tomlFile.getName() + "]."); - Files.deleteIfExists(jsonFile.toPath()); - } - invalidateLanguageCache(); - } catch (Throwable e) { - Adapt.warn("Failed to migrate legacy language json files: " + e.getMessage()); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/LongTag.java b/src/main/java/com/volmit/adapt/util/LongTag.java deleted file mode 100644 index 3b64ac587..000000000 --- a/src/main/java/com/volmit/adapt/util/LongTag.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * The TAG_Long tag. - * - * @author Graham Edgecombe - */ -public final class LongTag extends Tag { - - /** - * The value. - */ - private final long value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public LongTag(String name, long value) { - super(name); - this.value = value; - } - - @Override - public Long getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_Long" + append + ": " + value; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Looper.java b/src/main/java/com/volmit/adapt/util/Looper.java deleted file mode 100644 index 4c82a5af8..000000000 --- a/src/main/java/com/volmit/adapt/util/Looper.java +++ /dev/null @@ -1,45 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; - -public abstract class Looper extends Thread { - public void run() { - while (!interrupted()) { - try { - long m = loop(); - - if (m < 0) { - break; - } - - Thread.sleep(m); - } catch (InterruptedException e) { - break; - } catch (Throwable e) { - e.printStackTrace(); - } - } - - Adapt.info("Thread " + getName() + " Shutdown."); - } - - protected abstract long loop(); -} diff --git a/src/main/java/com/volmit/adapt/util/M.java b/src/main/java/com/volmit/adapt/util/M.java deleted file mode 100644 index e8dcbf769..000000000 --- a/src/main/java/com/volmit/adapt/util/M.java +++ /dev/null @@ -1,374 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; -import java.util.regex.Matcher; - -/** - * Math - * - * @author cyberpwn - */ -public class M { - private static final int precision = 128; - private static final int modulus = 360 * precision; - private static final float[] sin = new float[modulus]; - public static int tick = 0; - - static { - for (int i = 0; i < sin.length; i++) { - sin[i] = (float) Math.sin((i * Math.PI) / (precision * 180)); - } - } - - /** - * Scales B by an external range change so that
- *
- * BMIN < B < BMAX
- * AMIN < RESULT < AMAX
- *
- * So Given rangeScale(0, 20, 0, 10, 5) -> 10
- * 0 < 5 < 10
- * 0 < ? < 20
- *
- * would return 10 - * - * @param amin the resulting minimum - * @param amax the resulting maximum - * @param bmin the initial minimum - * @param bmax the initial maximum - * @param b the initial value - * @return the resulting value - */ - public static double rangeScale(double amin, double amax, double bmin, double bmax, double b) { - return amin + ((amax - amin) * ((b - bmin) / (bmax - bmin))); - } - - /** - * Get the percent (inverse lerp) from "from" to "to" where "at". - *

- * If from = 0 and to = 100 and at = 25 then it would return 0.25 - * - * @param from the from - * @param to the to - * @param at the at - * @return the percent - */ - public static double lerpInverse(double from, double to, double at) { - return M.rangeScale(0, 1, from, to, at); - } - - /** - * Linear interpolation from a to b where f is the percent across - * - * @param a the first pos (0) - * @param b the second pos (1) - * @param f the percent - * @return the value - */ - public static double lerp(double a, double b, double f) { - return a + (f * (b - a)); - } - - /** - * Bilinear interpolation - * - * @param a the first point (0, 0) - * @param b the second point (1, 0) - * @param c the third point (0, 1) - * @param d the fourth point (1, 1) - * @param x the x - * @param y the y - * @return the bilerped value - */ - public static double bilerp(double a, double b, double c, double d, double x, double y) { - return lerp(lerp(a, b, x), lerp(c, d, x), y); - } - - /** - * Trilinear interpolation - * - * @param a the first point (0, 0, 0) - * @param b the second point (1, 0, 0) - * @param c the third point (0, 0, 1) - * @param d the fourth point (1, 0, 1) - * @param e the fifth point (0, 1, 0) - * @param f the sixth point (1, 1, 0) - * @param g the seventh point (0, 1, 1) - * @param h the eighth point (1, 1, 1) - * @param x the x - * @param y the y - * @param z the z - * @return the trilerped value - */ - public static double trilerp(double a, double b, double c, double d, double e, double f, double g, double h, double x, double y, double z) { - return lerp(bilerp(a, b, c, d, x, y), bilerp(e, f, g, h, x, y), z); - } - - /** - * Clip a value - * - * @param value the value - * @param min the min - * @param max the max - * @return the clipped value - */ - @SuppressWarnings("unchecked") - public static T clip(T value, T min, T max) { - return (T) Double.valueOf(Math.min(max.doubleValue(), Math.max(min.doubleValue(), value.doubleValue()))); - } - - /** - * Get true or false based on random percent - * - * @param d between 0 and 1 - * @return true if true - */ - public static boolean r(Double d) { - if (d == null) { - return Math.random() < 0.5; - } - - return Math.random() < d; - } - - /** - * Get the ticks per second from a time in nanoseconds, the rad can be used for - * multiple ticks - * - * @param ns the time in nanoseconds - * @param rad the radius of the time - * @return the ticks per second in double form - */ - public static double tps(long ns, int rad) { - return (20.0 * (ns / 50000000.0)) / rad; - } - - /** - * Get the number of ticks from a time in nanoseconds - * - * @param ns the nanoseconds - * @return the amount of ticks - */ - public static double ticksFromNS(long ns) { - return (ns / 50000000.0); - } - - /** - * Get a random int from to (inclusive) - * - * @param f the from - * @param t the to - * @return the value - */ - public static int irand(int f, int t) { - return f + (int) (Math.random() * ((t - f) + 1)); - } - - /** - * Get a random float from to (inclusive) - * - * @param f the from - * @param t the to - * @return the value - */ - public static float frand(float f, float t) { - return f + (float) (Math.random() * ((t - f) + 1)); - } - - /** - * Get a random double from to (inclusive) - * - * @param f the from - * @param t the to - * @return the value - */ - public static double drand(double f, double t) { - return f + (Math.random() * ((t - f) + 1)); - } - - /** - * Get system Nanoseconds - * - * @return nanoseconds (current) - */ - public static long ns() { - return System.nanoTime(); - } - - /** - * Get the current millisecond time - * - * @return milliseconds - */ - public static long ms() { - return System.currentTimeMillis(); - } - - /** - * Fast sin function - * - * @param a the number - * @return the sin - */ - public static float sin(float a) { - return sinLookup((int) (a * precision + 0.5f)); - } - - /** - * Fast cos function - * - * @param a the number - * @return the cos - */ - public static float cos(float a) { - return sinLookup((int) ((a + 90f) * precision + 0.5f)); - } - - /** - * Fast tan function - * - * @param a the number - * @return the tan - */ - public static float tan(float a) { - float c = cos(a); - return sin(a) / (c == 0 ? 0.0000001f : c); - } - - /** - * Biggest number - * - * @param doubles the numbers - * @return the biggest one - */ - @SuppressWarnings("unchecked") - public static T max(T... doubles) { - double max = Double.MIN_VALUE; - - for (T i : doubles) { - if (i.doubleValue() > max) { - max = i.doubleValue(); - } - } - - return (T) Double.valueOf(max); - } - - /** - * Smallest number - * - * @param doubles the numbers - * @return the smallest one - */ - @SuppressWarnings("unchecked") - public static T min(T... doubles) { - double min = Double.MAX_VALUE; - - for (T i : doubles) { - if (i.doubleValue() < min) { - min = i.doubleValue(); - } - } - - return (T) Double.valueOf(min); - } - - /** - * Evaluates an expression using javascript engine and returns the double - * result. This can take variable parameters, so you need to define them. - * Parameters are defined as $[0-9]. For example evaluate("4$0/$1", 1, 2); This - * makes the expression (4x1)/2 == 2. Keep note that you must use 0-9, you - * cannot skip, or start at a number other than 0. - * - * @param expression the expression with variables - * @param args the arguments/variables - * @return the resulting double value - * @throws ScriptException ... gg - * @throws IndexOutOfBoundsException learn to count - */ - public static double evaluate(String expression, Double... args) throws ScriptException, IndexOutOfBoundsException { - for (int i = 0; i < args.length; i++) { - String current = "$" + i; - - if (expression.contains(current)) { - expression = expression.replaceAll(Matcher.quoteReplacement(current), args[i] + ""); - } - } - - return evaluate(expression); - } - - /** - * Evaluates an expression using javascript engine and returns the double - * - * @param expression the mathimatical expression - * @return the double result - * @throws ScriptException ... gg - */ - public static double evaluate(String expression) throws ScriptException { - ScriptEngineManager mgr = new ScriptEngineManager(); - ScriptEngine scriptEngine = mgr.getEngineByName("JavaScript"); - - return Double.valueOf(scriptEngine.eval(expression).toString()); - } - - /** - * is the number "is" within from-to - * - * @param from the lower end - * @param to the upper end - * @param is the check - * @return true if its within - */ - public static boolean within(int from, int to, int is) { - return is >= from && is <= to; - } - -// /** -// * Get the amount of days past since the epoch time (1970 jan 1 utc) -// * -// * @return the epoch days -// */ -// public static long epochDays() { -// return epochDays(M.ms()); -// } -// -// /** -// * Get the amount of days past since the epoch time (1970 jan 1 utc) -// * -// * @param ms the time in milliseconds -// * @return the epoch days -// */ -// private static long epochDays(long ms) { -// return ms / 1000 / 60 / 60 / 24; -// } - - private static float sinLookup(int a) { - return a >= 0 ? sin[a % (modulus)] : -sin[-a % (modulus)]; - } - - public static boolean interval(int tickInterval) { - return tick % (tickInterval <= 0 ? 1 : tickInterval) == 0; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/MaterialBlock.java b/src/main/java/com/volmit/adapt/util/MaterialBlock.java deleted file mode 100644 index d11af81c7..000000000 --- a/src/main/java/com/volmit/adapt/util/MaterialBlock.java +++ /dev/null @@ -1,126 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; - -/** - * Material blocks - * - * @author cyberpwn - */ -@SuppressWarnings("deprecation") -public class MaterialBlock { - private Material material; - private Byte data; - - /** - * Create a materialblock - * - * @param material the material - * @param data the data - */ - public MaterialBlock(Material material, Byte data) { - this.material = material; - this.data = data; - } - - public MaterialBlock(Material material) { - this.material = material; - data = 0; - } - - public MaterialBlock(Location location) { - this(location.getBlock()); - } - - public MaterialBlock(BlockState state) { - material = state.getType(); - data = state.getData().getData(); - } - - public MaterialBlock(Block block) { - material = block.getType(); - data = block.getData(); - } - - public MaterialBlock() { - material = Material.AIR; - data = 0; - } - - public Material getMaterial() { - return material; - } - - public void setMaterial(Material material) { - this.material = material; - } - - public Byte getData() { - return data; - } - - public void setData(Byte data) { - this.data = data; - } - - @Override - public String toString() { - if (getData() == 0) { - return getMaterial().toString(); - } - - return getMaterial().toString() + ":" + getData(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((data == null) ? 0 : data.hashCode()); - result = prime * result + ((material == null) ? 0 : material.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - MaterialBlock other = (MaterialBlock) obj; - if (data == null) { - if (other.data != null) { - return false; - } - } else if (!data.equals(other.data)) { - return false; - } - return material == other.material; - } -} diff --git a/src/main/java/com/volmit/adapt/util/MathHelper.java b/src/main/java/com/volmit/adapt/util/MathHelper.java deleted file mode 100644 index 57877cf5c..000000000 --- a/src/main/java/com/volmit/adapt/util/MathHelper.java +++ /dev/null @@ -1,492 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.Random; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.IntPredicate; - -public class MathHelper { - public static final float a = MathHelper.c(2.0f); - private static final float[] b = (float[]) a((Object) new float[65536], var0 -> - { - for (int var1 = 0; var1 < ((float[]) var0).length; ++var1) { - ((float[]) var0)[var1] = (float) Math.sin((double) var1 * 3.141592653589793 * 2.0 / 65536.0); - } - }); - private static final Random c = new Random(); - private static final int[] d = new int[]{0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; - private static final double e = Double.longBitsToDouble(4805340802404319232L); - private static final double[] f = new double[257]; - private static final double[] g = new double[257]; - - static { - for (int var02 = 0; var02 < 257; ++var02) { - double var1 = (double) var02 / 256.0; - double var3 = Math.asin(var1); - MathHelper.g[var02] = Math.cos(var3); - MathHelper.f[var02] = var3; - } - } - - public static T a(T var0, Consumer var1) { - var1.accept(var0); - return var0; - } - - public static float sin(float var0) { - return b[(int) (var0 * 10430.378f) & 65535]; - } - - public static float cos(float var0) { - return b[(int) (var0 * 10430.378f + 16384.0f) & 65535]; - } - - public static float c(float var0) { - return (float) Math.sqrt(var0); - } - - public static float sqrt(double var0) { - return (float) Math.sqrt(var0); - } - - public static int d(float var0) { - int var1 = (int) var0; - return var0 < (float) var1 ? var1 - 1 : var1; - } - - public static int floor(double var0) { - int var2 = (int) var0; - return var0 < (double) var2 ? var2 - 1 : var2; - } - - public static long d(double var0) { - long var2 = (long) var0; - return var0 < (double) var2 ? var2 - 1L : var2; - } - - public static float e(float var0) { - return Math.abs(var0); - } - - public static int a(int var0) { - return Math.abs(var0); - } - - public static int f(float var0) { - int var1 = (int) var0; - return var0 > (float) var1 ? var1 + 1 : var1; - } - - public static int f(double var0) { - int var2 = (int) var0; - return var0 > (double) var2 ? var2 + 1 : var2; - } - - public static int clamp(int var0, int var1, int var2) { - if (var0 < var1) { - return var1; - } - if (var0 > var2) { - return var2; - } - return var0; - } - - public static float a(float var0, float var1, float var2) { - if (var0 < var1) { - return var1; - } - if (var0 > var2) { - return var2; - } - return var0; - } - - public static double a(double var0, double var2, double var4) { - if (var0 < var2) { - return var2; - } - if (var0 > var4) { - return var4; - } - return var0; - } - - public static double b(double var0, double var2, double var4) { - if (var4 < 0.0) { - return var0; - } - if (var4 > 1.0) { - return var2; - } - return MathHelper.d(var4, var0, var2); - } - - public static double a(double var0, double var2) { - if (var0 < 0.0) { - var0 = -var0; - } - if (var2 < 0.0) { - var2 = -var2; - } - return var0 > var2 ? var0 : var2; - } - - public static int a(int var0, int var1) { - return Math.floorDiv(var0, var1); - } - - public static int nextInt(Random var0, int var1, int var2) { - if (var1 >= var2) { - return var1; - } - return var0.nextInt(var2 - var1 + 1) + var1; - } - - public static float a(Random var0, float var1, float var2) { - if (var1 >= var2) { - return var1; - } - return var0.nextFloat() * (var2 - var1) + var1; - } - - public static double a(Random var0, double var1, double var3) { - if (var1 >= var3) { - return var1; - } - return var0.nextDouble() * (var3 - var1) + var1; - } - - public static double a(long[] var0) { - long var1 = 0L; - for (long var6 : var0) { - var1 += var6; - } - return (double) var1 / (double) var0.length; - } - - public static boolean b(double var0, double var2) { - return Math.abs(var2 - var0) < 9.999999747378752E-6; - } - - public static int b(int var0, int var1) { - return Math.floorMod(var0, var1); - } - - public static float g(float var0) { - float var1 = var0 % 360.0f; - if (var1 >= 180.0f) { - var1 -= 360.0f; - } - if (var1 < -180.0f) { - var1 += 360.0f; - } - return var1; - } - - public static double g(double var0) { - double var2 = var0 % 360.0; - if (var2 >= 180.0) { - var2 -= 360.0; - } - if (var2 < -180.0) { - var2 += 360.0; - } - return var2; - } - - public static float c(float var0, float var1) { - return MathHelper.g(var1 - var0); - } - - public static float d(float var0, float var1) { - return MathHelper.e(MathHelper.c(var0, var1)); - } - - public static float b(float var0, float var1, float var2) { - float var3 = MathHelper.c(var0, var1); - float var4 = MathHelper.a(var3, -var2, var2); - return var1 - var4; - } - - public static float c(float var0, float var1, float var2) { - var2 = MathHelper.e(var2); - if (var0 < var1) { - return MathHelper.a(var0 + var2, var0, var1); - } - return MathHelper.a(var0 - var2, var1, var0); - } - - public static float d(float var0, float var1, float var2) { - float var3 = MathHelper.c(var0, var1); - return MathHelper.c(var0, var0 + var3, var2); - } - - public static int c(int var0) { - int var1 = var0 - 1; - var1 |= var1 >> 1; - var1 |= var1 >> 2; - var1 |= var1 >> 4; - var1 |= var1 >> 8; - var1 |= var1 >> 16; - return var1 + 1; - } - - public static boolean d(int var0) { - return var0 != 0 && (var0 & var0 - 1) == 0; - } - - public static int e(int var0) { - var0 = MathHelper.d(var0) ? var0 : MathHelper.c(var0); - return d[(int) ((long) var0 * 125613361L >> 27) & 31]; - } - - public static int f(int var0) { - return MathHelper.e(var0) - (MathHelper.d(var0) ? 0 : 1); - } - - public static int c(int var0, int var1) { - int var2; - if (var1 == 0) { - return 0; - } - if (var0 == 0) { - return var1; - } - if (var0 < 0) { - var1 *= -1; - } - if ((var2 = var0 % var1) == 0) { - return var0; - } - return var0 + var1 - var2; - } - - public static float h(float var0) { - return var0 - (float) MathHelper.d(var0); - } - - public static double h(double var0) { - return var0 - (double) MathHelper.d(var0); - } - - public static long a(BlockPosition var0) { - return c(var0.getX(), var0.getY(), var0.getZ()); - } - - public static long c(int var0, int var1, int var2) { - long var3 = (long) (var0 * 3129871) ^ (long) var2 * 116129781L ^ (long) var1; - var3 = var3 * var3 * 42317861L + var3 * 11L; - return var3 >> 16; - } - - public static UUID a(Random var0) { - long var1 = var0.nextLong() & -61441L | 16384L; - long var3 = var0.nextLong() & 0x3FFFFFFFFFFFFFFFL | Long.MIN_VALUE; - return new UUID(var1, var3); - } - - public static UUID a() { - return MathHelper.a(c); - } - - public static double c(double var0, double var2, double var4) { - return (var0 - var2) / (var4 - var2); - } - - public static double d(double var0, double var2) { - double var9; - boolean var6; - boolean var7; - boolean var8; - double var4 = var2 * var2 + var0 * var0; - if (Double.isNaN(var4)) { - return Double.NaN; - } - @SuppressWarnings("unused") - boolean bl = var6 = var0 < 0.0; - if (var6) { - var0 = -var0; - } - @SuppressWarnings("unused") - boolean bl2 = var7 = var2 < 0.0; - if (var7) { - var2 = -var2; - } - @SuppressWarnings("unused") - boolean bl3 = var8 = var0 > var2; - if (var8) { - var9 = var2; - var2 = var0; - var0 = var9; - } - var9 = MathHelper.i(var4); - double var11 = e + (var0 *= var9); - int var13 = (int) Double.doubleToRawLongBits(var11); - double var14 = f[var13]; - double var16 = g[var13]; - double var18 = var11 - e; - double var20 = var0 * var16 - (var2 *= var9) * var18; - double var22 = (6.0 + var20 * var20) * var20 * 0.16666666666666666; - double var24 = var14 + var22; - if (var8) { - var24 = 1.5707963267948966 - var24; - } - if (var7) { - var24 = 3.141592653589793 - var24; - } - if (var6) { - var24 = -var24; - } - return var24; - } - - public static double i(double var0) { - double var2 = 0.5 * var0; - long var4 = Double.doubleToRawLongBits(var0); - var4 = 6910469410427058090L - (var4 >> 1); - var0 = Double.longBitsToDouble(var4); - var0 *= 1.5 - var2 * var0 * var0; - return var0; - } - - public static int f(float var0, float var1, float var2) { - float var9; - float var8; - float var10; - int var3 = (int) (var0 * 6.0f) % 6; - float var4 = var0 * 6.0f - (float) var3; - float var5 = var2 * (1.0f - var1); - float var6 = var2 * (1.0f - var4 * var1); - float var7 = var2 * (1.0f - (1.0f - var4) * var1); - switch (var3) { - case 0: { - var8 = var2; - var9 = var7; - var10 = var5; - break; - } - case 1: { - var8 = var6; - var9 = var2; - var10 = var5; - break; - } - case 2: { - var8 = var5; - var9 = var2; - var10 = var7; - break; - } - case 3: { - var8 = var5; - var9 = var6; - var10 = var2; - break; - } - case 4: { - var8 = var7; - var9 = var5; - var10 = var2; - break; - } - case 5: { - var8 = var2; - var9 = var5; - var10 = var6; - break; - } - default: { - throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + var0 + ", " + var1 + ", " + var2); - } - } - int var11 = MathHelper.clamp((int) (var8 * 255.0f), 0, 255); - int var12 = MathHelper.clamp((int) (var9 * 255.0f), 0, 255); - int var13 = MathHelper.clamp((int) (var10 * 255.0f), 0, 255); - return var11 << 16 | var12 << 8 | var13; - } - - public static int g(int var0) { - var0 ^= var0 >>> 16; - var0 *= -2048144789; - var0 ^= var0 >>> 13; - var0 *= -1028477387; - var0 ^= var0 >>> 16; - return var0; - } - - public static int a(int var0, int var1, IntPredicate var2) { - int var3 = var1 - var0; - while (var3 > 0) { - int var4 = var3 / 2; - int var5 = var0 + var4; - if (var2.test(var5)) { - var3 = var4; - continue; - } - var0 = var5 + 1; - var3 -= var4 + 1; - } - return var0; - } - - public static float g(float var0, float var1, float var2) { - return var1 + var0 * (var2 - var1); - } - - public static double d(double var0, double var2, double var4) { - return var2 + var0 * (var4 - var2); - } - - public static double a(double var0, double var2, double var4, double var6, double var8, double var10) { - return MathHelper.d(var2, MathHelper.d(var0, var4, var6), MathHelper.d(var0, var8, var10)); - } - - public static double a(double var0, double var2, double var4, double var6, double var8, double var10, double var12, double var14, double var16, double var18, double var20) { - return MathHelper.d(var4, MathHelper.a(var0, var2, var6, var8, var10, var12), MathHelper.a(var0, var2, var14, var16, var18, var20)); - } - - public static double j(double var0) { - return var0 * var0 * var0 * (var0 * (var0 * 6.0 - 15.0) + 10.0); - } - - public static int k(double var0) { - if (var0 == 0.0) { - return 0; - } - return var0 > 0.0 ? 1 : -1; - } - - @Deprecated - public static float j(float var0, float var1, float var2) { - float var3; - for (var3 = var1 - var0; var3 < -180.0f; var3 += 360.0f) { - } - while (var3 >= 180.0f) { - var3 -= 360.0f; - } - return var0 + var2 * var3; - } - - public static float k(float var0) { - return var0 * var0; - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/Metrics.java b/src/main/java/com/volmit/adapt/util/Metrics.java deleted file mode 100644 index 5629299d7..000000000 --- a/src/main/java/com/volmit/adapt/util/Metrics.java +++ /dev/null @@ -1,862 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; - -import javax.net.ssl.HttpsURLConnection; -import java.io.*; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; - -public class Metrics { - - private final Plugin plugin; - - private final MetricsBase metricsBase; - - /** - * Creates a new Metrics instance. - * - * @param plugin Your plugin instance. - * @param serviceId The id of the service. It can be found at What is my plugin id? - */ - public Metrics(JavaPlugin plugin, int serviceId) { - this.plugin = plugin; - // Get the config file - File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); - File configFile = new File(bStatsFolder, "config.yml"); - YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); - if (!config.isSet("serverUuid")) { - config.addDefault("enabled", true); - config.addDefault("serverUuid", UUID.randomUUID().toString()); - config.addDefault("logFailedRequests", false); - config.addDefault("logSentData", false); - config.addDefault("logResponseStatusText", false); - // Inform the server owners about bStats - config - .options() - .header( - "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" - + "many people use their plugin and their total player count. It's recommended to keep bStats\n" - + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" - + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" - + "anonymous.") - .copyDefaults(true); - try { - config.save(configFile); - } catch (IOException ignored) { - Adapt.verbose("Failed to save bStats config file."); - } - } - // Load the data - boolean enabled = config.getBoolean("enabled", true); - String serverUUID = config.getString("serverUuid"); - boolean logErrors = config.getBoolean("logFailedRequests", false); - boolean logSentData = config.getBoolean("logSentData", false); - boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); - metricsBase = - new MetricsBase( - "bukkit", - serverUUID, - serviceId, - enabled, - this::appendPlatformData, - this::appendServiceData, - submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), - plugin::isEnabled, - (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), - (message) -> this.plugin.getLogger().log(Level.INFO, message), - logErrors, - logSentData, - logResponseStatusText); - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - metricsBase.addCustomChart(chart); - } - - private void appendPlatformData(JsonObjectBuilder builder) { - builder.appendField("playerAmount", getPlayerAmount()); - builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); - builder.appendField("bukkitVersion", Bukkit.getVersion()); - builder.appendField("bukkitName", Bukkit.getName()); - builder.appendField("javaVersion", System.getProperty("java.version")); - builder.appendField("osName", System.getProperty("os.name")); - builder.appendField("osArch", System.getProperty("os.arch")); - builder.appendField("osVersion", System.getProperty("os.version")); - builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); - } - - private void appendServiceData(JsonObjectBuilder builder) { - builder.appendField("pluginVersion", plugin.getDescription().getVersion()); - } - - private int getPlayerAmount() { - try { - // Around MC 1.8 the return type was changed from an array to a collection, - // This fixes java.lang.NoSuchMethodError: - // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; - Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - return onlinePlayersMethod.getReturnType().equals(Collection.class) - ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() - : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; - } catch (Exception e) { - // Just use the new method if the reflection failed - return Bukkit.getOnlinePlayers().size(); - } - } - - public static class MetricsBase { - - /** - * The version of the Metrics class. - */ - public static final String METRICS_VERSION = "2.2.1"; - - private static final ScheduledExecutorService scheduler = - Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); - - private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; - - private final String platform; - - private final String serverUuid; - - private final int serviceId; - - private final Consumer appendPlatformDataConsumer; - - private final Consumer appendServiceDataConsumer; - - private final Consumer submitTaskConsumer; - - private final Supplier checkServiceEnabledSupplier; - - private final BiConsumer errorLogger; - - private final Consumer infoLogger; - - private final boolean logErrors; - - private final boolean logSentData; - - private final boolean logResponseStatusText; - - private final Set customCharts = new HashSet<>(); - - private final boolean enabled; - - /** - * Creates a new MetricsBase class instance. - * - * @param platform The platform of the service. - * @param serviceId The id of the service. - * @param serverUuid The server uuid. - * @param enabled Whether or not data sending is enabled. - * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all platform-specific data. - * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all service-specific data. - * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be - * used to delegate the data collection to a another thread to prevent errors caused by - * concurrency. Can be {@code null}. - * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. - * @param errorLogger A consumer that accepts log message and an error. - * @param infoLogger A consumer that accepts info log messages. - * @param logErrors Whether or not errors should be logged. - * @param logSentData Whether or not the sent data should be logged. - * @param logResponseStatusText Whether or not the response status text should be logged. - */ - public MetricsBase( - String platform, - String serverUuid, - int serviceId, - boolean enabled, - Consumer appendPlatformDataConsumer, - Consumer appendServiceDataConsumer, - Consumer submitTaskConsumer, - Supplier checkServiceEnabledSupplier, - BiConsumer errorLogger, - Consumer infoLogger, - boolean logErrors, - boolean logSentData, - boolean logResponseStatusText) { - this.platform = platform; - this.serverUuid = serverUuid; - this.serviceId = serviceId; - this.enabled = enabled; - this.appendPlatformDataConsumer = appendPlatformDataConsumer; - this.appendServiceDataConsumer = appendServiceDataConsumer; - this.submitTaskConsumer = submitTaskConsumer; - this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; - this.errorLogger = errorLogger; - this.infoLogger = infoLogger; - this.logErrors = logErrors; - this.logSentData = logSentData; - this.logResponseStatusText = logResponseStatusText; - checkRelocation(); - if (enabled) { - startSubmitting(); - } - } - - /** - * Gzips the given string. - * - * @param str The string to gzip. - * @return The gzipped string. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - - public void addCustomChart(CustomChart chart) { - this.customCharts.add(chart); - } - - private void startSubmitting() { - final Runnable submitTask = - () -> { - if (!enabled || !checkServiceEnabledSupplier.get()) { - // Submitting data or service is disabled - scheduler.shutdown(); - return; - } - if (submitTaskConsumer != null) { - submitTaskConsumer.accept(this::submitData); - } else { - this.submitData(); - } - }; - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution - // of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into the initial - // and second delay. - // WARNING: You must not modify and part of this Metrics class, including the submit delay or - // frequency! - // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! - long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); - long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); - scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); - scheduler.scheduleAtFixedRate( - submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); - } - - private void submitData() { - final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); - appendPlatformDataConsumer.accept(baseJsonBuilder); - final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); - appendServiceDataConsumer.accept(serviceJsonBuilder); - JsonObjectBuilder.JsonObject[] chartData = - customCharts.stream() - .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) - .filter(Objects::nonNull) - .toArray(JsonObjectBuilder.JsonObject[]::new); - serviceJsonBuilder.appendField("id", serviceId); - serviceJsonBuilder.appendField("customCharts", chartData); - baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); - baseJsonBuilder.appendField("serverUUID", serverUuid); - baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); - JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); - scheduler.execute( - () -> { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logErrors) { - errorLogger.accept("Could not submit bStats metrics data", e); - } - } - }); - } - - private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { - if (logSentData) { - infoLogger.accept("Sent bStats metrics data: " + data.toString()); - } - String url = String.format(REPORT_URL, platform); - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", "Metrics-Service/1"); - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - if (logResponseStatusText) { - infoLogger.accept("Sent data to bStats and received response: " + builder); - } - } - - /** - * Checks that the class was properly relocated. - */ - private void checkRelocation() { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null - || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this little - // "trick" ... :D - final String defaultPackage = - new String(new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); - final String examplePackage = - new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure no one just copy & pastes the example and uses the wrong package - // names - if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) - || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - } - - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - valuesBuilder.appendField(entry.getKey(), new int[]{entry.getValue()}); - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public abstract static class CustomChart { - - private final String chartId; - - protected CustomChart(String chartId) { - if (chartId == null) { - throw new IllegalArgumentException("chartId must not be null"); - } - this.chartId = chartId; - } - - public JsonObjectBuilder.JsonObject getRequestJsonObject( - BiConsumer errorLogger, boolean logErrors) { - JsonObjectBuilder builder = new JsonObjectBuilder(); - builder.appendField("chartId", chartId); - try { - JsonObjectBuilder.JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - builder.appendField("data", data); - } catch (Throwable t) { - if (logErrors) { - errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return builder.build(); - } - - protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; - } - - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - /** - * An extremely simple JSON builder. - * - *

While this class is neither feature-rich nor the most performant one, it's sufficient enough - * for its use-case. - */ - public static class JsonObjectBuilder { - - private StringBuilder builder = new StringBuilder(); - - private boolean hasAtLeastOneField = false; - - public JsonObjectBuilder() { - builder.append("{"); - } - - /** - * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. - * - *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. - * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). - * - * @param value The value to escape. - * @return The escaped value. - */ - private static String escape(String value) { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '"') { - builder.append("\\\""); - } else if (c == '\\') { - builder.append("\\\\"); - } else if (c <= '\u000F') { - builder.append("\\u000").append(Integer.toHexString(c)); - } else if (c <= '\u001F') { - builder.append("\\u00").append(Integer.toHexString(c)); - } else { - builder.append(c); - } - } - return builder.toString(); - } - - /** - * Appends a null field to the JSON. - * - * @param key The key of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendNull(String key) { - appendFieldUnescaped(key, "null"); - return this; - } - - /** - * Appends a string field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String value) { - if (value == null) { - throw new IllegalArgumentException("JSON value must not be null"); - } - appendFieldUnescaped(key, "\"" + escape(value) + "\""); - return this; - } - - /** - * Appends an integer field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int value) { - appendFieldUnescaped(key, String.valueOf(value)); - return this; - } - - /** - * Appends an object to the JSON. - * - * @param key The key of the field. - * @param object The object. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject object) { - if (object == null) { - throw new IllegalArgumentException("JSON object must not be null"); - } - appendFieldUnescaped(key, object.toString()); - return this; - } - - /** - * Appends a string array to the JSON. - * - * @param key The key of the field. - * @param values The string array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values) - .map(value -> "\"" + escape(value) + "\"") - .collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an integer array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an object array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends a field to the object. - * - * @param key The key of the field. - * @param escapedValue The escaped value of the field. - */ - private void appendFieldUnescaped(String key, String escapedValue) { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - if (key == null) { - throw new IllegalArgumentException("JSON key must not be null"); - } - if (hasAtLeastOneField) { - builder.append(","); - } - builder.append("\"").append(escape(key)).append("\":").append(escapedValue); - hasAtLeastOneField = true; - } - - /** - * Builds the JSON string and invalidates this builder. - * - * @return The built JSON string. - */ - public JsonObject build() { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - JsonObject object = new JsonObject(builder.append("}").toString()); - builder = null; - return object; - } - - /** - * A super simple representation of a JSON object. - * - *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not - * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, - * JsonObject)}. - */ - public static class JsonObject { - - private final String value; - - private JsonObject(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/MortarCommand.java b/src/main/java/com/volmit/adapt/util/MortarCommand.java deleted file mode 100644 index 7d72a54c3..000000000 --- a/src/main/java/com/volmit/adapt/util/MortarCommand.java +++ /dev/null @@ -1,218 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KList; -import org.bukkit.Sound; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; - -/** - * Represents a pawn command - * - * @author cyberpwn - */ -public abstract class MortarCommand implements ICommand { - private final KList children; - private final KList nodes; - private final KList requiredPermissions; - private final String node; - private String category; - private String description; - - /** - * Override this with a super constructor as most commands shouldn't change - * these parameters - * - * @param node the node (primary node) i.e. volume - * @param nodes the aliases. i.e. v, vol, bile - */ - public MortarCommand(String node, String... nodes) { - category = ""; - this.node = node; - this.nodes = new KList<>(); - this.nodes.add(nodes); - requiredPermissions = new KList<>(); - children = buildChildren(); - description = "No Description"; - } - - @Override - public List handleTab(MortarSender sender, String[] args) { - List v = new ArrayList<>(); - if (args.length == 0) { - for (MortarCommand i : getChildren()) { - v.add(i.getNode()); - } - } - - addTabOptions(sender, args, v); - - if (v.isEmpty()) { - return null; - } - - if (sender.isPlayer()) { - SoundPlayer spw = SoundPlayer.of(sender.player().getWorld()); - spw.play(sender.player().getLocation(), Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 0.25f, 1.7f); - } - - return v; - } - - public abstract void addTabOptions(MortarSender sender, String[] args, List list); - - public void printHelp(MortarSender sender) { - boolean b = false; - - for (MortarCommand i : getChildren()) { - for (String j : i.getRequiredPermissions()) { - if (!sender.hasPermission(j)) { - continue; - } - } - - b = true; - - sender.sendMessage(C.GREEN + i.getNode() + " " + C.WHITE + i.getArgsUsage() + C.GRAY + " - " + i.getDescription()); - } - - if (!b) { - sender.sendMessage("There are either no sub-commands or you do not have permission to use them."); - } - - if (sender.isPlayer()) { - SoundPlayer spw = SoundPlayer.of(sender.player().getWorld()); - spw.play(sender.player().getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 0.28f, 1.4f); - spw.play(sender.player().getLocation(), Sound.ITEM_AXE_STRIP, 0.35f, 1.7f); - } - } - - protected abstract String getArgsUsage(); - - public String getDescription() { - return description; - } - - protected void setDescription(String description) { - this.description = description; - } - - protected void requiresPermission(MortarPermission node) { - if (node == null) { - return; - } - - requiresPermission(node.toString()); - } - - protected void requiresPermission(String node) { - if (node == null) { - return; - } - - requiredPermissions.add(node); - } - - public void rejectAny(int past, MortarSender sender, String[] a) { - if (a.length > past) { - int p = past; - - String m = ""; - - for (String i : a) { - p--; - if (p < 0) { - m += i + ", "; - } - } - - if (!m.trim().isEmpty()) { - sender.sendMessage("Parameters Ignored: " + m); - } - } - } - - @Override - public String getNode() { - return node; - } - - @Override - public KList getNodes() { - return nodes; - } - - @Override - public KList getAllNodes() { - return getNodes().copy().qadd(getNode()); - } - - @Override - public void addNode(String node) { - getNodes().add(node); - } - - public List getChildren() { - return children; - } - - private KList buildChildren() { - KList p = new KList<>(); - - for (Field i : getClass().getDeclaredFields()) { - if (i.isAnnotationPresent(Command.class)) { - try { - i.setAccessible(true); - MortarCommand pc = (MortarCommand) i.getType().getConstructor().newInstance(); - Command c = i.getAnnotation(Command.class); - - if (!c.value().trim().isEmpty()) { - pc.setCategory(c.value().trim()); - } else { - pc.setCategory(getCategory()); - } - - p.add(pc); - } catch (IllegalArgumentException | IllegalAccessException | InstantiationException | - InvocationTargetException | NoSuchMethodException | SecurityException e) { - e.printStackTrace(); - } - } - } - - return p; - } - - @Override - public List getRequiredPermissions() { - return requiredPermissions; - } - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } -} diff --git a/src/main/java/com/volmit/adapt/util/MortarPermission.java b/src/main/java/com/volmit/adapt/util/MortarPermission.java deleted file mode 100644 index ecaf27e30..000000000 --- a/src/main/java/com/volmit/adapt/util/MortarPermission.java +++ /dev/null @@ -1,97 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.command.CommandSender; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; - -public abstract class MortarPermission { - private MortarPermission parent; - - public MortarPermission() { - for (Field i : getClass().getDeclaredFields()) { - if (i.isAnnotationPresent(Permission.class)) { - try { - MortarPermission px = (MortarPermission) i.getType().getConstructor().newInstance(); - px.setParent(this); - i.set(Modifier.isStatic(i.getModifiers()) ? null : this, px); - } catch (IllegalArgumentException | IllegalAccessException | InstantiationException | - InvocationTargetException | NoSuchMethodException | SecurityException e) { - e.printStackTrace(); - } - } - } - } - - public List getChildren() { - List p = new ArrayList<>(); - - for (Field i : getClass().getDeclaredFields()) { - if (i.isAnnotationPresent(Permission.class)) { - try { - p.add((MortarPermission) i.get(Modifier.isStatic(i.getModifiers()) ? null : this)); - } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) { - e.printStackTrace(); - } - } - } - - return p; - } - - public String getFullNode() { - if (hasParent()) { - return getParent().getFullNode() + "." + getNode(); - } - - return getNode(); - } - - protected abstract String getNode(); - - public abstract String getDescription(); - - public abstract boolean isDefault(); - - @Override - public String toString() { - return getFullNode(); - } - - public boolean hasParent() { - return getParent() != null; - } - - public MortarPermission getParent() { - return parent; - } - - public void setParent(MortarPermission parent) { - this.parent = parent; - } - - public boolean has(CommandSender sender) { - return sender.hasPermission(getFullNode()); - } -} diff --git a/src/main/java/com/volmit/adapt/util/MortarSender.java b/src/main/java/com/volmit/adapt/util/MortarSender.java deleted file mode 100644 index 505ea08be..000000000 --- a/src/main/java/com/volmit/adapt/util/MortarSender.java +++ /dev/null @@ -1,213 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import lombok.Getter; -import lombok.Setter; -import org.bukkit.Server; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionAttachment; -import org.bukkit.permissions.PermissionAttachmentInfo; -import org.bukkit.plugin.Plugin; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Set; -import java.util.UUID; - -/** - * Represents a volume sender. A command sender with extra crap in it - * - * @author cyberpwn - */ -public class MortarSender implements CommandSender { - private final CommandSender s; - private String tag; - - @Getter - @Setter - private String command; - - /** - * Wrap a command sender - * - * @param s the command sender - */ - public MortarSender(CommandSender s) { - tag = ""; - this.s = s; - } - - public MortarSender(CommandSender s, String tag) { - this.tag = tag; - this.s = s; - } - - /** - * Get the command tag - * - * @return the command tag - */ - public String getTag() { - return tag; - } - - /** - * Set a command tag (prefix for sendMessage) - * - * @param tag the tag - */ - public void setTag(String tag) { - this.tag = tag; - } - - /** - * Is this sender a player? - * - * @return true if it is - */ - public boolean isPlayer() { - return getS() instanceof Player; - } - - /** - * Force cast to player (be sure to check first) - * - * @return a casted player - */ - public Player player() { - return (Player) getS(); - } - - /** - * Get the origin sender this object is wrapping - * - * @return the command sender - */ - public CommandSender getS() { - return s; - } - - @Override - public boolean isPermissionSet(String name) { - return s.isPermissionSet(name); - } - - @Override - public boolean isPermissionSet(Permission perm) { - return s.isPermissionSet(perm); - } - - @Override - public boolean hasPermission(String name) { - return s.hasPermission(name); - } - - @Override - public boolean hasPermission(Permission perm) { - return s.hasPermission(perm); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { - return s.addAttachment(plugin, name, value); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin) { - return s.addAttachment(plugin); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { - return s.addAttachment(plugin, name, value, ticks); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, int ticks) { - return s.addAttachment(plugin, ticks); - } - - @Override - public void removeAttachment(PermissionAttachment attachment) { - s.removeAttachment(attachment); - } - - @Override - public void recalculatePermissions() { - s.recalculatePermissions(); - } - - @Override - public Set getEffectivePermissions() { - return s.getEffectivePermissions(); - } - - @Override - public boolean isOp() { - return s.isOp(); - } - - @Override - public void setOp(boolean value) { - s.setOp(value); - } - - public void hr() { - s.sendMessage("========================================================"); - } - - @Override - public void sendMessage(String message) { - s.sendMessage(C.translateAlternateColorCodes('&', getTag()) + message); - } - - @Override - public void sendMessage(String[] messages) { - for (String str : messages) - s.sendMessage(C.translateAlternateColorCodes('&', getTag() + str)); - } - - @Override - public void sendMessage(@Nullable UUID sender, @NotNull String message) { - s.sendMessage(sender, message); - } - - @Override - public void sendMessage(@Nullable UUID sender, @NotNull String... messages) { - s.sendMessage(sender, messages); - } - - @Override - public Server getServer() { - return s.getServer(); - } - - @Override - public String getName() { - return s.getName(); - } - - @Override - public Spigot spigot() { - return s.spigot(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/MultiBurst.java b/src/main/java/com/volmit/adapt/util/MultiBurst.java deleted file mode 100644 index 81f7144b3..000000000 --- a/src/main/java/com/volmit/adapt/util/MultiBurst.java +++ /dev/null @@ -1,70 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import lombok.Getter; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public class MultiBurst { - public static MultiBurst burst = new MultiBurst(Runtime.getRuntime().availableProcessors()); - @Getter - private final ExecutorService service; - private int tid; - - public MultiBurst(int tc) { - service = Executors.newFixedThreadPool(tc, r -> { - tid++; - Thread t = new Thread(r); - t.setName("Adapt Workgroup " + tid); - t.setPriority(Thread.MAX_PRIORITY); - t.setUncaughtExceptionHandler((et, e) -> - { - Adapt.info("Exception encountered in " + et.getName()); - e.printStackTrace(); - }); - - return t; - }); - } - - public void burst(Runnable... r) { - burst(r.length).queue(r).complete(); - } - - public void sync(Runnable... r) { - for (Runnable i : r) { - i.run(); - } - } - - public BurstExecutor burst(int estimate) { - return new BurstExecutor(service, estimate); - } - - public BurstExecutor burst() { - return burst(16); - } - - public void lazy(Runnable o) { - service.execute(o); - } -} diff --git a/src/main/java/com/volmit/adapt/util/NBTConstants.java b/src/main/java/com/volmit/adapt/util/NBTConstants.java deleted file mode 100644 index ec3538afe..000000000 --- a/src/main/java/com/volmit/adapt/util/NBTConstants.java +++ /dev/null @@ -1,63 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -/** - * Changes : Neil Wightman - Support 19133 Tag_Int_Array tag - */ - -/** - * A class which holds constant values. - * - * @author Graham Edgecombe - */ -public final class NBTConstants { - - /** - * The character set used by NBT (UTF-8). - */ - public static final Charset CHARSET = StandardCharsets.UTF_8; - - /** - * Tag type constants. - */ - public static final int TYPE_END = 0, - TYPE_BYTE = 1, - TYPE_SHORT = 2, - TYPE_INT = 3, - TYPE_LONG = 4, - TYPE_FLOAT = 5, - TYPE_DOUBLE = 6, - TYPE_BYTE_ARRAY = 7, - TYPE_STRING = 8, - TYPE_LIST = 9, - TYPE_COMPOUND = 10, - TYPE_INT_ARRAY = 11; - - /** - * Default private constructor. - */ - private NBTConstants() { - - } - -} diff --git a/src/main/java/com/volmit/adapt/util/NBTInputStream.java b/src/main/java/com/volmit/adapt/util/NBTInputStream.java deleted file mode 100644 index c0643a33f..000000000 --- a/src/main/java/com/volmit/adapt/util/NBTInputStream.java +++ /dev/null @@ -1,191 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.Closeable; -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.zip.GZIPInputStream; - -/** - * Changes : - * Neil Wightman - Support 19133 Tag_Int_Array tag - */ - -/** - *

- * This class reads NBT, or - * Named Binary Tag streams, and produces an object graph of subclasses of the Tag - * object.

- * - *

- * The NBT format was created by Markus Persson, and the specification may be found at - * http://www.minecraft.net/docs/NBT.txt.

- * - * @author Graham Edgecombe - */ -public final class NBTInputStream implements Closeable { - - /** - * The data input stream. - */ - private final DataInputStream is; - - /** - * Create a new NBTInputStream, which will source its data from the specified input stream. - * - * @param is The output stream - */ - public NBTInputStream(DataInputStream is) { - this.is = is; - } - - /** - * Creates a new NBTInputStream, which will source its data from the specified input stream. - * The stream will be decompressed using GZIP. - * - * @param is The input stream. - * @throws IOException if an I/O error occurs. - */ - public NBTInputStream(InputStream is) throws IOException { - this.is = new DataInputStream(new GZIPInputStream(is)); - } - - /** - * Reads an NBT tag from the stream. - * - * @return The tag that was read. - * @throws IOException if an I/O error occurs. - */ - public Tag readTag() throws IOException { - return readTag(0); - } - - /** - * Reads an NBT from the stream. - * - * @param depth The depth of this tag. - * @return The tag that was read. - * @throws IOException if an I/O error occurs. - */ - private Tag readTag(int depth) throws IOException { - int type = is.readByte() & 0xFF; - - String name; - if (type != NBTConstants.TYPE_END) { - int nameLength = is.readShort() & 0xFFFF; - byte[] nameBytes = new byte[nameLength]; - is.readFully(nameBytes); - name = new String(nameBytes, NBTConstants.CHARSET); - } else { - name = ""; - } - - return readTagPayload(type, name, depth); - } - - /** - * Reads the payload of a tag, given the name and type. - * - * @param type The type. - * @param name The name. - * @param depth The depth. - * @return The tag. - * @throws IOException if an I/O error occurs. - */ - private Tag readTagPayload(int type, String name, int depth) throws IOException { - switch (type) { - case NBTConstants.TYPE_END: - if (depth == 0) { - throw new IOException("TAG_End found without a TAG_Compound/TAG_List tag preceding it."); - } else { - return new EndTag(); - } - case NBTConstants.TYPE_BYTE: - return new ByteTag(name, is.readByte()); - case NBTConstants.TYPE_SHORT: - return new ShortTag(name, is.readShort()); - case NBTConstants.TYPE_INT: - return new IntTag(name, is.readInt()); - case NBTConstants.TYPE_LONG: - return new LongTag(name, is.readLong()); - case NBTConstants.TYPE_FLOAT: - return new FloatTag(name, is.readFloat()); - case NBTConstants.TYPE_DOUBLE: - return new DoubleTag(name, is.readDouble()); - case NBTConstants.TYPE_BYTE_ARRAY: - int length = is.readInt(); - byte[] bytes = new byte[length]; - is.readFully(bytes); - return new ByteArrayTag(name, bytes); - case NBTConstants.TYPE_STRING: - length = is.readShort(); - bytes = new byte[length]; - is.readFully(bytes); - return new StringTag(name, new String(bytes, NBTConstants.CHARSET)); - case NBTConstants.TYPE_LIST: - int childType = is.readByte(); - length = is.readInt(); - - List tagList = new ArrayList(); - for (int i = 0; i < length; i++) { - Tag tag = readTagPayload(childType, "", depth + 1); - if (tag instanceof EndTag) { - throw new IOException("TAG_End not permitted in a list."); - } - tagList.add(tag); - } - - return new ListTag(name, NBTUtils.getTypeClass(childType), tagList); - case NBTConstants.TYPE_COMPOUND: - Map tagMap = new HashMap(); - while (true) { - Tag tag = readTag(depth + 1); - if (tag instanceof EndTag) { - break; - } else { - tagMap.put(tag.getName(), tag); - } - } - - return new CompoundTag(name, tagMap); - case NBTConstants.TYPE_INT_ARRAY: - length = is.readInt(); - int[] value = new int[length]; - for (int i = 0; i < length; i++) { - value[i] = is.readInt(); - } - return new IntArrayTag(name, value); - default: - throw new IOException("Invalid tag type: " + type + "."); - } - } - - @Override - public void close() throws IOException { - is.close(); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/NBTOutputStream.java b/src/main/java/com/volmit/adapt/util/NBTOutputStream.java deleted file mode 100644 index 8cb0d4975..000000000 --- a/src/main/java/com/volmit/adapt/util/NBTOutputStream.java +++ /dev/null @@ -1,287 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.Closeable; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; -import java.util.zip.GZIPOutputStream; -/** - * Changes : Neil Wightman - Support 19133 Tag_Int_Array tag - */ - -/** - *

- * This class writes NBT, or - * Named Binary Tag Tag objects to an underlying OutputStream.

- * - *

- * The NBT format was created by Markus Persson, and the specification may be found at - * http://www.minecraft.net/docs/NBT.txt.

- * - * @author Graham Edgecombe - */ -public final class NBTOutputStream implements Closeable { - - /** - * The output stream. - */ - private final DataOutputStream os; - - /** - * Create a new NBTOutputStream, which will write data to the specified underlying output stream. - * - * @param os The output stream - */ - public NBTOutputStream(DataOutputStream os) { - this.os = os; - } - - /** - * Creates a new NBTOutputStream, which will write data to the specified underlying output stream. - * the stream will be compressed using GZIP. - * - * @param os The output stream. - * @throws IOException if an I/O error occurs. - */ - public NBTOutputStream(OutputStream os) throws IOException { - this.os = new DataOutputStream(new GZIPOutputStream(os)); - } - - /** - * Writes a tag. - * - * @param tag The tag to write. - * @throws IOException if an I/O error occurs. - */ - public void writeTag(Tag tag) throws IOException { - int type = NBTUtils.getTypeCode(tag.getClass()); - String name = tag.getName(); - byte[] nameBytes = name.getBytes(NBTConstants.CHARSET); - - os.writeByte(type); - os.writeShort(nameBytes.length); - os.write(nameBytes); - - if (type == NBTConstants.TYPE_END) { - throw new IOException("Named TAG_End not permitted."); - } - - writeTagPayload(tag); - } - - /** - * Writes tag payload. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeTagPayload(Tag tag) throws IOException { - int type = NBTUtils.getTypeCode(tag.getClass()); - switch (type) { - case NBTConstants.TYPE_END: - writeEndTagPayload((EndTag) tag); - break; - case NBTConstants.TYPE_BYTE: - writeByteTagPayload((ByteTag) tag); - break; - case NBTConstants.TYPE_SHORT: - writeShortTagPayload((ShortTag) tag); - break; - case NBTConstants.TYPE_INT: - writeIntTagPayload((IntTag) tag); - break; - case NBTConstants.TYPE_LONG: - writeLongTagPayload((LongTag) tag); - break; - case NBTConstants.TYPE_FLOAT: - writeFloatTagPayload((FloatTag) tag); - break; - case NBTConstants.TYPE_DOUBLE: - writeDoubleTagPayload((DoubleTag) tag); - break; - case NBTConstants.TYPE_BYTE_ARRAY: - writeByteArrayTagPayload((ByteArrayTag) tag); - break; - case NBTConstants.TYPE_STRING: - writeStringTagPayload((StringTag) tag); - break; - case NBTConstants.TYPE_LIST: - writeListTagPayload((ListTag) tag); - break; - case NBTConstants.TYPE_COMPOUND: - writeCompoundTagPayload((CompoundTag) tag); - break; - case NBTConstants.TYPE_INT_ARRAY: - writeIntArrayTagPayload((IntArrayTag) tag); - break; - default: - throw new IOException("Invalid tag type: " + type + "."); - } - } - - /** - * Writes a TAG_Byte tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeByteTagPayload(ByteTag tag) throws IOException { - os.writeByte(tag.getValue()); - } - - /** - * Writes a TAG_Byte_Array tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeByteArrayTagPayload(ByteArrayTag tag) throws IOException { - byte[] bytes = tag.getValue(); - os.writeInt(bytes.length); - os.write(bytes); - } - - - /** - * Writes a TAG_Compound tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeCompoundTagPayload(CompoundTag tag) throws IOException { - for (Tag childTag : tag.getValue().values()) { - writeTag(childTag); - } - os.writeByte((byte) 0); // end tag - better way? - } - - /** - * Writes a TAG_List tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeListTagPayload(ListTag tag) throws IOException { - Class clazz = tag.getType(); - List tags = tag.getValue(); - int size = tags.size(); - - os.writeByte(NBTUtils.getTypeCode(clazz)); - os.writeInt(size); - for (int i = 0; i < size; i++) { - writeTagPayload(tags.get(i)); - } - } - - /** - * Writes a TAG_String tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeStringTagPayload(StringTag tag) throws IOException { - byte[] bytes = tag.getValue().getBytes(NBTConstants.CHARSET); - os.writeShort(bytes.length); - os.write(bytes); - } - - /** - * Writes a TAG_Double tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeDoubleTagPayload(DoubleTag tag) throws IOException { - os.writeDouble(tag.getValue()); - } - - /** - * Writes a TAG_Float tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeFloatTagPayload(FloatTag tag) throws IOException { - os.writeFloat(tag.getValue()); - } - - /** - * Writes a TAG_Long tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeLongTagPayload(LongTag tag) throws IOException { - os.writeLong(tag.getValue()); - } - - /** - * Writes a TAG_Int tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeIntTagPayload(IntTag tag) throws IOException { - os.writeInt(tag.getValue()); - } - - /** - * Writes a TAG_Short tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeShortTagPayload(ShortTag tag) throws IOException { - os.writeShort(tag.getValue()); - } - - /** - * Writes a TAG_Empty tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeEndTagPayload(EndTag tag) { - /* empty */ - } - - /** - * Writes a TAG_Int_Array tag. - * - * @param tag The tag. - * @throws IOException if an I/O error occurs. - */ - private void writeIntArrayTagPayload(IntArrayTag tag) throws IOException { - final int[] values = tag.getValue(); - os.writeInt(values.length); - for (final int value : values) { - os.writeInt(value); - } - } - - @Override - public void close() throws IOException { - os.close(); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/NBTUtils.java b/src/main/java/com/volmit/adapt/util/NBTUtils.java deleted file mode 100644 index 0c9ae9495..000000000 --- a/src/main/java/com/volmit/adapt/util/NBTUtils.java +++ /dev/null @@ -1,151 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Changes : Neil Wightman - Support 19133 Tag_Int_Array tag - */ - -/** - * A class which contains NBT-related utility methods. This currently supports reading 19133 but only writing - * 19132. - * - * @author Graham Edgecombe - */ -public final class NBTUtils { - - /** - * Default private constructor. - */ - private NBTUtils() { - - } - - /** - * Gets the type name of a tag. - * - * @param clazz The tag class. - * @return The type name. - */ - public static String getTypeName(Class clazz) { - if (clazz.equals(ByteArrayTag.class)) { - return "TAG_Byte_Array"; - } else if (clazz.equals(ByteTag.class)) { - return "TAG_Byte"; - } else if (clazz.equals(CompoundTag.class)) { - return "TAG_Compound"; - } else if (clazz.equals(DoubleTag.class)) { - return "TAG_Double"; - } else if (clazz.equals(EndTag.class)) { - return "TAG_End"; - } else if (clazz.equals(FloatTag.class)) { - return "TAG_Float"; - } else if (clazz.equals(IntTag.class)) { - return "TAG_Int"; - } else if (clazz.equals(ListTag.class)) { - return "TAG_List"; - } else if (clazz.equals(LongTag.class)) { - return "TAG_Long"; - } else if (clazz.equals(ShortTag.class)) { - return "TAG_Short"; - } else if (clazz.equals(StringTag.class)) { - return "TAG_String"; - } else if (clazz.equals(IntArrayTag.class)) { - return "TAG_Int_Array"; - } else { - throw new IllegalArgumentException("Invalid tag classs (" + clazz.getName() + ")."); - } - } - - /** - * Gets the type code of a tag class. - * - * @param clazz The tag class. - * @return The type code. - * @throws IllegalArgumentException if the tag class is invalid. - */ - public static int getTypeCode(Class clazz) { - if (clazz.equals(ByteArrayTag.class)) { - return NBTConstants.TYPE_BYTE_ARRAY; - } else if (clazz.equals(ByteTag.class)) { - return NBTConstants.TYPE_BYTE; - } else if (clazz.equals(CompoundTag.class)) { - return NBTConstants.TYPE_COMPOUND; - } else if (clazz.equals(DoubleTag.class)) { - return NBTConstants.TYPE_DOUBLE; - } else if (clazz.equals(EndTag.class)) { - return NBTConstants.TYPE_END; - } else if (clazz.equals(FloatTag.class)) { - return NBTConstants.TYPE_FLOAT; - } else if (clazz.equals(IntTag.class)) { - return NBTConstants.TYPE_INT; - } else if (clazz.equals(ListTag.class)) { - return NBTConstants.TYPE_LIST; - } else if (clazz.equals(LongTag.class)) { - return NBTConstants.TYPE_LONG; - } else if (clazz.equals(ShortTag.class)) { - return NBTConstants.TYPE_SHORT; - } else if (clazz.equals(StringTag.class)) { - return NBTConstants.TYPE_STRING; - } else if (clazz.equals(IntArrayTag.class)) { - return NBTConstants.TYPE_INT_ARRAY; - } else { - throw new IllegalArgumentException("Invalid tag classs (" + clazz.getName() + ")."); - } - } - - /** - * Gets the class of a type of tag. - * - * @param type The type. - * @return The class. - * @throws IllegalArgumentException if the tag type is invalid. - */ - public static Class getTypeClass(int type) { - switch (type) { - case NBTConstants.TYPE_END: - return EndTag.class; - case NBTConstants.TYPE_BYTE: - return ByteTag.class; - case NBTConstants.TYPE_SHORT: - return ShortTag.class; - case NBTConstants.TYPE_INT: - return IntTag.class; - case NBTConstants.TYPE_LONG: - return LongTag.class; - case NBTConstants.TYPE_FLOAT: - return FloatTag.class; - case NBTConstants.TYPE_DOUBLE: - return DoubleTag.class; - case NBTConstants.TYPE_BYTE_ARRAY: - return ByteArrayTag.class; - case NBTConstants.TYPE_STRING: - return StringTag.class; - case NBTConstants.TYPE_LIST: - return ListTag.class; - case NBTConstants.TYPE_COMPOUND: - return CompoundTag.class; - case NBTConstants.TYPE_INT_ARRAY: - return IntArrayTag.class; - default: - throw new IllegalArgumentException("Invalid tag type : " + type + "."); - } - } - -} diff --git a/src/main/java/com/volmit/adapt/util/NMSVersion.java b/src/main/java/com/volmit/adapt/util/NMSVersion.java deleted file mode 100644 index 8dc8d22e3..000000000 --- a/src/main/java/com/volmit/adapt/util/NMSVersion.java +++ /dev/null @@ -1,138 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.ArrayList; -import java.util.List; - -public enum NMSVersion { - R1_16, - R1_15, - R1_14, - R1_13, - R1_13_1, - R1_12, - R1_11, - R1_10, - R1_9_4, - R1_9_2, - R1_8; - - public static NMSVersion getMinimum() { - return values()[values().length - 1]; - } - - public static NMSVersion getMaximum() { - return values()[0]; - } - - public static NMSVersion current() { - if (tryVersion("1_8_R3")) { - return R1_8; - } - - if (tryVersion("1_9_R1")) { - return R1_9_2; - } - - if (tryVersion("1_9_R2")) { - return R1_9_4; - } - - if (tryVersion("1_10_R1")) { - return R1_10; - } - - if (tryVersion("1_11_R1")) { - return R1_11; - } - - if (tryVersion("1_12_R1")) { - return R1_12; - } - - if (tryVersion("1_13_R1")) { - return R1_13; - } - - if (tryVersion("1_13_R2")) { - return R1_13_1; - } - - if (tryVersion("1_14_R1")) { - return R1_14; - } - - if (tryVersion("1_15_R1")) { - return R1_15; - } - - if (tryVersion("1_16_R1")) { - return R1_16; - } - return null; - } - - private static boolean tryVersion(String v) { - try { - Class.forName("org.bukkit.craftbukkit.v" + v + ".CraftWorld"); - return true; - } catch (Throwable e) { - - } - - return false; - } - - public List getAboveInclusive() { - List n = new ArrayList<>(); - - for (NMSVersion i : values()) { - if (i.ordinal() >= ordinal()) { - n.add(i); - } - } - - return n; - } - - public List betweenInclusive(NMSVersion other) { - List n = new ArrayList<>(); - - for (NMSVersion i : values()) { - if (i.ordinal() <= Math.max(other.ordinal(), ordinal()) && i.ordinal() >= Math.min(ordinal(), other.ordinal())) { - n.add(i); - } - } - - return n; - } - - public List getBelowInclusive() { - List n = new ArrayList<>(); - - for (NMSVersion i : values()) { - if (i.ordinal() <= ordinal()) { - n.add(i); - } - } - - return n; - } -} diff --git a/src/main/java/com/volmit/adapt/util/NastyFunction.java b/src/main/java/com/volmit/adapt/util/NastyFunction.java deleted file mode 100644 index 3c54f1767..000000000 --- a/src/main/java/com/volmit/adapt/util/NastyFunction.java +++ /dev/null @@ -1,23 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public interface NastyFunction { - R run(T t) throws Throwable; -} diff --git a/src/main/java/com/volmit/adapt/util/NastyFuture.java b/src/main/java/com/volmit/adapt/util/NastyFuture.java deleted file mode 100644 index 4826af9a1..000000000 --- a/src/main/java/com/volmit/adapt/util/NastyFuture.java +++ /dev/null @@ -1,23 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public interface NastyFuture { - R run() throws Throwable; -} diff --git a/src/main/java/com/volmit/adapt/util/NastyRunnable.java b/src/main/java/com/volmit/adapt/util/NastyRunnable.java deleted file mode 100644 index 82d6d0b39..000000000 --- a/src/main/java/com/volmit/adapt/util/NastyRunnable.java +++ /dev/null @@ -1,23 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public interface NastyRunnable { - void run() throws Throwable; -} diff --git a/src/main/java/com/volmit/adapt/util/NibbleArray.java b/src/main/java/com/volmit/adapt/util/NibbleArray.java deleted file mode 100644 index d4507f51f..000000000 --- a/src/main/java/com/volmit/adapt/util/NibbleArray.java +++ /dev/null @@ -1,195 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.StringJoiner; - -public class NibbleArray implements Writable { - private static final int[] MASKS = new int[8]; - - static { - for (int i = 0; i < MASKS.length; i++) { - MASKS[i] = maskFor(i); - } - } - - private final int size; - private final Object lock = new Object(); - private byte[] data; - private int depth; - private byte mask; - private transient int bitIndex, byteIndex, bitInByte; - - public NibbleArray(int capacity, DataInputStream in) throws IOException { - size = capacity; - read(in); - } - - public NibbleArray(int nibbleDepth, int capacity) { - if (nibbleDepth > 8 || nibbleDepth < 1) { - throw new IllegalArgumentException(); - } - - int neededBits = nibbleDepth * capacity; - - size = capacity; - depth = nibbleDepth; - data = new byte[(neededBits + neededBits % 8) / 8]; - mask = (byte) maskFor(nibbleDepth); - } - - public NibbleArray(int nibbleDepth, int capacity, NibbleArray existing) { - if (nibbleDepth > 8 || nibbleDepth < 1) { - throw new IllegalArgumentException(); - } - - int neededBits = nibbleDepth * capacity; - size = capacity; - depth = nibbleDepth; - data = new byte[(neededBits + neededBits % 8) / 8]; - mask = (byte) maskFor(nibbleDepth); - - for (int i = 0; i < Math.min(size, existing.size()); i++) { - set(i, existing.get(i)); - } - } - - public static int maskFor(int amountOfBits) { - return powerOfTwo(amountOfBits) - 1; - } - - public static int powerOfTwo(int power) { - int result = 1; - - for (int i = 0; i < power; i++) { - result *= 2; - } - - return result; - } - - public static String binaryString(byte b, ByteOrder byteOrder) { - String str = String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(' ', '0'); - - return byteOrder.equals(ByteOrder.BIG_ENDIAN) ? str : reverse(str); - } - - public static String reverse(String str) { - return new StringBuilder(str).reverse().toString(); - } - - @Override - public void write(DataOutputStream o) throws IOException { - o.writeByte(depth + Byte.MIN_VALUE); - o.write(data); - } - - @Override - public void read(DataInputStream i) throws IOException { - depth = i.readByte() - Byte.MIN_VALUE; - int neededBits = depth * size; - data = new byte[(neededBits + neededBits % 8) / 8]; - mask = (byte) maskFor(depth); - i.read(data); - } - - public int size() { - return size; - } - - public byte get(int index) { - synchronized (lock) { - bitIndex = index * depth; - byteIndex = bitIndex >> 3; - bitInByte = bitIndex & 7; - int value = data[byteIndex] >> bitInByte; - - if (bitInByte + depth > 8) { - value |= data[byteIndex + 1] << bitInByte; - } - - return (byte) (value & mask); - } - } - - public byte getAsync(int index) { - int bitIndex = index * depth; - int byteIndex = bitIndex >> 3; - int bitInByte = bitIndex & 7; - int value = data[byteIndex] >> bitInByte; - - if (bitInByte + depth > 8) { - value |= data[byteIndex + 1] << bitInByte; - } - - return (byte) (value & mask); - } - - public void set(int index, int nibble) { - set(index, (byte) nibble); - } - - public void set(int index, byte nybble) { - synchronized (lock) { - bitIndex = index * depth; - byteIndex = bitIndex >> 3; - bitInByte = bitIndex & 7; - data[byteIndex] = (byte) (((~(data[byteIndex] & (mask << bitInByte)) & data[byteIndex]) | ((nybble & mask) << bitInByte)) & 0xff); - - if (bitInByte + depth > 8) { - data[byteIndex + 1] = (byte) (((~(data[byteIndex + 1] & MASKS[bitInByte + depth - 8]) & data[byteIndex + 1]) | ((nybble & mask) >> (8 - bitInByte))) & 0xff); - } - } - } - - public String toBitsString() { - return toBitsString(ByteOrder.BIG_ENDIAN); - } - - public String toBitsString(ByteOrder byteOrder) { - StringJoiner joiner = new StringJoiner(" "); - - for (int i = 0; i < data.length; i++) { - joiner.add(binaryString(data[i], byteOrder)); - } - - return joiner.toString(); - } - - public void clear() { - Arrays.fill(data, (byte) 0); - } - - public void setAll(byte nibble) { - for (int i = 0; i < size; i++) { - set(i, nibble); - } - } - - public void setAll(int nibble) { - for (int i = 0; i < size; i++) { - set(i, (byte) nibble); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/NoiseInjector.java b/src/main/java/com/volmit/adapt/util/NoiseInjector.java deleted file mode 100644 index db9f3cec2..000000000 --- a/src/main/java/com/volmit/adapt/util/NoiseInjector.java +++ /dev/null @@ -1,24 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@FunctionalInterface -public interface NoiseInjector { - double[] combine(double src, double value); -} diff --git a/src/main/java/com/volmit/adapt/util/NoiseProvider.java b/src/main/java/com/volmit/adapt/util/NoiseProvider.java deleted file mode 100644 index 41a893e5f..000000000 --- a/src/main/java/com/volmit/adapt/util/NoiseProvider.java +++ /dev/null @@ -1,24 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@FunctionalInterface -public interface NoiseProvider { - double noise(double x, double z); -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/O.java b/src/main/java/com/volmit/adapt/util/O.java deleted file mode 100644 index 50dbf654d..000000000 --- a/src/main/java/com/volmit/adapt/util/O.java +++ /dev/null @@ -1,65 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KList; - -public class O implements Observable { - private T t = null; - private KList> observers; - - @Override - public T get() { - return t; - } - - @Override - public O set(T t) { - T x = t; - this.t = t; - - if (observers != null && observers.isNotEmpty()) { - observers.forEach((o) -> o.onChanged(x, t)); - } - - return this; - } - - @Override - public boolean has() { - return t != null; - } - - @Override - public O clearObservers() { - observers.clear(); - return this; - } - - @Override - public O observe(Observer t) { - if (observers == null) { - observers = new KList<>(); - } - - observers.add(t); - - return this; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Observable.java b/src/main/java/com/volmit/adapt/util/Observable.java deleted file mode 100644 index bf8a2afa9..000000000 --- a/src/main/java/com/volmit/adapt/util/Observable.java +++ /dev/null @@ -1,31 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public interface Observable { - T get(); - - Observable set(T t); - - boolean has(); - - Observable clearObservers(); - - Observable observe(Observer t); -} diff --git a/src/main/java/com/volmit/adapt/util/Observer.java b/src/main/java/com/volmit/adapt/util/Observer.java deleted file mode 100644 index 45da41aae..000000000 --- a/src/main/java/com/volmit/adapt/util/Observer.java +++ /dev/null @@ -1,24 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@FunctionalInterface -public interface Observer { - void onChanged(T from, T to); -} diff --git a/src/main/java/com/volmit/adapt/util/ParticleSender.java b/src/main/java/com/volmit/adapt/util/ParticleSender.java deleted file mode 100644 index ae8852283..000000000 --- a/src/main/java/com/volmit/adapt/util/ParticleSender.java +++ /dev/null @@ -1,122 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Bukkit; -import org.bukkit.Color; -import org.bukkit.Particle; -import org.bukkit.World; -import org.bukkit.block.data.BlockData; -import org.bukkit.entity.Player; -import org.bukkit.material.MaterialData; - -/** - * Particle sender using the Bukkit particles API for 1.9+ servers - * - * @author MrMicky - */ -@SuppressWarnings("deprecation") -interface ParticleSender { - - void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data); - - Object getParticle(ParticleType particle); - - boolean isValidData(Object particle, Object data); - - default double color(double color) { - return color / 255.0; - } - - class ParticleSenderImpl implements ParticleSender { - - @Override - public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data) { - Particle bukkitParticle = Particle.valueOf(particle.toString()); - - if (data instanceof Color) { - if (particle.getDataType() == Color.class) { - Color color = (Color) data; - count = 0; - offsetX = color(color.getRed()); - offsetY = color(color.getGreen()); - offsetZ = color(color.getBlue()); - extra = 1.0; - } - data = null; - } - - if (receiver instanceof World) { - ((World) receiver).spawnParticle(bukkitParticle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } else if (receiver instanceof Player) { - ((Player) receiver).spawnParticle(bukkitParticle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } - } - - @Override - public Particle getParticle(ParticleType particle) { - try { - return Particle.valueOf(particle.toString()); - } catch (IllegalArgumentException e) { - return null; - } - } - - @Override - public boolean isValidData(Object particle, Object data) { - return isValidDataBukkit((Particle) particle, data); - } - - public boolean isValidDataBukkit(Particle particle, Object data) { - return particle.getDataType() == Void.class || particle.getDataType().isInstance(data); - } - } - - class ParticleSender1_13 extends ParticleSenderImpl { - @Override - public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, Object data) { - Particle bukkitParticle = Particle.valueOf(particle.toString()); - - if (bukkitParticle.getDataType() == Particle.DustOptions.class) { - if (data instanceof Color) { - data = new Particle.DustOptions((Color) data, 1); - } else if (data == null) { - data = new Particle.DustOptions(Color.RED, 1); - } - } else if (bukkitParticle.getDataType() == BlockData.class && data instanceof MaterialData) { - data = Bukkit.createBlockData(((MaterialData) data).getItemType()); - } - - super.spawnParticle(receiver, particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data); - } - - @Override - public boolean isValidDataBukkit(Particle particle, Object data) { - if (particle.getDataType() == Particle.DustOptions.class && data instanceof Color) { - return true; - } - - if (particle.getDataType() == BlockData.class && data instanceof MaterialData) { - return true; - } - - return super.isValidDataBukkit(particle, data); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/ParticleSenderLegacy.java b/src/main/java/com/volmit/adapt/util/ParticleSenderLegacy.java deleted file mode 100644 index 1d33b47da..000000000 --- a/src/main/java/com/volmit/adapt/util/ParticleSenderLegacy.java +++ /dev/null @@ -1,184 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Color; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -/** - * Legacy particle sender with NMS for 1.7/1.8 servers - * - * @author MrMicky - */ -@SuppressWarnings("deprecation") -class ParticleSenderLegacy implements ParticleSender { - - private static final boolean SERVER_IS_1_8; - - private static final Constructor PACKET_PARTICLE; - private static final Class ENUM_PARTICLE; - - private static final Method WORLD_GET_HANDLE; - private static final Method WORLD_SEND_PARTICLE; - - private static final Method PLAYER_GET_HANDLE; - private static final Field PLAYER_CONNECTION; - private static final Method SEND_PACKET; - private static final int[] EMPTY = new int[0]; - - static { - ENUM_PARTICLE = FastReflection.nmsOptionalClass("EnumParticle").orElse(null); - SERVER_IS_1_8 = ENUM_PARTICLE != null; - - try { - Class packetParticleClass = FastReflection.nmsClass("PacketPlayOutWorldParticles"); - Class playerClass = FastReflection.nmsClass("EntityPlayer"); - Class playerConnectionClass = FastReflection.nmsClass("PlayerConnection"); - Class worldClass = FastReflection.nmsClass("WorldServer"); - Class entityPlayerClass = FastReflection.nmsClass("EntityPlayer"); - - Class craftPlayerClass = FastReflection.obcClass("entity.CraftPlayer"); - Class craftWorldClass = FastReflection.obcClass("CraftWorld"); - - if (SERVER_IS_1_8) { - PACKET_PARTICLE = packetParticleClass.getConstructor(ENUM_PARTICLE, boolean.class, float.class, - float.class, float.class, float.class, float.class, float.class, float.class, int.class, - int[].class); - WORLD_SEND_PARTICLE = worldClass.getDeclaredMethod("sendParticles", entityPlayerClass, ENUM_PARTICLE, - boolean.class, double.class, double.class, double.class, int.class, double.class, double.class, - double.class, double.class, int[].class); - } else { - PACKET_PARTICLE = packetParticleClass.getConstructor(String.class, float.class, float.class, float.class, - float.class, float.class, float.class, float.class, int.class); - WORLD_SEND_PARTICLE = worldClass.getDeclaredMethod("a", String.class, double.class, double.class, - double.class, int.class, double.class, double.class, double.class, double.class); - } - - WORLD_GET_HANDLE = craftWorldClass.getDeclaredMethod("getHandle"); - PLAYER_GET_HANDLE = craftPlayerClass.getDeclaredMethod("getHandle"); - PLAYER_CONNECTION = playerClass.getField("playerConnection"); - SEND_PACKET = playerConnectionClass.getMethod("sendPacket", FastReflection.nmsClass("Packet")); - } catch (ReflectiveOperationException e) { - throw new ExceptionInInitializerError(e); - } - } - - @Override - public void spawnParticle(Object receiver, ParticleType particle, double x, double y, double z, int count, double offsetX, double offsetY, - double offsetZ, double extra, Object data) { - try { - int[] datas = toData(particle, data); - - if (data instanceof Color) { - if (particle.getDataType() == Color.class) { - Color color = (Color) data; - count = 0; - offsetX = color(color.getRed()); - offsetY = color(color.getGreen()); - offsetZ = color(color.getBlue()); - extra = 1.0; - } - } - - if (receiver instanceof World) { - Object worldServer = WORLD_GET_HANDLE.invoke(receiver); - - if (SERVER_IS_1_8) { - WORLD_SEND_PARTICLE.invoke(worldServer, null, getEnumParticle(particle), true, x, y, z, count, offsetX, offsetY, offsetZ, extra, datas); - } else { - String particleName = particle.getLegacyName() + (datas.length != 2 ? "" : "_" + datas[0] + "_" + datas[1]); - WORLD_SEND_PARTICLE.invoke(worldServer, particleName, x, y, z, count, offsetX, offsetY, offsetZ, extra); - } - } else if (receiver instanceof Player) { - Object packet; - - if (SERVER_IS_1_8) { - packet = PACKET_PARTICLE.newInstance(getEnumParticle(particle), true, (float) x, (float) y, - (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count, datas); - } else { - String particleName = particle.getLegacyName() + (datas.length != 2 ? "" : "_" + datas[0] + "_" + datas[1]); - packet = PACKET_PARTICLE.newInstance(particleName, (float) x, (float) y, (float) z, - (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); - } - - Object entityPlayer = PLAYER_GET_HANDLE.invoke(receiver); - Object playerConnection = PLAYER_CONNECTION.get(entityPlayer); - SEND_PACKET.invoke(playerConnection, packet); - } - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - - @Override - public boolean isValidData(Object particle, Object data) { - return true; - } - - @Override - public Object getParticle(ParticleType particle) { - if (!SERVER_IS_1_8) { - return particle.getLegacyName(); - } - - try { - return getEnumParticle(particle); - } catch (IllegalArgumentException e) { - return null; - } - } - - private Object getEnumParticle(ParticleType particleType) { - return FastReflection.enumValueOf(ENUM_PARTICLE, particleType.toString()); - } - - private int[] toData(ParticleType particle, Object data) { - Class dataType = particle.getDataType(); - if (dataType == ItemStack.class) { - if (!(data instanceof ItemStack)) { - return SERVER_IS_1_8 ? new int[2] : new int[]{1, 0}; - } - - ItemStack itemStack = (ItemStack) data; - return new int[]{itemStack.getType().getId(), itemStack.getDurability()}; - } - - if (dataType == MaterialData.class) { - if (!(data instanceof MaterialData)) { - return SERVER_IS_1_8 ? new int[1] : new int[]{1, 0}; - } - - MaterialData materialData = (MaterialData) data; - if (SERVER_IS_1_8) { - return new int[]{materialData.getItemType().getId() + (materialData.getData() << 12)}; - } else { - return new int[]{materialData.getItemType().getId(), materialData.getData()}; - } - } - - return EMPTY; - } -} diff --git a/src/main/java/com/volmit/adapt/util/ParticleType.java b/src/main/java/com/volmit/adapt/util/ParticleType.java deleted file mode 100644 index 0d8be0985..000000000 --- a/src/main/java/com/volmit/adapt/util/ParticleType.java +++ /dev/null @@ -1,195 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Color; -import org.bukkit.inventory.ItemStack; -import org.bukkit.material.MaterialData; - -/** - * @author MrMicky - */ -@SuppressWarnings("deprecation") -public enum ParticleType { - - // 1.7+ - EXPLOSION_NORMAL("explode", "poof"), - EXPLOSION_LARGE("largeexplode", "explosion"), - EXPLOSION_HUGE("hugeexplosion", "explosion_emitter"), - FIREWORKS_SPARK("fireworksSpark", "firework"), - WATER_BUBBLE("bubble", "bubble"), - WATER_SPLASH("splash", "splash"), - WATER_WAKE("wake", "fishing"), - SUSPENDED("suspended", "underwater"), - SUSPENDED_DEPTH("depthsuspend", "underwater"), - CRIT("crit", "crit"), - CRIT_MAGIC("magicCrit", "enchanted_hit"), - SMOKE_NORMAL("smoke", "smoke"), - SMOKE_LARGE("largesmoke", "large_smoke"), - SPELL("spell", "effect"), - SPELL_INSTANT("instantSpell", "instant_effect"), - SPELL_MOB("mobSpell", "entity_effect"), - SPELL_MOB_AMBIENT("mobSpellAmbient", "ambient_entity_effect"), - SPELL_WITCH("witchMagic", "witch"), - DRIP_WATER("dripWater", "dripping_water"), - DRIP_LAVA("dripLava", "dripping_lava"), - VILLAGER_ANGRY("angryVillager", "angry_villager"), - VILLAGER_HAPPY("happyVillager", "happy_villager"), - TOWN_AURA("townaura", "mycelium"), - NOTE("note", "note"), - PORTAL("portal", "portal"), - ENCHANTMENT_TABLE("enchantmenttable", "enchant"), - FLAME("flame", "flame"), - LAVA("lava", "lava"), - // FOOTSTEP("footstep", null), - CLOUD("cloud", "cloud"), - REDSTONE("reddust", "dust"), - SNOWBALL("snowballpoof", "item_snowball"), - SNOW_SHOVEL("snowshovel", "item_snowball"), - SLIME("slime", "item_slime"), - HEART("heart", "heart"), - ITEM_CRACK("iconcrack", "item"), - BLOCK_CRACK("blockcrack", "block"), - BLOCK_DUST("blockdust", "block"), - - // 1.8+ - BARRIER("barrier", "barrier", 8), - WATER_DROP("droplet", "rain", 8), - MOB_APPEARANCE("mobappearance", "elder_guardian", 8), - // ITEM_TAKE("take", null, 8), - - // 1.9+ - DRAGON_BREATH("dragonbreath", "dragon_breath", 9), - END_ROD("endRod", "end_rod", 9), - DAMAGE_INDICATOR("damageIndicator", "damage_indicator", 9), - SWEEP_ATTACK("sweepAttack", "sweep_attack", 9), - - // 1.10+ - FALLING_DUST("fallingdust", "falling_dust", 10), - - // 1.11+ - TOTEM("totem", "totem_of_undying", 11), - SPIT("spit", "spit", 11), - - // 1.13+ - SQUID_INK(13), - BUBBLE_POP(13), - CURRENT_DOWN(13), - BUBBLE_COLUMN_UP(13), - NAUTILUS(13), - DOLPHIN(13), - - // 1.14+ - SNEEZE(14), - CAMPFIRE_COSY_SMOKE(14), - CAMPFIRE_SIGNAL_SMOKE(14), - COMPOSTER(14), - FLASH(14), - FALLING_LAVA(14), - LANDING_LAVA(14), - FALLING_WATER(14), - - // 1.15+ - DRIPPING_HONEY(15), - FALLING_HONEY(15), - LANDING_HONEY(15), - FALLING_NECTAR(15); - - private static final int SERVER_VERSION_ID; - - static { - String ver = FastReflection.VERSION; - SERVER_VERSION_ID = ver.charAt(4) == '_' ? Character.getNumericValue(ver.charAt(3)) : Integer.parseInt(ver.substring(3, 5)); - } - - private final String legacyName; - private final String name; - private final int minimumVersion; - - // 1.7 particles - ParticleType(String legacyName, String name) { - this(legacyName, name, -1); - } - - // 1.13+ particles - ParticleType(int minimumVersion) { - this.legacyName = null; - this.name = name().toLowerCase(); - this.minimumVersion = minimumVersion; - } - - // 1.8-1.12 particles - ParticleType(String legacyName, String name, int minimumVersion) { - this.legacyName = legacyName; - this.name = name; - this.minimumVersion = minimumVersion; - } - - public static ParticleType getParticle(String particleName) { - try { - return ParticleType.valueOf(particleName.toUpperCase()); - } catch (IllegalArgumentException e) { - for (ParticleType particle : values()) { - if (particle.getName().equalsIgnoreCase(particleName)) { - return particle; - } - - if (particle.hasLegacyName() && particle.getLegacyName().equalsIgnoreCase(particleName)) { - return particle; - } - } - } - return null; - } - - public boolean hasLegacyName() { - return legacyName != null; - } - - public String getLegacyName() { - if (!hasLegacyName()) { - throw new IllegalStateException("Particle " + name() + " don't have legacy name"); - } - return legacyName; - } - - public String getName() { - return name; - } - - public boolean isSupported() { - return minimumVersion <= 0 || SERVER_VERSION_ID >= minimumVersion; - } - - public Class getDataType() { - switch (this) { - case ITEM_CRACK: - return ItemStack.class; - case BLOCK_CRACK: - case BLOCK_DUST: - case FALLING_DUST: - //noinspection deprecation - return MaterialData.class; - case REDSTONE: - return Color.class; - default: - return Void.class; - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/PersistentJson.java b/src/main/java/com/volmit/adapt/util/PersistentJson.java deleted file mode 100644 index 234f0d56b..000000000 --- a/src/main/java/com/volmit/adapt/util/PersistentJson.java +++ /dev/null @@ -1,41 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import org.bukkit.NamespacedKey; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; - -public class PersistentJson { - - public static void write(PersistentDataContainer c, String key, Object data) { - c.set(new NamespacedKey(Adapt.instance, key), PersistentDataType.STRING, Json.toJson(data, false)); - } - - private static T fromJSON(PersistentDataContainer c, String key, Class type) { - String s = c.get(new NamespacedKey(Adapt.instance, key), PersistentDataType.STRING); - - if (s == null) { - return Json.fromJson(s, type); - } - - return null; - } -} diff --git a/src/main/java/com/volmit/adapt/util/PhantomInventory.java b/src/main/java/com/volmit/adapt/util/PhantomInventory.java deleted file mode 100644 index 0efefaed7..000000000 --- a/src/main/java/com/volmit/adapt/util/PhantomInventory.java +++ /dev/null @@ -1,217 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.HumanEntity; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; - -public class PhantomInventory implements PhantomInventoryWrapper { - protected Inventory i; - - public PhantomInventory(Inventory i) { - this.i = i; - } - - @Override - public HashMap addItem(ItemStack... arg0) throws IllegalArgumentException { - return i.addItem(arg0); - } - - @Override - public HashMap all(Material arg0) throws IllegalArgumentException { - return i.all(arg0); - } - - @Override - public HashMap all(ItemStack arg0) { - return i.all(arg0); - } - - @Override - public void clear() { - i.clear(); - } - - @Override - public void clear(int arg0) { - i.clear(arg0); - } - - @Override - public boolean contains(Material arg0) throws IllegalArgumentException { - return i.contains(arg0); - } - - @Override - public boolean contains(ItemStack arg0) { - return i.contains(arg0); - } - - @Override - public boolean contains(Material arg0, int arg1) throws IllegalArgumentException { - return i.contains(arg0, arg1); - } - - @Override - public boolean contains(ItemStack arg0, int arg1) { - return i.contains(arg0, arg1); - } - - @Override - public boolean containsAtLeast(ItemStack arg0, int arg1) { - return i.containsAtLeast(arg0, arg1); - } - - @Override - public int first(Material arg0) throws IllegalArgumentException { - return i.first(arg0); - } - - @Override - public int first(ItemStack arg0) { - return i.first(arg0); - } - - @Override - public int firstEmpty() { - return i.firstEmpty(); - } - - @Override - public boolean isEmpty() { - return i.isEmpty(); - } - - @Override - public ItemStack[] getContents() { - return i.getContents(); - } - - @Override - public void setContents(ItemStack[] arg0) throws IllegalArgumentException { - i.setContents(arg0); - } - - @Override - public InventoryHolder getHolder() { - return i.getHolder(); - } - - @Override - public ItemStack getItem(int arg0) { - return i.getItem(arg0); - } - - @Override - public int getMaxStackSize() { - return i.getMaxStackSize(); - } - - @Override - public void setMaxStackSize(int arg0) { - i.setMaxStackSize(arg0); - } - - @Override - public int getSize() { - return i.getSize(); - } - - @Override - public InventoryType getType() { - return i.getType(); - } - - @Override - public List getViewers() { - return i.getViewers(); - } - - @Override - public ListIterator iterator() { - return i.iterator(); - } - - @Override - public ListIterator iterator(int arg0) { - return i.iterator(arg0); - } - - @Override - public void remove(Material arg0) throws IllegalArgumentException { - i.remove(arg0); - } - - @Override - public void remove(ItemStack arg0) { - i.remove(arg0); - } - - @Override - public HashMap removeItem(ItemStack... arg0) throws IllegalArgumentException { - return i.removeItem(arg0); - } - - @Override - public void setItem(int arg0, ItemStack arg1) { - i.setItem(arg0, arg1); - } - - @Override - public boolean hasSpace() { - return firstEmpty() != -1; - } - - @Override - public int getSlotsLeft() { - int x = 0; - - for (ItemStack i : getContents()) { - if (i == null || i.getType().equals(Material.AIR)) { - x++; - } - } - - return x; - } - - @Override - public Location getLocation() { - return i.getLocation(); - } - - @Override - public ItemStack[] getStorageContents() { - return i.getStorageContents(); - } - - @Override - public void setStorageContents(ItemStack[] arg0) throws IllegalArgumentException { - i.setStorageContents(arg0); - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/Point3d.java b/src/main/java/com/volmit/adapt/util/Point3d.java deleted file mode 100644 index dbed324ac..000000000 --- a/src/main/java/com/volmit/adapt/util/Point3d.java +++ /dev/null @@ -1,179 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 3 element point that is represented by double precision floating point - * x,y,z coordinates. - */ -public class Point3d extends Tuple3d implements java.io.Serializable { - - // Compatible with 1.1 - static final long serialVersionUID = 5718062286069042927L; - - /** - * Constructs and initializes a Point3d from the specified xyz coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - */ - public Point3d(double x, double y, double z) { - super(x, y, z); - } - - - /** - * Constructs and initializes a Point3d from the array of length 3. - * - * @param p the array of length 3 containing xyz in order - */ - public Point3d(double[] p) { - super(p); - } - - - /** - * Constructs and initializes a Point3d from the specified Point3d. - * - * @param p1 the Point3d containing the initialization x y z data - */ - public Point3d(Point3d p1) { - super(p1); - } - - - /** - * Constructs and initializes a Point3d from the specified Point3f. - * - * @param p1 the Point3f containing the initialization x y z data - */ - public Point3d(Point3f p1) { - super(p1); - } - - - /** - * Constructs and initializes a Point3d from the specified Tuple3f. - * - * @param t1 the Tuple3f containing the initialization x y z data - */ - public Point3d(Tuple3f t1) { - super(t1); - } - - - /** - * Constructs and initializes a Point3d from the specified Tuple3d. - * - * @param t1 the Tuple3d containing the initialization x y z data - */ - public Point3d(Tuple3d t1) { - super(t1); - } - - - /** - * Constructs and initializes a Point3d to (0,0,0). - */ - public Point3d() { - super(); - } - - - /** - * Returns the square of the distance between this point and point p1. - * - * @param p1 the other point - * @return the square of the distance - */ - public final double distanceSquared(Point3d p1) { - double dx, dy, dz; - - dx = this.x - p1.x; - dy = this.y - p1.y; - dz = this.z - p1.z; - return (dx * dx + dy * dy + dz * dz); - } - - - /** - * Returns the distance between this point and point p1. - * - * @param p1 the other point - * @return the distance - */ - public final double distance(Point3d p1) { - double dx, dy, dz; - - dx = this.x - p1.x; - dy = this.y - p1.y; - dz = this.z - p1.z; - return Math.sqrt(dx * dx + dy * dy + dz * dz); - } - - - /** - * Computes the L-1 (Manhattan) distance between this point and - * point p1. The L-1 distance is equal to: - * abs(x1-x2) + abs(y1-y2) + abs(z1-z2). - * - * @param p1 the other point - * @return the L-1 distance - */ - public final double distanceL1(Point3d p1) { - return Math.abs(this.x - p1.x) + Math.abs(this.y - p1.y) + - Math.abs(this.z - p1.z); - } - - - /** - * Computes the L-infinite distance between this point and - * point p1. The L-infinite distance is equal to - * MAX[abs(x1-x2), abs(y1-y2), abs(z1-z2)]. - * - * @param p1 the other point - * @return the L-infinite distance - */ - public final double distanceLinf(Point3d p1) { - double tmp; - tmp = Math.max(Math.abs(this.x - p1.x), Math.abs(this.y - p1.y)); - - return Math.max(tmp, Math.abs(this.z - p1.z)); - } - - - /** - * Multiplies each of the x,y,z components of the Point4d parameter - * by 1/w and places the projected values into this point. - * - * @param p1 the source Point4d, which is not modified - */ - public final void project(Point4d p1) { - double oneOw; - - oneOw = 1 / p1.w; - x = p1.x * oneOw; - y = p1.y * oneOw; - z = p1.z * oneOw; - - } - - -} diff --git a/src/main/java/com/volmit/adapt/util/Point3f.java b/src/main/java/com/volmit/adapt/util/Point3f.java deleted file mode 100644 index 7f04d5f61..000000000 --- a/src/main/java/com/volmit/adapt/util/Point3f.java +++ /dev/null @@ -1,180 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 3 element point that is represented by single precision floating point - * x,y,z coordinates. - */ -public class Point3f extends Tuple3f implements java.io.Serializable { - - - // Compatible with 1.1 - static final long serialVersionUID = -8689337816398030143L; - - /** - * Constructs and initializes a Point3f from the specified xyz coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - */ - public Point3f(float x, float y, float z) { - super(x, y, z); - } - - - /** - * Constructs and initializes a Point3f from the array of length 3. - * - * @param p the array of length 3 containing xyz in order - */ - public Point3f(float[] p) { - super(p); - } - - - /** - * Constructs and initializes a Point3f from the specified Point3f. - * - * @param p1 the Point3f containing the initialization x y z data - */ - public Point3f(Point3f p1) { - super(p1); - } - - - /** - * Constructs and initializes a Point3f from the specified Point3d. - * - * @param p1 the Point3d containing the initialization x y z data - */ - public Point3f(Point3d p1) { - super(p1); - } - - - /** - * Constructs and initializes a Point3f from the specified Tuple3f. - * - * @param t1 the Tuple3f containing the initialization x y z data - */ - public Point3f(Tuple3f t1) { - super(t1); - } - - - /** - * Constructs and initializes a Point3f from the specified Tuple3d. - * - * @param t1 the Tuple3d containing the initialization x y z data - */ - public Point3f(Tuple3d t1) { - super(t1); - } - - - /** - * Constructs and initializes a Point3f to (0,0,0). - */ - public Point3f() { - super(); - } - - - /** - * Computes the square of the distance between this point and - * point p1. - * - * @param p1 the other point - * @return the square of the distance - */ - public final float distanceSquared(Point3f p1) { - float dx, dy, dz; - - dx = this.x - p1.x; - dy = this.y - p1.y; - dz = this.z - p1.z; - return dx * dx + dy * dy + dz * dz; - } - - - /** - * Computes the distance between this point and point p1. - * - * @param p1 the other point - * @return the distance - */ - public final float distance(Point3f p1) { - float dx, dy, dz; - - dx = this.x - p1.x; - dy = this.y - p1.y; - dz = this.z - p1.z; - return (float) Math.sqrt(dx * dx + dy * dy + dz * dz); - } - - - /** - * Computes the L-1 (Manhattan) distance between this point and - * point p1. The L-1 distance is equal to: - * abs(x1-x2) + abs(y1-y2) + abs(z1-z2). - * - * @param p1 the other point - * @return the L-1 distance - */ - public final float distanceL1(Point3f p1) { - return (Math.abs(this.x - p1.x) + Math.abs(this.y - p1.y) + Math.abs(this.z - p1.z)); - } - - - /** - * Computes the L-infinite distance between this point and - * point p1. The L-infinite distance is equal to - * MAX[abs(x1-x2), abs(y1-y2), abs(z1-z2)]. - * - * @param p1 the other point - * @return the L-infinite distance - */ - public final float distanceLinf(Point3f p1) { - float tmp; - tmp = Math.max(Math.abs(this.x - p1.x), Math.abs(this.y - p1.y)); - return (Math.max(tmp, Math.abs(this.z - p1.z))); - - } - - - /** - * Multiplies each of the x,y,z components of the Point4f parameter - * by 1/w and places the projected values into this point. - * - * @param p1 the source Point4f, which is not modified - */ - public final void project(Point4f p1) { - float oneOw; - - oneOw = 1 / p1.w; - x = p1.x * oneOw; - y = p1.y * oneOw; - z = p1.z * oneOw; - - } - - -} diff --git a/src/main/java/com/volmit/adapt/util/Point4d.java b/src/main/java/com/volmit/adapt/util/Point4d.java deleted file mode 100644 index c201e1fb8..000000000 --- a/src/main/java/com/volmit/adapt/util/Point4d.java +++ /dev/null @@ -1,214 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 4 element vector represented by double precision floating point - * x,y,z,w coordinates. - */ -public class Point4d extends Tuple4d implements java.io.Serializable { - - // Compatible with 1.1 - static final long serialVersionUID = 1733471895962736949L; - - - /** - * Constructs and initializes a Point4d from the specified xyzw coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - * @param w the w coordinate - */ - public Point4d(double x, double y, double z, double w) { - super(x, y, z, w); - } - - /** - * Constructs and initializes a Point4d from the coordinates contained - * in the array. - * - * @param p the array of length 4 containing xyzw in order - */ - public Point4d(double[] p) { - super(p); - } - - - /** - * Constructs and initializes a Point4d from the specified Point4d. - * - * @param p1 the Point4d containing the initialization x y z w data - */ - public Point4d(Point4d p1) { - super(p1); - } - - - /** - * Constructs and initializes a Point4d from the specified Point4f. - * - * @param p1 the Point4f containing the initialization x y z w data - */ - public Point4d(Point4f p1) { - super(p1); - } - - - /** - * Constructs and initializes a Point4d from the specified Tuple4f. - * - * @param t1 the Tuple4f containing the initialization x y z w data - */ - public Point4d(Tuple4f t1) { - super(t1); - } - - - /** - * Constructs and initializes a Point4d from the specified Tuple4d. - * - * @param t1 the Tuple4d containing the initialization x y z w data - */ - public Point4d(Tuple4d t1) { - super(t1); - } - - - /** - * Constructs and initializes a Point4d from the specified Tuple3d. - * The x,y,z components of this point are set to the corresponding - * components of tuple t1. The w component of this point - * is set to 1. - * - * @param t1 the tuple to be copied - * @since vecmath 1.2 - */ - public Point4d(Tuple3d t1) { - super(t1.x, t1.y, t1.z, 1.0); - } - - - /** - * Constructs and initializes a Point4d to (0,0,0,0). - */ - public Point4d() { - super(); - } - - - /** - * Sets the x,y,z components of this point to the corresponding - * components of tuple t1. The w component of this point - * is set to 1. - * - * @param t1 the tuple to be copied - * @since vecmath 1.2 - */ - public final void set(Tuple3d t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - this.w = 1.0; - } - - - /** - * Returns the square of the distance between this point and point p1. - * - * @param p1 the first point - * @return the square of distance between this point and point p1 - */ - public final double distanceSquared(Point4d p1) { - double dx, dy, dz, dw; - - dx = this.x - p1.x; - dy = this.y - p1.y; - dz = this.z - p1.z; - dw = this.w - p1.w; - return (dx * dx + dy * dy + dz * dz + dw * dw); - } - - - /** - * Returns the distance between this point and point p1. - * - * @param p1 the first point - * @return the distance between these this point and point p1. - */ - public final double distance(Point4d p1) { - double dx, dy, dz, dw; - - dx = this.x - p1.x; - dy = this.y - p1.y; - dz = this.z - p1.z; - dw = this.w - p1.w; - return Math.sqrt(dx * dx + dy * dy + dz * dz + dw * dw); - } - - - /** - * Computes the L-1 (Manhattan) distance between this point and - * point p1. The L-1 distance is equal to: - * abs(x1-x2) + abs(y1-y2) + abs(z1-z2) + abs(w1-w2). - * - * @param p1 the other point - * @return the L-1 distance - */ - public final double distanceL1(Point4d p1) { - return Math.abs(this.x - p1.x) + Math.abs(this.y - p1.y) + - Math.abs(this.z - p1.z) + Math.abs(this.w - p1.w); - } - - /** - * Computes the L-infinite distance between this point and - * point p1. The L-infinite distance is equal to - * MAX[abs(x1-x2), abs(y1-y2), abs(z1-z2), abs(w1-w2)]. - * - * @param p1 the other point - * @return the L-infinite distance - */ - public final double distanceLinf(Point4d p1) { - double t1, t2; - t1 = Math.max(Math.abs(this.x - p1.x), Math.abs(this.y - p1.y)); - t2 = Math.max(Math.abs(this.z - p1.z), Math.abs(this.w - p1.w)); - - return Math.max(t1, t2); - } - - /** - * Multiplies each of the x,y,z components of the Point4d parameter - * by 1/w, places the projected values into this point, and places - * a 1 as the w parameter of this point. - * - * @param p1 the source Point4d, which is not modified - */ - public final void project(Point4d p1) { - double oneOw; - - oneOw = 1 / p1.w; - x = p1.x * oneOw; - y = p1.y * oneOw; - z = p1.z * oneOw; - w = 1.0; - - } - - -} diff --git a/src/main/java/com/volmit/adapt/util/Point4f.java b/src/main/java/com/volmit/adapt/util/Point4f.java deleted file mode 100644 index ed0f99723..000000000 --- a/src/main/java/com/volmit/adapt/util/Point4f.java +++ /dev/null @@ -1,214 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 4 element point represented by single precision floating point x,y,z,w - * coordinates. - */ -public class Point4f extends Tuple4f implements java.io.Serializable { - - - // Compatible with 1.1 - static final long serialVersionUID = 4643134103185764459L; - - /** - * Constructs and initializes a Point4f from the specified xyzw coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - * @param w the w coordinate - */ - public Point4f(float x, float y, float z, float w) { - super(x, y, z, w); - } - - - /** - * Constructs and initializes a Point4f from the array of length 4. - * - * @param p the array of length 4 containing xyzw in order - */ - public Point4f(float[] p) { - super(p); - } - - - /** - * Constructs and initializes a Point4f from the specified Point4f. - * - * @param p1 the Point4f containing the initialization x y z w data - */ - public Point4f(Point4f p1) { - super(p1); - } - - - /** - * Constructs and initializes a Point4f from the specified Point4d. - * - * @param p1 the Point4d containing the initialization x y z w data - */ - public Point4f(Point4d p1) { - super(p1); - } - - - /** - * Constructs and initializes a Point4f from the specified Tuple4f. - * - * @param t1 the Tuple4f containing the initialization x y z w data - */ - public Point4f(Tuple4f t1) { - super(t1); - } - - - /** - * Constructs and initializes a Point4f from the specified Tuple4d. - * - * @param t1 the Tuple4d containing the initialization x y z w data - */ - public Point4f(Tuple4d t1) { - super(t1); - } - - - /** - * Constructs and initializes a Point4f from the specified Tuple3f. - * The x,y,z components of this point are set to the corresponding - * components of tuple t1. The w component of this point - * is set to 1. - * - * @param t1 the tuple to be copied - * @since vecmath 1.2 - */ - public Point4f(Tuple3f t1) { - super(t1.x, t1.y, t1.z, 1.0f); - } - - - /** - * Constructs and initializes a Point4f to (0,0,0,0). - */ - public Point4f() { - super(); - } - - - /** - * Sets the x,y,z components of this point to the corresponding - * components of tuple t1. The w component of this point - * is set to 1. - * - * @param t1 the tuple to be copied - * @since vecmath 1.2 - */ - public final void set(Tuple3f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - this.w = 1.0f; - } - - - /** - * Computes the square of the distance between this point and point p1. - * - * @param p1 the other point - * @return the square of distance between these two points as a float - */ - public final float distanceSquared(Point4f p1) { - float dx, dy, dz, dw; - - dx = this.x - p1.x; - dy = this.y - p1.y; - dz = this.z - p1.z; - dw = this.w - p1.w; - return (dx * dx + dy * dy + dz * dz + dw * dw); - } - - - /** - * Computes the distance between this point and point p1. - * - * @param p1 the other point - * @return the distance between the two points - */ - public final float distance(Point4f p1) { - float dx, dy, dz, dw; - - dx = this.x - p1.x; - dy = this.y - p1.y; - dz = this.z - p1.z; - dw = this.w - p1.w; - return (float) Math.sqrt(dx * dx + dy * dy + dz * dz + dw * dw); - } - - - /** - * Computes the L-1 (Manhattan) distance between this point and - * point p1. The L-1 distance is equal to: - * abs(x1-x2) + abs(y1-y2) + abs(z1-z2) + abs(w1-w2). - * - * @param p1 the other point - * @return the L-1 distance - */ - public final float distanceL1(Point4f p1) { - return (Math.abs(this.x - p1.x) + Math.abs(this.y - p1.y) + Math.abs(this.z - p1.z) + Math.abs(this.w - p1.w)); - } - - - /** - * Computes the L-infinite distance between this point and - * point p1. The L-infinite distance is equal to - * MAX[abs(x1-x2), abs(y1-y2), abs(z1-z2), abs(w1-w2)]. - * - * @param p1 the other point - * @return the L-infinite distance - */ - public final float distanceLinf(Point4f p1) { - float t1, t2; - t1 = Math.max(Math.abs(this.x - p1.x), Math.abs(this.y - p1.y)); - t2 = Math.max(Math.abs(this.z - p1.z), Math.abs(this.w - p1.w)); - - return (Math.max(t1, t2)); - - } - - /** - * Multiplies each of the x,y,z components of the Point4f parameter - * by 1/w, places the projected values into this point, and places - * a 1 as the w parameter of this point. - * - * @param p1 the source Point4f, which is not modified - */ - public final void project(Point4f p1) { - float oneOw; - - oneOw = 1 / p1.w; - x = p1.x * oneOw; - y = p1.y * oneOw; - z = p1.z * oneOw; - w = 1.0f; - - } - -} diff --git a/src/main/java/com/volmit/adapt/util/PrecisionStopwatch.java b/src/main/java/com/volmit/adapt/util/PrecisionStopwatch.java deleted file mode 100644 index 6d031d5fe..000000000 --- a/src/main/java/com/volmit/adapt/util/PrecisionStopwatch.java +++ /dev/null @@ -1,123 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class PrecisionStopwatch { - private long nanos; - private long startNano; - private long millis; - private long startMillis; - private double time; - private boolean profiling; - - public PrecisionStopwatch() { - reset(); - profiling = false; - } - - public static PrecisionStopwatch start() { - PrecisionStopwatch p = new PrecisionStopwatch(); - p.begin(); - - return p; - } - - public void begin() { - profiling = true; - startNano = System.nanoTime(); - startMillis = System.currentTimeMillis(); - } - - public void end() { - if (!profiling) { - return; - } - - profiling = false; - nanos = System.nanoTime() - startNano; - millis = System.currentTimeMillis() - startMillis; - time = (double) nanos / 1000000.0; - time = (double) millis - time > 1.01 ? millis : time; - } - - public void reset() { - nanos = -1; - millis = -1; - startNano = -1; - startMillis = -1; - time = -0; - profiling = false; - } - - public double getTicks() { - return getMilliseconds() / 50.0; - } - - public double getSeconds() { - return getMilliseconds() / 1000.0; - } - - public double getMinutes() { - return getSeconds() / 60.0; - } - - public double getHours() { - return getMinutes() / 60.0; - } - - public double getMilliseconds() { - nanos = System.nanoTime() - startNano; - millis = System.currentTimeMillis() - startMillis; - time = (double) nanos / 1000000.0; - time = (double) millis - time > 1.01 ? millis : time; - return time; - } - - public long getNanoseconds() { - return (long) (time * 1000000.0); - } - - public long getNanos() { - return nanos; - } - - public long getStartNano() { - return startNano; - } - - public long getMillis() { - return millis; - } - - public long getStartMillis() { - return startMillis; - } - - public double getTime() { - return time; - } - - public boolean isProfiling() { - return profiling; - } - - public void rewind(long l) { - startMillis -= l; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Queue.java b/src/main/java/com/volmit/adapt/util/Queue.java deleted file mode 100644 index f8492679d..000000000 --- a/src/main/java/com/volmit/adapt/util/Queue.java +++ /dev/null @@ -1,51 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.util.Arrays; -import java.util.List; - -public interface Queue { - static Queue create(List t) { - return new ShurikenQueue().queue(t); - } - - @SuppressWarnings("unchecked") - static Queue create(T... t) { - return new ShurikenQueue().queue(Arrays.stream(t).toList()); - } - - Queue queue(T t); - - Queue queue(List t); - - boolean hasNext(int amt); - - boolean hasNext(); - - T next(); - - List next(int amt); - - Queue clear(); - - int size(); - - boolean contains(T p); -} diff --git a/src/main/java/com/volmit/adapt/util/QueueExecutor.java b/src/main/java/com/volmit/adapt/util/QueueExecutor.java deleted file mode 100644 index d61c9fa4e..000000000 --- a/src/main/java/com/volmit/adapt/util/QueueExecutor.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class QueueExecutor extends Looper { - private final Queue queue; - private boolean shutdown; - - public QueueExecutor() { - queue = new ShurikenQueue(); - shutdown = false; - } - - public Queue queue() { - return queue; - } - - @Override - protected long loop() { - while (queue.hasNext()) { - try { - queue.next().run(); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - if (shutdown && !queue.hasNext()) { - interrupt(); - return -1; - } - - return Math.max(500, (long) getRunTime() * 10); - } - - public double getRunTime() { - return 0; - } - - public void shutdown() { - shutdown = true; - } -} diff --git a/src/main/java/com/volmit/adapt/util/RNG.java b/src/main/java/com/volmit/adapt/util/RNG.java deleted file mode 100644 index 623e6ac57..000000000 --- a/src/main/java/com/volmit/adapt/util/RNG.java +++ /dev/null @@ -1,170 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.nio.charset.StandardCharsets; -import java.util.Random; -import java.util.UUID; - -public class RNG extends Random { - public static final RNG r = new RNG(); - private static final char[] CHARGEN = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-=!@#$%^&*()_+`~[];',./<>?:\\\"{}|\\\\".toCharArray(); - private static final long serialVersionUID = 5222938581174415179L; - private final long sx; - - public RNG() { - super(); - sx = 0; - } - - public RNG(long seed) { - super(seed); - this.sx = seed; - } - - /** - * Creates a seed (long) from the hash of the seed string - * - * @param seed the seed (string) - */ - public RNG(String seed) { - this(UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).getLeastSignificantBits() + UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).getMostSignificantBits() + (seed.length() * 32564)); - } - - public RNG nextParallelRNG(int signature) { - return new RNG(sx + signature); - } - - public RNG nextParallelRNG(long signature) { - return new RNG(sx + signature); - } - - public RNG nextRNG() { - return new RNG(nextLong()); - } - - public String s(int length) { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < length; i++) { - sb.append(c()); - } - - return sb.toString(); - } - - public char c() { - return CHARGEN[i(CHARGEN.length - 1)]; - } - - /** - * Pick a random enum - * - * @param t the enum class - * @return the enum - */ - public T e(Class t) { - T[] c = t.getEnumConstants(); - return c[i(c.length)]; - } - - public boolean b() { - return nextBoolean(); - } - - public boolean b(double percent) { - return d() > percent; - } - - public short si(int lowerBound, int upperBound) { - return (short) (lowerBound + (nextFloat() * ((upperBound - lowerBound) + 1))); - } - - public short si(int upperBound) { - return si(0, upperBound); - } - - public short si() { - return si(1); - } - - public float f(float lowerBound, float upperBound) { - return lowerBound + (nextFloat() * ((upperBound - lowerBound))); - } - - public float f(float upperBound) { - return f(0, upperBound); - } - - public float f() { - return f(1); - } - - public double d(double lowerBound, double upperBound) { - return M.lerp(lowerBound, upperBound, nextDouble()); - } - - public double d(double upperBound) { - return d(0, upperBound); - } - - public double d() { - return d(1); - } - - public int i(int lowerBound, int upperBound) { - return (int) Math.round(d(lowerBound, upperBound)); - } - - public int i(int upperBound) { - return i(Math.min(upperBound, 0), Math.max(0, upperBound)); - } - - public long l(long lowerBound, long upperBound) { - return Math.round(d(lowerBound, upperBound)); - } - - public long l(long upperBound) { - return l(0, upperBound); - } - - public int imax() { - return i(Integer.MIN_VALUE, Integer.MAX_VALUE); - } - - public long lmax() { - return l(Long.MIN_VALUE, Long.MAX_VALUE); - } - - public float fmax() { - return f(Float.MIN_VALUE, Float.MAX_VALUE); - } - - public double dmax() { - return d(Double.MIN_VALUE, Double.MAX_VALUE); - } - - public short simax() { - return si(Short.MIN_VALUE, Short.MAX_VALUE); - } - - public boolean chance(double chance) { - return chance >= nextDouble(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/ReactiveFolder.java b/src/main/java/com/volmit/adapt/util/ReactiveFolder.java deleted file mode 100644 index b86639523..000000000 --- a/src/main/java/com/volmit/adapt/util/ReactiveFolder.java +++ /dev/null @@ -1,78 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import art.arcane.amulet.io.FolderWatcher; - -import java.io.File; -import java.util.List; - -public class ReactiveFolder { - private final File folder; - private final Consumer3, List, List> hotload; - private FolderWatcher fw; - - public ReactiveFolder(File folder, Consumer3, List, List> hotload) { - this.folder = folder; - this.hotload = hotload; - this.fw = new FolderWatcher(folder); - fw.checkModified(); - } - - public void checkIgnore() { - fw = new FolderWatcher(folder); - } - - public void check() { - boolean modified = false; - - if (fw.checkModified()) { - for (File i : fw.getCreated()) { - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json")) { - modified = true; - break; - } - } - - if (!modified) { - for (File i : fw.getChanged()) { - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json")) { - modified = true; - break; - } - } - } - - if (!modified) { - for (File i : fw.getDeleted()) { - if (i.getName().endsWith(".iob") || i.getName().endsWith(".json")) { - modified = true; - break; - } - } - } - } - - if (modified) { - hotload.accept(fw.getCreated(), fw.getChanged(), fw.getDeleted()); - } - - fw.checkModified(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/RollingSequence.java b/src/main/java/com/volmit/adapt/util/RollingSequence.java deleted file mode 100644 index cf0a40fa7..000000000 --- a/src/main/java/com/volmit/adapt/util/RollingSequence.java +++ /dev/null @@ -1,105 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KList; - -public class RollingSequence extends Average { - private double median; - private double max; - private double min; - private boolean dirtyMedian; - private int dirtyExtremes; - private boolean precision; - - public RollingSequence(int size) { - super(size); - median = 0; - min = 0; - max = 0; - setPrecision(false); - } - - public double addLast(int amt) { - double f = 0; - - for (int i = 0; i < Math.min(values.length, amt); i++) { - f += values[i]; - } - - return f; - } - - public boolean isPrecision() { - return precision; - } - - public void setPrecision(boolean p) { - this.precision = p; - } - - public double getMin() { - if (dirtyExtremes > (isPrecision() ? 0 : values.length)) { - resetExtremes(); - } - - return min; - } - - public double getMax() { - if (dirtyExtremes > (isPrecision() ? 0 : values.length)) { - resetExtremes(); - } - - return max; - } - - public double getMedian() { - if (dirtyMedian) { - recalculateMedian(); - } - - return median; - } - - private void recalculateMedian() { - median = new KList().forceAdd(values).sort().middleValue(); - dirtyMedian = false; - } - - public void resetExtremes() { - max = Integer.MIN_VALUE; - min = Integer.MAX_VALUE; - - for (double i : values) { - max = M.max(max, i); - min = M.min(min, i); - } - - dirtyExtremes = 0; - } - - public void put(double i) { - super.put(i); - dirtyMedian = true; - dirtyExtremes++; - max = M.max(max, i); - min = M.min(min, i); - } -} diff --git a/src/main/java/com/volmit/adapt/util/RouterCommand.java b/src/main/java/com/volmit/adapt/util/RouterCommand.java deleted file mode 100644 index 3fcbe9b71..000000000 --- a/src/main/java/com/volmit/adapt/util/RouterCommand.java +++ /dev/null @@ -1,62 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; - -/** - * Assistive command router - * - * @author cyberpwn - */ -public class RouterCommand extends org.bukkit.command.Command { - private final CommandExecutor ex; - private String usage; - - /** - * The router command routes commands to bukkit executors - * - * @param realCommand the real command - * @param ex the executor - */ - public RouterCommand(ICommand realCommand, CommandExecutor ex) { - super(realCommand.getNode().toLowerCase()); - setAliases(realCommand.getNodes()); - - this.ex = ex; - } - - @Override - public Command setUsage(String u) { - this.usage = u; - return this; - } - - @Override - public String getUsage() { - return usage; - } - - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - return ex.onCommand(sender, this, commandLabel, args); - } -} diff --git a/src/main/java/com/volmit/adapt/util/S.java b/src/main/java/com/volmit/adapt/util/S.java deleted file mode 100644 index f6795dc3a..000000000 --- a/src/main/java/com/volmit/adapt/util/S.java +++ /dev/null @@ -1,29 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public abstract class S implements Runnable { - public S() { - J.s(this); - } - - public S(int delay) { - J.s(this, delay); - } -} diff --git a/src/main/java/com/volmit/adapt/util/SQLManager.java b/src/main/java/com/volmit/adapt/util/SQLManager.java deleted file mode 100644 index d4395806d..000000000 --- a/src/main/java/com/volmit/adapt/util/SQLManager.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.UUID; - -public class SQLManager { - - private static final String TABLE_NAME = "ADAPT_DATA"; - private static final String CREATE_TABLE_QUERY = "CREATE TABLE " + TABLE_NAME + " (UUID char(36) NOT NULL UNIQUE, DATA MEDIUMTEXT NOT NULL)"; - private static final String UPDATE_QUERY = "INSERT INTO " + TABLE_NAME + " (UUID, DATA) VALUES(?, ?) ON DUPLICATE KEY UPDATE DATA=?"; - private static final String FETCH_QUERY = "SELECT DATA FROM " + TABLE_NAME + " WHERE UUID=?"; - private static final String DELETE_QUERY = "DELETE FROM " + TABLE_NAME + " WHERE UUID=?"; - - private Connection connection; - - public synchronized void establishConnection() { - if (connection != null) { - closeConnection(); - } - - AdaptConfig config = AdaptConfig.get(); - try { - connection = DriverManager.getConnection(assembleUrl(config), config.getSql().getUsername(), config.getSql().getPassword()); - int verifySeconds = Math.max(1, Math.min(10, config.getSqlSecondsCheckverify())); - if (!connection.isValid(verifySeconds)) { - throw new SQLException("Connection timed out"); - } else { - setupDatabase(); - } - } catch (SQLException e) { - handleSQLException("Failed to establish a connection to the SQL server!", e); - connection = null; - } - } - - private void setupDatabase() throws SQLException { - if (!connection.getMetaData().getTables(null, null, TABLE_NAME, new String[]{"TABLE"}).next()) { - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(CREATE_TABLE_QUERY); - } - } - } - - public synchronized void closeConnection() { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - handleSQLException("Failed to close the connection to the SQL server!", e); - } - connection = null; - } - } - - public synchronized void updateData(UUID uuid, String data) { - executeWithRetry(conn -> { - try (PreparedStatement statement = conn.prepareStatement(UPDATE_QUERY)) { - statement.setString(1, uuid.toString()); - statement.setString(2, data); - statement.setString(3, data); - statement.executeUpdate(); - } - }, "Failed to write data to the SQL server!"); - } - - public synchronized void delete(UUID uuid) { - executeWithRetry(conn -> { - try (PreparedStatement statement = conn.prepareStatement(DELETE_QUERY)) { - statement.setString(1, uuid.toString()); - statement.executeUpdate(); - } - }, "Failed to delete data from the SQL server!"); - } - - public synchronized String fetchData(UUID uuid) { - try { - checkAndReestablishConnection(); - try (PreparedStatement statement = connection.prepareStatement(FETCH_QUERY)) { - statement.setString(1, uuid.toString()); - try (ResultSet set = statement.executeQuery()) { - if (!set.next()) { - return null; - } - return set.getString("DATA"); - } - } - } catch (SQLException e) { - handleSQLException("Failed to read data from the SQL server!", e); - return null; - } - } - - private void executeWithRetry(SqlAction action, String errorMessage) { - try { - checkAndReestablishConnection(); - action.run(connection); - } catch (SQLException e) { - handleSQLException(errorMessage, e); - } - } - - private void checkAndReestablishConnection() throws SQLException { - if (connection == null || !connection.isValid(AdaptConfig.get().getSqlSecondsCheckverify())) { // 30 sec by default - establishConnection(); - } - if (connection == null) { - throw new SQLException("No active SQL connection"); - } - } - - private void handleSQLException(String message, SQLException e) { - Adapt.error(message); - Adapt.error("\t" + e.getClass().getSimpleName() + (e.getMessage() != null ? ": " + e.getMessage() : "")); - } - - private String assembleUrl(AdaptConfig config) { - long connectTimeout = Math.max(1000L, config.getSql().getConnectionTimeout()); - long socketTimeout = Math.max(connectTimeout, connectTimeout * 2L); - return String.format( - "jdbc:mysql://%s:%d/%s?connectTimeout=%d&socketTimeout=%d", - config.getSql().getHost(), - config.getSql().getPort(), - config.getSql().getDatabase(), - connectTimeout, - socketTimeout - ); - } - - @FunctionalInterface - interface SqlAction { - void run(Connection connection) throws SQLException; - } -} diff --git a/src/main/java/com/volmit/adapt/util/SR.java b/src/main/java/com/volmit/adapt/util/SR.java deleted file mode 100644 index 7fd87b87b..000000000 --- a/src/main/java/com/volmit/adapt/util/SR.java +++ /dev/null @@ -1,40 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public abstract class SR implements Runnable, CancellableTask { - private int id = 0; - - public SR() { - this(0); - } - - public SR(int interval) { - id = J.sr(this, interval); - } - - @Override - public void cancel() { - J.csr(id); - } - - public int getId() { - return id; - } -} diff --git a/src/main/java/com/volmit/adapt/util/ScoreDirection.java b/src/main/java/com/volmit/adapt/util/ScoreDirection.java deleted file mode 100644 index 0fef120b9..000000000 --- a/src/main/java/com/volmit/adapt/util/ScoreDirection.java +++ /dev/null @@ -1,32 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * @author Missionary (missionarymc@gmail.com) - * @since 5/31/2018 - */ -@DontObfuscate -public enum ScoreDirection { - @DontObfuscate - UP, - - @DontObfuscate - DOWN -} diff --git a/src/main/java/com/volmit/adapt/util/ShortTag.java b/src/main/java/com/volmit/adapt/util/ShortTag.java deleted file mode 100644 index 7d2e13951..000000000 --- a/src/main/java/com/volmit/adapt/util/ShortTag.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * The TAG_Short tag. - * - * @author Graham Edgecombe - */ -public final class ShortTag extends Tag { - - /** - * The value. - */ - private final short value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public ShortTag(String name, short value) { - super(name); - this.value = value; - } - - @Override - public Short getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_Short" + append + ": " + value; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Shrinkwrap.java b/src/main/java/com/volmit/adapt/util/Shrinkwrap.java deleted file mode 100644 index cb9fc7d06..000000000 --- a/src/main/java/com/volmit/adapt/util/Shrinkwrap.java +++ /dev/null @@ -1,39 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class Shrinkwrap { - private T t; - - public Shrinkwrap(T t) { - set(t); - } - - public Shrinkwrap() { - this(null); - } - - public T get() { - return t; - } - - public void set(T t) { - this.t = t; - } -} diff --git a/src/main/java/com/volmit/adapt/util/ShurikenQueue.java b/src/main/java/com/volmit/adapt/util/ShurikenQueue.java deleted file mode 100644 index 07f46cdc2..000000000 --- a/src/main/java/com/volmit/adapt/util/ShurikenQueue.java +++ /dev/null @@ -1,102 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KList; - -import java.util.ArrayList; -import java.util.List; - -public class ShurikenQueue implements Queue { - private KList queue; - private boolean randomPop; - private boolean reversePop; - - public ShurikenQueue() { - clear(); - } - - public ShurikenQueue responsiveMode() { - reversePop = true; - return this; - } - - public ShurikenQueue randomMode() { - randomPop = true; - return this; - } - - @Override - public ShurikenQueue queue(T t) { - queue.add(t); - return this; - } - - @Override - public ShurikenQueue queue(List t) { - queue.addAll(t); - return this; - } - - @Override - public boolean hasNext(int amt) { - return queue.size() >= amt; - } - - @Override - public boolean hasNext() { - return !queue.isEmpty(); - } - - @Override - public T next() { - return reversePop ? queue.popLast() : randomPop ? queue.popRandom() : queue.pop(); - } - - @Override - public List next(int amt) { - List t = new ArrayList<>(); - - for (int i = 0; i < amt; i++) { - if (!hasNext()) { - break; - } - - t.add(next()); - } - - return t; - } - - @Override - public ShurikenQueue clear() { - queue = new KList<>(); - return this; - } - - @Override - public int size() { - return queue.size(); - } - - @Override - public boolean contains(T p) { - return queue.contains(p); - } -} diff --git a/src/main/java/com/volmit/adapt/util/SoundPlayer.java b/src/main/java/com/volmit/adapt/util/SoundPlayer.java deleted file mode 100644 index 977352bbd..000000000 --- a/src/main/java/com/volmit/adapt/util/SoundPlayer.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.volmit.adapt.util; - -import com.volmit.adapt.AdaptConfig; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import org.bukkit.Location; -import org.bukkit.Sound; -import org.bukkit.SoundCategory; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.Collection; -import java.util.List; - -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -public class SoundPlayer { - - private final Collection players; - - public static SoundPlayer of(Collection players) { - return new SoundPlayer(players); - } - - public static SoundPlayer of(Player player) { - return new SoundPlayer(List.of(player)); - } - - public static SoundPlayer of(World world) { - return new SoundPlayer(world.getPlayers()); - } - - public void play(@NotNull Location location, @NotNull Sound sound) { - play(location, sound, 1.0f, 1.0f); - } - - public void play(@NotNull Entity entity, @NotNull Sound sound, float volume, float pitch) { - play(entity.getLocation(), sound, volume, pitch); - } - - public void play(@NotNull Location location, @NotNull Sound sound, float volume, float pitch) { - if (!areSoundsEnabled()) { - return; - } - players.forEach(player -> player.playSound(location, sound, volume, pitch)); - //J.s(() -> Objects.requireNonNull(location.getWorld()).playSound(location, sound, volume, pitch)); - } - - public void play(@NotNull Location location, @NotNull Sound sound, SoundCategory category, float volume, float pitch) { - if (!areSoundsEnabled()) { - return; - } - players.forEach(player -> player.playSound(location, sound, category, volume, pitch)); - //J.s(() -> Objects.requireNonNull(location.getWorld()).playSound(location, sound, volume, pitch)); - } - - private boolean areSoundsEnabled() { - AdaptConfig.Effects effects = AdaptConfig.get().getEffects(); - return effects == null || effects.isSoundsEnabled(); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Sphere.java b/src/main/java/com/volmit/adapt/util/Sphere.java deleted file mode 100644 index 07ea94bbb..000000000 --- a/src/main/java/com/volmit/adapt/util/Sphere.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KList; - -import java.util.Iterator; - -public class Sphere implements Iterator, Cloneable { - private final KList blocks; - private int i = 0; - - public Sphere(int radius) { - int dist = radius * radius * radius; - - blocks = new KList<>(); - for (int x = -radius; x <= radius; x++) { - for (int z = -radius; z <= radius; z++) { - for (int y = -radius; y <= radius; y++) { - if (x * x + z * z + y * y > dist) - continue; - - blocks.add(new BlockPosition(x, y, z)); - } - } - } - } - - private Sphere(KList blocks) { - this.blocks = blocks.copy(); - } - - public void reset() { - i = 0; - } - - @Override - public boolean hasNext() { - return i < blocks.size(); - } - - @Override - public BlockPosition next() { - return blocks.get(i++); - } - - @Override - public Sphere clone() { - return new Sphere(blocks); - } -} diff --git a/src/main/java/com/volmit/adapt/util/Spiraled.java b/src/main/java/com/volmit/adapt/util/Spiraled.java deleted file mode 100644 index 15955c723..000000000 --- a/src/main/java/com/volmit/adapt/util/Spiraled.java +++ /dev/null @@ -1,24 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -@FunctionalInterface -public interface Spiraled { - void on(int x, int z); -} diff --git a/src/main/java/com/volmit/adapt/util/Spiraler.java b/src/main/java/com/volmit/adapt/util/Spiraler.java deleted file mode 100644 index 3ab93f4f6..000000000 --- a/src/main/java/com/volmit/adapt/util/Spiraler.java +++ /dev/null @@ -1,77 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class Spiraler { - private final Spiraled spiraled; - int x, z, dx, dz, sizeX, sizeZ, t, maxI, i; - int ox, oz; - - public Spiraler(int sizeX, int sizeZ, Spiraled spiraled) { - ox = 0; - oz = 0; - this.spiraled = spiraled; - retarget(sizeX, sizeZ); - } - - static void Spiral(int X, int Y) { - - } - - public void drain() { - while (hasNext()) { - next(); - } - } - - public Spiraler setOffset(int ox, int oz) { - this.ox = ox; - this.oz = oz; - return this; - } - - public void retarget(int sizeX, int sizeZ) { - this.sizeX = sizeX; - this.sizeZ = sizeZ; - x = z = dx = 0; - dz = -1; - i = 0; - t = Math.max(sizeX, sizeZ); - maxI = t * t; - } - - public boolean hasNext() { - return i < maxI; - } - - public void next() { - if ((-sizeX / 2 <= x) && (x <= sizeX / 2) && (-sizeZ / 2 <= z) && (z <= sizeZ / 2)) { - spiraled.on(x + ox, z + ox); - } - - if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) { - t = dx; - dx = -dz; - dz = t; - } - x += dx; - z += dz; - i++; - } -} diff --git a/src/main/java/com/volmit/adapt/util/StringTag.java b/src/main/java/com/volmit/adapt/util/StringTag.java deleted file mode 100644 index cb0791cbb..000000000 --- a/src/main/java/com/volmit/adapt/util/StringTag.java +++ /dev/null @@ -1,59 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * The TAG_String tag. - * - * @author Graham Edgecombe - */ -public final class StringTag extends Tag { - - /** - * The value. - */ - private final String value; - - /** - * Creates the tag. - * - * @param name The name. - * @param value The value. - */ - public StringTag(String name, String value) { - super(name); - this.value = value; - } - - @Override - public String getValue() { - return value; - } - - @Override - public String toString() { - String name = getName(); - String append = ""; - if (name != null && !name.equals("")) { - append = "(\"" + this.getName() + "\")"; - } - return "TAG_String" + append + ": " + value; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Supplier2.java b/src/main/java/com/volmit/adapt/util/Supplier2.java deleted file mode 100644 index c4108e5b7..000000000 --- a/src/main/java/com/volmit/adapt/util/Supplier2.java +++ /dev/null @@ -1,23 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public interface Supplier2 { - void get(T t, TT tt); -} diff --git a/src/main/java/com/volmit/adapt/util/Supplier3.java b/src/main/java/com/volmit/adapt/util/Supplier3.java deleted file mode 100644 index d0e00915a..000000000 --- a/src/main/java/com/volmit/adapt/util/Supplier3.java +++ /dev/null @@ -1,23 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public interface Supplier3 { - void get(T t, TT tt, TTT ttt); -} diff --git a/src/main/java/com/volmit/adapt/util/Switch.java b/src/main/java/com/volmit/adapt/util/Switch.java deleted file mode 100644 index 9139a55ce..000000000 --- a/src/main/java/com/volmit/adapt/util/Switch.java +++ /dev/null @@ -1,42 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class Switch { - private volatile boolean b; - - /** - * Defaulted off - */ - public Switch() { - b = false; - } - - public void flip() { - b = true; - } - - public boolean isFlipped() { - return b; - } - - public void reset() { - b = false; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Tag.java b/src/main/java/com/volmit/adapt/util/Tag.java deleted file mode 100644 index 65d6a3e36..000000000 --- a/src/main/java/com/volmit/adapt/util/Tag.java +++ /dev/null @@ -1,58 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Represents a single NBT tag. - * - * @author Graham Edgecombe - */ -public abstract class Tag { - - /** - * The name of this tag. - */ - private final String name; - - /** - * Creates the tag with the specified name. - * - * @param name The name. - */ - public Tag(String name) { - this.name = name; - } - - /** - * Gets the name of this tag. - * - * @return The name of this tag. - */ - public final String getName() { - return name; - } - - /** - * Gets the value of this tag. - * - * @return The value of this tag. - */ - public abstract Object getValue(); - -} diff --git a/src/main/java/com/volmit/adapt/util/TaskExecutor.java b/src/main/java/com/volmit/adapt/util/TaskExecutor.java deleted file mode 100644 index 5d8760490..000000000 --- a/src/main/java/com/volmit/adapt/util/TaskExecutor.java +++ /dev/null @@ -1,204 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; -import java.util.concurrent.ForkJoinWorkerThread; - -public class TaskExecutor { - private final ExecutorService service; - private int xc; - - public TaskExecutor(int threadLimit, int priority, String name) { - xc = 1; - - if (threadLimit == 1) { - service = Executors.newSingleThreadExecutor((r) -> - { - Thread t = new Thread(r); - t.setName(name); - t.setPriority(priority); - - return t; - }); - } else if (threadLimit > 1) { - final ForkJoinWorkerThreadFactory factory = new ForkJoinWorkerThreadFactory() { - @Override - public ForkJoinWorkerThread newThread(ForkJoinPool pool) { - final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); - worker.setName(name + " " + xc++); - worker.setPriority(priority); - return worker; - } - }; - - service = new ForkJoinPool(threadLimit, factory, null, false); - } else { - service = Executors.newCachedThreadPool((r) -> - { - Thread t = new Thread(r); - t.setName(name + " " + xc++); - t.setPriority(priority); - - return t; - }); - } - } - - public TaskGroup startWork() { - return new TaskGroup(this); - } - - public void close() { - J.a(() -> - { - J.sleep(10000); - service.shutdown(); - }); - } - - public void closeNow() { - service.shutdown(); - } - - public enum TaskState { - QUEUED, - RUNNING, - COMPLETED, - FAILED - } - - public static class TaskGroup { - private final List tasks; - private final TaskExecutor e; - - public TaskGroup(TaskExecutor e) { - tasks = new ArrayList<>(); - this.e = e; - } - - public TaskGroup queue(NastyRunnable... r) { - for (NastyRunnable i : r) { - tasks.add(new AssignedTask(i)); - } - - return this; - } - - public TaskGroup queue(List r) { - for (NastyRunnable i : r) { - tasks.add(new AssignedTask(i)); - } - - return this; - } - - public TaskResult execute() { - double timeElapsed = 0; - int tasksExecuted = 0; - int tasksFailed = 0; - int tasksCompleted = 0; - tasks.forEach((t) -> t.go(e)); - long msv = M.ns(); - - waiting: - while (true) { - try { - Thread.sleep(0); - } catch (InterruptedException e1) { - Adapt.verbose("Interrupted while waiting for tasks to complete"); - } - - for (AssignedTask i : tasks) { - if (i.state.equals(TaskState.QUEUED) || i.state.equals(TaskState.RUNNING)) { - continue waiting; - } - } - - timeElapsed = (double) (M.ns() - msv) / 1000000D; - - for (AssignedTask i : tasks) { - if (i.state.equals(TaskState.COMPLETED)) { - tasksCompleted++; - } else { - tasksFailed++; - } - - tasksExecuted++; - } - - break; - } - - return new TaskResult(timeElapsed, tasksExecuted, tasksFailed, tasksCompleted); - } - } - - @ToString - public static class TaskResult { - public final double timeElapsed; - public final int tasksExecuted; - public final int tasksFailed; - public final int tasksCompleted; - - public TaskResult(double timeElapsed, int tasksExecuted, int tasksFailed, int tasksCompleted) { - this.timeElapsed = timeElapsed; - this.tasksExecuted = tasksExecuted; - this.tasksFailed = tasksFailed; - this.tasksCompleted = tasksCompleted; - } - } - - public static class AssignedTask { - @Getter - private final NastyRunnable task; - @Getter - @Setter - private TaskState state; - - public AssignedTask(NastyRunnable task) { - this.task = task; - state = TaskState.QUEUED; - } - - public void go(TaskExecutor e) { - e.service.execute(() -> - { - state = TaskState.RUNNING; - try { - task.run(); - state = TaskState.COMPLETED; - } catch (Throwable ex) { - ex.printStackTrace(); - state = TaskState.FAILED; - } - }); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/ThreadMonitor.java b/src/main/java/com/volmit/adapt/util/ThreadMonitor.java deleted file mode 100644 index 91d0cc3b6..000000000 --- a/src/main/java/com/volmit/adapt/util/ThreadMonitor.java +++ /dev/null @@ -1,87 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; - -/** - * Not particularly efficient or perfectly accurate but is great at fast thread - * switching detection - * - * @author dan - */ -public class ThreadMonitor extends Thread { - private final Thread monitor; - private final ChronoLatch cl; - private final RollingSequence sq = new RollingSequence(3); - int cycles = 0; - private boolean running; - private State lastState; - private PrecisionStopwatch st; - - private ThreadMonitor(Thread monitor) { - running = true; - st = PrecisionStopwatch.start(); - this.monitor = monitor; - lastState = State.NEW; - cl = new ChronoLatch(1000); - start(); - } - - public static ThreadMonitor bind(Thread monitor) { - return new ThreadMonitor(monitor); - } - - public void run() { - while (running) { - try { - Thread.sleep(0); - State s = monitor.getState(); - if (lastState != s) { - cycles++; - pushState(s); - } - - lastState = s; - - if (cl.flip()) { - Adapt.info("Cycles: " + Form.f(cycles) + " (" + Form.duration(sq.getAverage(), 2) + ")"); - } - } catch (Throwable e) { - running = false; - break; - } - } - } - - public void pushState(State s) { - if (s != State.RUNNABLE) { - if (st != null) { - sq.put(st.getMilliseconds()); - } - } else { - - st = PrecisionStopwatch.start(); - } - } - - public void unbind() { - running = false; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Tuple2d.java b/src/main/java/com/volmit/adapt/util/Tuple2d.java deleted file mode 100644 index 04daeca2b..000000000 --- a/src/main/java/com/volmit/adapt/util/Tuple2d.java +++ /dev/null @@ -1,581 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A generic 2-element tuple that is represented by double-precision - * floating point x,y coordinates. - */ -public abstract class Tuple2d implements java.io.Serializable, Cloneable { - - static final long serialVersionUID = 6205762482756093838L; - - /** - * The x coordinate. - */ - public double x; - - /** - * The y coordinate. - */ - public double y; - - - /** - * Constructs and initializes a Tuple2d from the specified xy coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - */ - public Tuple2d(double x, double y) { - this.x = x; - this.y = y; - } - - - /** - * Constructs and initializes a Tuple2d from the specified array. - * - * @param t the array of length 2 containing xy in order - */ - public Tuple2d(double[] t) { - this.x = t[0]; - this.y = t[1]; - } - - - /** - * Constructs and initializes a Tuple2d from the specified Tuple2d. - * - * @param t1 the Tuple2d containing the initialization x y data - */ - public Tuple2d(Tuple2d t1) { - this.x = t1.x; - this.y = t1.y; - } - - - /** - * Constructs and initializes a Tuple2d from the specified Tuple2f. - * - * @param t1 the Tuple2f containing the initialization x y data - */ - public Tuple2d(Tuple2f t1) { - this.x = t1.x; - this.y = t1.y; - } - - /** - * Constructs and initializes a Tuple2d to (0,0). - */ - public Tuple2d() { - this.x = 0.0; - this.y = 0.0; - } - - - /** - * Sets the value of this tuple to the specified xy coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - */ - public final void set(double x, double y) { - this.x = x; - this.y = y; - } - - - /** - * Sets the value of this tuple from the 2 values specified in - * the array. - * - * @param t the array of length 2 containing xy in order - */ - public final void set(double[] t) { - this.x = t[0]; - this.y = t[1]; - } - - - /** - * Sets the value of this tuple to the value of the Tuple2d argument. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple2d t1) { - this.x = t1.x; - this.y = t1.y; - } - - - /** - * Sets the value of this tuple to the value of Tuple2f t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple2f t1) { - this.x = t1.x; - this.y = t1.y; - } - - /** - * Copies the value of the elements of this tuple into the array t. - * - * @param t the array that will contain the values of the vector - */ - public final void get(double[] t) { - t[0] = this.x; - t[1] = this.y; - } - - - /** - * Sets the value of this tuple to the vector sum of tuples t1 and t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void add(Tuple2d t1, Tuple2d t2) { - this.x = t1.x + t2.x; - this.y = t1.y + t2.y; - } - - - /** - * Sets the value of this tuple to the vector sum of itself and tuple t1. - * - * @param t1 the other tuple - */ - public final void add(Tuple2d t1) { - this.x += t1.x; - this.y += t1.y; - } - - - /** - * Sets the value of this tuple to the vector difference of - * tuple t1 and t2 (this = t1 - t2). - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void sub(Tuple2d t1, Tuple2d t2) { - this.x = t1.x - t2.x; - this.y = t1.y - t2.y; - } - - - /** - * Sets the value of this tuple to the vector difference of - * itself and tuple t1 (this = this - t1). - * - * @param t1 the other vector - */ - public final void sub(Tuple2d t1) { - this.x -= t1.x; - this.y -= t1.y; - } - - - /** - * Sets the value of this tuple to the negation of tuple t1. - * - * @param t1 the source vector - */ - public final void negate(Tuple2d t1) { - this.x = -t1.x; - this.y = -t1.y; - } - - - /** - * Negates the value of this vector in place. - */ - public final void negate() { - this.x = -this.x; - this.y = -this.y; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1. - * - * @param s the scalar value - * @param t1 the source tuple - */ - public final void scale(double s, Tuple2d t1) { - this.x = s * t1.x; - this.y = s * t1.y; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself. - * - * @param s the scalar value - */ - public final void scale(double s) { - this.x *= s; - this.y *= s; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1 and then adds tuple t2 (this = s*t1 + t2). - * - * @param s the scalar value - * @param t1 the tuple to be multipled - * @param t2 the tuple to be added - */ - public final void scaleAdd(double s, Tuple2d t1, Tuple2d t2) { - this.x = s * t1.x + t2.x; - this.y = s * t1.y + t2.y; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself and then adds tuple t1 (this = s*this + t1). - * - * @param s the scalar value - * @param t1 the tuple to be added - */ - public final void scaleAdd(double s, Tuple2d t1) { - this.x = s * this.x + t1.x; - this.y = s * this.y + t1.y; - } - - - /** - * Returns a hash code value based on the data values in this - * object. Two different Tuple2d objects with identical data values - * (i.e., Tuple2d.equals returns true) will return the same hash - * code value. Two objects with different data members may return the - * same hash value, although this is not likely. - * - * @return the integer hash code value - */ - public int hashCode() { - long bits = 1L; - bits = 31L * bits + VecMathUtil.doubleToLongBits(x); - bits = 31L * bits + VecMathUtil.doubleToLongBits(y); - return (int) (bits ^ (bits >> 32)); - } - - - /** - * Returns true if all of the data members of Tuple2d t1 are - * equal to the corresponding data members in this Tuple2d. - * - * @param t1 the vector with which the comparison is made - * @return true or false - */ - public boolean equals(Tuple2d t1) { - try { - return (this.x == t1.x && this.y == t1.y); - } catch (NullPointerException e2) { - return false; - } - - } - - /** - * Returns true if the Object t1 is of type Tuple2d and all of the - * data members of t1 are equal to the corresponding data members in - * this Tuple2d. - * - * @param t1 the object with which the comparison is made - * @return true or false - */ - public boolean equals(Object t1) { - try { - Tuple2d t2 = (Tuple2d) t1; - return (this.x == t2.x && this.y == t2.y); - } catch (NullPointerException | ClassCastException e2) { - return false; - } - - } - - /** - * Returns true if the L-infinite distance between this tuple - * and tuple t1 is less than or equal to the epsilon parameter, - * otherwise returns false. The L-infinite - * distance is equal to MAX[abs(x1-x2), abs(y1-y2)]. - * - * @param t1 the tuple to be compared to this tuple - * @param epsilon the threshold value - * @return true or false - */ - public boolean epsilonEquals(Tuple2d t1, double epsilon) { - double diff; - - diff = x - t1.x; - if (Double.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = y - t1.y; - if (Double.isNaN(diff)) return false; - return !((diff < 0 ? -diff : diff) > epsilon); - } - - /** - * Returns a string that contains the values of this Tuple2d. - * The form is (x,y). - * - * @return the String representation - */ - public String toString() { - return ("(" + this.x + ", " + this.y + ")"); - } - - - /** - * Clamps the tuple parameter to the range [low, high] and - * places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clamp(double min, double max, Tuple2d t) { - if (t.x > max) { - x = max; - } else if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else if (t.y < min) { - y = min; - } else { - y = t.y; - } - - } - - - /** - * Clamps the minimum value of the tuple parameter to the min - * parameter and places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMin(double min, Tuple2d t) { - if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y < min) { - y = min; - } else { - y = t.y; - } - - } - - - /** - * Clamps the maximum value of the tuple parameter to the max - * parameter and places the values into this tuple. - * - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMax(double max, Tuple2d t) { - if (t.x > max) { - x = max; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else { - y = t.y; - } - - } - - - /** - * Sets each component of the tuple parameter to its absolute - * value and places the modified values into this tuple. - * - * @param t the source tuple, which will not be modified - */ - public final void absolute(Tuple2d t) { - x = Math.abs(t.x); - y = Math.abs(t.y); - } - - - /** - * Clamps this tuple to the range [low, high]. - * - * @param min the lowest value in this tuple after clamping - * @param max the highest value in this tuple after clamping - */ - public final void clamp(double min, double max) { - if (x > max) { - x = max; - } else if (x < min) { - x = min; - } - - if (y > max) { - y = max; - } else if (y < min) { - y = min; - } - - } - - - /** - * Clamps the minimum value of this tuple to the min parameter. - * - * @param min the lowest value in this tuple after clamping - */ - public final void clampMin(double min) { - if (x < min) x = min; - if (y < min) y = min; - } - - - /** - * Clamps the maximum value of this tuple to the max parameter. - * - * @param max the highest value in the tuple after clamping - */ - public final void clampMax(double max) { - if (x > max) x = max; - if (y > max) y = max; - } - - - /** - * Sets each component of this tuple to its absolute value. - */ - public final void absolute() { - x = Math.abs(x); - y = Math.abs(y); - } - - - /** - * Linearly interpolates between tuples t1 and t2 and places the - * result into this tuple: this = (1-alpha)*t1 + alpha*t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - * @param alpha the alpha interpolation parameter - */ - public final void interpolate(Tuple2d t1, Tuple2d t2, double alpha) { - this.x = (1 - alpha) * t1.x + alpha * t2.x; - this.y = (1 - alpha) * t1.y + alpha * t2.y; - } - - - /** - * Linearly interpolates between this tuple and tuple t1 and - * places the result into this tuple: this = (1-alpha)*this + alpha*t1. - * - * @param t1 the first tuple - * @param alpha the alpha interpolation parameter - */ - public final void interpolate(Tuple2d t1, double alpha) { - this.x = (1 - alpha) * this.x + alpha * t1.x; - this.y = (1 - alpha) * this.y + alpha * t1.y; - - } - - /** - * Creates a new object of the same class as this object. - * - * @return a clone of this instance. - * @throws OutOfMemoryError if there is not enough memory. - * @see java.lang.Cloneable - * @since vecmath 1.3 - */ - public Object clone() { - // Since there are no arrays we can just use Object.clone() - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - - /** - * Get the x coordinate. - * - * @return the x coordinate. - * @since vecmath 1.5 - */ - public final double getX() { - return x; - } - - - /** - * Set the x coordinate. - * - * @param x value to x coordinate. - * @since vecmath 1.5 - */ - public final void setX(double x) { - this.x = x; - } - - - /** - * Get the y coordinate. - * - * @return the y coordinate. - * @since vecmath 1.5 - */ - public final double getY() { - return y; - } - - - /** - * Set the y coordinate. - * - * @param y value to y coordinate. - * @since vecmath 1.5 - */ - public final void setY(double y) { - this.y = y; - } - -} diff --git a/src/main/java/com/volmit/adapt/util/Tuple2f.java b/src/main/java/com/volmit/adapt/util/Tuple2f.java deleted file mode 100644 index 97e2a78af..000000000 --- a/src/main/java/com/volmit/adapt/util/Tuple2f.java +++ /dev/null @@ -1,586 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A generic 2-element tuple that is represented by single-precision - * floating point x,y coordinates. - */ -public abstract class Tuple2f implements java.io.Serializable, Cloneable { - - static final long serialVersionUID = 9011180388985266884L; - - /** - * The x coordinate. - */ - public float x; - - /** - * The y coordinate. - */ - public float y; - - - /** - * Constructs and initializes a Tuple2f from the specified xy coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - */ - public Tuple2f(float x, float y) { - this.x = x; - this.y = y; - } - - - /** - * Constructs and initializes a Tuple2f from the specified array. - * - * @param t the array of length 2 containing xy in order - */ - public Tuple2f(float[] t) { - this.x = t[0]; - this.y = t[1]; - } - - - /** - * Constructs and initializes a Tuple2f from the specified Tuple2f. - * - * @param t1 the Tuple2f containing the initialization x y data - */ - public Tuple2f(Tuple2f t1) { - this.x = t1.x; - this.y = t1.y; - } - - - /** - * Constructs and initializes a Tuple2f from the specified Tuple2d. - * - * @param t1 the Tuple2d containing the initialization x y data - */ - public Tuple2f(Tuple2d t1) { - this.x = (float) t1.x; - this.y = (float) t1.y; - } - - - /** - * Constructs and initializes a Tuple2f to (0,0). - */ - public Tuple2f() { - this.x = (float) 0.0; - this.y = (float) 0.0; - } - - - /** - * Sets the value of this tuple to the specified xy coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - */ - public final void set(float x, float y) { - this.x = x; - this.y = y; - } - - - /** - * Sets the value of this tuple from the 2 values specified in - * the array. - * - * @param t the array of length 2 containing xy in order - */ - public final void set(float[] t) { - this.x = t[0]; - this.y = t[1]; - } - - - /** - * Sets the value of this tuple to the value of the Tuple2f argument. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple2f t1) { - this.x = t1.x; - this.y = t1.y; - } - - - /** - * Sets the value of this tuple to the value of the Tuple2d argument. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple2d t1) { - this.x = (float) t1.x; - this.y = (float) t1.y; - } - - - /** - * Copies the value of the elements of this tuple into the array t. - * - * @param t the array that will contain the values of the vector - */ - public final void get(float[] t) { - t[0] = this.x; - t[1] = this.y; - } - - - /** - * Sets the value of this tuple to the vector sum of tuples t1 and t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void add(Tuple2f t1, Tuple2f t2) { - this.x = t1.x + t2.x; - this.y = t1.y + t2.y; - } - - - /** - * Sets the value of this tuple to the vector sum of itself and tuple t1. - * - * @param t1 the other tuple - */ - public final void add(Tuple2f t1) { - this.x += t1.x; - this.y += t1.y; - } - - - /** - * Sets the value of this tuple to the vector difference of - * tuple t1 and t2 (this = t1 - t2). - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void sub(Tuple2f t1, Tuple2f t2) { - this.x = t1.x - t2.x; - this.y = t1.y - t2.y; - } - - - /** - * Sets the value of this tuple to the vector difference of - * itself and tuple t1 (this = this - t1). - * - * @param t1 the other tuple - */ - public final void sub(Tuple2f t1) { - this.x -= t1.x; - this.y -= t1.y; - } - - - /** - * Sets the value of this tuple to the negation of tuple t1. - * - * @param t1 the source tuple - */ - public final void negate(Tuple2f t1) { - this.x = -t1.x; - this.y = -t1.y; - } - - - /** - * Negates the value of this vector in place. - */ - public final void negate() { - this.x = -this.x; - this.y = -this.y; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1. - * - * @param s the scalar value - * @param t1 the source tuple - */ - public final void scale(float s, Tuple2f t1) { - this.x = s * t1.x; - this.y = s * t1.y; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself. - * - * @param s the scalar value - */ - public final void scale(float s) { - this.x *= s; - this.y *= s; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1 and then adds tuple t2 (this = s*t1 + t2). - * - * @param s the scalar value - * @param t1 the tuple to be multipled - * @param t2 the tuple to be added - */ - public final void scaleAdd(float s, Tuple2f t1, Tuple2f t2) { - this.x = s * t1.x + t2.x; - this.y = s * t1.y + t2.y; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself and then adds tuple t1 (this = s*this + t1). - * - * @param s the scalar value - * @param t1 the tuple to be added - */ - public final void scaleAdd(float s, Tuple2f t1) { - this.x = s * this.x + t1.x; - this.y = s * this.y + t1.y; - } - - - /** - * Returns a hash code value based on the data values in this - * object. Two different Tuple2f objects with identical data values - * (i.e., Tuple2f.equals returns true) will return the same hash - * code value. Two objects with different data members may return the - * same hash value, although this is not likely. - * - * @return the integer hash code value - */ - public int hashCode() { - long bits = 1L; - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(x); - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(y); - return (int) (bits ^ (bits >> 32)); - } - - - /** - * Returns true if all of the data members of Tuple2f t1 are - * equal to the corresponding data members in this Tuple2f. - * - * @param t1 the vector with which the comparison is made - * @return true or false - */ - public boolean equals(Tuple2f t1) { - try { - return (this.x == t1.x && this.y == t1.y); - } catch (NullPointerException e2) { - return false; - } - - } - - /** - * Returns true if the Object t1 is of type Tuple2f and all of the - * data members of t1 are equal to the corresponding data members in - * this Tuple2f. - * - * @param t1 the object with which the comparison is made - * @return true or false - */ - public boolean equals(Object t1) { - try { - Tuple2f t2 = (Tuple2f) t1; - return (this.x == t2.x && this.y == t2.y); - } catch (NullPointerException e2) { - return false; - } catch (ClassCastException e1) { - return false; - } - - } - - /** - * Returns true if the L-infinite distance between this tuple - * and tuple t1 is less than or equal to the epsilon parameter, - * otherwise returns false. The L-infinite - * distance is equal to MAX[abs(x1-x2), abs(y1-y2)]. - * - * @param t1 the tuple to be compared to this tuple - * @param epsilon the threshold value - * @return true or false - */ - public boolean epsilonEquals(Tuple2f t1, float epsilon) { - float diff; - - diff = x - t1.x; - if (Float.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = y - t1.y; - if (Float.isNaN(diff)) return false; - return !((diff < 0 ? -diff : diff) > epsilon); - } - - /** - * Returns a string that contains the values of this Tuple2f. - * The form is (x,y). - * - * @return the String representation - */ - public String toString() { - return ("(" + this.x + ", " + this.y + ")"); - } - - - /** - * Clamps the tuple parameter to the range [low, high] and - * places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clamp(float min, float max, Tuple2f t) { - if (t.x > max) { - x = max; - } else if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else if (t.y < min) { - y = min; - } else { - y = t.y; - } - - } - - - /** - * Clamps the minimum value of the tuple parameter to the min - * parameter and places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMin(float min, Tuple2f t) { - if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y < min) { - y = min; - } else { - y = t.y; - } - - } - - - /** - * Clamps the maximum value of the tuple parameter to the max - * parameter and places the values into this tuple. - * - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMax(float max, Tuple2f t) { - if (t.x > max) { - x = max; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else { - y = t.y; - } - - } - - - /** - * Sets each component of the tuple parameter to its absolute - * value and places the modified values into this tuple. - * - * @param t the source tuple, which will not be modified - */ - public final void absolute(Tuple2f t) { - x = Math.abs(t.x); - y = Math.abs(t.y); - } - - - /** - * Clamps this tuple to the range [low, high]. - * - * @param min the lowest value in this tuple after clamping - * @param max the highest value in this tuple after clamping - */ - public final void clamp(float min, float max) { - if (x > max) { - x = max; - } else if (x < min) { - x = min; - } - - if (y > max) { - y = max; - } else if (y < min) { - y = min; - } - - } - - - /** - * Clamps the minimum value of this tuple to the min parameter. - * - * @param min the lowest value in this tuple after clamping - */ - public final void clampMin(float min) { - if (x < min) x = min; - if (y < min) y = min; - } - - - /** - * Clamps the maximum value of this tuple to the max parameter. - * - * @param max the highest value in the tuple after clamping - */ - public final void clampMax(float max) { - if (x > max) x = max; - if (y > max) y = max; - } - - - /** - * Sets each component of this tuple to its absolute value. - */ - public final void absolute() { - x = Math.abs(x); - y = Math.abs(y); - } - - - /** - * Linearly interpolates between tuples t1 and t2 and places the - * result into this tuple: this = (1-alpha)*t1 + alpha*t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - * @param alpha the alpha interpolation parameter - */ - public final void interpolate(Tuple2f t1, Tuple2f t2, float alpha) { - this.x = (1 - alpha) * t1.x + alpha * t2.x; - this.y = (1 - alpha) * t1.y + alpha * t2.y; - - } - - - /** - * Linearly interpolates between this tuple and tuple t1 and - * places the result into this tuple: this = (1-alpha)*this + alpha*t1. - * - * @param t1 the first tuple - * @param alpha the alpha interpolation parameter - */ - public final void interpolate(Tuple2f t1, float alpha) { - - this.x = (1 - alpha) * this.x + alpha * t1.x; - this.y = (1 - alpha) * this.y + alpha * t1.y; - - } - - /** - * Creates a new object of the same class as this object. - * - * @return a clone of this instance. - * @throws OutOfMemoryError if there is not enough memory. - * @see java.lang.Cloneable - * @since vecmath 1.3 - */ - public Object clone() { - // Since there are no arrays we can just use Object.clone() - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - - /** - * Get the x coordinate. - * - * @return the x coordinate. - * @since vecmath 1.5 - */ - public final float getX() { - return x; - } - - - /** - * Set the x coordinate. - * - * @param x value to x coordinate. - * @since vecmath 1.5 - */ - public final void setX(float x) { - this.x = x; - } - - - /** - * Get the y coordinate. - * - * @return the y coordinate. - * @since vecmath 1.5 - */ - public final float getY() { - return y; - } - - - /** - * Set the y coordinate. - * - * @param y value to y coordinate. - * @since vecmath 1.5 - */ - public final void setY(float y) { - this.y = y; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Tuple3d.java b/src/main/java/com/volmit/adapt/util/Tuple3d.java deleted file mode 100644 index f3ea10749..000000000 --- a/src/main/java/com/volmit/adapt/util/Tuple3d.java +++ /dev/null @@ -1,744 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A generic 3-element tuple that is represented by double-precision - * floating point x,y,z coordinates. - */ -public abstract class Tuple3d implements java.io.Serializable, Cloneable { - - static final long serialVersionUID = 5542096614926168415L; - - /** - * The x coordinate. - */ - public double x; - - /** - * The y coordinate. - */ - public double y; - - /** - * The z coordinate. - */ - public double z; - - - /** - * Constructs and initializes a Tuple3d from the specified xyz coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - */ - public Tuple3d(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Constructs and initializes a Tuple3d from the array of length 3. - * - * @param t the array of length 3 containing xyz in order - */ - public Tuple3d(double[] t) { - this.x = t[0]; - this.y = t[1]; - this.z = t[2]; - } - - /** - * Constructs and initializes a Tuple3d from the specified Tuple3d. - * - * @param t1 the Tuple3d containing the initialization x y z data - */ - public Tuple3d(Tuple3d t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - } - - /** - * Constructs and initializes a Tuple3d from the specified Tuple3f. - * - * @param t1 the Tuple3f containing the initialization x y z data - */ - public Tuple3d(Tuple3f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - } - - /** - * Constructs and initializes a Tuple3d to (0,0,0). - */ - public Tuple3d() { - this.x = 0.0; - this.y = 0.0; - this.z = 0.0; - } - - /** - * Sets the value of this tuple to the specified xyz coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - */ - public final void set(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Sets the value of this tuple to the value of the xyz coordinates - * located in the array of length 3. - * - * @param t the array of length 3 containing xyz in order - */ - public final void set(double[] t) { - this.x = t[0]; - this.y = t[1]; - this.z = t[2]; - } - - /** - * Sets the value of this tuple to the value of tuple t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple3d t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - } - - /** - * Sets the value of this tuple to the value of tuple t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple3f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - } - - /** - * Copies the x,y,z coordinates of this tuple into the array t - * of length 3. - * - * @param t the target array - */ - public final void get(double[] t) { - t[0] = this.x; - t[1] = this.y; - t[2] = this.z; - } - - - /** - * Copies the x,y,z coordinates of this tuple into the tuple t. - * - * @param t the Tuple3d object into which the values of this object are copied - */ - public final void get(Tuple3d t) { - t.x = this.x; - t.y = this.y; - t.z = this.z; - } - - - /** - * Sets the value of this tuple to the sum of tuples t1 and t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void add(Tuple3d t1, Tuple3d t2) { - this.x = t1.x + t2.x; - this.y = t1.y + t2.y; - this.z = t1.z + t2.z; - } - - - /** - * Sets the value of this tuple to the sum of itself and t1. - * - * @param t1 the other tuple - */ - public final void add(Tuple3d t1) { - this.x += t1.x; - this.y += t1.y; - this.z += t1.z; - } - - /** - * Sets the value of this tuple to the difference of tuples - * t1 and t2 (this = t1 - t2). - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void sub(Tuple3d t1, Tuple3d t2) { - this.x = t1.x - t2.x; - this.y = t1.y - t2.y; - this.z = t1.z - t2.z; - } - - /** - * Sets the value of this tuple to the difference - * of itself and t1 (this = this - t1). - * - * @param t1 the other tuple - */ - public final void sub(Tuple3d t1) { - this.x -= t1.x; - this.y -= t1.y; - this.z -= t1.z; - } - - - /** - * Sets the value of this tuple to the negation of tuple t1. - * - * @param t1 the source tuple - */ - public final void negate(Tuple3d t1) { - this.x = -t1.x; - this.y = -t1.y; - this.z = -t1.z; - } - - - /** - * Negates the value of this tuple in place. - */ - public final void negate() { - this.x = -this.x; - this.y = -this.y; - this.z = -this.z; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1. - * - * @param s the scalar value - * @param t1 the source tuple - */ - public final void scale(double s, Tuple3d t1) { - this.x = s * t1.x; - this.y = s * t1.y; - this.z = s * t1.z; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself. - * - * @param s the scalar value - */ - public final void scale(double s) { - this.x *= s; - this.y *= s; - this.z *= s; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1 and then adds tuple t2 (this = s*t1 + t2). - * - * @param s the scalar value - * @param t1 the tuple to be multipled - * @param t2 the tuple to be added - */ - public final void scaleAdd(double s, Tuple3d t1, Tuple3d t2) { - this.x = s * t1.x + t2.x; - this.y = s * t1.y + t2.y; - this.z = s * t1.z + t2.z; - } - - - /** - * @Deprecated Use scaleAdd(double,Tuple3d) instead - */ - public final void scaleAdd(double s, Tuple3f t1) { - scaleAdd(s, new Point3d(t1)); - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself and then adds tuple t1 (this = s*this + t1). - * - * @param s the scalar value - * @param t1 the tuple to be added - */ - public final void scaleAdd(double s, Tuple3d t1) { - this.x = s * this.x + t1.x; - this.y = s * this.y + t1.y; - this.z = s * this.z + t1.z; - } - - - /** - * Returns a string that contains the values of this Tuple3d. - * The form is (x,y,z). - * - * @return the String representation - */ - public String toString() { - return "(" + this.x + ", " + this.y + ", " + this.z + ")"; - } - - - /** - * Returns a hash code value based on the data values in this - * object. Two different Tuple3d objects with identical data values - * (i.e., Tuple3d.equals returns true) will return the same hash - * code value. Two objects with different data members may return the - * same hash value, although this is not likely. - * - * @return the integer hash code value - */ - public int hashCode() { - long bits = 1L; - bits = 31L * bits + VecMathUtil.doubleToLongBits(x); - bits = 31L * bits + VecMathUtil.doubleToLongBits(y); - bits = 31L * bits + VecMathUtil.doubleToLongBits(z); - return (int) (bits ^ (bits >> 32)); - } - - - /** - * Returns true if all of the data members of Tuple3d t1 are - * equal to the corresponding data members in this Tuple3d. - * - * @param t1 the tuple with which the comparison is made - * @return true or false - */ - public boolean equals(Tuple3d t1) { - try { - return (this.x == t1.x && this.y == t1.y && this.z == t1.z); - } catch (NullPointerException e2) { - return false; - } - } - - /** - * Returns true if the Object t1 is of type Tuple3d and all of the - * data members of t1 are equal to the corresponding data members in - * this Tuple3d. - * - * @param t1 the Object with which the comparison is made - * @return true or false - */ - public boolean equals(Object t1) { - try { - Tuple3d t2 = (Tuple3d) t1; - return (this.x == t2.x && this.y == t2.y && this.z == t2.z); - } catch (ClassCastException e1) { - return false; - } catch (NullPointerException e2) { - return false; - } - - } - - /** - * Returns true if the L-infinite distance between this tuple - * and tuple t1 is less than or equal to the epsilon parameter, - * otherwise returns false. The L-infinite - * distance is equal to MAX[abs(x1-x2), abs(y1-y2), abs(z1-z2)]. - * - * @param t1 the tuple to be compared to this tuple - * @param epsilon the threshold value - * @return true or false - */ - public boolean epsilonEquals(Tuple3d t1, double epsilon) { - double diff; - - diff = x - t1.x; - if (Double.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = y - t1.y; - if (Double.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = z - t1.z; - if (Double.isNaN(diff)) return false; - return !((diff < 0 ? -diff : diff) > epsilon); - - } - - - /** - * @Deprecated Use clamp(double,double,Tuple3d) instead - */ - public final void clamp(float min, float max, Tuple3d t) { - clamp(min, (double) max, t); - } - - - /** - * Clamps the tuple parameter to the range [low, high] and - * places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clamp(double min, double max, Tuple3d t) { - if (t.x > max) { - x = max; - } else if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else if (t.y < min) { - y = min; - } else { - y = t.y; - } - - if (t.z > max) { - z = max; - } else if (t.z < min) { - z = min; - } else { - z = t.z; - } - - } - - - /** - * @Deprecated Use clampMin(double,Tuple3d) instead - */ - public final void clampMin(float min, Tuple3d t) { - clampMin((double) min, t); - } - - - /** - * Clamps the minimum value of the tuple parameter to the min - * parameter and places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMin(double min, Tuple3d t) { - if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y < min) { - y = min; - } else { - y = t.y; - } - - if (t.z < min) { - z = min; - } else { - z = t.z; - } - - } - - - /** - * @Deprecated Use clampMax(double,Tuple3d) instead - */ - public final void clampMax(float max, Tuple3d t) { - clampMax((double) max, t); - } - - - /** - * Clamps the maximum value of the tuple parameter to the max - * parameter and places the values into this tuple. - * - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMax(double max, Tuple3d t) { - if (t.x > max) { - x = max; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else { - y = t.y; - } - - if (t.z > max) { - z = max; - } else { - z = t.z; - } - - } - - - /** - * Sets each component of the tuple parameter to its absolute - * value and places the modified values into this tuple. - * - * @param t the source tuple, which will not be modified - */ - public final void absolute(Tuple3d t) { - x = Math.abs(t.x); - y = Math.abs(t.y); - z = Math.abs(t.z); - - } - - - /** - * @Deprecated Use clamp(double,double) instead - */ - public final void clamp(float min, float max) { - clamp(min, (double) max); - } - - - /** - * Clamps this tuple to the range [low, high]. - * - * @param min the lowest value in this tuple after clamping - * @param max the highest value in this tuple after clamping - */ - public final void clamp(double min, double max) { - if (x > max) { - x = max; - } else if (x < min) { - x = min; - } - - if (y > max) { - y = max; - } else if (y < min) { - y = min; - } - - if (z > max) { - z = max; - } else if (z < min) { - z = min; - } - - } - - - /** - * @Deprecated Use clampMin(double) instead - */ - public final void clampMin(float min) { - clampMin((double) min); - } - - - /** - * Clamps the minimum value of this tuple to the min parameter. - * - * @param min the lowest value in this tuple after clamping - */ - public final void clampMin(double min) { - if (x < min) x = min; - if (y < min) y = min; - if (z < min) z = min; - - } - - - /** - * @Deprecated Use clampMax(double) instead - */ - public final void clampMax(float max) { - clampMax((double) max); - } - - - /** - * Clamps the maximum value of this tuple to the max parameter. - * - * @param max the highest value in the tuple after clamping - */ - public final void clampMax(double max) { - if (x > max) x = max; - if (y > max) y = max; - if (z > max) z = max; - } - - - /** - * Sets each component of this tuple to its absolute value. - */ - public final void absolute() { - x = Math.abs(x); - y = Math.abs(y); - z = Math.abs(z); - } - - - /** - * @Deprecated Use interpolate(Tuple3d,Tuple3d,double) instead - */ - public final void interpolate(Tuple3d t1, Tuple3d t2, float alpha) { - interpolate(t1, t2, (double) alpha); - } - - - /** - * Linearly interpolates between tuples t1 and t2 and places the - * result into this tuple: this = (1-alpha)*t1 + alpha*t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - * @param alpha the alpha interpolation parameter - */ - public final void interpolate(Tuple3d t1, Tuple3d t2, double alpha) { - this.x = (1 - alpha) * t1.x + alpha * t2.x; - this.y = (1 - alpha) * t1.y + alpha * t2.y; - this.z = (1 - alpha) * t1.z + alpha * t2.z; - } - - - /** - * @Deprecated Use interpolate(Tuple3d,double) instead - */ - public final void interpolate(Tuple3d t1, float alpha) { - interpolate(t1, (double) alpha); - } - - - /** - * Linearly interpolates between this tuple and tuple t1 and - * places the result into this tuple: this = (1-alpha)*this + alpha*t1. - * - * @param t1 the first tuple - * @param alpha the alpha interpolation parameter - */ - public final void interpolate(Tuple3d t1, double alpha) { - this.x = (1 - alpha) * this.x + alpha * t1.x; - this.y = (1 - alpha) * this.y + alpha * t1.y; - this.z = (1 - alpha) * this.z + alpha * t1.z; - } - - /** - * Creates a new object of the same class as this object. - * - * @return a clone of this instance. - * @throws OutOfMemoryError if there is not enough memory. - * @see java.lang.Cloneable - * @since vecmath 1.3 - */ - public Object clone() { - // Since there are no arrays we can just use Object.clone() - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - /** - * Get the x coordinate. - * - * @return the x coordinate. - * @since vecmath 1.5 - */ - public final double getX() { - return x; - } - - - /** - * Set the x coordinate. - * - * @param x value to x coordinate. - * @since vecmath 1.5 - */ - public final void setX(double x) { - this.x = x; - } - - - /** - * Get the y coordinate. - * - * @return the y coordinate. - * @since vecmath 1.5 - */ - public final double getY() { - return y; - } - - - /** - * Set the y coordinate. - * - * @param y value to y coordinate. - * @since vecmath 1.5 - */ - public final void setY(double y) { - this.y = y; - } - - /** - * Get the z coordinate. - * - * @return the z coordinate. - * @since vecmath 1.5 - */ - public final double getZ() { - return z; - } - - - /** - * Set the z coordinate. - * - * @param z value to z coordinate. - * @since vecmath 1.5 - */ - public final void setZ(double z) { - this.z = z; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Tuple3f.java b/src/main/java/com/volmit/adapt/util/Tuple3f.java deleted file mode 100644 index e74541623..000000000 --- a/src/main/java/com/volmit/adapt/util/Tuple3f.java +++ /dev/null @@ -1,689 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A generic 3-element tuple that is represented by single precision-floating - * point x,y,z coordinates. - */ -public abstract class Tuple3f implements java.io.Serializable, Cloneable { - - static final long serialVersionUID = 5019834619484343712L; - - /** - * The x coordinate. - */ - public float x; - - /** - * The y coordinate. - */ - public float y; - - /** - * The z coordinate. - */ - public float z; - - - /** - * Constructs and initializes a Tuple3f from the specified xyz coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - */ - public Tuple3f(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - } - - - /** - * Constructs and initializes a Tuple3f from the array of length 3. - * - * @param t the array of length 3 containing xyz in order - */ - public Tuple3f(float[] t) { - this.x = t[0]; - this.y = t[1]; - this.z = t[2]; - } - - - /** - * Constructs and initializes a Tuple3f from the specified Tuple3f. - * - * @param t1 the Tuple3f containing the initialization x y z data - */ - public Tuple3f(Tuple3f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - } - - - /** - * Constructs and initializes a Tuple3f from the specified Tuple3d. - * - * @param t1 the Tuple3d containing the initialization x y z data - */ - public Tuple3f(Tuple3d t1) { - this.x = (float) t1.x; - this.y = (float) t1.y; - this.z = (float) t1.z; - } - - - /** - * Constructs and initializes a Tuple3f to (0,0,0). - */ - public Tuple3f() { - this.x = 0.0f; - this.y = 0.0f; - this.z = 0.0f; - } - - - /** - * Returns a string that contains the values of this Tuple3f. - * The form is (x,y,z). - * - * @return the String representation - */ - public String toString() { - return "(" + this.x + ", " + this.y + ", " + this.z + ")"; - } - - - /** - * Sets the value of this tuple to the specified xyz coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - */ - public final void set(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - } - - - /** - * Sets the value of this tuple to the xyz coordinates specified in - * the array of length 3. - * - * @param t the array of length 3 containing xyz in order - */ - public final void set(float[] t) { - this.x = t[0]; - this.y = t[1]; - this.z = t[2]; - } - - - /** - * Sets the value of this tuple to the value of tuple t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple3f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - } - - - /** - * Sets the value of this tuple to the value of tuple t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple3d t1) { - this.x = (float) t1.x; - this.y = (float) t1.y; - this.z = (float) t1.z; - } - - - /** - * Gets the value of this tuple and copies the values into t. - * - * @param t the array of length 3 into which the values are copied - */ - public final void get(float[] t) { - t[0] = this.x; - t[1] = this.y; - t[2] = this.z; - } - - - /** - * Gets the value of this tuple and copies the values into t. - * - * @param t the Tuple3f object into which the values of this object are copied - */ - public final void get(Tuple3f t) { - t.x = this.x; - t.y = this.y; - t.z = this.z; - } - - - /** - * Sets the value of this tuple to the vector sum of tuples t1 and t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void add(Tuple3f t1, Tuple3f t2) { - this.x = t1.x + t2.x; - this.y = t1.y + t2.y; - this.z = t1.z + t2.z; - } - - - /** - * Sets the value of this tuple to the vector sum of itself and tuple t1. - * - * @param t1 the other tuple - */ - public final void add(Tuple3f t1) { - this.x += t1.x; - this.y += t1.y; - this.z += t1.z; - } - - - /** - * Sets the value of this tuple to the vector difference - * of tuples t1 and t2 (this = t1 - t2). - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void sub(Tuple3f t1, Tuple3f t2) { - this.x = t1.x - t2.x; - this.y = t1.y - t2.y; - this.z = t1.z - t2.z; - } - - - /** - * Sets the value of this tuple to the vector difference of - * itself and tuple t1 (this = this - t1) . - * - * @param t1 the other tuple - */ - public final void sub(Tuple3f t1) { - this.x -= t1.x; - this.y -= t1.y; - this.z -= t1.z; - } - - - /** - * Sets the value of this tuple to the negation of tuple t1. - * - * @param t1 the source tuple - */ - public final void negate(Tuple3f t1) { - this.x = -t1.x; - this.y = -t1.y; - this.z = -t1.z; - } - - - /** - * Negates the value of this tuple in place. - */ - public final void negate() { - this.x = -this.x; - this.y = -this.y; - this.z = -this.z; - } - - - /** - * Sets the value of this vector to the scalar multiplication - * of tuple t1. - * - * @param s the scalar value - * @param t1 the source tuple - */ - public final void scale(float s, Tuple3f t1) { - this.x = s * t1.x; - this.y = s * t1.y; - this.z = s * t1.z; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of the scale factor with this. - * - * @param s the scalar value - */ - public final void scale(float s) { - this.x *= s; - this.y *= s; - this.z *= s; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1 and then adds tuple t2 (this = s*t1 + t2). - * - * @param s the scalar value - * @param t1 the tuple to be scaled and added - * @param t2 the tuple to be added without a scale - */ - public final void scaleAdd(float s, Tuple3f t1, Tuple3f t2) { - this.x = s * t1.x + t2.x; - this.y = s * t1.y + t2.y; - this.z = s * t1.z + t2.z; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself and then adds tuple t1 (this = s*this + t1). - * - * @param s the scalar value - * @param t1 the tuple to be added - */ - public final void scaleAdd(float s, Tuple3f t1) { - this.x = s * this.x + t1.x; - this.y = s * this.y + t1.y; - this.z = s * this.z + t1.z; - } - - - /** - * Returns true if the Object t1 is of type Tuple3f and all of the - * data members of t1 are equal to the corresponding data members in - * this Tuple3f. - * - * @param t1 the vector with which the comparison is made - * @return true or false - */ - public boolean equals(Tuple3f t1) { - try { - return (this.x == t1.x && this.y == t1.y && this.z == t1.z); - } catch (NullPointerException e2) { - return false; - } - } - - /** - * Returns true if the Object t1 is of type Tuple3f and all of the - * data members of t1 are equal to the corresponding data members in - * this Tuple3f. - * - * @param t1 the Object with which the comparison is made - * @return true or false - */ - public boolean equals(Object t1) { - try { - Tuple3f t2 = (Tuple3f) t1; - return (this.x == t2.x && this.y == t2.y && this.z == t2.z); - } catch (NullPointerException e2) { - return false; - } catch (ClassCastException e1) { - return false; - } - } - - - /** - * Returns true if the L-infinite distance between this tuple - * and tuple t1 is less than or equal to the epsilon parameter, - * otherwise returns false. The L-infinite - * distance is equal to MAX[abs(x1-x2), abs(y1-y2), abs(z1-z2)]. - * - * @param t1 the tuple to be compared to this tuple - * @param epsilon the threshold value - * @return true or false - */ - public boolean epsilonEquals(Tuple3f t1, float epsilon) { - float diff; - - diff = x - t1.x; - if (Float.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = y - t1.y; - if (Float.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = z - t1.z; - if (Float.isNaN(diff)) return false; - return !((diff < 0 ? -diff : diff) > epsilon); - - } - - - /** - * Returns a hash code value based on the data values in this - * object. Two different Tuple3f objects with identical data values - * (i.e., Tuple3f.equals returns true) will return the same hash - * code value. Two objects with different data members may return the - * same hash value, although this is not likely. - * - * @return the integer hash code value - */ - public int hashCode() { - long bits = 1L; - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(x); - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(y); - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(z); - return (int) (bits ^ (bits >> 32)); - } - - - /** - * Clamps the tuple parameter to the range [low, high] and - * places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clamp(float min, float max, Tuple3f t) { - if (t.x > max) { - x = max; - } else if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else if (t.y < min) { - y = min; - } else { - y = t.y; - } - - if (t.z > max) { - z = max; - } else if (t.z < min) { - z = min; - } else { - z = t.z; - } - - } - - - /** - * Clamps the minimum value of the tuple parameter to the min - * parameter and places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMin(float min, Tuple3f t) { - if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y < min) { - y = min; - } else { - y = t.y; - } - - if (t.z < min) { - z = min; - } else { - z = t.z; - } - - } - - - /** - * Clamps the maximum value of the tuple parameter to the max - * parameter and places the values into this tuple. - * - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMax(float max, Tuple3f t) { - if (t.x > max) { - x = max; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else { - y = t.y; - } - - if (t.z > max) { - z = max; - } else { - z = t.z; - } - - } - - - /** - * Sets each component of the tuple parameter to its absolute - * value and places the modified values into this tuple. - * - * @param t the source tuple, which will not be modified - */ - public final void absolute(Tuple3f t) { - x = Math.abs(t.x); - y = Math.abs(t.y); - z = Math.abs(t.z); - } - - - /** - * Clamps this tuple to the range [low, high]. - * - * @param min the lowest value in this tuple after clamping - * @param max the highest value in this tuple after clamping - */ - public final void clamp(float min, float max) { - if (x > max) { - x = max; - } else if (x < min) { - x = min; - } - - if (y > max) { - y = max; - } else if (y < min) { - y = min; - } - - if (z > max) { - z = max; - } else if (z < min) { - z = min; - } - - } - - - /** - * Clamps the minimum value of this tuple to the min parameter. - * - * @param min the lowest value in this tuple after clamping - */ - public final void clampMin(float min) { - if (x < min) x = min; - if (y < min) y = min; - if (z < min) z = min; - - } - - - /** - * Clamps the maximum value of this tuple to the max parameter. - * - * @param max the highest value in the tuple after clamping - */ - public final void clampMax(float max) { - if (x > max) x = max; - if (y > max) y = max; - if (z > max) z = max; - - } - - - /** - * Sets each component of this tuple to its absolute value. - */ - public final void absolute() { - x = Math.abs(x); - y = Math.abs(y); - z = Math.abs(z); - - } - - - /** - * Linearly interpolates between tuples t1 and t2 and places the - * result into this tuple: this = (1-alpha)*t1 + alpha*t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - * @param alpha the alpha interpolation parameter - */ - public final void interpolate(Tuple3f t1, Tuple3f t2, float alpha) { - this.x = (1 - alpha) * t1.x + alpha * t2.x; - this.y = (1 - alpha) * t1.y + alpha * t2.y; - this.z = (1 - alpha) * t1.z + alpha * t2.z; - - - } - - - /** - * Linearly interpolates between this tuple and tuple t1 and - * places the result into this tuple: this = (1-alpha)*this + alpha*t1. - * - * @param t1 the first tuple - * @param alpha the alpha interpolation parameter - */ - public final void interpolate(Tuple3f t1, float alpha) { - this.x = (1 - alpha) * this.x + alpha * t1.x; - this.y = (1 - alpha) * this.y + alpha * t1.y; - this.z = (1 - alpha) * this.z + alpha * t1.z; - - - } - - /** - * Creates a new object of the same class as this object. - * - * @return a clone of this instance. - * @throws OutOfMemoryError if there is not enough memory. - * @see java.lang.Cloneable - * @since vecmath 1.3 - */ - public Object clone() { - // Since there are no arrays we can just use Object.clone() - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - - /** - * Get the x coordinate. - * - * @return the x coordinate. - * @since vecmath 1.5 - */ - public final float getX() { - return x; - } - - - /** - * Set the x coordinate. - * - * @param x value to x coordinate. - * @since vecmath 1.5 - */ - public final void setX(float x) { - this.x = x; - } - - - /** - * Get the y coordinate. - * - * @return the y coordinate. - * @since vecmath 1.5 - */ - public final float getY() { - return y; - } - - - /** - * Set the y coordinate. - * - * @param y value to y coordinate. - * @since vecmath 1.5 - */ - public final void setY(float y) { - this.y = y; - } - - /** - * Get the z coordinate. - * - * @return the z coordinate - * @since vecmath 1.5 - */ - public final float getZ() { - return z; - } - - - /** - * Set the Z coordinate. - * - * @param z value to z coordinate. - * @since vecmath 1.5 - */ - public final void setZ(float z) { - this.z = z; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Tuple4d.java b/src/main/java/com/volmit/adapt/util/Tuple4d.java deleted file mode 100644 index ea83e34d9..000000000 --- a/src/main/java/com/volmit/adapt/util/Tuple4d.java +++ /dev/null @@ -1,849 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 4 element tuple represented by double precision floating point - * x,y,z,w coordinates. - */ -public abstract class Tuple4d implements java.io.Serializable, Cloneable { - - static final long serialVersionUID = -4748953690425311052L; - - /** - * The x coordinate. - */ - public double x; - - /** - * The y coordinate. - */ - public double y; - - /** - * The z coordinate. - */ - public double z; - - /** - * The w coordinate. - */ - public double w; - - - /** - * Constructs and initializes a Tuple4d from the specified xyzw coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - * @param w the w coordinate - */ - public Tuple4d(double x, double y, double z, double w) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; - } - - - /** - * Constructs and initializes a Tuple4d from the coordinates contained - * in the array. - * - * @param t the array of length 4 containing xyzw in order - */ - public Tuple4d(double[] t) { - this.x = t[0]; - this.y = t[1]; - this.z = t[2]; - this.w = t[3]; - } - - - /** - * Constructs and initializes a Tuple4d from the specified Tuple4d. - * - * @param t1 the Tuple4d containing the initialization x y z w data - */ - public Tuple4d(Tuple4d t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - this.w = t1.w; - } - - - /** - * Constructs and initializes a Tuple4d from the specified Tuple4f. - * - * @param t1 the Tuple4f containing the initialization x y z w data - */ - public Tuple4d(Tuple4f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - this.w = t1.w; - } - - - /** - * Constructs and initializes a Tuple4d to (0,0,0,0). - */ - public Tuple4d() { - this.x = 0.0; - this.y = 0.0; - this.z = 0.0; - this.w = 0.0; - } - - - /** - * Sets the value of this tuple to the specified xyzw coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - * @param w the w coordinate - */ - public final void set(double x, double y, double z, double w) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; - } - - - /** - * Sets the value of this tuple to the specified xyzw coordinates. - * - * @param t the array of length 4 containing xyzw in order - */ - public final void set(double[] t) { - this.x = t[0]; - this.y = t[1]; - this.z = t[2]; - this.w = t[3]; - } - - - /** - * Sets the value of this tuple to the value of tuple t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple4d t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - this.w = t1.w; - } - - - /** - * Sets the value of this tuple to the value of tuple t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple4f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - this.w = t1.w; - } - - - /** - * Gets the value of this tuple and places it into the array t of - * length four in x,y,z,w order. - * - * @param t the array of length four - */ - public final void get(double[] t) { - t[0] = this.x; - t[1] = this.y; - t[2] = this.z; - t[3] = this.w; - } - - - /** - * Gets the value of this tuple and places it into the Tuple4d - * argument of - * length four in x,y,z,w order. - * - * @param t the Tuple into which the values will be copied - */ - public final void get(Tuple4d t) { - t.x = this.x; - t.y = this.y; - t.z = this.z; - t.w = this.w; - } - - - /** - * Sets the value of this tuple to the tuple sum of tuples t1 and t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void add(Tuple4d t1, Tuple4d t2) { - this.x = t1.x + t2.x; - this.y = t1.y + t2.y; - this.z = t1.z + t2.z; - this.w = t1.w + t2.w; - } - - - /** - * Sets the value of this tuple to the sum of itself and tuple t1. - * - * @param t1 the other tuple - */ - public final void add(Tuple4d t1) { - this.x += t1.x; - this.y += t1.y; - this.z += t1.z; - this.w += t1.w; - } - - - /** - * Sets the value of this tuple to the difference - * of tuples t1 and t2 (this = t1 - t2). - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void sub(Tuple4d t1, Tuple4d t2) { - this.x = t1.x - t2.x; - this.y = t1.y - t2.y; - this.z = t1.z - t2.z; - this.w = t1.w - t2.w; - } - - - /** - * Sets the value of this tuple to the difference of itself - * and tuple t1 (this = this - t1). - * - * @param t1 the other tuple - */ - public final void sub(Tuple4d t1) { - this.x -= t1.x; - this.y -= t1.y; - this.z -= t1.z; - this.w -= t1.w; - } - - - /** - * Sets the value of this tuple to the negation of tuple t1. - * - * @param t1 the source tuple - */ - public final void negate(Tuple4d t1) { - this.x = -t1.x; - this.y = -t1.y; - this.z = -t1.z; - this.w = -t1.w; - } - - - /** - * Negates the value of this tuple in place. - */ - public final void negate() { - this.x = -this.x; - this.y = -this.y; - this.z = -this.z; - this.w = -this.w; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of the scale factor with the tuple t1. - * - * @param s the scalar value - * @param t1 the source tuple - */ - public final void scale(double s, Tuple4d t1) { - this.x = s * t1.x; - this.y = s * t1.y; - this.z = s * t1.z; - this.w = s * t1.w; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of the scale factor with this. - * - * @param s the scalar value - */ - public final void scale(double s) { - this.x *= s; - this.y *= s; - this.z *= s; - this.w *= s; - } - - - /** - * Sets the value of this tuple to the scalar multiplication by s - * of tuple t1 plus tuple t2 (this = s*t1 + t2). - * - * @param s the scalar value - * @param t1 the tuple to be multipled - * @param t2 the tuple to be added - */ - public final void scaleAdd(double s, Tuple4d t1, Tuple4d t2) { - this.x = s * t1.x + t2.x; - this.y = s * t1.y + t2.y; - this.z = s * t1.z + t2.z; - this.w = s * t1.w + t2.w; - } - - - /** - * @Deprecated Use scaleAdd(double,Tuple4d) instead - */ - public final void scaleAdd(float s, Tuple4d t1) { - scaleAdd((double) s, t1); - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself and then adds tuple t1 (this = s*this + t1). - * - * @param s the scalar value - * @param t1 the tuple to be added - */ - public final void scaleAdd(double s, Tuple4d t1) { - this.x = s * this.x + t1.x; - this.y = s * this.y + t1.y; - this.z = s * this.z + t1.z; - this.w = s * this.w + t1.w; - } - - - /** - * Returns a string that contains the values of this Tuple4d. - * The form is (x,y,z,w). - * - * @return the String representation - */ - public String toString() { - return "(" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + ")"; - } - - - /** - * Returns true if all of the data members of Tuple4d t1 are - * equal to the corresponding data members in this Tuple4d. - * - * @param t1 the tuple with which the comparison is made - * @return true or false - */ - public boolean equals(Tuple4d t1) { - try { - return (this.x == t1.x && this.y == t1.y && this.z == t1.z - && this.w == t1.w); - } catch (NullPointerException e2) { - return false; - } - } - - /** - * Returns true if the Object t1 is of type Tuple4d and all of the - * data members of t1 are equal to the corresponding data members in - * this Tuple4d. - * - * @param t1 the object with which the comparison is made - * @return true or false - */ - public boolean equals(Object t1) { - try { - - Tuple4d t2 = (Tuple4d) t1; - return (this.x == t2.x && this.y == t2.y && - this.z == t2.z && this.w == t2.w); - } catch (NullPointerException e2) { - return false; - } catch (ClassCastException e1) { - return false; - } - } - - - /** - * Returns true if the L-infinite distance between this tuple - * and tuple t1 is less than or equal to the epsilon parameter, - * otherwise returns false. The L-infinite - * distance is equal to - * MAX[abs(x1-x2), abs(y1-y2), abs(z1-z2), abs(w1-w2)]. - * - * @param t1 the tuple to be compared to this tuple - * @param epsilon the threshold value - * @return true or false - */ - public boolean epsilonEquals(Tuple4d t1, double epsilon) { - double diff; - - diff = x - t1.x; - if (Double.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = y - t1.y; - if (Double.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = z - t1.z; - if (Double.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = w - t1.w; - if (Double.isNaN(diff)) return false; - return !((diff < 0 ? -diff : diff) > epsilon); - - } - - - /** - * Returns a hash code value based on the data values in this - * object. Two different Tuple4d objects with identical data values - * (i.e., Tuple4d.equals returns true) will return the same hash - * code value. Two objects with different data members may return the - * same hash value, although this is not likely. - * - * @return the integer hash code value - */ - public int hashCode() { - long bits = 1L; - bits = 31L * bits + VecMathUtil.doubleToLongBits(x); - bits = 31L * bits + VecMathUtil.doubleToLongBits(y); - bits = 31L * bits + VecMathUtil.doubleToLongBits(z); - bits = 31L * bits + VecMathUtil.doubleToLongBits(w); - return (int) (bits ^ (bits >> 32)); - } - - - /** - * @Deprecated Use clamp(double,double,Tuple4d) instead - */ - public final void clamp(float min, float max, Tuple4d t) { - clamp(min, (double) max, t); - } - - - /** - * Clamps the tuple parameter to the range [low, high] and - * places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clamp(double min, double max, Tuple4d t) { - if (t.x > max) { - x = max; - } else if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else if (t.y < min) { - y = min; - } else { - y = t.y; - } - - if (t.z > max) { - z = max; - } else if (t.z < min) { - z = min; - } else { - z = t.z; - } - - if (t.w > max) { - w = max; - } else if (t.w < min) { - w = min; - } else { - w = t.w; - } - - } - - - /** - * @Deprecated Use clampMin(double,Tuple4d) instead - */ - public final void clampMin(float min, Tuple4d t) { - clampMin((double) min, t); - } - - - /** - * Clamps the minimum value of the tuple parameter to the min - * parameter and places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMin(double min, Tuple4d t) { - if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y < min) { - y = min; - } else { - y = t.y; - } - - if (t.z < min) { - z = min; - } else { - z = t.z; - } - - if (t.w < min) { - w = min; - } else { - w = t.w; - } - - } - - - /** - * @Deprecated Use clampMax(double,Tuple4d) instead - */ - public final void clampMax(float max, Tuple4d t) { - clampMax((double) max, t); - } - - - /** - * Clamps the maximum value of the tuple parameter to the max - * parameter and places the values into this tuple. - * - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMax(double max, Tuple4d t) { - if (t.x > max) { - x = max; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else { - y = t.y; - } - - if (t.z > max) { - z = max; - } else { - z = t.z; - } - - if (t.w > max) { - w = max; - } else { - w = t.z; - } - - } - - - /** - * Sets each component of the tuple parameter to its absolute - * value and places the modified values into this tuple. - * - * @param t the source tuple, which will not be modified - */ - public final void absolute(Tuple4d t) { - x = Math.abs(t.x); - y = Math.abs(t.y); - z = Math.abs(t.z); - w = Math.abs(t.w); - - } - - - /** - * @Deprecated Use clamp(double,double) instead - */ - public final void clamp(float min, float max) { - clamp(min, (double) max); - } - - - /** - * Clamps this tuple to the range [low, high]. - * - * @param min the lowest value in this tuple after clamping - * @param max the highest value in this tuple after clamping - */ - public final void clamp(double min, double max) { - if (x > max) { - x = max; - } else if (x < min) { - x = min; - } - - if (y > max) { - y = max; - } else if (y < min) { - y = min; - } - - if (z > max) { - z = max; - } else if (z < min) { - z = min; - } - - if (w > max) { - w = max; - } else if (w < min) { - w = min; - } - - } - - - /** - * @Deprecated Use clampMin(double) instead - */ - public final void clampMin(float min) { - clampMin((double) min); - } - - - /** - * Clamps the minimum value of this tuple to the min parameter. - * - * @param min the lowest value in this tuple after clamping - */ - public final void clampMin(double min) { - if (x < min) x = min; - if (y < min) y = min; - if (z < min) z = min; - if (w < min) w = min; - } - - - /** - * @Deprecated Use clampMax(double) instead - */ - public final void clampMax(float max) { - clampMax((double) max); - } - - - /** - * Clamps the maximum value of this tuple to the max parameter. - * - * @param max the highest value in the tuple after clamping - */ - public final void clampMax(double max) { - if (x > max) x = max; - if (y > max) y = max; - if (z > max) z = max; - if (w > max) w = max; - - } - - - /** - * Sets each component of this tuple to its absolute value. - */ - public final void absolute() { - x = Math.abs(x); - y = Math.abs(y); - z = Math.abs(z); - w = Math.abs(w); - - } - - - /** - * @Deprecated Use interpolate(Tuple4d,Tuple4d,double) instead - */ - public void interpolate(Tuple4d t1, Tuple4d t2, float alpha) { - interpolate(t1, t2, (double) alpha); - } - - - /** - * Linearly interpolates between tuples t1 and t2 and places the - * result into this tuple: this = (1-alpha)*t1 + alpha*t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - * @param alpha the alpha interpolation parameter - */ - public void interpolate(Tuple4d t1, Tuple4d t2, double alpha) { - this.x = (1 - alpha) * t1.x + alpha * t2.x; - this.y = (1 - alpha) * t1.y + alpha * t2.y; - this.z = (1 - alpha) * t1.z + alpha * t2.z; - this.w = (1 - alpha) * t1.w + alpha * t2.w; - } - - - /** - * @Deprecated Use interpolate(Tuple4d,double) instead - */ - public void interpolate(Tuple4d t1, float alpha) { - interpolate(t1, (double) alpha); - } - - - /** - * Linearly interpolates between this tuple and tuple t1 and - * places the result into this tuple: this = (1-alpha)*this + alpha*t1. - * - * @param t1 the first tuple - * @param alpha the alpha interpolation parameter - */ - public void interpolate(Tuple4d t1, double alpha) { - this.x = (1 - alpha) * this.x + alpha * t1.x; - this.y = (1 - alpha) * this.y + alpha * t1.y; - this.z = (1 - alpha) * this.z + alpha * t1.z; - this.w = (1 - alpha) * this.w + alpha * t1.w; - } - - /** - * Creates a new object of the same class as this object. - * - * @return a clone of this instance. - * @throws OutOfMemoryError if there is not enough memory. - * @see java.lang.Cloneable - * @since vecmath 1.3 - */ - public Object clone() { - // Since there are no arrays we can just use Object.clone() - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - /** - * Get the x coordinate. - * - * @return the x coordinate. - * @since vecmath 1.5 - */ - public final double getX() { - return x; - } - - - /** - * Set the x coordinate. - * - * @param x value to x coordinate. - * @since vecmath 1.5 - */ - public final void setX(double x) { - this.x = x; - } - - - /** - * Get the y coordinate. - * - * @return the y coordinate. - * @since vecmath 1.5 - */ - public final double getY() { - return y; - } - - - /** - * Set the y coordinate. - * - * @param y value to y coordinate. - * @since vecmath 1.5 - */ - public final void setY(double y) { - this.y = y; - } - - /** - * Get the z coordinate. - * - * @return the z coordinate. - * @since vecmath 1.5 - */ - public final double getZ() { - return z; - } - - - /** - * Set the z coordinate. - * - * @param z value to z coordinate. - * @since vecmath 1.5 - */ - public final void setZ(double z) { - this.z = z; - } - - - /** - * Get the w coordinate. - * - * @return the w coordinate. - * @since vecmath 1.5 - */ - public final double getW() { - return w; - } - - - /** - * Set the w coordinate. - * - * @param w value to w coordinate. - * @since vecmath 1.5 - */ - public final void setW(double w) { - this.w = w; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Tuple4f.java b/src/main/java/com/volmit/adapt/util/Tuple4f.java deleted file mode 100644 index 8b954e17c..000000000 --- a/src/main/java/com/volmit/adapt/util/Tuple4f.java +++ /dev/null @@ -1,773 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 4-element tuple represented by single-precision floating point x,y,z,w - * coordinates. - */ -public abstract class Tuple4f implements java.io.Serializable, Cloneable { - - static final long serialVersionUID = 7068460319248845763L; - - /** - * The x coordinate. - */ - public float x; - - /** - * The y coordinate. - */ - public float y; - - /** - * The z coordinate. - */ - public float z; - - /** - * The w coordinate. - */ - public float w; - - - /** - * Constructs and initializes a Tuple4f from the specified xyzw coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - * @param w the w coordinate - */ - public Tuple4f(float x, float y, float z, float w) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; - } - - - /** - * Constructs and initializes a Tuple4f from the array of length 4. - * - * @param t the array of length 4 containing xyzw in order - */ - public Tuple4f(float[] t) { - this.x = t[0]; - this.y = t[1]; - this.z = t[2]; - this.w = t[3]; - } - - - /** - * Constructs and initializes a Tuple4f from the specified Tuple4f. - * - * @param t1 the Tuple4f containing the initialization x y z w data - */ - public Tuple4f(Tuple4f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - this.w = t1.w; - } - - - /** - * Constructs and initializes a Tuple4f from the specified Tuple4d. - * - * @param t1 the Tuple4d containing the initialization x y z w data - */ - public Tuple4f(Tuple4d t1) { - this.x = (float) t1.x; - this.y = (float) t1.y; - this.z = (float) t1.z; - this.w = (float) t1.w; - } - - - /** - * Constructs and initializes a Tuple4f to (0,0,0,0). - */ - public Tuple4f() { - this.x = 0.0f; - this.y = 0.0f; - this.z = 0.0f; - this.w = 0.0f; - } - - - /** - * Sets the value of this tuple to the specified xyzw coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - * @param w the w coordinate - */ - public final void set(float x, float y, float z, float w) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; - } - - - /** - * Sets the value of this tuple to the specified coordinates in the - * array of length 4. - * - * @param t the array of length 4 containing xyzw in order - */ - public final void set(float[] t) { - this.x = t[0]; - this.y = t[1]; - this.z = t[2]; - this.w = t[3]; - } - - - /** - * Sets the value of this tuple to the value of tuple t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple4f t1) { - this.x = t1.x; - this.y = t1.y; - this.z = t1.z; - this.w = t1.w; - } - - - /** - * Sets the value of this tuple to the value of tuple t1. - * - * @param t1 the tuple to be copied - */ - public final void set(Tuple4d t1) { - this.x = (float) t1.x; - this.y = (float) t1.y; - this.z = (float) t1.z; - this.w = (float) t1.w; - } - - - /** - * Copies the values of this tuple into the array t. - * - * @param t the array - */ - public final void get(float[] t) { - t[0] = this.x; - t[1] = this.y; - t[2] = this.z; - t[3] = this.w; - } - - - /** - * Copies the values of this tuple into the tuple t. - * - * @param t the target tuple - */ - public final void get(Tuple4f t) { - t.x = this.x; - t.y = this.y; - t.z = this.z; - t.w = this.w; - } - - - /** - * Sets the value of this tuple to the sum of tuples t1 and t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void add(Tuple4f t1, Tuple4f t2) { - this.x = t1.x + t2.x; - this.y = t1.y + t2.y; - this.z = t1.z + t2.z; - this.w = t1.w + t2.w; - } - - - /** - * Sets the value of this tuple to the sum of itself and t1. - * - * @param t1 the other tuple - */ - public final void add(Tuple4f t1) { - this.x += t1.x; - this.y += t1.y; - this.z += t1.z; - this.w += t1.w; - } - - - /** - * Sets the value of this tuple to the difference - * of tuples t1 and t2 (this = t1 - t2). - * - * @param t1 the first tuple - * @param t2 the second tuple - */ - public final void sub(Tuple4f t1, Tuple4f t2) { - this.x = t1.x - t2.x; - this.y = t1.y - t2.y; - this.z = t1.z - t2.z; - this.w = t1.w - t2.w; - } - - - /** - * Sets the value of this tuple to the difference - * of itself and t1 (this = this - t1). - * - * @param t1 the other tuple - */ - public final void sub(Tuple4f t1) { - this.x -= t1.x; - this.y -= t1.y; - this.z -= t1.z; - this.w -= t1.w; - } - - - /** - * Sets the value of this tuple to the negation of tuple t1. - * - * @param t1 the source tuple - */ - public final void negate(Tuple4f t1) { - this.x = -t1.x; - this.y = -t1.y; - this.z = -t1.z; - this.w = -t1.w; - } - - - /** - * Negates the value of this tuple in place. - */ - public final void negate() { - this.x = -this.x; - this.y = -this.y; - this.z = -this.z; - this.w = -this.w; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1. - * - * @param s the scalar value - * @param t1 the source tuple - */ - public final void scale(float s, Tuple4f t1) { - this.x = s * t1.x; - this.y = s * t1.y; - this.z = s * t1.z; - this.w = s * t1.w; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of the scale factor with this. - * - * @param s the scalar value - */ - public final void scale(float s) { - this.x *= s; - this.y *= s; - this.z *= s; - this.w *= s; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of tuple t1 plus tuple t2 (this = s*t1 + t2). - * - * @param s the scalar value - * @param t1 the tuple to be multipled - * @param t2 the tuple to be added - */ - public final void scaleAdd(float s, Tuple4f t1, Tuple4f t2) { - this.x = s * t1.x + t2.x; - this.y = s * t1.y + t2.y; - this.z = s * t1.z + t2.z; - this.w = s * t1.w + t2.w; - } - - - /** - * Sets the value of this tuple to the scalar multiplication - * of itself and then adds tuple t1 (this = s*this + t1). - * - * @param s the scalar value - * @param t1 the tuple to be added - */ - public final void scaleAdd(float s, Tuple4f t1) { - this.x = s * this.x + t1.x; - this.y = s * this.y + t1.y; - this.z = s * this.z + t1.z; - this.w = s * this.w + t1.w; - } - - - /** - * Returns a string that contains the values of this Tuple4f. - * The form is (x,y,z,w). - * - * @return the String representation - */ - public String toString() { - return "(" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + ")"; - } - - /** - * Returns true if all of the data members of Tuple4f t1 are - * equal to the corresponding data members in this Tuple4f. - * - * @param t1 the vector with which the comparison is made - * @return true or false - */ - public boolean equals(Tuple4f t1) { - try { - return (this.x == t1.x && this.y == t1.y && this.z == t1.z - && this.w == t1.w); - } catch (NullPointerException e2) { - return false; - } - } - - /** - * Returns true if the Object t1 is of type Tuple4f and all of the - * data members of t1 are equal to the corresponding data members in - * this Tuple4f. - * - * @param t1 the object with which the comparison is made - * @return true or false - */ - public boolean equals(Object t1) { - try { - Tuple4f t2 = (Tuple4f) t1; - return (this.x == t2.x && this.y == t2.y && - this.z == t2.z && this.w == t2.w); - } catch (NullPointerException e2) { - return false; - } catch (ClassCastException e1) { - return false; - } - } - - - /** - * Returns true if the L-infinite distance between this tuple - * and tuple t1 is less than or equal to the epsilon parameter, - * otherwise returns false. The L-infinite - * distance is equal to - * MAX[abs(x1-x2), abs(y1-y2), abs(z1-z2), abs(w1-w2)]. - * - * @param t1 the tuple to be compared to this tuple - * @param epsilon the threshold value - * @return true or false - */ - public boolean epsilonEquals(Tuple4f t1, float epsilon) { - float diff; - - diff = x - t1.x; - if (Float.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = y - t1.y; - if (Float.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = z - t1.z; - if (Float.isNaN(diff)) return false; - if ((diff < 0 ? -diff : diff) > epsilon) return false; - - diff = w - t1.w; - if (Float.isNaN(diff)) return false; - return !((diff < 0 ? -diff : diff) > epsilon); - } - - - /** - * Returns a hash code value based on the data values in this - * object. Two different Tuple4f objects with identical data values - * (i.e., Tuple4f.equals returns true) will return the same hash - * code value. Two objects with different data members may return the - * same hash value, although this is not likely. - * - * @return the integer hash code value - */ - public int hashCode() { - long bits = 1L; - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(x); - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(y); - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(z); - bits = 31L * bits + (long) VecMathUtil.floatToIntBits(w); - return (int) (bits ^ (bits >> 32)); - } - - - /** - * Clamps the tuple parameter to the range [low, high] and - * places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clamp(float min, float max, Tuple4f t) { - if (t.x > max) { - x = max; - } else if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else if (t.y < min) { - y = min; - } else { - y = t.y; - } - - if (t.z > max) { - z = max; - } else if (t.z < min) { - z = min; - } else { - z = t.z; - } - - if (t.w > max) { - w = max; - } else if (t.w < min) { - w = min; - } else { - w = t.w; - } - - } - - - /** - * Clamps the minimum value of the tuple parameter to the min - * parameter and places the values into this tuple. - * - * @param min the lowest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMin(float min, Tuple4f t) { - if (t.x < min) { - x = min; - } else { - x = t.x; - } - - if (t.y < min) { - y = min; - } else { - y = t.y; - } - - if (t.z < min) { - z = min; - } else { - z = t.z; - } - - if (t.w < min) { - w = min; - } else { - w = t.w; - } - - - } - - - /** - * Clamps the maximum value of the tuple parameter to the max - * parameter and places the values into this tuple. - * - * @param max the highest value in the tuple after clamping - * @param t the source tuple, which will not be modified - */ - public final void clampMax(float max, Tuple4f t) { - if (t.x > max) { - x = max; - } else { - x = t.x; - } - - if (t.y > max) { - y = max; - } else { - y = t.y; - } - - if (t.z > max) { - z = max; - } else { - z = t.z; - } - - if (t.w > max) { - w = max; - } else { - w = t.z; - } - - } - - - /** - * Sets each component of the tuple parameter to its absolute - * value and places the modified values into this tuple. - * - * @param t the source tuple, which will not be modified - */ - public final void absolute(Tuple4f t) { - x = Math.abs(t.x); - y = Math.abs(t.y); - z = Math.abs(t.z); - w = Math.abs(t.w); - } - - - /** - * Clamps this tuple to the range [low, high]. - * - * @param min the lowest value in this tuple after clamping - * @param max the highest value in this tuple after clamping - */ - public final void clamp(float min, float max) { - if (x > max) { - x = max; - } else if (x < min) { - x = min; - } - - if (y > max) { - y = max; - } else if (y < min) { - y = min; - } - - if (z > max) { - z = max; - } else if (z < min) { - z = min; - } - - if (w > max) { - w = max; - } else if (w < min) { - w = min; - } - - } - - - /** - * Clamps the minimum value of this tuple to the min parameter. - * - * @param min the lowest value in this tuple after clamping - */ - public final void clampMin(float min) { - if (x < min) x = min; - if (y < min) y = min; - if (z < min) z = min; - if (w < min) w = min; - - } - - - /** - * Clamps the maximum value of this tuple to the max parameter. - * - * @param max the highest value in the tuple after clamping - */ - public final void clampMax(float max) { - if (x > max) x = max; - if (y > max) y = max; - if (z > max) z = max; - if (w > max) w = max; - - } - - - /** - * Sets each component of this tuple to its absolute value. - */ - public final void absolute() { - x = Math.abs(x); - y = Math.abs(y); - z = Math.abs(z); - w = Math.abs(w); - } - - - /** - * Linearly interpolates between tuples t1 and t2 and places the - * result into this tuple: this = (1-alpha)*t1 + alpha*t2. - * - * @param t1 the first tuple - * @param t2 the second tuple - * @param alpha the alpha interpolation parameter - */ - public void interpolate(Tuple4f t1, Tuple4f t2, float alpha) { - this.x = (1 - alpha) * t1.x + alpha * t2.x; - this.y = (1 - alpha) * t1.y + alpha * t2.y; - this.z = (1 - alpha) * t1.z + alpha * t2.z; - this.w = (1 - alpha) * t1.w + alpha * t2.w; - - } - - - /** - * Linearly interpolates between this tuple and tuple t1 and - * places the result into this tuple: this = (1-alpha)*this + alpha*t1. - * - * @param t1 the first tuple - * @param alpha the alpha interpolation parameter - */ - public void interpolate(Tuple4f t1, float alpha) { - this.x = (1 - alpha) * this.x + alpha * t1.x; - this.y = (1 - alpha) * this.y + alpha * t1.y; - this.z = (1 - alpha) * this.z + alpha * t1.z; - this.w = (1 - alpha) * this.w + alpha * t1.w; - - } - - /** - * Creates a new object of the same class as this object. - * - * @return a clone of this instance. - * @throws OutOfMemoryError if there is not enough memory. - * @see java.lang.Cloneable - * @since vecmath 1.3 - */ - public Object clone() { - // Since there are no arrays we can just use Object.clone() - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - /** - * Get the x coordinate. - * - * @return the x coordinate. - * @since vecmath 1.5 - */ - public final float getX() { - return x; - } - - - /** - * Set the x coordinate. - * - * @param x value to x coordinate. - * @since vecmath 1.5 - */ - public final void setX(float x) { - this.x = x; - } - - - /** - * Get the y coordinate. - * - * @return the y coordinate. - * @since vecmath 1.5 - */ - public final float getY() { - return y; - } - - - /** - * Set the y coordinate. - * - * @param y value to y coordinate. - * @since vecmath 1.5 - */ - public final void setY(float y) { - this.y = y; - } - - /** - * Get the z coordinate. - * - * @return the z coordinate. - * @since vecmath 1.5 - */ - public final float getZ() { - return z; - } - - - /** - * Set the z coordinate. - * - * @param z value to z coordinate. - * @since vecmath 1.5 - */ - public final void setZ(float z) { - this.z = z; - } - - - /** - * Get the w coordinate. - * - * @return the w coordinate. - * @since vecmath 1.5 - */ - public final float getW() { - return w; - } - - - /** - * Set the w coordinate. - * - * @param w value to w coordinate. - * @since vecmath 1.5 - */ - public final void setW(float w) { - this.w = w; - } -} diff --git a/src/main/java/com/volmit/adapt/util/UIElement.java b/src/main/java/com/volmit/adapt/util/UIElement.java deleted file mode 100644 index 61d7ffcf7..000000000 --- a/src/main/java/com/volmit/adapt/util/UIElement.java +++ /dev/null @@ -1,281 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.reflect.registries.Enchantments; -import com.volmit.adapt.util.reflect.registries.ItemFlags; -import org.bukkit.Material; -import org.bukkit.inventory.ItemFlag; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -public class UIElement implements Element { - private final String id; - private final KList lore; - private MaterialBlock material; - private CustomModel model; - private boolean enchanted; - private String name; - private double progress; - private boolean bg; - private Callback eLeft; - private Callback eRight; - private Callback eShiftLeft; - private Callback eShiftRight; - private Callback eDraggedInto; - private Callback eOtherDraggedInto; - private int count; - - public UIElement(String id) { - this.id = id; - lore = new KList<>(); - enchanted = false; - count = 1; - material = new MaterialBlock(Material.AIR); - } - - @Override - public MaterialBlock getMaterial() { - return material; - } - - @Override - public UIElement setMaterial(MaterialBlock material) { - this.material = material; - return this; - } - - public Double clip(double value, double min, double max) { - return Double.valueOf(Math.min(max, Math.max(min, value))); - } - - @Override - public boolean isEnchanted() { - return enchanted; - } - - @Override - public UIElement setEnchanted(boolean enchanted) { - this.enchanted = enchanted; - return this; - } - - @Override - public String getId() { - return id; - } - - @Override - public String getName() { - return name; - } - - @Override - public UIElement setName(String name) { - this.name = name; - return this; - } - - @Override - public CustomModel getModel() { - return model; - } - - @Override - public UIElement setModel(CustomModel model) { - this.model = model; - return this; - } - - @Override - public KList getLore() { - return lore; - } - - @Override - public UIElement onLeftClick(Callback clicked) { - eLeft = clicked; - return this; - } - - @Override - public UIElement onRightClick(Callback clicked) { - eRight = clicked; - return this; - } - - @Override - public UIElement onShiftLeftClick(Callback clicked) { - eShiftLeft = clicked; - return this; - } - - @Override - public UIElement onShiftRightClick(Callback clicked) { - eShiftRight = clicked; - return this; - } - - @Override - public UIElement onDraggedInto(Callback into) { - eDraggedInto = into; - return this; - } - - @Override - public UIElement onOtherDraggedInto(Callback other) { - eOtherDraggedInto = other; - return this; - } - - @Override - public Element call(ElementEvent event, Element context) { - try { - switch (event) { - case DRAG_INTO: - eDraggedInto.run(context); - return this; - case LEFT: - eLeft.run(context); - return this; - case OTHER_DRAG_INTO: - eOtherDraggedInto.run(context); - return this; - case RIGHT: - eRight.run(context); - return this; - case SHIFT_LEFT: - eShiftLeft.run(context); - return this; - case SHIFT_RIGHT: - eShiftRight.run(context); - return this; - } - } catch (NullPointerException e) { - - } catch (Throwable e) { - e.printStackTrace(); - } - - return this; - } - - @Override - public Element addLore(String loreLine) { - getLore().add(wrapWordsWithFormatting(loreLine.replaceAll("\\Q\n\\E", " "), 52).split("\\Q\n\\E")); - return this; - } - - public String wrapWordsWithFormatting(String f, int l) { - StringBuilder sb = new StringBuilder(); - String last = null; - for (String i : Form.wrapWords(f, l).split("\\Q\n\\E")) { - if (last != null) { - sb.append("\n").append(C.getLastColors(last)).append(i); - } else { - sb.append("\n").append(i); - } - - last = i; - } - - return sb.substring(1); - } - - @Override - public Element setBackground(boolean bg) { - this.bg = bg; - return this; - } - - @Override - public boolean isBackgrond() { - return bg; - } - - @Override - public Element setCount(int c) { - count = clip(c, 1, 64).intValue(); - return this; - } - - @Override - public int getCount() { - return count; - } - - @SuppressWarnings("deprecation") - @Override - public ItemStack computeItemStack() { - try { - ItemStack is = getModel() != null ? getModel().toItemStack() : - new ItemStack(getMaterial().getMaterial()); - is.setAmount(getCount()); - is.setDurability(getEffectiveDurability()); - - ItemMeta im = is.getItemMeta(); - if (im == null) return is; - im.setDisplayName(getName()); - im.setLore(getLore().copy()); - if (isEnchanted()) { - im.addEnchant(Enchantments.DURABILITY, 1, true); - } - // Hide all attributes and enchants and stuff! - im.addItemFlags(ItemFlag.HIDE_ENCHANTS); - im.addItemFlags(ItemFlag.HIDE_ATTRIBUTES); - im.addItemFlags(ItemFlags.HIDE_POTION_EFFECTS); - im.addItemFlags(ItemFlag.HIDE_DYE); - im.addItemFlags(ItemFlag.HIDE_DESTROYS); - im.addItemFlags(ItemFlag.HIDE_UNBREAKABLE); - - is.setItemMeta(im); - return is; - } catch (Throwable e) { - e.printStackTrace(); - } - - return null; - } - - @Override - public Element setProgress(double progress) { - this.progress = clip(progress, 0D, 1D); - return this; - } - - @Override - public double getProgress() { - return progress; - } - - @Override - public short getEffectiveDurability() { - if (progress == 1D) { - return 0; - } - - if (getMaterial().getMaterial().getMaxDurability() == 0) { - return 0; - } else { - int prog = (int) ((double) getMaterial().getMaterial().getMaxDurability() * (1D - getProgress())); - return clip(prog, 1, (getMaterial().getMaterial().getMaxDurability() - 1)).shortValue(); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/UIStaticDecorator.java b/src/main/java/com/volmit/adapt/util/UIStaticDecorator.java deleted file mode 100644 index 3a0c1e68a..000000000 --- a/src/main/java/com/volmit/adapt/util/UIStaticDecorator.java +++ /dev/null @@ -1,34 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Material; - -public class UIStaticDecorator implements WindowDecorator { - private final Element element; - - public UIStaticDecorator(Element element) { - this.element = element == null ? new UIElement("bg").setMaterial(new MaterialBlock(Material.AIR)) : element; - } - - @Override - public Element onDecorateBackground(Window window, int position, int row) { - return element; - } -} diff --git a/src/main/java/com/volmit/adapt/util/UIVoidDecorator.java b/src/main/java/com/volmit/adapt/util/UIVoidDecorator.java deleted file mode 100644 index f184b628f..000000000 --- a/src/main/java/com/volmit/adapt/util/UIVoidDecorator.java +++ /dev/null @@ -1,27 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class UIVoidDecorator implements WindowDecorator { - @Override - public Element onDecorateBackground(Window window, int position, int row) { - return null; - } -} - diff --git a/src/main/java/com/volmit/adapt/util/UIWindow.java b/src/main/java/com/volmit/adapt/util/UIWindow.java deleted file mode 100644 index 55fd483ba..000000000 --- a/src/main/java/com/volmit/adapt/util/UIWindow.java +++ /dev/null @@ -1,527 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.api.version.Version; -import lombok.Getter; -import lombok.Setter; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class UIWindow implements Window, Listener { - private final Player viewer; - private final Map elements; - private WindowDecorator decorator; - private Callback eClose; - private WindowResolution resolution; - private String title; - private boolean visible; - private int viewportPosition; - private int viewportSize; - private int highestRow; - private String tag; - private Inventory inventory; - private int clickcheck; - private boolean doubleclicked; - - public UIWindow(Player viewer) { - clickcheck = 0; - doubleclicked = false; - this.viewer = viewer; - this.elements = new HashMap<>(); - setTitle(""); - setDecorator(new UIVoidDecorator()); - setResolution(WindowResolution.W9_H6); - setViewportHeight(getResolution().getMaxHeight()); - setViewportPosition(0); - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void on(InventoryClickEvent e) { - if (!e.getWhoClicked().equals(viewer)) { - return; - } - - if (!isVisible()) { - return; - } - - if (!(e.getInventory().getHolder() instanceof Holder)) { - return; - } - - if (e.getClickedInventory() == null) { - return; - } - - if (!e.getView().getType().equals(getResolution().getType())) { - return; - } - - if (e.getClickedInventory().getType().equals(getResolution().getType())) { - Element element = getElement(getLayoutPosition(e.getSlot()), getLayoutRow(e.getSlot())); - - switch (e.getAction()) { - case CLONE_STACK: - case COLLECT_TO_CURSOR: - case DROP_ALL_CURSOR: - case DROP_ALL_SLOT: - case DROP_ONE_CURSOR: - case DROP_ONE_SLOT: - case HOTBAR_MOVE_AND_READD: - case HOTBAR_SWAP: - case MOVE_TO_OTHER_INVENTORY: - case NOTHING: - case PICKUP_ALL: - case PICKUP_HALF: - case PICKUP_ONE: - case PICKUP_SOME: - case PLACE_ALL: - case PLACE_ONE: - case PLACE_SOME: - case SWAP_WITH_CURSOR: - case UNKNOWN: - break; - } - - switch (e.getClick()) { - case DOUBLE_CLICK -> doubleclicked = true; - case LEFT -> { - clickcheck++; - if (clickcheck == 1) { - J.s(() -> - { - if (clickcheck == 1) { - clickcheck = 0; - - if (element != null) { - element.call(ElementEvent.LEFT, element); - } - } - }); - } else if (clickcheck == 2) { - J.s(() -> - { - if (doubleclicked) { - doubleclicked = false; - } else { - scroll(1); - } - - clickcheck = 0; - }); - } - } - case RIGHT -> { - if (element != null) { - element.call(ElementEvent.RIGHT, element); - } else { - scroll(-1); - } - } - case SHIFT_LEFT -> { - if (element != null) { - element.call(ElementEvent.SHIFT_LEFT, element); - } - } - case SHIFT_RIGHT -> { - if (element != null) { - element.call(ElementEvent.SHIFT_RIGHT, element); - } - } - default -> { - } - } - } - - e.setCancelled(true); - - } - - @EventHandler(priority = EventPriority.MONITOR) - public void on(InventoryCloseEvent e) { - - if (!e.getPlayer().equals(viewer)) { - return; - } - - if (!(e.getInventory().getHolder() instanceof Holder)) { - return; - } - - if (isVisible()) { - close(); - callClosed(); - } - } - - @Override - public WindowDecorator getDecorator() { - return decorator; - } - - @Override - public UIWindow setDecorator(WindowDecorator decorator) { - this.decorator = decorator; - return this; - } - - @Override - public UIWindow close() { - setVisible(false); - return this; - } - - @Override - public UIWindow open() { - setVisible(true); - return this; - } - - @Override - public boolean isVisible() { - return visible; - } - - @Override - public UIWindow setVisible(boolean visible) { - if (isVisible() == visible) { - return this; - } - - if (visible) { - Bukkit.getPluginManager().registerEvents(this, Adapt.instance); - - var openInventory = viewer.getOpenInventory().getTopInventory(); - if (openInventory.getHolder() instanceof Holder holder) { - inventory = getCurrentInventory(this, holder); - } else { - inventory = createInventory(this); - } - - this.visible = true; - updateInventory(); - } else { - this.visible = false; - HandlerList.unregisterAll(this); - viewer.closeInventory(); - } - return this; - } - - @Override - public int getViewportPosition() { - return viewportPosition; - } - - @Override - public UIWindow setViewportPosition(int viewportPosition) { - this.viewportPosition = viewportPosition; - scroll(0); - updateInventory(); - - return this; - } - - @Override - public int getMaxViewportPosition() { - return Math.max(0, highestRow - getViewportHeight()); - } - - @Override - public UIWindow scroll(int direction) { - viewportPosition = (int) clip(viewportPosition + direction, 0, getMaxViewportPosition()).doubleValue(); - updateInventory(); - - return this; - } - - @Override - public int getViewportHeight() { - return viewportSize; - } - - @Override - public UIWindow setViewportHeight(int height) { - viewportSize = (int) clip(height, 1, getResolution().getMaxHeight()).doubleValue(); - - if (isVisible()) { - reopen(); - } - - return this; - } - - @Override - public String getTitle() { - return title; - } - - @Override - public UIWindow setTitle(String title) { - this.title = title; - - if (isVisible()) { - reopen(); - } - - return this; - } - - @Override - public String getTag() { - return tag; - } - - @Override - public void setTag(String s) { - tag = s; - } - - @Override - public UIWindow setElement(int position, int row, Element e) { - if (row > highestRow) { - highestRow = row; - } - - elements.put(getRealPosition((int) clip(position, -getResolution().getMaxWidthOffset(), getResolution().getMaxWidthOffset()).doubleValue(), row), e); - updateInventory(); - return this; - } - - @Override - public Element getElement(int position, int row) { - return elements.get(getRealPosition((int) clip(position, -getResolution().getMaxWidthOffset(), getResolution().getMaxWidthOffset()).doubleValue(), row)); - } - - @Override - public Player getViewer() { - return viewer; - } - - @Override - public UIWindow onClosed(Callback window) { - eClose = window; - return this; - } - - @Override - public int getViewportSlots() { - return getViewportHeight() * getResolution().getWidth(); - } - - @Override - public int getLayoutRow(int viewportSlottedPosition) { - return getRow(getRealLayoutPosition(viewportSlottedPosition)); - } - - @Override - public int getLayoutPosition(int viewportSlottedPosition) { - return getPosition(viewportSlottedPosition); - } - - @Override - public int getRealLayoutPosition(int viewportSlottedPosition) { - return getRealPosition(getPosition(viewportSlottedPosition), getRow(viewportSlottedPosition) + getViewportPosition()); - } - - @Override - public int getRealPosition(int position, int row) { - return (int) (((row * getResolution().getWidth()) + getResolution().getMaxWidthOffset()) + clip(position, -getResolution().getMaxWidthOffset(), getResolution().getMaxWidthOffset())); - } - - @Override - public int getRow(int realPosition) { - return realPosition / getResolution().getWidth(); - } - - @Override - public int getPosition(int realPosition) { - return (realPosition % getResolution().getWidth()) - getResolution().getMaxWidthOffset(); - } - - @Override - public Window callClosed() { - if (eClose != null) { - eClose.run(this); - } - - return this; - } - - @Override - public boolean hasElement(int position, int row) { - return getElement(position, row) != null; - } - - @Override - public WindowResolution getResolution() { - return resolution; - } - - public Double clip(double value, double min, double max) { - return Math.min(max, Math.max(min, value)); - } - - @Override - public Window setResolution(WindowResolution resolution) { - close(); - this.resolution = resolution; - setViewportHeight((int) clip(getViewportHeight(), 1, getResolution().getMaxHeight()).doubleValue()); - return this; - } - - @Override - public Window clearElements() { - highestRow = 0; - elements.clear(); - updateInventory(); - return this; - } - - @Override - public Window updateInventory() { - if (!isVisible() || inventory == null) { - return this; - } - - Inventory top = viewer.getOpenInventory().getTopInventory(); - if (!(top.getHolder() instanceof Holder holder) || holder.getWindow() != this) { - return this; - } - - if (inventory != top) { - inventory = top; - } - - if (Version.SET_TITLE) { - try { - viewer.getOpenInventory().setTitle(getTitle()); - } catch (IllegalArgumentException ignored) { - return this; - } - } - - ItemStack[] is = inventory.getContents(); - Set isf = new HashSet<>(); - - for (int i = 0; i < is.length; i++) { - ItemStack isc = is[i]; - ItemStack isx = computeItemStack(i); - int layoutRow = getLayoutRow(i); - int layoutPosition = getLayoutPosition(i); - - if (isx != null && !hasElement(layoutPosition, layoutRow)) { - ItemStack gg = isx.clone(); - gg.setAmount(gg.getAmount() + 1); - isf.add(gg); - } - - if (((isc == null) != (isx == null)) || isx != null && isc != null && !isc.equals(isx)) { - inventory.setItem(i, isx); - } - } - - return this; - } - - @Override - public ItemStack computeItemStack(int viewportSlot) { - int layoutRow = getLayoutRow(viewportSlot); - int layoutPosition = getLayoutPosition(viewportSlot); - Element e = hasElement(layoutPosition, layoutRow) ? getElement(layoutPosition, layoutRow) : getDecorator().onDecorateBackground(this, layoutPosition, layoutRow); - - if (e != null) { - return e.computeItemStack(); - } - - return null; - } - - @Override - public Window reopen() { - if (Version.SET_TITLE) { - visible = false; - HandlerList.unregisterAll(this); - return open(); - } - return this.close().open(); - } - - @Setter - @Getter - private static class Holder implements InventoryHolder { - private Inventory inventory; - private WindowResolution resolution; - private UIWindow window; - - public void unregister() { - HandlerList.unregisterAll(window); - window.visible = false; - } - } - - private static Inventory createInventory(UIWindow window) { - var holder = new Holder(); - Inventory inventory; - if (window.getResolution().getType().equals(InventoryType.CHEST)) { - inventory = Bukkit.createInventory(holder, window.getViewportHeight() * 9, window.getTitle()); - } else { - inventory = Bukkit.createInventory(holder, window.getResolution().getType(), window.getTitle()); - } - holder.setResolution(window.getResolution()); - holder.setInventory(inventory); - holder.setWindow(window); - - window.viewer.openInventory(inventory); - return inventory; - } - - private static Inventory getCurrentInventory(UIWindow window, Holder holder) { - boolean requiresResize = window.getResolution().getType().equals(InventoryType.CHEST) - && holder.inventory.getSize() != window.getViewportHeight() * window.getResolution().getWidth(); - if (!Version.SET_TITLE || holder.getResolution() != window.getResolution() || requiresResize) { - holder.window.close(); - return createInventory(window); - } - - var openInventory = holder.inventory; - holder.unregister(); - holder.setWindow(window); - - openInventory.clear(); - return openInventory; - } -} diff --git a/src/main/java/com/volmit/adapt/util/V.java b/src/main/java/com/volmit/adapt/util/V.java deleted file mode 100644 index 91c80c34b..000000000 --- a/src/main/java/com/volmit/adapt/util/V.java +++ /dev/null @@ -1,128 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.List; - -public class V { - private final Object o; - private boolean local; - private boolean suppress = false; - - public V(Class c, Object... parameters) { - this.o = Violator.construct(c, parameters); - this.local = true; - } - - public V(Object o) { - this.o = o; - this.local = true; - } - - public V(Object o, boolean local, boolean suppress) { - this(o); - this.local = local; - this.suppress = suppress; - } - - public V(Object o, boolean local) { - this(o); - this.local = local; - } - - public T get(Class t) { - try { - return local ? Violator.getDeclaredAnnotation(o.getClass(), t) : Violator.getAnnotation(o.getClass(), t); - } catch (Throwable e) { - if (!suppress) { - e.printStackTrace(); - } - } - - return null; - } - - public T get(Class t, String mn, Class... pars) { - try { - return local ? Violator.getDeclaredAnnotation(Violator.getDeclaredMethod(o.getClass(), mn, pars), t) : Violator.getAnnotation(Violator.getMethod(o.getClass(), mn, pars), t); - } catch (Throwable e) { - if (!suppress) { - e.printStackTrace(); - } - } - - return null; - } - - public T get(Class t, String mn) { - try { - return local ? Violator.getDeclaredAnnotation(Violator.getDeclaredField(o.getClass(), mn), t) : Violator.getAnnotation(Violator.getField(o.getClass(), mn), t); - } catch (Throwable e) { - if (!suppress) { - e.printStackTrace(); - } - } - - return null; - } - - @SuppressWarnings("unchecked") - public T get(String field) { - try { - return (T) (local ? Violator.getDeclaredField(o.getClass(), field) : Violator.getField(o.getClass(), field)).get(o); - } catch (Throwable e) { - if (!suppress) { - e.printStackTrace(); - } - } - - return null; - } - - public Object invoke(String method, Object... parameters) { - List> par = new ArrayList<>(); - - for (Object i : parameters) { - par.add(i.getClass()); - } - - try { - return (local ? Violator.getDeclaredMethod(o.getClass(), method, par.toArray(new Class[par.size()])) : Violator.getMethod(o.getClass(), method, par.toArray(new Class[par.size()]))).invoke(o, parameters); - } catch (Throwable e) { - if (!suppress) { - e.printStackTrace(); - } - } - - return null; - } - - public void set(String field, Object value) { - try { - // https://github.com/VolmitSoftware/Mortar/issues/5 - (local ? Violator.getDeclaredField(o.getClass(), field) : Violator.getField(o.getClass(), field)).set(o, value); - } catch (Throwable e) { - if (!suppress) { - e.printStackTrace(); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/VecMathUtil.java b/src/main/java/com/volmit/adapt/util/VecMathUtil.java deleted file mode 100644 index d8743b2e2..000000000 --- a/src/main/java/com/volmit/adapt/util/VecMathUtil.java +++ /dev/null @@ -1,87 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * Utility vecmath class used when computing the hash code for vecmath - * objects containing float or double values. This fixes Issue 36. - */ -class VecMathUtil { - /** - * Do not construct an instance of this class. - */ - private VecMathUtil() { - } - - /** - * Returns the representation of the specified floating-point - * value according to the IEEE 754 floating-point "single format" - * bit layout, after first mapping -0.0 to 0.0. This method is - * identical to Float.floatToIntBits(float) except that an integer - * value of 0 is returned for a floating-point value of - * -0.0f. This is done for the purpose of computing a hash code - * that satisfies the contract of hashCode() and equals(). The - * equals() method in each vecmath class does a pair-wise "==" - * test on each floating-point field in the class (e.g., x, y, and - * z for a Tuple3f). Since 0.0f == -0.0f returns true, - * we must also return the same hash code for two objects, one of - * which has a field with a value of -0.0f and the other of which - * has a cooresponding field with a value of 0.0f. - * - * @param f an input floating-point number - * @return the integer bits representing that floating-point - * number, after first mapping -0.0f to 0.0f - */ - static int floatToIntBits(float f) { - // Check for +0 or -0 - if (f == 0.0f) { - return 0; - } else { - return Float.floatToIntBits(f); - } - } - - /** - * Returns the representation of the specified floating-point - * value according to the IEEE 754 floating-point "double format" - * bit layout, after first mapping -0.0 to 0.0. This method is - * identical to Double.doubleToLongBits(double) except that an - * integer value of 0L is returned for a floating-point value of - * -0.0. This is done for the purpose of computing a hash code - * that satisfies the contract of hashCode() and equals(). The - * equals() method in each vecmath class does a pair-wise "==" - * test on each floating-point field in the class (e.g., x, y, and - * z for a Tuple3d). Since 0.0 == -0.0 returns true, we - * must also return the same hash code for two objects, one of - * which has a field with a value of -0.0 and the other of which - * has a cooresponding field with a value of 0.0. - * - * @param d an input double precision floating-point number - * @return the integer bits representing that floating-point - * number, after first mapping -0.0f to 0.0f - */ - static long doubleToLongBits(double d) { - // Check for +0 or -0 - if (d == 0.0) { - return 0L; - } else { - return Double.doubleToLongBits(d); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/Vector2d.java b/src/main/java/com/volmit/adapt/util/Vector2d.java deleted file mode 100644 index 8788409b0..000000000 --- a/src/main/java/com/volmit/adapt/util/Vector2d.java +++ /dev/null @@ -1,168 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 2-element vector that is represented by double-precision floating - * point x,y coordinates. - */ -public class Vector2d extends Tuple2d implements java.io.Serializable { - - // Combatible with 1.1 - static final long serialVersionUID = 8572646365302599857L; - - /** - * Constructs and initializes a Vector2d from the specified xy coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - */ - public Vector2d(double x, double y) { - super(x, y); - } - - - /** - * Constructs and initializes a Vector2d from the specified array. - * - * @param v the array of length 2 containing xy in order - */ - public Vector2d(double[] v) { - super(v); - } - - - /** - * Constructs and initializes a Vector2d from the specified Vector2d. - * - * @param v1 the Vector2d containing the initialization x y data - */ - public Vector2d(Vector2d v1) { - super(v1); - } - - - /** - * Constructs and initializes a Vector2d from the specified Vector2f. - * - * @param v1 the Vector2f containing the initialization x y data - */ - public Vector2d(Vector2f v1) { - super(v1); - } - - - /** - * Constructs and initializes a Vector2d from the specified Tuple2d. - * - * @param t1 the Tuple2d containing the initialization x y data - */ - public Vector2d(Tuple2d t1) { - super(t1); - } - - - /** - * Constructs and initializes a Vector2d from the specified Tuple2f. - * - * @param t1 the Tuple2f containing the initialization x y data - */ - public Vector2d(Tuple2f t1) { - super(t1); - } - - - /** - * Constructs and initializes a Vector2d to (0,0). - */ - public Vector2d() { - super(); - } - - - /** - * Computes the dot product of the this vector and vector v1. - * - * @param v1 the other vector - */ - public final double dot(Vector2d v1) { - return (this.x * v1.x + this.y * v1.y); - } - - - /** - * Returns the length of this vector. - * - * @return the length of this vector - */ - public final double length() { - return Math.sqrt(this.x * this.x + this.y * this.y); - } - - /** - * Returns the squared length of this vector. - * - * @return the squared length of this vector - */ - public final double lengthSquared() { - return (this.x * this.x + this.y * this.y); - } - - /** - * Sets the value of this vector to the normalization of vector v1. - * - * @param v1 the un-normalized vector - */ - public final void normalize(Vector2d v1) { - double norm; - - norm = 1.0 / Math.sqrt(v1.x * v1.x + v1.y * v1.y); - this.x = v1.x * norm; - this.y = v1.y * norm; - } - - /** - * Normalizes this vector in place. - */ - public final void normalize() { - double norm; - - norm = 1.0 / Math.sqrt(this.x * this.x + this.y * this.y); - this.x *= norm; - this.y *= norm; - } - - - /** - * Returns the angle in radians between this vector and the vector - * parameter; the return value is constrained to the range [0,PI]. - * - * @param v1 the other vector - * @return the angle in radians in the range [0,PI] - */ - public final double angle(Vector2d v1) { - double vDot = this.dot(v1) / (this.length() * v1.length()); - if (vDot < -1.0) vDot = -1.0; - if (vDot > 1.0) vDot = 1.0; - return Math.acos(vDot); - - } - - -} diff --git a/src/main/java/com/volmit/adapt/util/Vector2f.java b/src/main/java/com/volmit/adapt/util/Vector2f.java deleted file mode 100644 index 71bc62510..000000000 --- a/src/main/java/com/volmit/adapt/util/Vector2f.java +++ /dev/null @@ -1,168 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 2-element vector that is represented by single-precision floating - * point x,y coordinates. - */ -public class Vector2f extends Tuple2f implements java.io.Serializable { - - // Combatible with 1.1 - static final long serialVersionUID = -2168194326883512320L; - - /** - * Constructs and initializes a Vector2f from the specified xy coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - */ - public Vector2f(float x, float y) { - super(x, y); - } - - - /** - * Constructs and initializes a Vector2f from the specified array. - * - * @param v the array of length 2 containing xy in order - */ - public Vector2f(float[] v) { - super(v); - } - - - /** - * Constructs and initializes a Vector2f from the specified Vector2f. - * - * @param v1 the Vector2f containing the initialization x y data - */ - public Vector2f(Vector2f v1) { - super(v1); - } - - - /** - * Constructs and initializes a Vector2f from the specified Vector2d. - * - * @param v1 the Vector2d containing the initialization x y data - */ - public Vector2f(Vector2d v1) { - super(v1); - } - - - /** - * Constructs and initializes a Vector2f from the specified Tuple2f. - * - * @param t1 the Tuple2f containing the initialization x y data - */ - public Vector2f(Tuple2f t1) { - super(t1); - } - - - /** - * Constructs and initializes a Vector2f from the specified Tuple2d. - * - * @param t1 the Tuple2d containing the initialization x y data - */ - public Vector2f(Tuple2d t1) { - super(t1); - } - - - /** - * Constructs and initializes a Vector2f to (0,0). - */ - public Vector2f() { - super(); - } - - - /** - * Computes the dot product of the this vector and vector v1. - * - * @param v1 the other vector - */ - public final float dot(Vector2f v1) { - return (this.x * v1.x + this.y * v1.y); - } - - - /** - * Returns the length of this vector. - * - * @return the length of this vector - */ - public final float length() { - return (float) Math.sqrt(this.x * this.x + this.y * this.y); - } - - /** - * Returns the squared length of this vector. - * - * @return the squared length of this vector - */ - public final float lengthSquared() { - return (this.x * this.x + this.y * this.y); - } - - /** - * Sets the value of this vector to the normalization of vector v1. - * - * @param v1 the un-normalized vector - */ - public final void normalize(Vector2f v1) { - float norm; - - norm = (float) (1.0 / Math.sqrt(v1.x * v1.x + v1.y * v1.y)); - this.x = v1.x * norm; - this.y = v1.y * norm; - } - - /** - * Normalizes this vector in place. - */ - public final void normalize() { - float norm; - - norm = (float) - (1.0 / Math.sqrt(this.x * this.x + this.y * this.y)); - this.x *= norm; - this.y *= norm; - } - - - /** - * Returns the angle in radians between this vector and the vector - * parameter; the return value is constrained to the range [0,PI]. - * - * @param v1 the other vector - * @return the angle in radians in the range [0,PI] - */ - public final float angle(Vector2f v1) { - double vDot = this.dot(v1) / (this.length() * v1.length()); - if (vDot < -1.0) vDot = -1.0; - if (vDot > 1.0) vDot = 1.0; - return ((float) (Math.acos(vDot))); - } - - -} diff --git a/src/main/java/com/volmit/adapt/util/Vector3d.java b/src/main/java/com/volmit/adapt/util/Vector3d.java deleted file mode 100644 index 1abd32e4c..000000000 --- a/src/main/java/com/volmit/adapt/util/Vector3d.java +++ /dev/null @@ -1,192 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 3-element vector that is represented by double-precision floating point - * x,y,z coordinates. If this value represents a normal, then it should - * be normalized. - */ -public class Vector3d extends Tuple3d implements java.io.Serializable { - - // Combatible with 1.1 - static final long serialVersionUID = 3761969948420550442L; - - /** - * Constructs and initializes a Vector3d from the specified xyz coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - */ - public Vector3d(double x, double y, double z) { - super(x, y, z); - } - - - /** - * Constructs and initializes a Vector3d from the array of length 3. - * - * @param v the array of length 3 containing xyz in order - */ - public Vector3d(double[] v) { - super(v); - } - - - /** - * Constructs and initializes a Vector3d from the specified Vector3d. - * - * @param v1 the Vector3d containing the initialization x y z data - */ - public Vector3d(Vector3d v1) { - super(v1); - } - - - /** - * Constructs and initializes a Vector3d from the specified Vector3f. - * - * @param v1 the Vector3f containing the initialization x y z data - */ - public Vector3d(Vector3f v1) { - super(v1); - } - - - /** - * Constructs and initializes a Vector3d from the specified Tuple3f. - * - * @param t1 the Tuple3f containing the initialization x y z data - */ - public Vector3d(Tuple3f t1) { - super(t1); - } - - - /** - * Constructs and initializes a Vector3d from the specified Tuple3d. - * - * @param t1 the Tuple3d containing the initialization x y z data - */ - public Vector3d(Tuple3d t1) { - super(t1); - } - - - /** - * Constructs and initializes a Vector3d to (0,0,0). - */ - public Vector3d() { - super(); - } - - - /** - * Sets this vector to the vector cross product of vectors v1 and v2. - * - * @param v1 the first vector - * @param v2 the second vector - */ - public final void cross(Vector3d v1, Vector3d v2) { - double x, y; - - x = v1.y * v2.z - v1.z * v2.y; - y = v2.x * v1.z - v2.z * v1.x; - this.z = v1.x * v2.y - v1.y * v2.x; - this.x = x; - this.y = y; - } - - - /** - * Sets the value of this vector to the normalization of vector v1. - * - * @param v1 the un-normalized vector - */ - public final void normalize(Vector3d v1) { - double norm; - - norm = 1.0 / Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z); - this.x = v1.x * norm; - this.y = v1.y * norm; - this.z = v1.z * norm; - } - - - /** - * Normalizes this vector in place. - */ - public final void normalize() { - double norm; - - norm = 1.0 / Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - this.x *= norm; - this.y *= norm; - this.z *= norm; - } - - - /** - * Returns the dot product of this vector and vector v1. - * - * @param v1 the other vector - * @return the dot product of this and v1 - */ - public final double dot(Vector3d v1) { - return (this.x * v1.x + this.y * v1.y + this.z * v1.z); - } - - - /** - * Returns the squared length of this vector. - * - * @return the squared length of this vector - */ - public final double lengthSquared() { - return (this.x * this.x + this.y * this.y + this.z * this.z); - } - - - /** - * Returns the length of this vector. - * - * @return the length of this vector - */ - public final double length() { - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - } - - - /** - * Returns the angle in radians between this vector and the vector - * parameter; the return value is constrained to the range [0,PI]. - * - * @param v1 the other vector - * @return the angle in radians in the range [0,PI] - */ - public final double angle(Vector3d v1) { - double vDot = this.dot(v1) / (this.length() * v1.length()); - if (vDot < -1.0) vDot = -1.0; - if (vDot > 1.0) vDot = 1.0; - return Math.acos(vDot); - } - - -} diff --git a/src/main/java/com/volmit/adapt/util/Vector3f.java b/src/main/java/com/volmit/adapt/util/Vector3f.java deleted file mode 100644 index d73758ff0..000000000 --- a/src/main/java/com/volmit/adapt/util/Vector3f.java +++ /dev/null @@ -1,189 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -/** - * A 3-element vector that is represented by single-precision floating point - * x,y,z coordinates. If this value represents a normal, then it should - * be normalized. - */ -public class Vector3f extends Tuple3f implements java.io.Serializable { - - // Combatible with 1.1 - static final long serialVersionUID = -7031930069184524614L; - - /** - * Constructs and initializes a Vector3f from the specified xyz coordinates. - * - * @param x the x coordinate - * @param y the y coordinate - * @param z the z coordinate - */ - public Vector3f(float x, float y, float z) { - super(x, y, z); - } - - - /** - * Constructs and initializes a Vector3f from the array of length 3. - * - * @param v the array of length 3 containing xyz in order - */ - public Vector3f(float[] v) { - super(v); - } - - - /** - * Constructs and initializes a Vector3f from the specified Vector3f. - * - * @param v1 the Vector3f containing the initialization x y z data - */ - public Vector3f(Vector3f v1) { - super(v1); - } - - - /** - * Constructs and initializes a Vector3f from the specified Vector3d. - * - * @param v1 the Vector3d containing the initialization x y z data - */ - public Vector3f(Vector3d v1) { - super(v1); - } - - - /** - * Constructs and initializes a Vector3f from the specified Tuple3f. - * - * @param t1 the Tuple3f containing the initialization x y z data - */ - public Vector3f(Tuple3f t1) { - super(t1); - } - - - /** - * Constructs and initializes a Vector3f from the specified Tuple3d. - * - * @param t1 the Tuple3d containing the initialization x y z data - */ - public Vector3f(Tuple3d t1) { - super(t1); - } - - - /** - * Constructs and initializes a Vector3f to (0,0,0). - */ - public Vector3f() { - super(); - } - - - /** - * Returns the squared length of this vector. - * - * @return the squared length of this vector - */ - public final float lengthSquared() { - return (this.x * this.x + this.y * this.y + this.z * this.z); - } - - /** - * Returns the length of this vector. - * - * @return the length of this vector - */ - public final float length() { - return (float) - Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - } - - - /** - * Sets this vector to be the vector cross product of vectors v1 and v2. - * - * @param v1 the first vector - * @param v2 the second vector - */ - public final void cross(Vector3f v1, Vector3f v2) { - float x, y; - - x = v1.y * v2.z - v1.z * v2.y; - y = v2.x * v1.z - v2.z * v1.x; - this.z = v1.x * v2.y - v1.y * v2.x; - this.x = x; - this.y = y; - } - - /** - * Computes the dot product of this vector and vector v1. - * - * @param v1 the other vector - * @return the dot product of this vector and v1 - */ - public final float dot(Vector3f v1) { - return (this.x * v1.x + this.y * v1.y + this.z * v1.z); - } - - /** - * Sets the value of this vector to the normalization of vector v1. - * - * @param v1 the un-normalized vector - */ - public final void normalize(Vector3f v1) { - float norm; - - norm = (float) (1.0 / Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z)); - this.x = v1.x * norm; - this.y = v1.y * norm; - this.z = v1.z * norm; - } - - /** - * Normalizes this vector in place. - */ - public final void normalize() { - float norm; - - norm = (float) - (1.0 / Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)); - this.x *= norm; - this.y *= norm; - this.z *= norm; - } - - - /** - * Returns the angle in radians between this vector and the vector - * parameter; the return value is constrained to the range [0,PI]. - * - * @param v1 the other vector - * @return the angle in radians in the range [0,PI] - */ - public final float angle(Vector3f v1) { - double vDot = this.dot(v1) / (this.length() * v1.length()); - if (vDot < -1.0) vDot = -1.0; - if (vDot > 1.0) vDot = 1.0; - return ((float) (Math.acos(vDot))); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/VectorMath.java b/src/main/java/com/volmit/adapt/util/VectorMath.java deleted file mode 100644 index f63943666..000000000 --- a/src/main/java/com/volmit/adapt/util/VectorMath.java +++ /dev/null @@ -1,620 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.Axis; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Entity; -import org.bukkit.util.Vector; - -import java.util.ArrayList; -import java.util.List; - -/** - * Vector utilities - * - * @author cyberpwn - */ -public class VectorMath { - public static Vector scaleStatic(Axis x, Vector v, double amt) { - switch (x) { - case X: - return scaleX(v, amt); - case Y: - return scaleY(v, amt); - case Z: - return scaleZ(v, amt); - } - - return null; - } - - public static Vector scaleX(Vector v, double amt) { - double x = v.getX(); - double y = v.getY(); - double z = v.getZ(); - double rx = x == 0 ? 1 : amt / x; - - return new Vector(x * rx, y * rx, z * rx); - } - - public static Vector scaleY(Vector v, double amt) { - double x = v.getX(); - double y = v.getY(); - double z = v.getZ(); - double rx = y == 0 ? 1 : amt / y; - - return new Vector(x * rx, y * rx, z * rx); - } - - public static Vector scaleZ(Vector v, double amt) { - double x = v.getX(); - double y = v.getY(); - double z = v.getZ(); - double rx = z == 0 ? 1 : amt / z; - - return new Vector(x * rx, y * rx, z * rx); - } - - public static Vector reverseXZ(Vector v) { - v.setX(-v.getX()); - v.setZ(-v.getZ()); - return v; - } - - public static boolean isLookingNear(Location a, Location b, double maxOff) { - Vector perfect = VectorMath.direction(a, b); - Vector actual = a.getDirection(); - - return perfect.distance(actual) <= maxOff; - } - - public static Vector rotate(Direction current, Direction to, Vector v) { - if (current.equals(to)) { - return v; - } else if (current.equals(to.reverse())) { - if (current.isVertical()) { - return new Vector(v.getX(), -v.getY(), v.getZ()); - } else { - return new Vector(-v.getX(), v.getY(), -v.getZ()); - } - } else { - Vector c = current.toVector().clone().add(to.toVector()); - - if (c.getX() == 0) { - if (c.getY() != c.getZ()) { - return rotate90CX(v); - } - - return rotate90CCX(v); - } else if (c.getY() == 0) { - if (c.getX() != c.getZ()) { - return rotate90CY(v); - } - - return rotate90CCY(v); - } else if (c.getZ() == 0) { - if (c.getX() != c.getY()) { - return rotate90CZ(v); - } - - return rotate90CCZ(v); - } - } - - return v; - } - - // Y X 0 0 - // X Z 0 0 - - // 0 X Y 0 - // 0 Z X 0 - - public static Vector rotate(Direction current, Direction to, Vector v, int w, int h, int d) { - if (current.equals(to)) { - return v; - } else if (current.equals(to.reverse())) { - if (current.isVertical()) { - return new Vector(v.getX(), -v.getY() + h, v.getZ()); - } else { - return new Vector(-v.getX() + w, v.getY(), -v.getZ() + d); - } - } else { - Vector c = current.toVector().clone().add(to.toVector()); - - if (c.getX() == 0) { - if (c.getY() != c.getZ()) { - return rotate90CX(v, d); - } - - return rotate90CCX(v, h); - } else if (c.getY() == 0) { - if (c.getX() != c.getZ()) { - return rotate90CY(v, d); - } - - return rotate90CCY(v, w); - } else if (c.getZ() == 0) { - if (c.getX() != c.getY()) { - return rotate90CZ(v, w); - } - - return rotate90CCZ(v, h); - } - } - - return v; - } - - public static Vector rotate90CX(Vector v) { - return new Vector(v.getX(), -v.getZ(), v.getY()); - } - - public static Vector rotate90CCX(Vector v) { - return new Vector(v.getX(), v.getZ(), -v.getY()); - } - - public static Vector rotate90CY(Vector v) { - return new Vector(-v.getZ(), v.getY(), v.getX()); - } - - public static Vector rotate90CCY(Vector v) { - return new Vector(v.getZ(), v.getY(), -v.getX()); - } - - public static Vector rotate90CZ(Vector v) { - return new Vector(v.getY(), -v.getX(), v.getZ()); - } - - public static Vector rotate90CCZ(Vector v) { - return new Vector(-v.getY(), v.getX(), v.getZ()); - } - - public static Vector rotate90CX(Vector v, int s) { - return new Vector(v.getX(), -v.getZ() + s, v.getY()); - } - - public static Vector rotate90CCX(Vector v, int s) { - return new Vector(v.getX(), v.getZ(), -v.getY() + s); - } - - public static Vector rotate90CY(Vector v, int s) { - return new Vector(-v.getZ() + s, v.getY(), v.getX()); - } - - public static Vector rotate90CCY(Vector v, int s) { - return new Vector(v.getZ(), v.getY(), -v.getX() + s); - } - - public static Vector rotate90CZ(Vector v, int s) { - return new Vector(v.getY(), -v.getX() + s, v.getZ()); - } - - public static Vector rotate90CCZ(Vector v, int s) { - return new Vector(-v.getY() + s, v.getX(), v.getZ()); - } - - public static Vector getAxis(Direction current, Direction to) { - if (current.equals(Direction.U) || current.equals(Direction.D)) { - if (to.equals(Direction.U) || to.equals(Direction.D)) { - return new Vector(1, 0, 0); - } else { - if (current.equals(Direction.N) || current.equals(Direction.S)) { - return Direction.E.toVector(); - } else { - return Direction.S.toVector(); - } - } - } - - return new Vector(0, 1, 0); - } - - private static double round(double value, int precision) { - return Double.valueOf(Form.f(value, precision)); - } - - public static Vector clip(Vector v, int decimals) { - v.setX(round(v.getX(), decimals)); - v.setY(round(v.getY(), decimals)); - v.setZ(round(v.getZ(), decimals)); - return v; - } - - public static Vector rotateVectorCC(Vector vec, Vector axis, double deg) { - double theta = Math.toRadians(deg); - double x, y, z; - double u, v, w; - x = vec.getX(); - y = vec.getY(); - z = vec.getZ(); - u = axis.getX(); - v = axis.getY(); - w = axis.getZ(); - double xPrime = u * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + x * Math.cos(theta) + (-w * y + v * z) * Math.sin(theta); - double yPrime = v * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + y * Math.cos(theta) + (w * x - u * z) * Math.sin(theta); - double zPrime = w * (u * x + v * y + w * z) * (1d - Math.cos(theta)) + z * Math.cos(theta) + (-v * x + u * y) * Math.sin(theta); - - return clip(new Vector(xPrime, yPrime, zPrime), 4); - } - - /** - * Get all SIMPLE block faces from a more specific block face (SOUTH_EAST) = - * (south, east) - * - * @param f the block face - * @return multiple faces, or one if the face is already simple - */ - public static List split(BlockFace f) { - List faces = new ArrayList<>(); - - switch (f) { - case DOWN: - faces.add(BlockFace.DOWN); - break; - case EAST: - faces.add(BlockFace.EAST); - break; - case EAST_NORTH_EAST: - faces.add(BlockFace.EAST); - faces.add(BlockFace.EAST); - faces.add(BlockFace.NORTH); - break; - case EAST_SOUTH_EAST: - faces.add(BlockFace.EAST); - faces.add(BlockFace.EAST); - faces.add(BlockFace.SOUTH); - break; - case NORTH: - faces.add(BlockFace.NORTH); - break; - case NORTH_EAST: - faces.add(BlockFace.NORTH); - faces.add(BlockFace.EAST); - break; - case NORTH_NORTH_EAST: - faces.add(BlockFace.NORTH); - faces.add(BlockFace.NORTH); - faces.add(BlockFace.EAST); - break; - case NORTH_NORTH_WEST: - faces.add(BlockFace.NORTH); - faces.add(BlockFace.NORTH); - faces.add(BlockFace.WEST); - break; - case NORTH_WEST: - faces.add(BlockFace.NORTH); - faces.add(BlockFace.WEST); - break; - case SELF: - faces.add(BlockFace.SELF); - break; - case SOUTH: - faces.add(BlockFace.SOUTH); - break; - case SOUTH_EAST: - faces.add(BlockFace.SOUTH); - faces.add(BlockFace.EAST); - break; - case SOUTH_SOUTH_EAST: - faces.add(BlockFace.SOUTH); - faces.add(BlockFace.SOUTH); - faces.add(BlockFace.EAST); - break; - case SOUTH_SOUTH_WEST: - faces.add(BlockFace.SOUTH); - faces.add(BlockFace.SOUTH); - faces.add(BlockFace.WEST); - break; - case SOUTH_WEST: - faces.add(BlockFace.SOUTH); - faces.add(BlockFace.WEST); - break; - case UP: - faces.add(BlockFace.UP); - break; - case WEST: - faces.add(BlockFace.WEST); - break; - case WEST_NORTH_WEST: - faces.add(BlockFace.WEST); - faces.add(BlockFace.WEST); - faces.add(BlockFace.NORTH); - break; - case WEST_SOUTH_WEST: - faces.add(BlockFace.WEST); - faces.add(BlockFace.WEST); - faces.add(BlockFace.SOUTH); - break; - default: - break; - } - - return faces; - } - - /** - * Get a normalized vector going from a location to another - * - * @param from from here - * @param to to here - * @return the normalized vector direction - */ - public static Vector direction(Location from, Location to) { - return to.clone().subtract(from.clone()).toVector().normalize(); - } - - public static Vector directionNoNormal(Location from, Location to) { - return to.clone().subtract(from.clone()).toVector(); - } - - /** - * Get the vector direction from the yaw and pitch - * - * @param yaw the yaw - * @param pitch the pitch - * @return the vector - */ - public static Vector toVector(float yaw, float pitch) { - return new Vector(Math.cos(pitch) * Math.cos(yaw), Math.sin(pitch), Math.cos(pitch) * Math.sin(-yaw)); - } - - /** - * Add an impulse (force) to an entity - * - * @param e the entity - * @param v the vector - */ - public static void impulse(Entity e, Vector v) { - impulse(e, v, 1.0); - } - - /** - * Add an impulse (force) on an entity - * - * @param e the entity - * @param v the vector - * @param effectiveness the effectiveness - */ - public static void impulse(Entity e, Vector v, double effectiveness) { - Vector vx = e.getVelocity(); - vx.add(v.clone().multiply(effectiveness)); - e.setVelocity(vx); - } - - /** - * Reverse a direction - * - * @param v the direction - * @return the reversed direction - */ - public static Vector reverse(Vector v) { - if (v.getX() != 0) { - v.setX(-v.getX()); - } - - if (v.getY() != 0) { - v.setY(-v.getY()); - } - - if (v.getZ() != 0) { - v.setZ(-v.getZ()); - } - - return v; - } - - /** - * Get a speed value from a vector (velocity) - * - * @param v the vector - * @return the speed - */ - public static double getSpeed(Vector v) { - Vector vi = new Vector(0, 0, 0); - Vector vt = new Vector(0, 0, 0).add(v); - - return vi.distance(vt); - } - - /** - * Shift all vectors based on the given vector - * - * @param vector the vector direction to shift the vectors - * @param vectors the vectors to be shifted - * @return the shifted vectors - */ - public static List shift(Vector vector, List vectors) { - return new ArrayList<>(new GListAdapter() { - @Override - public Vector onAdapt(Vector from) { - return from.add(vector); - } - }.adapt(vectors)); - } - - /** - * Attempt to get the blockFace for the vector (will be tri-normalized) - * - * @param v the vector - * @return the block face or null - */ - public static BlockFace getBlockFace(Vector v) { - Vector p = triNormalize(v); - - for (BlockFace i : BlockFace.values()) { - if (p.getX() == i.getModX() && p.getY() == i.getModY() && p.getZ() == i.getModZ()) { - return i; - } - } - - for (BlockFace i : BlockFace.values()) { - if (p.getX() == i.getModX() && p.getZ() == i.getModZ()) { - return i; - } - } - - for (BlockFace i : BlockFace.values()) { - if (p.getY() == i.getModY() && p.getZ() == i.getModZ()) { - return i; - } - } - - for (BlockFace i : BlockFace.values()) { - if (p.getX() == i.getModX() || p.getY() == i.getModY()) { - return i; - } - } - - for (BlockFace i : BlockFace.values()) { - if (p.getX() == i.getModX() || p.getY() == i.getModY() || p.getZ() == i.getModZ()) { - return i; - } - } - - return null; - } - - /** - * Angle the vector in a self relative direction - * - * @param v the initial direction - * @param amt the amount to shift in the direction - * @return the shifted direction - */ - public static Vector angleLeft(Vector v, float amt) { - Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); - l.setDirection(v); - float y = l.getYaw(); - float p = l.getPitch(); - CDou cy = new CDou(360); - CDou cp = new CDou(180); - cy.set(y); - cp.set(p); - cy.sub(amt); - l.setYaw((float) cy.get()); - l.setPitch((float) cp.get()); - - return l.getDirection(); - } - - /** - * Angle the vector in a self relative direction - * - * @param v the initial direction - * @param amt the amount to shift in the direction - * @return the shifted direction - */ - public static Vector angleRight(Vector v, float amt) { - Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); - l.setDirection(v); - float y = l.getYaw(); - float p = l.getPitch(); - CDou cy = new CDou(360); - CDou cp = new CDou(180); - cy.set(y); - cp.set(p); - cy.add(amt); - l.setYaw((float) cy.get()); - l.setPitch((float) cp.get()); - - return l.getDirection(); - } - - /** - * Angle the vector in a self relative direction - * - * @param v the initial direction - * @param amt the amount to shift in the direction - * @return the shifted direction - */ - public static Vector angleUp(Vector v, float amt) { - Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); - l.setDirection(v); - float y = l.getYaw(); - float p = l.getPitch(); - CDou cy = new CDou(360); - cy.set(y); - l.setYaw((float) cy.get()); - l.setPitch(Math.max(-90, p - amt)); - - return l.getDirection(); - } - - /** - * Angle the vector in a self relative direction - * - * @param v the initial direction - * @param amt the amount to shift in the direction - * @return the shifted direction - */ - public static Vector angleDown(Vector v, float amt) { - Location l = new Location(Bukkit.getWorlds().get(0), 0, 0, 0); - l.setDirection(v); - float y = l.getYaw(); - float p = l.getPitch(); - CDou cy = new CDou(360); - cy.set(y); - l.setYaw((float) cy.get()); - l.setPitch(Math.min(90, p + amt)); - - return l.getDirection(); - } - - /** - * (clone) Force normalize the vector into three points, 1, 0, or -1. If the - * value is > 0.333 (1) if the value is less than -0.333 (-1) else 0 - * - * @param direction the direction - * @return the vector - */ - public static Vector triNormalize(Vector direction) { - Vector v = direction.clone(); - v.normalize(); - - if (v.getX() > 0.333) { - v.setX(1); - } else if (v.getX() < -0.333) { - v.setX(-1); - } else { - v.setX(0); - } - - if (v.getY() > 0.333) { - v.setY(1); - } else if (v.getY() < -0.333) { - v.setY(-1); - } else { - v.setY(0); - } - - if (v.getZ() > 0.333) { - v.setZ(1); - } else if (v.getZ() < -0.333) { - v.setZ(-1); - } else { - v.setZ(0); - } - - return v; - } -} diff --git a/src/main/java/com/volmit/adapt/util/VelocitySpeed.java b/src/main/java/com/volmit/adapt/util/VelocitySpeed.java deleted file mode 100644 index 3675e7215..000000000 --- a/src/main/java/com/volmit/adapt/util/VelocitySpeed.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.volmit.adapt.util; - -import org.bukkit.Input; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; - -public final class VelocitySpeed { - public static final double EPSILON = 1.0E-6; - - private VelocitySpeed() { - } - - public static InputSnapshot readInput(Player p, double fallbackThresholdSquared) { - try { - Input input = p.getCurrentInput(); - if (input != null) { - return new InputSnapshot(input.isForward(), input.isBackward(), input.isLeft(), input.isRight()); - } - } catch (NoSuchMethodError ignored) { - // Fallback path for runtimes without Player#getCurrentInput. - } - - Vector horizontal = horizontalOnly(p.getVelocity()); - if (horizontal.lengthSquared() <= Math.max(0, fallbackThresholdSquared)) { - return InputSnapshot.NONE; - } - - Vector movementDirection = horizontal.normalize(); - Vector look = p.getLocation().getDirection().setY(0); - if (look.lengthSquared() <= EPSILON) { - return InputSnapshot.NONE; - } - - look.normalize(); - Vector right = new Vector(-look.getZ(), 0, look.getX()); - double forwardDot = movementDirection.dot(look); - double sideDot = movementDirection.dot(right); - return new InputSnapshot(forwardDot > 0.2, forwardDot < -0.2, sideDot < -0.2, sideDot > 0.2); - } - - public static Vector resolveHorizontalDirection(Player p, InputSnapshot input) { - Vector look = p.getLocation().getDirection().setY(0); - if (look.lengthSquared() <= EPSILON) { - return new Vector(); - } - - look.normalize(); - Vector right = new Vector(-look.getZ(), 0, look.getX()); - Vector direction = new Vector(); - if (input.forward()) { - direction.add(look); - } - if (input.backward()) { - direction.subtract(look); - } - if (input.right()) { - direction.add(right); - } - if (input.left()) { - direction.subtract(right); - } - - if (direction.lengthSquared() <= EPSILON) { - return direction; - } - - return direction.normalize(); - } - - public static Vector horizontalOnly(Vector velocity) { - return new Vector(velocity.getX(), 0, velocity.getZ()); - } - - public static Vector moveTowards(Vector current, Vector target, double maxDelta) { - if (maxDelta <= 0) { - return current.clone(); - } - - Vector delta = target.clone().subtract(current); - double distance = delta.length(); - if (distance <= EPSILON || distance <= maxDelta) { - return target.clone(); - } - - return current.clone().add(delta.multiply(maxDelta / distance)); - } - - public static Vector clampHorizontal(Vector horizontal, double maxSpeed) { - double clamped = Math.max(0, maxSpeed); - if (clamped <= 0) { - return new Vector(); - } - - if (horizontal.lengthSquared() <= clamped * clamped) { - return horizontal; - } - - return horizontal.normalize().multiply(clamped); - } - - public static void setHorizontalVelocity(Player p, Vector horizontal) { - Vector velocity = p.getVelocity(); - p.setVelocity(new Vector(horizontal.getX(), velocity.getY(), horizontal.getZ())); - } - - public static void hardStopHorizontal(Player p) { - Vector velocity = p.getVelocity(); - p.setVelocity(new Vector(0, velocity.getY(), 0)); - } - - public static double speedAmplifierScalar(int amplifier) { - return 1.0 + (Math.max(0, amplifier) + 1) * 0.2; - } - - public record InputSnapshot(boolean forward, boolean backward, boolean left, boolean right) { - public static final InputSnapshot NONE = new InputSnapshot(false, false, false, false); - - public boolean hasHorizontal() { - return forward || backward || left || right; - } - - public boolean isForwardOnly() { - return forward && !backward && !left && !right; - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/Violator.java b/src/main/java/com/volmit/adapt/util/Violator.java deleted file mode 100644 index 523f5d053..000000000 --- a/src/main/java/com/volmit/adapt/util/Violator.java +++ /dev/null @@ -1,270 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentSkipListMap; - -public class Violator { - protected static ConcurrentSkipListMap nodes = new ConcurrentSkipListMap(); - - private static String id(Object o, Object h) { - if (o instanceof Field) { - return id(((Field) o).getDeclaringClass(), null) + "." + ((Field) o).getName(); - } - - if (o instanceof String) { - return (String) o; - } - - if (o instanceof Class) { - return ((Class) o).getCanonicalName(); - } - - if (o instanceof Constructor) { - Constructor co = (Constructor) o; - - String mx = ""; - - for (Class i : co.getParameterTypes()) { - mx += "," + i.getCanonicalName(); - } - - mx = mx.length() >= 1 ? mx.substring(1) : mx; - - return id(co.getDeclaringClass(), null) + "(" + mx + ")"; - } - - if (o instanceof Method) { - String mx = ""; - - for (Class i : ((Method) o).getParameterTypes()) { - mx += "," + i.getCanonicalName(); - } - - mx = mx.length() >= 1 ? mx.substring(1) : mx; - - return id(((Method) o).getDeclaringClass(), null) + "." + ((Method) o).getName() + "(" + mx + ")"; - } - - if (o instanceof Annotation) { - Annotation a = (Annotation) o; - return "@" + a.annotationType().getCanonicalName() + "[" + id(h, null) + "]"; - } - - return o.hashCode() + o.toString(); - } - - private static void p(String n, Object o) { - nodes.put(n, o); - } - - private static boolean h(String n) { - return nodes.containsKey(n); - } - - private static Object g(String n) { - return nodes.get(n); - } - - public static Constructor getConstructor(Class c, Class... params) throws NoSuchMethodException, SecurityException { - String mx = ""; - - for (Class i : params) { - mx += "," + i.getCanonicalName(); - } - - mx = mx.length() >= 1 ? mx.substring(1) : mx; - - if (!h(id(c, null) + "(" + mx + ")")) { - Constructor co = c.getConstructor(params); - co.setAccessible(true); - p(id(co, null), co); - } - - return (Constructor) g(id(c, null) + "(" + mx + ")"); - } - - @SuppressWarnings("rawtypes") - public static Field getField(Class c, String name) throws Throwable { - if (!h(id(c, null) + "." + name)) { - try { - Field f = c.getField(name); - f.setAccessible(true); - p(id(c, null) + "." + name, f); - } catch (NoSuchFieldException e) { - Class s = c.getSuperclass(); - if (null == s) { - throw e; - } - Field f = s.getField(name); - f.setAccessible(true); - p(id(c, null) + "." + name, f); - } - } - - return (Field) g(id(c, null) + "." + name); - } - - @SuppressWarnings("rawtypes") - public static Field getDeclaredField(Class c, String name) throws Throwable { - if (!h(id(c, null) + "." + name)) { - try { - Field f = c.getDeclaredField(name); - f.setAccessible(true); - p(id(c, null) + "." + name, f); - } catch (NoSuchFieldException e) { - Class s = c.getSuperclass(); - if (null == s) { - throw e; - } - Field f = s.getDeclaredField(name); - f.setAccessible(true); - p(id(c, null) + "." + name, f); - } - } - - return (Field) g(id(c, null) + "." + name); - } - - public static Method getMethod(Class c, String name, Class... pars) throws Throwable { - String iv = ""; - String mx = ""; - - for (Class i : pars) { - mx += "," + i.getCanonicalName(); - } - - mx = mx.length() >= 1 ? mx.substring(1) : mx; - iv = id(c, null) + "." + name + "(" + mx + ")"; - - if (!h(iv)) { - Method f = c.getMethod(name, pars); - f.setAccessible(true); - p(iv, f); - } - - return (Method) g(iv); - } - - @SuppressWarnings("unchecked") - public static T construct(Class c, Object... parameters) { - List> cv = new ArrayList>(); - - for (Object i : parameters) { - cv.add(i.getClass()); - } - - try { - Constructor co = getConstructor(c, cv.toArray(new Class[cv.size()])); - return (T) co.newInstance(parameters); - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - public static Method getDeclaredMethod(Class c, String name, Class... pars) throws Throwable { - String iv = ""; - String mx = ""; - - for (Class i : pars) { - mx += "," + i.getCanonicalName(); - } - - mx = mx.length() >= 1 ? mx.substring(1) : mx; - iv = id(c, null) + "." + name + "(" + mx + ")"; - - if (!h(iv)) { - Method f = c.getDeclaredMethod(name, pars); - f.setAccessible(true); - p(iv, f); - } - - return (Method) g(iv); - } - - @SuppressWarnings("unchecked") - public static T getAnnotation(Class c, Class a) throws Throwable { - if (!h("@" + a.getCanonicalName() + "[" + c.getCanonicalName() + "]")) { - T f = c.getAnnotation(a); - p(id(f, c), f); - } - - return (T) g("@" + a.getCanonicalName() + "[" + c.getCanonicalName() + "]"); - } - - @SuppressWarnings("unchecked") - public static T getDeclaredAnnotation(Class c, Class a) throws Throwable { - if (!h("@" + a.getCanonicalName() + "[" + c.getCanonicalName() + "]")) { - T f = c.getDeclaredAnnotation(a); - p(id(f, c), f); - } - - return (T) g("@" + a.getCanonicalName() + "[" + c.getCanonicalName() + "]"); - } - - @SuppressWarnings("unchecked") - public static T getAnnotation(Field c, Class a) throws Throwable { - if (!h("@" + a.getCanonicalName() + "[" + id(c, null) + "]")) { - T f = c.getAnnotation(a); - p(id(f, c), f); - } - - return (T) g("@" + a.getCanonicalName() + "[" + id(c, null) + "]"); - } - - @SuppressWarnings("unchecked") - public static T getDeclaredAnnotation(Field c, Class a) throws Throwable { - if (!h("@" + a.getCanonicalName() + "[" + id(c, null) + "]")) { - T f = c.getDeclaredAnnotation(a); - p(id(f, c), f); - } - - return (T) g("@" + a.getCanonicalName() + "[" + id(c, null) + "]"); - } - - @SuppressWarnings("unchecked") - public static T getAnnotation(Method c, Class a) throws Throwable { - if (!h("@" + a.getCanonicalName() + "[" + id(c, null) + "]")) { - T f = c.getAnnotation(a); - p(id(f, c), f); - } - - return (T) g("@" + a.getCanonicalName() + "[" + id(c, null) + "]"); - } - - @SuppressWarnings("unchecked") - public static T getDeclaredAnnotation(Method c, Class a) throws Throwable { - if (!h("@" + a.getCanonicalName() + "[" + id(c, null) + "]")) { - T f = c.getDeclaredAnnotation(a); - p(id(f, c), f); - - System.out.println("Set as " + id(f, c) + " as " + ("@" + a.getCanonicalName() + "[" + id(c, null) + "]")); - } - - return (T) g("@" + a.getCanonicalName() + "[" + id(c, null) + "]"); - } -} diff --git a/src/main/java/com/volmit/adapt/util/VirtualCommand.java b/src/main/java/com/volmit/adapt/util/VirtualCommand.java deleted file mode 100644 index 2d2d64830..000000000 --- a/src/main/java/com/volmit/adapt/util/VirtualCommand.java +++ /dev/null @@ -1,184 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.collection.KMap; -import org.bukkit.Bukkit; -import org.bukkit.Sound; -import org.bukkit.command.CommandSender; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.Map; - - -/** - * Represents a virtual command. A chain of iterative processing through - * subcommands. - * - * @author cyberpwn - */ -public class VirtualCommand { - private final ICommand command; - private final String tag; - - private final KMap, VirtualCommand> children; - - public VirtualCommand(ICommand command) { - this(command, ""); - } - - public VirtualCommand(ICommand command, String tag) { - this.command = command; - children = new KMap<>(); - this.tag = tag; - - for (Field i : command.getClass().getDeclaredFields()) { - if (i.isAnnotationPresent(Command.class)) { - try { - Command cc = i.getAnnotation(Command.class); - ICommand cmd = (ICommand) i.getType().getConstructor().newInstance(); - new V(command, true, true).set(i.getName(), cmd); - children.put(cmd.getAllNodes(), new VirtualCommand(cmd, cc.value().trim().isEmpty() ? tag : cc.value().trim())); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - - public String getTag() { - return tag; - } - - public ICommand getCommand() { - return command; - } - - public Map, VirtualCommand> getChildren() { - return children; - } - - public boolean hit(CommandSender sender, KList chain) { - return hit(sender, chain, null); - } - - public boolean hit(CommandSender sender, KList chain, String label) { - MortarSender vs = new MortarSender(sender); - vs.setTag(tag); - - if (label != null) { - vs.setCommand(label); - } - - if (chain.isEmpty()) { - if (!checkPermissions(sender, command)) { - return true; - } - - return command.handle(vs, new String[0]); - } - - String nl = chain.get(0); - - for (List i : children.k()) { - for (String j : i) { - if (j.equalsIgnoreCase(nl)) { - vs.setCommand(chain.get(0)); - VirtualCommand cmd = children.get(i); - KList c = chain.copy(); - c.remove(0); - if (cmd.hit(sender, c, vs.getCommand())) { - if (vs.isPlayer()) { - SoundPlayer spw = SoundPlayer.of(vs.player().getWorld()); - spw.play(vs.player().getLocation(), Sound.ITEM_AXE_STRIP, 0.35f, 1.8f); - } - - return true; - } - } - } - } - - if (!checkPermissions(sender, command)) { - return true; - } - - return command.handle(vs, chain.toArray(new String[chain.size()])); - } - - public List hitTab(CommandSender sender, KList chain, String label) { - MortarSender vs = new MortarSender(sender); - vs.setTag(tag); - - if (label != null) - vs.setCommand(label); - - if (chain.isEmpty()) { - if (!checkPermissions(sender, command)) { - return null; - } - - return command.handleTab(vs, new String[0]); - } - - String nl = chain.get(0); - - for (List i : children.k()) { - for (String j : i) { - if (j.equalsIgnoreCase(nl)) { - vs.setCommand(chain.get(0)); - VirtualCommand cmd = children.get(i); - KList c = chain.copy(); - c.remove(0); - List v = cmd.hitTab(sender, c, vs.getCommand()); - if (v != null) { - return v; - } - } - } - } - - if (!checkPermissions(sender, command)) { - return null; - } - - return command.handleTab(vs, chain.toArray(new String[chain.size()])); - } - - private boolean checkPermissions(CommandSender sender, ICommand command2) { - boolean failed = false; - - for (String i : command.getRequiredPermissions()) { - if (!sender.hasPermission(i)) { - failed = true; - Bukkit.getScheduler().scheduleSyncDelayedTask(Adapt.instance, () -> Adapt.messagePlayer(sender.getServer().getPlayer(sender.getName()), "- " + C.WHITE + i), 0); - } - } - - if (failed) { - Adapt.messagePlayer(sender.getServer().getPlayer(sender.getName()), "Insufficient Permissions"); - return false; - } - - return true; - } -} diff --git a/src/main/java/com/volmit/adapt/util/VoidOutputStream.java b/src/main/java/com/volmit/adapt/util/VoidOutputStream.java deleted file mode 100644 index 26304b70a..000000000 --- a/src/main/java/com/volmit/adapt/util/VoidOutputStream.java +++ /dev/null @@ -1,29 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.IOException; -import java.io.OutputStream; - -public class VoidOutputStream extends OutputStream { - @Override - public void write(int b) throws IOException { - // poof - } -} diff --git a/src/main/java/com/volmit/adapt/util/VolmitPlugin.java b/src/main/java/com/volmit/adapt/util/VolmitPlugin.java deleted file mode 100644 index 5cadf70bd..000000000 --- a/src/main/java/com/volmit/adapt/util/VolmitPlugin.java +++ /dev/null @@ -1,297 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.reflect.events.ReflectiveEvents; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.util.List; - -public abstract class VolmitPlugin extends JavaPlugin implements Listener { - public static boolean bad = false; - private KMap controllers; - private List cachedControllers; - private KMap, IController> cachedClassControllers; - - public void l(Object l) { - Adapt.info("[" + getName() + "]: " + l); - } - - public void w(Object l) { - Adapt.warn("[" + getName() + "]: " + l); - } - - public void f(Object l) { - Adapt.error("[" + getName() + "]: " + l); - } - - public void v(Object l) { - Adapt.verbose("[" + getName() + "]: " + l); - } - - public void onEnable() { - registerInstance(); - registerControllers(); - Bukkit.getScheduler().scheduleSyncRepeatingTask(this, this::tickControllers, 0, 0); - J.a(this::outputInfo); - registerListener(this); - start(); - } - - public void unregisterAll() { - try { - stopControllers(); - unregisterListeners(); - unregisterInstance(); - } catch (Exception e) { - Adapt.error("Adapt: Failed to unregister all, You have a plugin that is not unloading properly. This is a bug in that plugin. Please report it to the developer. This is on shutdown however, so it's not a big deal."); - Adapt.error("Adapt: This is not a bug in Adapt. This is a bug in another plugin. Adapt is unloading ALL Command Nodes with Adapt ID's, If another plugin is unloading all or some of these nodes, it will cause this error."); - } - } - - private void outputInfo() { - try { - IO.delete(getDataFolder("info")); - getDataFolder("info").mkdirs(); - outputPluginInfo(); - } catch (Throwable ignored) { - Adapt.verbose("Failed to output info"); - - } - } - - - - - private void outputPluginInfo() throws IOException { - FileConfiguration fc = new YamlConfiguration(); - fc.set("version", getDescription().getVersion()); - fc.set("name", getDescription().getName()); - fc.save(getDataFile("info", "plugin.yml")); - } - - - - @Override - public void onDisable() { - stop(); - Bukkit.getScheduler().cancelTasks(this); - unregisterListener(this); - unregisterAll(); - } - - private void tickControllers() { - if (bad) { - return; - } - - for (IController i : getControllers()) { - tickController(i); - } - } - - private void tickController(IController i) { - if (bad) { - return; - } - - if (i.getTickInterval() < 0) { - return; - } - - M.tick++; - if (M.interval(i.getTickInterval())) { - try { - i.tick(); - } catch (Throwable e) { - w("Failed to tick controller " + i.getName()); - e.printStackTrace(); - } - } - } - - public List getControllers() { - return cachedControllers; - } - - private void registerControllers() { - if (bad) { - return; - } - controllers = new KMap<>(); - cachedClassControllers = new KMap<>(); - - for (Field i : getClass().getDeclaredFields()) { - if (i.isAnnotationPresent(Control.class)) { - try { - i.setAccessible(true); - IController pc = (IController) i.getType().getConstructor().newInstance(); - registerController(pc); - i.set(this, pc); - v("Registered " + pc.getName() + " (" + i.getName() + ")"); - } catch (IllegalArgumentException | IllegalAccessException | InstantiationException | - InvocationTargetException | NoSuchMethodException | SecurityException e) { - w("Failed to register controller (field " + i.getName() + ")"); - e.printStackTrace(); - } - } - } - - cachedControllers = controllers.v(); - } - - public IController getController(Class c) { - return cachedClassControllers.get(c); - } - - private void registerController(IController pc) { - if (bad) { - return; - } - controllers.put(pc.getName(), pc); - cachedClassControllers.put(pc.getClass(), pc); - registerListener(pc); - - try { - pc.start(); - v("Started " + pc.getName()); - } catch (Throwable e) { - w("Failed to start controller " + pc.getName()); - e.printStackTrace(); - } - } - - private void registerInstance() { - if (bad) { - return; - } - for (Field i : getClass().getDeclaredFields()) { - if (i.isAnnotationPresent(Instance.class)) { - try { - i.setAccessible(true); - i.set(Modifier.isStatic(i.getModifiers()) ? null : this, this); - v("Registered Instance " + i.getName()); - } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) { - w("Failed to register instance (field " + i.getName() + ")"); - e.printStackTrace(); - } - } - } - } - - private void unregisterInstance() { - if (bad) { - return; - } - for (Field i : getClass().getDeclaredFields()) { - if (i.isAnnotationPresent(Instance.class)) { - try { - i.setAccessible(true); - i.set(Modifier.isStatic(i.getModifiers()) ? null : this, null); - v("Unregistered Instance " + i.getName()); - } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) { - w("Failed to unregister instance (field " + i.getName() + ")"); - e.printStackTrace(); - } - } - } - } - - - public String getTag() { - if (bad) { - return ""; - } - return getTag(""); - } - - public void registerListener(Listener l) { - if (bad) { - return; - } - Bukkit.getPluginManager().registerEvents(l, this); - ReflectiveEvents.register(l); - } - - public void unregisterListener(Listener l) { - if (bad) { - return; - } - HandlerList.unregisterAll(l); - } - - public void unregisterListeners() { - if (bad) { - return; - } - HandlerList.unregisterAll((Listener) this); - } - - - private void stopControllers() { - if (bad) { - return; - } - for (IController i : controllers.v()) { - try { - unregisterListener(i); - i.stop(); - v("Stopped " + i.getName()); - } catch (Throwable e) { - w("Failed to stop controller " + i.getName()); - e.printStackTrace(); - } - } - } - - public File getDataFile(String... strings) { - File f = new File(getDataFolder(), String.join(File.separator, strings)); - f.getParentFile().mkdirs(); - return f; - } - - public File getDataFolder(String... strings) { - if (strings.length == 0) { - return super.getDataFolder(); - } - - File f = new File(getDataFolder(), String.join(File.separator, strings)); - f.mkdirs(); - - return f; - } - - public abstract void start(); - - public abstract void stop(); - - public abstract String getTag(String subTag); -} diff --git a/src/main/java/com/volmit/adapt/util/VolmitSender.java b/src/main/java/com/volmit/adapt/util/VolmitSender.java deleted file mode 100644 index 26885c1f1..000000000 --- a/src/main/java/com/volmit/adapt/util/VolmitSender.java +++ /dev/null @@ -1,635 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util; - - -//import com.volmit.Adapt.Adapt; -//import com.volmit.Adapt.util.collection.List; -//import com.volmit.Adapt.util.collection.Map; -//import com.volmit.Adapt.util.decree.DecreeParameter; -//import com.volmit.Adapt.util.decree.virtual.VirtualDecreeCommand; -import static art.arcane.amulet.MagicalSugar.*; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameter; -import com.volmit.adapt.util.decree.virtual.VirtualDecreeCommand; -import lombok.Getter; -import lombok.Setter; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.title.Title; -import org.bukkit.Server; -import org.bukkit.Sound; -import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.entity.Player; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionAttachment; -import org.bukkit.permissions.PermissionAttachmentInfo; -import org.bukkit.plugin.Plugin; - -import java.time.Duration; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Represents a volume sender. A command sender with extra crap in its - * - * @author cyberpwn - */ -public class VolmitSender implements CommandSender { - @Getter - private static final Map helpCache = new HashMap<>(); - private final CommandSender s; - public boolean useConsoleCustomColors = true; - public boolean useCustomColorsIngame = true; - public int spinh = -20; - public int spins = 7; - public int spinb = 8; - private String tag; - @Getter - @Setter - private String command; - - /** - * Wrap a command sender - * - * @param s the command sender - */ - public VolmitSender(CommandSender s) { - tag = ""; - this.s = s; - } - - public VolmitSender(CommandSender s, String tag) { - this.tag = tag; - this.s = s; - } - - public static long getTick() { - return com.volmit.adapt.util.M.ms() / 16; - } - - public static String pulse(String colorA, String colorB, double speed) { - return ""; - } - - public static String pulse(double speed) { - return Form.f(invertSpread((((getTick() * 15D * speed) % 1000D) / 1000D)), 3).replaceAll("\\Q,\\E", ".").replaceAll("\\Q?\\E", "-"); - } - - public static double invertSpread(double v) { - return ((1D - v) * 2D) - 1D; - } - - public static List paginate(List all, int linesPerPage, int page, AtomicBoolean hasNext) { - int totalPages = (int) Math.ceil((double) all.size() / linesPerPage); - page = page < 0 ? 0 : page >= totalPages ? totalPages - 1 : page; - hasNext.set(page < totalPages - 1); - List d = new ArrayList<>(); - - for (int i = linesPerPage * page; i < Math.min(all.size(), linesPerPage * (page + 1)); i++) { - d.add(all.get(i)); - } - - return d; - } - - /** - * Get the command tag - * - * @return the command tag - */ - public String getTag() { - return tag; - } - - /** - * Set a command tag (prefix for sendMessage) - * - * @param tag the tag - */ - public void setTag(String tag) { - this.tag = tag; - } - - /** - * Is this sender a player? - * - * @return true if it is - */ - public boolean isPlayer() { - return getS() instanceof Player; - } - - /** - * Is this sender a console? - * - * @return true if it is - */ - public boolean isConsole() { - return getS() instanceof ConsoleCommandSender; - } - - /** - * Force cast to player (be sure to check first) - * - * @return a casted player - */ - public Player player() { - return (Player) getS(); - } - - /** - * Get the origin sender this object is wrapping - * - * @return the command sender - */ - public CommandSender getS() { - return s; - } - - @Override - public boolean isPermissionSet(String name) { - return s.isPermissionSet(name); - } - - @Override - public boolean isPermissionSet(Permission perm) { - return s.isPermissionSet(perm); - } - - @Override - public boolean hasPermission(String name) { - return s.hasPermission(name); - } - - @Override - public boolean hasPermission(Permission perm) { - return s.hasPermission(perm); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { - return s.addAttachment(plugin, name, value); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin) { - return s.addAttachment(plugin); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { - return s.addAttachment(plugin, name, value, ticks); - } - - @Override - public PermissionAttachment addAttachment(Plugin plugin, int ticks) { - return s.addAttachment(plugin, ticks); - } - - @Override - public void removeAttachment(PermissionAttachment attachment) { - s.removeAttachment(attachment); - } - - @Override - public void recalculatePermissions() { - s.recalculatePermissions(); - } - - @Override - public Set getEffectivePermissions() { - return s.getEffectivePermissions(); - } - - @Override - public boolean isOp() { - return s.isOp(); - } - - @Override - public void setOp(boolean value) { - s.setOp(value); - } - - public void hr() { - s.sendMessage("========================================================"); - } - - public void sendTitle(String title, String subtitle, int i, int s, int o) { - Adapt.audiences.player(player()).showTitle(Title.title( - createComponent(title), - createComponent(subtitle), - Title.Times.times(Duration.ofMillis(i), Duration.ofMillis(s), Duration.ofMillis(o)))); - } - - public void sendProgress(double percent, String thing) { - //noinspection IfStatementWithIdenticalBranches - if (percent < 0) { - int l = 44; - int g = (int) (1D * l); - sendTitle(C.ADAPT + thing + " ", 0, 500, 250); - sendActionNoProcessing("" + "" + pulse("#00BFFF", "#003366", 1D) + " " + Form.repeat(" ", g) + "" + Form.repeat(" ", l - g)); - } else { - int l = 44; - int g = (int) (percent * l); - sendTitle(C.ADAPT + thing + " " + C.BLUE + "" + Form.pc(percent, 0), 0, 500, 250); - sendActionNoProcessing("" + "" + pulse("#00BFFF", "#003366", 1D) + " " + Form.repeat(" ", g) + "" + Form.repeat(" ", l - g)); - } - } - - public void sendAction(String action) { - Adapt.audiences.player(player()).sendActionBar(createNoPrefixComponent(action)); - } - - public void sendActionNoProcessing(String action) { - Adapt.audiences.player(player()).sendActionBar(createNoPrefixComponentNoProcessing(action)); - } - - public void sendTitle(String subtitle, int i, int s, int o) { - Adapt.audiences.player(player()).showTitle(Title.title( - createNoPrefixComponent(" "), - createNoPrefixComponent(subtitle), - Title.Times.times(Duration.ofMillis(i), Duration.ofMillis(s), Duration.ofMillis(o)))); - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean canUseCustomColors(VolmitSender volmitSender) { - return volmitSender.isPlayer() ? useCustomColorsIngame : useConsoleCustomColors; - } - - private Component createNoPrefixComponent(String message) { - if (!canUseCustomColors(this)) { - String t = C.translateAlternateColorCodes('&', MiniMessage.miniMessage().stripTags(message)); - return MiniMessage.miniMessage().deserialize(t); - } - - String t = C.translateAlternateColorCodes('&', message); - String a = C.aura(t, spinh, spins, spinb, 0.36); - return MiniMessage.miniMessage().deserialize(a); - } - - private Component createNoPrefixComponentNoProcessing(String message) { - return MiniMessage.builder().postProcessor(c -> c).build().deserialize(message); - } - - private Component createComponent(String message) { - if (!canUseCustomColors(this)) { - String t = C.translateAlternateColorCodes('&', MiniMessage.miniMessage().stripTags(getTag() + message)); - return MiniMessage.miniMessage().deserialize(t); - } - - String t = C.translateAlternateColorCodes('&', getTag() + message); - String a = C.aura(t, spinh, spins, spinb); - return MiniMessage.miniMessage().deserialize(a); - } - - private Component createComponentRaw(String message) { - if (!canUseCustomColors(this)) { - String t = C.translateAlternateColorCodes('&', MiniMessage.miniMessage().stripTags(getTag() + message)); - return MiniMessage.miniMessage().deserialize(t); - } - - String t = C.translateAlternateColorCodes('&', getTag() + message); - return MiniMessage.miniMessage().deserialize(t); - } - - public void showWaiting(String passive, CompletableFuture f) { - AtomicInteger v = new AtomicInteger(); - v.set(J.sr(() -> { - if (f.isDone()) { - J.csr(v.get()); - sendAction(" "); - return; - } - - sendProgress(-1, passive); - }, 1)); - J.a(() -> { - try { - f.get(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - }); - - } - - @Override - public void sendMessage(String message) { - if (s instanceof CommandDummy) { - return; - } - - if ((!useCustomColorsIngame && s instanceof Player) || !useConsoleCustomColors) { - s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message)); - return; - } - - if (message.contains("")) { - s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message.replaceAll("\\Q\\E", ""))); - return; - } - - try { - Adapt.audiences.sender(s).sendMessage(createComponent(message)); - } catch (Throwable e) { - System.out.println("============================================="); - e.printStackTrace(); - if (e.getCause() != null) { - e.getCause().printStackTrace(); - } - System.out.println("============================================="); - String t = C.translateAlternateColorCodes('&', getTag() + message); - String a = C.aura(t, spinh, spins, spinb); - - Adapt.debug("Failure to parse " + a); - s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message)); - } - } - - public void sendMessageBasic(String message) { - s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message)); - } - - public void sendMessageRaw(String message) { - if (s instanceof CommandDummy) { - return; - } - - if ((!useCustomColorsIngame && s instanceof Player) || !useConsoleCustomColors) { - s.sendMessage(C.translateAlternateColorCodes('&', message)); - return; - } - - if (message.contains("")) { - s.sendMessage(message.replaceAll("\\Q\\E", "")); - return; - } - - try { - Adapt.audiences.sender(s).sendMessage(createComponentRaw(message)); - } catch (Throwable e) { - String t = C.translateAlternateColorCodes('&', getTag() + message); - String a = C.aura(t, spinh, spins, spinb); - - System.out.println("============================================="); - e.printStackTrace(); - if (e.getCause() != null) { - e.getCause().printStackTrace(); - } - System.out.println("============================================="); - Adapt.debug("Failure to parse " + a); - s.sendMessage(C.translateAlternateColorCodes('&', getTag() + message)); - } - } - - @Override - public void sendMessage(String[] messages) { - for (String str : messages) - sendMessage(str); - } - - @Override - public void sendMessage(UUID uuid, String message) { - sendMessage(message); - } - - @Override - public void sendMessage(UUID uuid, String[] messages) { - sendMessage(messages); - } - - @Override - public Server getServer() { - return s.getServer(); - } - - @Override - public String getName() { - return s.getName(); - } - - @Override - public Spigot spigot() { - return s.spigot(); - } - - private String pickRandoms(int max, VirtualDecreeCommand i) { - KList m = new KList<>(); - for (int ix = 0; ix < max; ix++) { - m.add((i.isNode() - ? (i.getNode().getParameters().isNotEmpty()) - ? "<#B0E0E6>✦ <#00FA9A>" - + i.getParentPath() - + " <#00CED1>" - + i.getName() + " " - + i.getNode().getParameters().shuffleCopy(RNG.r).kConvert((f) - -> (f.isRequired() || RNG.r.b(0.5) - ? "<#f2e15e>" + f.getNames().getRandom() + "=" - + "<#9370DB>" + f.example() - : "")) - .toString(" ") - : "" - : "")); - } - - return m.removeDuplicates().kConvert((iff) -> iff.replaceAll("\\Q \\E", " ")).toString("\n"); - } - - public void sendHeader(String name, int overrideLength) { - int len = overrideLength; - int h = name.length() + 2; - String s = Form.repeat(" ", len - h - 4); - String si = Form.repeat("(", 3); - String so = Form.repeat(")", 3); - String sf = "["; - String se = "]"; - - if (name.trim().isEmpty()) { - sendMessageRaw("" + sf + s + "" + s + se); - } else { - sendMessageRaw("" + sf + s + si + " " + name + " " + so + s + se); - } - } - - public void sendHeader(String name) { - sendHeader(name, 40); - } - - public void sendDecreeHelp(VirtualDecreeCommand v) { - sendDecreeHelp(v, 0); - } - - public void sendDecreeHelp(VirtualDecreeCommand v, int page) { - if (!isPlayer()) { - for (VirtualDecreeCommand i : v.getNodes()) { - sendDecreeHelpNode(i); - } - - return; - } - - int m = v.getNodes().size(); - - sendMessageRaw("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); - - if (v.getNodes().isNotEmpty()) { - sendHeader(v.getPath() + (page > 0 ? (" {" + (page + 1) + "}") : "")); - if (isPlayer() && v.getParent() != null) { - sendMessageRaw("Click to go back to <#4682B4>" + Form.capitalize(v.getParent().getName()) + " Help" + "'><#B30000>〈 Back"); - } - - AtomicBoolean next = new AtomicBoolean(false); - for (VirtualDecreeCommand i : paginate(v.getNodes(), 17, page, next)) { - sendDecreeHelpNode(i); - } - - String s = ""; - int l = 75 - (page > 0 ? 10 : 0) - (next.get() ? 10 : 0); - - if (page > 0) { - s += "Click to go back to page " + page + "'>〈 Page " + page + " "; - } - - s += "" + Form.repeat(" ", l) + ""; - - if (next.get()) { - s += " Click to go to back to page " + (page + 2) + "'>Page " + (page + 2) + " ❭"; - } - - sendMessageRaw(s); - } else { - sendMessage(C.RED + "There are no subcommands in this group! Contact support, this is a command design issue!"); - } - } - - public void sendDecreeHelpNode(VirtualDecreeCommand i) { - if (isPlayer() || s instanceof CommandDummy) { - sendMessageRaw(helpCache.computeIfAbsent(i.getPath(), (k) -> { - String newline = "\n"; - - /// Command - // Contains main command & aliases - String realText = i.getPath() + " >" + "<#E6F2FF>⇀ " + i.getName(); - String hoverTitle = i.getNames().copy().reverse().kConvert((f) -> "<#00BFFF>" + f).toString(", "); - String description = "<#3fe05a>✎ <#6ad97d>" + i.getDescription(); - String usage = "<#FF0000>✒ <#A52A2A>"; - - String onClick; - if (i.isNode()) { - if (i.getNode().getParameters().isEmpty()) { - usage += "There are no parameters. Click to type command."; - onClick = "suggest_command"; - } else { - usage += "Hover over all of the parameters to learn more."; - onClick = "suggest_command"; - } - } else { - usage += "This is a command category. Click to run."; - onClick = "run_command"; - } - - String suggestion = ""; - String suggestions = ""; - if (i.isNode() && i.getNode().getParameters().isNotEmpty()) { - suggestion += newline + "<#B0E0E6>✦ <#00FA9A>" + i.getParentPath() + " <#00CED1>" + i.getName() + " " - + i.getNode().getParameters().kConvert((f) -> "<#9370DB>" + f.example()).toString(" "); - suggestions += newline + "" + pickRandoms(Math.min(i.getNode().getParameters().size() + 1, 5), i); - } - - /// Params - StringBuilder nodes = new StringBuilder(); - if (i.isNode()) { - for (DecreeParameter p : i.getNode().getParameters()) { - - String nTitle = "" + p.getName(); - String nHoverTitle = p.getNames().kConvert((ff) -> "<#ff9900>" + ff).toString(", "); - String nDescription = "<#2E8B57>✎ <#3CB371>" + p.getDescription(); - String nUsage; - String fullTitle; - Adapt.debug("Contextual: " + p.isContextual() + " / player: " + isPlayer()); - if (p.isContextual() && (isPlayer() || s instanceof CommandDummy)) { - fullTitle = "<#FFD700>[" + nTitle + "<#FFD700>] "; - nUsage = "<#ff9900>➱ <#FFD700>The value may be derived from environment context."; - } else if (p.isRequired()) { - fullTitle = "[" + nTitle + "] "; - nUsage = "<#CD5C5C>⚠ <#F08080>This parameter is required."; - } else if (p.hasDefault()) { - fullTitle = "<#F7F7F7>⊰" + nTitle + "<#F7F7F7>⊱"; - nUsage = "<#1E90FF>✔ <#87CEEB>Defaults to \"" + p.getParam().defaultValue() + "\" if undefined."; - } else { - fullTitle = "<#F7F7F7>⊰" + nTitle + "<#F7F7F7>⊱"; - nUsage = "<#8A2BE2>✔ <#87CEEB>This parameter is optional."; - } - String type = "<#cc00ff>✢ <#ff33cc>This parameter is of type " + p.getType().getSimpleName() + "."; - - nodes - .append("") - .append(fullTitle) - .append(""); - } - } else { - nodes = new StringBuilder(" - Category of Commands"); - } - - /// Wrapper - String wrapper = - "" + - "" + - "" + - " " + - nodes; - - return wrapper; - })); - } else { - sendMessage(i.getPath()); - } - } - - public void playSound(Sound sound, float volume, float pitch) { - if (isPlayer()) { - player().playSound(player().getLocation(), sound, volume, pitch); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/WeightMap.java b/src/main/java/com/volmit/adapt/util/WeightMap.java deleted file mode 100644 index 804d4381f..000000000 --- a/src/main/java/com/volmit/adapt/util/WeightMap.java +++ /dev/null @@ -1,63 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import com.volmit.adapt.util.collection.KMap; - -public class WeightMap extends KMap { - private static final long serialVersionUID = 87558033900969389L; - private boolean modified = false; - private double lastWeight = 0; - - public double getPercentChance(T t) { - if (totalWeight() <= 0) { - return 0; - } - - return getWeight(t) / totalWeight(); - } - - public void clear() { - modified = true; - } - - public WeightMap setWeight(T t, double weight) { - modified = true; - put(t, weight); - - return this; - } - - public double getWeight(T t) { - return get(t); - } - - public double totalWeight() { - if (!modified) { - return lastWeight; - } - - modified = false; - Shrinkwrap s = new Shrinkwrap(0D); - k().forEach((d) -> s.set(s.get() + 1)); - lastWeight = s.get(); - - return lastWeight; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Window.java b/src/main/java/com/volmit/adapt/util/Window.java deleted file mode 100644 index dab519eb5..000000000 --- a/src/main/java/com/volmit/adapt/util/Window.java +++ /dev/null @@ -1,94 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -public interface Window { - WindowDecorator getDecorator(); - - Window setDecorator(WindowDecorator decorator); - - WindowResolution getResolution(); - - Window setResolution(WindowResolution resolution); - - Window clearElements(); - - Window close(); - - Window open(); - - Window callClosed(); - - Window updateInventory(); - - ItemStack computeItemStack(int viewportSlot); - - int getLayoutRow(int viewportSlottedPosition); - - int getLayoutPosition(int viewportSlottedPosition); - - int getRealLayoutPosition(int viewportSlottedPosition); - - int getRealPosition(int position, int row); - - int getRow(int realPosition); - - int getPosition(int realPosition); - - boolean isVisible(); - - Window setVisible(boolean visible); - - int getViewportPosition(); - - Window setViewportPosition(int position); - - int getViewportSlots(); - - int getMaxViewportPosition(); - - Window scroll(int direction); - - int getViewportHeight(); - - Window setViewportHeight(int height); - - String getTitle(); - - Window setTitle(String title); - - String getTag(); - - void setTag(String s); - - boolean hasElement(int position, int row); - - Window setElement(int position, int row, Element e); - - Element getElement(int position, int row); - - Player getViewer(); - - Window reopen(); - - Window onClosed(Callback window); -} diff --git a/src/main/java/com/volmit/adapt/util/WindowDecorator.java b/src/main/java/com/volmit/adapt/util/WindowDecorator.java deleted file mode 100644 index c5afc1302..000000000 --- a/src/main/java/com/volmit/adapt/util/WindowDecorator.java +++ /dev/null @@ -1,23 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public interface WindowDecorator { - Element onDecorateBackground(Window window, int position, int row); -} diff --git a/src/main/java/com/volmit/adapt/util/WindowResolution.java b/src/main/java/com/volmit/adapt/util/WindowResolution.java deleted file mode 100644 index 1e86aca90..000000000 --- a/src/main/java/com/volmit/adapt/util/WindowResolution.java +++ /dev/null @@ -1,53 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import org.bukkit.event.inventory.InventoryType; - -public enum WindowResolution { - W9_H6(9, 6, InventoryType.CHEST), - W5_H1(5, 1, InventoryType.HOPPER), - W3_H3(3, 3, InventoryType.DROPPER); - - private final int width; - private final int maxHeight; - private final InventoryType type; - - WindowResolution(int w, int h, InventoryType type) { - this.width = w; - this.maxHeight = h; - this.type = type; - } - - public int getMaxWidthOffset() { - return (getWidth() - 1) / 2; - } - - public int getWidth() { - return width; - } - - public int getMaxHeight() { - return maxHeight; - } - - public InventoryType getType() { - return type; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Wrapper.java b/src/main/java/com/volmit/adapt/util/Wrapper.java deleted file mode 100644 index f1b38e74a..000000000 --- a/src/main/java/com/volmit/adapt/util/Wrapper.java +++ /dev/null @@ -1,70 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -public class Wrapper { - private T t; - - public Wrapper(T t) { - set(t); - } - - public void set(T t) { - this.t = t; - } - - public T get() { - return t; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((t == null) ? 0 : t.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Wrapper)) { - return false; - } - - Wrapper other = (Wrapper) obj; - if (t == null) { - return other.t == null; - } else return t.equals(other.t); - } - - @Override - public String toString() { - if (t != null) { - return get().toString(); - } - - return super.toString() + " (null)"; - } -} diff --git a/src/main/java/com/volmit/adapt/util/Writable.java b/src/main/java/com/volmit/adapt/util/Writable.java deleted file mode 100644 index 35eea50f5..000000000 --- a/src/main/java/com/volmit/adapt/util/Writable.java +++ /dev/null @@ -1,29 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public interface Writable { - void write(DataOutputStream o) throws IOException; - - void read(DataInputStream i) throws IOException; -} diff --git a/src/main/java/com/volmit/adapt/util/cache/AtomicCache.java b/src/main/java/com/volmit/adapt/util/cache/AtomicCache.java deleted file mode 100644 index da65037ee..000000000 --- a/src/main/java/com/volmit/adapt/util/cache/AtomicCache.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.cache; - - -//import com.volmit.react.React; -//import com.volmit.react.util.function.NastySupplier; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.function.NastySupplier; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Supplier; - -public class AtomicCache { - private transient final AtomicReference t; - private transient final AtomicBoolean set; - private transient final ReentrantLock lock; - private transient final boolean nullSupport; - - public AtomicCache() { - this(false); - } - - public AtomicCache(boolean nullSupport) { - set = nullSupport ? new AtomicBoolean() : null; - t = new AtomicReference<>(); - lock = new ReentrantLock(); - this.nullSupport = nullSupport; - } - - public void reset() { - t.set(null); - - if (nullSupport) { - set.set(false); - } - } - - public T aquireNasty(NastySupplier t) { - return aquire(() -> { - try { - return t.get(); - } catch (Throwable e) { - return null; - } - }); - } - - public T aquireNastyPrint(NastySupplier t) { - return aquire(() -> { - try { - return t.get(); - } catch (Throwable e) { - e.printStackTrace(); - return null; - } - }); - } - - public T aquire(Supplier t) { - if (this.t.get() != null) { - return this.t.get(); - } else if (nullSupport && set.get()) { - return null; - } - - lock.lock(); - - if (this.t.get() != null) { - lock.unlock(); - return this.t.get(); - } else if (nullSupport && set.get()) { - lock.unlock(); - return null; - } - - try { - this.t.set(t.get()); - - if (nullSupport) { - set.set(true); - } - } catch (Throwable e) { - Adapt.error("Atomic cache failure!"); - e.printStackTrace(); - } - - lock.unlock(); - - return this.t.get(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/cache/Cache.java b/src/main/java/com/volmit/adapt/util/cache/Cache.java deleted file mode 100644 index e8f191d26..000000000 --- a/src/main/java/com/volmit/adapt/util/cache/Cache.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.cache; - -import org.bukkit.Chunk; - -public interface Cache { - static long key(Chunk chunk) { - return key(chunk.getX(), chunk.getZ()); - } - - static long key(int x, int z) { - return (((long) x) << 32) | (z & 0xffffffffL); - } - - static int keyX(long key) { - return (int) (key >> 32); - } - - static int keyZ(long key) { - return (int) key; - } - - static int to1D(int x, int y, int z, int w, int h) { - return (z * w * h) + (y * w) + x; - } - - static int[] to3D(int idx, int w, int h) { - final int z = idx / (w * h); - idx -= (z * w * h); - final int y = idx / w; - final int x = idx % w; - return new int[]{x, y, z}; - } - - int getId(); - - V get(int x, int z); -} diff --git a/src/main/java/com/volmit/adapt/util/cache/ChunkCache2D.java b/src/main/java/com/volmit/adapt/util/cache/ChunkCache2D.java deleted file mode 100644 index 23a40c953..000000000 --- a/src/main/java/com/volmit/adapt/util/cache/ChunkCache2D.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.cache; - -//import com.volmit.react.util.function.Function2; - -import com.volmit.adapt.util.Function2; - -import java.util.concurrent.atomic.AtomicReferenceArray; - -public class ChunkCache2D { - private final AtomicReferenceArray cache; - - public ChunkCache2D() { - this.cache = new AtomicReferenceArray<>(256); - } - - public T get(int x, int z, Function2 resolver) { - int key = ((z & 15) * 16) + (x & 15); - T t = cache.get(key); - - if (t == null) { - t = resolver.apply(x, z); - cache.set(key, t); - } - - return t; - } -} diff --git a/src/main/java/com/volmit/adapt/util/cache/WorldCache2D.java b/src/main/java/com/volmit/adapt/util/cache/WorldCache2D.java deleted file mode 100644 index c6da7755e..000000000 --- a/src/main/java/com/volmit/adapt/util/cache/WorldCache2D.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.cache; - -//import com.volmit.react.util.data.KCache; -//import com.volmit.react.util.function.Function2; - -import com.volmit.adapt.util.Function2; -import com.volmit.adapt.util.data.KCache; - -public class WorldCache2D { - private final KCache> chunks; - private final Function2 resolver; - - public WorldCache2D(Function2 resolver) { - this.resolver = resolver; - chunks = new KCache<>((x) -> new ChunkCache2D<>(), 1024); - } - - public T get(int x, int z) { - ChunkCache2D chunk = chunks.get(Cache.key(x >> 4, z >> 4)); - return chunk.get(x, z, resolver); - } - - public long getSize() { - return chunks.getSize() * 256L; - } -} diff --git a/src/main/java/com/volmit/adapt/util/collection/GBiset.java b/src/main/java/com/volmit/adapt/util/collection/GBiset.java deleted file mode 100644 index 6a0f791da..000000000 --- a/src/main/java/com/volmit/adapt/util/collection/GBiset.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.collection; - - -import java.io.Serializable; - -/** - * A Biset - * - * @param the first object type - * @param the second object type - * @author cyberpwn - */ -@SuppressWarnings("hiding") -public class GBiset implements Serializable { - private static final long serialVersionUID = 1L; - private A a; - private B b; - - /** - * Create a new Biset - * - * @param a the first object - * @param b the second object - */ - public GBiset(A a, B b) { - this.a = a; - this.b = b; - } - - /** - * Get the object of the type A - * - * @return the first object - */ - public A getA() { - return a; - } - - /** - * Set the first object - * - * @param a the first object A - */ - public void setA(A a) { - this.a = a; - } - - /** - * Get the second object - * - * @return the second object - */ - public B getB() { - return b; - } - - /** - * Set the second object - */ - public void setB(B b) { - this.b = b; - } -} diff --git a/src/main/java/com/volmit/adapt/util/collection/GListAdapter.java b/src/main/java/com/volmit/adapt/util/collection/GListAdapter.java deleted file mode 100644 index 9f29c552c..000000000 --- a/src/main/java/com/volmit/adapt/util/collection/GListAdapter.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.collection; - - -import java.util.List; - -/** - * Adapts a list of objects into a list of other objects - * - * @param the from object in lists (the item INSIDE the list) - * @param the to object in lists (the item INSIDE the list) - * @author cyberpwn - */ -public abstract class GListAdapter { - /** - * Adapts a list of FROM to a list of TO - * - * @param from the from list - * @return the to list - */ - public List adapt(List from) { - List adapted = new KList<>(); - - for (FROM i : from) { - TO t = onAdapt(i); - - if (t != null) { - adapted.add(onAdapt(i)); - } - } - - return adapted; - } - - /** - * Adapts a list object FROM to TO for use with the adapt method - * - * @param from the from object - * @return the to object - */ - public abstract TO onAdapt(FROM from); -} diff --git a/src/main/java/com/volmit/adapt/util/collection/KList.java b/src/main/java/com/volmit/adapt/util/collection/KList.java deleted file mode 100644 index eafdef4c0..000000000 --- a/src/main/java/com/volmit/adapt/util/collection/KList.java +++ /dev/null @@ -1,689 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.collection; - -import com.google.common.util.concurrent.AtomicDoubleArray; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.RNG; - -import java.util.*; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collector; -import java.util.stream.Collectors; - -@SuppressWarnings("ALL") -public class KList extends ArrayList implements List { - private static final long serialVersionUID = -2892550695744823337L; - - @SafeVarargs - public KList(T... ts) { - super(); - add(ts); - } - - public KList() { - super(); - } - - public KList(int cap) { - super(cap); - } - - public KList(Collection values) { - super(); - add(values); - } - - public KList(Enumeration e) { - super(); - add(e); - } - - public static Collector> collector() { - return Collectors.toCollection(KList::new); - } - - public static KList asStringList(List oo) { - KList s = new KList(); - - for (Object i : oo) { - s.add(i.toString()); - } - - return s; - } - - public int indexOfAddIfNeeded(T v) { - addIfMissing(v); - return indexOf(v); - } - - /** - * Remove and return the last element. - */ - @Override - public T removeLast() { - return super.removeLast(); - } - - public void addMultiple(T t, int c) { - for (int i = 0; i < c; i++) { - add(t); - } - } - - private KList add(Enumeration e) { - while (e.hasMoreElements()) { - add(e.nextElement()); - } - - return this; - } - - public KList add(Collection values) { - addAll(values); - return this; - } - - /** - * Create a Map out of this list where this list becomes the values of the - * returned map. You must specify each key for each value in this list. In the - * function, returning null will not add the keyval pair. - * - * @param the inferred key type - * @param f the function - * @return the new map - */ - public KMap asValues(Function f) { - KMap m = new KMap(); - forEach((i) -> m.putNonNull(f.apply(i), i)); - return m; - } - - /** - * Create a Map out of this list where this list becomes the keys of the - * returned map. You must specify each value for each key in this list. In the - * function, returning null will not add the keyval pair. - * - * @param the inferred value type - * @param f the function - * @return the new map - */ - public KMap asKeys(Function f) { - KMap m = new KMap(); - forEach((i) -> m.putNonNull(i, f.apply(i))); - return m; - } - - /** - * Cut this list into targetCount sublists - * - * @param targetCount the target count of sublists - * @return the list of sublists - */ - public KList> divide(int targetCount) { - return split(size() / targetCount); - } - - /** - * Split this list into a list of sublists with roughly targetSize elements of T - * per sublist - * - * @param targetSize the target size - * @return the list of sublists - */ - public KList> split(int targetSize) { - targetSize = targetSize < 1 ? 1 : targetSize; - KList> gg = new KList<>(); - KList b = new KList<>(); - - for (T i : this) { - if (b.size() >= targetSize) { - gg.add(b.copy()); - b.clear(); - } - - b.add(i); - } - - if (!b.isEmpty()) { - gg.add(b); - } - - return gg; - } - - /** - * Rewrite this list by checking each value and changing the value (or not). - * Return null to remove the element in the function - * - * @param t the function - * @return the same list (not a copy) - */ - public KList rewrite(Function t) { - KList m = copy(); - clear(); - - for (T i : m) { - addNonNull(t.apply(i)); - } - - return this; - } - - /** - * To array - * - * @return the array - */ - @SuppressWarnings("unchecked") - public T[] array() { - return (T[]) toArray(); - } - - /** - * Return a copy of this list - * - * @return the copy - */ - public KList copy() { - return new KList().add(this); - } - - /** - * Shuffle the list - * - * @return the same list - */ - public KList shuffle() { - Collections.shuffle(this); - return this; - } - - public KList shuffle(Random rng) { - Collections.shuffle(this, rng); - return this; - } - - /** - * Sort the list (based on toString comparison) - * - * @return the same list - */ - public KList sort() { - Collections.sort(this, (a, b) -> a.toString().compareTo(b.toString())); - return this; - } - - /** - * Reverse this list - * - * @return the same list - */ - public KList reverse() { - Collections.reverse(this); - return this; - } - - @Override - public String toString() { - return "[" + toString(", ") + "]"; - } - - /** - * Tostring with a seperator for each item in the list - * - * @param split the seperator - * @return the string representing this object - */ - public String toString(String split) { - if (isEmpty()) { - return ""; - } - - if (size() == 1) { - return get(0) + ""; - } - - StringBuilder b = new StringBuilder(); - - for (String i : toStringList()) { - b.append(split).append(i == null ? "null" : i); - } - - return b.substring(split.length()); - } - - /** - * Invoke tostring on each value in the list into a string list - * - * @return the string list - */ - public KList toStringList() { - return kConvert((t) -> t + ""); - } - - /** - * Add the contents of the given list (v) into this list using a converter - * - * @param the type of the forign list - * @param v the forign (given) list - * @param converter the converter that converts the forign type into this list type - * @return this list (builder) - */ - public KList addFrom(List v, Function converter) { - v.forEach((g) -> add(converter.apply(g))); - return this; - } - - /** - * Convert this list into another list type. Such as GList to - * GList. list.convert((i) -> "" + i); - */ - public KList kConvert(Function converter) { - KList v = new KList(); - forEach((t) -> v.addNonNull(converter.apply(t))); - return v; - } - - public KList removeWhere(Predicate t) { - for (T i : copy()) { - if (t.test(i)) { - remove(i); - } - } - - return this; - } - - /** - * Adds T to the list, ignores if null - * - * @param t the value to add - * @return the same list - */ - public KList addNonNull(T t) { - if (t != null) { - super.add(t); - } - - return this; - } - - /** - * Swaps the values of index a and b. For example "hello", "world", "!" swap(1, - * 2) would change the list to "hello", "!", "world" - * - * @param a the first index - * @param b the second index - * @return the same list (builder), not a copy - */ - public KList swapIndexes(int a, int b) { - T aa = remove(a); - T bb = get(b); - add(a, bb); - remove(b); - add(b, aa); - - return this; - } - - /** - * Remove a number of elements from the list - * - * @param t the elements - * @return this list - */ - @SuppressWarnings("unchecked") - public KList remove(T... t) { - for (T i : t) { - super.remove(i); - } - - return this; - } - - /** - * Add another glist's contents to this one (addall builder) - * - * @param t the list - * @return the same list - */ - public KList add(KList t) { - super.addAll(t); - return this; - } - - /** - * Add a number of values to this list - * - * @param t the list - * @return this list - */ - @SuppressWarnings("unchecked") - public KList add(T... t) { - for (T i : t) { - super.add(i); - } - - return this; - } - - /** - * Check if this list has an index at the given index - * - * @param index the given index - * @return true if size > index - */ - public boolean hasIndex(int index) { - return size() > index && index >= 0; - } - - /** - * Get the last index of this list (size - 1) - * - * @return the last index of this list - */ - public int last() { - return size() - 1; - } - - /** - * Deduplicate this list by converting to linked hash set and back - * - * @return the deduplicated list - */ - public KList dedupe() { - LinkedHashSet lhs = new LinkedHashSet(this); - return qclear().add(lhs); - } - - /** - * Clear this list (and return it) - * - * @return the same list - */ - public KList qclear() { - super.clear(); - return this; - } - - /** - * Simply !isEmpty() - * - * @return true if this list has 1 or more element(s) - */ - public boolean hasElements() { - return !isEmpty(); - } - - /** - * Simply !isEmpty() - * - * @return true if this list has 1 or more element(s) - */ - public boolean isNotEmpty() { - return !isEmpty(); - } - - /** - * Pop the first item off this list and return it - * - * @return the popped off item or null if the list is empty - */ - public T pop() { - if (isEmpty()) { - return null; - } - - return remove(0); - } - - /** - * Pop the last item off this list and return it - * - * @return the popped off item or null if the list is empty - */ - public T popLast() { - if (isEmpty()) { - return null; - } - - return remove(last()); - } - - public T popRandom() { - if (isEmpty()) { - return null; - } - - if (size() == 1) { - return pop(); - } - - return remove(M.irand(0, last())); - } - - public T popRandom(RNG rng) { - if (isEmpty()) { - return null; - } - - if (size() == 1) { - return pop(); - } - - return remove(rng.i(0, last())); - } - - public KList sub(int f, int t) { - KList g = new KList<>(); - - for (int i = f; i < M.min(size(), t); i++) { - g.add(get(i)); - } - - return g; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(Object[] values) { - for (Object i : values) { - add((T) i); - } - - return this; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(int[] values) { - for (Object i : values) { - add((T) i); - } - - return this; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(double[] values) { - for (Object i : values) { - add((T) i); - } - - return this; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(AtomicDoubleArray values) { - for (int i = 0; i < values.length(); i++) { - add((T) ((Object) values.get(i))); - } - - return this; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(float[] values) { - for (Object i : values) { - add((T) i); - } - - return this; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(byte[] values) { - for (Object i : values) { - add((T) i); - } - - return this; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(short[] values) { - for (Object i : values) { - add((T) i); - } - - return this; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(long[] values) { - for (Object i : values) { - add((T) i); - } - - return this; - } - - @SuppressWarnings("unchecked") - public KList forceAdd(boolean[] values) { - for (Object i : values) { - add((T) i); - } - - return this; - } - - public T middleValue() { - return get(middleIndex()); - } - - // todo: this should be private - public int middleIndex() { - return size() % 2 == 0 ? (size() / 2) : ((size() / 2) + 1); - } - - public T getRandom() { - if (isEmpty()) { - return null; - } - - if (size() == 1) { - return get(0); - } - - return get(M.irand(0, last())); - } - - public KList popRandom(RNG rng, int c) { - KList m = new KList<>(); - - for (int i = 0; i < c; i++) { - if (isEmpty()) { - break; - } - - m.add(popRandom()); - } - - return m; - } - - public T getRandom(RNG rng) { - if (isEmpty()) { - return null; - } - - if (size() == 1) { - return get(0); - } - - return get(rng.i(0, last())); - } - - public KList qdel(T t) { - remove(t); - return this; - } - - public KList qadd(T t) { - add(t); - return this; - } - - public KList qaddIfMissing(T t) { - addIfMissing(t); - return this; - } - - public KList removeDuplicates() { - KSet v = new KSet<>(); - v.addAll(this); - KList m = new KList<>(); - m.addAll(v); - return m; - } - - public KList nonNull() { - return removeWhere(Objects::isNull); - } - - public boolean addIfMissing(T t) { - if (!contains(t)) { - add(t); - return true; - } - - return false; - } - - public void addAllIfMissing(KList t) { - for (T i : t) { - if (!contains(i)) { - add(i); - } - } - } - - public KList shuffleCopy(Random rng) { - KList t = copy(); - t.shuffle(rng); - return t; - } - - public KList qdrop() { - pop(); - return this; - } -} diff --git a/src/main/java/com/volmit/adapt/util/collection/KMap.java b/src/main/java/com/volmit/adapt/util/collection/KMap.java deleted file mode 100644 index 50e9cde89..000000000 --- a/src/main/java/com/volmit/adapt/util/collection/KMap.java +++ /dev/null @@ -1,390 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.collection; - -import com.volmit.adapt.util.Consumer2; -import com.volmit.adapt.util.Consumer3; -import com.volmit.adapt.util.Queue; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -@SuppressWarnings("ALL") -public class KMap extends ConcurrentHashMap { - private static final long serialVersionUID = 7288942695300448163L; - - public KMap() { - super(); - } - - public KMap(Map gMap) { - this(); - put(gMap); - } - - public K getKey(V value) { - for (KeyPair i : keypair()) { - if (i.getV().equals(value)) { - return i.getK(); - } - } - - return null; - } - - /** - * Puts a value into a map-value-list based on the key such that if GMap> where V is GList - * - * @param the list type in the value type - * @param k the key to look for - * @param vs the values to put into the list of the given key - * @return the same list (builder) - */ - @SuppressWarnings("unchecked") - public KMap putValueList(K k, S... vs) { - try { - KMap> s = (KMap>) this; - - if (!s.containsKey(k)) { - s.put(k, new KList()); - } - - s.get(k).add(vs); - } catch (Throwable e) { - e.printStackTrace(); - - } - - return this; - } - - /** - * Returns a sorted list of keys from this map, based on the sorting order of - * the values. - * - * @return the value-sorted key list - */ - public KList sortK() { - KList k = new KList(); - KList v = v(); - - Collections.sort(v, new Comparator() { - @Override - public int compare(V v, V t1) { - return v.toString().compareTo(t1.toString()); - } - }); - - for (V i : v) { - for (K j : k()) { - if (get(j).equals(i)) { - k.add(j); - } - } - } - - k.dedupe(); - return k; - } - - /** - * Returns a sorted list of keys from this map, based on the sorting order of - * the values. Sorting is based on numerical values - * - * @return the value-sorted key list - */ - public KList sortKNumber() { - KList k = new KList(); - KList v = v(); - - Collections.sort(v, new Comparator() { - @Override - public int compare(V v, V t1) { - Number n1 = (Number) v; - Number n2 = (Number) t1; - - return (int) ((n1.doubleValue() - n2.doubleValue()) * 1000); - } - }); - - for (V i : v) { - for (K j : k()) { - if (get(j).equals(i)) { - k.add(j); - } - } - } - - k.dedupe(); - return k; - } - - /** - * Put another map's values into this map - * - * @param m the map to insert - * @return this map (builder) - */ - public KMap put(Map m) { - putAll(m); - return this; - } - - /** - * Return a copy of this map - * - * @return the copied map - */ - public KMap copy() { - return new KMap(this); - } - - /** - * Loop through each keyvalue set (copy of it) with the map parameter - * - * @param f the function - * @return the same gmap - */ - public KMap rewrite(Consumer3> f) { - KMap m = copy(); - - for (K i : m.k()) { - f.accept(i, get(i), this); - } - - return this; - } - - /** - * Loop through each keyvalue set (copy of it) - * - * @param f the function - * @return the same gmap - */ - public KMap keach(Consumer2 f) { - for (K i : k()) { - f.accept(i, get(i)); - } - - return this; - } - - /** - * Flip the hashmap and flatten the value list even if there are multiple keys - * - * @return the flipped and flattened hashmap - */ - public KMap flipFlatten() { - KMap> f = kFlip(); - KMap m = new KMap<>(); - - for (V i : f.k()) { - m.putNonNull(i, m.isEmpty() ? null : m.get(0)); - } - - return m; - } - - /** - * Flip the hashmap so keys are now list-keys in the value position - * - * @return the flipped hashmap - */ - public KMap> kFlip() { - KMap> flipped = new KMap>(); - - for (K i : keySet()) { - if (i == null) { - continue; - } - - if (!flipped.containsKey(get(i))) { - flipped.put(get(i), new KList()); - } - - flipped.get(get(i)).add(i); - } - - return flipped; - } - - /** - * Sort values based on the keys sorting order - * - * @return the values (sorted) - */ - public KList sortV() { - KList v = new KList(); - KList k = k(); - - Collections.sort(k, new Comparator() { - @Override - public int compare(K v, K t1) { - return v.toString().compareTo(t1.toString()); - } - }); - - for (K i : k) { - for (V j : v()) { - if (get(i).equals(j)) { - v.add(j); - } - } - } - - v.dedupe(); - return v; - } - - public KList sortVNoDedupe() { - KList v = new KList(); - KList k = k(); - - Collections.sort(k, new Comparator() { - @Override - public int compare(K v, K t1) { - return v.toString().compareTo(t1.toString()); - } - }); - - for (K i : k) { - for (V j : v()) { - if (get(i).equals(j)) { - v.add(j); - } - } - } - - return v; - } - - /** - * Get a copy of this maps keys - * - * @return the keys - */ - public KList k() { - KList k = new KList(); - Enumeration kk = keys(); - - while (kk.hasMoreElements()) { - K kkk = kk.nextElement(); - k.add(kkk); - } - - return k; - } - - /** - * Get a copy of this maps values - * - * @return the values - */ - public KList v() { - return new KList(values()); - } - - /** - * Still works as it normally should except it returns itself (builder) - * - * @param key the key - * @param value the value (single only supported) - */ - public KMap qput(K key, V value) { - super.put(key, value); - return this; - } - - /** - * Works just like put, except it wont put anything unless the key and value are - * nonnull - * - * @param key the nonnull key - * @param value the nonnull value - * @return the same map - */ - public KMap putNonNull(K key, V value) { - if (key != null || value != null) { - put(key, value); - } - - return this; - } - - public V putThen(K key, V valueIfKeyNotPresent) { - if (!containsKey(key)) { - put(key, valueIfKeyNotPresent); - } - - return get(key); - } - - /** - * Clear this map and return it - * - * @return the cleared map - */ - public KMap qclear() { - super.clear(); - return this; - } - - /** - * Convert this map to keypairs - * - * @return the keypair list - */ - public KList> keypair() { - KList> g = new KList<>(); - forEach((k, v) -> g.add(new KeyPair(k, v))); - return g; - } - - /** - * Create a keypair queue - * - * @return the queue - */ - public Queue> enqueue() { - return Queue.create(keypair()); - } - - /** - * Create a key queue - * - * @return the queue - */ - public Queue enqueueKeys() { - return Queue.create(k()); - } - - /** - * Create a value queue - * - * @return the queue - */ - public Queue enqueueValues() { - return Queue.create(v()); - } -} diff --git a/src/main/java/com/volmit/adapt/util/collection/KSet.java b/src/main/java/com/volmit/adapt/util/collection/KSet.java deleted file mode 100644 index a8cd08539..000000000 --- a/src/main/java/com/volmit/adapt/util/collection/KSet.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.collection; - -import org.jetbrains.annotations.NotNull; - -import java.io.Serial; -import java.io.Serializable; -import java.util.AbstractSet; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; - - -public class KSet extends AbstractSet implements Serializable { - @Serial - private static final long serialVersionUID = 1L; - private final ConcurrentHashMap map; - - public KSet() { - map = new ConcurrentHashMap<>(); - } - - public KSet(Collection c) { - this(); - addAll(c); - } - - public KSet(int initialCapacity, float loadFactor) { - map = new ConcurrentHashMap<>(initialCapacity, loadFactor); - } - - public KSet(int initialCapacity) { - map = new ConcurrentHashMap<>(initialCapacity); - } - - @Override - public int size() { - return map.size(); - } - - @Override - public boolean contains(Object o) { - return map.containsKey(o); - } - - @Override - public boolean add(T t) { - return map.putIfAbsent(t, Boolean.TRUE) == null; - } - - @Override - public boolean remove(Object o) { - return map.remove(o) != null; - } - - @Override - public void clear() { - map.clear(); - } - - @NotNull - @Override - public Iterator iterator() { - return map.keySet().iterator(); - } - - public KSet copy() { - return new KSet<>(this); - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/collection/KeyPair.java b/src/main/java/com/volmit/adapt/util/collection/KeyPair.java deleted file mode 100644 index af0698f37..000000000 --- a/src/main/java/com/volmit/adapt/util/collection/KeyPair.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.collection; - -/** - * Represents a keypair - * - * @param the key type - * @param the value type - * @author cyberpwn - */ -@SuppressWarnings("hiding") -public class KeyPair { - private K k; - private V v; - - /** - * Create a keypair - * - * @param k the key - * @param v the value - */ - public KeyPair(K k, V v) { - this.k = k; - this.v = v; - } - - public K getK() { - return k; - } - - public void setK(K k) { - this.k = k; - } - - public V getV() { - return v; - } - - public void setV(V v) { - this.v = v; - } -} diff --git a/src/main/java/com/volmit/adapt/util/collection/StateList.java b/src/main/java/com/volmit/adapt/util/collection/StateList.java deleted file mode 100644 index bad070c8f..000000000 --- a/src/main/java/com/volmit/adapt/util/collection/StateList.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.collection; - -public class StateList { - private final KList states; - - public StateList(String... states) { - this.states = new KList(states); - - if (getBits() > 64) { - throw new RuntimeException("StateLists cannot exceed 64 bits! You are trying to use " + getBits() + " bits!"); - } - } - - public StateList(Enum... states) { - this.states = new KList>().kConvert(Enum::name); - - if (getBits() > 64) { - throw new RuntimeException("StateLists cannot exceed 64 bits! You are trying to use " + getBits() + " bits!"); - } - } - - public long max() { - return (long) (Math.pow(2, getBits()) - 1); - } - - public KList getEnabled(long list) { - KList f = new KList<>(); - - for (String i : states) { - if (is(list, i)) { - f.add(i); - } - } - - return f; - } - - public long of(String... enabledStates) { - long b = 0; - - for (String i : enabledStates) { - b |= getBit(i); - } - - return b; - } - - public long set(long list, String state, boolean enabled) { - long bit = getBit(state); - boolean is = is(list, state); - - if (enabled && !is) { - return list | bit; - } else if (!enabled && is) { - return list ^ bit; - } - - return list; - } - - public boolean is(long list, String state) { - long bit = getBit(state); - - return bit > 0 && (list & bit) == bit; - } - - public boolean hasBit(String state) { - return getBit(state) > 0; - } - - public long getBit(String state) { - return getBit(states.indexOf(state)); - } - - public long getBit(int index) { - return (long) (index < 0 ? -1 : Math.pow(2, index)); - } - - public int getBytes() { - return getBits() == 0 ? 0 : ((getBits() >> 2) + 1); - } - - public int getBits() { - return states.size(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/command/FCommand.java b/src/main/java/com/volmit/adapt/util/command/FCommand.java deleted file mode 100644 index e850d5122..000000000 --- a/src/main/java/com/volmit/adapt/util/command/FCommand.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.volmit.adapt.util.command; - -public interface FCommand { - -} diff --git a/src/main/java/com/volmit/adapt/util/command/FConst.java b/src/main/java/com/volmit/adapt/util/command/FConst.java deleted file mode 100644 index 4c244e80d..000000000 --- a/src/main/java/com/volmit/adapt/util/command/FConst.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.volmit.adapt.util.command; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.format.TextColor; -import org.bukkit.Sound; - -import java.awt.*; - -public class FConst { - public static final Color COLOR_ERROR = new Color(255, 0, 0); - public static final Color COLOR_SUCCESS = new Color(0, 255, 0); - public static final Color COLOR_WARNING = new Color(255, 255, 0); - public static final Color COLOR_INFO = new Color(255, 255, 255); - public static final Feedback TELEPORT = Feedback.builder() - .sound(SoundFeedback.builder() - .sound(Sound.ENTITY_ENDER_EYE_LAUNCH) - .volume(0.7f) - .pitch(1.25f) - .build()) - .build(); - - public static Feedback error(String message, Object... args) { - return Feedback.builder() - .message(errorText(message, args)) - .sound(SoundFeedback.builder().sound(Sound.BLOCK_DEEPSLATE_BREAK).pitch(0.5f).volume(1f).build()) - .build(); - } - - public static Feedback success(String message, Object... args) { - return Feedback.builder() - .message(successText(message, args)) - .sound(SoundFeedback.builder().sound(Sound.BLOCK_AMETHYST_BLOCK_PLACE).pitch(1.5f).volume(1f).build()) - .sound(SoundFeedback.builder().sound(Sound.ITEM_ARMOR_EQUIP_ELYTRA).pitch(1.1f).volume(1f).build()) - .build(); - } - - public static Feedback warning(String message, Object... args) { - return Feedback.builder() - .message(warningText(message, args)) - .sound(SoundFeedback.builder().sound(Sound.ITEM_ARMOR_EQUIP_CHAIN).pitch(0.6f).volume(1f).build()) - .build(); - } - - public static Feedback info(String message, Object... args) { - return Feedback.builder() - .message(infoText(message, args)) - .sound(SoundFeedback.builder().sound(Sound.ITEM_ARMOR_EQUIP_LEATHER).pitch(1.1f).volume(1f).build()) - .build(); - } - - public static TextComponent errorText(String message, Object... args) { - return Component.text(message.formatted(args)).color(TextColor.color(FConst.COLOR_ERROR.getRGB())); - } - - public static TextComponent successText(String message, Object... args) { - return Component.text(message.formatted(args)).color(TextColor.color(FConst.COLOR_SUCCESS.getRGB())); - } - - public static TextComponent warningText(String message, Object... args) { - return Component.text(message.formatted(args)).color(TextColor.color(FConst.COLOR_WARNING.getRGB())); - } - - public static TextComponent infoText(String message, Object... args) { - return Component.text(message.formatted(args)).color(TextColor.color(FConst.COLOR_INFO.getRGB())); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/command/FService.java b/src/main/java/com/volmit/adapt/util/command/FService.java deleted file mode 100644 index 50526b997..000000000 --- a/src/main/java/com/volmit/adapt/util/command/FService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.volmit.adapt.util.command; - -public interface FService { - void start(); - - void stop(); -} diff --git a/src/main/java/com/volmit/adapt/util/command/Feedback.java b/src/main/java/com/volmit/adapt/util/command/Feedback.java deleted file mode 100644 index ead28264a..000000000 --- a/src/main/java/com/volmit/adapt/util/command/Feedback.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.volmit.adapt.util.command; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.VolmitSender; -import lombok.Builder; -import lombok.Data; -import lombok.Singular; -import lombok.experimental.Accessors; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.util.List; - -@Builder -@Data -@Accessors(chain = true, fluent = true) -public class Feedback { - @Singular - private List sounds; - @Singular - private List messages; - - public void send(CommandSender serverOrPlayer) { - if (serverOrPlayer instanceof Player p) { - for (SoundFeedback i : sounds) { - i.play(p); - } - } - - Component prefix = Component.text("[").color(NamedTextColor.GRAY) - .append(Component.text("Adapt").color(NamedTextColor.DARK_RED)) - .append(Component.text("] ")); - for (TextComponent i : messages) { - Adapt.audiences.sender(serverOrPlayer).sendMessage(Component.text() - .append(prefix) - .append(i) - .build()); - } - } - - public void send(VolmitSender sender) { - send(sender.getS()); - } -} diff --git a/src/main/java/com/volmit/adapt/util/command/SoundFeedback.java b/src/main/java/com/volmit/adapt/util/command/SoundFeedback.java deleted file mode 100644 index a3646248e..000000000 --- a/src/main/java/com/volmit/adapt/util/command/SoundFeedback.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.volmit.adapt.util.command; - -import lombok.Builder; -import lombok.Data; -import lombok.experimental.Accessors; -import org.bukkit.Sound; -import org.bukkit.entity.Player; - -@Builder -@Data -@Accessors(chain = true, fluent = true) -public class SoundFeedback { - private Sound sound; - @Builder.Default - private float volume = 1f; - @Builder.Default - private float pitch = 1f; - - public void play(Player p) { - p.playSound(p.getLocation(), sound, volume, pitch); - } -} diff --git a/src/main/java/com/volmit/adapt/util/config/ConfigDoc.java b/src/main/java/com/volmit/adapt/util/config/ConfigDoc.java deleted file mode 100644 index a5aff1c3a..000000000 --- a/src/main/java/com/volmit/adapt/util/config/ConfigDoc.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.volmit.adapt.util.config; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -@Retention(RUNTIME) -@Target(FIELD) -public @interface ConfigDoc { - String value(); - - String impact() default ""; -} diff --git a/src/main/java/com/volmit/adapt/util/config/ConfigDocumentation.java b/src/main/java/com/volmit/adapt/util/config/ConfigDocumentation.java deleted file mode 100644 index b61ab4523..000000000 --- a/src/main/java/com/volmit/adapt/util/config/ConfigDocumentation.java +++ /dev/null @@ -1,312 +0,0 @@ -package com.volmit.adapt.util.config; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -public final class ConfigDocumentation { - private static final Map SUMMARY_BY_KEY = Map.ofEntries( - Map.entry("enabled", "Enables or disables this feature."), - Map.entry("permanent", "Keeps this adaptation permanently active once learned."), - Map.entry("baseCost", "Base knowledge cost used when learning this adaptation."), - Map.entry("initialCost", "Knowledge cost required to purchase level 1."), - Map.entry("costFactor", "Scaling factor applied to higher adaptation levels."), - Map.entry("maxLevel", "Maximum level a player can reach for this adaptation."), - Map.entry("setInterval", "Tick interval used by this logic."), - Map.entry("minXp", "Minimum xp threshold required for this skill logic."), - Map.entry("language", "Primary language file used for localizations."), - Map.entry("fallbackLanguageDontChangeUnlessYouKnowWhatYouAreDoing", "Fallback language used when a localization key is missing."), - Map.entry("autoUpdateLanguage", "When enabled, language files are refreshed from plugin resources."), - Map.entry("autoUpdateCheck", "Checks for plugin updates during startup."), - Map.entry("metrics", "Sends anonymous bStats usage metrics."), - Map.entry("xpInCreative", "Allows skill xp gain while players are in creative or spectator."), - Map.entry("allowAdaptationsInCreative", "Allows using adaptations in creative mode."), - Map.entry("blacklistedWorlds", "World folder names where Adapt logic is disabled."), - Map.entry("experienceMaxLevel", "Global maximum level cap for skill progression."), - Map.entry("adaptActivatorBlock", "Block type players right-click to open the skills UI."), - Map.entry("adaptActivatorBlockName", "Display name used in UI text for the activator block."), - Map.entry("customModels", "Enables custom model lookups from the models config."), - Map.entry("advancements", "Enables Adapt advancement registration and grant flow."), - Map.entry("loginBonus", "Grants the configured login bonus message/rewards."), - Map.entry("welcomeMessage", "Shows the Adapt welcome message when players join."), - Map.entry("useSql", "Uses SQL as the player data backend."), - Map.entry("useRedis", "Enables Redis synchronization when SQL is active.") - ); - - private static final Map IMPACT_BY_KEY = Map.ofEntries( - Map.entry("enabled", "Set to false to disable behavior without uninstalling files."), - Map.entry("permanent", "True removes the normal learn/unlearn flow and treats it as always learned."), - Map.entry("baseCost", "Higher values make each level cost more knowledge."), - Map.entry("initialCost", "Higher values make unlocking the first level more expensive."), - Map.entry("costFactor", "Higher values increase level-to-level cost growth."), - Map.entry("maxLevel", "Higher values allow more levels; lower values cap progression sooner."), - Map.entry("setInterval", "Lower values run logic more often; higher values run it less often."), - Map.entry("minXp", "Higher values delay when this skill starts applying."), - Map.entry("metrics", "Set to false to opt out of bStats telemetry."), - Map.entry("xpInCreative", "Set to true if you want creative/spectator players to gain xp."), - Map.entry("allowAdaptationsInCreative", "Set to true to let creative players trigger adaptations."), - Map.entry("experienceMaxLevel", "Higher values raise the hard cap for skill leveling."), - Map.entry("customModels", "Set to false to disable all custom model assignments."), - Map.entry("advancements", "Set to false to disable advancement creation and toast notifications."), - Map.entry("useSql", "Switching this changes where player data is loaded/saved."), - Map.entry("useRedis", "Requires SQL support and Redis credentials to synchronize across servers.") - ); - private static final Set ALWAYS_VISIBLE_KEYS = Set.of( - "enabled", - "permanent", - "baseCost", - "initialCost", - "costFactor", - "maxLevel", - "minXp", - "showParticles", - "showSounds" - ); - - private ConfigDocumentation() { - } - - public static List buildFieldComments(String sourceTag, String path, Field field, Object value) { - List lines = new ArrayList<>(); - ConfigDoc annotation = field.getAnnotation(ConfigDoc.class); - String key = field.getName(); - String summary; - String impact; - - if (annotation != null) { - summary = annotation.value().strip(); - impact = annotation.impact().strip(); - if (isGenericSummary(summary)) { - summary = defaultSummary(sourceTag, path, field); - } - if (impact.isBlank() || isGenericImpact(impact)) { - impact = defaultImpact(field, value); - } - } else { - summary = SUMMARY_BY_KEY.getOrDefault(key, defaultSummary(sourceTag, path, field)); - impact = IMPACT_BY_KEY.getOrDefault(key, defaultImpact(field, value)); - } - - if (summary != null && !summary.isBlank()) { - lines.add(summary); - } - if (!impact.isBlank()) { - lines.add("Effect: " + impact); - } - return lines; - } - - public static boolean shouldExposeField(String sourceTag, String path, Field field, Object value) { - if (field == null) { - return false; - } - if (field.getAnnotation(ConfigAdvanced.class) != null) { - return false; - } - - String key = field.getName(); - if (ALWAYS_VISIBLE_KEYS.contains(key)) { - return true; - } - - String lowered = key.toLowerCase(Locale.ROOT); - Class type = field.getType(); - boolean isBoolean = type == boolean.class || type == Boolean.class; - - // Hide challenge reward tuning; these are rarely gameplay-critical knobs. - if (lowered.startsWith("challenge") && lowered.contains("reward")) { - return false; - } - - // Internal update cadence knobs are advanced and should stay out of default configs. - if (lowered.equals("setinterval") || lowered.equals("statintervalms")) { - return false; - } - - // Hide over-granular audiovisual tuning by default. - if (lowered.contains("pitch") || lowered.contains("volume")) { - return false; - } - if (lowered.contains("sound") && !isBoolean) { - return false; - } - if (lowered.contains("particlesize") || lowered.contains("particlecount") || lowered.contains("particleevery")) { - return false; - } - if (lowered.contains("xoffset") || lowered.contains("yoffset") || lowered.contains("zoffset")) { - return false; - } - - // Hide fallback/anti-edge tuning that is mostly diagnostic. - if (lowered.contains("fallback") || lowered.contains("variance") || lowered.contains("curveexponent")) { - return false; - } - - return true; - } - - public static List buildSectionComments(String sourceTag, String path) { - if (path == null || path.isBlank()) { - return List.of(); - } - - String leaf = path; - int idx = leaf.lastIndexOf('.'); - if (idx >= 0 && idx + 1 < leaf.length()) { - leaf = leaf.substring(idx + 1); - } - - String humanLeaf = humanize(leaf); - if (sourceTag != null && sourceTag.startsWith("skill:")) { - return List.of("Settings for the " + sourceTag.substring("skill:".length()) + " skill " + humanLeaf + " section."); - } - if (sourceTag != null && sourceTag.startsWith("adaptation:")) { - return List.of("Settings for the " + sourceTag.substring("adaptation:".length()) + " adaptation " + humanLeaf + " section."); - } - - return List.of("Settings for " + humanLeaf + "."); - } - - private static String defaultSummary(String sourceTag, String path, Field field) { - String key = field.getName(); - String lower = key.toLowerCase(Locale.ROOT); - String subject = subject(sourceTag, path); - if (lower.contains("cooldown")) { - return "Cooldown between " + subject + " activations."; - } - if (lower.contains("chance")) { - return "Chance for " + subject + " to trigger."; - } - if (lower.contains("xp")) { - return "XP gain tuning for " + subject + "."; - } - if (lower.contains("multiplier") || lower.contains("factor") || lower.contains("scalar")) { - return "Scaling applied to " + subject + "."; - } - if (lower.contains("duration") || lower.contains("ticks") || lower.contains("millis") || lower.endsWith("ms")) { - return "Duration or timing used by " + subject + "."; - } - if (lower.contains("radius") || lower.contains("range") || lower.contains("distance")) { - return "Distance/area limit used by " + subject + "."; - } - if (lower.startsWith("min") || lower.contains("threshold")) { - return "Minimum threshold required for " + subject + "."; - } - if (lower.startsWith("max") || lower.contains("cap")) { - return "Maximum cap applied to " + subject + "."; - } - - String label = humanize(field.getName()); - if (sourceTag != null && sourceTag.startsWith("skill:")) { - return "Controls " + label + " for the " + sourceTag.substring("skill:".length()) + " skill."; - } - if (sourceTag != null && sourceTag.startsWith("adaptation:")) { - return "Controls " + label + " for the " + sourceTag.substring("adaptation:".length()) + " adaptation."; - } - if (path != null && !path.isBlank()) { - return "Controls " + label + " in the " + path + " section."; - } - return "Controls " + label + "."; - } - - private static String defaultImpact(Field field, Object value) { - Class type = field.getType(); - String lower = field.getName().toLowerCase(Locale.ROOT); - if (type == boolean.class || type == Boolean.class) { - return "True enables this behavior and false disables it."; - } - if (lower.contains("chance")) { - return "Use values near 0.0-1.0; higher values trigger more often."; - } - if (lower.contains("cooldown")) { - return "Higher values increase time between activations; lower values allow more frequent triggers."; - } - if (lower.contains("xp")) { - return "Higher values grant more progression; lower values slow progression."; - } - if (lower.contains("multiplier") || lower.contains("factor") || lower.contains("scalar")) { - return "Higher values scale the effect more strongly; lower values scale it down."; - } - if (lower.contains("duration") || lower.contains("ticks") || lower.contains("millis") || lower.endsWith("ms")) { - return "Higher values make the effect last longer; lower values shorten it."; - } - if (lower.contains("radius") || lower.contains("range") || lower.contains("distance")) { - return "Higher values affect a wider area; lower values keep the effect tighter."; - } - if (lower.startsWith("min") || lower.contains("threshold")) { - return "Higher values make activation stricter; lower values make it easier to trigger."; - } - if (lower.startsWith("max") || lower.contains("cap")) { - return "Higher values raise the upper limit; lower values clamp the effect sooner."; - } - if (Number.class.isAssignableFrom(type) || type.isPrimitive() && type != boolean.class && type != char.class) { - return "Higher values increase intensity or limits; lower values reduce them."; - } - if (type.isEnum()) { - return "Changing this selects a different operating mode."; - } - if (type == String.class || type == char.class || type == Character.class) { - return "Changing this alters the identifier or text used by the feature."; - } - if (value instanceof List) { - return "Add or remove entries to control which values are included."; - } - if (value instanceof Map) { - return "Edit entries to control per-key overrides for this feature."; - } - return ""; - } - - private static boolean isGenericSummary(String summary) { - if (summary == null || summary.isBlank()) { - return true; - } - - String lower = summary.toLowerCase(Locale.ROOT).trim(); - return lower.startsWith("controls ") || lower.equals("no description provided"); - } - - private static boolean isGenericImpact(String impact) { - if (impact == null || impact.isBlank()) { - return true; - } - - String lower = impact.toLowerCase(Locale.ROOT); - return lower.contains("higher values usually increase intensity, limits, or frequency; lower values reduce it.") - || lower.contains("true enables this behavior and false disables it."); - } - - private static String subject(String sourceTag, String path) { - if (sourceTag != null && sourceTag.startsWith("skill:")) { - return "the " + sourceTag.substring("skill:".length()) + " skill"; - } - if (sourceTag != null && sourceTag.startsWith("adaptation:")) { - return "the " + sourceTag.substring("adaptation:".length()) + " adaptation"; - } - if (path != null && !path.isBlank()) { - return "the " + path + " section"; - } - return "this feature"; - } - - private static String humanize(String key) { - if (key == null || key.isBlank()) { - return "this setting"; - } - - String spaced = key - .replace('_', ' ') - .replace('-', ' ') - .replaceAll("([a-z])([A-Z])", "$1 $2") - .trim(); - if (spaced.isBlank()) { - return key; - } - - String lower = spaced.toLowerCase(Locale.ROOT); - return Character.toUpperCase(lower.charAt(0)) + lower.substring(1); - } -} diff --git a/src/main/java/com/volmit/adapt/util/config/ConfigFileSupport.java b/src/main/java/com/volmit/adapt/util/config/ConfigFileSupport.java deleted file mode 100644 index 1dc598201..000000000 --- a/src/main/java/com/volmit/adapt/util/config/ConfigFileSupport.java +++ /dev/null @@ -1,268 +0,0 @@ -package com.volmit.adapt.util.config; - -import com.google.gson.JsonElement; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.ConfigRewriteReporter; -import com.volmit.adapt.util.IO; -import com.volmit.adapt.util.Json; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Locale; - -public final class ConfigFileSupport { - private static final long MAX_CONFIG_BYTES_DEFAULT = 2L * 1024L * 1024L; - private static final long MAX_CONFIG_BYTES_SKILL_OR_ADAPTATION = 256L * 1024L; - - private ConfigFileSupport() { - } - - public static T load( - File canonicalFile, - File legacyFile, - Class type, - T fallback, - boolean overwriteOnReadFailure, - String sourceTag, - String createdMessage - ) throws IOException { - long maxConfigBytes = maxConfigBytesForSourceTag(sourceTag); - boolean canonicalizeExisting = shouldCanonicalizeExisting(sourceTag, overwriteOnReadFailure); - if (canonicalFile != null && canonicalFile.exists()) { - try { - if (canonicalFile.length() > maxConfigBytes) { - throw new IOException("Config file is too large (" + canonicalFile.length() + " bytes)"); - } - String raw = IO.readAll(canonicalFile); - T loaded = deserialize(raw, canonicalFile, type); - if (loaded == null) { - throw new IOException("Config parser returned null."); - } - - if (canonicalizeExisting) { - String canonical = serialize(loaded, canonicalFile, sourceTag); - if (!normalize(canonical).equals(normalize(raw))) { - ConfigRewriteReporter.reportRewrite(canonicalFile, sourceTag, raw, canonical); - IO.writeAll(canonicalFile, canonical); - } - } - deleteLegacyFileIfMigrated(canonicalFile, legacyFile, sourceTag); - return loaded; - } catch (Throwable e) { - if (overwriteOnReadFailure) { - ConfigRewriteReporter.reportFallbackRewrite(canonicalFile, sourceTag, reason("invalid config", e)); - IO.writeAll(canonicalFile, serialize(fallback, canonicalFile, sourceTag)); - return fallback; - } - - throw new IOException("Invalid config", e); - } - } - - if (legacyFile != null && legacyFile.exists()) { - try { - if (legacyFile.length() > maxConfigBytes) { - throw new IOException("Legacy config file is too large (" + legacyFile.length() + " bytes)"); - } - String raw = IO.readAll(legacyFile); - T loaded = deserialize(raw, legacyFile, type); - if (loaded == null) { - throw new IOException("Config parser returned null."); - } - - IO.writeAll(canonicalFile, serialize(loaded, canonicalFile, sourceTag)); - Adapt.info("Migrated legacy config [" + legacyPath(legacyFile) + "] -> [" + legacyPath(canonicalFile) + "]."); - deleteLegacyFileIfMigrated(canonicalFile, legacyFile, sourceTag); - return loaded; - } catch (Throwable e) { - if (overwriteOnReadFailure) { - ConfigRewriteReporter.reportFallbackRewrite(canonicalFile, sourceTag, reason("invalid legacy config", e)); - IO.writeAll(canonicalFile, serialize(fallback, canonicalFile, sourceTag)); - return fallback; - } - - throw new IOException("Invalid legacy config", e); - } - } - - IO.writeAll(canonicalFile, serialize(fallback, canonicalFile, sourceTag)); - if (createdMessage != null && !createdMessage.isBlank()) { - Adapt.info(createdMessage); - } - return fallback; - } - - public static String normalize(String text) { - if (text == null) { - return ""; - } - return text.replace("\r\n", "\n").stripTrailing(); - } - - public static File toTomlFile(File file) { - if (file == null) { - return null; - } - return replaceExtension(file, ".toml"); - } - - public static File toJsonFile(File file) { - if (file == null) { - return null; - } - return replaceExtension(file, ".json"); - } - - public static File replaceExtension(File file, String extension) { - String name = file.getName(); - int idx = name.lastIndexOf('.'); - String base = idx >= 0 ? name.substring(0, idx) : name; - return new File(file.getParentFile(), base + extension); - } - - public static boolean isTomlFile(File file) { - return file != null && file.getName().toLowerCase(Locale.ROOT).endsWith(".toml"); - } - - public static boolean isJsonFile(File file) { - if (file == null) { - return false; - } - String name = file.getName().toLowerCase(Locale.ROOT); - return name.endsWith(".json") || name.endsWith(".yml") || name.endsWith(".yaml"); - } - - public static boolean isSupportedConfigFile(File file) { - return isTomlFile(file) || isJsonFile(file); - } - - public static String configNameFromFileName(String fileName) { - if (fileName == null) { - return null; - } - String lower = fileName.toLowerCase(Locale.ROOT); - if (lower.endsWith(".toml")) { - return fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); - } - if (lower.endsWith(".json")) { - return fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); - } - if (lower.endsWith(".yml")) { - return fileName.substring(0, fileName.length() - 4).toLowerCase(Locale.ROOT); - } - if (lower.endsWith(".yaml")) { - return fileName.substring(0, fileName.length() - 5).toLowerCase(Locale.ROOT); - } - return null; - } - - public static JsonElement parseToJsonElement(String raw, File file) { - if (raw == null || raw.isBlank()) { - return null; - } - - try { - if (isTomlFile(file)) { - return TomlCodec.toJsonElement(raw); - } - - return Json.fromJson(raw, JsonElement.class); - } catch (Throwable ignored) { - } - - try { - return TomlCodec.toJsonElement(raw); - } catch (Throwable ignored) { - return null; - } - } - - public static String serializeJsonElementToToml(JsonElement element) { - return TomlCodec.toToml(element); - } - - public static boolean deleteLegacyFileIfMigrated(File canonicalFile, File legacyFile, String sourceTag) { - if (canonicalFile == null || legacyFile == null) { - return false; - } - if (!canonicalFile.exists() || !canonicalFile.isFile() || !legacyFile.exists() || !legacyFile.isFile()) { - return false; - } - if (canonicalFile.getAbsoluteFile().equals(legacyFile.getAbsoluteFile())) { - return false; - } - - try { - boolean deleted = Files.deleteIfExists(legacyFile.toPath()); - if (deleted) { - Adapt.verbose("Deleted migrated legacy config [" + legacyPath(legacyFile) + "] for [" + (sourceTag == null ? "config" : sourceTag) + "]."); - } - return deleted; - } catch (Throwable e) { - Adapt.warn("Failed to delete migrated legacy config [" + legacyPath(legacyFile) + "]: " + e.getMessage()); - return false; - } - } - - private static T deserialize(String raw, File sourceFile, Class type) throws IOException { - try { - if (isTomlFile(sourceFile)) { - return TomlCodec.fromToml(raw, type); - } - return Json.fromJson(raw, type); - } catch (Throwable e) { - throw new IOException("Failed to parse " + sourceFile.getName(), e); - } - } - - private static String serialize(Object loaded, File targetFile, String sourceTag) { - if (isTomlFile(targetFile)) { - return TomlCodec.toToml(loaded, sourceTag); - } - return Json.toJson(loaded, true); - } - - private static long maxConfigBytesForSourceTag(String sourceTag) { - if (sourceTag != null && (sourceTag.startsWith("skill:") || sourceTag.startsWith("adaptation:"))) { - return MAX_CONFIG_BYTES_SKILL_OR_ADAPTATION; - } - return MAX_CONFIG_BYTES_DEFAULT; - } - - private static boolean shouldCanonicalizeExisting(String sourceTag, boolean overwriteOnReadFailure) { - if (sourceTag == null) { - return true; - } - - // During initial startup of skill/adaptation content we prioritize fast parse/load. - // Canonical rewrites still occur via explicit canonicalization/hotload paths. - if (overwriteOnReadFailure && (sourceTag.startsWith("skill:") || sourceTag.startsWith("adaptation:"))) { - return false; - } - - return true; - } - - private static String reason(String prefix, Throwable error) { - if (error == null || error.getMessage() == null || error.getMessage().isBlank()) { - return prefix; - } - return prefix + ": " + error.getMessage(); - } - - private static String legacyPath(File file) { - if (file == null) { - return ""; - } - try { - File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); - if (dataFolder == null) { - return file.getPath(); - } - return dataFolder.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '/'); - } catch (Throwable ignored) { - return file.getPath(); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/config/ConfigMigrationManager.java b/src/main/java/com/volmit/adapt/util/config/ConfigMigrationManager.java deleted file mode 100644 index e7419377b..000000000 --- a/src/main/java/com/volmit/adapt/util/config/ConfigMigrationManager.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.volmit.adapt.util.config; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.IO; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -public final class ConfigMigrationManager { - private static final Object LOCK = new Object(); - private static final DateTimeFormatter TS = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"); - private static volatile boolean backupAttempted = false; - - private ConfigMigrationManager() { - } - - public static void backupLegacyJsonConfigsOnce() { - synchronized (LOCK) { - if (backupAttempted) { - return; - } - backupAttempted = true; - - File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); - if (dataFolder == null || !dataFolder.exists()) { - return; - } - - File marker = Adapt.instance.getDataFile("adapt", "migrations", ".legacy-json-backed-up"); - if (marker.exists()) { - return; - } - - List legacyJson = collectLegacyJsonFiles(dataFolder); - if (legacyJson.isEmpty()) { - return; - } - - boolean migrationNeeded = legacyJson.stream() - .map(ConfigFileSupport::toTomlFile) - .anyMatch(file -> file != null && !file.exists()); - if (!migrationNeeded) { - return; - } - - File backupDir = Adapt.instance.getDataFolder("adapt", "migrations", "backups"); - String timestamp = LocalDateTime.now().format(TS); - File zip = new File(backupDir, timestamp + "-pre-toml-migration.zip"); - - try { - zipLegacyFiles(dataFolder, legacyJson, zip); - IO.writeAll(marker, "backup=" + zip.getName() + "\ncreated=" + timestamp + "\n"); - Adapt.warn("Created legacy config backup before TOML migration: " + zip.getPath()); - } catch (Throwable e) { - Adapt.warn("Failed to create legacy config backup zip: " + e.getMessage()); - } - } - } - - public static int deleteMigratedLegacyJsonFiles() { - synchronized (LOCK) { - File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); - if (dataFolder == null || !dataFolder.exists()) { - return 0; - } - - int deleted = 0; - for (File legacyJson : collectLegacyJsonFiles(dataFolder)) { - if (legacyJson == null || !legacyJson.exists() || !legacyJson.isFile()) { - continue; - } - - File canonicalToml = ConfigFileSupport.toTomlFile(legacyJson); - if (canonicalToml == null || !canonicalToml.exists() || !canonicalToml.isFile()) { - continue; - } - - try { - if (Files.deleteIfExists(legacyJson.toPath())) { - deleted++; - } - } catch (Throwable e) { - Adapt.warn("Failed to delete migrated legacy config [" + legacyPath(dataFolder, legacyJson) + "]: " + e.getMessage()); - } - } - - return deleted; - } - } - - public static boolean hasLegacySkillOrAdaptationJsonFiles() { - synchronized (LOCK) { - File dataFolder = Adapt.instance == null ? null : Adapt.instance.getDataFolder(); - if (dataFolder == null || !dataFolder.exists()) { - return false; - } - - File adaptRoot = new File(dataFolder, "adapt"); - return hasAnyJson(new File(adaptRoot, "skills")) || hasAnyJson(new File(adaptRoot, "adaptations")); - } - } - - private static List collectLegacyJsonFiles(File dataFolder) { - List files = new ArrayList<>(); - addScopedJsonFiles(new File(dataFolder, "adapt"), files); - addScopedJsonFiles(new File(dataFolder, "languages"), files); - return files; - } - - private static void addScopedJsonFiles(File root, List out) { - if (root == null || !root.exists() || !root.isDirectory()) { - return; - } - - ArrayDeque queue = new ArrayDeque<>(); - queue.add(root); - while (!queue.isEmpty()) { - File next = queue.removeFirst(); - File[] children = next.listFiles(); - if (children == null || children.length == 0) { - continue; - } - - for (File child : children) { - if (child == null) { - continue; - } - if (child.isDirectory()) { - queue.add(child); - continue; - } - - if (child.getName().toLowerCase(Locale.ROOT).endsWith(".json")) { - out.add(child); - } - } - } - } - - private static boolean hasAnyJson(File root) { - if (root == null || !root.exists() || !root.isDirectory()) { - return false; - } - - ArrayDeque queue = new ArrayDeque<>(); - queue.add(root); - while (!queue.isEmpty()) { - File next = queue.removeFirst(); - File[] children = next.listFiles(); - if (children == null || children.length == 0) { - continue; - } - - for (File child : children) { - if (child == null) { - continue; - } - if (child.isDirectory()) { - queue.add(child); - continue; - } - if (child.getName().toLowerCase(Locale.ROOT).endsWith(".json")) { - return true; - } - } - } - - return false; - } - - private static void zipLegacyFiles(File dataFolder, List files, File zipFile) throws Exception { - zipFile.getParentFile().mkdirs(); - try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile), StandardCharsets.UTF_8)) { - byte[] buffer = new byte[8192]; - for (File file : files) { - if (file == null || !file.exists() || !file.isFile()) { - continue; - } - - String relative = dataFolder.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '/'); - out.putNextEntry(new ZipEntry(relative)); - try (FileInputStream in = new FileInputStream(file)) { - int read; - while ((read = in.read(buffer)) != -1) { - out.write(buffer, 0, read); - } - } - out.closeEntry(); - } - } - } - - private static String legacyPath(File dataFolder, File file) { - if (file == null) { - return ""; - } - try { - if (dataFolder != null && dataFolder.exists()) { - return dataFolder.toPath().relativize(file.toPath()).toString().replace(File.separatorChar, '/'); - } - } catch (Throwable ignored) { - } - return file.getPath(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/config/TomlCodec.java b/src/main/java/com/volmit/adapt/util/config/TomlCodec.java deleted file mode 100644 index 555d232be..000000000 --- a/src/main/java/com/volmit/adapt/util/config/TomlCodec.java +++ /dev/null @@ -1,472 +0,0 @@ -package com.volmit.adapt.util.config; - -import com.google.gson.JsonElement; -import com.volmit.adapt.util.Json; -import com.moandjiezana.toml.Toml; - -import java.io.IOException; -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public final class TomlCodec { - private TomlCodec() { - } - - public static T fromToml(String raw, Class type) throws IOException { - try { - Object parsed = parseToml(raw); - String json = Json.toJson(parsed, false); - return Json.fromJson(json, type); - } catch (Throwable e) { - throw new IOException("Invalid toml", e); - } - } - - public static JsonElement toJsonElement(String raw) throws IOException { - try { - Object parsed = parseToml(raw); - String json = Json.toJson(parsed, false); - return Json.fromJson(json, JsonElement.class); - } catch (Throwable e) { - throw new IOException("Invalid toml", e); - } - } - - public static String toToml(Object object, String sourceTag) { - return new ReflectiveTomlWriter(sourceTag).write(object); - } - - public static String toToml(JsonElement element) { - Object data = Json.NORMAL.fromJson(element, Object.class); - return new GenericTomlWriter().write(data); - } - - private static Object parseToml(String raw) { - Toml toml = new Toml().read(raw == null ? "" : raw); - Map map = toml.toMap(); - if (map == null) { - return new LinkedHashMap(); - } - return map; - } - - private static final class ReflectiveTomlWriter { - private final StringBuilder out = new StringBuilder(); - private final String sourceTag; - - private ReflectiveTomlWriter(String sourceTag) { - this.sourceTag = sourceTag == null ? "config" : sourceTag; - } - - private String write(Object root) { - if (root == null) { - return ""; - } - - out.append("# Adapt configuration - ").append(sourceTag).append('\n'); - out.append("# This file is canonicalized on load; comments and new keys may update automatically.\n"); - ConfigDescription desc = root.getClass().getAnnotation(ConfigDescription.class); - if (desc != null && !desc.value().isBlank()) { - out.append("#\n"); - out.append("# ").append(desc.value().strip()).append('\n'); - } - out.append('\n'); - writePojoSection("", root); - return normalize(out.toString()); - } - - private void writePojoSection(String path, Object sectionObject) { - if (sectionObject == null) { - return; - } - - List fields = getSerializableFields(sectionObject.getClass()); - List deferred = new ArrayList<>(); - - for (Field field : fields) { - Object value = getFieldValue(field, sectionObject); - if (value == null) { - continue; - } - if (!ConfigDocumentation.shouldExposeField(sourceTag, path, field, value)) { - continue; - } - - if (isInlineValue(value)) { - writeFieldComments(path, field, value); - out.append(formatKey(field.getName())).append(" = ").append(formatInlineValue(value)).append('\n'); - } else { - deferred.add(field); - } - } - - for (Field field : deferred) { - Object value = getFieldValue(field, sectionObject); - if (value == null) { - continue; - } - if (!ConfigDocumentation.shouldExposeField(sourceTag, path, field, value)) { - continue; - } - - String childPath = joinPath(path, field.getName()); - if (value instanceof Map map) { - writeMapSection(childPath, map, field); - continue; - } - - writeSectionHeader(childPath, ConfigDocumentation.buildSectionComments(sourceTag, childPath)); - writePojoSection(childPath, value); - } - } - - private void writeMapSection(String sectionPath, Map map, Field sourceField) { - writeSectionHeader(sectionPath, ConfigDocumentation.buildFieldComments(sourceTag, sectionPath, sourceField, map)); - if (map.isEmpty()) { - return; - } - - List> deferred = new ArrayList<>(); - for (Map.Entry entry : map.entrySet()) { - if (entry == null || entry.getKey() == null) { - continue; - } - - Object value = entry.getValue(); - if (value == null) { - continue; - } - - if (isInlineValue(value)) { - out.append(formatKey(String.valueOf(entry.getKey()))) - .append(" = ") - .append(formatInlineValue(value)) - .append('\n'); - } else { - deferred.add(entry); - } - } - - for (Map.Entry entry : deferred) { - Object value = entry.getValue(); - if (value == null || entry.getKey() == null) { - continue; - } - - String childPath = joinPath(sectionPath, String.valueOf(entry.getKey())); - if (value instanceof Map nested) { - writeSectionHeader(childPath, List.of()); - writeMapBody(childPath, nested); - } else { - writeSectionHeader(childPath, List.of()); - writePojoSection(childPath, value); - } - } - } - - private void writeMapBody(String sectionPath, Map map) { - List> deferred = new ArrayList<>(); - for (Map.Entry entry : map.entrySet()) { - if (entry == null || entry.getKey() == null || entry.getValue() == null) { - continue; - } - if (isInlineValue(entry.getValue())) { - out.append(formatKey(String.valueOf(entry.getKey()))) - .append(" = ") - .append(formatInlineValue(entry.getValue())) - .append('\n'); - } else { - deferred.add(entry); - } - } - - for (Map.Entry entry : deferred) { - String childPath = joinPath(sectionPath, String.valueOf(entry.getKey())); - Object value = entry.getValue(); - if (value instanceof Map nested) { - writeSectionHeader(childPath, List.of()); - writeMapBody(childPath, nested); - } else { - writeSectionHeader(childPath, List.of()); - writePojoSection(childPath, value); - } - } - } - - private void writeFieldComments(String path, Field field, Object value) { - List comments = ConfigDocumentation.buildFieldComments(sourceTag, path, field, value); - for (String comment : comments) { - if (comment == null || comment.isBlank()) { - continue; - } - out.append("# ").append(comment.strip()).append('\n'); - } - } - - private void writeSectionHeader(String path, List comments) { - if (path == null || path.isBlank()) { - return; - } - - if (!out.isEmpty() && out.charAt(out.length() - 1) != '\n') { - out.append('\n'); - } - if (!out.isEmpty()) { - out.append('\n'); - } - - for (String comment : comments) { - if (comment == null || comment.isBlank()) { - continue; - } - out.append("# ").append(comment.strip()).append('\n'); - } - out.append('[').append(renderPath(path)).append(']').append('\n'); - } - } - - private static final class GenericTomlWriter { - private final StringBuilder out = new StringBuilder(); - private String lastTopLevelSection; - - private String write(Object root) { - if (root instanceof Map map) { - writeMapSection("", map, 0); - return normalize(out.toString()); - } - - out.append("value = ").append(formatInlineValue(root)).append('\n'); - return normalize(out.toString()); - } - - private void writeMapSection(String path, Map map, int depth) { - if (!path.isBlank()) { - writeSectionHeader(path, depth); - } - - List> deferred = new ArrayList<>(); - String valueIndent = " ".repeat(Math.max(0, depth)); - for (Map.Entry entry : map.entrySet()) { - if (entry == null || entry.getKey() == null || entry.getValue() == null) { - continue; - } - - Object value = entry.getValue(); - if (isInlineValue(value)) { - out.append(valueIndent) - .append(formatKey(String.valueOf(entry.getKey()))) - .append(" = ") - .append(formatInlineValue(value)) - .append('\n'); - } else { - deferred.add(entry); - } - } - - for (Map.Entry entry : deferred) { - String childPath = joinPath(path, String.valueOf(entry.getKey())); - Object value = entry.getValue(); - if (value instanceof Map nested) { - writeMapSection(childPath, nested, depth + 1); - } else { - Map wrapper = new LinkedHashMap<>(); - wrapper.put("value", value); - writeMapSection(childPath, wrapper, depth + 1); - } - } - } - - private void writeSectionHeader(String path, int depth) { - String topLevel = topLevelSegment(path); - if (depth == 1 && (lastTopLevelSection == null || !lastTopLevelSection.equals(topLevel))) { - if (!out.isEmpty()) { - out.append('\n'); - } - out.append("# ").append(topLevel).append('\n'); - lastTopLevelSection = topLevel; - } else if (!out.isEmpty()) { - out.append('\n'); - } - - out.append('[').append(renderPath(path)).append(']').append('\n'); - } - - private String topLevelSegment(String path) { - if (path == null || path.isBlank()) { - return ""; - } - - int dot = path.indexOf('.'); - if (dot == -1) { - return path; - } - return path.substring(0, dot); - } - } - - private static List getSerializableFields(Class type) { - List out = new ArrayList<>(); - collectFields(type, out); - return out; - } - - private static void collectFields(Class type, List out) { - if (type == null || type == Object.class) { - return; - } - - collectFields(type.getSuperclass(), out); - for (Field field : type.getDeclaredFields()) { - if (field.isSynthetic()) { - continue; - } - int modifiers = field.getModifiers(); - if (Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) { - continue; - } - field.setAccessible(true); - out.add(field); - } - } - - private static Object getFieldValue(Field field, Object object) { - try { - return field.get(object); - } catch (Throwable ignored) { - return null; - } - } - - private static boolean isInlineValue(Object value) { - if (value == null) { - return true; - } - - if (value instanceof String - || value instanceof Number - || value instanceof Boolean - || value instanceof Character - || value instanceof Enum) { - return true; - } - - if (value.getClass().isArray()) { - int length = Array.getLength(value); - for (int i = 0; i < length; i++) { - Object v = Array.get(value, i); - if (!isInlineValue(v) || v instanceof Map || v instanceof Collection) { - return false; - } - } - return true; - } - - if (value instanceof Collection collection) { - for (Object item : collection) { - if (!isInlineValue(item) || item instanceof Map || item instanceof Collection) { - return false; - } - } - return true; - } - - return false; - } - - private static String formatInlineValue(Object value) { - if (value == null) { - return "\"\""; - } - - if (value instanceof String string) { - return '"' + escape(string) + '"'; - } - if (value instanceof Character c) { - return '"' + escape(String.valueOf(c)) + '"'; - } - if (value instanceof Enum enumValue) { - return '"' + escape(enumValue.name()) + '"'; - } - if (value instanceof Number || value instanceof Boolean) { - return String.valueOf(value); - } - if (value.getClass().isArray()) { - int length = Array.getLength(value); - List parts = new ArrayList<>(length); - for (int i = 0; i < length; i++) { - parts.add(formatInlineValue(Array.get(value, i))); - } - return "[" + String.join(", ", parts) + "]"; - } - if (value instanceof Collection collection) { - List parts = new ArrayList<>(collection.size()); - for (Object item : collection) { - parts.add(formatInlineValue(item)); - } - return "[" + String.join(", ", parts) + "]"; - } - - return '"' + escape(String.valueOf(value)) + '"'; - } - - private static String renderPath(String path) { - if (path == null || path.isBlank()) { - return ""; - } - - String[] parts = path.split("\\."); - List rendered = new ArrayList<>(parts.length); - for (String part : parts) { - rendered.add(formatKey(part)); - } - return String.join(".", rendered); - } - - private static String formatKey(String key) { - if (key == null || key.isBlank()) { - return "\"\""; - } - - if (key.matches("[A-Za-z0-9_-]+")) { - return key; - } - return '"' + escape(key) + '"'; - } - - private static String joinPath(String a, String b) { - if (a == null || a.isBlank()) { - return b; - } - if (b == null || b.isBlank()) { - return a; - } - return a + "." + b; - } - - private static String escape(String input) { - return input - .replace("\\", "\\\\") - .replace("\"", "\\\"") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t"); - } - - private static String normalize(String raw) { - if (raw == null) { - return ""; - } - - String normalized = raw.replace("\r\n", "\n").stripTrailing(); - if (!normalized.isEmpty()) { - normalized += "\n"; - } - return normalized; - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/B.java b/src/main/java/com/volmit/adapt/util/data/B.java deleted file mode 100644 index 866922e5f..000000000 --- a/src/main/java/com/volmit/adapt/util/data/B.java +++ /dev/null @@ -1,661 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - - -import art.arcane.chrono.ChronoLatch; -//import com.volmit.react.React; -//import com.volmit.react.util.collection.KList; -//import com.volmit.react.util.collection.KMap; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.reflect.registries.Materials; -import it.unimi.dsi.fastutil.ints.*; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Waterlogged; -import org.bukkit.block.data.type.Leaves; -import org.bukkit.block.data.type.PointedDripstone; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import static org.bukkit.Material.*; - -public class B { - private static final KMap custom = new KMap<>(); - - private static final Material AIR_MATERIAL = Material.AIR; - private static final BlockData AIR = AIR_MATERIAL.createBlockData(); - private static final IntSet foliageCache = buildFoliageCache(); - private static final IntSet deepslateCache = buildDeepslateCache(); - private static final Int2IntMap normal2DeepslateCache = buildNormal2DeepslateCache(); - private static final Int2IntMap deepslate2NormalCache = buildDeepslate2NormalCache(); - private static final IntSet decorantCache = buildDecorantCache(); - private static final IntSet storageCache = buildStorageCache(); - private static final IntSet storageChestCache = buildStorageChestCache(); - private static final IntSet litCache = buildLitCache(); - private static final ChronoLatch clw = new ChronoLatch(1000); - - private static IntSet buildFoliageCache() { - IntSet b = new IntOpenHashSet(); - Arrays.stream(new Material[]{ - POPPY, - DANDELION, - CORNFLOWER, - SWEET_BERRY_BUSH, - CRIMSON_ROOTS, - WARPED_ROOTS, - NETHER_SPROUTS, - ALLIUM, - AZURE_BLUET, - BLUE_ORCHID, - OXEYE_DAISY, - LILY_OF_THE_VALLEY, - WITHER_ROSE, - DARK_OAK_SAPLING, - ACACIA_SAPLING, - JUNGLE_SAPLING, - BIRCH_SAPLING, - SPRUCE_SAPLING, - Materials.CHERRY_SAPLING, - Materials.PALE_OAK_SAPLING, - OAK_SAPLING, - ORANGE_TULIP, - PINK_TULIP, - RED_TULIP, - WHITE_TULIP, - FERN, - LARGE_FERN, - Materials.GRASS, - TALL_GRASS - }).forEach((i) -> b.add(i.ordinal())); - - return IntSets.unmodifiable(b); - } - - private static IntSet buildDeepslateCache() { - IntSet b = new IntOpenHashSet(); - Arrays.stream(new Material[]{ - DEEPSLATE, - DEEPSLATE_BRICKS, - DEEPSLATE_BRICK_SLAB, - DEEPSLATE_BRICK_STAIRS, - DEEPSLATE_BRICK_WALL, - DEEPSLATE_TILE_SLAB, - DEEPSLATE_TILES, - DEEPSLATE_TILE_STAIRS, - DEEPSLATE_TILE_WALL, - CRACKED_DEEPSLATE_TILES - }).forEach((i) -> b.add(i.ordinal())); - - return IntSets.unmodifiable(b); - } - - private static Int2IntMap buildNormal2DeepslateCache() { - Int2IntMap b = new Int2IntOpenHashMap(); - - b.put(COAL_ORE.ordinal(), DEEPSLATE_COAL_ORE.ordinal()); - b.put(EMERALD_ORE.ordinal(), DEEPSLATE_EMERALD_ORE.ordinal()); - b.put(DIAMOND_ORE.ordinal(), DEEPSLATE_DIAMOND_ORE.ordinal()); - b.put(COPPER_ORE.ordinal(), DEEPSLATE_COPPER_ORE.ordinal()); - b.put(GOLD_ORE.ordinal(), DEEPSLATE_GOLD_ORE.ordinal()); - b.put(IRON_ORE.ordinal(), DEEPSLATE_IRON_ORE.ordinal()); - b.put(LAPIS_ORE.ordinal(), DEEPSLATE_LAPIS_ORE.ordinal()); - b.put(REDSTONE_ORE.ordinal(), DEEPSLATE_REDSTONE_ORE.ordinal()); - - return b; - } - - private static Int2IntMap buildDeepslate2NormalCache() { - Int2IntMap b = new Int2IntOpenHashMap(); - - b.put(DEEPSLATE_COAL_ORE.ordinal(), COAL_ORE.ordinal()); - b.put(DEEPSLATE_EMERALD_ORE.ordinal(), EMERALD_ORE.ordinal()); - b.put(DEEPSLATE_DIAMOND_ORE.ordinal(), DIAMOND_ORE.ordinal()); - b.put(DEEPSLATE_COPPER_ORE.ordinal(), COPPER_ORE.ordinal()); - b.put(DEEPSLATE_GOLD_ORE.ordinal(), GOLD_ORE.ordinal()); - b.put(DEEPSLATE_IRON_ORE.ordinal(), IRON_ORE.ordinal()); - b.put(DEEPSLATE_LAPIS_ORE.ordinal(), LAPIS_ORE.ordinal()); - b.put(DEEPSLATE_REDSTONE_ORE.ordinal(), REDSTONE_ORE.ordinal()); - - return b; - } - - private static IntSet buildDecorantCache() { - IntSet b = new IntOpenHashSet(); - Arrays.stream(new Material[]{ - Materials.GRASS, - TALL_GRASS, - FERN, - LARGE_FERN, - CORNFLOWER, - SUNFLOWER, - CHORUS_FLOWER, - POPPY, - DANDELION, - OXEYE_DAISY, - ORANGE_TULIP, - PINK_TULIP, - RED_TULIP, - WHITE_TULIP, - LILAC, - DEAD_BUSH, - SWEET_BERRY_BUSH, - ROSE_BUSH, - WITHER_ROSE, - ALLIUM, - BLUE_ORCHID, - LILY_OF_THE_VALLEY, - CRIMSON_FUNGUS, - WARPED_FUNGUS, - RED_MUSHROOM, - BROWN_MUSHROOM, - CRIMSON_ROOTS, - AZURE_BLUET, - WEEPING_VINES, - WEEPING_VINES_PLANT, - WARPED_ROOTS, - NETHER_SPROUTS, - TWISTING_VINES, - TWISTING_VINES_PLANT, - SUGAR_CANE, - WHEAT, - POTATOES, - CARROTS, - BEETROOTS, - NETHER_WART, - SEA_PICKLE, - SEAGRASS, - ACACIA_BUTTON, - BIRCH_BUTTON, - CRIMSON_BUTTON, - DARK_OAK_BUTTON, - JUNGLE_BUTTON, - OAK_BUTTON, - POLISHED_BLACKSTONE_BUTTON, - SPRUCE_BUTTON, - STONE_BUTTON, - WARPED_BUTTON, - TORCH, - SOUL_TORCH, - GLOW_LICHEN, - VINE, - SCULK_VEIN - }).forEach((i) -> b.add(i.ordinal())); - b.addAll(foliageCache); - - return IntSets.unmodifiable(b); - } - - private static IntSet buildLitCache() { - IntSet b = new IntOpenHashSet(); - Arrays.stream(new Material[]{ - GLOWSTONE, - AMETHYST_CLUSTER, - SMALL_AMETHYST_BUD, - MEDIUM_AMETHYST_BUD, - LARGE_AMETHYST_BUD, - END_ROD, - SOUL_SAND, - TORCH, - REDSTONE_TORCH, - SOUL_TORCH, - REDSTONE_WALL_TORCH, - WALL_TORCH, - SOUL_WALL_TORCH, - LANTERN, - CANDLE, - JACK_O_LANTERN, - REDSTONE_LAMP, - MAGMA_BLOCK, - LIGHT, - SHROOMLIGHT, - SEA_LANTERN, - SOUL_LANTERN, - FIRE, - SOUL_FIRE, - SEA_PICKLE, - BREWING_STAND, - REDSTONE_ORE, - }).forEach((i) -> b.add(i.ordinal())); - - return IntSets.unmodifiable(b); - } - - private static IntSet buildStorageCache() { - IntSet b = new IntOpenHashSet(); - Arrays.stream(new Material[]{ - CHEST, - SMOKER, - TRAPPED_CHEST, - SHULKER_BOX, - WHITE_SHULKER_BOX, - ORANGE_SHULKER_BOX, - MAGENTA_SHULKER_BOX, - LIGHT_BLUE_SHULKER_BOX, - YELLOW_SHULKER_BOX, - LIME_SHULKER_BOX, - PINK_SHULKER_BOX, - GRAY_SHULKER_BOX, - LIGHT_GRAY_SHULKER_BOX, - CYAN_SHULKER_BOX, - PURPLE_SHULKER_BOX, - BLUE_SHULKER_BOX, - BROWN_SHULKER_BOX, - GREEN_SHULKER_BOX, - RED_SHULKER_BOX, - BLACK_SHULKER_BOX, - BARREL, - DISPENSER, - DROPPER, - HOPPER, - FURNACE, - BLAST_FURNACE - }).forEach((i) -> b.add(i.ordinal())); - - return IntSets.unmodifiable(b); - } - - public static BlockData toDeepSlateOre(BlockData block, BlockData ore) { - int key = ore.getMaterial().ordinal(); - - if (isDeepSlate(block)) { - if (normal2DeepslateCache.containsKey(key)) { - return Material.values()[normal2DeepslateCache.get(key)].createBlockData(); - } - } else { - if (deepslate2NormalCache.containsKey(key)) { - return Material.values()[deepslate2NormalCache.get(key)].createBlockData(); - } - } - - return ore; - } - - public static boolean isDeepSlate(BlockData blockData) { - return deepslateCache.contains(blockData.getMaterial().ordinal()); - } - - public static boolean isOre(BlockData blockData) { - return blockData.getMaterial().name().endsWith("_ORE"); - } - - private static IntSet buildStorageChestCache() { - IntSet b = new IntOpenHashSet(storageCache); - b.remove(SMOKER.ordinal()); - b.remove(FURNACE.ordinal()); - b.remove(BLAST_FURNACE.ordinal()); - - return IntSets.unmodifiable(b); - } - - public static boolean canPlaceOnto(Material mat, Material onto) { - if ((onto.equals(CRIMSON_NYLIUM) || onto.equals(WARPED_NYLIUM)) && - (mat.equals(CRIMSON_FUNGUS) || mat.equals(CRIMSON_ROOTS) || mat.equals(WARPED_FUNGUS) || mat.equals(WARPED_ROOTS))) { - return true; - } - - if (isFoliage(mat)) { - if (!isFoliagePlantable(onto)) { - return false; - } - } - - if (onto.equals(Material.AIR) || - onto.equals(B.getMaterial("CAVE_AIR")) - || onto.equals(B.getMaterial("VOID_AIR"))) { - return false; - } - - if (onto.equals(Material.GRASS_BLOCK) && mat.equals(Material.DEAD_BUSH)) { - return false; - } - - if (onto.equals(Material.DIRT_PATH)) { - if (!mat.isSolid()) { - return false; - } - } - - if (onto.equals(Material.ACACIA_LEAVES) - || onto.equals(Material.BIRCH_LEAVES) - || onto.equals(Material.DARK_OAK_LEAVES) - || onto.equals(Material.JUNGLE_LEAVES) - || onto.equals(Material.OAK_LEAVES) - || onto.equals(Material.SPRUCE_LEAVES)) { - return mat.isSolid(); - } - - return true; - } - - public static boolean isFoliagePlantable(BlockData d) { - return d.getMaterial().equals(Material.GRASS_BLOCK) - || d.getMaterial().equals(Material.ROOTED_DIRT) - || d.getMaterial().equals(Material.DIRT) - || d.getMaterial().equals(Material.COARSE_DIRT) - || d.getMaterial().equals(Material.PODZOL); - } - - public static boolean isFoliagePlantable(Material d) { - return d.equals(Material.GRASS_BLOCK) - || d.equals(Material.DIRT) - || d.equals(TALL_GRASS) - || d.equals(TALL_SEAGRASS) - || d.equals(LARGE_FERN) - || d.equals(SUNFLOWER) - || d.equals(PEONY) - || d.equals(LILAC) - || d.equals(ROSE_BUSH) - || d.equals(Material.ROOTED_DIRT) - || d.equals(Material.COARSE_DIRT) - || d.equals(Material.PODZOL); - } - - public static boolean isWater(BlockData b) { - return b.getMaterial().equals(Material.WATER); - } - - public static BlockData getAir() { - return AIR; - } - - public static Material getMaterialOrNull(String bdx) { - try { - return Material.valueOf(bdx.trim().toUpperCase()); - } catch (Throwable e) { - e.printStackTrace(); - if (clw.flip()) { - Adapt.warn("Unknown Material: " + bdx); - } - return null; - } - } - - public static Material getMaterial(String bdx) { - Material m = getMaterialOrNull(bdx); - - if (m == null) { - return AIR_MATERIAL; - } - - return m; - } - - public static boolean isSolid(BlockData mat) { - if (mat == null) - return false; - return mat.getMaterial().isSolid(); - } - - public static BlockData getOrNull(String bdxf) { - try { - String bd = bdxf.trim(); - - if (!custom.isEmpty() && custom.containsKey(bd)) { - return custom.get(bd); - } - - if (bd.startsWith("minecraft:cauldron[level=")) { - bd = bd.replaceAll("\\Q:cauldron[\\E", ":water_cauldron["); - } - - if (bd.equals("minecraft:grass_path")) { - return DIRT_PATH.createBlockData(); - } - - BlockData bdx = parseBlockData(bd); - - if (bdx == null) { - return AIR; - } - - return bdx; - } catch (Throwable e) { - e.printStackTrace(); - } - - return null; - } - - public static BlockData get(String bdxf) { - BlockData bd = getOrNull(bdxf); - - if (bd != null) { - return bd; - } - - return AIR; - } - - private static synchronized BlockData createBlockData(String s) { - try { - return Bukkit.createBlockData(s); - } catch (IllegalArgumentException e) { - if (s.contains("[")) { - return createBlockData(s.split("\\Q[\\E")[0]); - } - } - - return null; - } - - private static BlockData parseBlockData(String ix) { - try { - BlockData bx = null; - - if (bx == null) { - try { - bx = createBlockData(ix.toLowerCase()); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - if (bx == null) { - try { - bx = createBlockData("minecraft:" + ix.toLowerCase()); - } catch (Throwable e) { - - } - } - - if (bx == null) { - try { - bx = Material.valueOf(ix.toUpperCase()).createBlockData(); - } catch (Throwable e) { - - } - } - - if (bx == null) { - return null; - } - - if (bx instanceof Leaves) { - ((Leaves) bx).setPersistent(false); - } - - return bx; - } catch (Throwable e) { - String block = ix.contains(":") ? ix.split(":")[1].toLowerCase() : ix.toLowerCase(); - String state = block.contains("[") ? block.split("\\Q[\\E")[1].split("\\Q]\\E")[0] : ""; - Map stateMap = new HashMap<>(); - if (!state.equals("")) { - Arrays.stream(state.split(",")).forEach(s -> stateMap.put(s.split("=")[0], s.split("=")[1])); - } - block = block.split("\\Q[\\E")[0]; - - switch (block) { - case "cauldron" -> block = "water_cauldron"; - case "grass_path" -> block = "dirt_path"; - case "concrete" -> block = "white_concrete"; - case "wool" -> block = "white_wool"; - case "beetroots" -> { - if (stateMap.containsKey("age")) { - String updated = stateMap.get("age"); - switch (updated) { - case "7" -> updated = "3"; - case "3", "4", "5" -> updated = "2"; - case "1", "2" -> updated = "1"; - } - stateMap.put("age", updated); - } - } - } - - Map newStates = new HashMap<>(); - for (String key : stateMap.keySet()) { //Iterate through every state and check if its valid - try { - String newState = block + "[" + key + "=" + stateMap.get(key) + "]"; - createBlockData(newState); - newStates.put(key, stateMap.get(key)); - - } catch (IllegalArgumentException ignored) { - } - } - - //Combine all the "good" states again - state = newStates.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(",")); - if (!state.equals("")) state = "[" + state + "]"; - String newBlock = block + state; - Adapt.debug("Converting " + ix + " to " + newBlock); - - try { - return createBlockData(newBlock); - } catch (Throwable e1) { - e1.printStackTrace(); - } - - return null; - } - } - - public static boolean isStorage(BlockData mat) { - return storageCache.contains(mat.getMaterial().ordinal()); - } - - public static boolean isStorageChest(BlockData mat) { - return storageChestCache.contains(mat.getMaterial().ordinal()); - } - - public static boolean isLit(BlockData mat) { - return litCache.contains(mat.getMaterial().ordinal()); - } - - public static boolean isUpdatable(BlockData mat) { - return isLit(mat) - || isStorage(mat) - || (mat instanceof PointedDripstone - && ((PointedDripstone) mat).getThickness().equals(PointedDripstone.Thickness.TIP)); - } - - public static boolean isFoliage(Material d) { - return foliageCache.contains(d.ordinal()); - } - - public static boolean isFoliage(BlockData d) { - return isFoliage(d.getMaterial()); - } - - public static boolean isDecorant(BlockData m) { - return decorantCache.contains(m.getMaterial().ordinal()); - } - - public static KList get(KList find) { - KList b = new KList<>(); - - for (String i : find) { - BlockData bd = get(i); - - if (bd != null) { - b.add(bd); - } - } - - return b; - } - - public static boolean isFluid(BlockData d) { - return d.getMaterial().equals(Material.WATER) || d.getMaterial().equals(Material.LAVA); - } - - public static boolean isAirOrFluid(BlockData d) { - return isAir(d) || isFluid(d); - } - - public static boolean isAir(BlockData d) { - if (d == null) { - return true; - } - - return d.getMaterial().equals(Material.AIR) || d.getMaterial().equals(Material.CAVE_AIR) || d.getMaterial().equals(Material.VOID_AIR); - } - - - public synchronized static String[] getBlockTypes() { - KList bt = new KList<>(); - - for (Material i : Material.values()) { - if (i.isBlock()) { - String v = i.createBlockData().getAsString(true); - - if (v.contains("[")) { - v = v.split("\\Q[\\E")[0]; - } - - bt.add(v); - } - } - - return bt.toArray(new String[0]); - } - - public static String[] getItemTypes() { - KList bt = new KList<>(); - - for (Material i : Material.values()) { - String v = i.name().toLowerCase().trim(); - bt.add(v); - } - - return bt.toArray(new String[0]); - } - - public static boolean isWaterLogged(BlockData b) { - return (b instanceof Waterlogged) && ((Waterlogged) b).isWaterlogged(); - } - - public static void registerCustomBlockData(String namespace, String key, BlockData blockData) { - custom.put(namespace + ":" + key, blockData); - } - - public static boolean isVineBlock(BlockData data) { - return switch (data.getMaterial()) { - case VINE, SCULK_VEIN, GLOW_LICHEN -> true; - default -> false; - }; - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/ChunkCache.java b/src/main/java/com/volmit/adapt/util/data/ChunkCache.java deleted file mode 100644 index 4d930dab2..000000000 --- a/src/main/java/com/volmit/adapt/util/data/ChunkCache.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -//import com.volmit.react.util.function.Function2; - -import com.volmit.adapt.util.Function2; - -import java.util.concurrent.atomic.AtomicReferenceArray; - -public class ChunkCache { - private final AtomicReferenceArray cache; - - public ChunkCache() { - cache = new AtomicReferenceArray<>(256); - } - - public T compute(int x, int z, Function2 function) { - T t = get(x & 15, z & 15); - - if (t == null) { - t = function.apply(x, z); - set(x & 15, z & 15, t); - } - - return t; - } - - private void set(int x, int z, T t) { - cache.set(x * 16 + z, t); - } - - private T get(int x, int z) { - return cache.get(x * 16 + z); - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/ComplexCache.java b/src/main/java/com/volmit/adapt/util/data/ComplexCache.java deleted file mode 100644 index effd8d590..000000000 --- a/src/main/java/com/volmit/adapt/util/data/ComplexCache.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -//import com.volmit.react.util.cache.Cache; -//import com.volmit.react.util.collection.KMap; - -import com.volmit.adapt.util.cache.Cache; -import com.volmit.adapt.util.collection.KMap; - -public class ComplexCache { - private final KMap> chunks; - - public ComplexCache() { - chunks = new KMap<>(); - } - - public boolean has(int x, int z) { - return chunks.containsKey(Cache.key(x, z)); - } - - public void invalidate(int x, int z) { - chunks.remove(Cache.key(x, z)); - } - - public ChunkCache chunk(int x, int z) { - return chunks.computeIfAbsent(Cache.key(x, z), (f) -> new ChunkCache<>()); - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/Cuboid.java b/src/main/java/com/volmit/adapt/util/data/Cuboid.java deleted file mode 100644 index 98cb517e1..000000000 --- a/src/main/java/com/volmit/adapt/util/data/Cuboid.java +++ /dev/null @@ -1,751 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -//import com.volmit.react.util.collection.KList; -//import com.volmit.react.util.math.Direction; -import com.volmit.adapt.util.Direction; -import com.volmit.adapt.util.collection.KList; -import org.bukkit.*; -import org.bukkit.block.Block; -import org.bukkit.configuration.serialization.ConfigurationSerializable; -import org.bukkit.entity.Entity; - -import java.util.*; - -/** - * Cuboids - * - * @author cyberpwn - */ -public class Cuboid implements Iterable, Cloneable, ConfigurationSerializable { - protected final String worldName; - protected int x1, y1, z1; - protected int x2, y2, z2; - - /** - * Construct a Cuboid given two Location objects which represent any two corners - * of the Cuboid. - * - * @param l1 one of the corners - * @param l2 the other corner - */ - public Cuboid(Location l1, Location l2) { - if (!l1.getWorld().equals(l2.getWorld())) { - throw new IllegalArgumentException("locations must be on the same world"); - } - - worldName = l1.getWorld().getName(); - x1 = Math.min(l1.getBlockX(), l2.getBlockX()); - y1 = Math.min(l1.getBlockY(), l2.getBlockY()); - z1 = Math.min(l1.getBlockZ(), l2.getBlockZ()); - x2 = Math.max(l1.getBlockX(), l2.getBlockX()); - y2 = Math.max(l1.getBlockY(), l2.getBlockY()); - z2 = Math.max(l1.getBlockZ(), l2.getBlockZ()); - } - - /** - * Construct a one-block Cuboid at the given Location of the Cuboid. - * - * @param l1 location of the Cuboid - */ - public Cuboid(Location l1) { - this(l1, l1); - } - - /** - * Copy constructor. - * - * @param other the Cuboid to copy - */ - public Cuboid(Cuboid other) { - this(other.getWorld().getName(), other.x1, other.y1, other.z1, other.x2, other.y2, other.z2); - } - - /** - * Construct a Cuboid in the given World and xyz co-ordinates - * - * @param world the Cuboid's world - * @param x1 X co-ordinate of corner 1 - * @param y1 Y co-ordinate of corner 1 - * @param z1 Z co-ordinate of corner 1 - * @param x2 X co-ordinate of corner 2 - * @param y2 Y co-ordinate of corner 2 - * @param z2 Z co-ordinate of corner 2 - */ - public Cuboid(World world, int x1, int y1, int z1, int x2, int y2, int z2) { - this.worldName = world.getName(); - this.x1 = Math.min(x1, x2); - this.x2 = Math.max(x1, x2); - this.y1 = Math.min(y1, y2); - this.y2 = Math.max(y1, y2); - this.z1 = Math.min(z1, z2); - this.z2 = Math.max(z1, z2); - } - - /** - * Construct a Cuboid in the given world name and xyz co-ordinates. - * - * @param worldName the Cuboid's world name - * @param x1 X co-ordinate of corner 1 - * @param y1 Y co-ordinate of corner 1 - * @param z1 Z co-ordinate of corner 1 - * @param x2 X co-ordinate of corner 2 - * @param y2 Y co-ordinate of corner 2 - * @param z2 Z co-ordinate of corner 2 - */ - private Cuboid(String worldName, int x1, int y1, int z1, int x2, int y2, int z2) { - this.worldName = worldName; - this.x1 = Math.min(x1, x2); - this.x2 = Math.max(x1, x2); - this.y1 = Math.min(y1, y2); - this.y2 = Math.max(y1, y2); - this.z1 = Math.min(z1, z2); - this.z2 = Math.max(z1, z2); - } - - public Cuboid(Map map) { - worldName = (String) map.get("worldName"); - x1 = (Integer) map.get("x1"); - x2 = (Integer) map.get("x2"); - y1 = (Integer) map.get("y1"); - y2 = (Integer) map.get("y2"); - z1 = (Integer) map.get("z1"); - z2 = (Integer) map.get("z2"); - } - - public KList getEntities() { - KList en = new KList<>(); - - for (Chunk i : getChunks()) { - for (Entity j : i.getEntities()) { - if (contains(j.getLocation())) { - en.add(j); - } - } - } - - return en; - } - - /** - * Set the locations - * - * @param l1 a - * @param l2 b - */ - public void set(Location l1, Location l2) { - x1 = Math.min(l1.getBlockX(), l2.getBlockX()); - y1 = Math.min(l1.getBlockY(), l2.getBlockY()); - z1 = Math.min(l1.getBlockZ(), l2.getBlockZ()); - x2 = Math.max(l1.getBlockX(), l2.getBlockX()); - y2 = Math.max(l1.getBlockY(), l2.getBlockY()); - z2 = Math.max(l1.getBlockZ(), l2.getBlockZ()); - } - - @Override - public Map serialize() { - Map map = new HashMap<>(); - map.put("worldName", worldName); - map.put("x1", x1); - map.put("y1", y1); - map.put("z1", z1); - map.put("x2", x2); - map.put("y2", y2); - map.put("z2", z2); - return map; - } - - public Cuboid flatten(int level) { - return new Cuboid(getWorld(), x1, level, z1, x2, level, z2); - } - - /** - * Get the Location of the lower northeast corner of the Cuboid (minimum XYZ - * co-ordinates). - * - * @return Location of the lower northeast corner - */ - public Location getLowerNE() { - return new Location(getWorld(), x1, y1, z1); - } - - /** - * Get the Location of the upper southwest corner of the Cuboid (maximum XYZ - * co-ordinates). - * - * @return Location of the upper southwest corner - */ - public Location getUpperSW() { - return new Location(getWorld(), x2, y2, z2); - } - - /** - * Get the the centre of the Cuboid - * - * @return Location at the centre of the Cuboid - */ - public Location getCenter() { - int x1 = getUpperX() + 1; - int y1 = getUpperY() + 1; - int z1 = getUpperZ() + 1; - return new Location(getWorld(), getLowerX() + (x1 - getLowerX()) / 2.0, getLowerY() + (y1 - getLowerY()) / 2.0, getLowerZ() + (z1 - getLowerZ()) / 2.0); - } - - /** - * Get the Cuboid's world. - * - * @return the World object representing this Cuboid's world - * @throws IllegalStateException if the world is not loaded - */ - public World getWorld() { - World world = Bukkit.getWorld(worldName); - if (world == null) { - throw new IllegalStateException("world '" + worldName + "' is not loaded"); - } - return world; - } - - /** - * Get the size of this Cuboid along the X axis - * - * @return Size of Cuboid along the X axis - */ - public int getSizeX() { - return (x2 - x1) + 1; - } - - /** - * Get the size of this Cuboid along the Y axis - * - * @return Size of Cuboid along the Y axis - */ - public int getSizeY() { - return (y2 - y1) + 1; - } - - /** - * Get the size of this Cuboid along the Z axis - * - * @return Size of Cuboid along the Z axis - */ - public int getSizeZ() { - return (z2 - z1) + 1; - } - - /** - * Get the cuboid dimensions - * - * @return the dimensions - */ - public Dimension getDimension() { - return new Dimension(getSizeX(), getSizeY(), getSizeZ()); - } - - /** - * Get the minimum X co-ordinate of this Cuboid - * - * @return the minimum X co-ordinate - */ - public int getLowerX() { - return x1; - } - - /** - * Get the minimum Y co-ordinate of this Cuboid - * - * @return the minimum Y co-ordinate - */ - public int getLowerY() { - return y1; - } - - /** - * Get the minimum Z co-ordinate of this Cuboid - * - * @return the minimum Z co-ordinate - */ - public int getLowerZ() { - return z1; - } - - /** - * Get the maximum X co-ordinate of this Cuboid - * - * @return the maximum X co-ordinate - */ - public int getUpperX() { - return x2; - } - - /** - * Get the maximum Y co-ordinate of this Cuboid - * - * @return the maximum Y co-ordinate - */ - public int getUpperY() { - return y2; - } - - /** - * Get the maximum Z co-ordinate of this Cuboid - * - * @return the maximum Z co-ordinate - */ - public int getUpperZ() { - return z2; - } - - /** - * Get the Blocks at the eight corners of the Cuboid. - * - * @return array of Block objects representing the Cuboid corners - */ - public Block[] corners() { - Block[] res = new Block[8]; - World w = getWorld(); - res[0] = w.getBlockAt(x1, y1, z1); - res[1] = w.getBlockAt(x1, y1, z2); - res[2] = w.getBlockAt(x1, y2, z1); - res[3] = w.getBlockAt(x1, y2, z2); - res[4] = w.getBlockAt(x2, y1, z1); - res[5] = w.getBlockAt(x2, y1, z2); - res[6] = w.getBlockAt(x2, y2, z1); - res[7] = w.getBlockAt(x2, y2, z2); - return res; - } - - /** - * Expand the Cuboid in the given direction by the given amount. Negative - * amounts will shrink the Cuboid in the given direction. Shrinking a cuboid's - * face past the opposite face is not an error and will return a valid Cuboid. - * - * @param dir the direction in which to expand - * @param amount the number of blocks by which to expand - * @return a new Cuboid expanded by the given direction and amount - */ - public Cuboid expand(CuboidDirection dir, int amount) { - return switch (dir) { - case North -> new Cuboid(worldName, x1 - amount, y1, z1, x2, y2, z2); - case South -> new Cuboid(worldName, x1, y1, z1, x2 + amount, y2, z2); - case East -> new Cuboid(worldName, x1, y1, z1 - amount, x2, y2, z2); - case West -> new Cuboid(worldName, x1, y1, z1, x2, y2, z2 + amount); - case Down -> new Cuboid(worldName, x1, y1 - amount, z1, x2, y2, z2); - case Up -> new Cuboid(worldName, x1, y1, z1, x2, y2 + amount, z2); - default -> throw new IllegalArgumentException("invalid direction " + dir); - }; - } - - public Cuboid expand(Direction dir, int amount) { - int ax = dir.toVector().getBlockX() == 1 ? amount : 0; - int sx = dir.toVector().getBlockX() == -1 ? -amount : 0; - int ay = dir.toVector().getBlockY() == 1 ? amount : 0; - int sy = dir.toVector().getBlockY() == -1 ? -amount : 0; - int az = dir.toVector().getBlockZ() == 1 ? amount : 0; - int sz = dir.toVector().getBlockZ() == -1 ? -amount : 0; - return new Cuboid(worldName, x1 + sx, y1 + sy, z1 + sz, x2 + ax, y2 + ay, z2 + az); - } - - /** - * Shift the Cuboid in the given direction by the given amount. - * - * @param dir the direction in which to shift - * @param amount the number of blocks by which to shift - * @return a new Cuboid shifted by the given direction and amount - */ - public Cuboid shift(CuboidDirection dir, int amount) { - return expand(dir, amount).expand(dir.opposite(), -amount); - } - - /** - * Outset (grow) the Cuboid in the given direction by the given amount. - * - * @param dir the direction in which to outset (must be Horizontal, Vertical, or - * Both) - * @param amount the number of blocks by which to outset - * @return a new Cuboid outset by the given direction and amount - */ - public Cuboid outset(CuboidDirection dir, int amount) { - Cuboid c = switch (dir) { - case Horizontal -> - expand(CuboidDirection.North, amount).expand(CuboidDirection.South, amount).expand(CuboidDirection.East, amount).expand(CuboidDirection.West, amount); - case Vertical -> expand(CuboidDirection.Down, amount).expand(CuboidDirection.Up, amount); - case Both -> outset(CuboidDirection.Horizontal, amount).outset(CuboidDirection.Vertical, amount); - default -> throw new IllegalArgumentException("invalid direction " + dir); - }; - return c; - } - - /** - * Inset (shrink) the Cuboid in the given direction by the given amount. - * Equivalent to calling outset() with a negative amount. - * - * @param dir the direction in which to inset (must be Horizontal, Vertical, or - * Both) - * @param amount the number of blocks by which to inset - * @return a new Cuboid inset by the given direction and amount - */ - public Cuboid inset(CuboidDirection dir, int amount) { - return outset(dir, -amount); - } - - /** - * Return true if the point at (x,y,z) is contained within this Cuboid. - * - * @param x the X co-ordinate - * @param y the Y co-ordinate - * @param z the Z co-ordinate - * @return true if the given point is within this Cuboid, false otherwise - */ - public boolean contains(int x, int y, int z) { - return x >= x1 && x <= x2 && y >= y1 && y <= y2 && z >= z1 && z <= z2; - } - - /** - * Check if the given Block is contained within this Cuboid. - * - * @param b the Block to check for - * @return true if the Block is within this Cuboid, false otherwise - */ - public boolean contains(Block b) { - return contains(b.getLocation()); - } - - /** - * Check if the given Location is contained within this Cuboid. - * - * @param l the Location to check for - * @return true if the Location is within this Cuboid, false otherwise - */ - public boolean contains(Location l) { - return worldName.equals(l.getWorld().getName()) && contains(l.getBlockX(), l.getBlockY(), l.getBlockZ()); - } - - /** - * Get the volume of this Cuboid. - * - * @return the Cuboid volume, in blocks - */ - public int volume() { - return getSizeX() * getSizeY() * getSizeZ(); - } - - /** - * Get the average light level of all empty (air) blocks in the Cuboid. Returns - * 0 if there are no empty blocks. - * - * @return the average light level of this Cuboid - */ - public byte averageLightLevel() { - long total = 0; - int n = 0; - for (Block b : this) { - if (b.isEmpty()) { - total += b.getLightLevel(); - ++n; - } - } - return n > 0 ? (byte) (total / n) : 0; - } - - /** - * Contract the Cuboid, returning a Cuboid with any air around the edges - * removed, just large enough to include all non-air blocks. - * - * @return a new Cuboid with no external air blocks - */ - public Cuboid contract() { - return this.contract(CuboidDirection.Down).contract(CuboidDirection.South).contract(CuboidDirection.East).contract(CuboidDirection.Up).contract(CuboidDirection.North).contract(CuboidDirection.West); - } - - /** - * Contract the Cuboid in the given direction, returning a new Cuboid which has - * no exterior empty space. E.g. a direction of Down will push the top face - * downwards as much as possible. - * - * @param dir the direction in which to contract - * @return a new Cuboid contracted in the given direction - */ - public Cuboid contract(CuboidDirection dir) { - Cuboid face = getFace(dir.opposite()); - switch (dir) { - case Down -> { - while (face.containsOnly(Material.AIR) && face.getLowerY() > this.getLowerY()) { - face = face.shift(CuboidDirection.Down, 1); - } - return new Cuboid(worldName, x1, y1, z1, x2, face.getUpperY(), z2); - } - case Up -> { - while (face.containsOnly(Material.AIR) && face.getUpperY() < this.getUpperY()) { - face = face.shift(CuboidDirection.Up, 1); - } - return new Cuboid(worldName, x1, face.getLowerY(), z1, x2, y2, z2); - } - case North -> { - while (face.containsOnly(Material.AIR) && face.getLowerX() > this.getLowerX()) { - face = face.shift(CuboidDirection.North, 1); - } - return new Cuboid(worldName, x1, y1, z1, face.getUpperX(), y2, z2); - } - case South -> { - while (face.containsOnly(Material.AIR) && face.getUpperX() < this.getUpperX()) { - face = face.shift(CuboidDirection.South, 1); - } - return new Cuboid(worldName, face.getLowerX(), y1, z1, x2, y2, z2); - } - case East -> { - while (face.containsOnly(Material.AIR) && face.getLowerZ() > this.getLowerZ()) { - face = face.shift(CuboidDirection.East, 1); - } - return new Cuboid(worldName, x1, y1, z1, x2, y2, face.getUpperZ()); - } - case West -> { - while (face.containsOnly(Material.AIR) && face.getUpperZ() < this.getUpperZ()) { - face = face.shift(CuboidDirection.West, 1); - } - return new Cuboid(worldName, x1, y1, face.getLowerZ(), x2, y2, z2); - } - default -> throw new IllegalArgumentException("Invalid direction " + dir); - } - } - - /** - * Get the Cuboid representing the face of this Cuboid. The resulting Cuboid - * will be one block thick in the axis perpendicular to the requested face. - * - * @param dir which face of the Cuboid to get - * @return the Cuboid representing this Cuboid's requested face - */ - public Cuboid getFace(CuboidDirection dir) { - return switch (dir) { - case Down -> new Cuboid(worldName, x1, y1, z1, x2, y1, z2); - case Up -> new Cuboid(worldName, x1, y2, z1, x2, y2, z2); - case North -> new Cuboid(worldName, x1, y1, z1, x1, y2, z2); - case South -> new Cuboid(worldName, x2, y1, z1, x2, y2, z2); - case East -> new Cuboid(worldName, x1, y1, z1, x2, y2, z1); - case West -> new Cuboid(worldName, x1, y1, z2, x2, y2, z2); - default -> throw new IllegalArgumentException("Invalid direction " + dir); - }; - } - - /** - * Check if the Cuboid contains only blocks of the given type - * - * @param material the material to check for - * @return true if this Cuboid contains only blocks of the given type - */ - public boolean containsOnly(Material material) { - for (Block b : this) { - if (b.getType() != material) { - return false; - } - } - return true; - } - - /** - * Get the Cuboid big enough to hold both this Cuboid and the given one. - * - * @param other the other Cuboid to include - * @return a new Cuboid large enough to hold this Cuboid and the given Cuboid - */ - public Cuboid getBoundingCuboid(Cuboid other) { - if (other == null) { - return this; - } - - int xMin = Math.min(getLowerX(), other.getLowerX()); - int yMin = Math.min(getLowerY(), other.getLowerY()); - int zMin = Math.min(getLowerZ(), other.getLowerZ()); - int xMax = Math.max(getUpperX(), other.getUpperX()); - int yMax = Math.max(getUpperY(), other.getUpperY()); - int zMax = Math.max(getUpperZ(), other.getUpperZ()); - - return new Cuboid(worldName, xMin, yMin, zMin, xMax, yMax, zMax); - } - - /** - * Get a block relative to the lower NE point of the Cuboid. - * - * @param x the X co-ordinate - * @param y the Y co-ordinate - * @param z the Z co-ordinate - * @return the block at the given position - */ - public Block getRelativeBlock(int x, int y, int z) { - return getWorld().getBlockAt(x1 + x, y1 + y, z1 + z); - } - - /** - * Get a block relative to the lower NE point of the Cuboid in the given World. - * This version of getRelativeBlock() should be used if being called many times, - * to avoid excessive calls to getWorld(). - * - * @param w the World - * @param x the X co-ordinate - * @param y the Y co-ordinate - * @param z the Z co-ordinate - * @return the block at the given position - */ - public Block getRelativeBlock(World w, int x, int y, int z) { - return w.getBlockAt(x1 + x, y1 + y, z1 + z); - } - - /** - * Get a list of the chunks which are fully or partially contained in this - * cuboid. - * - * @return a list of Chunk objects - */ - public List getChunks() { - List res = new ArrayList<>(); - - World w = getWorld(); - int x1 = getLowerX() & ~0xf; - int x2 = getUpperX() & ~0xf; - int z1 = getLowerZ() & ~0xf; - int z2 = getUpperZ() & ~0xf; - for (int x = x1; x <= x2; x += 16) { - for (int z = z1; z <= z2; z += 16) { - res.add(w.getChunkAt(x >> 4, z >> 4)); - } - } - return res; - } - - /* - Set all the blocks within the Cuboid to the given MaterialData, using a - MassBlockUpdate object for fast updates. - - @param mat - * the MaterialData to set - * @param mbu - * the MassBlockUpdate object - */ - - /** - * Reset the light level of all blocks within this Cuboid. - */ - - /* - * (non-Javadoc) - * - * @see java.lang.Iterable#iterator() - */ - @Override - public Iterator iterator() { - return new CuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#clone() - */ - @SuppressWarnings("MethodDoesntCallSuperMethod") - @Override - public Cuboid clone() { - return new Cuboid(this); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "Cuboid: " + worldName + "," + x1 + "," + y1 + "," + z1 + "=>" + x2 + "," + y2 + "," + z2; - } - - public enum CuboidDirection { - - North, - East, - South, - West, - Up, - Down, - Horizontal, - Vertical, - Both, - Unknown; - - public CuboidDirection opposite() { - return switch (this) { - case North -> South; - case East -> West; - case South -> North; - case West -> East; - case Horizontal -> Vertical; - case Vertical -> Horizontal; - case Up -> Down; - case Down -> Up; - case Both -> Both; - default -> Unknown; - }; - } - } - - public static class CuboidIterator implements Iterator { - private final World w; - private final int baseX; - private final int baseY; - private final int baseZ; - private final int sizeX; - private final int sizeY; - private final int sizeZ; - private int x, y, z; - - public CuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) { - this.w = w; - baseX = x1; - baseY = y1; - baseZ = z1; - sizeX = Math.abs(x2 - x1) + 1; - sizeY = Math.abs(y2 - y1) + 1; - sizeZ = Math.abs(z2 - z1) + 1; - x = y = z = 0; - } - - @Override - public boolean hasNext() { - return x < sizeX && y < sizeY && z < sizeZ; - } - - @Override - public Block next() { - Block b = w.getBlockAt(baseX + x, baseY + y, baseZ + z); - if (++x >= sizeX) { - x = 0; - if (++y >= sizeY) { - y = 0; - ++z; - } - } - return b; - } - - @Override - public void remove() { - // nop - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/CuboidException.java b/src/main/java/com/volmit/adapt/util/data/CuboidException.java deleted file mode 100644 index 4a9b7aa98..000000000 --- a/src/main/java/com/volmit/adapt/util/data/CuboidException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -/** - * Represents a cuboid exception - * - * @author cyberpwn - */ -public class CuboidException extends Exception { - private static final long serialVersionUID = 1L; - - public CuboidException(String string) { - super(string); - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/DUTF.java b/src/main/java/com/volmit/adapt/util/data/DUTF.java deleted file mode 100644 index 5b3580b2e..000000000 --- a/src/main/java/com/volmit/adapt/util/data/DUTF.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -/** - *

Encodes signed and unsigned values using a common variable-length - * scheme, found for example in - *
- * Google's Protocol Buffers. It uses fewer bytes to encode smaller values, - * but will use slightly more bytes to encode large values.

- *

- *

Signed values are further encoded using so-called zig-zag encoding - * in order to make them "compatible" with variable-length encoding.

- */ -public final class DUTF { - - private DUTF() { - } - - public static void write(String s, DataOutputStream dos) throws IOException { - byte[] b = s.getBytes(StandardCharsets.UTF_8); - dos.writeShort(b.length); - dos.write(b); - } - - public static String read(DataInputStream din) throws IOException { - byte[] d = new byte[din.readShort()]; - din.read(d); - return new String(d, StandardCharsets.UTF_8); - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/DataPalette.java b/src/main/java/com/volmit/adapt/util/data/DataPalette.java deleted file mode 100644 index abe115344..000000000 --- a/src/main/java/com/volmit/adapt/util/data/DataPalette.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -//import com.volmit.react.util.collection.KList; - -import com.volmit.adapt.util.collection.KList; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public class DataPalette { - private final KList palette; - - public DataPalette() { - this(new KList<>(16)); - } - - public DataPalette(KList palette) { - this.palette = palette; - } - - public static DataPalette getPalette(IOAdapter adapter, DataInputStream din) throws IOException { - KList palette = new KList<>(); - int s = din.readShort() - Short.MIN_VALUE; - - for (int i = 0; i < s; i++) { - palette.add(adapter.read(din)); - } - - return new DataPalette<>(palette); - } - - public KList getPalette() { - return palette; - } - - public T get(int index) { - synchronized (palette) { - if (!palette.hasIndex(index)) { - return null; - } - - return palette.get(index); - } - } - - public int getIndex(T t) { - int v = 0; - - synchronized (palette) { - v = palette.indexOf(t); - - if (v == -1) { - v = palette.size(); - palette.add(t); - } - } - - return v; - } - - public void write(IOAdapter adapter, DataOutputStream dos) throws IOException { - synchronized (palette) { - dos.writeShort(getPalette().size() + Short.MIN_VALUE); - - for (T t : palette) { - adapter.write(t, dos); - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/Dimension.java b/src/main/java/com/volmit/adapt/util/data/Dimension.java deleted file mode 100644 index c8cfec9f9..000000000 --- a/src/main/java/com/volmit/adapt/util/data/Dimension.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -/** - * Dimensions - * - * @author cyberpwn - */ -public class Dimension { - private final int width; - private final int height; - private final int depth; - - /** - * Make a dimension - * - * @param width width of this (X) - * @param height the height (Y) - * @param depth the depth (Z) - */ - public Dimension(int width, int height, int depth) { - this.width = width; - this.height = height; - this.depth = depth; - } - - /** - * Make a dimension - * - * @param width width of this (X) - * @param height the height (Y) - */ - public Dimension(int width, int height) { - this.width = width; - this.height = height; - this.depth = 0; - } - - /** - * Get the direction of the flat part of this dimension (null if no thin - * face) - * - * @return the direction of the flat pane or null - */ - public DimensionFace getPane() { - if (width == 1) { - return DimensionFace.X; - } - - if (height == 1) { - return DimensionFace.Y; - } - - if (depth == 1) { - return DimensionFace.Z; - } - - return null; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public int getDepth() { - return depth; - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/DimensionFace.java b/src/main/java/com/volmit/adapt/util/data/DimensionFace.java deleted file mode 100644 index cc9a2459d..000000000 --- a/src/main/java/com/volmit/adapt/util/data/DimensionFace.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -/** - * Represents a dimension (coordinates not worlds) - * - * @author cyberpwn - */ -public enum DimensionFace { - /** - * The X dimension (width) - */ - X, - - /** - * The Y dimension (height) - */ - Y, - - /** - * The Z dimension (depth) - */ - Z -} diff --git a/src/main/java/com/volmit/adapt/util/data/DoubleArrayUtils.java b/src/main/java/com/volmit/adapt/util/data/DoubleArrayUtils.java deleted file mode 100644 index cac4e0303..000000000 --- a/src/main/java/com/volmit/adapt/util/data/DoubleArrayUtils.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - - -import com.google.common.util.concurrent.AtomicDoubleArray; - -import java.util.Arrays; - -public class DoubleArrayUtils { - public static void shiftRight(double[] values, double push) { - if (values.length - 2 + 1 >= 0) System.arraycopy(values, 0, values, 1, values.length - 2 + 1); - - values[0] = push; - } - - public static void wrapRight(double[] values) { - double last = values[values.length - 1]; - shiftRight(values, last); - } - - public static void fill(double[] values, double value) { - Arrays.fill(values, value); - } - - public static void fill(AtomicDoubleArray values, double value) { - for (int i = 0; i < values.length(); i++) { - values.set(i, value); - } - } - -} diff --git a/src/main/java/com/volmit/adapt/util/data/Heafty.java b/src/main/java/com/volmit/adapt/util/data/Heafty.java deleted file mode 100644 index c8d5c97b0..000000000 --- a/src/main/java/com/volmit/adapt/util/data/Heafty.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -@FunctionalInterface -public interface Heafty { - int getHeaft(); -} diff --git a/src/main/java/com/volmit/adapt/util/data/HeightMap.java b/src/main/java/com/volmit/adapt/util/data/HeightMap.java deleted file mode 100644 index b9625ddf2..000000000 --- a/src/main/java/com/volmit/adapt/util/data/HeightMap.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import java.util.Arrays; - -public class HeightMap { - private final int[] height; - - public HeightMap() { - height = new int[256]; - Arrays.fill(height, 0); - } - - public void setHeight(int x, int z, int h) { - height[x * 16 + z] = (h); - } - - public int getHeight(int x, int z) { - return height[x * 16 + z]; - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/IOAdapter.java b/src/main/java/com/volmit/adapt/util/data/IOAdapter.java deleted file mode 100644 index 97c128835..000000000 --- a/src/main/java/com/volmit/adapt/util/data/IOAdapter.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public interface IOAdapter { - void write(T t, DataOutputStream dos) throws IOException; - - T read(DataInputStream din) throws IOException; -} diff --git a/src/main/java/com/volmit/adapt/util/data/InvertedBiomeGrid.java b/src/main/java/com/volmit/adapt/util/data/InvertedBiomeGrid.java deleted file mode 100644 index 6986ef864..000000000 --- a/src/main/java/com/volmit/adapt/util/data/InvertedBiomeGrid.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import org.bukkit.block.Biome; -import org.bukkit.generator.ChunkGenerator.BiomeGrid; - -public class InvertedBiomeGrid implements BiomeGrid { - private final BiomeGrid grid; - - public InvertedBiomeGrid(BiomeGrid real) { - this.grid = real; - } - - - @SuppressWarnings("deprecation") - @Override - public Biome getBiome(int arg0, int arg1) { - return grid.getBiome(arg0, arg1); - } - - - @Override - public Biome getBiome(int arg0, int arg1, int arg2) { - return grid.getBiome(arg0, 255 - arg1, arg2); - } - - @SuppressWarnings("deprecation") - @Override - public void setBiome(int arg0, int arg1, Biome arg2) { - grid.setBiome(arg0, arg1, arg2); - } - - @Override - public void setBiome(int arg0, int arg1, int arg2, Biome arg3) { - grid.setBiome(arg0, 255 - arg1, arg2, arg3); - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/KCache.java b/src/main/java/com/volmit/adapt/util/data/KCache.java deleted file mode 100644 index a87cc65bf..000000000 --- a/src/main/java/com/volmit/adapt/util/data/KCache.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import com.github.benmanes.caffeine.cache.CacheLoader; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; -import com.volmit.adapt.util.RollingSequence; -//import com.volmit.react.util.math.RollingSequence; - -public class KCache { - private final long max; - private final LoadingCache cache; - private final boolean fastDump; - private final RollingSequence msu = new RollingSequence(100); - private CacheLoader loader; - - public KCache(CacheLoader loader, long max) { - this(loader, max, false); - } - - public KCache(CacheLoader loader, long max, boolean fastDump) { - this.max = max; - this.fastDump = fastDump; - this.loader = loader; - this.cache = create(loader); - } - - private LoadingCache create(CacheLoader loader) { - return Caffeine - .newBuilder() - .maximumSize(max) - .initialCapacity((int) (max)) - .build((k) -> loader == null ? null : loader.load(k)); - } - - - public void setLoader(CacheLoader loader) { - this.loader = loader; - } - - public void invalidate(K k) { - cache.invalidate(k); - } - - public void invalidate() { - cache.invalidateAll(); - } - - public V get(K k) { - return cache.get(k); - } - - public long getSize() { - return cache.estimatedSize(); - } - - public KCache getRawCache() { - return this; - } - - public long getMaxSize() { - return max; - } - - public boolean isClosed() { - return false; - } - - public boolean contains(K next) { - return cache.getIfPresent(next) != null; - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/MaterialBlock.java b/src/main/java/com/volmit/adapt/util/data/MaterialBlock.java deleted file mode 100644 index 536d6b8ad..000000000 --- a/src/main/java/com/volmit/adapt/util/data/MaterialBlock.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.BlockState; - -/** - * Material blocks - * - * @author cyberpwn - */ -@SuppressWarnings("deprecation") -public class MaterialBlock { - private Material material; - private Byte data; - - /** - * Create a materialblock - * - * @param material the material - * @param data the data - */ - public MaterialBlock(Material material, Byte data) { - this.material = material; - this.data = data; - } - - public MaterialBlock(Material material) { - this.material = material; - data = 0; - } - - public MaterialBlock(Location location) { - this(location.getBlock()); - } - - public MaterialBlock(BlockState state) { - material = state.getType(); - data = state.getData().getData(); - } - - public MaterialBlock(Block block) { - material = block.getType(); - data = block.getData(); - } - - public MaterialBlock() { - material = Material.AIR; - data = 0; - } - - public Material getMaterial() { - return material; - } - - public void setMaterial(Material material) { - this.material = material; - } - - public Byte getData() { - return data; - } - - public void setData(Byte data) { - this.data = data; - } - - @Override - public String toString() { - if (getData() == 0) { - return getMaterial().toString(); - } - - return getMaterial().toString() + ":" + getData(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((data == null) ? 0 : data.hashCode()); - result = prime * result + ((material == null) ? 0 : material.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - MaterialBlock other = (MaterialBlock) obj; - if (data == null) { - if (other.data != null) { - return false; - } - } else if (!data.equals(other.data)) { - return false; - } - return material == other.material; - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/Metadata.java b/src/main/java/com/volmit/adapt/util/data/Metadata.java deleted file mode 100644 index 857090d15..000000000 --- a/src/main/java/com/volmit/adapt/util/data/Metadata.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.volmit.adapt.util.data; - -import com.volmit.adapt.Adapt; -import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.metadata.Metadatable; - -import static java.util.UUID.randomUUID; - -public class Metadata { - private static final String UUID = randomUUID().toString(); - public static final Metadata VEIN_MINED = new Metadata("vein-mined", false); - - private final String key; - private final boolean defaultValue; - - private Metadata(String prefix, boolean defaultValue) { - this.key = UUID + prefix; - this.defaultValue = defaultValue; - } - - public boolean has(Metadatable metadatable) { - return metadatable.hasMetadata(key); - } - - public boolean get(Metadatable metadatable) { - return metadatable.hasMetadata(key) ? metadatable.getMetadata(key).get(0).asBoolean() : defaultValue; - } - - public void set(Metadatable metadatable, boolean value) { - if (value != defaultValue) metadatable.setMetadata(key, new FixedMetadataValue(Adapt.instance, value)); - else metadatable.removeMetadata(key, Adapt.instance); - } - - public void add(Metadatable metadatable) { - set(metadatable, !defaultValue); - } - - public void remove(Metadatable metadatable) { - set(metadatable, defaultValue); - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/NibbleArray.java b/src/main/java/com/volmit/adapt/util/data/NibbleArray.java deleted file mode 100644 index 12572dd92..000000000 --- a/src/main/java/com/volmit/adapt/util/data/NibbleArray.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.StringJoiner; - -public class NibbleArray implements Writable { - private static final int[] MASKS = new int[8]; - - static { - for (int i = 0; i < MASKS.length; i++) { - MASKS[i] = maskFor(i); - } - } - - private final int size; - private final Object lock = new Object(); - private byte[] data; - private int depth; - private byte mask; - private transient int bitIndex, byteIndex, bitInByte; - - public NibbleArray(int capacity, DataInputStream in) throws IOException { - size = capacity; - read(in); - } - - public NibbleArray(int nibbleDepth, int capacity) { - if (nibbleDepth > 8 || nibbleDepth < 1) { - throw new IllegalArgumentException(); - } - - int neededBits = nibbleDepth * capacity; - - size = capacity; - depth = nibbleDepth; - data = new byte[(neededBits + neededBits % 8) / 8]; - mask = (byte) maskFor(nibbleDepth); - } - - public NibbleArray(int nibbleDepth, int capacity, NibbleArray existing) { - if (nibbleDepth > 8 || nibbleDepth < 1) { - throw new IllegalArgumentException(); - } - - int neededBits = nibbleDepth * capacity; - size = capacity; - depth = nibbleDepth; - data = new byte[(neededBits + neededBits % 8) / 8]; - mask = (byte) maskFor(nibbleDepth); - - for (int i = 0; i < Math.min(size, existing.size()); i++) { - set(i, existing.get(i)); - } - } - - public static int maskFor(int amountOfBits) { - return powerOfTwo(amountOfBits) - 1; - } - - public static int powerOfTwo(int power) { - int result = 1; - - for (int i = 0; i < power; i++) { - result *= 2; - } - - return result; - } - - public static String binaryString(byte b, ByteOrder byteOrder) { - String str = String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(' ', '0'); - - return byteOrder.equals(ByteOrder.BIG_ENDIAN) ? str : reverse(str); - } - - public static String reverse(String str) { - return new StringBuilder(str).reverse().toString(); - } - - @Override - public void write(DataOutputStream o) throws IOException { - o.writeByte(depth + Byte.MIN_VALUE); - o.write(data); - } - - @Override - public void read(DataInputStream i) throws IOException { - depth = i.readByte() - Byte.MIN_VALUE; - int neededBits = depth * size; - data = new byte[(neededBits + neededBits % 8) / 8]; - mask = (byte) maskFor(depth); - i.read(data); - } - - public int size() { - return size; - } - - public byte get(int index) { - synchronized (lock) { - bitIndex = index * depth; - byteIndex = bitIndex >> 3; - bitInByte = bitIndex & 7; - int value = data[byteIndex] >> bitInByte; - - if (bitInByte + depth > 8) { - value |= data[byteIndex + 1] << bitInByte; - } - - return (byte) (value & mask); - } - } - - public byte get(int x, int y, int z) { - return get(index(x, y, z)); - } - - public int index(int x, int y, int z) { - return y << 8 | z << 4 | x; - } - - public void set(int x, int y, int z, int nibble) { - set(index(x, y, z), nibble); - } - - public void set(int index, int nibble) { - set(index, (byte) nibble); - } - - public void set(int index, byte nybble) { - synchronized (lock) { - bitIndex = index * depth; - byteIndex = bitIndex >> 3; - bitInByte = bitIndex & 7; - data[byteIndex] = (byte) (((~(data[byteIndex] & (mask << bitInByte)) & data[byteIndex]) | ((nybble & mask) << bitInByte)) & 0xff); - - if (bitInByte + depth > 8) { - data[byteIndex + 1] = (byte) (((~(data[byteIndex + 1] & MASKS[bitInByte + depth - 8]) & data[byteIndex + 1]) | ((nybble & mask) >> (8 - bitInByte))) & 0xff); - } - } - } - - public String toBitsString() { - return toBitsString(ByteOrder.BIG_ENDIAN); - } - - public String toBitsString(ByteOrder byteOrder) { - StringJoiner joiner = new StringJoiner(" "); - - for (byte datum : data) { - joiner.add(binaryString(datum, byteOrder)); - } - - return joiner.toString(); - } - - public void clear() { - Arrays.fill(data, (byte) 0); - } - - public void setAll(byte nibble) { - for (int i = 0; i < size; i++) { - set(i, nibble); - } - } - - public void setAll(int nibble) { - for (int i = 0; i < size; i++) { - set(i, (byte) nibble); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/NibbleDataPalette.java b/src/main/java/com/volmit/adapt/util/data/NibbleDataPalette.java deleted file mode 100644 index 49e1304f1..000000000 --- a/src/main/java/com/volmit/adapt/util/data/NibbleDataPalette.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -//import com.volmit.react.util.collection.KList; - -import com.volmit.adapt.util.collection.KList; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public abstract class NibbleDataPalette implements Writable { - private static final int DEFAULT_BITS_PER_BLOCK = 4; - private static final int CAPACITY = 4096; - private int bpb; - private NibbleArray data; - private KList palette; - - public NibbleDataPalette(T defaultValue) { - palette = new KList<>(); - bpb = DEFAULT_BITS_PER_BLOCK; - data = new NibbleArray(bpb, CAPACITY); - data.setAll(Byte.MIN_VALUE); - getPaletteId(defaultValue); - } - - public abstract T readType(DataInputStream i) throws IOException; - - public abstract void writeType(T t, DataOutputStream o) throws IOException; - - @Override - public void write(DataOutputStream o) throws IOException { - o.writeByte(bpb + Byte.MIN_VALUE); - o.writeByte(palette.size() + Byte.MIN_VALUE); - - for (T i : palette) { - writeType(i, o); - } - - data.write(o); - } - - @Override - public void read(DataInputStream i) throws IOException { - bpb = i.readByte() - Byte.MIN_VALUE; - palette = new KList<>(); - int v = i.readByte() - Byte.MIN_VALUE; - - for (int j = 0; j < v; j++) { - palette.add(readType(i)); - } - - data = new NibbleArray(CAPACITY, i); - } - - private void expand() { - if (bpb < 8) { - changeBitsPerBlock(bpb + 1); - } else { - throw new IndexOutOfBoundsException("The Data Palette can only handle at most 256 block types per 16x16x16 region. We cannot use more than 8 bits per block!"); - } - } - - public final void optimize() { - int targetBits = bpb; - int needed = palette.size(); - - for (int i = 1; i < bpb; i++) { - if (Math.pow(2, i) > needed) { - targetBits = i; - break; - } - } - - changeBitsPerBlock(targetBits); - } - - private void changeBitsPerBlock(int bits) { - bpb = bits; - data = new NibbleArray(bpb, CAPACITY, data); - } - - public final void set(int x, int y, int z, T d) { - data.set(getCoordinateIndex(x, y, z), getPaletteId(d)); - } - - public final T get(int x, int y, int z) { - return palette.get(data.get(getCoordinateIndex(x, y, z))); - } - - private int getPaletteId(T d) { - int index = palette.indexOf(d); - - if (index == -1) { - index = palette.size(); - palette.add(d); - - if (palette.size() > Math.pow(2, bpb)) { - expand(); - } - } - - return index + Byte.MIN_VALUE; - } - - private int getCoordinateIndex(int x, int y, int z) { - return y << 8 | z << 4 | x; - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/Recycler.java b/src/main/java/com/volmit/adapt/util/data/Recycler.java deleted file mode 100644 index 1cb03976b..000000000 --- a/src/main/java/com/volmit/adapt/util/data/Recycler.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; - -public class Recycler { - private final List> pool; - private final Supplier factory; - - public Recycler(Supplier factory) { - pool = new CopyOnWriteArrayList<>(); - this.factory = factory; - } - - public int getFreeObjects() { - int m = 0; - RecycledObject o; - for (RecycledObject tRecycledObject : pool) { - o = tRecycledObject; - - if (!o.isUsed()) { - m++; - } - } - - return m; - } - - public int getUsedOjects() { - int m = 0; - RecycledObject o; - for (RecycledObject tRecycledObject : pool) { - o = tRecycledObject; - - if (o.isUsed()) { - m++; - } - } - - return m; - } - - public void dealloc(T t) { - RecycledObject o; - - for (RecycledObject tRecycledObject : pool) { - o = tRecycledObject; - if (o.isUsed() && System.identityHashCode(t) == System.identityHashCode(o.getObject())) { - o.dealloc(); - return; - } - } - } - - public T alloc() { - RecycledObject o; - - for (RecycledObject tRecycledObject : pool) { - o = tRecycledObject; - if (o.alloc()) { - return o.getObject(); - } - } - - expand(); - - return alloc(); - } - - public boolean contract() { - return contract(Math.max(getFreeObjects() / 2, Runtime.getRuntime().availableProcessors())); - } - - public boolean contract(int maxFree) { - int remove = getFreeObjects() - maxFree; - - if (remove > 0) { - RecycledObject o; - - for (int i = pool.size() - 1; i > 0; i--) { - o = pool.get(i); - if (!o.isUsed()) { - pool.remove(i); - remove--; - - if (remove <= 0) { - return true; - } - } - } - } - - return false; - } - - public void expand() { - if (pool.isEmpty()) { - expand(Runtime.getRuntime().availableProcessors()); - return; - } - - expand(getUsedOjects() + Runtime.getRuntime().availableProcessors()); - } - - public void expand(int by) { - for (int i = 0; i < by; i++) { - pool.add(new RecycledObject<>(factory.get())); - } - } - - public int size() { - return pool.size(); - } - - public void deallocAll() { - pool.clear(); - } - - public static class RecycledObject { - private final T object; - private final AtomicBoolean used; - - public RecycledObject(T object) { - this.object = object; - used = new AtomicBoolean(false); - } - - public T getObject() { - return object; - } - - public boolean isUsed() { - return used.get(); - } - - public void dealloc() { - used.set(false); - } - - public boolean alloc() { - if (used.get()) { - return false; - } - - used.set(true); - return true; - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/Shrinkwrap.java b/src/main/java/com/volmit/adapt/util/data/Shrinkwrap.java deleted file mode 100644 index 288180afe..000000000 --- a/src/main/java/com/volmit/adapt/util/data/Shrinkwrap.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -public class Shrinkwrap { - private T t; - - public Shrinkwrap(T t) { - set(t); - } - - public Shrinkwrap() { - this(null); - } - - public T get() { - return t; - } - - public void set(T t) { - this.t = t; - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/TinyColor.java b/src/main/java/com/volmit/adapt/util/data/TinyColor.java deleted file mode 100644 index 45b1b6a33..000000000 --- a/src/main/java/com/volmit/adapt/util/data/TinyColor.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import java.awt.*; - -public class TinyColor { - private int r; - private int g; - private int b; - - public TinyColor(int color) { - this.r = (color >> 16) & 0xFF; - this.g = (color >> 8) & 0xFF; - this.b = color & 0xFF; - } - - public TinyColor(Color color) { - this(color.getRGB()); - } - - public TinyColor(String c) { - this(Color.decode(c)); - } - - public TinyColor(org.bukkit.Color c) { - this(c.asRGB()); - } - - public TinyColor(int r, int g, int b) { - this.r = r; - this.g = g; - this.b = b; - } - - public TinyColor(double h, double s, double b) { - this(Color.getHSBColor((float) h, (float) s, (float) b)); - } - - public TinyColor(float h, float s, float b) { - this(Color.getHSBColor(h, s, b)); - } - - public TinyColor brightness(float brightness) { - return new TinyColor(getHue(), getSaturation(), brightness); - } - - public TinyColor saturation(float saturation) { - return new TinyColor(getHue(), saturation, getBrightness()); - } - - public TinyColor hue(float hue) { - return new TinyColor(hue, getSaturation(), getBrightness()); - } - - public float getHue() { - return Color.RGBtoHSB(r, g, b, null)[0]; - } - - public float getSaturation() { - return Color.RGBtoHSB(r, g, b, null)[1]; - } - - public float getBrightness() { - return Color.RGBtoHSB(r, g, b, null)[2]; - } - - public TinyColor spin(int amount) { - int h = (int) Math.round(getHue() * 360); - h = (h + amount) % 360; - return hue((float) h / 360.0f); - } - - public int toRGB() { - return new Color(r, g, b).getRGB(); - } - - public Color getColor() { - return new Color(r, g, b); - } - - public org.bukkit.Color getBukkitColor() { - return org.bukkit.Color.fromRGB(r, g, b); - } - - public String toHex() { - return String.format("#%02x%02x%02x", r, g, b); - } - - public String toHex(boolean hash) { - if (hash) { - return toHex(); - } - - return String.format("%02x%02x%02x", r, g, b); - } - - public TinyColor copy() { - return new TinyColor(r, g, b); - } - - public TinyColor compliment() { - return spin(180); - } - - private float fclamp(float f, float min, float max) { - return Math.max(min, Math.min(max, f)); - } - - private int iclamp(int i, int min, int max) { - return Math.max(min, Math.min(max, i)); - } - - public TinyColor saturate(int amount) { - return saturation(fclamp(getSaturation() + ((float) amount / 100.0f), 0.0f, 1.0f)); - } - - public TinyColor desaturate(int amount) { - return saturate(-amount); - } - - public TinyColor brighten(int amount) { - return brightness(fclamp(getBrightness() + ((float) amount / 100.0f), 0.0f, 1.0f)); - } - - public TinyColor darken(int amount) { - return brighten(-amount); - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/Varint.java b/src/main/java/com/volmit/adapt/util/data/Varint.java deleted file mode 100644 index 501efa5b3..000000000 --- a/src/main/java/com/volmit/adapt/util/data/Varint.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -/** - *

Encodes signed and unsigned values using a common variable-length - * scheme, found for example in - * - * Google's Protocol Buffers. It uses fewer bytes to encode smaller values, - * but will use slightly more bytes to encode large values.

- *

- *

Signed values are further encoded using so-called zig-zag encoding - * in order to make them "compatible" with variable-length encoding.

- */ -public final class Varint { - - private Varint() { - } - - /** - * Encodes a value using the variable-length encoding from - * - * Google Protocol Buffers. It uses zig-zag encoding to efficiently - * encode signed values. If values are known to be nonnegative, - * {@link #writeUnsignedVarLong(long, DataOutput)} should be used. - * - * @param value value to encode - * @param out to writeNodeData bytes to - * @throws IOException if {@link DataOutput} throws {@link IOException} - */ - public static void writeSignedVarLong(long value, DataOutput out) throws IOException { - // Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types - writeUnsignedVarLong((value << 1) ^ (value >> 63), out); - } - - /** - * Encodes a value using the variable-length encoding from - * - * Google Protocol Buffers. Zig-zag is not used, so input must not be negative. - * If values can be negative, use {@link #writeSignedVarLong(long, DataOutput)} - * instead. This method treats negative input as like a large unsigned value. - * - * @param value value to encode - * @param out to writeNodeData bytes to - * @throws IOException if {@link DataOutput} throws {@link IOException} - */ - public static void writeUnsignedVarLong(long value, DataOutput out) throws IOException { - while ((value & 0xFFFFFFFFFFFFFF80L) != 0L) { - out.writeByte(((int) value & 0x7F) | 0x80); - value >>>= 7; - } - out.writeByte((int) value & 0x7F); - } - - /** - * @see #writeSignedVarLong(long, DataOutput) - */ - public static void writeSignedVarInt(int value, DataOutput out) throws IOException { - // Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types - writeUnsignedVarInt((value << 1) ^ (value >> 31), out); - } - - /** - * @see #writeUnsignedVarLong(long, DataOutput) - */ - public static void writeUnsignedVarInt(int value, DataOutput out) throws IOException { - while ((value & 0xFFFFFF80) != 0L) { - out.writeByte((value & 0x7F) | 0x80); - value >>>= 7; - } - out.writeByte(value & 0x7F); - } - - public static byte[] writeSignedVarInt(int value) { - // Great trick from http://code.google.com/apis/protocolbuffers/docs/encoding.html#types - return writeUnsignedVarInt((value << 1) ^ (value >> 31)); - } - - /** - * @see #writeUnsignedVarLong(long, DataOutput) - *

- * This one does not use streams and is much faster. - * Makes a single object each time, and that object is a primitive array. - */ - public static byte[] writeUnsignedVarInt(int value) { - byte[] byteArrayList = new byte[10]; - int i = 0; - while ((value & 0xFFFFFF80) != 0L) { - byteArrayList[i++] = ((byte) ((value & 0x7F) | 0x80)); - value >>>= 7; - } - byteArrayList[i] = ((byte) (value & 0x7F)); - byte[] out = new byte[i + 1]; - for (; i >= 0; i--) { - out[i] = byteArrayList[i]; - } - return out; - } - - /** - * @param in to read bytes from - * @return decode value - * @throws IOException if {@link DataInput} throws {@link IOException} - * @throws IllegalArgumentException if variable-length value does not terminate - * after 9 bytes have been read - * @see #writeSignedVarLong(long, DataOutput) - */ - public static long readSignedVarLong(DataInput in) throws IOException { - long raw = readUnsignedVarLong(in); - // This undoes the trick in writeSignedVarLong() - long temp = (((raw << 63) >> 63) ^ raw) >> 1; - // This extra step lets us deal with the largest signed values by treating - // negative results from read unsigned methods as like unsigned values - // Must re-flip the top bit if the original read value had it set. - return temp ^ (raw & (1L << 63)); - } - - /** - * @param in to read bytes from - * @return decode value - * @throws IOException if {@link DataInput} throws {@link IOException} - * @throws IllegalArgumentException if variable-length value does not terminate - * after 9 bytes have been read - * @see #writeUnsignedVarLong(long, DataOutput) - */ - public static long readUnsignedVarLong(DataInput in) throws IOException { - long value = 0L; - int i = 0; - long b; - while (((b = in.readByte()) & 0x80L) != 0) { - value |= (b & 0x7F) << i; - i += 7; - if (i > 63) { - throw new IllegalArgumentException("Variable length quantity is too long"); - } - } - return value | (b << i); - } - - /** - * @throws IllegalArgumentException if variable-length value does not terminate - * after 5 bytes have been read - * @throws IOException if {@link DataInput} throws {@link IOException} - * @see #readSignedVarLong(DataInput) - */ - public static int readSignedVarInt(DataInput in) throws IOException { - int raw = readUnsignedVarInt(in); - // This undoes the trick in writeSignedVarInt() - int temp = (((raw << 31) >> 31) ^ raw) >> 1; - // This extra step lets us deal with the largest signed values by treating - // negative results from read unsigned methods as like unsigned values. - // Must re-flip the top bit if the original read value had it set. - return temp ^ (raw & (1 << 31)); - } - - /** - * @throws IllegalArgumentException if variable-length value does not terminate - * after 5 bytes have been read - * @throws IOException if {@link DataInput} throws {@link IOException} - * @see #readUnsignedVarLong(DataInput) - */ - public static int readUnsignedVarInt(DataInput in) throws IOException { - int value = 0; - int i = 0; - int b; - while (((b = in.readByte()) & 0x80) != 0) { - value |= (b & 0x7F) << i; - i += 7; - if (i > 35) { - throw new IllegalArgumentException("Variable length quantity is too long"); - } - } - return value | (b << i); - } - - public static int readSignedVarInt(byte[] bytes) { - int raw = readUnsignedVarInt(bytes); - // This undoes the trick in writeSignedVarInt() - int temp = (((raw << 31) >> 31) ^ raw) >> 1; - // This extra step lets us deal with the largest signed values by treating - // negative results from read unsigned methods as like unsigned values. - // Must re-flip the top bit if the original read value had it set. - return temp ^ (raw & (1 << 31)); - } - - public static int readUnsignedVarInt(byte[] bytes) { - int value = 0; - int i = 0; - byte rb = Byte.MIN_VALUE; - for (byte b : bytes) { - rb = b; - if ((b & 0x80) == 0) { - break; - } - value |= (b & 0x7f) << i; - i += 7; - if (i > 35) { - throw new IllegalArgumentException("Variable length quantity is too long"); - } - } - return value | (rb << i); - } - -} diff --git a/src/main/java/com/volmit/adapt/util/data/WeightedRandom.java b/src/main/java/com/volmit/adapt/util/data/WeightedRandom.java deleted file mode 100644 index cdc73fc55..000000000 --- a/src/main/java/com/volmit/adapt/util/data/WeightedRandom.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -//import com.volmit.react.util.collection.KList; -//import com.volmit.react.util.collection.KeyPair; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.collection.KeyPair; - -import java.util.Random; - -public class WeightedRandom { - - private final KList> weightedObjects = new KList<>(); - private final Random random; - private int totalWeight = 0; - - public WeightedRandom(Random random) { - this.random = random; - } - - public WeightedRandom() { - this.random = new Random(); - } - - public void put(T object, int weight) { - weightedObjects.add(new KeyPair<>(object, weight)); - totalWeight += weight; - } - - public T pullRandom() { - int pull = random.nextInt(totalWeight); - int index = 0; - while (pull > 0) { - pull -= weightedObjects.get(index).getV(); - index++; - } - return weightedObjects.get(index).getK(); - } - - public int getSize() { - return weightedObjects.size(); - } - - public void shuffle() { - weightedObjects.shuffle(random); - } -} diff --git a/src/main/java/com/volmit/adapt/util/data/Writable.java b/src/main/java/com/volmit/adapt/util/data/Writable.java deleted file mode 100644 index a8473362d..000000000 --- a/src/main/java/com/volmit/adapt/util/data/Writable.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.data; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -public interface Writable { - void write(DataOutputStream o) throws IOException; - - void read(DataInputStream i) throws IOException; -} diff --git a/src/main/java/com/volmit/adapt/util/decree/DecreeContext.java b/src/main/java/com/volmit/adapt/util/decree/DecreeContext.java deleted file mode 100644 index bea9eaa0b..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/DecreeContext.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree; - -import art.arcane.chrono.ChronoLatch; -import com.volmit.adapt.util.VolmitSender; -import com.volmit.adapt.util.collection.KMap; - -public class DecreeContext { - private static final ChronoLatch cl = new ChronoLatch(60000); - private static final KMap context = new KMap<>(); - - public static VolmitSender get() { - return context.get(Thread.currentThread()); - } - - public static void touch(VolmitSender c) { - synchronized (context) { - context.put(Thread.currentThread(), c); - - if (cl.flip()) { - for (Thread i : context.k()) { - if (!i.isAlive()) { - context.remove(i); - } - } - } - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/DecreeContextHandler.java b/src/main/java/com/volmit/adapt/util/decree/DecreeContextHandler.java deleted file mode 100644 index 6d83bfa05..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/DecreeContextHandler.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.VolmitSender; - -import java.util.HashMap; -import java.util.Map; - -public interface DecreeContextHandler { - Map, DecreeContextHandler> contextHandlers = buildContextHandlers(); - - static Map, DecreeContextHandler> buildContextHandlers() { - Map, DecreeContextHandler> contextHandlers = new HashMap<>(); - - try { - Adapt.initialize("com.volmit.adapt.util.decree.context").forEach((i) - -> contextHandlers.put(((DecreeContextHandler) i).getType(), (DecreeContextHandler) i)); - } catch (Throwable e) { - e.printStackTrace(); - } - - return contextHandlers; - } - - Class getType(); - - T handle(VolmitSender sender); -} diff --git a/src/main/java/com/volmit/adapt/util/decree/DecreeExecutor.java b/src/main/java/com/volmit/adapt/util/decree/DecreeExecutor.java deleted file mode 100644 index 476390a32..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/DecreeExecutor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree; - -import com.volmit.adapt.util.VolmitSender; -import org.bukkit.World; -import org.bukkit.entity.Player; - -public interface DecreeExecutor { - default VolmitSender sender() { - return DecreeContext.get(); - } - - default Player player() { - return sender().player(); - } - - default World world() { - if (sender().isPlayer()) { - return sender().player().getWorld(); - } - return null; - } - - default T get(T v, T ifUndefined) { - return v == null ? ifUndefined : v; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/DecreeNode.java b/src/main/java/com/volmit/adapt/util/decree/DecreeNode.java deleted file mode 100644 index cb51755b3..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/DecreeNode.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.annotations.Decree; -import com.volmit.adapt.util.decree.annotations.Param; -import lombok.Data; - -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.List; - -@Data -public class DecreeNode { - private final Method method; - private final Object instance; - private final Decree decree; - - public DecreeNode(Object instance, Method method) { - this.instance = instance; - this.method = method; - this.decree = method.getDeclaredAnnotation(Decree.class); - if (decree == null) { - throw new RuntimeException("Cannot instantiate DecreeNode on method " + method.getName() + " in " + method.getDeclaringClass().getCanonicalName() + " not annotated by @Decree"); - } - } - - /** - * Get the parameters of this decree node - * - * @return The list of parameters if ALL are annotated by @{@link Param}, else null - */ - public KList getParameters() { - KList required = new KList<>(); - KList optional = new KList<>(); - - for (Parameter i : method.getParameters()) { - DecreeParameter p = new DecreeParameter(i); - if (p.isRequired()) { - required.add(p); - } else { - optional.add(p); - } - } - - required.addAll(optional); - - return required; - } - - public String getName() { - return decree.name().isEmpty() ? method.getName() : decree.name(); - } - - public DecreeOrigin getOrigin() { - return decree.origin(); - } - - public String getDescription() { - return decree.description().isEmpty() ? Decree.DEFAULT_DESCRIPTION : decree.description(); - } - - public KList getNames() { - KList d = new KList<>(); - d.add(getName()); - - for (String i : decree.aliases()) { - if (i.isEmpty()) { - continue; - } - - d.add(i); - } - - - d.removeDuplicates(); - return d; - } - - public boolean isSync() { - return decree.sync(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/DecreeOrigin.java b/src/main/java/com/volmit/adapt/util/decree/DecreeOrigin.java deleted file mode 100644 index e5143f0fd..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/DecreeOrigin.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree; - -import com.volmit.adapt.util.VolmitSender; - -public enum DecreeOrigin { - PLAYER, - CONSOLE, - /** - * Both the player and the console - */ - BOTH; - - /** - * Check if the origin is valid for a sender - * - * @param sender The sender to check - * @return True if valid for origin - */ - public boolean validFor(VolmitSender sender) { - if (sender.isPlayer()) { - return this.equals(PLAYER) || this.equals(BOTH); - } else { - return this.equals(CONSOLE) || this.equals(BOTH); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/DecreeParameter.java b/src/main/java/com/volmit/adapt/util/decree/DecreeParameter.java deleted file mode 100644 index 80ddf7d53..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/DecreeParameter.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree; - -import com.volmit.adapt.util.cache.AtomicCache; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.annotations.Param; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import com.volmit.adapt.util.decree.specialhandlers.DummyHandler; -import lombok.Data; - -import java.lang.reflect.Parameter; - -@Data -public class DecreeParameter { - private final Parameter parameter; - private final Param param; - private transient final AtomicCache> handlerCache = new AtomicCache<>(); - - public DecreeParameter(Parameter parameter) { - this.parameter = parameter; - this.param = parameter.getDeclaredAnnotation(Param.class); - if (param == null) { - throw new RuntimeException("Cannot instantiate DecreeParameter on " + parameter.getName() + " in method " + parameter.getDeclaringExecutable().getName() + "(...) in class " + parameter.getDeclaringExecutable().getDeclaringClass().getCanonicalName() + " not annotated by @Param"); - } - } - - public DecreeParameterHandler getHandler() { - return handlerCache.aquire(() -> { - try { - if (param.customHandler().equals(DummyHandler.class)) { - return DecreeSystem.getHandler(getType()); - } - - return param.customHandler().getConstructor().newInstance(); - } catch (Throwable e) { - e.printStackTrace(); - } - - return null; - }); - } - - public Class getType() { - return parameter.getType(); - } - - public String getName() { - return param.name().isEmpty() ? parameter.getName() : param.name(); - } - - public String getDescription() { - return param.description().isEmpty() ? Param.DEFAULT_DESCRIPTION : param.description(); - } - - public boolean isRequired() { - return !hasDefault(); - } - - public KList getNames() { - KList d = new KList<>(); - - for (String i : param.aliases()) { - if (i.isEmpty()) { - continue; - } - - d.add(i); - } - - d.add(getName()); - d.removeDuplicates(); - - return d; - } - - public Object getDefaultValue() throws DecreeParsingException { - return param.defaultValue().trim().isEmpty() ? null : getHandler().parse(param.defaultValue().trim(), true); - } - - public boolean hasDefault() { - return !param.defaultValue().trim().isEmpty(); - } - - public String example() { - KList ff = getHandler().getPossibilities(); - ff = ff != null ? ff : new KList<>(); - KList f = ff.kConvert((i) -> getHandler().toStringForce(i)); - if (f.isEmpty()) { - f = new KList<>(); - f.add(getHandler().getRandomDefault()); - } - - return f.getRandom(); - } - - public boolean isContextual() { - return param.contextual(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/DecreeParameterHandler.java b/src/main/java/com/volmit/adapt/util/decree/DecreeParameterHandler.java deleted file mode 100644 index 2640c358a..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/DecreeParameterHandler.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public interface DecreeParameterHandler { - /** - * Should return the possible values for this type - * - * @return Possibilities for this type. - */ - KList getPossibilities(); - - default boolean isDummy() { - return false; - } - - /** - * Converting the type back to a string (inverse of the {@link #parse(String) parse} method) - * - * @param t The input of the designated type to convert to a String - * @return The resulting string - */ - String toString(T t); - - /** - * Forces conversion to the designated type before converting to a string using {@link #toString(T t)} - * - * @param t The object to convert to string (that should be of this type) - * @return The resulting string. - */ - default String toStringForce(Object t) { - return toString((T) t); - } - - /** - * Should parse a String into the designated type - * - * @param in The string to parse - * @return The value extracted from the string, of the designated type - * @throws DecreeParsingException Thrown when the parsing fails (ex: "oop" translated to an integer throws this) - */ - default T parse(String in) throws DecreeParsingException { - return parse(in, false); - } - - /** - * Should parse a String into the designated type. You can force it to not throw a whichexception - * - * @param in The string to parse - * @param force force an option instead of throwing decreewhich - * @return The value extracted from the string, of the designated type - * @throws DecreeParsingException Thrown when the parsing fails (ex: "oop" translated to an integer throws this) - */ - T parse(String in, boolean force) throws DecreeParsingException; - - /** - * Returns whether a certain type is supported by this handler
- * - * @param type The type to check - * @return True if supported, false if not - */ - boolean supports(Class type); - - /** - * The possible entries for the inputted string (support for autocomplete on partial entries) - * - * @param input The inputted string to check against - * @return A {@link List} of possibilities - */ - default KList getPossibilities(String input) { - if (input.trim().isEmpty()) { - KList f = getPossibilities(); - return f == null ? new KList<>() : f; - } - - input = input.trim(); - KList possible = getPossibilities(); - KList matches = new KList<>(); - - if (possible == null || possible.isEmpty()) { - return matches; - } - - if (input.isEmpty()) { - return getPossibilities(); - } - - KList converted = possible.kConvert(v -> toString(v).trim()); - - for (int i = 0; i < converted.size(); i++) { - String g = converted.get(i); - // if - // G == I or - // I in G or - // G in I - if (g.equalsIgnoreCase(input) || g.toLowerCase().contains(input.toLowerCase()) || input.toLowerCase().contains(g.toLowerCase())) { - matches.add(possible.get(i)); - } - } - - return matches; - } - - default String getRandomDefault() { - return "NOEXAMPLE"; - } - - default double getMultiplier(AtomicReference g) { - double multiplier = 1; - String in = g.get(); - boolean valid = true; - while (valid) { - boolean trim = false; - if (in.toLowerCase().endsWith("k")) { - multiplier *= 1000; - trim = true; - } else if (in.toLowerCase().endsWith("m")) { - multiplier *= 1000000; - trim = true; - } else if (in.toLowerCase().endsWith("h")) { - multiplier *= 100; - trim = true; - } else if (in.toLowerCase().endsWith("c")) { - multiplier *= 16; - trim = true; - } else if (in.toLowerCase().endsWith("r")) { - multiplier *= (16 * 32); - trim = true; - } else { - valid = false; - } - - if (trim) { - in = in.substring(0, in.length() - 1); - } - } - - g.set(in); - return multiplier; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/DecreeSystem.java b/src/main/java/com/volmit/adapt/util/decree/DecreeSystem.java deleted file mode 100644 index 5acab1533..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/DecreeSystem.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.*; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.virtual.VirtualDecreeCommand; -import org.bukkit.Sound; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -public interface DecreeSystem extends CommandExecutor, TabCompleter { - KList> handlers = Adapt.initialize("com.volmit.adapt.util.decree.handlers", null).kConvert((i) -> (DecreeParameterHandler) i); - - static KList enhanceArgs(String[] args) { - return enhanceArgs(args, true); - } - - static KList enhanceArgs(String[] args, boolean trim) { - KList a = new KList<>(); - - if (args.length == 0) { - return a; - } - - StringBuilder flat = new StringBuilder(); - for (String i : args) { - if (trim) { - if (i.trim().isEmpty()) { - continue; - } - - flat.append(" ").append(i.trim()); - } else { - if (i.endsWith(" ")) { - flat.append(" ").append(i.trim()).append(" "); - } - } - } - - flat = new StringBuilder(flat.length() > 0 ? trim ? flat.toString().trim().length() > 0 ? flat.substring(1).trim() : flat.toString().trim() : flat.substring(1) : flat); - StringBuilder arg = new StringBuilder(); - boolean quoting = false; - - for (int x = 0; x < flat.length(); x++) { - char i = flat.charAt(x); - char j = x < flat.length() - 1 ? flat.charAt(x + 1) : i; - boolean hasNext = x < flat.length(); - - if (i == ' ' && !quoting) { - if (!arg.toString().trim().isEmpty() && trim) { - a.add(arg.toString().trim()); - arg = new StringBuilder(); - } - } else if (i == '"') { - if (!quoting && (arg.length() == 0)) { - quoting = true; - } else if (quoting) { - quoting = false; - - if (hasNext && j == ' ') { - if (!arg.toString().trim().isEmpty() && trim) { - a.add(arg.toString().trim()); - arg = new StringBuilder(); - } - } else if (!hasNext) { - if (!arg.toString().trim().isEmpty() && trim) { - a.add(arg.toString().trim()); - arg = new StringBuilder(); - } - } - } - } else { - arg.append(i); - } - } - - if (!arg.toString().trim().isEmpty() && trim) { - a.add(arg.toString().trim()); - } - - return a; - } - - /** - * Get the handler for the specified type - * - * @param type The type to handle - * @return The corresponding {@link DecreeParameterHandler}, or null - */ - static DecreeParameterHandler getHandler(Class type) { - for (DecreeParameterHandler i : handlers) { - if (i.supports(type)) { - return i; - } - } - Adapt.error("Unhandled type in Decree Parameter: " + type.getName() + ". This is bad!"); - return null; - } - - /** - * The root class to start command searching from - */ - VirtualDecreeCommand getRoot(); - - default boolean call(VolmitSender sender, String[] args) { - DecreeContext.touch(sender); - return getRoot().invoke(sender, enhanceArgs(args)); - } - - @Nullable - @Override - default List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { - Adapt.verbose("Received Tab Complete from %s for %s".formatted(sender.getName(), "/" + alias + String.join(" ", args))); - KList enhanced = new KList<>(args); - KList v = getRoot().tabComplete(enhanced, enhanced.toString(" ")); - v.removeDuplicates(); - - if (sender instanceof Player p) { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f)); - } - - return v; - } - - @Override - default boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - Adapt.verbose("Received Command from %s: /%s".formatted(sender.getName(), label + String.join(" ", args))); - if (!sender.hasPermission("adapt.main")) { - sender.sendMessage("You lack the Permission 'adapt.main'"); - return true; - } - - if (!call(new VolmitSender(sender), args)) { - if (sender instanceof Player p) { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_DEPLETE, 0.77f, 0.25f); - sp.play(p.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.2f, 0.45f); - } - - sender.sendMessage(C.RED + "Unknown Adapt Command"); - } else { - if (sender instanceof Player p) { - SoundPlayer sp = SoundPlayer.of(p); - sp.play(p.getLocation(), Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 1.65f); - sp.play(p.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.125f, 2.99f); - } - } - return true; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/annotations/Decree.java b/src/main/java/com/volmit/adapt/util/decree/annotations/Decree.java deleted file mode 100644 index 932bf597b..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/annotations/Decree.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.annotations; - -import com.volmit.adapt.util.decree.DecreeOrigin; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface Decree { - - String DEFAULT_DESCRIPTION = "No Description Provided"; - - /** - * The name of this command, which is the Method's name by default - */ - String name() default ""; - - /** - * If the node's functions MUST be run in sync, set this to true.
- * Defaults to false - */ - boolean sync() default false; - - /** - * The description of this command.
- * Is {@link #DEFAULT_DESCRIPTION} by default - */ - String description() default DEFAULT_DESCRIPTION; - - /** - * The origin this command must come from.
- * Must be elements of the {@link DecreeOrigin} enum
- * By default, is {@link DecreeOrigin#BOTH}, meaning both console & player can send the command - */ - DecreeOrigin origin() default DecreeOrigin.BOTH; - - /** - * The aliases of this parameter (instead of just the {@link #name() name} (if specified) or Method Name (name of - * method))
- * Can be initialized as just a string (ex. "alias") or as an array (ex. {"alias1", "alias2"})
- * If someone uses /plugin foo and you specify alias="f" here, /plugin f will do the exact same. - */ - String[] aliases() default ""; -} diff --git a/src/main/java/com/volmit/adapt/util/decree/annotations/Param.java b/src/main/java/com/volmit/adapt/util/decree/annotations/Param.java deleted file mode 100644 index c3a5d6557..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/annotations/Param.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.annotations; - -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.specialhandlers.DummyHandler; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.PARAMETER) -public @interface Param { - String DEFAULT_DESCRIPTION = "No Description Provided"; - - /** - * The main name of this command.
- * Required parameter.
- * This is what is used in game, alongside any (if specified) {@link #aliases() aliases} - */ - String name() default ""; - - /** - * The description of this parameter, used in help-popups in game.
- * The default value is {@link #DEFAULT_DESCRIPTION} - */ - String description() default DEFAULT_DESCRIPTION; - - /** - * The default value for this argument.
- * The entered string is parsed to the value similarly to how commandline-text would be.
- * Which indicates the variable MUST be defined by the person running the command.
- * If you define this, the variable automatically becomes non-required, but can still be set. - */ - String defaultValue() default ""; - - /** - * The aliases of this parameter (instead of just the {@link #name() name} (if specified) or Method Name (name of - * method))
- * Can be initialized as just a string (ex. "alias") or as an array (ex. {"alias1", "alias2"})
- * If someone uses /plugin foo bar=baz and you specify alias="b" here, /plugin foo b=baz will do the exact same. - */ - String[] aliases() default ""; - - /** - * Attempts to dynamically pull context from the player, default data or something else for supported types - */ - boolean contextual() default false; - - Class> customHandler() default DummyHandler.class; -} diff --git a/src/main/java/com/volmit/adapt/util/decree/context/AdaptationListingHandler.java b/src/main/java/com/volmit/adapt/util/decree/context/AdaptationListingHandler.java deleted file mode 100644 index ba286bedf..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/context/AdaptationListingHandler.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.volmit.adapt.util.decree.context; - -import com.volmit.adapt.api.adaptation.Adaptation; -import com.volmit.adapt.api.skill.Skill; -import com.volmit.adapt.api.skill.SkillRegistry; -import com.volmit.adapt.util.collection.KList; - -public class AdaptationListingHandler { - - private static KList adaptationLists; - private static KList adaptationSkillLists; - private static KList adaptationProviders; - private static KList skillProviders; - - - public static void initializeAdaptationListings() { - adaptationLists = new KList<>(); - adaptationSkillLists = new KList<>(); - adaptationProviders = new KList<>(); - skillProviders = new KList<>(); - - getAdaptionListings(); - getAdaptionSkillListings(); - getAdaptationProviders(); - getSkillProvider(); - } - - public static KList getAdaptionListings() { - if (adaptationLists.isNotEmpty()) return adaptationLists; - - AdaptationList main = new AdaptationList("[Main]"); - adaptationLists.add(main); - - for (Skill skill : SkillRegistry.skills.sortV()) { - if (!skill.isEnabled()) { - continue; - } - AdaptationList skillList = new AdaptationList("[Skill]-" + skill.getName()); - adaptationLists.add(skillList); - - for (Adaptation adaptation : skill.getAdaptations()) { - if (!adaptation.isEnabled()) { - continue; - } - AdaptationList adaptationList = new AdaptationList("[Adaptation]-" + adaptation.getName()); - adaptationLists.add(adaptationList); - } - } - return adaptationLists; - } - - public static KList getAdaptionSkillListings() { - if (adaptationSkillLists.isNotEmpty()) return adaptationSkillLists; - - AdaptationSkillList t1 = new AdaptationSkillList("[all]"); - adaptationSkillLists.add(t1); - AdaptationSkillList t2 = new AdaptationSkillList("[random]"); - adaptationSkillLists.add(t2); - for (Skill skill : SkillRegistry.skills.sortV()) { - if (!skill.isEnabled()) { - continue; - } - AdaptationSkillList t3 = new AdaptationSkillList(skill.getName()); - adaptationSkillLists.add(t3); - } - return adaptationSkillLists; - } - - public static KList getAdaptationProviders() { - if (adaptationProviders.isNotEmpty()) return adaptationProviders; - - for (Skill skill : SkillRegistry.skills.sortV()) { - if (!skill.isEnabled()) { - continue; - } - for (Adaptation adaptation : skill.getAdaptations()) { - if (!adaptation.isEnabled()) { - continue; - } - AdaptationProvider suggestion = new AdaptationProvider(skill.getName() + ":" +adaptation.getName()); - adaptationProviders.add(suggestion); - } - } - return adaptationProviders; - } - - public static KList getSkillProvider() { - if (skillProviders.isNotEmpty()) return skillProviders; - - for (Skill skill : SkillRegistry.skills.sortV()) { - if (!skill.isEnabled()) { - continue; - } - SkillProvider t1 = new SkillProvider(skill.getName()); - skillProviders.add(t1); - } - return skillProviders; - } - - public record AdaptationList(String name) { - public boolean startsWith(String prefix) { - return name.startsWith(prefix); - } - - public boolean equals(String prefix) { - return name.equalsIgnoreCase(prefix); - } - } - - public record AdaptationSkillList(String name) { - public boolean startsWith(String prefix) { - return name.startsWith(prefix); - } - - public boolean equals(String prefix) { - return name.equalsIgnoreCase(prefix); - } - } - - public record AdaptationProvider(String name) { - public boolean startsWith(String prefix) { - return name.startsWith(prefix); - } - - public boolean equals(String prefix) { - return name.equalsIgnoreCase(prefix); - } - } - - public record SkillProvider(String name) { - public boolean startsWith(String prefix) { - return name.startsWith(prefix); - } - - public boolean equals(String prefix) { - return name.equalsIgnoreCase(prefix); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/context/WorldContextHandler.java b/src/main/java/com/volmit/adapt/util/decree/context/WorldContextHandler.java deleted file mode 100644 index 6826fa0e5..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/context/WorldContextHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.context; - -import com.volmit.adapt.util.VolmitSender; -import com.volmit.adapt.util.decree.DecreeContextHandler; -import org.bukkit.World; - -public class WorldContextHandler implements DecreeContextHandler { - public Class getType() { - return World.class; - } - - public World handle(VolmitSender sender) { - return sender.isPlayer() ? sender.player().getWorld() : null; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/exceptions/DecreeParsingException.java b/src/main/java/com/volmit/adapt/util/decree/exceptions/DecreeParsingException.java deleted file mode 100644 index 9ec0cde32..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/exceptions/DecreeParsingException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.exceptions; - -/** - * Thrown when a decree parameter is parsed, but parsing fails - */ -public class DecreeParsingException extends Exception { - public DecreeParsingException(String message) { - super(message); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationListHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationListHandler.java deleted file mode 100644 index 33ff7d46b..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationListHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.context.AdaptationListingHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -public class AdaptationListHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return AdaptationListingHandler.getAdaptionListings(); - } - - @Override - public String toString(AdaptationListingHandler.AdaptationList adaptationList) { - return adaptationList.name(); - } - - @Override - public AdaptationListingHandler.AdaptationList parse(String in, boolean force) throws DecreeParsingException { - return new AdaptationListingHandler.AdaptationList(in); - } - - @Override - public boolean supports(Class type) { - return type.equals(AdaptationListingHandler.AdaptationList.class); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationProviderHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationProviderHandler.java deleted file mode 100644 index 975ebea9f..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationProviderHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.context.AdaptationListingHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -public class AdaptationProviderHandler implements DecreeParameterHandler { - - @Override - public KList getPossibilities() { - return AdaptationListingHandler.getAdaptationProviders(); - } - - @Override - public String toString(AdaptationListingHandler.AdaptationProvider adaptationProvider) { - return adaptationProvider.name(); - } - - @Override - public AdaptationListingHandler.AdaptationProvider parse(String in, boolean force) throws DecreeParsingException { - return new AdaptationListingHandler.AdaptationProvider(in); - } - - @Override - public boolean supports(Class type) { - return type.equals(AdaptationListingHandler.AdaptationProvider.class); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationSkillListHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationSkillListHandler.java deleted file mode 100644 index 08ca99949..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/AdaptationSkillListHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.context.AdaptationListingHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -public class AdaptationSkillListHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return AdaptationListingHandler.getAdaptionSkillListings(); - } - - @Override - public String toString(AdaptationListingHandler.AdaptationSkillList adaptationSkillList) { - return adaptationSkillList.name(); - } - - @Override - public AdaptationListingHandler.AdaptationSkillList parse(String in, boolean force) throws DecreeParsingException { - return new AdaptationListingHandler.AdaptationSkillList(in); - } - - @Override - public boolean supports(Class type) { - return type.equals(AdaptationListingHandler.AdaptationSkillList.class); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/BlockVectorHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/BlockVectorHandler.java deleted file mode 100644 index d1d573eb5..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/BlockVectorHandler.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.VolmitSender; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeContext; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.DecreeSystem; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import org.bukkit.FluidCollisionMode; -import org.bukkit.entity.Player; -import org.bukkit.util.BlockVector; - -public class BlockVectorHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KList vx = new KList<>(); - VolmitSender s = DecreeContext.get(); - - if (s.isPlayer()) { - vx.add(s.player().getLocation().toVector().toBlockVector()); - } - - return vx; - } - - @Override - public String toString(BlockVector v) { - if (v.getY() == 0) { - return Form.f(v.getBlockX(), 2) + "," + Form.f(v.getBlockZ(), 2); - } - - return Form.f(v.getBlockX(), 2) + "," + Form.f(v.getBlockY(), 2) + "," + Form.f(v.getBlockZ(), 2); - } - - @Override - public BlockVector parse(String in, boolean force) throws DecreeParsingException { - try { - if (in.contains(",")) { - String[] comp = in.split("\\Q,\\E"); - - if (comp.length == 2) { - return new BlockVector(Integer.parseInt(comp[0].trim()), 0, Integer.parseInt(comp[1].trim())); - } else if (comp.length == 3) { - return new BlockVector(Integer.parseInt(comp[0].trim()), - Integer.parseInt(comp[1].trim()), - Integer.parseInt(comp[2].trim())); - } else { - throw new DecreeParsingException("Could not parse components for vector. You have " + comp.length + " components. Expected 2 or 3."); - } - } else if (in.equalsIgnoreCase("here") || in.equalsIgnoreCase("me") || in.equalsIgnoreCase("self")) { - if (!DecreeContext.get().isPlayer()) { - throw new DecreeParsingException("You cannot specify me,self,here as a console."); - } - - return DecreeContext.get().player().getLocation().toVector().toBlockVector(); - } else if (in.equalsIgnoreCase("look") || in.equalsIgnoreCase("cursor") || in.equalsIgnoreCase("crosshair")) { - if (!DecreeContext.get().isPlayer()) { - throw new DecreeParsingException("You cannot specify look,cursor,crosshair as a console."); - } - - return DecreeContext.get().player().getTargetBlockExact(256, FluidCollisionMode.NEVER).getLocation().toVector().toBlockVector(); - } else if (in.trim().toLowerCase().startsWith("player:")) { - String v = in.trim().split("\\Q:\\E")[1]; - - - KList px = DecreeSystem.getHandler(Player.class).getPossibilities(v); - - if (px != null && px.isNotEmpty()) { - return ((Player) px.get(0)).getLocation().toVector().toBlockVector(); - } else if (px == null || px.isEmpty()) { - throw new DecreeParsingException("Cannot find player: " + v); - } - } - } catch (Throwable e) { - throw new DecreeParsingException("Unable to get Vector for \"" + in + "\" because of an uncaught exception: " + e); - } - - return null; - } - - @Override - public boolean supports(Class type) { - return type.equals(BlockVector.class); - } - - @Override - public String getRandomDefault() { - return M.r(0.5) ? "0,0" : "0,0,0"; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/BooleanHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/BooleanHandler.java deleted file mode 100644 index 4129c12aa..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/BooleanHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.M; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.List; - -public class BooleanHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - @Override - public String toString(Boolean aByte) { - return aByte.toString(); - } - - @Override - public Boolean parse(String in, boolean force) throws DecreeParsingException { - try { - if (in.equals("null") || in.equals("other") || in.equals("flip")) { - return null; - } - return Boolean.parseBoolean(in); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to parse boolean \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Boolean.class) || type.equals(boolean.class); - } - - @Override - public String getRandomDefault() { - return M.r(0.5) + ""; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/ByteHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/ByteHandler.java deleted file mode 100644 index 65b2a2d82..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/ByteHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.RNG; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.List; - -public class ByteHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - @Override - public String toString(Byte aByte) { - return aByte.toString(); - } - - @Override - public Byte parse(String in, boolean force) throws DecreeParsingException { - try { - return Byte.parseByte(in); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to parse byte \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Byte.class) || type.equals(byte.class); - } - - @Override - public String getRandomDefault() { - return RNG.r.i(0, Byte.MAX_VALUE) + ""; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/DoubleHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/DoubleHandler.java deleted file mode 100644 index 0c1accc15..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/DoubleHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.RNG; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public class DoubleHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - @Override - public Double parse(String in, boolean force) throws DecreeParsingException { - try { - AtomicReference r = new AtomicReference<>(in); - double m = getMultiplier(r); - return Double.parseDouble(r.get()) * m; - } catch (Throwable e) { - throw new DecreeParsingException("Unable to parse double \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Double.class) || type.equals(double.class); - } - - @Override - public String toString(Double f) { - return f.toString(); - } - - @Override - public String getRandomDefault() { - return Form.f(RNG.r.d(0, 99.99), 1) + ""; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/FloatHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/FloatHandler.java deleted file mode 100644 index 2b215fcfe..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/FloatHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.RNG; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public class FloatHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - @Override - public Float parse(String in, boolean force) throws DecreeParsingException { - try { - AtomicReference r = new AtomicReference<>(in); - double m = getMultiplier(r); - return (float) (Float.parseFloat(r.get()) * m); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to parse float \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Float.class) || type.equals(float.class); - } - - @Override - public String toString(Float f) { - return f.toString(); - } - - @Override - public String getRandomDefault() { - return Form.f(RNG.r.d(0, 99.99), 1) + ""; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/IntegerHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/IntegerHandler.java deleted file mode 100644 index f051e5d48..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/IntegerHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.RNG; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public class IntegerHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - @Override - public Integer parse(String in, boolean force) throws DecreeParsingException { - try { - AtomicReference r = new AtomicReference<>(in); - double m = getMultiplier(r); - return (int) (Integer.valueOf(r.get()).doubleValue() * m); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to parse integer \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Integer.class) || type.equals(int.class); - } - - @Override - public String toString(Integer f) { - return f.toString(); - } - - @Override - public String getRandomDefault() { - return RNG.r.i(0, 99) + ""; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/LongHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/LongHandler.java deleted file mode 100644 index 5bb163290..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/LongHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.RNG; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public class LongHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - @Override - public Long parse(String in, boolean force) throws DecreeParsingException { - try { - AtomicReference r = new AtomicReference<>(in); - double m = getMultiplier(r); - if (m == 1) - return Long.parseLong(r.get()); - else - return (long) (Long.valueOf(r.get()).doubleValue() * m); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to parse long \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Long.class) || type.equals(long.class); - } - - @Override - public String toString(Long f) { - return f.toString(); - } - - @Override - public String getRandomDefault() { - return RNG.r.i(0, 99) + ""; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/OptionalWorldHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/OptionalWorldHandler.java deleted file mode 100644 index c993fab35..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/OptionalWorldHandler.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import org.bukkit.Bukkit; -import org.bukkit.World; - -import java.util.ArrayList; -import java.util.List; - -public class OptionalWorldHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KList options = new KList<>(); - options.add("ALL"); - for (World world : Bukkit.getWorlds()) { - if (!world.getName().toLowerCase().startsWith("adapt/")) { - options.add(world.getName()); - } - } - return options; - } - - @Override - public String toString(String world) { - return world; - } - - @Override - public String parse(String in, boolean force) throws DecreeParsingException { - return in; - } - - @Override - public boolean supports(Class type) { - return false; - } - - @Override - public String getRandomDefault() { - return "ALL"; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/ParticleHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/ParticleHandler.java deleted file mode 100644 index 7eff87bbf..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/ParticleHandler.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import org.bukkit.Particle; - -public class ParticleHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return new KList<>(Particle.values()); - } - - @Override - public String toString(Particle particle) { - return particle.name(); - } - - @Override - public Particle parse(String in, boolean force) throws DecreeParsingException { - try { - return Particle.valueOf(in); - } catch (IllegalArgumentException e) { - throw new DecreeParsingException("Invalid particle: " + in); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Particle.class); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/PlayerHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/PlayerHandler.java deleted file mode 100644 index 42d448afa..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/PlayerHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -public class PlayerHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - if (Adapt.instance != null && Adapt.instance.getAdaptServer() != null) { - return new KList<>(Adapt.instance.getAdaptServer().getOnlinePlayerSnapshot()); - } - return new KList<>(new ArrayList<>(Bukkit.getOnlinePlayers())); - } - - @Override - public String toString(Player player) { - return player.getName(); - } - - @Override - public Player parse(String in, boolean force) throws DecreeParsingException { - List options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find Player \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Biome \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Player.class); - } - - @Override - public String getRandomDefault() { - return "playername"; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/ShortHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/ShortHandler.java deleted file mode 100644 index f296a7ccf..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/ShortHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.RNG; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; - -public class ShortHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - @Override - public Short parse(String in, boolean force) throws DecreeParsingException { - try { - AtomicReference r = new AtomicReference<>(in); - double m = getMultiplier(r); - return (short) (Short.valueOf(r.get()).doubleValue() * m); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to parse short \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Short.class) || type.equals(short.class); - } - - @Override - public String toString(Short f) { - return f.toString(); - } - - @Override - public String getRandomDefault() { - return RNG.r.i(0, 99) + ""; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/SkillProviderHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/SkillProviderHandler.java deleted file mode 100644 index 31bcc1fb6..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/SkillProviderHandler.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.context.AdaptationListingHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -public class SkillProviderHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return AdaptationListingHandler.getSkillProvider(); - } - - @Override - public String toString(AdaptationListingHandler.SkillProvider skillProvider) { - return skillProvider.name(); - } - - @Override - public AdaptationListingHandler.SkillProvider parse(String in, boolean force) throws DecreeParsingException { - return new AdaptationListingHandler.SkillProvider(in); - } - - @Override - public boolean supports(Class type) { - return type.equals(AdaptationListingHandler.SkillProvider.class); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/SoundHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/SoundHandler.java deleted file mode 100644 index 5a2351336..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/SoundHandler.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import org.bukkit.Sound; - -public class SoundHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return new KList<>(Sound.values()); - } - - @Override - public String toString(Sound sound) { - return sound.name(); - } - - @Override - public Sound parse(String in, boolean force) throws DecreeParsingException { - try { - return Sound.valueOf(in); - } catch (IllegalArgumentException e) { - throw new DecreeParsingException("Invalid sound: " + in); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(Sound.class); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/StringHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/StringHandler.java deleted file mode 100644 index ba34b2c7b..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/StringHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -/** - * Abstraction can sometimes breed stupidity - */ -public class StringHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - @Override - public String toString(String s) { - return s; - } - - @Override - public String parse(String in, boolean force) throws DecreeParsingException { - return in; - } - - @Override - public boolean supports(Class type) { - return type.equals(String.class); - } - - @Override - public String getRandomDefault() { - return new KList().qadd("text").qadd("string") - .qadd("blah").qadd("derp").qadd("yolo").getRandom(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/VectorHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/VectorHandler.java deleted file mode 100644 index 3f7f59096..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/VectorHandler.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.Form; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeContext; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.DecreeSystem; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import org.bukkit.FluidCollisionMode; -import org.bukkit.entity.Player; -import org.bukkit.util.BlockVector; -import org.bukkit.util.Vector; - -public class VectorHandler implements DecreeParameterHandler { - - private static final KList randoms = new KList<>( - "here", - "0,0,0", - "0,0", - "look", - "player:" - ); - - @Override - public KList getPossibilities() { - return null; - } - - @Override - public String toString(Vector v) { - if (v.getY() == 0) { - return Form.f(v.getX(), 2) + "," + Form.f(v.getZ(), 2); - } - - return Form.f(v.getX(), 2) + "," + Form.f(v.getY(), 2) + "," + Form.f(v.getZ(), 2); - } - - @Override - public Vector parse(String in, boolean force) throws DecreeParsingException { - try { - if (in.contains(",")) { - String[] comp = in.split("\\Q,\\E"); - - if (comp.length == 2) { - return new BlockVector(Double.parseDouble(comp[0].trim()), 0, Double.parseDouble(comp[1].trim())); - } else if (comp.length == 3) { - return new BlockVector(Double.parseDouble(comp[0].trim()), - Double.parseDouble(comp[1].trim()), - Double.parseDouble(comp[2].trim())); - } else { - throw new DecreeParsingException("Could not parse components for vector. You have " + comp.length + " components. Expected 2 or 3."); - } - } else if (in.equalsIgnoreCase("here") || in.equalsIgnoreCase("me") || in.equalsIgnoreCase("self")) { - if (!DecreeContext.get().isPlayer()) { - throw new DecreeParsingException("You cannot specify me,self,here as a console."); - } - - return DecreeContext.get().player().getLocation().toVector(); - } else if (in.equalsIgnoreCase("look") || in.equalsIgnoreCase("cursor") || in.equalsIgnoreCase("crosshair")) { - if (!DecreeContext.get().isPlayer()) { - throw new DecreeParsingException("You cannot specify look,cursor,crosshair as a console."); - } - - return DecreeContext.get().player().getTargetBlockExact(256, FluidCollisionMode.NEVER).getLocation().toVector(); - } else if (in.trim().toLowerCase().startsWith("player:")) { - String v = in.trim().split("\\Q:\\E")[1]; - - - KList px = DecreeSystem.getHandler(Player.class).getPossibilities(v); - - if (px != null && px.isNotEmpty()) { - return ((Player) px.get(0)).getLocation().toVector(); - } else if (px == null || px.isEmpty()) { - throw new DecreeParsingException("Cannot find player: " + v); - } - } - } catch (Throwable e) { - throw new DecreeParsingException("Unable to get Vector for \"" + in + "\" because of an uncaught exception: " + e); - } - - return null; - } - - @Override - public boolean supports(Class type) { - return type.equals(Vector.class); - } - - @Override - public String getRandomDefault() { - return randoms.getRandom(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/handlers/WorldHandler.java b/src/main/java/com/volmit/adapt/util/decree/handlers/WorldHandler.java deleted file mode 100644 index c9477f121..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/handlers/WorldHandler.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.handlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import org.bukkit.Bukkit; -import org.bukkit.World; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -public class WorldHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - KList options = new KList<>(); - for (World world : Bukkit.getWorlds()) { - if (!world.getName().toLowerCase().startsWith("adapt/")) { - options.add(world); - } - } - return options; - } - - @Override - public String toString(World world) { - return world.getName(); - } - - @Override - public World parse(String in, boolean force) throws DecreeParsingException { - List options = getPossibilities(in); - - if (options.isEmpty()) { - throw new DecreeParsingException("Unable to find World \"" + in + "\""); - } - try { - return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); - } catch (Throwable e) { - throw new DecreeParsingException("Unable to filter which Biome \"" + in + "\""); - } - } - - @Override - public boolean supports(Class type) { - return type.equals(World.class); - } - - @Override - public String getRandomDefault() { - return "world"; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/specialhandlers/DummyHandler.java b/src/main/java/com/volmit/adapt/util/decree/specialhandlers/DummyHandler.java deleted file mode 100644 index 96a585a2a..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/specialhandlers/DummyHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.specialhandlers; - -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.decree.DecreeParameterHandler; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; - -import java.util.List; - -public class DummyHandler implements DecreeParameterHandler { - @Override - public KList getPossibilities() { - return null; - } - - public boolean isDummy() { - return true; - } - - @Override - public String toString(Object o) { - return null; - } - - @Override - public Object parse(String in, boolean force) throws DecreeParsingException { - return null; - } - - @Override - public boolean supports(Class type) { - return false; - } -} diff --git a/src/main/java/com/volmit/adapt/util/decree/specialhandlers/NullablePlayerHandler.java b/src/main/java/com/volmit/adapt/util/decree/specialhandlers/NullablePlayerHandler.java deleted file mode 100644 index 76b5a56e2..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/specialhandlers/NullablePlayerHandler.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.volmit.adapt.util.decree.specialhandlers; - -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import com.volmit.adapt.util.decree.handlers.PlayerHandler; -import org.bukkit.entity.Player; - -public class NullablePlayerHandler extends PlayerHandler { - - @Override - public Player parse(String in, boolean force) throws DecreeParsingException { - return getPossibilities(in).stream().filter((i) -> toString(i).equalsIgnoreCase(in)).findFirst().orElse(null); - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/decree/virtual/VirtualDecreeCommand.java b/src/main/java/com/volmit/adapt/util/decree/virtual/VirtualDecreeCommand.java deleted file mode 100644 index fbd43ecb3..000000000 --- a/src/main/java/com/volmit/adapt/util/decree/virtual/VirtualDecreeCommand.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.decree.virtual; - - -import art.arcane.amulet.format.Form; -import art.arcane.chrono.ChronoLatch; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.CommandDummy; -import com.volmit.adapt.util.J; -import com.volmit.adapt.util.VolmitSender; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.decree.DecreeContext; -import com.volmit.adapt.util.decree.DecreeContextHandler; -import com.volmit.adapt.util.decree.DecreeNode; -import com.volmit.adapt.util.decree.DecreeParameter; -import com.volmit.adapt.util.decree.annotations.Decree; -import com.volmit.adapt.util.decree.exceptions.DecreeParsingException; -import lombok.Data; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -@Data -public class VirtualDecreeCommand { - private final Class type; - private final VirtualDecreeCommand parent; - private final KList nodes; - private final DecreeNode node; - String[] gradients = new String[]{ - "", - "", - "", - "", - "", - "" - }; - private ChronoLatch cl = new ChronoLatch(1000); - - private VirtualDecreeCommand(Class type, VirtualDecreeCommand parent, KList nodes, DecreeNode node) { - this.parent = parent; - this.type = type; - this.nodes = nodes; - this.node = node; - } - - public static VirtualDecreeCommand createRoot(Object v) throws Throwable { - return createRoot(null, v); - } - - public static VirtualDecreeCommand createRoot(VirtualDecreeCommand parent, Object v) throws Throwable { - VirtualDecreeCommand c = new VirtualDecreeCommand(v.getClass(), parent, new KList<>(), null); - - for (Field i : v.getClass().getDeclaredFields()) { - if (Modifier.isStatic(i.getModifiers()) || Modifier.isFinal(i.getModifiers()) || Modifier.isTransient(i.getModifiers()) || Modifier.isVolatile(i.getModifiers())) { - continue; - } - - if (!i.getType().isAnnotationPresent(Decree.class)) { - continue; - } - - i.setAccessible(true); - Object childRoot = i.get(v); - - if (childRoot == null) { - childRoot = i.getType().getConstructor().newInstance(); - i.set(v, childRoot); - } - - c.getNodes().add(createRoot(c, childRoot)); - } - - for (Method i : v.getClass().getDeclaredMethods()) { - if (Modifier.isStatic(i.getModifiers()) || Modifier.isFinal(i.getModifiers()) || Modifier.isPrivate(i.getModifiers())) { - continue; - } - - if (!i.isAnnotationPresent(Decree.class)) { - continue; - } - - c.getNodes().add(new VirtualDecreeCommand(v.getClass(), c, new KList<>(), new DecreeNode(v, i))); - } - - return c; - } - - public void cacheAll() { - VolmitSender sender = new VolmitSender(new CommandDummy()); - - if (isNode()) { - sender.sendDecreeHelpNode(this); - } - - for (VirtualDecreeCommand j : nodes) { - j.cacheAll(); - } - } - - public String getPath() { - KList n = new KList<>(); - VirtualDecreeCommand cursor = this; - - while (cursor.getParent() != null) { - cursor = cursor.getParent(); - n.add(cursor.getName()); - } - - return "/" + n.reverse().qadd(getName()).toString(" "); - } - - public String getParentPath() { - return getParent().getPath(); - } - - public String getName() { - return isNode() ? getNode().getName() : getType().getDeclaredAnnotation(Decree.class).name(); - } - - public String getDescription() { - return isNode() ? getNode().getDescription() : getType().getDeclaredAnnotation(Decree.class).description(); - } - - public KList getNames() { - if (isNode()) { - return getNode().getNames(); - } - - Decree dc = getType().getDeclaredAnnotation(Decree.class); - KList d = new KList<>(); - d.add(dc.name()); - for (String i : dc.aliases()) { - if (i.isEmpty()) { - continue; - } - - d.add(i); - } - - d.removeDuplicates(); - - return d; - } - - public boolean isNode() { - return node != null; - } - - public KList tabComplete(KList args, String raw) { - KList skip = new KList<>(); - KList tabs = new KList<>(); - invokeTabComplete(args, skip, tabs, raw); - return tabs; - } - - private boolean invokeTabComplete(KList args, KList skip, KList tabs, String raw) { - if (isNode()) { - tab(args, tabs); - skip.add(hashCode()); - return false; - } - - if (args.isEmpty()) { - tab(args, tabs); - return true; - } - - String head = args.get(0); - - if (args.size() > 1 || head.endsWith(" ")) { - VirtualDecreeCommand match = matchNode(head, skip); - - if (match != null) { - args.pop(); - return match.invokeTabComplete(args, skip, tabs, raw); - } - - skip.add(hashCode()); - } else { - tab(args, tabs); - } - - return false; - } - - private void tab(List args, List tabs) { - String last = null; - List ignore = new ArrayList<>(); - Runnable la = () -> { - - }; - for (String a : args) { - la.run(); - last = a; - la = () -> { - if (isNode()) { - String sea = a.contains("=") ? a.split("\\Q=\\E")[0] : a; - sea = sea.trim(); - - searching: - for (DecreeParameter i : getNode().getParameters()) { - for (String m : i.getNames()) { - if (m.equalsIgnoreCase(sea) || m.toLowerCase().contains(sea.toLowerCase()) || sea.toLowerCase().contains(m.toLowerCase())) { - ignore.add(i); - continue searching; - } - } - } - } - }; - } - - if (last != null) { - if (isNode()) { - for (DecreeParameter i : getNode().getParameters()) { - if (ignore.contains(i)) { - continue; - } - - int g = 0; - - if (last.contains("=")) { - String[] vv = last.trim().split("\\Q=\\E"); - String vx = vv.length == 2 ? vv[1] : ""; - for (String f : i.getHandler().getPossibilities(vx).kConvert((v) -> i.getHandler().toStringForce(v))) { - g++; - tabs.add(i.getName() + "=" + f); - } - } else { - for (String f : i.getHandler().getPossibilities("").kConvert((v) -> i.getHandler().toStringForce(v))) { - g++; - tabs.add(i.getName() + "=" + f); - } - } - - if (g == 0) { - tabs.add(i.getName() + "="); - } - } - } else { - for (VirtualDecreeCommand i : getNodes()) { - String m = i.getName(); - if (m.equalsIgnoreCase(last) || m.toLowerCase().contains(last.toLowerCase()) || last.toLowerCase().contains(m.toLowerCase())) { - tabs.addAll(i.getNames()); - } - } - } - } - } - - /** - * Maps the input a player typed to the parameters of this command - * - * @param sender The sender - * @param in The input - * @return A map of all the parameter names and their values - */ - private KMap> map(VolmitSender sender, List in) { - KMap> data = new KMap<>(); - List nowhich = new ArrayList<>(); - - List unknownInputs = new ArrayList<>(in.stream().filter(s -> !s.contains("=")).collect(Collectors.toList())); - List knownInputs = new ArrayList<>(in.stream().filter(s -> s.contains("=")).collect(Collectors.toList())); - - //Loop known inputs - for (int x = 0; x < knownInputs.size(); x++) { - String stringParam = knownInputs.get(x); - int original = in.indexOf(stringParam); - - String[] v = stringParam.split("\\Q=\\E"); - String key = v[0]; - String value = v[1]; - DecreeParameter param = null; - - //Find decree parameter from string param - for (DecreeParameter j : getNode().getParameters()) { - for (String k : j.getNames()) { - if (k.equalsIgnoreCase(key)) { - param = j; - break; - } - } - } - - //If it failed, see if we can find it by checking if the names contain the param - if (param == null) { - for (DecreeParameter j : getNode().getParameters()) { - for (String k : j.getNames()) { - if (k.toLowerCase().contains(key.toLowerCase()) || key.toLowerCase().contains(k.toLowerCase())) { - param = j; - break; - } - } - } - } - - //Still failed to find, error them - if (param == null) { - Adapt.debug("Can't find parameter key for " + key + "=" + value + " in " + getPath()); - sender.sendMessage(C.YELLOW + "Unknown Parameter: " + key); - unknownInputs.add(value); //Add the value to the unknowns and see if we can assume it later - continue; - } - - key = param.getName(); - - try { - data.put(key, Optional.ofNullable(param.getHandler().parse(value, nowhich.contains(original)))); //Parse and put - } catch (DecreeParsingException e) { - Adapt.debug("Can't parse parameter value for " + key + "=" + value + " in " + getPath() + " using handler " + param.getHandler().getClass().getSimpleName()); - sender.sendMessage(C.RED + "Cannot convert \"" + value + "\" into a " + param.getType().getSimpleName()); - e.printStackTrace(); - return null; - } - } - - //Make a list of decree params that haven't been identified - List decreeParameters = new KList<>(getNode().getParameters().stream().filter(param -> !data.contains(param.getName())).collect(Collectors.toList())); - - //Loop Unknown inputs - for (int x = 0; x < unknownInputs.size(); x++) { - String stringParam = unknownInputs.get(x); - int original = in.indexOf(stringParam); - try { - DecreeParameter par = decreeParameters.get(x); - - try { - data.put(par.getName(), Optional.ofNullable(par.getHandler().parse(stringParam, nowhich.contains(original)))); - } catch (DecreeParsingException e) { - Adapt.debug("Can't parse parameter value for " + par.getName() + "=" + stringParam + " in " + getPath() + " using handler " + par.getHandler().getClass().getSimpleName()); - sender.sendMessage(C.RED + "Cannot convert \"" + stringParam + "\" into a " + par.getType().getSimpleName()); - e.printStackTrace(); - return null; - } - } catch (IndexOutOfBoundsException e) { - sender.sendMessage(C.YELLOW + "Unknown Parameter: " + stringParam + " (" + Form.getNumberSuffixThStRd(x + 1) + " argument)"); - } - } - - return data; - } - - public boolean invoke(VolmitSender sender, KList realArgs) { - return invoke(sender, realArgs, new KList<>()); - } - - public boolean invoke(VolmitSender sender, KList args, List skip) { - Adapt.debug("@ " + getPath() + " with " + args.toString(", ")); - if (isNode()) { - Adapt.debug("Invoke " + getPath() + "(" + args.toString(",") + ") at "); - if (invokeNode(sender, map(sender, args))) { - return true; - } - - skip.add(hashCode()); - return false; - } - - if (args.isEmpty()) { - sender.sendDecreeHelp(this); - - return true; - } else if (args.size() == 1) { - for (String i : args) { - if (i.startsWith("help=")) { - sender.sendDecreeHelp(this, Integer.parseInt(i.split("\\Q=\\E")[1]) - 1); - return true; - } - } - } - - String head = args.get(0); - VirtualDecreeCommand match = matchNode(head, skip); - - if (match != null) { - args.pop(); - return match.invoke(sender, args, skip); - } - - skip.add(hashCode()); - - return false; - } - - private boolean invokeNode(VolmitSender sender, KMap> map) { - if (map == null) { - return false; - } - - Object[] params = new Object[getNode().getMethod().getParameterCount()]; - int vm = 0; - for (DecreeParameter i : getNode().getParameters()) { - Object value = map.getOrDefault(i.getName(), Optional.empty()).orElse(null); - - try { - if (value == null && i.hasDefault()) { - value = i.getDefaultValue(); - } - } catch (DecreeParsingException e) { - Adapt.debug("Can't parse parameter value for " + i.getName() + "=" + i.getParam().defaultValue() + " in " + getPath() + " using handler " + i.getHandler().getClass().getSimpleName()); - sender.sendMessage(C.RED + "Cannot convert \"" + i.getParam().defaultValue() + "\" into a " + i.getType().getSimpleName()); - return false; - } - - if (sender.isPlayer() && i.isContextual() && value == null) { - Adapt.debug("Contextual!"); - DecreeContextHandler ch = DecreeContextHandler.contextHandlers.get(i.getType()); - - if (ch != null) { - value = ch.handle(sender); - - if (value != null) { - Adapt.debug("Parameter \"" + i.getName() + "\" derived a value of \"" + i.getHandler().toStringForce(value) + "\" from " + ch.getClass().getSimpleName()); - } else { - Adapt.debug("Parameter \"" + i.getName() + "\" could not derive a value from \"" + ch.getClass().getSimpleName()); - } - } else { - Adapt.debug("Parameter \"" + i.getName() + "\" is contextual but has no context handler for \"" + i.getType().getCanonicalName() + "\""); - } - } - - if (i.hasDefault() && value == null) { - try { - Adapt.debug("Parameter \"" + i.getName() + "\" is using default value \"" + i.getParam().defaultValue() + "\""); - value = i.getDefaultValue(); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - if (i.isRequired() && value == null) { - sender.sendMessage(C.RED + "Missing argument \"" + i.getName() + "\" (" + i.getType().getSimpleName() + ") as the " + Form.getNumberSuffixThStRd(vm + 1) + " argument."); - sender.sendDecreeHelpNode(this); - return false; - } - - params[vm] = value; - vm++; - } - - DecreeContext.touch(sender); - Runnable rx = () -> { - try { - DecreeContext.touch(sender); - getNode().getMethod().setAccessible(true); - getNode().getMethod().invoke(getNode().getInstance(), params); - } catch (Throwable e) { - e.printStackTrace(); - throw new RuntimeException("Failed to execute "); // TODO: - } - }; - - if (getNode().isSync()) { - J.s(rx); - } else { - rx.run(); - } - - return true; - } - - public KList matchAllNodes(String in) { - KList g = new KList<>(); - - if (in.trim().isEmpty()) { - g.addAll(nodes); - return g; - } - - for (VirtualDecreeCommand i : nodes) { - if (i.matches(in)) { - g.add(i); - } - } - - for (VirtualDecreeCommand i : nodes) { - if (i.deepMatches(in)) { - g.add(i); - } - } - - g.removeDuplicates(); - return g; - } - - public VirtualDecreeCommand matchNode(String in, List skip) { - if (in.trim().isEmpty()) { - return null; - } - - for (VirtualDecreeCommand i : nodes) { - if (skip.contains(i.hashCode())) { - continue; - } - - if (i.matches(in)) { - return i; - } - } - - for (VirtualDecreeCommand i : nodes) { - if (skip.contains(i.hashCode())) { - continue; - } - - if (i.deepMatches(in)) { - return i; - } - } - - return null; - } - - public boolean deepMatches(String in) { - List a = getNames(); - - for (String i : a) { - if (i.toLowerCase().contains(in.toLowerCase()) || in.toLowerCase().contains(i.toLowerCase())) { - return true; - } - } - - return false; - } - - @Override - public int hashCode() { - return Objects.hash(getName(), getDescription(), getType(), getPath()); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof VirtualDecreeCommand)) { - return false; - } - return this.hashCode() == obj.hashCode(); - } - - public boolean matches(String in) { - List a = getNames(); - - for (String i : a) { - if (i.equalsIgnoreCase(in)) { - return true; - } - } - - return false; - } -} diff --git a/src/main/java/com/volmit/adapt/util/function/Consumer2.java b/src/main/java/com/volmit/adapt/util/function/Consumer2.java deleted file mode 100644 index 347120785..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Consumer2.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@SuppressWarnings({"hiding", "RedundantSuppression"}) -@FunctionalInterface -public interface Consumer2 { - void accept(A a, B b); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Consumer2IO.java b/src/main/java/com/volmit/adapt/util/function/Consumer2IO.java deleted file mode 100644 index 3f572b7f9..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Consumer2IO.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -import java.io.IOException; - -@SuppressWarnings({"hiding", "RedundantSuppression"}) -@FunctionalInterface -public interface Consumer2IO { - void accept(A a, B b) throws IOException; -} diff --git a/src/main/java/com/volmit/adapt/util/function/Consumer3.java b/src/main/java/com/volmit/adapt/util/function/Consumer3.java deleted file mode 100644 index ca3e69abe..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Consumer3.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@SuppressWarnings("ALL") -@FunctionalInterface -public interface Consumer3 { - void accept(A a, B b, C c); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Consumer4.java b/src/main/java/com/volmit/adapt/util/function/Consumer4.java deleted file mode 100644 index 1c5677cb3..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Consumer4.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface Consumer4 { - void accept(A a, B b, C c, D d); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Consumer4IO.java b/src/main/java/com/volmit/adapt/util/function/Consumer4IO.java deleted file mode 100644 index eb861681e..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Consumer4IO.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -import java.io.IOException; - -@FunctionalInterface -public interface Consumer4IO { - void accept(A a, B b, C c, D d) throws IOException; -} diff --git a/src/main/java/com/volmit/adapt/util/function/Consumer5.java b/src/main/java/com/volmit/adapt/util/function/Consumer5.java deleted file mode 100644 index 485d32582..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Consumer5.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface Consumer5 { - void accept(A a, B b, C c, D d, E e); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Consumer6.java b/src/main/java/com/volmit/adapt/util/function/Consumer6.java deleted file mode 100644 index 1fc570c1c..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Consumer6.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface Consumer6 { - void accept(A a, B b, C c, D d, E e, F f); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Consumer8.java b/src/main/java/com/volmit/adapt/util/function/Consumer8.java deleted file mode 100644 index 3f31cee03..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Consumer8.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface Consumer8 { - void accept(A a, B b, C c, D d, E e, F f, G g, H h); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Function2.java b/src/main/java/com/volmit/adapt/util/function/Function2.java deleted file mode 100644 index d7f4cfa83..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Function2.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface Function2 { - R apply(A a, B b); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Function3.java b/src/main/java/com/volmit/adapt/util/function/Function3.java deleted file mode 100644 index ca2aa0bc4..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Function3.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface Function3 { - R apply(A a, B b, C c); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Function4.java b/src/main/java/com/volmit/adapt/util/function/Function4.java deleted file mode 100644 index 5786e56e2..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Function4.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface Function4 { - R apply(A a, B b, C c, D d); -} diff --git a/src/main/java/com/volmit/adapt/util/function/NastyFunction.java b/src/main/java/com/volmit/adapt/util/function/NastyFunction.java deleted file mode 100644 index 94d0e864d..000000000 --- a/src/main/java/com/volmit/adapt/util/function/NastyFunction.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -public interface NastyFunction { - R run(T t); -} diff --git a/src/main/java/com/volmit/adapt/util/function/NastyFuture.java b/src/main/java/com/volmit/adapt/util/function/NastyFuture.java deleted file mode 100644 index d16030386..000000000 --- a/src/main/java/com/volmit/adapt/util/function/NastyFuture.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -public interface NastyFuture { - R run(); -} diff --git a/src/main/java/com/volmit/adapt/util/function/NastyRunnable.java b/src/main/java/com/volmit/adapt/util/function/NastyRunnable.java deleted file mode 100644 index 17753fc17..000000000 --- a/src/main/java/com/volmit/adapt/util/function/NastyRunnable.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -public interface NastyRunnable { - void run() throws Throwable; -} diff --git a/src/main/java/com/volmit/adapt/util/function/NastySupplier.java b/src/main/java/com/volmit/adapt/util/function/NastySupplier.java deleted file mode 100644 index 02180b037..000000000 --- a/src/main/java/com/volmit/adapt/util/function/NastySupplier.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -public interface NastySupplier { - T get() throws Throwable; -} diff --git a/src/main/java/com/volmit/adapt/util/function/NoiseInjector.java b/src/main/java/com/volmit/adapt/util/function/NoiseInjector.java deleted file mode 100644 index de4e15894..000000000 --- a/src/main/java/com/volmit/adapt/util/function/NoiseInjector.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface NoiseInjector { - double[] combine(double src, double value); -} diff --git a/src/main/java/com/volmit/adapt/util/function/NoiseProvider.java b/src/main/java/com/volmit/adapt/util/function/NoiseProvider.java deleted file mode 100644 index bed4bbc89..000000000 --- a/src/main/java/com/volmit/adapt/util/function/NoiseProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface NoiseProvider { - double noise(double x, double z); -} diff --git a/src/main/java/com/volmit/adapt/util/function/NoiseProvider3.java b/src/main/java/com/volmit/adapt/util/function/NoiseProvider3.java deleted file mode 100644 index f3d561cd9..000000000 --- a/src/main/java/com/volmit/adapt/util/function/NoiseProvider3.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -@FunctionalInterface -public interface NoiseProvider3 { - double noise(double x, double y, double z); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Supplier2.java b/src/main/java/com/volmit/adapt/util/function/Supplier2.java deleted file mode 100644 index d0ebe27d8..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Supplier2.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -public interface Supplier2 { - void get(T t, TT tt); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Supplier3.java b/src/main/java/com/volmit/adapt/util/function/Supplier3.java deleted file mode 100644 index d69c85ad8..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Supplier3.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -public interface Supplier3 { - void get(T t, TT tt, TTT ttt); -} diff --git a/src/main/java/com/volmit/adapt/util/function/Supplier3R.java b/src/main/java/com/volmit/adapt/util/function/Supplier3R.java deleted file mode 100644 index f05df6dce..000000000 --- a/src/main/java/com/volmit/adapt/util/function/Supplier3R.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2016-2025 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - */ - -package com.volmit.adapt.util.function; - -public interface Supplier3R { - TTTT get(T t, TT tt, TTT ttt); -} diff --git a/src/main/java/com/volmit/adapt/util/redis/RedisSync.java b/src/main/java/com/volmit/adapt/util/redis/RedisSync.java deleted file mode 100644 index 133d80c2d..000000000 --- a/src/main/java/com/volmit/adapt/util/redis/RedisSync.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.volmit.adapt.util.redis; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.volmit.adapt.Adapt; -import com.volmit.adapt.AdaptConfig; -import com.volmit.adapt.api.world.PlayerData; -import com.volmit.adapt.util.redis.codec.Codec; -import com.volmit.adapt.util.redis.codec.DataMessage; -import com.volmit.adapt.util.redis.codec.DataRequest; -import com.volmit.adapt.util.redis.codec.Message; -import io.lettuce.core.RedisClient; -import io.lettuce.core.pubsub.api.reactive.ChannelMessage; -import io.lettuce.core.pubsub.api.reactive.RedisPubSubReactiveCommands; -import lombok.NonNull; -import lombok.extern.java.Log; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -@Log -public class RedisSync implements AutoCloseable { - private final RedisClient redisClient; - private final RedisPubSubReactiveCommands pubSub; - private final Cache dataCache = Caffeine.newBuilder() - .expireAfterWrite(1, TimeUnit.MINUTES) - .build(); - - public RedisSync() { - if (!AdaptConfig.get().isUseRedis() || !AdaptConfig.get().isUseSql()) { - this.redisClient = null; - this.pubSub = null; - return; - } - - this.redisClient = AdaptConfig.get().getRedis().createClient(); - this.pubSub = redisClient.connectPubSub(Codec.INSTANCE).reactive(); - pubSub.subscribe("Adapt:data").subscribe(); - pubSub.observeChannels().doOnNext(this::update).subscribe(); - } - - private void update(@NotNull ChannelMessage<@NotNull String, @Nullable Message> channelMessage) { - if (!channelMessage.getChannel().equals("Adapt:data")) return; - Message raw = channelMessage.getMessage(); - if (raw instanceof DataMessage message) { - Adapt.verbose("Received player data for " + message.uuid()); - dataCache.put(message.uuid(), message.json()); - } else if (raw instanceof DataRequest message) { - Adapt.instance.getAdaptServer() - .getPlayerData(message.uuid()) - .map(data -> data.toJson(false)) - .ifPresent(data -> publish(message.uuid(), data)); - } - } - - public void publish(@NonNull UUID uuid, @NonNull String playerData) { - if (pubSub == null) return; - Adapt.verbose("Publishing player data for " + uuid); - pubSub.publish("Adapt:data", new DataMessage(uuid, playerData)) - .subscribe() - .dispose(); - } - - @NonNull - public Optional cachedData(@NonNull UUID uuid) { - if (pubSub == null) return Optional.empty(); - return Optional.ofNullable(dataCache.getIfPresent(uuid)) - .map(PlayerData::fromJson); - } - - @Override - public void close() throws Exception { - if (redisClient != null) - redisClient.close(); - } -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/Reflect.java b/src/main/java/com/volmit/adapt/util/reflect/Reflect.java deleted file mode 100644 index 04cb0eaac..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/Reflect.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.volmit.adapt.util.reflect; - -import org.jetbrains.annotations.NotNull; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Optional; - -public class Reflect { - - @NotNull - public static Optional> getClass(@NotNull String className) { - try { - return Optional.of(Class.forName(className)); - } catch (ClassNotFoundException e) { - return Optional.empty(); - } - } - - @NotNull - public static > Optional getEnum(@NotNull Class enumClass, @NotNull String enumName) { - try { - return Optional.of(Enum.valueOf(enumClass, enumName)); - } catch (IllegalArgumentException e) { - return Optional.empty(); - } - } - - @NotNull - public static > E getEnum(@NotNull Class enumClass, @NotNull String... enumNames) { - if (enumNames.length == 0) throw new IllegalArgumentException("Need at least one enum name"); - for (String enumName : enumNames) { - Optional optionalEnum = getEnum(enumClass, enumName); - if (optionalEnum.isPresent()) return optionalEnum.get(); - } - throw new IllegalArgumentException("No Enum found for names " + Arrays.toString(enumNames)); - } - - @NotNull - public static Optional getMethod(@NotNull Class clazz, @NotNull String methodName, @NotNull Class... parameterTypes) { - try { - return Optional.of(clazz.getDeclaredMethod(methodName, parameterTypes)); - } catch (NoSuchMethodException e) { - return Optional.empty(); - } - } - - public static E getField(Class clazz, @NotNull String... fieldNames) { - if (fieldNames.length == 0) throw new IllegalArgumentException("Need at least one field name"); - for (String fieldName : fieldNames) { - Optional optionalField = getField(clazz, fieldName); - if (optionalField.isPresent()) { - try { - return (E) optionalField.get().get(null); - } catch (IllegalAccessException ignored) { - } - } - } - throw new IllegalArgumentException("No Field found for names " + Arrays.toString(fieldNames)); - } - - @NotNull - public static Optional getField(@NotNull Class clazz, @NotNull String fieldName) { - try { - return Optional.of(clazz.getDeclaredField(fieldName)); - } catch (NoSuchFieldException e) { - return Optional.empty(); - } - } -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/WrappedField.java b/src/main/java/com/volmit/adapt/util/reflect/WrappedField.java deleted file mode 100644 index 60bade744..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/WrappedField.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.volmit.adapt.util.reflect; - -import com.volmit.adapt.Adapt; - -import java.lang.reflect.Field; - -public class WrappedField { - - private final Field field; - - public WrappedField(Class origin, String methodName) { - Field f = null; - try { - f = origin.getDeclaredField(methodName); - f.setAccessible(true); - } catch (NoSuchFieldException e) { - Adapt.error("Failed to created WrappedField %s#%s: %s%s".formatted(origin.getSimpleName(), methodName, e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage())); - } - this.field = f; - } - - public T get() { - return get(null); - } - - public T get(C instance) { - if (field == null) { - return null; - } - - try { - return (T) field.get(instance); - } catch (IllegalAccessException e) { - Adapt.error("Failed to get WrappedField %s#%s: %s%s".formatted(field.getDeclaringClass().getSimpleName(), field.getName(), e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage())); - return null; - } - } - - public boolean hasFailed() { - return field == null; - } -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/WrappedReturningMethod.java b/src/main/java/com/volmit/adapt/util/reflect/WrappedReturningMethod.java deleted file mode 100644 index 0d8f6a029..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/WrappedReturningMethod.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.volmit.adapt.util.reflect; - - -import com.volmit.adapt.Adapt; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public final class WrappedReturningMethod { - - private final Method method; - - public WrappedReturningMethod(Class origin, String methodName, Class... paramTypes) { - Method m = null; - try { - m = origin.getDeclaredMethod(methodName, paramTypes); - m.setAccessible(true); - } catch (NoSuchMethodException e) { - Adapt.error("Failed to created WrappedMethod %s#%s: %s%s".formatted(origin.getSimpleName(), methodName, e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage())); - } - this.method = m; - } - - public R invoke(Object... args) { - return invoke(null, args); - } - - public R invoke(C instance, Object... args) { - if (method == null) { - return null; - } - - try { - return (R) method.invoke(instance, args); - } catch (InvocationTargetException | IllegalAccessException e) { - Adapt.error("Failed to invoke WrappedMethod %s#%s: %s%s".formatted(method.getDeclaringClass().getSimpleName(), method.getName(), e.getClass().getSimpleName(), e.getMessage().equals("") ? "" : " | " + e.getMessage())); - return null; - } - } -} \ No newline at end of file diff --git a/src/main/java/com/volmit/adapt/util/reflect/events/ReflectiveEvents.java b/src/main/java/com/volmit/adapt/util/reflect/events/ReflectiveEvents.java deleted file mode 100644 index 11e553662..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/events/ReflectiveEvents.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.volmit.adapt.util.reflect.events; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.collection.KList; -import com.volmit.adapt.util.collection.KMap; -import com.volmit.adapt.util.reflect.Reflect; -import com.volmit.adapt.util.reflect.events.api.Event; -import com.volmit.adapt.util.reflect.events.api.ReflectiveHandler; -import com.volmit.adapt.util.reflect.events.api.entity.EndermanAttackPlayerEvent; -import com.volmit.adapt.util.reflect.events.api.entity.EntityDismountEvent; -import com.volmit.adapt.util.reflect.events.api.entity.EntityMountEvent; -import lombok.NonNull; -import org.bukkit.event.EventException; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; -import org.bukkit.plugin.EventExecutor; -import org.bukkit.plugin.RegisteredListener; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Modifier; -import java.lang.reflect.Proxy; -import java.util.Arrays; -import java.util.Optional; -import java.util.stream.Collectors; - -public class ReflectiveEvents { - private static final KMap, Class> EVENTS = new KMap<>(); - private static final KMap, HandlerList> HANDLERS = new KMap<>(); - - static { - register(EntityMountEvent.class, "org.bukkit.event.entity.EntityMountEvent", "org.spigotmc.event.entity.EntityMountEvent"); - register(EntityDismountEvent.class, "org.bukkit.event.entity.EntityDismountEvent", "org.spigotmc.event.entity.EntityDismountEvent"); - register(EndermanAttackPlayerEvent.class, "com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent"); - } - - public static void register(@NonNull Listener listener) { - if (Adapt.bad) return; - - Arrays.stream(listener.getClass().getDeclaredMethods()) - .filter(method -> method.isAnnotationPresent(ReflectiveHandler.class)) - .filter(method -> !Modifier.isStatic(method.getModifiers())) - .filter(method -> method.getParameterCount() == 1) - .filter(method -> Event.class.isAssignableFrom(method.getParameterTypes()[0])) - .collect(Collectors.toMap(method -> HANDLERS.get(method.getParameterTypes()[0]), - method -> { - try { - if (!Modifier.isPublic(method.getModifiers())) { - method.setAccessible(true); - } - } catch (Throwable e) { - return new KList(); - } - - - Class eventClass = method.getParameterTypes()[0]; - Class bukkitClass = EVENTS.get(eventClass); - ReflectiveHandler handler = method.getAnnotation(ReflectiveHandler.class); - - EventExecutor executor = (obj, event) -> { - if (!bukkitClass.isAssignableFrom(event.getClass())) - return; - - try { - method.invoke(obj, newProxy(obj, eventClass)); - } catch (InvocationTargetException e) { - throw new EventException(e.getCause()); - } catch (Throwable e) { - throw new EventException(e); - } - }; - - return new KList<>(new RegisteredListener(listener, executor, handler.priority(), Adapt.instance, handler.ignoreCancelled())); - }, KList::add)) - .forEach((handlerList, registeredListeners) -> { - if (handlerList == null) return; - handlerList.registerAll(registeredListeners); - }); - } - - public static void register(Class eventInterface, String... classes) { - for (String clazz : classes) { - Optional> opt = Reflect.getClass(clazz); - if (opt.isEmpty()) - continue; - - var handlerList = getHandlerList(opt.get()); - if (handlerList == null) { - Adapt.warn("Event class does not contain HandlerList: " + clazz); - continue; - } - - EVENTS.put(eventInterface, opt.get()); - HANDLERS.put(opt.get(), handlerList); - return; - } - } - - public static boolean exists(@NonNull Class eventInterface) { - return EVENTS.containsKey(eventInterface); - } - - @Nullable - private static HandlerList getHandlerList(Class parent) { - while (parent != null) { - if (!org.bukkit.event.Event.class.isAssignableFrom(parent)) - return null; - - try { - var method = parent.getDeclaredMethod("getHandlerList"); - return (HandlerList) method.invoke(null); - } catch (Throwable e) { - parent = parent.getSuperclass(); - } - } - return null; - } - - private static Object newProxy(Object o, Class... interfaces) { - return Proxy.newProxyInstance(Event.class.getClassLoader(), interfaces, (proxy, method, args) -> method.invoke(o, args)); - } -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/events/api/Event.java b/src/main/java/com/volmit/adapt/util/reflect/events/api/Event.java deleted file mode 100644 index c6e8ce83a..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/events/api/Event.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.volmit.adapt.util.reflect.events.api; - -public interface Event { - boolean isAsynchronous(); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/events/api/ReflectiveHandler.java b/src/main/java/com/volmit/adapt/util/reflect/events/api/ReflectiveHandler.java deleted file mode 100644 index 2b2197d00..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/events/api/ReflectiveHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.volmit.adapt.util.reflect.events.api; - -import org.bukkit.event.EventPriority; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -public @interface ReflectiveHandler { - EventPriority priority() default EventPriority.NORMAL; - - boolean ignoreCancelled() default false; -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EndermanAttackPlayerEvent.java b/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EndermanAttackPlayerEvent.java deleted file mode 100644 index b2affe7d9..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EndermanAttackPlayerEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.volmit.adapt.util.reflect.events.api.entity; - -import org.bukkit.entity.Enderman; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.jetbrains.annotations.NotNull; - -public interface EndermanAttackPlayerEvent extends EntityEvent, Cancellable { - @NotNull - Enderman getEntity(); - @NotNull - Player getPlayer(); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityDismountEvent.java b/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityDismountEvent.java deleted file mode 100644 index 59f7bbc07..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityDismountEvent.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.volmit.adapt.util.reflect.events.api.entity; - -import org.bukkit.entity.Entity; -import org.bukkit.event.Cancellable; -import org.jetbrains.annotations.NotNull; - -public interface EntityDismountEvent extends EntityEvent, Cancellable { - @NotNull - Entity getDismounted(); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityEvent.java b/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityEvent.java deleted file mode 100644 index ee15f5212..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityEvent.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.volmit.adapt.util.reflect.events.api.entity; - -import com.volmit.adapt.util.reflect.events.api.Event; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; - -public interface EntityEvent extends Event { - Entity getEntity(); - EntityType getType(); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityMountEvent.java b/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityMountEvent.java deleted file mode 100644 index fe3939160..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/events/api/entity/EntityMountEvent.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.volmit.adapt.util.reflect.events.api.entity; - -import org.bukkit.entity.Entity; -import org.bukkit.event.Cancellable; -import org.jetbrains.annotations.NotNull; - -public interface EntityMountEvent extends EntityEvent, Cancellable { - @NotNull - Entity getMount(); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/Attributes.java b/src/main/java/com/volmit/adapt/util/reflect/registries/Attributes.java deleted file mode 100644 index 3b9d21282..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/Attributes.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import org.bukkit.attribute.Attribute; - -public class Attributes { - public static final Attribute GENERIC_ARMOR = RegistryUtil.find(Attribute.class, "generic_armor", "armor"); - public static final Attribute GENERIC_ATTACK_DAMAGE = RegistryUtil.find(Attribute.class, "generic_attack_damage", "attack_damage"); - public static final Attribute GENERIC_MAX_HEALTH = RegistryUtil.find(Attribute.class, "generic_max_health", "max_health"); - public static final Attribute GENERIC_MOVEMENT_SPEED = RegistryUtil.find(Attribute.class, "generic_movement_speed", "movement_speed"); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/Enchantments.java b/src/main/java/com/volmit/adapt/util/reflect/registries/Enchantments.java deleted file mode 100644 index ea122e71a..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/Enchantments.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import org.bukkit.enchantments.Enchantment; - -public class Enchantments { - public static final Enchantment DURABILITY = RegistryUtil.find(Enchantment.class, "unbreaking", "durability"); - public static final Enchantment ARROW_INFINITE = RegistryUtil.find(Enchantment.class, "infinity", "arrow_infinite"); - public static final Enchantment LOOT_BONUS_BLOCKS = RegistryUtil.find(Enchantment.class, "fortune", "loot_bonus_blocks"); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/EntityTypes.java b/src/main/java/com/volmit/adapt/util/reflect/registries/EntityTypes.java deleted file mode 100644 index c526fa196..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/EntityTypes.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import org.bukkit.entity.EntityType; - -public class EntityTypes { - public static final EntityType ENDER_CRYSTAL = RegistryUtil.find(EntityType.class, "ender_crystal", "end_crystal"); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/ItemFlags.java b/src/main/java/com/volmit/adapt/util/reflect/registries/ItemFlags.java deleted file mode 100644 index 61e5fbfb4..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/ItemFlags.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import org.bukkit.inventory.ItemFlag; - -public class ItemFlags { - public static final ItemFlag HIDE_POTION_EFFECTS = RegistryUtil.find(ItemFlag.class, "hide_potion_effects", "hide_additional_tooltip"); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/Materials.java b/src/main/java/com/volmit/adapt/util/reflect/registries/Materials.java deleted file mode 100644 index f6f1236ba..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/Materials.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import org.bukkit.Material; - -public class Materials { - public static final Material GRASS = RegistryUtil.find(Material.class, "grass", "short_grass"); - - /* Added Materials */ - public static final Material MACE = RegistryUtil.findNullable(Material.class, "mace"); - //1.20+ - public static final Material OAK_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "oak_hanging_sign"); - public static final Material OAK_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "oak_wall_hanging_sign"); - public static final Material SPRUCE_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "spruce_hanging_sign"); - public static final Material SPRUCE_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "spruce_wall_hanging_sign"); - public static final Material BIRCH_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "birch_hanging_sign"); - public static final Material BIRCH_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "birch_wall_hanging_sign"); - public static final Material JUNGLE_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "jungle_hanging_sign"); - public static final Material JUNGLE_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "jungle_wall_hanging_sign"); - public static final Material ACACIA_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "acacia_hanging_sign"); - public static final Material ACACIA_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "acacia_wall_hanging_sign"); - public static final Material DARK_OAK_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "dark_oak_hanging_sign"); - public static final Material DARK_OAK_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "dark_oak_wall_hanging_sign"); - public static final Material MANGROVE_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "mangrove_hanging_sign"); - public static final Material MANGROVE_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "mangrove_wall_hanging_sign"); - public static final Material CRIMSON_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "crimson_hanging_sign"); - public static final Material CRIMSON_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "crimson_wall_hanging_sign"); - public static final Material WARPED_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "warped_hanging_sign"); - public static final Material WARPED_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "warped_wall_hanging_sign"); - - public static final Material CHERRY_LOG = RegistryUtil.findNullable(Material.class, "cherry_log"); - public static final Material CHERRY_WOOD = RegistryUtil.findNullable(Material.class, "cherry_wood"); - public static final Material STRIPPED_CHERRY_LOG = RegistryUtil.findNullable(Material.class, "stripped_cherry_log"); - public static final Material STRIPPED_CHERRY_WOOD = RegistryUtil.findNullable(Material.class, "stripped_cherry_wood"); - public static final Material CHERRY_PLANKS = RegistryUtil.findNullable(Material.class, "cherry_planks"); - public static final Material CHERRY_STAIRS = RegistryUtil.findNullable(Material.class, "cherry_stairs"); - public static final Material CHERRY_SLAB = RegistryUtil.findNullable(Material.class, "cherry_slab"); - public static final Material CHERRY_FENCE = RegistryUtil.findNullable(Material.class, "cherry_fence"); - public static final Material CHERRY_FENCE_GATE = RegistryUtil.findNullable(Material.class, "cherry_fence_gate"); - public static final Material CHERRY_DOOR = RegistryUtil.findNullable(Material.class, "cherry_door"); - public static final Material CHERRY_TRAPDOOR = RegistryUtil.findNullable(Material.class, "cherry_trapdoor"); - public static final Material CHERRY_PRESSURE_PLATE = RegistryUtil.findNullable(Material.class, "cherry_pressure_plate"); - public static final Material CHERRY_BUTTON = RegistryUtil.findNullable(Material.class, "cherry_button"); - public static final Material CHERRY_SIGN = RegistryUtil.findNullable(Material.class, "cherry_sign"); - public static final Material CHERRY_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "cherry_hanging_sign"); - public static final Material CHERRY_WALL_SIGN = RegistryUtil.findNullable(Material.class, "cherry_wall_sign"); - public static final Material CHERRY_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "cherry_wall_hanging_sign"); - public static final Material CHERRY_LEAVES = RegistryUtil.findNullable(Material.class, "cherry_leaves"); - public static final Material CHERRY_SAPLING = RegistryUtil.findNullable(Material.class, "cherry_sapling"); - - public static final Material BAMBOO_BLOCK = RegistryUtil.findNullable(Material.class, "bamboo_block"); - public static final Material STRIPPED_BAMBOO_BLOCK = RegistryUtil.findNullable(Material.class, "stripped_bamboo_block"); - public static final Material BAMBOO_PLANKS = RegistryUtil.findNullable(Material.class, "bamboo_planks"); - public static final Material BAMBOO_MOSAIC = RegistryUtil.findNullable(Material.class, "bamboo_mosaic"); - public static final Material BAMBOO_STAIRS = RegistryUtil.findNullable(Material.class, "bamboo_stairs"); - public static final Material BAMBOO_MOSAIC_STAIRS = RegistryUtil.findNullable(Material.class, "bamboo_mosaic_stairs"); - public static final Material BAMBOO_SLAB = RegistryUtil.findNullable(Material.class, "bamboo_slab"); - public static final Material BAMBOO_MOSAIC_SLAB = RegistryUtil.findNullable(Material.class, "bamboo_mosaic_slab"); - public static final Material BAMBOO_FENCE = RegistryUtil.findNullable(Material.class, "bamboo_fence"); - public static final Material BAMBOO_FENCE_GATE = RegistryUtil.findNullable(Material.class, "bamboo_fence_gate"); - public static final Material BAMBOO_DOOR = RegistryUtil.findNullable(Material.class, "bamboo_door"); - public static final Material BAMBOO_TRAPDOOR = RegistryUtil.findNullable(Material.class, "bamboo_trapdoor"); - public static final Material BAMBOO_PRESSURE_PLATE = RegistryUtil.findNullable(Material.class, "bamboo_pressure_plate"); - public static final Material BAMBOO_BUTTON = RegistryUtil.findNullable(Material.class, "bamboo_button"); - public static final Material BAMBOO_SIGN = RegistryUtil.findNullable(Material.class, "bamboo_sign"); - public static final Material BAMBOO_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "bamboo_hanging_sign"); - public static final Material BAMBOO_WALL_SIGN = RegistryUtil.findNullable(Material.class, "bamboo_wall_sign"); - public static final Material BAMBOO_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "bamboo_wall_hanging_sign"); - - //1.21.4+ - public static final Material PALE_OAK_LOG = RegistryUtil.findNullable(Material.class, "pale_oak_log"); - public static final Material PALE_OAK_WOOD = RegistryUtil.findNullable(Material.class, "pale_oak_wood"); - public static final Material STRIPPED_PALE_OAK_LOG = RegistryUtil.findNullable(Material.class, "stripped_pale_oak_log"); - public static final Material STRIPPED_PALE_OAK_WOOD = RegistryUtil.findNullable(Material.class, "stripped_pale_oak_wood"); - - public static final Material PALE_OAK_PLANKS = RegistryUtil.findNullable(Material.class, "pale_oak_planks"); - public static final Material PALE_OAK_STAIRS = RegistryUtil.findNullable(Material.class, "pale_oak_stairs"); - public static final Material PALE_OAK_SLAB = RegistryUtil.findNullable(Material.class, "pale_oak_slab"); - public static final Material PALE_OAK_FENCE = RegistryUtil.findNullable(Material.class, "pale_oak_fence"); - public static final Material PALE_OAK_FENCE_GATE = RegistryUtil.findNullable(Material.class, "pale_oak_fence_gate"); - public static final Material PALE_OAK_DOOR = RegistryUtil.findNullable(Material.class, "pale_oak_door"); - public static final Material PALE_OAK_TRAPDOOR = RegistryUtil.findNullable(Material.class, "pale_oak_trapdoor"); - public static final Material PALE_OAK_PRESSURE_PLATE = RegistryUtil.findNullable(Material.class, "pale_oak_pressure_plate"); - public static final Material PALE_OAK_BUTTON = RegistryUtil.findNullable(Material.class, "pale_oak_button"); - - public static final Material PALE_OAK_SIGN = RegistryUtil.findNullable(Material.class, "pale_oak_sign"); - public static final Material PALE_OAK_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "pale_oak_hanging_sign"); - public static final Material PALE_OAK_WALL_SIGN = RegistryUtil.findNullable(Material.class, "pale_oak_wall_sign"); - public static final Material PALE_OAK_WALL_HANGING_SIGN = RegistryUtil.findNullable(Material.class, "pale_oak_wall_hanging_sign"); - - public static final Material PALE_OAK_LEAVES = RegistryUtil.findNullable(Material.class, "pale_oak_leaves"); - public static final Material PALE_OAK_SAPLING = RegistryUtil.findNullable(Material.class, "pale_oak_sapling"); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/Particles.java b/src/main/java/com/volmit/adapt/util/reflect/registries/Particles.java deleted file mode 100644 index 7106eeb92..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/Particles.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import org.bukkit.Particle; - -public class Particles { - public static final Particle REDSTONE = RegistryUtil.find(Particle.class, "redstone", "dust"); - public static final Particle ENCHANTMENT_TABLE = RegistryUtil.find(Particle.class, "enchantment_table", "enchant"); - public static final Particle CRIT_MAGIC = RegistryUtil.find(Particle.class, "crit_magic", "crit"); - public static final Particle TOTEM = RegistryUtil.find(Particle.class, "totem", "totem_of_undying"); - public static final Particle BLOCK_CRACK = RegistryUtil.find(Particle.class, "block_crack", "block"); - public static final Particle VILLAGER_HAPPY = RegistryUtil.find(Particle.class, "villager_happy", "happy_villager"); - public static final Particle ITEM_CRACK = RegistryUtil.find(Particle.class, "item_crack", "item"); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/PotionEffectTypes.java b/src/main/java/com/volmit/adapt/util/reflect/registries/PotionEffectTypes.java deleted file mode 100644 index cd468f587..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/PotionEffectTypes.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import org.bukkit.potion.PotionEffectType; - -public class PotionEffectTypes { - public static final PotionEffectType FAST_DIGGING = RegistryUtil.find(PotionEffectType.class, "fast_digging", "haste"); - public static final PotionEffectType DAMAGE_RESISTANCE = RegistryUtil.find(PotionEffectType.class, "damage_resistance", "resistance"); - public static final PotionEffectType JUMP = RegistryUtil.find(PotionEffectType.class, "jump", "jump_boost"); - public static final PotionEffectType SLOW_DIGGING = RegistryUtil.find(PotionEffectType.class, "slow_digging", "mining_fatigue"); - public static final PotionEffectType CONFUSION = RegistryUtil.find(PotionEffectType.class, "confusion", "nausea"); - public static final PotionEffectType INCREASE_DAMAGE = RegistryUtil.find(PotionEffectType.class, "increase_damage", "strength"); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/PotionTypes.java b/src/main/java/com/volmit/adapt/util/reflect/registries/PotionTypes.java deleted file mode 100644 index e1b62817d..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/PotionTypes.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import org.bukkit.potion.PotionType; - -public class PotionTypes { - public static final PotionType UNCRAFTABLE = RegistryUtil.findNullable(PotionType.class, "uncraftable", "empty"); - - public static final PotionType INSTANT_HEAL = RegistryUtil.find(PotionType.class, "instant_heal", "healing"); - public static final PotionType SPEED = RegistryUtil.find(PotionType.class, "speed", "swiftness"); - public static final PotionType REGEN = RegistryUtil.find(PotionType.class, "regen", "regeneration"); - public static final PotionType JUMP = RegistryUtil.find(PotionType.class, "jump", "leaping"); -} diff --git a/src/main/java/com/volmit/adapt/util/reflect/registries/RegistryUtil.java b/src/main/java/com/volmit/adapt/util/reflect/registries/RegistryUtil.java deleted file mode 100644 index 77249a8dd..000000000 --- a/src/main/java/com/volmit/adapt/util/reflect/registries/RegistryUtil.java +++ /dev/null @@ -1,243 +0,0 @@ -package com.volmit.adapt.util.reflect.registries; - -import com.volmit.adapt.util.cache.AtomicCache; -import lombok.NonNull; -import manifold.rt.api.util.Pair; -import org.bukkit.Bukkit; -import org.bukkit.Keyed; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.*; -import java.util.function.Supplier; -import java.util.stream.Collectors; - - -@SuppressWarnings("unchecked") -public class RegistryUtil { - private static final AtomicCache registryLookup = new AtomicCache<>(); - private static final Map, Map> KEYED_REGISTRY = new HashMap<>(); - private static final Map, Map> ENUM_REGISTRY = new HashMap<>(); - private static final Map, Registry> REGISTRY = new HashMap<>(); - - @NonNull - public static T find(@NonNull Class typeClass, @NonNull String... keys) { - return findOptional(typeClass, keys).orElseThrow(notFound(typeClass, keys)); - } - - @NonNull - public static Optional findOptional(@NonNull Class typeClass, @NonNull String... keys) { - return findOptional(typeClass, defaultLookup(), keys); - } - - @Nullable - public static T findNullable(@NonNull Class typeClass, @NonNull String... keys) { - return findOptional(typeClass, defaultLookup(), keys).orElse(null); - } - - @NonNull - public static T find(@NonNull Class typeClass, @Nullable Lookup lookup, @NonNull String... keys) { - return findOptional(typeClass, lookup, keys).orElseThrow(notFound(typeClass, keys)); - } - - @NonNull - public static Optional findOptional(@NonNull Class typeClass, @Nullable Lookup lookup, @NonNull String... keys) { - return findOptional(typeClass, lookup, Arrays.stream(keys).map(NamespacedKey::minecraft).toArray(NamespacedKey[]::new)); - } - - @NonNull - public static T find(@NonNull Class typeClass, @NonNull NamespacedKey... keys) { - return findOptional(typeClass, keys).orElseThrow(notFound(typeClass, keys)); - } - - @NonNull - public static Optional findOptional(@NonNull Class typeClass, @NonNull NamespacedKey... keys) { - return findOptional(typeClass, defaultLookup(), keys); - } - - @NonNull - public static T find(@NonNull Class typeClass, @Nullable Lookup lookup, @NonNull NamespacedKey... keys) { - return findOptional(typeClass, lookup, keys).orElseThrow(notFound(typeClass, keys)); - } - - @NonNull - public static Optional findOptional(@NonNull Class typeClass, @Nullable Lookup lookup, @NonNull NamespacedKey... keys) { - if (keys.length == 0) throw new IllegalArgumentException("Need at least one key"); - Registry registry = null; - if (Keyed.class.isAssignableFrom(typeClass)) { - registry = getRegistry(typeClass.asSubclass(Keyed.class)); - } - if (registry == null) { - registry = REGISTRY.computeIfAbsent(typeClass, t -> Arrays.stream(Registry.class.getDeclaredFields()) - .filter(field -> Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers())) - .filter(field -> Registry.class.isAssignableFrom(field.getType())) - .filter(field -> ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].equals(t)) - .map(field -> { - try { - return (Registry) field.get(null); - } catch (IllegalAccessException e) { - return null; - } - }) - .filter(Objects::nonNull) - .findFirst() - .orElse(null)); - } - - if (registry != null) { - for (NamespacedKey key : keys) { - Keyed value = registry.get(key); - if (value != null) - return Optional.of((T) value); - } - } - - if (lookup != null) - return lookup.find(typeClass, keys); - return Optional.empty(); - } - - @NonNull - public static Optional findByField(@NonNull Class typeClass, @NonNull NamespacedKey... keys) { - var values = KEYED_REGISTRY.computeIfAbsent(typeClass, RegistryUtil::getKeyedValues); - for (NamespacedKey key : keys) { - var value = values.get(key); - if (value != null) - return Optional.of((T) value); - } - return Optional.empty(); - } - - @NonNull - public static Optional findByEnum(@NonNull Class typeClass, @NonNull NamespacedKey... keys) { - var values = ENUM_REGISTRY.computeIfAbsent(typeClass, RegistryUtil::getEnumValues); - for (NamespacedKey key : keys) { - var value = values.get(key); - if (value != null) - return Optional.of((T) value); - } - return Optional.empty(); - } - - @NonNull - public static Lookup defaultLookup() { - return Lookup.combine(RegistryUtil::findByField, RegistryUtil::findByEnum); - } - - private static Map getKeyedValues(@NonNull Class typeClass) { - return Arrays.stream(typeClass.getDeclaredFields()) - .filter(field -> Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) - .filter(field -> Keyed.class.isAssignableFrom(field.getType())) - .map(field -> { - try { - return (Keyed) field.get(null); - } catch (Throwable e) { - return null; - } - }) - .map(keyed -> { - if (keyed == null) return null; - try { - return Pair.make(keyed.getKey(), keyed); - } catch (Throwable e) { - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond)); - } - - private static Map getEnumValues(@NonNull Class typeClass) { - return Arrays.stream(typeClass.getDeclaredFields()) - .filter(field -> Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) - .filter(field -> typeClass.isAssignableFrom(field.getType())) - .map(field -> { - try { - return Map.entry(NamespacedKey.minecraft(field.getName().toLowerCase()), field.get(null)); - } catch (Throwable e) { - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - } - - @FunctionalInterface - public interface Lookup { - @NonNull - Optional find(@NonNull Class typeClass, @NonNull NamespacedKey... keys); - - static Lookup combine(@NonNull Lookup... lookups) { - if (lookups.length == 0) throw new IllegalArgumentException("Need at least one lookup"); - return (typeClass, keys) -> { - Optional opt = Optional.empty(); - for (Lookup lookup : lookups) { - opt = opt.or(() -> lookup.find(typeClass, keys)); - } - return opt; - }; - } - } - - @Nullable - private static Registry getRegistry(@NotNull Class type) { - RegistryLookup lookup = registryLookup.aquire(() -> { - RegistryLookup bukkit; - try { - bukkit = Bukkit::getRegistry; - } catch (Throwable ignored) { - bukkit = null; - } - return new DefaultRegistryLookup(bukkit); - }); - return lookup.find(type); - } - - private interface RegistryLookup { - @Nullable - Registry find(@NonNull Class type); - } - - private static class DefaultRegistryLookup implements RegistryLookup { - private final RegistryLookup bukkit; - private final Map registries; - - private DefaultRegistryLookup(RegistryLookup bukkit) { - this.bukkit = bukkit; - registries = Arrays.stream(Registry.class.getDeclaredFields()) - .filter(field -> Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) - .filter(field -> Registry.class.isAssignableFrom(field.getType())) - .map(field -> { - var type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; - try { - return Map.entry(type, field.get(null)); - } catch (Throwable e) { - return null; - } - }) - .filter(Objects::nonNull) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a)); - } - - @Nullable - @Override - public Registry find(@NonNull Class type) { - if (bukkit == null) return (Registry) registries.get(type); - try { - return bukkit.find(type); - } catch (Throwable e) { - return (Registry) registries.get(type); - } - } - } - - public static Supplier notFound(Class type, T... keys) { - return () -> new IllegalArgumentException("No " + type.getSimpleName() + " found for keys: " + Arrays.deepToString(keys)); - } -} diff --git a/src/main/java/com/volmit/adapt/util/secret/SecretSplash.java b/src/main/java/com/volmit/adapt/util/secret/SecretSplash.java deleted file mode 100644 index a8677429f..000000000 --- a/src/main/java/com/volmit/adapt/util/secret/SecretSplash.java +++ /dev/null @@ -1,87 +0,0 @@ -/*------------------------------------------------------------------------------ - - Adapt is a Skill/Integration plugin for Minecraft Bukkit Servers - - Copyright (c) 2022 Arcane Arts (Volmit Software) - - - - This program is free software: you can redistribute it and/or modify - - it under the terms of the GNU General Public License as published by - - the Free Software Foundation, either version 3 of the License, or - - (at your option) any later version. - - - - This program is distributed in the hope that it will be useful, - - but WITHOUT ANY WARRANTY; without even the implied warranty of - - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - - GNU General Public License for more details. - - - - You should have received a copy of the GNU General Public License - - along with this program. If not, see . - -----------------------------------------------------------------------------*/ - -package com.volmit.adapt.util.secret; - -import com.volmit.adapt.Adapt; -import com.volmit.adapt.util.C; -import com.volmit.adapt.util.collection.KList; -import lombok.Getter; - -import java.nio.charset.StandardCharsets; -import java.util.Random; - -public class SecretSplash { - - @Getter - public static KList secretSplash = new KList<>( - "\n" + C.BLUE + " ⣞⢽⢪⢣⢣⢣⢫⡺⡵⣝⡮⣗⢷⢽⢽⢽⣮⡷⡽⣜⣜⢮⢺⣜⢷⢽⢝⡽⣝ \n" + - C.BLUE + " ⠸⡸⠜⠕⠕⠁⢁⢇⢏⢽⢺⣪⡳⡝⣎⣏⢯⢞⡿⣟⣷⣳⢯⡷⣽⢽⢯⣳⣫⠇ \n" + - C.BLUE + " ⢀⢀⢄⢬⢪⡪⡎⣆⡈⠚⠜⠕⠇⠗⠝⢕⢯⢫⣞⣯⣿⣻⡽⣏⢗⣗⠏⠀ " + C.DARK_RED + "Adapt\n" + - C.BLUE + " ⠪⡪⡪⣪⢪⢺⢸⢢⢓⢆⢤⢀⠀⠀⠀⠀⠈⢊⢞⡾⣿⡯⣏⢮⠷⠁⠀⠀ " + C.GRAY + "Version: " + C.DARK_RED + Adapt.instance.getDescription().getVersion() + "\n" + - C.BLUE + " ⠈⠊⠆⡃⠕⢕⢇⢇⢇⢇⢇⢏⢎⢎⢆⢄⠀⢑⣽⣿⢝⠲⠉⠀⠀⠀⠀ " + C.GRAY + "By: " + C.RED + "A" + C.GOLD + "r" + C.YELLOW + "c" + C.GREEN + "a" + C.DARK_GRAY + "n" + C.AQUA + "e " + C.AQUA + "A" + C.BLUE + "r" + C.DARK_BLUE + "t" + C.DARK_PURPLE + "s" + C.WHITE + " (Volmit Software)\n" + - C.BLUE + " ⡿⠂⠠⠀⡇⢇⠕⢈⣀⠀⠁⠡⠣⡣⡫⣂⣿⠯⢪⠰⠂⠀⠀⠀⠀ " + C.GRAY + "Java Version: " + C.DARK_RED + Adapt.getJavaVersion() + "\n" + - C.BLUE + " ⡦⡙⡂⢀⢤⢣⠣⡈⣾⡃⠠⠄⠀⡄⢱⣌⣶⢏⢊⠂⠀⠀⠀⠀⠀ ⠀\n" + - C.BLUE + " ⢝⡲⣜⡮⡏⢎⢌⢂⠙⠢⠐⢀⢘⢵⣽⣿⡿⠁⠁⠀⠀⠀⠀ ⠀⠀⠀\n" + - C.BLUE + " ⠨⣺⡺⡕⡕⡱⡑⡆⡕⡅⡕⡜⡼⢽⡻⠏⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀\n" + - C.BLUE + " ⣼⣳⣫⣾⣵⣗⡵⡱⡡⢣⢑⢕⢜⢕⡝⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀\n" + - C.BLUE + " ⣴⣿⣾⣿⣿⣿⡿⡽⡑⢌⠪⡢⡣⣣⡟⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀No Splash Screen?\n" + - C.BLUE + " ⡟⡾⣿⢿⢿⢵⣽⣾⣼⣘⢸⢸⣞⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠀⠀\n" + - C.BLUE + " ⠁⠇⠡⠩⡫⢿⣝⡻⡮⣒⢽⠋⠀⠀⠀⠀", - - "\n :::. :::::::-. :::. ::::::::::. :::::::::::: \n" + - " ;;`;; ;;, `';, ;;`;; `;;;```.;;;;;;;;;;;'''' " + C.GRAY + "Version: " + C.DARK_RED + Adapt.instance.getDescription().getVersion() + "\n" + - " ,[[ '[[, `[[ [[ ,[[ '[[, `]]nnn]]' [[ " + C.GRAY + "By: " + C.RED + "A" + C.GOLD + "r" + C.YELLOW + "c" + C.GREEN + "a" + C.DARK_GRAY + "n" + C.AQUA + "e " + C.AQUA + "A" + C.BLUE + "r" + C.DARK_BLUE + "t" + C.DARK_PURPLE + "s" + C.WHITE + " (Volmit Software)\n" + - " $$$$$$$$ $$, $$ $$$$$$$$ $$$\"\" $$ " + C.GRAY + "Java Version: " + C.DARK_RED + Adapt.getJavaVersion() + "\n" + - " 888 888,888_,o8P' 888 888,888o 88, \n" + - " YMM \"\"` MMMMP\"` YMM \"\"` YMMMb MMM \n", - - C.GRAY + "\n ██░ ██ ▓█████ ██▓ ██▓███ ███▄ ▄███▓▓█████ \n" + - C.GRAY + "▓██░ ██▒▓█ ▀ ▓██▒ ▓██░ ██▒ ▓██▒▀█▀ ██▒▓█ ▀ " + C.DARK_RED + "Adapt \n" + - C.GRAY + "▒██▀▀██░▒███ ▒██░ ▓██░ ██▓▒ ▓██ ▓██░▒███ " + C.GRAY + "Version: " + C.DARK_RED + Adapt.instance.getDescription().getVersion() + " \n" + - C.GRAY + "░▓█ ░██ ▒▓█ ▄ ▒██░ ▒██▄█▓▒ ▒ ▒██ ▒██ ▒▓█ ▄ " + C.GRAY + "By: " + C.RED + "A" + C.GOLD + "r" + C.YELLOW + "c" + C.GREEN + "a" + C.DARK_GRAY + "n" + C.AQUA + "e " + C.AQUA + "A" + C.BLUE + "r" + C.DARK_BLUE + "t" + C.DARK_PURPLE + "s" + C.WHITE + " (Volmit Software)\n" + - C.GRAY + "░▓█▒░██▓░▒████▒░██████▒▒██▒ ░ ░ ▒██▒ ░██▒░▒████▒ " + C.GRAY + "Java Version: " + C.DARK_RED + Adapt.getJavaVersion() + " \n" + - C.GRAY + " ▒ ░░▒░▒░░ ▒░ ░░ ▒░▓ ░▒▓▒░ ░ ░ ░ ▒░ ░ ░░░ ▒░ ░ ", - - C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣶⣿⣿⣷⣶⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n" + - C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣾⣿⣿⡿⢿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀ ⠀\n" + - C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⡟⠁⣰⣿⣿⣿⡿⠿⠻⠿⣿⣿⣿⣿⣧⠀ ⠀⠀⠀\n" + - C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⠏⠀⣴⣿⣿⣿⠉⠀⠀⠀⠀⠀⠈⢻⣿⣿⣇⠀⠀⠀ \n" + - C.GRAY + "⠀⠀⠀⠀⢀⣠⣼⣿⣿⡏⠀⢠⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⡀⠀ ⠀" + C.DARK_RED + "Adapt \n" + - C.GRAY + "⠀⠀⠀⣰⣿⣿⣿⣿⣿⡇⠀⢸⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⡇⠀ ⠀" + C.GRAY + "Version: " + C.DARK_RED + Adapt.instance.getDescription().getVersion() + " \n" + - C.GRAY + "⠀⠀⢰⣿⣿⡿⣿⣿⣿⡇⠀⠘⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⢀⣸⣿⣿⣿⠁⠀ ⠀" + C.GRAY + "By: " + C.RED + "A" + C.GOLD + "r" + C.YELLOW + "c" + C.GREEN + "a" + C.DARK_GRAY + "n" + C.AQUA + "e " + C.AQUA + "A" + C.BLUE + "r" + C.DARK_BLUE + "t" + C.DARK_PURPLE + "s" + C.WHITE + " (Volmit Software)\n" + - C.GRAY + "⠀⠀⣿⣿⣿⠁⣿⣿⣿⡇⠀⠀⠻⣿⣿⣿⣷⣶⣶⣶⣶⣶⣿⣿⣿⣿⠃⠀ ⠀⠀" + C.GRAY + "Java Version: " + C.DARK_RED + Adapt.getJavaVersion() + " \n" + - C.GRAY + "⠀⢰⣿⣿⡇⠀⣿⣿⣿⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀ ⠀\n" + - C.GRAY + "⠀⢸⣿⣿⡇⠀⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠉⠛⠛⠛⠉⢉⣿⣿⠀⠀⠀⠀⠀ ⠀\n" + - C.GRAY + "⠀⢸⣿⣿⣇⠀⣿⣿⣿⠀⠀⠀⠀⠀⢀⣤⣤⣤⡀⠀⠀⢸⣿⣿⣿⣷⣦⠀ ⠀⠀\n" + - C.GRAY + "⠀⠀⢻⣿⣿⣶⣿⣿⣿⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣦⡀⠀⠉⠉⠻⣿⣿⡇⠀ ⠀ \n" + - C.GRAY + "⠀⠀⠀⠛⠿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠈⠹⣿⣿⣇⣀⠀⣠⣾⣿⣿⡇⠀⠀ ⠀⠀" + C.DARK_RED + "sus\n " + - C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣦⣤⣤⣤⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀ ⠀\n" + - C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⢿⣿⣿⣿⣿⣿⣿⠿⠋⠉⠛⠋⠉⠉⠁⠀⠀⠀ ⠀\n" + - C.GRAY + "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠁" - - ); - - public static String randomString7() { - byte[] array = new byte[7]; // length is bounded by 7 - new Random().nextBytes(array); - String generatedString = new String(array, StandardCharsets.UTF_8); - - return generatedString; - } -} diff --git a/src/main/java/fr/skytasul/glowingentities/GlowingEntities.java b/src/main/java/fr/skytasul/glowingentities/GlowingEntities.java new file mode 100644 index 000000000..9c954cf62 --- /dev/null +++ b/src/main/java/fr/skytasul/glowingentities/GlowingEntities.java @@ -0,0 +1,283 @@ +package fr.skytasul.glowingentities; + +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.scores.PlayerTeam; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.scores.Team; +import net.minecraft.world.scores.TeamColor; +import org.bukkit.ChatColor; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class GlowingEntities implements Listener { + private static final byte GLOWING_FLAG = 1 << 6; + + private final @NotNull Plugin plugin; + private final EntityDataAccessor sharedFlagsAccessor; + private final Scoreboard scoreboard; + private final EnumMap teams; + + private Map glowing; + private boolean enabled; + private int uid; + + public GlowingEntities(@NotNull Plugin plugin) { + this.plugin = Objects.requireNonNull(plugin); + this.sharedFlagsAccessor = loadSharedFlagsAccessor(); + this.scoreboard = new Scoreboard(); + this.teams = new EnumMap<>(ChatColor.class); + enable(); + } + + public void enable() { + if (enabled) { + throw new IllegalStateException("The Glowing Entities API has already been enabled."); + } + + plugin.getServer().getPluginManager().registerEvents(this, plugin); + glowing = new HashMap<>(); + uid = ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE); + enabled = true; + } + + public void disable() { + if (!enabled) { + return; + } + + HandlerList.unregisterAll(this); + glowing.clear(); + glowing = null; + uid = 0; + enabled = false; + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + if (glowing != null) { + glowing.remove(event.getPlayer()); + } + } + + public void setGlowing(org.bukkit.entity.Entity entity, Player receiver) throws ReflectiveOperationException { + setGlowing(entity, receiver, null); + } + + public void setGlowing(org.bukkit.entity.Entity entity, Player receiver, ChatColor color) + throws ReflectiveOperationException { + String teamID = entity instanceof Player ? entity.getName() : entity.getUniqueId().toString(); + byte flags = getEntityFlags(entity); + setGlowing(entity.getEntityId(), teamID, receiver, color, flags); + } + + public void setGlowing(int entityID, String teamID, Player receiver) throws ReflectiveOperationException { + setGlowing(entityID, teamID, receiver, null, (byte) 0); + } + + public void setGlowing(int entityID, String teamID, Player receiver, ChatColor color) + throws ReflectiveOperationException { + setGlowing(entityID, teamID, receiver, color, (byte) 0); + } + + public void setGlowing(int entityID, String teamID, Player receiver, ChatColor color, byte otherFlags) + throws ReflectiveOperationException { + ensureEnabled(); + if (color != null && !color.isColor()) { + throw new IllegalArgumentException("ChatColor must be a color format"); + } + + PlayerData playerData = glowing.computeIfAbsent(receiver, PlayerData::new); + GlowingData glowingData = playerData.glowingDatas.get(entityID); + if (glowingData == null) { + glowingData = new GlowingData(entityID, teamID, color, otherFlags); + playerData.glowingDatas.put(entityID, glowingData); + } else { + if (glowingData.color != null && glowingData.color != color) { + removeGlowingColor(playerData, glowingData); + } + glowingData.teamID = teamID; + glowingData.color = color; + glowingData.otherFlags = otherFlags; + } + + sendMetadata(receiver, entityID, computeFlags(glowingData)); + if (color != null) { + setGlowingColor(playerData, glowingData); + } + } + + public void unsetGlowing(org.bukkit.entity.Entity entity, Player receiver) throws ReflectiveOperationException { + unsetGlowing(entity.getEntityId(), receiver); + } + + public void unsetGlowing(int entityID, Player receiver) throws ReflectiveOperationException { + ensureEnabled(); + + PlayerData playerData = glowing.get(receiver); + if (playerData == null) { + return; + } + + GlowingData glowingData = playerData.glowingDatas.remove(entityID); + if (glowingData == null) { + return; + } + + sendMetadata(receiver, entityID, glowingData.otherFlags); + if (glowingData.color != null) { + removeGlowingColor(playerData, glowingData); + } + } + + private void ensureEnabled() { + if (!enabled) { + throw new IllegalStateException("The Glowing Entities API is not enabled."); + } + } + + private byte getEntityFlags(org.bukkit.entity.Entity entity) { + Entity nmsEntity = ((CraftEntity) entity).getHandle(); + return nmsEntity.getEntityData().get(sharedFlagsAccessor); + } + + private byte computeFlags(GlowingData glowingData) { + return (byte) (glowingData.otherFlags | GLOWING_FLAG); + } + + private void sendMetadata(Player player, int entityID, byte flags) { + SynchedEntityData.DataValue value = SynchedEntityData.DataValue.create(sharedFlagsAccessor, flags); + sendPacket(player, new ClientboundSetEntityDataPacket(entityID, List.of(value))); + } + + private void setGlowingColor(PlayerData playerData, GlowingData glowingData) { + boolean sendCreation = false; + if (playerData.sentColors == null) { + playerData.sentColors = EnumSet.of(glowingData.color); + sendCreation = true; + } else if (playerData.sentColors.add(glowingData.color)) { + sendCreation = true; + } + + TeamData teamData = getTeamData(glowingData.color); + if (sendCreation) { + sendPacket(playerData.player, ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(teamData.team, true)); + } + sendPacket(playerData.player, + ClientboundSetPlayerTeamPacket.createPlayerPacket( + teamData.team, + glowingData.teamID, + ClientboundSetPlayerTeamPacket.Action.ADD + )); + } + + private void removeGlowingColor(PlayerData playerData, GlowingData glowingData) { + TeamData teamData = teams.get(glowingData.color); + if (teamData == null) { + return; + } + + sendPacket(playerData.player, + ClientboundSetPlayerTeamPacket.createPlayerPacket( + teamData.team, + glowingData.teamID, + ClientboundSetPlayerTeamPacket.Action.REMOVE + )); + } + + private TeamData getTeamData(ChatColor color) { + TeamData teamData = teams.get(color); + if (teamData != null) { + return teamData; + } + + TeamData created = new TeamData(uid, color, scoreboard); + teams.put(color, created); + return created; + } + + private void sendPacket(Player player, Packet packet) { + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + serverPlayer.connection.send(packet); + } + + private static EntityDataAccessor loadSharedFlagsAccessor() { + try { + Field field = Entity.class.getDeclaredField("DATA_SHARED_FLAGS_ID"); + field.setAccessible(true); + @SuppressWarnings("unchecked") + EntityDataAccessor accessor = (EntityDataAccessor) field.get(null); + return accessor; + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Unable to locate entity shared flags metadata accessor.", e); + } + } + + private static final class PlayerData { + private final Player player; + private final Map glowingDatas; + private EnumSet sentColors; + + private PlayerData(Player player) { + this.player = player; + this.glowingDatas = new HashMap<>(); + } + } + + private static final class GlowingData { + private final int entityID; + private String teamID; + private ChatColor color; + private byte otherFlags; + + private GlowingData(int entityID, String teamID, ChatColor color, byte otherFlags) { + this.entityID = entityID; + this.teamID = teamID; + this.color = color; + this.otherFlags = otherFlags; + } + } + + private static final class TeamData { + private final PlayerTeam team; + + private TeamData(int uid, ChatColor color, Scoreboard scoreboard) { + this.team = new PlayerTeam(scoreboard, "glow-" + uid + color.getChar()); + team.setCollisionRule(Team.CollisionRule.NEVER); + team.setColor(Optional.of(resolveTeamColor(color))); + } + + private static TeamColor resolveTeamColor(ChatColor color) { + String name = color.name().toLowerCase(Locale.ROOT); + TeamColor teamColor = TeamColor.byName(name); + if (teamColor == null) { + return TeamColor.WHITE; + } + return teamColor; + } + } +} diff --git a/src/main/java/fr/skytasul/reflection/Version.java b/src/main/java/fr/skytasul/reflection/Version.java new file mode 100644 index 000000000..2f508ad6b --- /dev/null +++ b/src/main/java/fr/skytasul/reflection/Version.java @@ -0,0 +1,85 @@ +package fr.skytasul.reflection; + +import org.jetbrains.annotations.NotNull; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public record Version(int major, int minor, int patch) implements Comparable { + private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)(?:\\.(\\d+))?.*$"); + public static final Version ZERO = new Version(0, 0, 0); + + public boolean is(int major, int minor, int patch) { + return major() == major && minor() == minor && patch() == patch; + } + + public boolean is(@NotNull Version version) { + return this.equals(version); + } + + public boolean isAfter(int major, int minor, int patch) { + if (major() > major) { + return true; + } + if (major() < major) { + return false; + } + if (minor() > minor) { + return true; + } + if (minor() < minor) { + return false; + } + return patch() >= patch; + } + + public boolean isAfter(@NotNull Version version) { + return isAfter(version.major(), version.minor(), version.patch()); + } + + public boolean isBefore(int major, int minor, int patch) { + return !isAfter(major, minor, patch); + } + + public boolean isBefore(@NotNull Version version) { + return isBefore(version.major(), version.minor(), version.patch()); + } + + @Override + public int compareTo(Version o) { + if (o.equals(this)) { + return 0; + } + return isAfter(o) ? 1 : -1; + } + + @Override + public @NotNull String toString() { + return toString(false); + } + + public @NotNull String toString(boolean omitPatch) { + if (omitPatch && patch == 0) { + return "%d.%d".formatted(major, minor); + } + return "%d.%d.%d".formatted(major, minor, patch); + } + + public static @NotNull Version parse(@NotNull String string) throws IllegalArgumentException { + Matcher matcher = VERSION_PATTERN.matcher(string.trim()); + if (!matcher.matches()) { + throw new IllegalArgumentException("Malformed version: " + string); + } + + int major = Integer.parseInt(matcher.group(1)); + int minor = Integer.parseInt(matcher.group(2)); + String patchGroup = matcher.group(3); + int patch = patchGroup != null ? Integer.parseInt(patchGroup) : 0; + return new Version(major, minor, patch); + } + + public static @NotNull Version @NotNull [] parseArray(String... versions) { + return Stream.of(versions).map(Version::parse).toArray(Version[]::new); + } +} diff --git a/src/main/resources/de_DE.toml b/src/main/resources/de_DE.toml index 6d02e2adb..9fa4e4633 100644 --- a/src/main/resources/de_DE.toml +++ b/src/main/resources/de_DE.toml @@ -2,2060 +2,2060 @@ [advancement] [advancement.challenge_move_1k] - title = "Muss mich bewegen!" - description = "Laufe über 1 Kilometer (1.000 Blöcke)" +title = "Muss mich bewegen!" +description = "Laufe über 1 Kilometer (1.000 Blöcke)" [advancement.challenge_sprint_5k] - title = "Sprinte 5K!" - description = "Laufe über 5 Kilometer (5.000 Blöcke)" +title = "Sprinte 5K!" +description = "Laufe über 5 Kilometer (5.000 Blöcke)" [advancement.challenge_sprint_50k] - title = "Düse 50K!" - description = "Laufe über 50 Kilometer (50.000 Blöcke)" +title = "Düse 50K!" +description = "Laufe über 50 Kilometer (50.000 Blöcke)" [advancement.challenge_sprint_500k] - title = "Durchquere das Universum!!" - description = "Laufe über 500 Kilometer (500.000 Blöcke)" +title = "Durchquere das Universum!!" +description = "Laufe über 500 Kilometer (500.000 Blöcke)" [advancement.challenge_sprint_marathon] - title = "Sprinte einen (buchstäblichen) Marathon!" - description = "Sprinte über 42.195 Blöcke!" +title = "Sprinte einen (buchstäblichen) Marathon!" +description = "Sprinte über 42.195 Blöcke!" [advancement.challenge_place_1k] - title = "Bauanfänger!" - description = "Platziere 1.000 Blöcke" +title = "Bauanfänger!" +description = "Platziere 1.000 Blöcke" [advancement.challenge_place_5k] - title = "Fortgeschrittener Bauarbeiter!" - description = "Platziere 5.000 Blöcke" +title = "Fortgeschrittener Bauarbeiter!" +description = "Platziere 5.000 Blöcke" [advancement.challenge_place_50k] - title = "Erfahrener Baumeister!" - description = "Platziere 50.000 Blöcke" +title = "Erfahrener Baumeister!" +description = "Platziere 50.000 Blöcke" [advancement.challenge_place_500k] - title = "Meisterbaumeister!" - description = "Platziere 500.000 Blöcke" +title = "Meisterbaumeister!" +description = "Platziere 500.000 Blöcke" [advancement.challenge_place_5m] - title = "Akolyth der Symmetrie!" - description = "REALITÄT IST DEIN SPIELPLATZ! (5 Millionen Blöcke)" +title = "Akolyth der Symmetrie!" +description = "REALITÄT IST DEIN SPIELPLATZ! (5 Millionen Blöcke)" [advancement.challenge_chop_1k] - title = "Holzfäller-Anfänger!" - description = "Fälle 1.000 Blöcke" +title = "Holzfäller-Anfänger!" +description = "Fälle 1.000 Blöcke" [advancement.challenge_chop_5k] - title = "Fortgeschrittener Holzfäller!" - description = "Fälle 5.000 Blöcke" +title = "Fortgeschrittener Holzfäller!" +description = "Fälle 5.000 Blöcke" [advancement.challenge_chop_50k] - title = "Erfahrener Holzfäller!" - description = "Fälle 50.000 Blöcke" +title = "Erfahrener Holzfäller!" +description = "Fälle 50.000 Blöcke" [advancement.challenge_chop_500k] - title = "Meister-Holzfäller!" - description = "Fälle 500.000 Blöcke" +title = "Meister-Holzfäller!" +description = "Fälle 500.000 Blöcke" [advancement.challenge_chop_5m] - title = "Jackson the Dog" - description = "Der allerbeste brave Junge! (5 Millionen Blöcke)" +title = "Jackson the Dog" +description = "Der allerbeste brave Junge! (5 Millionen Blöcke)" [advancement.challenge_block_1k] - title = "Kaum geblockt!" - description = "Blockiere 1000 Treffer" +title = "Kaum geblockt!" +description = "Blockiere 1000 Treffer" [advancement.challenge_block_5k] - title = "Blocken macht Spaß!" - description = "Blockiere 5000 Treffer" +title = "Blocken macht Spaß!" +description = "Blockiere 5000 Treffer" [advancement.challenge_block_50k] - title = "Blocken ist mein Leben!" - description = "Blockiere 50.000 Treffer" +title = "Blocken ist mein Leben!" +description = "Blockiere 50.000 Treffer" [advancement.challenge_block_500k] - title = "Blocken ist meine Bestimmung!" - description = "Blockiere 500.000 Treffer" +title = "Blocken ist meine Bestimmung!" +description = "Blockiere 500.000 Treffer" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Blockiere 5.000.000 Treffer" +title = "Die Hand Die Verletzt" +description = "Blockiere 5.000.000 Treffer" [advancement.challenge_brew_1k] - title = "Alchemisten-Anfänger!" - description = "Konsumiere 1000 Tränke" +title = "Alchemisten-Anfänger!" +description = "Konsumiere 1000 Tränke" [advancement.challenge_brew_5k] - title = "Fortgeschrittener Alchemist!" - description = "Konsumiere 5000 Tränke" +title = "Fortgeschrittener Alchemist!" +description = "Konsumiere 5000 Tränke" [advancement.challenge_brew_50k] - title = "Erfahrener Alchemist!" - description = "Konsumiere 50.000 Tränke" +title = "Erfahrener Alchemist!" +description = "Konsumiere 50.000 Tränke" [advancement.challenge_brew_500k] - title = "Meister-Alchemist!" - description = "Konsumiere 500.000 Tränke" +title = "Meister-Alchemist!" +description = "Konsumiere 500.000 Tränke" [advancement.challenge_brew_5m] - title = "Der Alchemist" - description = "Konsumiere 5.000.000 Tränke" +title = "Der Alchemist" +description = "Konsumiere 5.000.000 Tränke" [advancement.challenge_brewsplash_1k] - title = "Wurftrank-Anfänger!" - description = "Wirf 1000 Tränke" +title = "Wurftrank-Anfänger!" +description = "Wirf 1000 Tränke" [advancement.challenge_brewsplash_5k] - title = "Fortgeschrittener Trankwerfer!" - description = "Wirf 5000 Tränke" +title = "Fortgeschrittener Trankwerfer!" +description = "Wirf 5000 Tränke" [advancement.challenge_brewsplash_50k] - title = "Erfahrener Trankwerfer!" - description = "Wirf 50.000 Tränke" +title = "Erfahrener Trankwerfer!" +description = "Wirf 50.000 Tränke" [advancement.challenge_brewsplash_500k] - title = "Meister-Trankwerfer!" - description = "Wirf 500.000 Tränke" +title = "Meister-Trankwerfer!" +description = "Wirf 500.000 Tränke" [advancement.challenge_brewsplash_5m] - title = "Der Splash-Meister" - description = "Wirf 5.000.000 Tränke" +title = "Der Splash-Meister" +description = "Wirf 5.000.000 Tränke" [advancement.challenge_craft_1k] - title = "Schlauer Handwerker!" - description = "Stelle 1000 Gegenstände her" +title = "Schlauer Handwerker!" +description = "Stelle 1000 Gegenstände her" [advancement.challenge_craft_5k] - title = "Mürrischer Handwerker!" - description = "Stelle 5000 Gegenstände her" +title = "Mürrischer Handwerker!" +description = "Stelle 5000 Gegenstände her" [advancement.challenge_craft_50k] - title = "Unterwürfiger Handwerker!" - description = "Stelle 50.000 Gegenstände her" +title = "Unterwürfiger Handwerker!" +description = "Stelle 50.000 Gegenstände her" [advancement.challenge_craft_500k] - title = "Kakophonischer Handwerker!" - description = "Stelle 500.000 Gegenstände her" +title = "Kakophonischer Handwerker!" +description = "Stelle 500.000 Gegenstände her" [advancement.challenge_craft_5m] - title = "Verhängnisvolles McCraftface" - description = "Stelle 5.000.000 Gegenstände her" +title = "Verhängnisvolles McCraftface" +description = "Stelle 5.000.000 Gegenstände her" [advancement.challenge_enchant_1k] - title = "Verzauberer-Anfänger!" - description = "Verzaubere 1000 Gegenstände" +title = "Verzauberer-Anfänger!" +description = "Verzaubere 1000 Gegenstände" [advancement.challenge_enchant_5k] - title = "Fortgeschrittener Verzauberer!" - description = "Verzaubere 5000 Gegenstände" +title = "Fortgeschrittener Verzauberer!" +description = "Verzaubere 5000 Gegenstände" [advancement.challenge_enchant_50k] - title = "Erfahrener Verzauberer!" - description = "Verzaubere 50.000 Gegenstände" +title = "Erfahrener Verzauberer!" +description = "Verzaubere 50.000 Gegenstände" [advancement.challenge_enchant_500k] - title = "Meister-Verzauberer!" - description = "Verzaubere 500.000 Gegenstände" +title = "Meister-Verzauberer!" +description = "Verzaubere 500.000 Gegenstände" [advancement.challenge_enchant_5m] - title = "Rätselhafter Verzauberer" - description = "Verzaubere 5.000.000 Gegenstände" +title = "Rätselhafter Verzauberer" +description = "Verzaubere 5.000.000 Gegenstände" [advancement.challenge_excavate_1k] - title = "Begeisterter Ausgräber!" - description = "Grabe 1000 Blöcke aus" +title = "Begeisterter Ausgräber!" +description = "Grabe 1000 Blöcke aus" [advancement.challenge_excavate_5k] - title = "Fortgeschrittener Ausgräber!" - description = "Grabe 5000 Blöcke aus" +title = "Fortgeschrittener Ausgräber!" +description = "Grabe 5000 Blöcke aus" [advancement.challenge_excavate_50k] - title = "Erfahrener Ausgräber!" - description = "Grabe 50.000 Blöcke aus" +title = "Erfahrener Ausgräber!" +description = "Grabe 50.000 Blöcke aus" [advancement.challenge_excavate_500k] - title = "Meister-Ausgräber!" - description = "Grabe 500.000 Blöcke aus" +title = "Meister-Ausgräber!" +description = "Grabe 500.000 Blöcke aus" [advancement.challenge_excavate_5m] - title = "Rätselhafter Ausgräber" - description = "Grabe 5.000.000 Blöcke aus" +title = "Rätselhafter Ausgräber" +description = "Grabe 5.000.000 Blöcke aus" [advancement.horrible_person] - title = "Du bist eine schreckliche Person" - description = "Unvorstellbar, wirklich" +title = "Du bist eine schreckliche Person" +description = "Unvorstellbar, wirklich" [advancement.challenge_turtle_egg_smasher] - title = "Schildkrötenei-Zertrümmerer!" - description = "Zerbrich 100 Schildkröteneier" +title = "Schildkrötenei-Zertrümmerer!" +description = "Zerbrich 100 Schildkröteneier" [advancement.challenge_turtle_egg_annihilator] - title = "Schildkrötenei-Vernichter!" - description = "Zerbrich 500 Schildkröteneier" +title = "Schildkrötenei-Vernichter!" +description = "Zerbrich 500 Schildkröteneier" [advancement.challenge_novice_hunter] - title = "Jäger-Anfänger!" - description = "Töte 100 Wesen" +title = "Jäger-Anfänger!" +description = "Töte 100 Wesen" [advancement.challenge_intermediate_hunter] - title = "Fortgeschrittener Jäger!" - description = "Töte 500 Wesen" +title = "Fortgeschrittener Jäger!" +description = "Töte 500 Wesen" [advancement.challenge_advanced_hunter] - title = "Erfahrener Jäger!" - description = "Töte 5000 Wesen" +title = "Erfahrener Jäger!" +description = "Töte 5000 Wesen" [advancement.challenge_creeper_conqueror] - title = "Creeper-Eroberer!" - description = "Töte 50 Creeper" +title = "Creeper-Eroberer!" +description = "Töte 50 Creeper" [advancement.challenge_creeper_annihilator] - title = "Creeper-Vernichter!" - description = "Töte 200 Creeper" +title = "Creeper-Vernichter!" +description = "Töte 200 Creeper" [advancement.challenge_pickaxe_1k] - title = "Bergmann-Anfänger" - description = "Zerbrich 1000 Blöcke" +title = "Bergmann-Anfänger" +description = "Zerbrich 1000 Blöcke" [advancement.challenge_pickaxe_5k] - title = "Geschickter Bergmann" - description = "Zerbrich 5000 Blöcke" +title = "Geschickter Bergmann" +description = "Zerbrich 5000 Blöcke" [advancement.challenge_pickaxe_50k] - title = "Erfahrener Bergmann" - description = "Zerbrich 50.000 Blöcke" +title = "Erfahrener Bergmann" +description = "Zerbrich 50.000 Blöcke" [advancement.challenge_pickaxe_500k] - title = "Meister-Bergmann" - description = "Zerbrich 500.000 Blöcke" +title = "Meister-Bergmann" +description = "Zerbrich 500.000 Blöcke" [advancement.challenge_pickaxe_5m] - title = "Legendärer Bergmann" - description = "Zerbrich 5.000.000 Blöcke" +title = "Legendärer Bergmann" +description = "Zerbrich 5.000.000 Blöcke" [advancement.challenge_eat_100] - title = "So viel zu essen!" - description = "Iss über 100 Gegenstände!" +title = "So viel zu essen!" +description = "Iss über 100 Gegenstände!" [advancement.challenge_eat_1000] - title = "Unstillbarer Hunger!" - description = "Iss über 1.000 Gegenstände!" +title = "Unstillbarer Hunger!" +description = "Iss über 1.000 Gegenstände!" [advancement.challenge_eat_10000] - title = "EWIGER HUNGER!" - description = "Iss über 10.000 Gegenstände!" +title = "EWIGER HUNGER!" +description = "Iss über 10.000 Gegenstände!" [advancement.challenge_harvest_100] - title = "Volle Ernte" - description = "Ernte über 100 Feldfrüchte!" +title = "Volle Ernte" +description = "Ernte über 100 Feldfrüchte!" [advancement.challenge_harvest_1000] - title = "Große Ernte" - description = "Ernte über 1.000 Feldfrüchte!" +title = "Große Ernte" +description = "Ernte über 1.000 Feldfrüchte!" [advancement.challenge_swim_1nm] - title = "Menschliches U-Boot!" - description = "Schwimme 1 Seemeile (1.852 Blöcke)" +title = "Menschliches U-Boot!" +description = "Schwimme 1 Seemeile (1.852 Blöcke)" [advancement.challenge_sneak_1k] - title = "Knieschmerzen" - description = "Schleiche über einen Kilometer (1.000 Blöcke)" +title = "Knieschmerzen" +description = "Schleiche über einen Kilometer (1.000 Blöcke)" [advancement.challenge_sneak_5k] - title = "Schattenwandler" - description = "Schleiche über 5.000 Blöcke" +title = "Schattenwandler" +description = "Schleiche über 5.000 Blöcke" [advancement.challenge_sneak_20k] - title = "Geist" - description = "Schleiche über 20.000 Blöcke" +title = "Geist" +description = "Schleiche über 20.000 Blöcke" [advancement.challenge_swim_5k] - title = "Tieftaucher" - description = "Schwimme über 5.000 Blöcke" +title = "Tieftaucher" +description = "Schwimme über 5.000 Blöcke" [advancement.challenge_swim_20k] - title = "Poseidons Auserwählter" - description = "Schwimme über 20.000 Blöcke" +title = "Poseidons Auserwählter" +description = "Schwimme über 20.000 Blöcke" [advancement.challenge_sword_100] - title = "Erstes Blut" - description = "Lande 100 Treffer mit einem Schwert" +title = "Erstes Blut" +description = "Lande 100 Treffer mit einem Schwert" [advancement.challenge_sword_1k] - title = "Klingentänzer" - description = "Lande 1.000 Treffer mit einem Schwert" +title = "Klingentänzer" +description = "Lande 1.000 Treffer mit einem Schwert" [advancement.challenge_sword_10k] - title = "Tausend Schnitte" - description = "Lande 10.000 Treffer mit einem Schwert" +title = "Tausend Schnitte" +description = "Lande 10.000 Treffer mit einem Schwert" [advancement.challenge_unarmed_100] - title = "Kneipenschläger" - description = "Lande 100 unbewaffnete Treffer" +title = "Kneipenschläger" +description = "Lande 100 unbewaffnete Treffer" [advancement.challenge_unarmed_1k] - title = "Eiserne Fäuste" - description = "Lande 1.000 unbewaffnete Treffer" +title = "Eiserne Fäuste" +description = "Lande 1.000 unbewaffnete Treffer" [advancement.challenge_unarmed_10k] - title = "One Punch" - description = "Lande 10.000 unbewaffnete Treffer" +title = "One Punch" +description = "Lande 10.000 unbewaffnete Treffer" [advancement.challenge_trag_1k] - title = "Blutpreis" - description = "Erleide 1.000 Schaden" +title = "Blutpreis" +description = "Erleide 1.000 Schaden" [advancement.challenge_trag_10k] - title = "Blutstrom" - description = "Erleide 10.000 Schaden" +title = "Blutstrom" +description = "Erleide 10.000 Schaden" [advancement.challenge_trag_100k] - title = "Avatar des Leidens" - description = "Erleide 100.000 Schaden" +title = "Avatar des Leidens" +description = "Erleide 100.000 Schaden" [advancement.challenge_ranged_100] - title = "Zielübung" - description = "Feuere 100 Projektile ab" +title = "Zielübung" +description = "Feuere 100 Projektile ab" [advancement.challenge_ranged_1k] - title = "Falkenauge" - description = "Feuere 1.000 Projektile ab" +title = "Falkenauge" +description = "Feuere 1.000 Projektile ab" [advancement.challenge_ranged_10k] - title = "Pfeilsturm" - description = "Feuere 10.000 Projektile ab" +title = "Pfeilsturm" +description = "Feuere 10.000 Projektile ab" [advancement.challenge_chronos_1h] - title = "Tick Tack" - description = "Verbringe 1 Stunde online" +title = "Tick Tack" +description = "Verbringe 1 Stunde online" [advancement.challenge_chronos_24h] - title = "Sand der Zeit" - description = "Verbringe 24 Stunden online" +title = "Sand der Zeit" +description = "Verbringe 24 Stunden online" [advancement.challenge_chronos_168h] - title = "Zeitlos" - description = "Verbringe 168 Stunden (1 Woche) online" +title = "Zeitlos" +description = "Verbringe 168 Stunden (1 Woche) online" [advancement.challenge_nether_50] - title = "Höllenpförtner" - description = "Erschlage 50 Netherwesen" +title = "Höllenpförtner" +description = "Erschlage 50 Netherwesen" [advancement.challenge_nether_500] - title = "Abgrundwächter" - description = "Erschlage 500 Netherwesen" +title = "Abgrundwächter" +description = "Erschlage 500 Netherwesen" [advancement.challenge_nether_5k] - title = "Herr des Nethers" - description = "Erschlage 5.000 Netherwesen" +title = "Herr des Nethers" +description = "Erschlage 5.000 Netherwesen" [advancement.challenge_rift_50] - title = "Raumanomalie" - description = "Teleportiere dich 50 Mal" +title = "Raumanomalie" +description = "Teleportiere dich 50 Mal" [advancement.challenge_rift_500] - title = "Leerenwandler" - description = "Teleportiere dich 500 Mal" +title = "Leerenwandler" +description = "Teleportiere dich 500 Mal" [advancement.challenge_rift_5k] - title = "Zwischen den Welten" - description = "Teleportiere dich 5.000 Mal" +title = "Zwischen den Welten" +description = "Teleportiere dich 5.000 Mal" [advancement.challenge_taming_10] - title = "Tierflüsterer" - description = "Züchte 10 Tiere" +title = "Tierflüsterer" +description = "Züchte 10 Tiere" [advancement.challenge_taming_50] - title = "Rudelführer" - description = "Züchte 50 Tiere" +title = "Rudelführer" +description = "Züchte 50 Tiere" [advancement.challenge_taming_500] - title = "Bestienmeister" - description = "Züchte 500 Tiere" +title = "Bestienmeister" +description = "Züchte 500 Tiere" # Agility [advancement.challenge_sprint_dist_5k] - title = "Geschwindigkeitsdaemon" - description = "Sprinte ueber 5 Kilometer (5.000 Bloecke)" +title = "Geschwindigkeitsdaemon" +description = "Sprinte ueber 5 Kilometer (5.000 Bloecke)" [advancement.challenge_sprint_dist_50k] - title = "Blitzbeine" - description = "Sprinte ueber 50 Kilometer (50.000 Bloecke)" +title = "Blitzbeine" +description = "Sprinte ueber 50 Kilometer (50.000 Bloecke)" [advancement.challenge_agility_swim_1k] - title = "Wasserlaeufer" - description = "Schwimme ueber 1 Kilometer (1.000 Bloecke)" +title = "Wasserlaeufer" +description = "Schwimme ueber 1 Kilometer (1.000 Bloecke)" [advancement.challenge_agility_swim_10k] - title = "Ozeanreisender" - description = "Schwimme ueber 10 Kilometer (10.000 Bloecke)" +title = "Ozeanreisender" +description = "Schwimme ueber 10 Kilometer (10.000 Bloecke)" [advancement.challenge_fly_1k] - title = "Himmelstaenzer" - description = "Fliege ueber 1 Kilometer (1.000 Bloecke)" +title = "Himmelstaenzer" +description = "Fliege ueber 1 Kilometer (1.000 Bloecke)" [advancement.challenge_fly_10k] - title = "Windreiter" - description = "Fliege ueber 10 Kilometer (10.000 Bloecke)" +title = "Windreiter" +description = "Fliege ueber 10 Kilometer (10.000 Bloecke)" [advancement.challenge_agility_sneak_500] - title = "Leise Schritte" - description = "Schleiche ueber 500 Bloecke" +title = "Leise Schritte" +description = "Schleiche ueber 500 Bloecke" [advancement.challenge_agility_sneak_5k] - title = "Phantomschritte" - description = "Schleiche ueber 5 Kilometer (5.000 Bloecke)" +title = "Phantomschritte" +description = "Schleiche ueber 5 Kilometer (5.000 Bloecke)" # Architect [advancement.challenge_demolish_500] - title = "Abrisstruppp" - description = "Baue 500 Bloecke ab" +title = "Abrisstruppp" +description = "Baue 500 Bloecke ab" [advancement.challenge_demolish_5k] - title = "Abrissbirne" - description = "Baue 5.000 Bloecke ab" +title = "Abrissbirne" +description = "Baue 5.000 Bloecke ab" [advancement.challenge_value_placed_10k] - title = "Wertvoller Baumeister" - description = "Platziere Bloecke im Wert von 10.000" +title = "Wertvoller Baumeister" +description = "Platziere Bloecke im Wert von 10.000" [advancement.challenge_value_placed_100k] - title = "Meisterarchitekt" - description = "Platziere Bloecke im Wert von 100.000" +title = "Meisterarchitekt" +description = "Platziere Bloecke im Wert von 100.000" [advancement.challenge_demolish_val_5k] - title = "Bergungsexperte" - description = "Berge 5.000 Blockwert durch Abriss" +title = "Bergungsexperte" +description = "Berge 5.000 Blockwert durch Abriss" [advancement.challenge_demolish_val_50k] - title = "Totaler Rueckbau" - description = "Berge 50.000 Blockwert durch Abriss" +title = "Totaler Rueckbau" +description = "Berge 50.000 Blockwert durch Abriss" [advancement.challenge_high_build_100] - title = "Himmelsbauer" - description = "Platziere 100 Bloecke ueber Y=128" +title = "Himmelsbauer" +description = "Platziere 100 Bloecke ueber Y=128" [advancement.challenge_high_build_1k] - title = "Wolkenarchitekt" - description = "Platziere 1.000 Bloecke ueber Y=128" +title = "Wolkenarchitekt" +description = "Platziere 1.000 Bloecke ueber Y=128" # Axes [advancement.challenge_axe_swing_500] - title = "Axtschwiniger" - description = "Schwinge deine Axt 500 Mal" +title = "Axtschwiniger" +description = "Schwinge deine Axt 500 Mal" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Schwinge deine Axt 5.000 Mal" +title = "Berserker" +description = "Schwinge deine Axt 5.000 Mal" [advancement.challenge_axe_damage_1k] - title = "Spalter" - description = "Verursache 1.000 Schaden mit Aexten" +title = "Spalter" +description = "Verursache 1.000 Schaden mit Aexten" [advancement.challenge_axe_damage_10k] - title = "Henkersbeil" - description = "Verursache 10.000 Schaden mit Aexten" +title = "Henkersbeil" +description = "Verursache 10.000 Schaden mit Aexten" [advancement.challenge_axe_value_5k] - title = "Holzhaendler" - description = "Ernte Holz im Wert von 5.000" +title = "Holzhaendler" +description = "Ernte Holz im Wert von 5.000" [advancement.challenge_axe_value_50k] - title = "Holzbaron" - description = "Ernte Holz im Wert von 50.000" +title = "Holzbaron" +description = "Ernte Holz im Wert von 50.000" [advancement.challenge_leaves_500] - title = "Laubblaeser" - description = "Entferne 500 Laubbloecke mit einer Axt" +title = "Laubblaeser" +description = "Entferne 500 Laubbloecke mit einer Axt" [advancement.challenge_leaves_5k] - title = "Entlauber" - description = "Entferne 5.000 Laubbloecke mit einer Axt" +title = "Entlauber" +description = "Entferne 5.000 Laubbloecke mit einer Axt" # Blocking [advancement.challenge_block_dmg_1k] - title = "Schadensabsorber" - description = "Blocke 1.000 Schaden mit einem Schild" +title = "Schadensabsorber" +description = "Blocke 1.000 Schaden mit einem Schild" [advancement.challenge_block_dmg_10k] - title = "Menschliches Schutzschild" - description = "Blocke 10.000 Schaden mit einem Schild" +title = "Menschliches Schutzschild" +description = "Blocke 10.000 Schaden mit einem Schild" [advancement.challenge_block_proj_100] - title = "Pfeilabwehrer" - description = "Blocke 100 Projektile mit einem Schild" +title = "Pfeilabwehrer" +description = "Blocke 100 Projektile mit einem Schild" [advancement.challenge_block_proj_1k] - title = "Projektilschild" - description = "Blocke 1.000 Projektile mit einem Schild" +title = "Projektilschild" +description = "Blocke 1.000 Projektile mit einem Schild" [advancement.challenge_block_melee_500] - title = "Pariermeister" - description = "Blocke 500 Nahkampfangriffe mit einem Schild" +title = "Pariermeister" +description = "Blocke 500 Nahkampfangriffe mit einem Schild" [advancement.challenge_block_melee_5k] - title = "Eiserne Festung" - description = "Blocke 5.000 Nahkampfangriffe mit einem Schild" +title = "Eiserne Festung" +description = "Blocke 5.000 Nahkampfangriffe mit einem Schild" [advancement.challenge_block_heavy_50] - title = "Panzer" - description = "Blocke 50 schwere Angriffe (ueber 5 Schaden)" +title = "Panzer" +description = "Blocke 50 schwere Angriffe (ueber 5 Schaden)" [advancement.challenge_block_heavy_500] - title = "Unbewegliches Objekt" - description = "Blocke 500 schwere Angriffe (ueber 5 Schaden)" +title = "Unbewegliches Objekt" +description = "Blocke 500 schwere Angriffe (ueber 5 Schaden)" # Brewing [advancement.challenge_brew_stands_10] - title = "Brauerei-Einrichtung" - description = "Platziere 10 Braustaende" +title = "Brauerei-Einrichtung" +description = "Platziere 10 Braustaende" [advancement.challenge_brew_stands_50] - title = "Trankfabrik" - description = "Platziere 50 Braustaende" +title = "Trankfabrik" +description = "Platziere 50 Braustaende" [advancement.challenge_brew_strong_25] - title = "Starkes Gebraeu" - description = "Trinke 25 verstaerkte Traenke" +title = "Starkes Gebraeu" +description = "Trinke 25 verstaerkte Traenke" [advancement.challenge_brew_strong_250] - title = "Maximale Wirkung" - description = "Trinke 250 verstaerkte Traenke" +title = "Maximale Wirkung" +description = "Trinke 250 verstaerkte Traenke" [advancement.challenge_brew_splash_hits_50] - title = "Spritzzone" - description = "Triff 50 Wesen mit Wurf-Traenken" +title = "Spritzzone" +description = "Triff 50 Wesen mit Wurf-Traenken" [advancement.challenge_brew_splash_hits_500] - title = "Pestdoktor" - description = "Triff 500 Wesen mit Wurf-Traenken" +title = "Pestdoktor" +description = "Triff 500 Wesen mit Wurf-Traenken" # Chronos [advancement.challenge_active_dist_1k] - title = "Rastlos" - description = "Reise 1 Kilometer waehrend du aktiv bist" +title = "Rastlos" +description = "Reise 1 Kilometer waehrend du aktiv bist" [advancement.challenge_active_dist_10k] - title = "Pfadfinder" - description = "Reise 10 Kilometer waehrend du aktiv bist" +title = "Pfadfinder" +description = "Reise 10 Kilometer waehrend du aktiv bist" [advancement.challenge_active_dist_100k] - title = "Ewige Bewegung" - description = "Reise 100 Kilometer waehrend du aktiv bist" +title = "Ewige Bewegung" +description = "Reise 100 Kilometer waehrend du aktiv bist" [advancement.challenge_beds_10] - title = "Fruehaufsteher" - description = "Schlafe 10 Mal in einem Bett" +title = "Fruehaufsteher" +description = "Schlafe 10 Mal in einem Bett" [advancement.challenge_beds_100] - title = "Zeitueberbruecker" - description = "Schlafe 100 Mal in einem Bett" +title = "Zeitueberbruecker" +description = "Schlafe 100 Mal in einem Bett" [advancement.challenge_chronos_tp_50] - title = "Zeitverschiebung" - description = "Teleportiere dich 50 Mal" +title = "Zeitverschiebung" +description = "Teleportiere dich 50 Mal" [advancement.challenge_chronos_tp_500] - title = "Zeitsprung" - description = "Teleportiere dich 500 Mal" +title = "Zeitsprung" +description = "Teleportiere dich 500 Mal" [advancement.challenge_chronos_deaths_10] - title = "Sterblich" - description = "Stirb 10 Mal" +title = "Sterblich" +description = "Stirb 10 Mal" [advancement.challenge_chronos_deaths_100] - title = "Todestrotzer" - description = "Stirb 100 Mal" +title = "Todestrotzer" +description = "Stirb 100 Mal" # Crafting [advancement.challenge_craft_value_10k] - title = "Handwerkswert" - description = "Stelle Gegenstaende im Wert von 10.000 her" +title = "Handwerkswert" +description = "Stelle Gegenstaende im Wert von 10.000 her" [advancement.challenge_craft_value_100k] - title = "Kunsthandwerker" - description = "Stelle Gegenstaende im Wert von 100.000 her" +title = "Kunsthandwerker" +description = "Stelle Gegenstaende im Wert von 100.000 her" [advancement.challenge_craft_tools_25] - title = "Werkzeugschmied" - description = "Stelle 25 Werkzeuge her" +title = "Werkzeugschmied" +description = "Stelle 25 Werkzeuge her" [advancement.challenge_craft_tools_250] - title = "Meisterschmied" - description = "Stelle 250 Werkzeuge her" +title = "Meisterschmied" +description = "Stelle 250 Werkzeuge her" [advancement.challenge_craft_armor_25] - title = "Ruestungsschmied" - description = "Stelle 25 Ruestungsteile her" +title = "Ruestungsschmied" +description = "Stelle 25 Ruestungsteile her" [advancement.challenge_craft_armor_250] - title = "Ruestungsmeister" - description = "Stelle 250 Ruestungsteile her" +title = "Ruestungsmeister" +description = "Stelle 250 Ruestungsteile her" [advancement.challenge_craft_mass_25k] - title = "Massenproduzent" - description = "Stelle 25.000 Gegenstaende her" +title = "Massenproduzent" +description = "Stelle 25.000 Gegenstaende her" [advancement.challenge_craft_mass_250k] - title = "Industrielle Revolution" - description = "Stelle 250.000 Gegenstaende her" +title = "Industrielle Revolution" +description = "Stelle 250.000 Gegenstaende her" # Discovery [advancement.challenge_discover_items_50] - title = "Sammler" - description = "Entdecke 50 einzigartige Gegenstaende" +title = "Sammler" +description = "Entdecke 50 einzigartige Gegenstaende" [advancement.challenge_discover_items_250] - title = "Katalogisierer" - description = "Entdecke 250 einzigartige Gegenstaende" +title = "Katalogisierer" +description = "Entdecke 250 einzigartige Gegenstaende" [advancement.challenge_discover_blocks_50] - title = "Vermesser" - description = "Entdecke 50 einzigartige Bloecke" +title = "Vermesser" +description = "Entdecke 50 einzigartige Bloecke" [advancement.challenge_discover_blocks_250] - title = "Geologe" - description = "Entdecke 250 einzigartige Bloecke" +title = "Geologe" +description = "Entdecke 250 einzigartige Bloecke" [advancement.challenge_discover_mobs_25] - title = "Beobachter" - description = "Entdecke 25 einzigartige Kreaturen" +title = "Beobachter" +description = "Entdecke 25 einzigartige Kreaturen" [advancement.challenge_discover_mobs_75] - title = "Naturforscher" - description = "Entdecke 75 einzigartige Kreaturen" +title = "Naturforscher" +description = "Entdecke 75 einzigartige Kreaturen" [advancement.challenge_discover_biomes_10] - title = "Wanderer" - description = "Entdecke 10 einzigartige Biome" +title = "Wanderer" +description = "Entdecke 10 einzigartige Biome" [advancement.challenge_discover_biomes_40] - title = "Weltreisender" - description = "Entdecke 40 einzigartige Biome" +title = "Weltreisender" +description = "Entdecke 40 einzigartige Biome" [advancement.challenge_discover_foods_10] - title = "Feinschmecker" - description = "Entdecke 10 einzigartige Nahrungsmittel" +title = "Feinschmecker" +description = "Entdecke 10 einzigartige Nahrungsmittel" [advancement.challenge_discover_foods_30] - title = "Kuechenmeister" - description = "Entdecke 30 einzigartige Nahrungsmittel" +title = "Kuechenmeister" +description = "Entdecke 30 einzigartige Nahrungsmittel" # Enchanting [advancement.challenge_enchant_power_100] - title = "Kraftweber" - description = "Sammle 100 Verzauberungskraft" +title = "Kraftweber" +description = "Sammle 100 Verzauberungskraft" [advancement.challenge_enchant_power_1k] - title = "Arkaner Meister" - description = "Sammle 1.000 Verzauberungskraft" +title = "Arkaner Meister" +description = "Sammle 1.000 Verzauberungskraft" [advancement.challenge_enchant_levels_1k] - title = "Stufenverschwender" - description = "Gib 1.000 Erfahrungsstufen fuer Verzauberungen aus" +title = "Stufenverschwender" +description = "Gib 1.000 Erfahrungsstufen fuer Verzauberungen aus" [advancement.challenge_enchant_levels_10k] - title = "EP-Verschlinger" - description = "Gib 10.000 Erfahrungsstufen fuer Verzauberungen aus" +title = "EP-Verschlinger" +description = "Gib 10.000 Erfahrungsstufen fuer Verzauberungen aus" [advancement.challenge_enchant_high_25] - title = "Hochstapler" - description = "Fuehre 25 Verzauberungen auf hoechster Stufe durch" +title = "Hochstapler" +description = "Fuehre 25 Verzauberungen auf hoechster Stufe durch" [advancement.challenge_enchant_high_250] - title = "Legendaerer Verzauberer" - description = "Fuehre 250 Verzauberungen auf hoechster Stufe durch" +title = "Legendaerer Verzauberer" +description = "Fuehre 250 Verzauberungen auf hoechster Stufe durch" [advancement.challenge_enchant_total_500] - title = "Stufenbrenner" - description = "Gib insgesamt 500 Stufen fuer Verzauberungen aus" +title = "Stufenbrenner" +description = "Gib insgesamt 500 Stufen fuer Verzauberungen aus" [advancement.challenge_enchant_total_5k] - title = "Arkane Investition" - description = "Gib insgesamt 5.000 Stufen fuer Verzauberungen aus" +title = "Arkane Investition" +description = "Gib insgesamt 5.000 Stufen fuer Verzauberungen aus" # Excavation [advancement.challenge_dig_swing_500] - title = "Graeber" - description = "Schwinge deine Schaufel 500 Mal" +title = "Graeber" +description = "Schwinge deine Schaufel 500 Mal" [advancement.challenge_dig_swing_5k] - title = "Baggerfahrer" - description = "Schwinge deine Schaufel 5.000 Mal" +title = "Baggerfahrer" +description = "Schwinge deine Schaufel 5.000 Mal" [advancement.challenge_dig_damage_1k] - title = "Schaufelritter" - description = "Verursache 1.000 Schaden mit einer Schaufel" +title = "Schaufelritter" +description = "Verursache 1.000 Schaden mit einer Schaufel" [advancement.challenge_dig_damage_10k] - title = "Schaufelmeister" - description = "Verursache 10.000 Schaden mit einer Schaufel" +title = "Schaufelmeister" +description = "Verursache 10.000 Schaden mit einer Schaufel" [advancement.challenge_dig_value_5k] - title = "Erdhaendler" - description = "Grabe Bloecke im Wert von 5.000 aus" +title = "Erdhaendler" +description = "Grabe Bloecke im Wert von 5.000 aus" [advancement.challenge_dig_value_50k] - title = "Erdbaron" - description = "Grabe Bloecke im Wert von 50.000 aus" +title = "Erdbaron" +description = "Grabe Bloecke im Wert von 50.000 aus" [advancement.challenge_dig_gravel_500] - title = "Kiesmalmer" - description = "Grabe 500 Kies-, Sand- oder Lehmbloecke" +title = "Kiesmalmer" +description = "Grabe 500 Kies-, Sand- oder Lehmbloecke" [advancement.challenge_dig_gravel_5k] - title = "Sandsieber" - description = "Grabe 5.000 Kies-, Sand- oder Lehmbloecke" +title = "Sandsieber" +description = "Grabe 5.000 Kies-, Sand- oder Lehmbloecke" # Herbalism [advancement.challenge_plant_100] - title = "Saemann" - description = "Pflanze 100 Feldfrueche" +title = "Saemann" +description = "Pflanze 100 Feldfrueche" [advancement.challenge_plant_1k] - title = "Gruener Daumen" - description = "Pflanze 1.000 Feldfrueche" +title = "Gruener Daumen" +description = "Pflanze 1.000 Feldfrueche" [advancement.challenge_plant_5k] - title = "Ackerbaron" - description = "Pflanze 5.000 Feldfrueche" +title = "Ackerbaron" +description = "Pflanze 5.000 Feldfrueche" [advancement.challenge_compost_50] - title = "Recycler" - description = "Kompostiere 50 Gegenstaende" +title = "Recycler" +description = "Kompostiere 50 Gegenstaende" [advancement.challenge_compost_500] - title = "Bodenveredler" - description = "Kompostiere 500 Gegenstaende" +title = "Bodenveredler" +description = "Kompostiere 500 Gegenstaende" [advancement.challenge_shear_50] - title = "Scherer" - description = "Schere 50 Wesen" +title = "Scherer" +description = "Schere 50 Wesen" [advancement.challenge_shear_250] - title = "Herdenmeister" - description = "Schere 250 Wesen" +title = "Herdenmeister" +description = "Schere 250 Wesen" # Hunter [advancement.challenge_kills_500] - title = "Toeter" - description = "Erschlage 500 Kreaturen" +title = "Toeter" +description = "Erschlage 500 Kreaturen" [advancement.challenge_kills_5k] - title = "Henker" - description = "Erschlage 5.000 Kreaturen" +title = "Henker" +description = "Erschlage 5.000 Kreaturen" [advancement.challenge_boss_1] - title = "Bossherausforderer" - description = "Besiege einen Boss" +title = "Bossherausforderer" +description = "Besiege einen Boss" [advancement.challenge_boss_10] - title = "Legendentoeter" - description = "Besiege 10 Bosse" +title = "Legendentoeter" +description = "Besiege 10 Bosse" # Nether [advancement.challenge_wither_dmg_500] - title = "Verwelkt" - description = "Ertrage 500 Wither-Schaden" +title = "Verwelkt" +description = "Ertrage 500 Wither-Schaden" [advancement.challenge_wither_dmg_5k] - title = "Seuchenueberlebender" - description = "Ertrage 5.000 Wither-Schaden" +title = "Seuchenueberlebender" +description = "Ertrage 5.000 Wither-Schaden" [advancement.challenge_wither_skel_25] - title = "Knochensammler" - description = "Erschlage 25 Witherskelette" +title = "Knochensammler" +description = "Erschlage 25 Witherskelette" [advancement.challenge_wither_skel_250] - title = "Skelettfluch" - description = "Erschlage 250 Witherskelette" +title = "Skelettfluch" +description = "Erschlage 250 Witherskelette" [advancement.challenge_wither_boss_1] - title = "Withertoeter" - description = "Besiege den Wither" +title = "Withertoeter" +description = "Besiege den Wither" [advancement.challenge_wither_boss_10] - title = "Netherbeherrscher" - description = "Besiege den Wither 10 Mal" +title = "Netherbeherrscher" +description = "Besiege den Wither 10 Mal" [advancement.challenge_roses_10] - title = "Todesqaertner" - description = "Zerstoere 10 Witherrosen" +title = "Todesqaertner" +description = "Zerstoere 10 Witherrosen" [advancement.challenge_roses_100] - title = "Seuchensammler" - description = "Zerstoere 100 Witherrosen" +title = "Seuchensammler" +description = "Zerstoere 100 Witherrosen" # Pickaxes [advancement.challenge_pick_swing_500] - title = "Bergmannsarm" - description = "Schwinge deine Spitzhacke 500 Mal" +title = "Bergmannsarm" +description = "Schwinge deine Spitzhacke 500 Mal" [advancement.challenge_pick_swing_5k] - title = "Tunnelgraber" - description = "Schwinge deine Spitzhacke 5.000 Mal" +title = "Tunnelgraber" +description = "Schwinge deine Spitzhacke 5.000 Mal" [advancement.challenge_pick_damage_1k] - title = "Hackenkaempfer" - description = "Verursache 1.000 Schaden mit einer Spitzhacke" +title = "Hackenkaempfer" +description = "Verursache 1.000 Schaden mit einer Spitzhacke" [advancement.challenge_pick_damage_10k] - title = "Kriegshacke" - description = "Verursache 10.000 Schaden mit einer Spitzhacke" +title = "Kriegshacke" +description = "Verursache 10.000 Schaden mit einer Spitzhacke" [advancement.challenge_pick_value_5k] - title = "Edelsteinfinder" - description = "Baue Bloecke im Wert von 5.000 ab" +title = "Edelsteinfinder" +description = "Baue Bloecke im Wert von 5.000 ab" [advancement.challenge_pick_value_50k] - title = "Erzbaron" - description = "Baue Bloecke im Wert von 50.000 ab" +title = "Erzbaron" +description = "Baue Bloecke im Wert von 50.000 ab" [advancement.challenge_pick_ores_500] - title = "Schuerfer" - description = "Baue 500 Erzbloecke ab" +title = "Schuerfer" +description = "Baue 500 Erzbloecke ab" [advancement.challenge_pick_ores_5k] - title = "Meisterbergmann" - description = "Baue 5.000 Erzbloecke ab" +title = "Meisterbergmann" +description = "Baue 5.000 Erzbloecke ab" # Ranged [advancement.challenge_ranged_dmg_1k] - title = "Scharfschuetze" - description = "Verursache 1.000 Fernkampfschaden" +title = "Scharfschuetze" +description = "Verursache 1.000 Fernkampfschaden" [advancement.challenge_ranged_dmg_10k] - title = "Toedlicher Schuetze" - description = "Verursache 10.000 Fernkampfschaden" +title = "Toedlicher Schuetze" +description = "Verursache 10.000 Fernkampfschaden" [advancement.challenge_ranged_dist_5k] - title = "Grosse Reichweite" - description = "Feuere Projektile ueber insgesamt 5.000 Bloecke Entfernung" +title = "Grosse Reichweite" +description = "Feuere Projektile ueber insgesamt 5.000 Bloecke Entfernung" [advancement.challenge_ranged_dist_50k] - title = "Meilenschuetze" - description = "Feuere Projektile ueber insgesamt 50.000 Bloecke Entfernung" +title = "Meilenschuetze" +description = "Feuere Projektile ueber insgesamt 50.000 Bloecke Entfernung" [advancement.challenge_ranged_kills_50] - title = "Bogenschuetze" - description = "Toete 50 Kreaturen mit Fernkampfwaffen" +title = "Bogenschuetze" +description = "Toete 50 Kreaturen mit Fernkampfwaffen" [advancement.challenge_ranged_kills_500] - title = "Meisterbogenschuetze" - description = "Toete 500 Kreaturen mit Fernkampfwaffen" +title = "Meisterbogenschuetze" +description = "Toete 500 Kreaturen mit Fernkampfwaffen" [advancement.challenge_longshot_25] - title = "Heckenschuetze" - description = "Lande 25 Weitschusstreffer (ueber 30 Bloecke)" +title = "Heckenschuetze" +description = "Lande 25 Weitschusstreffer (ueber 30 Bloecke)" [advancement.challenge_longshot_250] - title = "Adlerauge" - description = "Lande 250 Weitschusstreffer (ueber 30 Bloecke)" +title = "Adlerauge" +description = "Lande 250 Weitschusstreffer (ueber 30 Bloecke)" # Rift [advancement.challenge_rift_pearls_50] - title = "Perlenwerfer" - description = "Wirf 50 Enderperlen" +title = "Perlenwerfer" +description = "Wirf 50 Enderperlen" [advancement.challenge_rift_pearls_500] - title = "Teleportsuechtig" - description = "Wirf 500 Enderperlen" +title = "Teleportsuechtig" +description = "Wirf 500 Enderperlen" [advancement.challenge_rift_enderman_50] - title = "Enderman-Jaeger" - description = "Erschlage 50 Endermen" +title = "Enderman-Jaeger" +description = "Erschlage 50 Endermen" [advancement.challenge_rift_enderman_500] - title = "Leerenpirscher" - description = "Erschlage 500 Endermen" +title = "Leerenpirscher" +description = "Erschlage 500 Endermen" [advancement.challenge_rift_dragon_500] - title = "Drachenkaempfer" - description = "Verursache 500 Schaden am Enderdrachen" +title = "Drachenkaempfer" +description = "Verursache 500 Schaden am Enderdrachen" [advancement.challenge_rift_dragon_5k] - title = "Drachentoeter" - description = "Verursache 5.000 Schaden am Enderdrachen" +title = "Drachentoeter" +description = "Verursache 5.000 Schaden am Enderdrachen" [advancement.challenge_rift_crystal_10] - title = "Kristallbrecher" - description = "Zerstoere 10 Endkristalle" +title = "Kristallbrecher" +description = "Zerstoere 10 Endkristalle" [advancement.challenge_rift_crystal_100] - title = "Endzerstoerer" - description = "Zerstoere 100 Endkristalle" +title = "Endzerstoerer" +description = "Zerstoere 100 Endkristalle" # Seaborne [advancement.challenge_fish_25] - title = "Angler" - description = "Fange 25 Fische" +title = "Angler" +description = "Fange 25 Fische" [advancement.challenge_fish_250] - title = "Meisterfischer" - description = "Fange 250 Fische" +title = "Meisterfischer" +description = "Fange 250 Fische" [advancement.challenge_drowned_25] - title = "Ertrunkenen-Jaeger" - description = "Erschlage 25 Ertrunkene" +title = "Ertrunkenen-Jaeger" +description = "Erschlage 25 Ertrunkene" [advancement.challenge_drowned_250] - title = "Ozeanreiniger" - description = "Erschlage 250 Ertrunkene" +title = "Ozeanreiniger" +description = "Erschlage 250 Ertrunkene" [advancement.challenge_guardian_10] - title = "Waechtertoeter" - description = "Erschlage 10 Waechter" +title = "Waechtertoeter" +description = "Erschlage 10 Waechter" [advancement.challenge_guardian_100] - title = "Tempelraeuber" - description = "Erschlage 100 Waechter" +title = "Tempelraeuber" +description = "Erschlage 100 Waechter" [advancement.challenge_underwater_blocks_100] - title = "Unterwasserbergmann" - description = "Baue 100 Bloecke unter Wasser ab" +title = "Unterwasserbergmann" +description = "Baue 100 Bloecke unter Wasser ab" [advancement.challenge_underwater_blocks_1k] - title = "Unterwasseringenieur" - description = "Baue 1.000 Bloecke unter Wasser ab" +title = "Unterwasseringenieur" +description = "Baue 1.000 Bloecke unter Wasser ab" # Stealth [advancement.challenge_stealth_dmg_500] - title = "Meuchler" - description = "Verursache 500 Schaden waehrend du schleichst" +title = "Meuchler" +description = "Verursache 500 Schaden waehrend du schleichst" [advancement.challenge_stealth_dmg_5k] - title = "Lautloser Toeter" - description = "Verursache 5.000 Schaden waehrend du schleichst" +title = "Lautloser Toeter" +description = "Verursache 5.000 Schaden waehrend du schleichst" [advancement.challenge_stealth_kills_10] - title = "Assassine" - description = "Toete 10 Kreaturen waehrend du schleichst" +title = "Assassine" +description = "Toete 10 Kreaturen waehrend du schleichst" [advancement.challenge_stealth_kills_100] - title = "Schattensensenmann" - description = "Toete 100 Kreaturen waehrend du schleichst" +title = "Schattensensenmann" +description = "Toete 100 Kreaturen waehrend du schleichst" [advancement.challenge_stealth_time_1h] - title = "Geduldig" - description = "Verbringe 1 Stunde schleichend (3.600 Sekunden)" +title = "Geduldig" +description = "Verbringe 1 Stunde schleichend (3.600 Sekunden)" [advancement.challenge_stealth_time_10h] - title = "Meister der Schatten" - description = "Verbringe 10 Stunden schleichend (36.000 Sekunden)" +title = "Meister der Schatten" +description = "Verbringe 10 Stunden schleichend (36.000 Sekunden)" [advancement.challenge_stealth_arrows_50] - title = "Stiller Bogenschuetze" - description = "Feuere 50 Pfeile waehrend du schleichst" +title = "Stiller Bogenschuetze" +description = "Feuere 50 Pfeile waehrend du schleichst" [advancement.challenge_stealth_arrows_500] - title = "Phantombogenschuetze" - description = "Feuere 500 Pfeile waehrend du schleichst" +title = "Phantombogenschuetze" +description = "Feuere 500 Pfeile waehrend du schleichst" # Swords [advancement.challenge_sword_dmg_1k] - title = "Klingenlehrling" - description = "Verursache 1.000 Schaden mit Schwertern" +title = "Klingenlehrling" +description = "Verursache 1.000 Schaden mit Schwertern" [advancement.challenge_sword_dmg_10k] - title = "Schwertkaempfer" - description = "Verursache 10.000 Schaden mit Schwertern" +title = "Schwertkaempfer" +description = "Verursache 10.000 Schaden mit Schwertern" [advancement.challenge_sword_kills_50] - title = "Duellant" - description = "Toete 50 Kreaturen mit Schwertern" +title = "Duellant" +description = "Toete 50 Kreaturen mit Schwertern" [advancement.challenge_sword_kills_500] - title = "Gladiator" - description = "Toete 500 Kreaturen mit Schwertern" +title = "Gladiator" +description = "Toete 500 Kreaturen mit Schwertern" [advancement.challenge_sword_crit_50] - title = "Kritischer Treffer" - description = "Lande 50 kritische Treffer mit Schwertern" +title = "Kritischer Treffer" +description = "Lande 50 kritische Treffer mit Schwertern" [advancement.challenge_sword_crit_500] - title = "Praezisionsmeister" - description = "Lande 500 kritische Treffer mit Schwertern" +title = "Praezisionsmeister" +description = "Lande 500 kritische Treffer mit Schwertern" [advancement.challenge_sword_heavy_25] - title = "Schwerer Schlag" - description = "Lande 25 schwere Treffer mit Schwertern (ueber 8 Schaden)" +title = "Schwerer Schlag" +description = "Lande 25 schwere Treffer mit Schwertern (ueber 8 Schaden)" [advancement.challenge_sword_heavy_250] - title = "Verheerender Hieb" - description = "Lande 250 schwere Treffer mit Schwertern (ueber 8 Schaden)" +title = "Verheerender Hieb" +description = "Lande 250 schwere Treffer mit Schwertern (ueber 8 Schaden)" # Taming [advancement.challenge_pet_dmg_500] - title = "Tiertrainer" - description = "Deine Haustiere verursachen insgesamt 500 Schaden" +title = "Tiertrainer" +description = "Deine Haustiere verursachen insgesamt 500 Schaden" [advancement.challenge_pet_dmg_5k] - title = "Kriegsmeister" - description = "Deine Haustiere verursachen insgesamt 5.000 Schaden" +title = "Kriegsmeister" +description = "Deine Haustiere verursachen insgesamt 5.000 Schaden" [advancement.challenge_tamed_10] - title = "Tierfreund" - description = "Zaehme 10 Tiere" +title = "Tierfreund" +description = "Zaehme 10 Tiere" [advancement.challenge_tamed_100] - title = "Zoowaerter" - description = "Zaehme 100 Tiere" +title = "Zoowaerter" +description = "Zaehme 100 Tiere" [advancement.challenge_pet_kills_25] - title = "Rudeltaktik" - description = "Deine Haustiere erschlagen 25 Kreaturen" +title = "Rudeltaktik" +description = "Deine Haustiere erschlagen 25 Kreaturen" [advancement.challenge_pet_kills_250] - title = "Alphakommandant" - description = "Deine Haustiere erschlagen 250 Kreaturen" +title = "Alphakommandant" +description = "Deine Haustiere erschlagen 250 Kreaturen" [advancement.challenge_taming_2500] - title = "Zuchtexperte" - description = "Zuechte 2.500 Tiere" +title = "Zuchtexperte" +description = "Zuechte 2.500 Tiere" [advancement.challenge_taming_25k] - title = "Genetikmeister" - description = "Zuechte 25.000 Tiere" +title = "Genetikmeister" +description = "Zuechte 25.000 Tiere" # TragOul [advancement.challenge_trag_hits_500] - title = "Sandsack" - description = "Erhalte 500 Treffer" +title = "Sandsack" +description = "Erhalte 500 Treffer" [advancement.challenge_trag_hits_5k] - title = "Glutton fuer Strafe" - description = "Erhalte 5.000 Treffer" +title = "Glutton fuer Strafe" +description = "Erhalte 5.000 Treffer" [advancement.challenge_trag_deaths_10] - title = "Neun Leben" - description = "Stirb 10 Mal" +title = "Neun Leben" +description = "Stirb 10 Mal" [advancement.challenge_trag_deaths_100] - title = "Wiederkehrender Albtraum" - description = "Stirb 100 Mal" +title = "Wiederkehrender Albtraum" +description = "Stirb 100 Mal" [advancement.challenge_trag_fire_500] - title = "Brandopfer" - description = "Ertrage 500 Feuerschaden" +title = "Brandopfer" +description = "Ertrage 500 Feuerschaden" [advancement.challenge_trag_fire_5k] - title = "Phoeinx" - description = "Ertrage 5.000 Feuerschaden" +title = "Phoeinx" +description = "Ertrage 5.000 Feuerschaden" [advancement.challenge_trag_fall_500] - title = "Schwerkrafttest" - description = "Ertrage 500 Fallschaden" +title = "Schwerkrafttest" +description = "Ertrage 500 Fallschaden" [advancement.challenge_trag_fall_5k] - title = "Endgeschwindigkeit" - description = "Ertrage 5.000 Fallschaden" +title = "Endgeschwindigkeit" +description = "Ertrage 5.000 Fallschaden" # Unarmed [advancement.challenge_unarmed_dmg_1k] - title = "Raufbold" - description = "Verursache 1.000 Schaden mit blossen Faeusten" +title = "Raufbold" +description = "Verursache 1.000 Schaden mit blossen Faeusten" [advancement.challenge_unarmed_dmg_10k] - title = "Kampfkuenstler" - description = "Verursache 10.000 Schaden mit blossen Faeusten" +title = "Kampfkuenstler" +description = "Verursache 10.000 Schaden mit blossen Faeusten" [advancement.challenge_unarmed_kills_25] - title = "Faustkampf" - description = "Toete 25 Kreaturen mit blossen Faeusten" +title = "Faustkampf" +description = "Toete 25 Kreaturen mit blossen Faeusten" [advancement.challenge_unarmed_kills_250] - title = "Faust der Legende" - description = "Toete 250 Kreaturen mit blossen Faeusten" +title = "Faust der Legende" +description = "Toete 250 Kreaturen mit blossen Faeusten" [advancement.challenge_unarmed_crit_25] - title = "Kritischer Schlag" - description = "Lande 25 kritische Treffer mit blossen Faeusten" +title = "Kritischer Schlag" +description = "Lande 25 kritische Treffer mit blossen Faeusten" [advancement.challenge_unarmed_crit_250] - title = "Praezisionsfaust" - description = "Lande 250 kritische Treffer mit blossen Faeusten" +title = "Praezisionsfaust" +description = "Lande 250 kritische Treffer mit blossen Faeusten" [advancement.challenge_unarmed_heavy_25] - title = "Kraftschlag" - description = "Lande 25 schwere Treffer mit blossen Faeusten (ueber 6 Schaden)" +title = "Kraftschlag" +description = "Lande 25 schwere Treffer mit blossen Faeusten (ueber 6 Schaden)" [advancement.challenge_unarmed_heavy_250] - title = "Knockout-Koenig" - description = "Lande 250 schwere Treffer mit blossen Faeusten (ueber 6 Schaden)" +title = "Knockout-Koenig" +description = "Lande 250 schwere Treffer mit blossen Faeusten (ueber 6 Schaden)" # items [items] [items.bound_ender_peral] - name = "Reliquien-Portschlüssel" - usage1 = "Shift + Linksklick zum Binden" - usage2 = "Rechtsklick, um auf das gebundene Inventar zuzugreifen" +name = "Reliquien-Portschlüssel" +usage1 = "Shift + Linksklick zum Binden" +usage2 = "Rechtsklick, um auf das gebundene Inventar zuzugreifen" [items.bound_eye_of_ender] - name = "Okularanker" - usage1 = "Rechtsklick zum Verbrauchen und Teleportieren zum gebundenen Ort" - usage2 = "Shift + Linksklick, um an einen Block zu binden" +name = "Okularanker" +usage1 = "Rechtsklick zum Verbrauchen und Teleportieren zum gebundenen Ort" +usage2 = "Shift + Linksklick, um an einen Block zu binden" [items.bound_redstone_torch] - name = "Redstone-Fernsteuerung" - usage1 = "Rechtsklick, um einen 1-Tick-Redstone-Impuls zu erzeugen" - usage2 = "Shift + Linksklick auf einen 'Ziel'-Block zum Binden" +name = "Redstone-Fernsteuerung" +usage1 = "Rechtsklick, um einen 1-Tick-Redstone-Impuls zu erzeugen" +usage2 = "Shift + Linksklick auf einen 'Ziel'-Block zum Binden" [items.bound_snowball] - name = "Netzfalle!" - usage1 = "Werfen, um eine vorübergehende Spinnennetzfalle am Auftreffpunkt zu erstellen" +name = "Netzfalle!" +usage1 = "Werfen, um eine vorübergehende Spinnennetzfalle am Auftreffpunkt zu erstellen" [items.chrono_time_bottle] - name = "Zeit in einer Flasche" - usage1 = "Speichert passiv Zeit, solange sie sich in deinem Inventar befindet" - usage2 = "Rechtsklick auf zeitbasierte Blöcke oder Tierbabys, um gespeicherte Zeit zu verbrauchen" - stored = "Gespeicherte Zeit" +name = "Zeit in einer Flasche" +usage1 = "Speichert passiv Zeit, solange sie sich in deinem Inventar befindet" +usage2 = "Rechtsklick auf zeitbasierte Blöcke oder Tierbabys, um gespeicherte Zeit zu verbrauchen" +stored = "Gespeicherte Zeit" [items.chrono_time_bomb] - name = "Zeitbombe" - usage1 = "Rechtsklick, um einen Chronobolzen zu werfen, der ein Zeitfeld erzeugt" +name = "Zeitbombe" +usage1 = "Rechtsklick, um einen Chronobolzen zu werfen, der ein Zeitfeld erzeugt" [items.elevator_block] - name = "Aufzugblock" - usage1 = "Springen, um nach oben zu teleportieren" - usage2 = "Schleichen, um nach unten zu teleportieren" - usage3 = "Mindestens 2 Luftblöcke zwischen den Aufzügen" +name = "Aufzugblock" +usage1 = "Springen, um nach oben zu teleportieren" +usage2 = "Schleichen, um nach unten zu teleportieren" +usage3 = "Mindestens 2 Luftblöcke zwischen den Aufzügen" # snippets [snippets] [snippets.gui] - level = "Stufe" - knowledge = "Wissen" - power_used = "Verbrauchte Energie" - not_learned = "Noch nicht gelernt" - xp = "XP bis" - welcome = "Willkommen!" - welcome_back = "Willkommen zurück!" - xp_bonus_for_time = "XP für" - max_ability_power = "Maximale Fähigkeitsenergie" - unlock_this_by_clicking = "Entsperre dies per Rechtsklick: " - back = "Zurück" - unlearn_all = "Alles verlernen" - unlearned_all = "Alles verlernt" +level = "Stufe" +knowledge = "Wissen" +power_used = "Verbrauchte Energie" +not_learned = "Noch nicht gelernt" +xp = "XP bis" +welcome = "Willkommen!" +welcome_back = "Willkommen zurück!" +xp_bonus_for_time = "XP für" +max_ability_power = "Maximale Fähigkeitsenergie" +unlock_this_by_clicking = "Entsperre dies per Rechtsklick: " +back = "Zurück" +unlearn_all = "Alles verlernen" +unlearned_all = "Alles verlernt" [snippets.adapt_menu] - may_not_unlearn = "KANN NICHT VERLERNT WERDEN" - may_unlearn = "KANN GELERNT/VERLERNT WERDEN" - knowledge_cost = "Wissenskosten" - knowledge_available = "Verfügbares Wissen" - already_learned = "Bereits gelernt" - unlearn_refund = "Klicken zum Verlernen & Erstatten" - no_refunds = "HARDCORE, ERSTATTUNGEN DEAKTIVIERT" - knowledge = "Wissen" - click_learn = "Klicken zum Erlernen" - no_knowledge = "(Du hast kein Wissen)" - you_only_have = "Du hast nur" - how_to_level_up = "Verbessere deine Fähigkeiten, um deine maximale Energie zu erhöhen." - not_enough_power = "Nicht genug Energie! Jede Fähigkeitsstufe kostet 1 Energie." - power = "Energie" - power_drain = "Energieverbrauch" - learned = "Gelernt " - unlearned = "Verlernt " - activator_block = "Bücherregal" +may_not_unlearn = "KANN NICHT VERLERNT WERDEN" +may_unlearn = "KANN GELERNT/VERLERNT WERDEN" +knowledge_cost = "Wissenskosten" +knowledge_available = "Verfügbares Wissen" +already_learned = "Bereits gelernt" +unlearn_refund = "Klicken zum Verlernen & Erstatten" +no_refunds = "HARDCORE, ERSTATTUNGEN DEAKTIVIERT" +knowledge = "Wissen" +click_learn = "Klicken zum Erlernen" +no_knowledge = "(Du hast kein Wissen)" +you_only_have = "Du hast nur" +how_to_level_up = "Verbessere deine Fähigkeiten, um deine maximale Energie zu erhöhen." +not_enough_power = "Nicht genug Energie! Jede Fähigkeitsstufe kostet 1 Energie." +power = "Energie" +power_drain = "Energieverbrauch" +learned = "Gelernt " +unlearned = "Verlernt " +activator_block = "Bücherregal" [snippets.knowledge_orb] - contains = "enthält" - knowledge = "Wissen" - rightclick = "Rechtsklick" - togainknowledge = "um dieses Wissen zu erlangen" - knowledge_orb = "Wissenskugel" +contains = "enthält" +knowledge = "Wissen" +rightclick = "Rechtsklick" +togainknowledge = "um dieses Wissen zu erlangen" +knowledge_orb = "Wissenskugel" [snippets.experience_orb] - contains = "enthält" - xp = "Erfahrung" - rightclick = "Rechtsklick" - togainxp = "um diese Erfahrung zu erlangen" - xporb = "Erfahrungskugel" +contains = "enthält" +xp = "Erfahrung" +rightclick = "Rechtsklick" +togainxp = "um diese Erfahrung zu erlangen" +xporb = "Erfahrungskugel" # skill [skill] [skill.agility] - name = "Agilität" - icon = "⇉" - description = "Agilität ist die Fähigkeit, sich schnell und geschmeidig angesichts von Hindernissen zu bewegen." +name = "Agilität" +icon = "⇉" +description = "Agilität ist die Fähigkeit, sich schnell und geschmeidig angesichts von Hindernissen zu bewegen." [skill.architect] - name = "Architekt" - icon = "⬧" - description = "Strukturen sind die Bausteine der Welt. Die Realität liegt in deinen Händen, du bestimmst sie." +name = "Architekt" +icon = "⬧" +description = "Strukturen sind die Bausteine der Welt. Die Realität liegt in deinen Händen, du bestimmst sie." [skill.axes] - name = "Äxte" - icon = "🪓" - description1 = "Warum Bäume fällen, wenn man stattdessen " - description2 = "Dinge" - description3 = "fällen kann - gleiches Ergebnis!" +name = "Äxte" +icon = "🪓" +description1 = "Warum Bäume fällen, wenn man stattdessen " +description2 = "Dinge" +description3 = "fällen kann - gleiches Ergebnis!" [skill.brewing] - name = "Brauen" - icon = "❦" - description = "Doppelte Blase, dreifache Blase, vierfache Blase - Ich kann diesen Trank immer noch nicht in einen Kessel füllen" +name = "Brauen" +icon = "❦" +description = "Doppelte Blase, dreifache Blase, vierfache Blase - Ich kann diesen Trank immer noch nicht in einen Kessel füllen" [skill.blocking] - name = "Blocken" - icon = "🛡" - description = "Stöcke und Steine brechen dir nicht die Knochen, aber ein Schild schon." +name = "Blocken" +icon = "🛡" +description = "Stöcke und Steine brechen dir nicht die Knochen, aber ein Schild schon." [skill.crafting] - name = "Handwerk" - icon = "⌂" - description = "Wenn keine Teile mehr zu platzieren sind, warum nicht einfach neue herstellen?" +name = "Handwerk" +icon = "⌂" +description = "Wenn keine Teile mehr zu platzieren sind, warum nicht einfach neue herstellen?" [skill.discovery] - name = "Entdeckung" - icon = "⚛" - description = "Wenn sich deine Wahrnehmung erweitert, entfaltet sich dein Geist und entdeckt das, was dir verborgen blieb." +name = "Entdeckung" +icon = "⚛" +description = "Wenn sich deine Wahrnehmung erweitert, entfaltet sich dein Geist und entdeckt das, was dir verborgen blieb." [skill.enchanting] - name = "Verzauberung" - icon = "♰" - description = "Wovon redest du eigentlich? Prophezeiungen, Visionen, abergläubisches Gefasel?" +name = "Verzauberung" +icon = "♰" +description = "Wovon redest du eigentlich? Prophezeiungen, Visionen, abergläubisches Gefasel?" [skill.excavation] - name = "Ausgrabung" - icon = "ᛳ" - description = "Diggy Diggy Hole..." +name = "Ausgrabung" +icon = "ᛳ" +description = "Diggy Diggy Hole..." [skill.herbalism] - name = "Kräuterkunde" - icon = "⚘" - description = "Ich kann keine Pflanzen finden, aber ein paar Samen und - ist das... Unkraut?" +name = "Kräuterkunde" +icon = "⚘" +description = "Ich kann keine Pflanzen finden, aber ein paar Samen und - ist das... Unkraut?" [skill.hunter] - name = "Jäger" - icon = "☠" - description = "Bei der Jagd geht es um den Weg, nicht um das Ergebnis." +name = "Jäger" +icon = "☠" +description = "Bei der Jagd geht es um den Weg, nicht um das Ergebnis." [skill.nether] - name = "Nether" - icon = "₪" - description = "Aus den Tiefen des Nethers selbst." +name = "Nether" +icon = "₪" +description = "Aus den Tiefen des Nethers selbst." [skill.pickaxe] - name = "Spitzhacke" - icon = "⛏" - description = "Zwerge sind die Bergleute, aber ich habe in meiner Zeit auch einiges gelernt. ICH BIN SCHWEDE!" +name = "Spitzhacke" +icon = "⛏" +description = "Zwerge sind die Bergleute, aber ich habe in meiner Zeit auch einiges gelernt. ICH BIN SCHWEDE!" [skill.ranged] - name = "Fernkampf" - icon = "🏹" - description = "Entfernung ist der Schlüssel zum Sieg und der Schlüssel zum Überleben." +name = "Fernkampf" +icon = "🏹" +description = "Entfernung ist der Schlüssel zum Sieg und der Schlüssel zum Überleben." [skill.rift] - name = "Riss" - icon = "❍" - description = "Der Riss ist ein ätzendes Geschirr, aber du hast das Geschirr gemeistert." +name = "Riss" +icon = "❍" +description = "Der Riss ist ein ätzendes Geschirr, aber du hast das Geschirr gemeistert." [skill.seaborne] - name = "Seefahrt" - icon = "🎣" - description = "Mit dieser Fähigkeit kannst du die Wunder des Wassers beherrschen." +name = "Seefahrt" +icon = "🎣" +description = "Mit dieser Fähigkeit kannst du die Wunder des Wassers beherrschen." [skill.stealth] - name = "Heimlichkeit" - icon = "☯" - description = "Die Kunst des Unsichtbaren. Wandle in den Schatten." +name = "Heimlichkeit" +icon = "☯" +description = "Die Kunst des Unsichtbaren. Wandle in den Schatten." [skill.swords] - name = "Schwerter" - icon = "⚔" - description = "Bei der Macht von GreyStone!" +name = "Schwerter" +icon = "⚔" +description = "Bei der Macht von GreyStone!" [skill.taming] - name = "Zähmung" - icon = "♥" - description = "Die Papageien und die Bienen... und du?" +name = "Zähmung" +icon = "♥" +description = "Die Papageien und die Bienen... und du?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Blut fließt durch die Adern des Universums. Zusammengepresst von deinen Händen." +name = "TragOul" +icon = "🗡" +description = "Blut fließt durch die Adern des Universums. Zusammengepresst von deinen Händen." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Ziehe die Uhr des Universums auf, erlebe den Fluss. Zerbrich die Uhr, werde sie." +name = "Chronos" +icon = "🕒" +description = "Ziehe die Uhr des Universums auf, erlebe den Fluss. Zerbrich die Uhr, werde sie." [skill.unarmed] - name = "Unbewaffnet" - icon = "»" - description = "Ohne Waffe heißt nicht ohne Stärke." +name = "Unbewaffnet" +icon = "»" +description = "Ohne Waffe heißt nicht ohne Stärke." # agility [agility] [agility.armor_up] - name = "Aufrüstung" - description = "Erhalte mehr Rüstung, je länger du sprintest!" - lore1 = "Maximale Rüstung" - lore2 = "Aufrüstungszeit" - lore = ["Maximale Rüstung", "Aufrüstungszeit"] +name = "Aufrüstung" +description = "Erhalte mehr Rüstung, je länger du sprintest!" +lore1 = "Maximale Rüstung" +lore2 = "Aufrüstungszeit" +lore = ["Maximale Rüstung", "Aufrüstungszeit"] [agility.ladder_slide] - name = "Leiterrutsche" - description = "Klettere und rutsche Leitern in beide Richtungen viel schneller." - lore1 = "Leitergeschwindigkeits-Multiplikator" - lore2 = "Schnelle Abstiegsgeschwindigkeit" - lore = ["Leitergeschwindigkeits-Multiplikator", "Schnelle Abstiegsgeschwindigkeit"] +name = "Leiterrutsche" +description = "Klettere und rutsche Leitern in beide Richtungen viel schneller." +lore1 = "Leitergeschwindigkeits-Multiplikator" +lore2 = "Schnelle Abstiegsgeschwindigkeit" +lore = ["Leitergeschwindigkeits-Multiplikator", "Schnelle Abstiegsgeschwindigkeit"] [agility.super_jump] - name = "Supersprung" - description = "Außergewöhnlicher Höhenvorteil." - lore1 = "Maximale Sprunghöhe" - lore2 = "Schleichen + Springen für Supersprung!" - lore = ["Maximale Sprunghöhe", "Schleichen + Springen für Supersprung!"] +name = "Supersprung" +description = "Außergewöhnlicher Höhenvorteil." +lore1 = "Maximale Sprunghöhe" +lore2 = "Schleichen + Springen für Supersprung!" +lore = ["Maximale Sprunghöhe", "Schleichen + Springen für Supersprung!"] [agility.wall_jump] - name = "Wandsprung" - description = "Halte Shift in der Luft an einer Wand, um dich festzuhalten und zu springen!" - lore1 = "Maximale Sprünge" - lore2 = "Sprunghöhe" - lore = ["Maximale Sprünge", "Sprunghöhe"] +name = "Wandsprung" +description = "Halte Shift in der Luft an einer Wand, um dich festzuhalten und zu springen!" +lore1 = "Maximale Sprünge" +lore2 = "Sprunghöhe" +lore = ["Maximale Sprünge", "Sprunghöhe"] [agility.wind_up] - name = "Anlauf" - description = "Werde schneller, je länger du sprintest!" - lore1 = "Maximale Geschwindigkeit" - lore2 = "Anlaufzeit" - lore = ["Maximale Geschwindigkeit", "Anlaufzeit"] +name = "Anlauf" +description = "Werde schneller, je länger du sprintest!" +lore1 = "Maximale Geschwindigkeit" +lore2 = "Anlaufzeit" +lore = ["Maximale Geschwindigkeit", "Anlaufzeit"] # architect [architect] [architect.elevator] - name = "Aufzug" - description = "Ermöglicht dir den Bau eines Aufzugs für schnelle vertikale Teleportation!" - lore1 = "Schaltet Aufzugrezept frei: X=WOLLE, Y=ENDERPERLE" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Schaltet Aufzugrezept frei: X=WOLLE, Y=ENDERPERLE", "XXX", "XYX", "XXX"] +name = "Aufzug" +description = "Ermöglicht dir den Bau eines Aufzugs für schnelle vertikale Teleportation!" +lore1 = "Schaltet Aufzugrezept frei: X=WOLLE, Y=ENDERPERLE" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Schaltet Aufzugrezept frei: X=WOLLE, Y=ENDERPERLE", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Magisches Fundament" - description = "Ermöglicht dir, beim Schleichen ein vorübergehendes Fundament unter dir zu erschaffen!" - lore1 = "Magisch erschaffen: " - lore2 = "Blöcke unter dir!" - lore = ["Magisch erschaffen: ", "Blöcke unter dir!"] +name = "Magisches Fundament" +description = "Ermöglicht dir, beim Schleichen ein vorübergehendes Fundament unter dir zu erschaffen!" +lore1 = "Magisch erschaffen: " +lore2 = "Blöcke unter dir!" +lore = ["Magisch erschaffen: ", "Blöcke unter dir!"] [architect.glass] - name = "Behutsamkeits-Glas" - description = "Verhindert den Verlust von Glasblöcken, wenn du sie mit leeren Händen zerbrichst!" - lore1 = "Deine Hände erhalten Behutsamkeit für Glas" - lore = ["Deine Hände erhalten Behutsamkeit für Glas"] +name = "Behutsamkeits-Glas" +description = "Verhindert den Verlust von Glasblöcken, wenn du sie mit leeren Händen zerbrichst!" +lore1 = "Deine Hände erhalten Behutsamkeit für Glas" +lore = ["Deine Hände erhalten Behutsamkeit für Glas"] [architect.wireless_redstone] - name = "Redstone-Fernsteuerung" - description = "Ermöglicht dir, eine Redstone-Fackel zu verwenden, um Redstone aus der Ferne zu schalten!" - lore1 = "Ziel + Redstone-Fackel + Enderperle = 1 Redstone-Fernsteuerung" - lore = ["Ziel + Redstone-Fackel + Enderperle = 1 Redstone-Fernsteuerung"] +name = "Redstone-Fernsteuerung" +description = "Ermöglicht dir, eine Redstone-Fackel zu verwenden, um Redstone aus der Ferne zu schalten!" +lore1 = "Ziel + Redstone-Fackel + Enderperle = 1 Redstone-Fernsteuerung" +lore = ["Ziel + Redstone-Fackel + Enderperle = 1 Redstone-Fernsteuerung"] [architect.placement] - name = "Baustab" - description = "Ermöglicht dir, mehrere Blöcke auf einmal zu platzieren. Zum Aktivieren schleiche und halte einen Block, der dem anvisierten Block entspricht, und platziere! Beachte, dass du dich etwas bewegen musst, um die Begrenzung auszulösen!" - lore1 = "Du brauchst" - lore2 = "Blöcke in deiner Hand, um dies zu platzieren" - lore3 = "Ein Material-Baustab" - lore = ["Du brauchst", "Blöcke in deiner Hand, um dies zu platzieren", "Ein Material-Baustab"] +name = "Baustab" +description = "Ermöglicht dir, mehrere Blöcke auf einmal zu platzieren. Zum Aktivieren schleiche und halte einen Block, der dem anvisierten Block entspricht, und platziere! Beachte, dass du dich etwas bewegen musst, um die Begrenzung auszulösen!" +lore1 = "Du brauchst" +lore2 = "Blöcke in deiner Hand, um dies zu platzieren" +lore3 = "Ein Material-Baustab" +lore = ["Du brauchst", "Blöcke in deiner Hand, um dies zu platzieren", "Ein Material-Baustab"] # axe [axe] [axe.chop] - name = "Axtschlag" - description = "Fälle Bäume per Rechtsklick auf den Stamm!" - lore1 = "Blöcke pro Hieb" - lore2 = "Hieb-Abklingzeit" - lore3 = "Werkzeugverschleiß" - lore = ["Blöcke pro Hieb", "Hieb-Abklingzeit", "Werkzeugverschleiß"] +name = "Axtschlag" +description = "Fälle Bäume per Rechtsklick auf den Stamm!" +lore1 = "Blöcke pro Hieb" +lore2 = "Hieb-Abklingzeit" +lore3 = "Werkzeugverschleiß" +lore = ["Blöcke pro Hieb", "Hieb-Abklingzeit", "Werkzeugverschleiß"] [axe.log_swap] - name = "Lucys Stammtauscher" - description = "Ändere die Art von Holzstämmen in einer Werkbank!" - lore1 = "8 Stämme beliebiger Art + 1 Setzling = 8 Stämme der Art des Setzlings" - lore = ["8 Stämme beliebiger Art + 1 Setzling = 8 Stämme der Art des Setzlings"] +name = "Lucys Stammtauscher" +description = "Ändere die Art von Holzstämmen in einer Werkbank!" +lore1 = "8 Stämme beliebiger Art + 1 Setzling = 8 Stämme der Art des Setzlings" +lore = ["8 Stämme beliebiger Art + 1 Setzling = 8 Stämme der Art des Setzlings"] [axe.drop_to_inventory] - name = "Axt-Drop-ins-Inventar" +name = "Axt-Drop-ins-Inventar" [axe.ground_smash] - name = "Axt-Bodenschlag" - description = "Springe, dann schleiche und zerschmettere alle Feinde in der Nähe." - lore1 = "Schaden" - lore2 = "Block-Radius" - lore3 = "Wucht" - lore4 = "Schlag-Abklingzeit" - lore = ["Schaden", "Block-Radius", "Wucht", "Schlag-Abklingzeit"] +name = "Axt-Bodenschlag" +description = "Springe, dann schleiche und zerschmettere alle Feinde in der Nähe." +lore1 = "Schaden" +lore2 = "Block-Radius" +lore3 = "Wucht" +lore4 = "Schlag-Abklingzeit" +lore = ["Schaden", "Block-Radius", "Wucht", "Schlag-Abklingzeit"] [axe.leaf_miner] - name = "Blattfresser" - description = "Ermöglicht dir, große Mengen Blätter auf einmal abzubauen!" - lore1 = "Schleiche und baue BLÄTTER ab" - lore2 = "Reichweite des Blattabbaus" - lore3 = "Du erhältst keine Drops von den Blättern (Exploit-Schutz)" - lore = ["Schleiche und baue BLÄTTER ab", "Reichweite des Blattabbaus", "Du erhältst keine Drops von den Blättern (Exploit-Schutz)"] +name = "Blattfresser" +description = "Ermöglicht dir, große Mengen Blätter auf einmal abzubauen!" +lore1 = "Schleiche und baue BLÄTTER ab" +lore2 = "Reichweite des Blattabbaus" +lore3 = "Du erhältst keine Drops von den Blättern (Exploit-Schutz)" +lore = ["Schleiche und baue BLÄTTER ab", "Reichweite des Blattabbaus", "Du erhältst keine Drops von den Blättern (Exploit-Schutz)"] [axe.wood_miner] - name = "Holzfäller" - description = "Ermöglicht dir, große Mengen Holz auf einmal abzubauen!" - lore1 = "Schleiche und fälle HOLZ/STÄMME (keine Bretter)" - lore2 = "Reichweite des Holzabbaus" - lore3 = "Funktioniert mit Drop-ins-Inventar" - lore = ["Schleiche und fälle HOLZ/STÄMME (keine Bretter)", "Reichweite des Holzabbaus", "Funktioniert mit Drop-ins-Inventar"] +name = "Holzfäller" +description = "Ermöglicht dir, große Mengen Holz auf einmal abzubauen!" +lore1 = "Schleiche und fälle HOLZ/STÄMME (keine Bretter)" +lore2 = "Reichweite des Holzabbaus" +lore3 = "Funktioniert mit Drop-ins-Inventar" +lore = ["Schleiche und fälle HOLZ/STÄMME (keine Bretter)", "Reichweite des Holzabbaus", "Funktioniert mit Drop-ins-Inventar"] # brewing [brewing] [brewing.lingering] - name = "Verweilendes Gebräu" - description = "Gebraute Tränke wirken länger!" - lore1 = "Dauer" - lore2 = "Dauer" - lore = ["Dauer", "Dauer"] +name = "Verweilendes Gebräu" +description = "Gebraute Tränke wirken länger!" +lore1 = "Dauer" +lore2 = "Dauer" +lore = ["Dauer", "Dauer"] [brewing.super_heated] - name = "Superheißes Gebräu" - description = "Braustände arbeiten schneller, je heißer sie sind." - lore1 = "Pro berührendem Feuerblock" - lore2 = "Pro berührendem Lavablock" - lore = ["Pro berührendem Feuerblock", "Pro berührendem Lavablock"] +name = "Superheißes Gebräu" +description = "Braustände arbeiten schneller, je heißer sie sind." +lore1 = "Pro berührendem Feuerblock" +lore2 = "Pro berührendem Lavablock" +lore = ["Pro berührendem Feuerblock", "Pro berührendem Lavablock"] [brewing.darkness] - name = "Abgefüllte Dunkelheit" - description = "Nicht sicher, warum du das brauchst, aber bitte schön!" - lore1 = "Nachtsichttrank + Schwarzer Beton = Trank der Dunkelheit (30 Sekunden)" - lore2 = "Achtung: Dies hindert den Benutzer am Sprinten!" - lore = ["Nachtsichttrank + Schwarzer Beton = Trank der Dunkelheit (30 Sekunden)", "Achtung: Dies hindert den Benutzer am Sprinten!"] +name = "Abgefüllte Dunkelheit" +description = "Nicht sicher, warum du das brauchst, aber bitte schön!" +lore1 = "Nachtsichttrank + Schwarzer Beton = Trank der Dunkelheit (30 Sekunden)" +lore2 = "Achtung: Dies hindert den Benutzer am Sprinten!" +lore = ["Nachtsichttrank + Schwarzer Beton = Trank der Dunkelheit (30 Sekunden)", "Achtung: Dies hindert den Benutzer am Sprinten!"] [brewing.haste] - name = "Abgefüllte Eile" - description = "Wenn Effizienz nicht ausreicht" - lore1 = "Schnelligkeitstrank + Amethystsplitter = Trank der Eile (60 Sekunden)" - lore2 = "Schnelligkeitstrank + Amethystblock = Trank der Eile-2 (30 Sekunden)" - lore = ["Schnelligkeitstrank + Amethystsplitter = Trank der Eile (60 Sekunden)", "Schnelligkeitstrank + Amethystblock = Trank der Eile-2 (30 Sekunden)"] +name = "Abgefüllte Eile" +description = "Wenn Effizienz nicht ausreicht" +lore1 = "Schnelligkeitstrank + Amethystsplitter = Trank der Eile (60 Sekunden)" +lore2 = "Schnelligkeitstrank + Amethystblock = Trank der Eile-2 (30 Sekunden)" +lore = ["Schnelligkeitstrank + Amethystsplitter = Trank der Eile (60 Sekunden)", "Schnelligkeitstrank + Amethystblock = Trank der Eile-2 (30 Sekunden)"] [brewing.absorption] - name = "Abgefüllte Absorption" - description = "Härte den Körper!" - lore1 = "Sofortige Heilung + Quarz = Trank der Absorption (60 Sekunden)" - lore2 = "Sofortige Heilung + Quarzblock = Trank der Absorption-2 (30 Sekunden)" - lore = ["Sofortige Heilung + Quarz = Trank der Absorption (60 Sekunden)", "Sofortige Heilung + Quarzblock = Trank der Absorption-2 (30 Sekunden)"] +name = "Abgefüllte Absorption" +description = "Härte den Körper!" +lore1 = "Sofortige Heilung + Quarz = Trank der Absorption (60 Sekunden)" +lore2 = "Sofortige Heilung + Quarzblock = Trank der Absorption-2 (30 Sekunden)" +lore = ["Sofortige Heilung + Quarz = Trank der Absorption (60 Sekunden)", "Sofortige Heilung + Quarzblock = Trank der Absorption-2 (30 Sekunden)"] [brewing.fatigue] - name = "Abgefüllte Müdigkeit" - description = "Schwäche den Körper!" - lore1 = "Schwächetrank + Schleimball = Trank der Müdigkeit (30 Sekunden)" - lore2 = "Schwächetrank + Schleimblock = Trank der Müdigkeit-2 (15 Sekunden)" - lore = ["Schwächetrank + Schleimball = Trank der Müdigkeit (30 Sekunden)", "Schwächetrank + Schleimblock = Trank der Müdigkeit-2 (15 Sekunden)"] +name = "Abgefüllte Müdigkeit" +description = "Schwäche den Körper!" +lore1 = "Schwächetrank + Schleimball = Trank der Müdigkeit (30 Sekunden)" +lore2 = "Schwächetrank + Schleimblock = Trank der Müdigkeit-2 (15 Sekunden)" +lore = ["Schwächetrank + Schleimball = Trank der Müdigkeit (30 Sekunden)", "Schwächetrank + Schleimblock = Trank der Müdigkeit-2 (15 Sekunden)"] [brewing.hunger] - name = "Abgefüllter Hunger" - description = "Nähre die Unersättlichen!" - lore1 = "Seltsamer Trank + Verrottetes Fleisch = Trank des Hungers (30 Sekunden)" - lore2 = "Schwächetrank + Verrottetes Fleisch = Trank des Hungers-3 (15 Sekunden)" - lore = ["Seltsamer Trank + Verrottetes Fleisch = Trank des Hungers (30 Sekunden)", "Schwächetrank + Verrottetes Fleisch = Trank des Hungers-3 (15 Sekunden)"] +name = "Abgefüllter Hunger" +description = "Nähre die Unersättlichen!" +lore1 = "Seltsamer Trank + Verrottetes Fleisch = Trank des Hungers (30 Sekunden)" +lore2 = "Schwächetrank + Verrottetes Fleisch = Trank des Hungers-3 (15 Sekunden)" +lore = ["Seltsamer Trank + Verrottetes Fleisch = Trank des Hungers (30 Sekunden)", "Schwächetrank + Verrottetes Fleisch = Trank des Hungers-3 (15 Sekunden)"] [brewing.nausea] - name = "Abgefüllte Übelkeit" - description = "Du machst mich krank!" - lore1 = "Seltsamer Trank + Brauner Pilz = Trank der Übelkeit (16 Sekunden)" - lore2 = "Seltsamer Trank + Karmesinpilz = Trank der Übelkeit-2 (8 Sekunden)" - lore = ["Seltsamer Trank + Brauner Pilz = Trank der Übelkeit (16 Sekunden)", "Seltsamer Trank + Karmesinpilz = Trank der Übelkeit-2 (8 Sekunden)"] +name = "Abgefüllte Übelkeit" +description = "Du machst mich krank!" +lore1 = "Seltsamer Trank + Brauner Pilz = Trank der Übelkeit (16 Sekunden)" +lore2 = "Seltsamer Trank + Karmesinpilz = Trank der Übelkeit-2 (8 Sekunden)" +lore = ["Seltsamer Trank + Brauner Pilz = Trank der Übelkeit (16 Sekunden)", "Seltsamer Trank + Karmesinpilz = Trank der Übelkeit-2 (8 Sekunden)"] [brewing.blindness] - name = "Abgefüllte Blindheit" - description = "Du bist ein schrecklicher Mensch..." - lore1 = "Seltsamer Trank + Tintenbeutel = Trank der Blindheit (30 Sekunden)" - lore2 = "Seltsamer Trank + Leuchtender Tintenbeutel = Trank der Blindheit-2 (15 Sekunden)" - lore = ["Seltsamer Trank + Tintenbeutel = Trank der Blindheit (30 Sekunden)", "Seltsamer Trank + Leuchtender Tintenbeutel = Trank der Blindheit-2 (15 Sekunden)"] +name = "Abgefüllte Blindheit" +description = "Du bist ein schrecklicher Mensch..." +lore1 = "Seltsamer Trank + Tintenbeutel = Trank der Blindheit (30 Sekunden)" +lore2 = "Seltsamer Trank + Leuchtender Tintenbeutel = Trank der Blindheit-2 (15 Sekunden)" +lore = ["Seltsamer Trank + Tintenbeutel = Trank der Blindheit (30 Sekunden)", "Seltsamer Trank + Leuchtender Tintenbeutel = Trank der Blindheit-2 (15 Sekunden)"] [brewing.resistance] - name = "Abgefüllter Widerstand" - description = "Befestigung in Vollendung!" - lore1 = "Seltsamer Trank + Eisenbarren = Trank des Widerstands (60 Sekunden)" - lore2 = "Seltsamer Trank + Eisenblock = Trank des Widerstands-2 (30 Sekunden)" - lore = ["Seltsamer Trank + Eisenbarren = Trank des Widerstands (60 Sekunden)", "Seltsamer Trank + Eisenblock = Trank des Widerstands-2 (30 Sekunden)"] +name = "Abgefüllter Widerstand" +description = "Befestigung in Vollendung!" +lore1 = "Seltsamer Trank + Eisenbarren = Trank des Widerstands (60 Sekunden)" +lore2 = "Seltsamer Trank + Eisenblock = Trank des Widerstands-2 (30 Sekunden)" +lore = ["Seltsamer Trank + Eisenbarren = Trank des Widerstands (60 Sekunden)", "Seltsamer Trank + Eisenblock = Trank des Widerstands-2 (30 Sekunden)"] [brewing.health_boost] - name = "Abgefülltes Leben" - description = "Wenn maximale Gesundheit nicht ausreicht..." - lore1 = "Trank der Heilung + Goldener Apfel = Trank der Gesundheitssteigerung (120 Sekunden)" - lore2 = "Trank der Heilung + Verzauberter Goldener Apfel = Trank der Gesundheitssteigerung-2 (120 Sekunden)" - lore = ["Trank der Heilung + Goldener Apfel = Trank der Gesundheitssteigerung (120 Sekunden)", "Trank der Heilung + Verzauberter Goldener Apfel = Trank der Gesundheitssteigerung-2 (120 Sekunden)"] +name = "Abgefülltes Leben" +description = "Wenn maximale Gesundheit nicht ausreicht..." +lore1 = "Trank der Heilung + Goldener Apfel = Trank der Gesundheitssteigerung (120 Sekunden)" +lore2 = "Trank der Heilung + Verzauberter Goldener Apfel = Trank der Gesundheitssteigerung-2 (120 Sekunden)" +lore = ["Trank der Heilung + Goldener Apfel = Trank der Gesundheitssteigerung (120 Sekunden)", "Trank der Heilung + Verzauberter Goldener Apfel = Trank der Gesundheitssteigerung-2 (120 Sekunden)"] [brewing.decay] - name = "Abgefüllter Verfall" - description = "Wer hätte gedacht, dass Detritus so nützlich sein würde?" - lore1 = "Schwächetrank + Giftige Kartoffel = Trank des Wither (16 Sekunden)" - lore2 = "Schwächetrank + Karmesinwurzeln = Trank des Wither-2 (8 Sekunden)" - lore = ["Schwächetrank + Giftige Kartoffel = Trank des Wither (16 Sekunden)", "Schwächetrank + Karmesinwurzeln = Trank des Wither-2 (8 Sekunden)"] +name = "Abgefüllter Verfall" +description = "Wer hätte gedacht, dass Detritus so nützlich sein würde?" +lore1 = "Schwächetrank + Giftige Kartoffel = Trank des Wither (16 Sekunden)" +lore2 = "Schwächetrank + Karmesinwurzeln = Trank des Wither-2 (8 Sekunden)" +lore = ["Schwächetrank + Giftige Kartoffel = Trank des Wither (16 Sekunden)", "Schwächetrank + Karmesinwurzeln = Trank des Wither-2 (8 Sekunden)"] [brewing.saturation] - name = "Abgefüllte Sättigung" - description = "Weißt du was... Ich bin nicht mal hungrig..." - lore1 = "Regenerationstrank + Gebackene Kartoffel = Trank der Sättigung" - lore2 = "Regenerationstrank + Heuballen = Trank der Sättigung-2" - lore = ["Regenerationstrank + Gebackene Kartoffel = Trank der Sättigung", "Regenerationstrank + Heuballen = Trank der Sättigung-2"] +name = "Abgefüllte Sättigung" +description = "Weißt du was... Ich bin nicht mal hungrig..." +lore1 = "Regenerationstrank + Gebackene Kartoffel = Trank der Sättigung" +lore2 = "Regenerationstrank + Heuballen = Trank der Sättigung-2" +lore = ["Regenerationstrank + Gebackene Kartoffel = Trank der Sättigung", "Regenerationstrank + Heuballen = Trank der Sättigung-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Ketten des Mephistopheles" - description = "Ermöglicht dir die Herstellung von Kettenrüstung" - lore1 = "Das Rezept ist dasselbe wie bei jeder anderen Rüstung, aber mit Eisennuggets" - lore = ["Das Rezept ist dasselbe wie bei jeder anderen Rüstung, aber mit Eisennuggets"] +name = "Ketten des Mephistopheles" +description = "Ermöglicht dir die Herstellung von Kettenrüstung" +lore1 = "Das Rezept ist dasselbe wie bei jeder anderen Rüstung, aber mit Eisennuggets" +lore = ["Das Rezept ist dasselbe wie bei jeder anderen Rüstung, aber mit Eisennuggets"] [blocking.horse_armorer] - name = "Herstellbare Pferderüstung" - description = "Ermöglicht dir die Herstellung von Pferderüstung" - lore1 = "Umgib einen Sattel mit dem gewünschten Material, um die Rüstung herzustellen" - lore = ["Umgib einen Sattel mit dem gewünschten Material, um die Rüstung herzustellen"] +name = "Herstellbare Pferderüstung" +description = "Ermöglicht dir die Herstellung von Pferderüstung" +lore1 = "Umgib einen Sattel mit dem gewünschten Material, um die Rüstung herzustellen" +lore = ["Umgib einen Sattel mit dem gewünschten Material, um die Rüstung herzustellen"] [blocking.saddle_crafter] - name = "Herstellbarer Sattel" - description = "Stelle einen Sattel aus Leder her" - lore1 = "Rezept: 5 Leder:" - lore = ["Rezept: 5 Leder:"] +name = "Herstellbarer Sattel" +description = "Stelle einen Sattel aus Leder her" +lore1 = "Rezept: 5 Leder:" +lore = ["Rezept: 5 Leder:"] [blocking.multi_armor] - name = "Multi-Rüstung" - description = "Binde Elytren an Rüstung" - lore1 = "Eine erstaunliche Fähigkeit zum Reisen." - lore2 = "Dynamisches Zusammenführen und Wechseln von Rüstung/Elytra im Flug!" - lore3 = "Zum Zusammenführen: Shift-Klick auf einen Gegenstand über einem anderen im Inventar." - lore4 = "Zum Trennen: Schleiche und wirf den Gegenstand, um ihn zu zerlegen." - lore5 = "Wenn deine Multi-Rüstung zerstört wird, verlierst du alle darin enthaltenen Gegenstände." - lore6 = "Ich brauche keine Rüstung, sie enttäuscht mich..." - lore = ["Eine erstaunliche Fähigkeit zum Reisen.", "Dynamisches Zusammenführen und Wechseln von Rüstung/Elytra im Flug!", "Zum Zusammenführen: Shift-Klick auf einen Gegenstand über einem anderen im Inventar.", "Zum Trennen: Schleiche und wirf den Gegenstand, um ihn zu zerlegen.", "Wenn deine Multi-Rüstung zerstört wird, verlierst du alle darin enthaltenen Gegenstände.", "Ich brauche keine Rüstung, sie enttäuscht mich..."] +name = "Multi-Rüstung" +description = "Binde Elytren an Rüstung" +lore1 = "Eine erstaunliche Fähigkeit zum Reisen." +lore2 = "Dynamisches Zusammenführen und Wechseln von Rüstung/Elytra im Flug!" +lore3 = "Zum Zusammenführen: Shift-Klick auf einen Gegenstand über einem anderen im Inventar." +lore4 = "Zum Trennen: Schleiche und wirf den Gegenstand, um ihn zu zerlegen." +lore5 = "Wenn deine Multi-Rüstung zerstört wird, verlierst du alle darin enthaltenen Gegenstände." +lore6 = "Ich brauche keine Rüstung, sie enttäuscht mich..." +lore = ["Eine erstaunliche Fähigkeit zum Reisen.", "Dynamisches Zusammenführen und Wechseln von Rüstung/Elytra im Flug!", "Zum Zusammenführen: Shift-Klick auf einen Gegenstand über einem anderen im Inventar.", "Zum Trennen: Schleiche und wirf den Gegenstand, um ihn zu zerlegen.", "Wenn deine Multi-Rüstung zerstört wird, verlierst du alle darin enthaltenen Gegenstände.", "Ich brauche keine Rüstung, sie enttäuscht mich..."] # crafting [crafting] [crafting.deconstruction] - name = "Dekonstruktion" - description = "Zerlege Blöcke & Gegenstände in wiederverwertbare Grundkomponenten!" - lore1 = "Wirf einen beliebigen Gegenstand auf den Boden." - lore2 = "Dann schleiche und Rechtsklick mit einer Schere" - lore = ["Wirf einen beliebigen Gegenstand auf den Boden.", "Dann schleiche und Rechtsklick mit einer Schere"] +name = "Dekonstruktion" +description = "Zerlege Blöcke & Gegenstände in wiederverwertbare Grundkomponenten!" +lore1 = "Wirf einen beliebigen Gegenstand auf den Boden." +lore2 = "Dann schleiche und Rechtsklick mit einer Schere" +lore = ["Wirf einen beliebigen Gegenstand auf den Boden.", "Dann schleiche und Rechtsklick mit einer Schere"] [crafting.xp] - name = "Handwerkserfahrung" - description = "Erhalte passiv EP beim Herstellen" - lore1 = "Erhalte EP beim Herstellen" - lore = ["Erhalte EP beim Herstellen"] +name = "Handwerkserfahrung" +description = "Erhalte passiv EP beim Herstellen" +lore1 = "Erhalte EP beim Herstellen" +lore = ["Erhalte EP beim Herstellen"] [crafting.reconstruction] - name = "Erzrekonstruktion" - description = "Stelle Erze aus ihren Grundkomponenten wieder her!" - lore1 = "8 der Drops und 1 Wirtsblock = 1 Erz (formlos)" - lore2 = "Drops müssen geschmolzen werden (falls zutreffend)" - lore3 = "Ausgenommen: Schrott, Quarz und Smaragde usw..." - lore4 = "Wirtsblock = Ummantelung. z.B.: Stein, Netherrack, Tiefenschiefer" - lore = ["8 der Drops und 1 Wirtsblock = 1 Erz (formlos)", "Drops müssen geschmolzen werden (falls zutreffend)", "Ausgenommen: Schrott, Quarz und Smaragde usw...", "Wirtsblock = Ummantelung. z.B.: Stein, Netherrack, Tiefenschiefer"] +name = "Erzrekonstruktion" +description = "Stelle Erze aus ihren Grundkomponenten wieder her!" +lore1 = "8 der Drops und 1 Wirtsblock = 1 Erz (formlos)" +lore2 = "Drops müssen geschmolzen werden (falls zutreffend)" +lore3 = "Ausgenommen: Schrott, Quarz und Smaragde usw..." +lore4 = "Wirtsblock = Ummantelung. z.B.: Stein, Netherrack, Tiefenschiefer" +lore = ["8 der Drops und 1 Wirtsblock = 1 Erz (formlos)", "Drops müssen geschmolzen werden (falls zutreffend)", "Ausgenommen: Schrott, Quarz und Smaragde usw...", "Wirtsblock = Ummantelung. z.B.: Stein, Netherrack, Tiefenschiefer"] [crafting.leather] - name = "Herstellbares Leder" - description = "Stelle Leder aus verrottetem Fleisch her" - lore1 = "Wirf es (verrottetes Fleisch) einfach aufs Lagerfeuer!" - lore = ["Wirf es (verrottetes Fleisch) einfach aufs Lagerfeuer!"] +name = "Herstellbares Leder" +description = "Stelle Leder aus verrottetem Fleisch her" +lore1 = "Wirf es (verrottetes Fleisch) einfach aufs Lagerfeuer!" +lore = ["Wirf es (verrottetes Fleisch) einfach aufs Lagerfeuer!"] [crafting.backpacks] - name = "Boutiliers Rucksäcke!" - description = "Das bringt das Mojang-Bündel ins Spiel!" - lore1 = "Du musst im Überlebensmodus sein, um dies zu nutzen" - lore2 = "XLX : Leder, Leine, Leder" - lore3 = "XSX : Leder, Fass, Leder" - lore4 = "XCX : Leder, Truhe, Leder" - lore = ["Du musst im Überlebensmodus sein, um dies zu nutzen", "XLX : Leder, Leine, Leder", "XSX : Leder, Fass, Leder", "XCX : Leder, Truhe, Leder"] +name = "Boutiliers Rucksäcke!" +description = "Das bringt das Mojang-Bündel ins Spiel!" +lore1 = "Du musst im Überlebensmodus sein, um dies zu nutzen" +lore2 = "XLX : Leder, Leine, Leder" +lore3 = "XSX : Leder, Fass, Leder" +lore4 = "XCX : Leder, Truhe, Leder" +lore = ["Du musst im Überlebensmodus sein, um dies zu nutzen", "XLX : Leder, Leine, Leder", "XSX : Leder, Fass, Leder", "XCX : Leder, Truhe, Leder"] [crafting.stations] - name = "Tragbare Tische!" - description = "Benutze einen Tisch direkt aus deiner Hand!" - lore2 = "ALLE GEGENSTÄNDE, DIE DU IM TISCH BEIM SCHLIEßEN VERGISST, SIND FÜR IMMER VERLOREN!" - lore3 = "Gültige Tische: Amboss, Werkbank, Schleifstein, Kartentisch, Steinsäge, Webstuhl" - lore = ["ALLE GEGENSTÄNDE, DIE DU IM TISCH BEIM SCHLIEßEN VERGISST, SIND FÜR IMMER VERLOREN!", "Gültige Tische: Amboss, Werkbank, Schleifstein, Kartentisch, Steinsäge, Webstuhl"] +name = "Tragbare Tische!" +description = "Benutze einen Tisch direkt aus deiner Hand!" +lore2 = "ALLE GEGENSTÄNDE, DIE DU IM TISCH BEIM SCHLIEßEN VERGISST, SIND FÜR IMMER VERLOREN!" +lore3 = "Gültige Tische: Amboss, Werkbank, Schleifstein, Kartentisch, Steinsäge, Webstuhl" +lore = ["ALLE GEGENSTÄNDE, DIE DU IM TISCH BEIM SCHLIEßEN VERGISST, SIND FÜR IMMER VERLOREN!", "Gültige Tische: Amboss, Werkbank, Schleifstein, Kartentisch, Steinsäge, Webstuhl"] [crafting.skulls] - name = "Herstellbare Schädel!" - description = "Mit Materialien kannst du Mob-Schädel herstellen!" - lore1 = "Umgib einen Knochenblock mit Folgendem, um einen Schädel zu erhalten:" - lore2 = "Zombie: Verrottetes Fleisch" - lore3 = "Skelett: Knochen" - lore4 = "Creeper: Schwarzpulver" - lore5 = "Wither: Netherziegel" - lore6 = "Drache: Drachenatem" - lore = ["Umgib einen Knochenblock mit Folgendem, um einen Schädel zu erhalten:", "Zombie: Verrottetes Fleisch", "Skelett: Knochen", "Creeper: Schwarzpulver", "Wither: Netherziegel", "Drache: Drachenatem"] +name = "Herstellbare Schädel!" +description = "Mit Materialien kannst du Mob-Schädel herstellen!" +lore1 = "Umgib einen Knochenblock mit Folgendem, um einen Schädel zu erhalten:" +lore2 = "Zombie: Verrottetes Fleisch" +lore3 = "Skelett: Knochen" +lore4 = "Creeper: Schwarzpulver" +lore5 = "Wither: Netherziegel" +lore6 = "Drache: Drachenatem" +lore = ["Umgib einen Knochenblock mit Folgendem, um einen Schädel zu erhalten:", "Zombie: Verrottetes Fleisch", "Skelett: Knochen", "Creeper: Schwarzpulver", "Wither: Netherziegel", "Drache: Drachenatem"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Zeit in einer Flasche" - description = "Trage eine Zeitflasche, die Zeit speichert, und verbrauche sie, um zeitbasierte Blöcke, Gewächse und altersabhängige Wesen wie Tierbabys zu beschleunigen. Rezept (Formlos): Schnelligkeitstrank + Uhr + Glasflasche." - lore1 = "Gespeicherte Sekunden pro Tick aufgeladen" - lore2 = "Zeitbeschleunigung pro gespeicherter Sekunde" - lore3 = "Rezept (Formlos): Schnelligkeitstrank + Uhr + Glasflasche" - lore = ["Gespeicherte Sekunden pro Tick aufgeladen", "Zeitbeschleunigung pro gespeicherter Sekunde", "Rezept (Formlos): Schnelligkeitstrank + Uhr + Glasflasche"] +name = "Zeit in einer Flasche" +description = "Trage eine Zeitflasche, die Zeit speichert, und verbrauche sie, um zeitbasierte Blöcke, Gewächse und altersabhängige Wesen wie Tierbabys zu beschleunigen. Rezept (Formlos): Schnelligkeitstrank + Uhr + Glasflasche." +lore1 = "Gespeicherte Sekunden pro Tick aufgeladen" +lore2 = "Zeitbeschleunigung pro gespeicherter Sekunde" +lore3 = "Rezept (Formlos): Schnelligkeitstrank + Uhr + Glasflasche" +lore = ["Gespeicherte Sekunden pro Tick aufgeladen", "Zeitbeschleunigung pro gespeicherter Sekunde", "Rezept (Formlos): Schnelligkeitstrank + Uhr + Glasflasche"] [chronos.aberrant_touch] - name = "Abweichende Berührung" - description = "Nahkampfangriffe verursachen stapelbare Langsamkeit auf Kosten von Hunger, mit strikten PvP-Grenzen, und verankern Ziele bei 5 Stapeln." - lore1 = "Nahkampfangriffe verursachen stapelbare Langsamkeit" - lore2 = "PvE-Langsamkeitsdauer-Obergrenze" - lore3 = "PvP-Langsamkeitsverstärker-Obergrenze" - lore = ["Nahkampfangriffe verursachen stapelbare Langsamkeit", "PvE-Langsamkeitsdauer-Obergrenze", "PvP-Langsamkeitsverstärker-Obergrenze"] +name = "Abweichende Berührung" +description = "Nahkampfangriffe verursachen stapelbare Langsamkeit auf Kosten von Hunger, mit strikten PvP-Grenzen, und verankern Ziele bei 5 Stapeln." +lore1 = "Nahkampfangriffe verursachen stapelbare Langsamkeit" +lore2 = "PvE-Langsamkeitsdauer-Obergrenze" +lore3 = "PvP-Langsamkeitsverstärker-Obergrenze" +lore = ["Nahkampfangriffe verursachen stapelbare Langsamkeit", "PvE-Langsamkeitsdauer-Obergrenze", "PvP-Langsamkeitsverstärker-Obergrenze"] [chronos.instant_recall] - name = "Sofortige Rückkehr" - description = "Links- oder Rechtsklick mit einer Uhr in der Hand, um zu einem kürzlichen Zeitpunkt zurückzuspulen, mit wiederhergestellter Gesundheit und Sättigung." - lore1 = "Rückspuldauer" - lore2 = "Abklingzeit" - lore3 = "Kein Inventar-Rollback" - lore = ["Rückspuldauer", "Abklingzeit", "Kein Inventar-Rollback"] +name = "Sofortige Rückkehr" +description = "Links- oder Rechtsklick mit einer Uhr in der Hand, um zu einem kürzlichen Zeitpunkt zurückzuspulen, mit wiederhergestellter Gesundheit und Sättigung." +lore1 = "Rückspuldauer" +lore2 = "Abklingzeit" +lore3 = "Kein Inventar-Rollback" +lore = ["Rückspuldauer", "Abklingzeit", "Kein Inventar-Rollback"] [chronos.time_bomb] - name = "Zeitbombe" - description = "Wirf eine hergestellte Chronobombe, die ein Zeitfeld erzeugt, Wesen verlangsamt und Geschosse einfriert." - lore1 = "Zeitfeld-Radius" - lore2 = "Zeitfeld-Dauer" - lore3 = "Bomben-Abklingzeit" - lore4 = "Rezept (Formlos): Uhr + Schneeball + Diamant + Sand" - lore = ["Zeitfeld-Radius", "Zeitfeld-Dauer", "Bomben-Abklingzeit", "Rezept (Formlos): Uhr + Schneeball + Diamant + Sand"] +name = "Zeitbombe" +description = "Wirf eine hergestellte Chronobombe, die ein Zeitfeld erzeugt, Wesen verlangsamt und Geschosse einfriert." +lore1 = "Zeitfeld-Radius" +lore2 = "Zeitfeld-Dauer" +lore3 = "Bomben-Abklingzeit" +lore4 = "Rezept (Formlos): Uhr + Schneeball + Diamant + Sand" +lore = ["Zeitfeld-Radius", "Zeitfeld-Dauer", "Bomben-Abklingzeit", "Rezept (Formlos): Uhr + Schneeball + Diamant + Sand"] # discovery [discovery] [discovery.armor] - name = "Weltrüstung" - description = "Passive Rüstung abhängig von der Härte nahegelegener Blöcke." - lore1 = "Passive Rüstung" - lore2 = "Basiert auf der Härte nahegelegener Blöcke" - lore3 = "Rüstungsstärke:" - lore = ["Passive Rüstung", "Basiert auf der Härte nahegelegener Blöcke", "Rüstungsstärke:"] +name = "Weltrüstung" +description = "Passive Rüstung abhängig von der Härte nahegelegener Blöcke." +lore1 = "Passive Rüstung" +lore2 = "Basiert auf der Härte nahegelegener Blöcke" +lore3 = "Rüstungsstärke:" +lore = ["Passive Rüstung", "Basiert auf der Härte nahegelegener Blöcke", "Rüstungsstärke:"] [discovery.unity] - name = "Experimentelle Einheit" - description = "Erfahrungskugeln sammeln fügt zufälligen Fähigkeiten EP hinzu." - lore1 = "EP " - lore2 = "Pro Kugel" - lore = ["EP ", "Pro Kugel"] +name = "Experimentelle Einheit" +description = "Erfahrungskugeln sammeln fügt zufälligen Fähigkeiten EP hinzu." +lore1 = "EP " +lore2 = "Pro Kugel" +lore = ["EP ", "Pro Kugel"] [discovery.resist] - name = "Experimenteller Widerstand" - description = "Verbrauche Erfahrung, um Schaden abzudämpfen, nur wenn ein Treffer dich unter 5 Herzen fallen lassen oder töten würde." - lore0 = "Löst nur bei kritischer Gesundheit (<= 5 Herzen) einmal alle 15 Sekunden aus" - lore1 = " Reduzierter Schaden" - lore2 = "Erfahrung verbraucht" - lore = ["Löst nur bei kritischer Gesundheit (<= 5 Herzen) einmal alle 15 Sekunden aus", " Reduzierter Schaden", "Erfahrung verbraucht"] +name = "Experimenteller Widerstand" +description = "Verbrauche Erfahrung, um Schaden abzudämpfen, nur wenn ein Treffer dich unter 5 Herzen fallen lassen oder töten würde." +lore0 = "Löst nur bei kritischer Gesundheit (<= 5 Herzen) einmal alle 15 Sekunden aus" +lore1 = " Reduzierter Schaden" +lore2 = "Erfahrung verbraucht" +lore = ["Löst nur bei kritischer Gesundheit (<= 5 Herzen) einmal alle 15 Sekunden aus", " Reduzierter Schaden", "Erfahrung verbraucht"] [discovery.villager] - name = "Dorfbewohner-Anziehung" - description = "Ermöglicht dir bessere Handelsangebote bei Dorfbewohnern!" - lore1 = "Verbraucht EP pro Interaktion mit Dorfbewohnern" - lore2 = "Chance pro Interaktion, EP zu verbrauchen und Handelsangebote zu verbessern" - lore3 = "Erforderlicher EP-Verbrauch pro Interaktion" - lore = ["Verbraucht EP pro Interaktion mit Dorfbewohnern", "Chance pro Interaktion, EP zu verbrauchen und Handelsangebote zu verbessern", "Erforderlicher EP-Verbrauch pro Interaktion"] +name = "Dorfbewohner-Anziehung" +description = "Ermöglicht dir bessere Handelsangebote bei Dorfbewohnern!" +lore1 = "Verbraucht EP pro Interaktion mit Dorfbewohnern" +lore2 = "Chance pro Interaktion, EP zu verbrauchen und Handelsangebote zu verbessern" +lore3 = "Erforderlicher EP-Verbrauch pro Interaktion" +lore = ["Verbraucht EP pro Interaktion mit Dorfbewohnern", "Chance pro Interaktion, EP zu verbrauchen und Handelsangebote zu verbessern", "Erforderlicher EP-Verbrauch pro Interaktion"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Lapislazuli-Rückgabe" - description = "Auf Kosten von 1 Stufe mehr EP, mit einer Chance, kostenlos Lapislazuli zurückzubekommen" - lore1 = "Für jede Stufe erhöhen sich die Verzauberungskosten um 1, können aber bis zu 3 Lapislazuli zurückgeben" - lore = ["Für jede Stufe erhöhen sich die Verzauberungskosten um 1, können aber bis zu 3 Lapislazuli zurückgeben"] +name = "Lapislazuli-Rückgabe" +description = "Auf Kosten von 1 Stufe mehr EP, mit einer Chance, kostenlos Lapislazuli zurückzubekommen" +lore1 = "Für jede Stufe erhöhen sich die Verzauberungskosten um 1, können aber bis zu 3 Lapislazuli zurückgeben" +lore = ["Für jede Stufe erhöhen sich die Verzauberungskosten um 1, können aber bis zu 3 Lapislazuli zurückgeben"] [enchanting.quick_enchant] - name = "Schnellklick-Verzauberung" - description = "Verzaubere Gegenstände, indem du Verzauberungsbücher direkt darauf klickst." - lore1 = "Maximale kombinierte Stufen" - lore2 = "Kann einen Gegenstand nicht mit mehr als " - lore3 = "Energie verzaubern" - lore = ["Maximale kombinierte Stufen", "Kann einen Gegenstand nicht mit mehr als ", "Energie verzaubern"] +name = "Schnellklick-Verzauberung" +description = "Verzaubere Gegenstände, indem du Verzauberungsbücher direkt darauf klickst." +lore1 = "Maximale kombinierte Stufen" +lore2 = "Kann einen Gegenstand nicht mit mehr als " +lore3 = "Energie verzaubern" +lore = ["Maximale kombinierte Stufen", "Kann einen Gegenstand nicht mit mehr als ", "Energie verzaubern"] [enchanting.return] - name = "EP-Rückgabe" - description = "Verzauberungs-EP werden dir beim Verzaubern eines Gegenstands zurückgegeben." - lore1 = "Ausgegebene Erfahrung hat eine Chance, beim Verzaubern erstattet zu werden" - lore2 = "Erfahrung pro Verzauberung" - lore = ["Ausgegebene Erfahrung hat eine Chance, beim Verzaubern erstattet zu werden", "Erfahrung pro Verzauberung"] +name = "EP-Rückgabe" +description = "Verzauberungs-EP werden dir beim Verzaubern eines Gegenstands zurückgegeben." +lore1 = "Ausgegebene Erfahrung hat eine Chance, beim Verzaubern erstattet zu werden" +lore2 = "Erfahrung pro Verzauberung" +lore = ["Ausgegebene Erfahrung hat eine Chance, beim Verzaubern erstattet zu werden", "Erfahrung pro Verzauberung"] # excavation [excavation] [excavation.haste] - name = "Eiliger Ausgräber" - description = "Beschleunigt den Grabungsprozess mit EILE!" - lore1 = "Erhalte Eile beim Graben" - lore2 = "x Stufen Eile, wenn du anfängst, IRGENDWELCHE Blöcke abzubauen." - lore = ["Erhalte Eile beim Graben", "x Stufen Eile, wenn du anfängst, IRGENDWELCHE Blöcke abzubauen."] +name = "Eiliger Ausgräber" +description = "Beschleunigt den Grabungsprozess mit EILE!" +lore1 = "Erhalte Eile beim Graben" +lore2 = "x Stufen Eile, wenn du anfängst, IRGENDWELCHE Blöcke abzubauen." +lore = ["Erhalte Eile beim Graben", "x Stufen Eile, wenn du anfängst, IRGENDWELCHE Blöcke abzubauen."] [excavation.spelunker] - name = "Supersehender Höhlenforscher!" - description = "Sieh Erze mit deinen Augen, aber durch den Boden!" - lore1 = "Erz in der Nebenhand, Leuchtbeeren in der Haupthand, und Schleichen!" - lore2 = "Blockreichweite: " - lore3 = "Verbraucht eine Leuchtbeere bei Benutzung" - lore = ["Erz in der Nebenhand, Leuchtbeeren in der Haupthand, und Schleichen!", "Blockreichweite: ", "Verbraucht eine Leuchtbeere bei Benutzung"] +name = "Supersehender Höhlenforscher!" +description = "Sieh Erze mit deinen Augen, aber durch den Boden!" +lore1 = "Erz in der Nebenhand, Leuchtbeeren in der Haupthand, und Schleichen!" +lore2 = "Blockreichweite: " +lore3 = "Verbraucht eine Leuchtbeere bei Benutzung" +lore = ["Erz in der Nebenhand, Leuchtbeeren in der Haupthand, und Schleichen!", "Blockreichweite: ", "Verbraucht eine Leuchtbeere bei Benutzung"] [excavation.drop_to_inventory] - name = "Schaufel-Drop-ins-Inventar" +name = "Schaufel-Drop-ins-Inventar" [excavation.omni_tool] - name = "OMNI - W.E.R.K." - description = "Tackles überdesigntes opulentes Taschenmesser" - lore1 = "Wahrscheinlich das Mächtigste von vielen: ermöglicht es dir," - lore2 = "Werkzeuge dynamisch zusammenzuführen und nach Bedarf zu wechseln." - lore3 = "Zum Zusammenführen: Shift-Klick auf einen Gegenstand über einem anderen im Inventar." - lore4 = "Zum Trennen: Schleiche und wirf den Gegenstand, um ihn zu zerlegen." - lore5 = "Werkzeuge in diesem Taschenmesser können nicht zerbrechen, aber zerbrochene können nicht benutzt werden" - lore6 = "Zusammenführbare Gegenstände insgesamt." - lore7 = "Warum fünf oder sechs Werkzeuge, wenn auch eins reicht!" - lore = ["Wahrscheinlich das Mächtigste von vielen: ermöglicht es dir,", "Werkzeuge dynamisch zusammenzuführen und nach Bedarf zu wechseln.", "Zum Zusammenführen: Shift-Klick auf einen Gegenstand über einem anderen im Inventar.", "Zum Trennen: Schleiche und wirf den Gegenstand, um ihn zu zerlegen.", "Werkzeuge in diesem Taschenmesser können nicht zerbrechen, aber zerbrochene können nicht benutzt werden", "Zusammenführbare Gegenstände insgesamt.", "Warum fünf oder sechs Werkzeuge, wenn auch eins reicht!"] +name = "OMNI - W.E.R.K." +description = "Tackles überdesigntes opulentes Taschenmesser" +lore1 = "Wahrscheinlich das Mächtigste von vielen: ermöglicht es dir," +lore2 = "Werkzeuge dynamisch zusammenzuführen und nach Bedarf zu wechseln." +lore3 = "Zum Zusammenführen: Shift-Klick auf einen Gegenstand über einem anderen im Inventar." +lore4 = "Zum Trennen: Schleiche und wirf den Gegenstand, um ihn zu zerlegen." +lore5 = "Werkzeuge in diesem Taschenmesser können nicht zerbrechen, aber zerbrochene können nicht benutzt werden" +lore6 = "Zusammenführbare Gegenstände insgesamt." +lore7 = "Warum fünf oder sechs Werkzeuge, wenn auch eins reicht!" +lore = ["Wahrscheinlich das Mächtigste von vielen: ermöglicht es dir,", "Werkzeuge dynamisch zusammenzuführen und nach Bedarf zu wechseln.", "Zum Zusammenführen: Shift-Klick auf einen Gegenstand über einem anderen im Inventar.", "Zum Trennen: Schleiche und wirf den Gegenstand, um ihn zu zerlegen.", "Werkzeuge in diesem Taschenmesser können nicht zerbrechen, aber zerbrochene können nicht benutzt werden", "Zusammenführbare Gegenstände insgesamt.", "Warum fünf oder sechs Werkzeuge, wenn auch eins reicht!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Wachstumsaura" - description = "Lasse die Natur um dich herum in einer Aura wachsen" - lore1 = "Block-Radius" - lore2 = "Stärke der Wachstumsaura" - lore3 = "Nahrungskosten" - lore = ["Block-Radius", "Stärke der Wachstumsaura", "Nahrungskosten"] +name = "Wachstumsaura" +description = "Lasse die Natur um dich herum in einer Aura wachsen" +lore1 = "Block-Radius" +lore2 = "Stärke der Wachstumsaura" +lore3 = "Nahrungskosten" +lore = ["Block-Radius", "Stärke der Wachstumsaura", "Nahrungskosten"] [herbalism.hippo] - name = "Raupe Nimmersatt" - description = "Nahrungsaufnahme gibt dir mehr Sättigung" - lore1 = "Nahrung) zusätzliche Sättigungspunkte beim Verzehr" - lore = ["Nahrung) zusätzliche Sättigungspunkte beim Verzehr"] +name = "Raupe Nimmersatt" +description = "Nahrungsaufnahme gibt dir mehr Sättigung" +lore1 = "Nahrung) zusätzliche Sättigungspunkte beim Verzehr" +lore = ["Nahrung) zusätzliche Sättigungspunkte beim Verzehr"] [herbalism.myconid] - name = "Myzel des Kräuterkundlers" - description = "Verleiht dir die Fähigkeit, Myzel herzustellen" - lore1 = "Jede Erde, ein brauner und ein roter Pilz ergeben Myzel." - lore = ["Jede Erde, ein brauner und ein roter Pilz ergeben Myzel."] +name = "Myzel des Kräuterkundlers" +description = "Verleiht dir die Fähigkeit, Myzel herzustellen" +lore1 = "Jede Erde, ein brauner und ein roter Pilz ergeben Myzel." +lore = ["Jede Erde, ein brauner und ein roter Pilz ergeben Myzel."] [herbalism.terralid] - name = "Terrain des Kräuterkundlers" - description = "Verleiht dir die Fähigkeit, Grasblöcke herzustellen" - lore1 = "Drei Samen über 3 Erde ergeben 3 Grasblöcke." - lore = ["Drei Samen über 3 Erde ergeben 3 Grasblöcke."] +name = "Terrain des Kräuterkundlers" +description = "Verleiht dir die Fähigkeit, Grasblöcke herzustellen" +lore1 = "Drei Samen über 3 Erde ergeben 3 Grasblöcke." +lore = ["Drei Samen über 3 Erde ergeben 3 Grasblöcke."] [herbalism.cobweb] - name = "Spinnennetzweber" - description = "Verleiht dir die Fähigkeit, Spinnweben in einer Werkbank herzustellen" - lore1 = "Neun Fäden ergeben ein Spinnennetz." - lore = ["Neun Fäden ergeben ein Spinnennetz."] +name = "Spinnennetzweber" +description = "Verleiht dir die Fähigkeit, Spinnweben in einer Werkbank herzustellen" +lore1 = "Neun Fäden ergeben ein Spinnennetz." +lore = ["Neun Fäden ergeben ein Spinnennetz."] [herbalism.mushroom_blocks] - name = "Pilzblockmacher" - description = "Verleiht dir die Fähigkeit, Pilzblöcke in einer Werkbank herzustellen" - lore1 = "Vier Pilze für einen Block, oder ein Block für einen Stiel." - lore = ["Vier Pilze für einen Block, oder ein Block für einen Stiel."] +name = "Pilzblockmacher" +description = "Verleiht dir die Fähigkeit, Pilzblöcke in einer Werkbank herzustellen" +lore1 = "Vier Pilze für einen Block, oder ein Block für einen Stiel." +lore = ["Vier Pilze für einen Block, oder ein Block für einen Stiel."] [herbalism.drop_to_inventory] - name = "Hacke-Drop-ins-Inventar" +name = "Hacke-Drop-ins-Inventar" [herbalism.hungry_shield] - name = "Hungriges Schild" - description = "Nimm Schaden an deinem Hunger vor deiner Gesundheit." - lore1 = "Durch Hunger abgewehrt" - lore = ["Durch Hunger abgewehrt"] +name = "Hungriges Schild" +description = "Nimm Schaden an deinem Hunger vor deiner Gesundheit." +lore1 = "Durch Hunger abgewehrt" +lore = ["Durch Hunger abgewehrt"] [herbalism.luck] - name = "Glück des Kräuterkundlers" - description = "Wenn du Gras/Blumen zerbrichst, hast du eine Chance, einen zufälligen Gegenstand zu erhalten" - lore0 = "Blumen = Nahrung, und Gras = Samen" - lore1 = "Chance, einen Gegenstand aus zerbrochenen Blumen zu erhalten" - lore2 = "Chance, einen Gegenstand aus zerbrochenem Gras zu erhalten" - lore = ["Blumen = Nahrung, und Gras = Samen", "Chance, einen Gegenstand aus zerbrochenen Blumen zu erhalten", "Chance, einen Gegenstand aus zerbrochenem Gras zu erhalten"] +name = "Glück des Kräuterkundlers" +description = "Wenn du Gras/Blumen zerbrichst, hast du eine Chance, einen zufälligen Gegenstand zu erhalten" +lore0 = "Blumen = Nahrung, und Gras = Samen" +lore1 = "Chance, einen Gegenstand aus zerbrochenen Blumen zu erhalten" +lore2 = "Chance, einen Gegenstand aus zerbrochenem Gras zu erhalten" +lore = ["Blumen = Nahrung, und Gras = Samen", "Chance, einen Gegenstand aus zerbrochenen Blumen zu erhalten", "Chance, einen Gegenstand aus zerbrochenem Gras zu erhalten"] [herbalism.replant] - name = "Ernten & Neupflanzen" - description = "Rechtsklick auf eine Pflanze mit einer Hacke, um sie zu ernten und neu zu pflanzen." - lore1 = "Blöcke Neupflanz-Radius" - lore = ["Blöcke Neupflanz-Radius"] +name = "Ernten & Neupflanzen" +description = "Rechtsklick auf eine Pflanze mit einer Hacke, um sie zu ernten und neu zu pflanzen." +lore1 = "Blöcke Neupflanz-Radius" +lore = ["Blöcke Neupflanz-Radius"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenalin" - description = "Verursache mehr Schaden, je weniger Gesundheit du hast (Nahkampf)" - lore1 = "Maximaler Schaden" - lore = ["Maximaler Schaden"] +name = "Adrenalin" +description = "Verursache mehr Schaden, je weniger Gesundheit du hast (Nahkampf)" +lore1 = "Maximaler Schaden" +lore = ["Maximaler Schaden"] [hunter.penalty] - name = "" - description = "" - lore1 = "Du erhältst Giftstapel, wenn dir der Hunger ausgeht" - lore = ["Du erhältst Giftstapel, wenn dir der Hunger ausgeht"] +name = "" +description = "" +lore1 = "Du erhältst Giftstapel, wenn dir der Hunger ausgeht" +lore = ["Du erhältst Giftstapel, wenn dir der Hunger ausgeht"] [hunter.drop_to_inventory] - name = "Gegenstände-Drop-ins-Inventar" - description = "Wenn du etwas tötest / einen Block mit einem Schwert zerstörst, werden die Drops in dein Inventar teleportiert" - lore1 = "Immer wenn ein Gegenstand von einem Mob/Block droppt, den du zerstörst, landet er in deinem Inventar." - lore = ["Immer wenn ein Gegenstand von einem Mob/Block droppt, den du zerstörst, landet er in deinem Inventar."] +name = "Gegenstände-Drop-ins-Inventar" +description = "Wenn du etwas tötest / einen Block mit einem Schwert zerstörst, werden die Drops in dein Inventar teleportiert" +lore1 = "Immer wenn ein Gegenstand von einem Mob/Block droppt, den du zerstörst, landet er in deinem Inventar." +lore = ["Immer wenn ein Gegenstand von einem Mob/Block droppt, den du zerstörst, landet er in deinem Inventar."] [hunter.invisibility] - name = "Verschwindender Schritt" - description = "Wenn du getroffen wirst, erhältst du Unsichtbarkeit auf Kosten von Hunger" - lore1 = "Erhalte passive Unsichtbarkeit bei Treffern" - lore2 = "x Unsichtbarkeitsstapel für 3 Sekunden bei Treffer" - lore3 = "x Stapelbarer Hunger" - lore4 = "Hungerstapel Dauer und Multiplikator." - lore5 = "Unsichtbarkeitsdauer" - lore = ["Erhalte passive Unsichtbarkeit bei Treffern", "x Unsichtbarkeitsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Unsichtbarkeitsdauer"] +name = "Verschwindender Schritt" +description = "Wenn du getroffen wirst, erhältst du Unsichtbarkeit auf Kosten von Hunger" +lore1 = "Erhalte passive Unsichtbarkeit bei Treffern" +lore2 = "x Unsichtbarkeitsstapel für 3 Sekunden bei Treffer" +lore3 = "x Stapelbarer Hunger" +lore4 = "Hungerstapel Dauer und Multiplikator." +lore5 = "Unsichtbarkeitsdauer" +lore = ["Erhalte passive Unsichtbarkeit bei Treffern", "x Unsichtbarkeitsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Unsichtbarkeitsdauer"] [hunter.jump_boost] - name = "Jägers Höhen" - description = "Wenn du getroffen wirst, erhältst du Sprungverstärkung auf Kosten von Hunger" - lore1 = "Erhalte passiven Sprungboost bei Treffern" - lore2 = "x Sprungverstärkungsstapel für 3 Sekunden bei Treffer" - lore3 = "x Stapelbarer Hunger" - lore4 = "Hungerstapel Dauer und Multiplikator." - lore5 = "Sprungverstärkung-Stapelmultiplikator, nicht Dauer." - lore = ["Erhalte passiven Sprungboost bei Treffern", "x Sprungverstärkungsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Sprungverstärkung-Stapelmultiplikator, nicht Dauer."] +name = "Jägers Höhen" +description = "Wenn du getroffen wirst, erhältst du Sprungverstärkung auf Kosten von Hunger" +lore1 = "Erhalte passiven Sprungboost bei Treffern" +lore2 = "x Sprungverstärkungsstapel für 3 Sekunden bei Treffer" +lore3 = "x Stapelbarer Hunger" +lore4 = "Hungerstapel Dauer und Multiplikator." +lore5 = "Sprungverstärkung-Stapelmultiplikator, nicht Dauer." +lore = ["Erhalte passiven Sprungboost bei Treffern", "x Sprungverstärkungsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Sprungverstärkung-Stapelmultiplikator, nicht Dauer."] [hunter.luck] - name = "Jägers Glück" - description = "Wenn du getroffen wirst, erhältst du Glück auf Kosten von Hunger" - lore1 = "Erhalte passives Glück bei Treffern" - lore2 = "x Glücksstapel für 3 Sekunden bei Treffer" - lore3 = "x Stapelbarer Hunger" - lore4 = "Hungerstapel Dauer und Multiplikator." - lore5 = "Glück-Stapelmultiplikator, nicht Dauer." - lore = ["Erhalte passives Glück bei Treffern", "x Glücksstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Glück-Stapelmultiplikator, nicht Dauer."] +name = "Jägers Glück" +description = "Wenn du getroffen wirst, erhältst du Glück auf Kosten von Hunger" +lore1 = "Erhalte passives Glück bei Treffern" +lore2 = "x Glücksstapel für 3 Sekunden bei Treffer" +lore3 = "x Stapelbarer Hunger" +lore4 = "Hungerstapel Dauer und Multiplikator." +lore5 = "Glück-Stapelmultiplikator, nicht Dauer." +lore = ["Erhalte passives Glück bei Treffern", "x Glücksstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Glück-Stapelmultiplikator, nicht Dauer."] [hunter.regen] - name = "Jägers Regeneration" - description = "Wenn du getroffen wirst, erhältst du Regeneration auf Kosten von Hunger" - lore1 = "Erhalte passive Regeneration bei Treffern" - lore2 = "x Regenerationsstapel für 3 Sekunden bei Treffer" - lore3 = "x Stapelbarer Hunger" - lore4 = "Hungerstapel Dauer und Multiplikator." - lore5 = "Regeneration-Stapelmultiplikator, nicht Dauer." - lore = ["Erhalte passive Regeneration bei Treffern", "x Regenerationsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Regeneration-Stapelmultiplikator, nicht Dauer."] +name = "Jägers Regeneration" +description = "Wenn du getroffen wirst, erhältst du Regeneration auf Kosten von Hunger" +lore1 = "Erhalte passive Regeneration bei Treffern" +lore2 = "x Regenerationsstapel für 3 Sekunden bei Treffer" +lore3 = "x Stapelbarer Hunger" +lore4 = "Hungerstapel Dauer und Multiplikator." +lore5 = "Regeneration-Stapelmultiplikator, nicht Dauer." +lore = ["Erhalte passive Regeneration bei Treffern", "x Regenerationsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Regeneration-Stapelmultiplikator, nicht Dauer."] [hunter.resistance] - name = "Jägers Widerstand" - description = "Wenn du getroffen wirst, erhältst du Widerstand auf Kosten von Hunger" - lore1 = "Erhalte passiven Widerstand bei Treffern" - lore2 = "x Widerstandsstapel für 3 Sekunden bei Treffer" - lore3 = "x Stapelbarer Hunger" - lore4 = "Hungerstapel Dauer und Multiplikator." - lore5 = "Widerstands-Stapelmultiplikator, nicht Dauer." - lore = ["Erhalte passiven Widerstand bei Treffern", "x Widerstandsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Widerstands-Stapelmultiplikator, nicht Dauer."] +name = "Jägers Widerstand" +description = "Wenn du getroffen wirst, erhältst du Widerstand auf Kosten von Hunger" +lore1 = "Erhalte passiven Widerstand bei Treffern" +lore2 = "x Widerstandsstapel für 3 Sekunden bei Treffer" +lore3 = "x Stapelbarer Hunger" +lore4 = "Hungerstapel Dauer und Multiplikator." +lore5 = "Widerstands-Stapelmultiplikator, nicht Dauer." +lore = ["Erhalte passiven Widerstand bei Treffern", "x Widerstandsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Widerstands-Stapelmultiplikator, nicht Dauer."] [hunter.speed] - name = "Jägers Geschwindigkeit" - description = "Wenn du getroffen wirst, erhältst du Geschwindigkeit auf Kosten von Hunger" - lore1 = "Erhalte passive Geschwindigkeit bei Treffern" - lore2 = "x Geschwindigkeitsstapel für 3 Sekunden bei Treffer" - lore3 = "x Stapelbarer Hunger" - lore4 = "Hungerstapel Dauer und Multiplikator." - lore5 = "Geschwindigkeit-Stapelmultiplikator, nicht Dauer." - lore = ["Erhalte passive Geschwindigkeit bei Treffern", "x Geschwindigkeitsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Geschwindigkeit-Stapelmultiplikator, nicht Dauer."] +name = "Jägers Geschwindigkeit" +description = "Wenn du getroffen wirst, erhältst du Geschwindigkeit auf Kosten von Hunger" +lore1 = "Erhalte passive Geschwindigkeit bei Treffern" +lore2 = "x Geschwindigkeitsstapel für 3 Sekunden bei Treffer" +lore3 = "x Stapelbarer Hunger" +lore4 = "Hungerstapel Dauer und Multiplikator." +lore5 = "Geschwindigkeit-Stapelmultiplikator, nicht Dauer." +lore = ["Erhalte passive Geschwindigkeit bei Treffern", "x Geschwindigkeitsstapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Geschwindigkeit-Stapelmultiplikator, nicht Dauer."] [hunter.strength] - name = "Jägers Stärke" - description = "Wenn du getroffen wirst, erhältst du Stärke auf Kosten von Hunger" - lore1 = "Erhalte passive Stärke bei Treffern" - lore2 = "x Stärkestapel für 3 Sekunden bei Treffer" - lore3 = "x Stapelbarer Hunger" - lore4 = "Hungerstapel Dauer und Multiplikator." - lore5 = "Stärke-Stapelmultiplikator, nicht Dauer." - lore = ["Erhalte passive Stärke bei Treffern", "x Stärkestapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Stärke-Stapelmultiplikator, nicht Dauer."] +name = "Jägers Stärke" +description = "Wenn du getroffen wirst, erhältst du Stärke auf Kosten von Hunger" +lore1 = "Erhalte passive Stärke bei Treffern" +lore2 = "x Stärkestapel für 3 Sekunden bei Treffer" +lore3 = "x Stapelbarer Hunger" +lore4 = "Hungerstapel Dauer und Multiplikator." +lore5 = "Stärke-Stapelmultiplikator, nicht Dauer." +lore = ["Erhalte passive Stärke bei Treffern", "x Stärkestapel für 3 Sekunden bei Treffer", "x Stapelbarer Hunger", "Hungerstapel Dauer und Multiplikator.", "Stärke-Stapelmultiplikator, nicht Dauer."] # nether [nether] [nether.skull_toss] - name = "Witherschädel-Wurf" - description1 = "Entfessle deinen inneren Wither, indem du" - description2 = "jemandes" - description3 = "Kopf benutzt." - lore1 = "Sekunden Abklingzeit zwischen Schädelwürfen." - lore2 = "Witherschädel verwenden: Wirf einen " - lore3 = "Witherschädel" - lore4 = "der beim Aufprall explodiert." - lore = ["Sekunden Abklingzeit zwischen Schädelwürfen.", "Witherschädel verwenden: Wirf einen ", "Witherschädel", "der beim Aufprall explodiert."] +name = "Witherschädel-Wurf" +description1 = "Entfessle deinen inneren Wither, indem du" +description2 = "jemandes" +description3 = "Kopf benutzt." +lore1 = "Sekunden Abklingzeit zwischen Schädelwürfen." +lore2 = "Witherschädel verwenden: Wirf einen " +lore3 = "Witherschädel" +lore4 = "der beim Aufprall explodiert." +lore = ["Sekunden Abklingzeit zwischen Schädelwürfen.", "Witherschädel verwenden: Wirf einen ", "Witherschädel", "der beim Aufprall explodiert."] [nether.wither_resist] - name = "Wither-Resistenz" - description = "Widersteht dem Wither durch die Kraft des Netherit." - lore1 = "Chance, Wither zu negieren (pro Rüstungsteil)." - lore2 = "Passiv: Netherit-Rüstung hat eine Chance, das " - lore3 = "Withern zu negieren." - lore = ["Chance, Wither zu negieren (pro Rüstungsteil).", "Passiv: Netherit-Rüstung hat eine Chance, das ", "Withern zu negieren."] +name = "Wither-Resistenz" +description = "Widersteht dem Wither durch die Kraft des Netherit." +lore1 = "Chance, Wither zu negieren (pro Rüstungsteil)." +lore2 = "Passiv: Netherit-Rüstung hat eine Chance, das " +lore3 = "Withern zu negieren." +lore = ["Chance, Wither zu negieren (pro Rüstungsteil).", "Passiv: Netherit-Rüstung hat eine Chance, das ", "Withern zu negieren."] [nether.fire_resist] - name = "Feuerresistenz" - description = "Widersteht Feuer durch Härtung deiner Haut." - lore1 = "Chance, den Brenneffekt zu negieren!" - lore = ["Chance, den Brenneffekt zu negieren!"] +name = "Feuerresistenz" +description = "Widersteht Feuer durch Härtung deiner Haut." +lore1 = "Chance, den Brenneffekt zu negieren!" +lore = ["Chance, den Brenneffekt zu negieren!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Automatisches Schmelzen" - description = "Ermöglicht das Schmelzen von abgebauten Vanilla-Erzen" - lore1 = "Erze, die geschmolzen werden können, werden automatisch geschmolzen" - lore2 = "% Chance auf ein Extra" - lore = ["Erze, die geschmolzen werden können, werden automatisch geschmolzen", "% Chance auf ein Extra"] +name = "Automatisches Schmelzen" +description = "Ermöglicht das Schmelzen von abgebauten Vanilla-Erzen" +lore1 = "Erze, die geschmolzen werden können, werden automatisch geschmolzen" +lore2 = "% Chance auf ein Extra" +lore = ["Erze, die geschmolzen werden können, werden automatisch geschmolzen", "% Chance auf ein Extra"] [pickaxe.chisel] - name = "Erzmeißel" - description = "Rechtsklick auf Erze, um mehr Erz herauszumeißeln, auf Kosten starker Haltbarkeitsabnutzung." - lore1 = "Chance auf Drop" - lore2 = "Werkzeugverschleiß" - lore = ["Chance auf Drop", "Werkzeugverschleiß"] +name = "Erzmeißel" +description = "Rechtsklick auf Erze, um mehr Erz herauszumeißeln, auf Kosten starker Haltbarkeitsabnutzung." +lore1 = "Chance auf Drop" +lore2 = "Werkzeugverschleiß" +lore = ["Chance auf Drop", "Werkzeugverschleiß"] [pickaxe.drop_to_inventory] - name = "Spitzhacke-Drop-ins-Inventar" - description = "Wenn du einen Block zerbrichst, wird der Gegenstand in dein Inventar teleportiert" - lore1 = "Immer wenn ein Gegenstand von einem zerbrochenen Block droppt, landet er in deinem Inventar." - lore = ["Immer wenn ein Gegenstand von einem zerbrochenen Block droppt, landet er in deinem Inventar."] +name = "Spitzhacke-Drop-ins-Inventar" +description = "Wenn du einen Block zerbrichst, wird der Gegenstand in dein Inventar teleportiert" +lore1 = "Immer wenn ein Gegenstand von einem zerbrochenen Block droppt, landet er in deinem Inventar." +lore = ["Immer wenn ein Gegenstand von einem zerbrochenen Block droppt, landet er in deinem Inventar."] [pickaxe.silk_spawner] - name = "Spitzhacke Behutsamkeits-Spawner" - description = "Spawner droppen beim Abbauen" - lore1 = "Spawner können mit Behutsamkeit abgebaut werden." - lore2 = "Spawner können im Schleichen abgebaut werden." - lore = ["Spawner können mit Behutsamkeit abgebaut werden.", "Spawner können im Schleichen abgebaut werden."] +name = "Spitzhacke Behutsamkeits-Spawner" +description = "Spawner droppen beim Abbauen" +lore1 = "Spawner können mit Behutsamkeit abgebaut werden." +lore2 = "Spawner können im Schleichen abgebaut werden." +lore = ["Spawner können mit Behutsamkeit abgebaut werden.", "Spawner können im Schleichen abgebaut werden."] [pickaxe.vein_miner] - name = "Aderabbau" - description = "Ermöglicht das Abbauen von Blöcken in einer Ader/einem Cluster von Vanilla-Erzen" - lore1 = "Schleiche und baue ERZE ab" - lore2 = "Reichweite des Aderabbaus" - lore3 = "Diese Fähigkeit fasst NICHT alle Drops zusammen!" - lore = ["Schleiche und baue ERZE ab", "Reichweite des Aderabbaus", "Diese Fähigkeit fasst NICHT alle Drops zusammen!"] +name = "Aderabbau" +description = "Ermöglicht das Abbauen von Blöcken in einer Ader/einem Cluster von Vanilla-Erzen" +lore1 = "Schleiche und baue ERZE ab" +lore2 = "Reichweite des Aderabbaus" +lore3 = "Diese Fähigkeit fasst NICHT alle Drops zusammen!" +lore = ["Schleiche und baue ERZE ab", "Reichweite des Aderabbaus", "Diese Fähigkeit fasst NICHT alle Drops zusammen!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Pfeilbergung" - description = "Stelle Pfeile wieder her, nachdem du einen Feind getötet hast." - lore1 = "Chance, Pfeile bei Treffer/Tötung zurückzubekommen" - lore2 = "Chance: " - lore = ["Chance, Pfeile bei Treffer/Tötung zurückzubekommen", "Chance: "] +name = "Pfeilbergung" +description = "Stelle Pfeile wieder her, nachdem du einen Feind getötet hast." +lore1 = "Chance, Pfeile bei Treffer/Tötung zurückzubekommen" +lore2 = "Chance: " +lore = ["Chance, Pfeile bei Treffer/Tötung zurückzubekommen", "Chance: "] [ranged.web_shot] - name = "Netzfalle" - description = "Umhülle dein Ziel mit Spinnweben, wenn du es triffst!" - lore1 = "8 Spinnweben um einen Schneeball, und werfen!" - lore2 = "Sekunden eines Käfigs, ungefähr." - lore = ["8 Spinnweben um einen Schneeball, und werfen!", "Sekunden eines Käfigs, ungefähr."] +name = "Netzfalle" +description = "Umhülle dein Ziel mit Spinnweben, wenn du es triffst!" +lore1 = "8 Spinnweben um einen Schneeball, und werfen!" +lore2 = "Sekunden eines Käfigs, ungefähr." +lore = ["8 Spinnweben um einen Schneeball, und werfen!", "Sekunden eines Käfigs, ungefähr."] [ranged.force_shot] - name = "Kraftschuss" - description = "Schieße Geschosse weiter und schneller!" - advancementname = "Weitschuss" - advancementlore = "Lande einen Schuss aus über 30 Blöcken Entfernung!" - lore1 = "Geschossgeschwindigkeit" - lore = ["Geschossgeschwindigkeit"] +name = "Kraftschuss" +description = "Schieße Geschosse weiter und schneller!" +advancementname = "Weitschuss" +advancementlore = "Lande einen Schuss aus über 30 Blöcken Entfernung!" +lore1 = "Geschossgeschwindigkeit" +lore = ["Geschossgeschwindigkeit"] [ranged.lunge_shot] - name = "Ausfallschuss" - description = "Während du fällst, schleudern dich deine Pfeile in eine zufällige Richtung" - lore1 = "Zufällige Schubgeschwindigkeit" - lore = ["Zufällige Schubgeschwindigkeit"] +name = "Ausfallschuss" +description = "Während du fällst, schleudern dich deine Pfeile in eine zufällige Richtung" +lore1 = "Zufällige Schubgeschwindigkeit" +lore = ["Zufällige Schubgeschwindigkeit"] [ranged.arrow_piercing] - name = "Pfeildurchbohrung" - description = "Fügt Geschossen Durchschlagskraft hinzu! Schieße durch Dinge hindurch!" - lore1 = "Ziele durchbohren" - lore = ["Ziele durchbohren"] +name = "Pfeildurchbohrung" +description = "Fügt Geschossen Durchschlagskraft hinzu! Schieße durch Dinge hindurch!" +lore1 = "Ziele durchbohren" +lore = ["Ziele durchbohren"] # rift [rift] [rift.remote_access] - name = "Fernzugriff" - description = "Greife in die Leere und gelange zu einem markierten Behälter." - lore1 = "Enderperle + Kompass = Reliquien-Portschlüssel" - lore2 = "Dieser Gegenstand ermöglicht dir den Fernzugriff auf Behälter" - lore3 = "Nach der Herstellung den Gegenstand ansehen für die Nutzung" - notcontainer = "Das ist kein Behälter" - lore = ["Enderperle + Kompass = Reliquien-Portschlüssel", "Dieser Gegenstand ermöglicht dir den Fernzugriff auf Behälter", "Nach der Herstellung den Gegenstand ansehen für die Nutzung"] +name = "Fernzugriff" +description = "Greife in die Leere und gelange zu einem markierten Behälter." +lore1 = "Enderperle + Kompass = Reliquien-Portschlüssel" +lore2 = "Dieser Gegenstand ermöglicht dir den Fernzugriff auf Behälter" +lore3 = "Nach der Herstellung den Gegenstand ansehen für die Nutzung" +notcontainer = "Das ist kein Behälter" +lore = ["Enderperle + Kompass = Reliquien-Portschlüssel", "Dieser Gegenstand ermöglicht dir den Fernzugriff auf Behälter", "Nach der Herstellung den Gegenstand ansehen für die Nutzung"] [rift.blink] - name = "Riss-Blinzeln" - description = "Kurze sofortige Teleportation, nur ein Wimpernschlag entfernt!" - lore1 = "Blöcke beim Blinzeln (2x vertikal)" - lore2 = "Beim Sprinten: Doppeltippe Springen, um zu " - lore3 = "Blinzeln" - lore = ["Blöcke beim Blinzeln (2x vertikal)", "Beim Sprinten: Doppeltippe Springen, um zu ", "Blinzeln"] +name = "Riss-Blinzeln" +description = "Kurze sofortige Teleportation, nur ein Wimpernschlag entfernt!" +lore1 = "Blöcke beim Blinzeln (2x vertikal)" +lore2 = "Beim Sprinten: Doppeltippe Springen, um zu " +lore3 = "Blinzeln" +lore = ["Blöcke beim Blinzeln (2x vertikal)", "Beim Sprinten: Doppeltippe Springen, um zu ", "Blinzeln"] [rift.chest] - name = "Einfache Endertruhe" - description = "Öffne eine Endertruhe per Linksklick in deiner Hand." - lore1 = "Klicke auf eine Endertruhe in deiner Hand, um sie zu öffnen (nicht platzieren)" - lore = ["Klicke auf eine Endertruhe in deiner Hand, um sie zu öffnen (nicht platzieren)"] +name = "Einfache Endertruhe" +description = "Öffne eine Endertruhe per Linksklick in deiner Hand." +lore1 = "Klicke auf eine Endertruhe in deiner Hand, um sie zu öffnen (nicht platzieren)" +lore = ["Klicke auf eine Endertruhe in deiner Hand, um sie zu öffnen (nicht platzieren)"] [rift.descent] - name = "Anti-Levitation" - description = "Bist du es leid, in der Luft festzusitzen? Das ist die Fähigkeit für dich!" - lore1 = "Einfach schleichen zum Absteigen, und du fällst langsamer als normal!" - lore2 = "Abklingzeit:" - lore = ["Einfach schleichen zum Absteigen, und du fällst langsamer als normal!", "Abklingzeit:"] +name = "Anti-Levitation" +description = "Bist du es leid, in der Luft festzusitzen? Das ist die Fähigkeit für dich!" +lore1 = "Einfach schleichen zum Absteigen, und du fällst langsamer als normal!" +lore2 = "Abklingzeit:" +lore = ["Einfach schleichen zum Absteigen, und du fällst langsamer als normal!", "Abklingzeit:"] [rift.gate] - name = "Risstor" - description = "Teleportiere dich zu einem markierten Ort." - lore1 = "HERSTELLUNG: Smaragd + Amethystsplitter + Enderperle" - lore2 = "Vor Gebrauch lesen!" - lore3 = "5s Verzögerung, " - lore4 = "du kannst während dieser Animation sterben" - lore = ["HERSTELLUNG: Smaragd + Amethystsplitter + Enderperle", "Vor Gebrauch lesen!", "5s Verzögerung, ", "du kannst während dieser Animation sterben"] +name = "Risstor" +description = "Teleportiere dich zu einem markierten Ort." +lore1 = "HERSTELLUNG: Smaragd + Amethystsplitter + Enderperle" +lore2 = "Vor Gebrauch lesen!" +lore3 = "5s Verzögerung, " +lore4 = "du kannst während dieser Animation sterben" +lore = ["HERSTELLUNG: Smaragd + Amethystsplitter + Enderperle", "Vor Gebrauch lesen!", "5s Verzögerung, ", "du kannst während dieser Animation sterben"] [rift.resist] - name = "Riss-Widerstand" - description = "Erhalte Resistenz bei der Verwendung von Ender-Gegenständen & Fähigkeiten" - lore1 = "+ Passiv: Bietet Widerstand, wenn du Riss-Fähigkeiten oder Ender-Gegenstände verwendest" - lore2 = "NICHT die tragbare Endertruhe, nur Dinge, die du verbrauchen kannst" - lore = ["+ Passiv: Bietet Widerstand, wenn du Riss-Fähigkeiten oder Ender-Gegenstände verwendest", "NICHT die tragbare Endertruhe, nur Dinge, die du verbrauchen kannst"] +name = "Riss-Widerstand" +description = "Erhalte Resistenz bei der Verwendung von Ender-Gegenständen & Fähigkeiten" +lore1 = "+ Passiv: Bietet Widerstand, wenn du Riss-Fähigkeiten oder Ender-Gegenstände verwendest" +lore2 = "NICHT die tragbare Endertruhe, nur Dinge, die du verbrauchen kannst" +lore = ["+ Passiv: Bietet Widerstand, wenn du Riss-Fähigkeiten oder Ender-Gegenstände verwendest", "NICHT die tragbare Endertruhe, nur Dinge, die du verbrauchen kannst"] [rift.visage] - name = "Riss-Antlitz" - description = "Verhindert, dass Endermen aggressiv werden, wenn du Enderperlen im Inventar hast." - lore1 = "Endermen werden nicht aggressiv, wenn du Enderperlen in deinem Inventar hast." - lore = ["Endermen werden nicht aggressiv, wenn du Enderperlen in deinem Inventar hast."] +name = "Riss-Antlitz" +description = "Verhindert, dass Endermen aggressiv werden, wenn du Enderperlen im Inventar hast." +lore1 = "Endermen werden nicht aggressiv, wenn du Enderperlen in deinem Inventar hast." +lore = ["Endermen werden nicht aggressiv, wenn du Enderperlen in deinem Inventar hast."] # seaborn [seaborn] [seaborn.oxygen] - name = "Organischer Sauerstofftank" - description = "Halte mehr Sauerstoff in deinen winzigen Lungen!" - lore1 = "Erhöhung der Sauerstoffkapazität" - lore = ["Erhöhung der Sauerstoffkapazität"] +name = "Organischer Sauerstofftank" +description = "Halte mehr Sauerstoff in deinen winzigen Lungen!" +lore1 = "Erhöhung der Sauerstoffkapazität" +lore = ["Erhöhung der Sauerstoffkapazität"] [seaborn.fishers_fantasy] - name = "Fischers Fantasie" - description = "Verdiene mehr EP beim Fischen und fange mehr Fische!" - lore1 = "Für jede Stufe gibt es eine Chance, mehr EP und Fisch zu erhalten!" - lore = ["Für jede Stufe gibt es eine Chance, mehr EP und Fisch zu erhalten!"] +name = "Fischers Fantasie" +description = "Verdiene mehr EP beim Fischen und fange mehr Fische!" +lore1 = "Für jede Stufe gibt es eine Chance, mehr EP und Fisch zu erhalten!" +lore = ["Für jede Stufe gibt es eine Chance, mehr EP und Fisch zu erhalten!"] [seaborn.haste] - name = "Schildkröten-Bergbau" - description = "Erhalte Eile beim Abbauen unter Wasser!" - lore1 = "Eile 3 wird unter Wasser beim Abbauen angewendet (stapelt sich mit Wasseraffinität), nachdem der Wasseratmungseffekt nachlässt!" - lore = ["Eile 3 wird unter Wasser beim Abbauen angewendet (stapelt sich mit Wasseraffinität), nachdem der Wasseratmungseffekt nachlässt!"] +name = "Schildkröten-Bergbau" +description = "Erhalte Eile beim Abbauen unter Wasser!" +lore1 = "Eile 3 wird unter Wasser beim Abbauen angewendet (stapelt sich mit Wasseraffinität), nachdem der Wasseratmungseffekt nachlässt!" +lore = ["Eile 3 wird unter Wasser beim Abbauen angewendet (stapelt sich mit Wasseraffinität), nachdem der Wasseratmungseffekt nachlässt!"] [seaborn.night_vision] - name = "Schildkröten-Sicht" - description = "Erhalte Nachtsicht, während du unter Wasser bist" - lore1 = "Erhalte einfach Nachtsicht unter Wasser, nachdem dein Wasseratmungseffekt nachlässt!" - lore = ["Erhalte einfach Nachtsicht unter Wasser, nachdem dein Wasseratmungseffekt nachlässt!"] +name = "Schildkröten-Sicht" +description = "Erhalte Nachtsicht, während du unter Wasser bist" +lore1 = "Erhalte einfach Nachtsicht unter Wasser, nachdem dein Wasseratmungseffekt nachlässt!" +lore = ["Erhalte einfach Nachtsicht unter Wasser, nachdem dein Wasseratmungseffekt nachlässt!"] [seaborn.dolphin_grace] - name = "Gunst des Delfins" - description = "Schwimme wie ein Delfin, ohne die Delfine" - lore1 = "+ Passiv: erhalte " - lore2 = "x Geschwindigkeit (Gunst des Delfins)" - lore3 = "Präzise deutsche Ingenieurskunst - Moment, das stimmt nicht... Nicht kompatibel mit Tiefenschreiter" - lore = ["+ Passiv: erhalte ", "x Geschwindigkeit (Gunst des Delfins)", "Präzise deutsche Ingenieurskunst - Moment, das stimmt nicht... Nicht kompatibel mit Tiefenschreiter"] +name = "Gunst des Delfins" +description = "Schwimme wie ein Delfin, ohne die Delfine" +lore1 = "+ Passiv: erhalte " +lore2 = "x Geschwindigkeit (Gunst des Delfins)" +lore3 = "Präzise deutsche Ingenieurskunst - Moment, das stimmt nicht... Nicht kompatibel mit Tiefenschreiter" +lore = ["+ Passiv: erhalte ", "x Geschwindigkeit (Gunst des Delfins)", "Präzise deutsche Ingenieurskunst - Moment, das stimmt nicht... Nicht kompatibel mit Tiefenschreiter"] # stealth [stealth] [stealth.ghost_armor] - name = "Geisterrüstung" - description = "Langsam aufbauende Rüstung, wenn kein Schaden erlitten wird, hält 1 Treffer" - lore1 = "Maximale Rüstung" - lore2 = "Geschwindigkeit" - lore = ["Maximale Rüstung", "Geschwindigkeit"] +name = "Geisterrüstung" +description = "Langsam aufbauende Rüstung, wenn kein Schaden erlitten wird, hält 1 Treffer" +lore1 = "Maximale Rüstung" +lore2 = "Geschwindigkeit" +lore = ["Maximale Rüstung", "Geschwindigkeit"] [stealth.night_vision] - name = "Heimliche Sicht" - description = "Erhalte Nachtsicht beim Schleichen" - lore1 = "Erhalte einen Schub von " - lore2 = "Nachtsicht" - lore3 = "beim Schleichen" - lore = ["Erhalte einen Schub von ", "Nachtsicht", "beim Schleichen"] +name = "Heimliche Sicht" +description = "Erhalte Nachtsicht beim Schleichen" +lore1 = "Erhalte einen Schub von " +lore2 = "Nachtsicht" +lore3 = "beim Schleichen" +lore = ["Erhalte einen Schub von ", "Nachtsicht", "beim Schleichen"] [stealth.snatch] - name = "Gegenstand-Schnappen" - description = "Schnappe dir fallengelassene Gegenstände sofort beim Schleichen!" - lore1 = "Schnapp-Radius" - lore = ["Schnapp-Radius"] +name = "Gegenstand-Schnappen" +description = "Schnappe dir fallengelassene Gegenstände sofort beim Schleichen!" +lore1 = "Schnapp-Radius" +lore = ["Schnapp-Radius"] [stealth.speed] - name = "Schleichgeschwindigkeit" - description = "Erhalte Geschwindigkeit beim Schleichen" - lore1 = "Schleichgeschwindigkeit" - lore = ["Schleichgeschwindigkeit"] +name = "Schleichgeschwindigkeit" +description = "Erhalte Geschwindigkeit beim Schleichen" +lore1 = "Schleichgeschwindigkeit" +lore = ["Schleichgeschwindigkeit"] [stealth.ender_veil] - name = "Enderschleier" - description = "Keine Kürbisse mehr nötig, um Enderman-Angriffe zu verhindern" - lore1 = "Verhindere Enderman-Angriffe beim Schleichen" - lore2 = "Verhindere alle Enderman-Angriffe" - lore = ["Verhindere Enderman-Angriffe beim Schleichen", "Verhindere alle Enderman-Angriffe"] +name = "Enderschleier" +description = "Keine Kürbisse mehr nötig, um Enderman-Angriffe zu verhindern" +lore1 = "Verhindere Enderman-Angriffe beim Schleichen" +lore2 = "Verhindere alle Enderman-Angriffe" +lore = ["Verhindere Enderman-Angriffe beim Schleichen", "Verhindere alle Enderman-Angriffe"] # sword [sword] [sword.machete] - name = "Machete" - description = "Schneide mit Leichtigkeit durchs Laub!" - lore1 = "Hieb-Radius" - lore2 = "Hieb-Abklingzeit" - lore3 = "Werkzeugverschleiß" - lore = ["Hieb-Radius", "Hieb-Abklingzeit", "Werkzeugverschleiß"] +name = "Machete" +description = "Schneide mit Leichtigkeit durchs Laub!" +lore1 = "Hieb-Radius" +lore2 = "Hieb-Abklingzeit" +lore3 = "Werkzeugverschleiß" +lore = ["Hieb-Radius", "Hieb-Abklingzeit", "Werkzeugverschleiß"] [sword.bloody_blade] - name = "Blutige Klinge" - description = "Schwertschläge verursachen Blutung!" - lore1 = "Das Treffen eines Lebewesens mit deinem Schwert verursacht Blutung" - lore2 = "Blutungsdauer" - lore3 = "Blutungs-Abklingzeit" - lore = ["Das Treffen eines Lebewesens mit deinem Schwert verursacht Blutung", "Blutungsdauer", "Blutungs-Abklingzeit"] +name = "Blutige Klinge" +description = "Schwertschläge verursachen Blutung!" +lore1 = "Das Treffen eines Lebewesens mit deinem Schwert verursacht Blutung" +lore2 = "Blutungsdauer" +lore3 = "Blutungs-Abklingzeit" +lore = ["Das Treffen eines Lebewesens mit deinem Schwert verursacht Blutung", "Blutungsdauer", "Blutungs-Abklingzeit"] [sword.poisoned_blade] - name = "Vergiftete Klinge" - description = "Schwertschläge verursachen Vergiftung!" - lore1 = "Das Treffen eines Lebewesens mit deinem Schwert verursacht Vergiftung" - lore2 = "Vergiftungsdauer" - lore3 = "Vergiftungs-Abklingzeit" - lore = ["Das Treffen eines Lebewesens mit deinem Schwert verursacht Vergiftung", "Vergiftungsdauer", "Vergiftungs-Abklingzeit"] +name = "Vergiftete Klinge" +description = "Schwertschläge verursachen Vergiftung!" +lore1 = "Das Treffen eines Lebewesens mit deinem Schwert verursacht Vergiftung" +lore2 = "Vergiftungsdauer" +lore3 = "Vergiftungs-Abklingzeit" +lore = ["Das Treffen eines Lebewesens mit deinem Schwert verursacht Vergiftung", "Vergiftungsdauer", "Vergiftungs-Abklingzeit"] # taming [taming] [taming.damage] - name = "Zähm-Schaden" - description = "Erhöhe den Schaden deiner gezähmten Tiere." - lore1 = "Erhöhter Schaden" - lore = ["Erhöhter Schaden"] +name = "Zähm-Schaden" +description = "Erhöhe den Schaden deiner gezähmten Tiere." +lore1 = "Erhöhter Schaden" +lore = ["Erhöhter Schaden"] [taming.health] - name = "Zähm-Gesundheit" - description = "Erhöhe die Gesundheit deiner gezähmten Tiere." - lore1 = "Erhöhte Gesundheit" - lore = ["Erhöhte Gesundheit"] +name = "Zähm-Gesundheit" +description = "Erhöhe die Gesundheit deiner gezähmten Tiere." +lore1 = "Erhöhte Gesundheit" +lore = ["Erhöhte Gesundheit"] [taming.regeneration] - name = "Zähm-Regeneration" - description = "Erhöhe die Regeneration deiner gezähmten Tiere." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Zähm-Regeneration" +description = "Erhöhe die Regeneration deiner gezähmten Tiere." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Dornen" - description = "Reflektiere Schaden zurück an deinen Angreifer!" - lore1 = "Vergoltener Schaden bei Treffern" - lore = ["Vergoltener Schaden bei Treffern"] +name = "Dornen" +description = "Reflektiere Schaden zurück an deinen Angreifer!" +lore1 = "Vergoltener Schaden bei Treffern" +lore = ["Vergoltener Schaden bei Treffern"] [tragoul.globe] - name = "Globus des Schmerzes" - description = "Verteile den verursachten Schaden basierend auf der Anzahl der Feinde um dich herum!" - lore1 = "Je mehr Feinde um dich herum, desto weniger Schaden fügst du jedem einzelnen zu" - lore2 = "Reichweite: " - lore3 = "Zusätzlicher Schaden an alle Wesen: " - lore = ["Je mehr Feinde um dich herum, desto weniger Schaden fügst du jedem einzelnen zu", "Reichweite: ", "Zusätzlicher Schaden an alle Wesen: "] +name = "Globus des Schmerzes" +description = "Verteile den verursachten Schaden basierend auf der Anzahl der Feinde um dich herum!" +lore1 = "Je mehr Feinde um dich herum, desto weniger Schaden fügst du jedem einzelnen zu" +lore2 = "Reichweite: " +lore3 = "Zusätzlicher Schaden an alle Wesen: " +lore = ["Je mehr Feinde um dich herum, desto weniger Schaden fügst du jedem einzelnen zu", "Reichweite: ", "Zusätzlicher Schaden an alle Wesen: "] [tragoul.healing] - name = "Wille des Schmerzes" - description = "Gewinne Gesundheit basierend auf dem verursachten Schaden!" - lore1 = "Dinge zu verletzen hat sich noch nie so gut angefühlt! Heile durch verursachten Schaden" - lore2 = "Es gibt ein 3-Sekunden-Schadensfenster für Heilung und 1 Sekunde Abklingzeit " - lore3 = "Heilung pro Schadensprozentsatz: " - lore = ["Dinge zu verletzen hat sich noch nie so gut angefühlt! Heile durch verursachten Schaden", "Es gibt ein 3-Sekunden-Schadensfenster für Heilung und 1 Sekunde Abklingzeit ", "Heilung pro Schadensprozentsatz: "] +name = "Wille des Schmerzes" +description = "Gewinne Gesundheit basierend auf dem verursachten Schaden!" +lore1 = "Dinge zu verletzen hat sich noch nie so gut angefühlt! Heile durch verursachten Schaden" +lore2 = "Es gibt ein 3-Sekunden-Schadensfenster für Heilung und 1 Sekunde Abklingzeit " +lore3 = "Heilung pro Schadensprozentsatz: " +lore = ["Dinge zu verletzen hat sich noch nie so gut angefühlt! Heile durch verursachten Schaden", "Es gibt ein 3-Sekunden-Schadensfenster für Heilung und 1 Sekunde Abklingzeit ", "Heilung pro Schadensprozentsatz: "] [tragoul.lance] - name = "Leichenlanzen" - description = "Einen Feind zu töten oder eine Fähigkeit, die einen Feind tötet, erzeugt eine Lanze, die einem nahegelegenen Feind Schaden zufügt!" - lore1 = "Lanzen zielen auf alles, was du tötest, UND wenn diese Fähigkeit einen Feind tötet." - lore2 = "Opfere einen Teil deines Lebens, um die Lanzen zu erschaffen (das kann dich töten)" - lore3 = "Max. Lanzen: 1 + " - lore = ["Lanzen zielen auf alles, was du tötest, UND wenn diese Fähigkeit einen Feind tötet.", "Opfere einen Teil deines Lebens, um die Lanzen zu erschaffen (das kann dich töten)", "Max. Lanzen: 1 + "] +name = "Leichenlanzen" +description = "Einen Feind zu töten oder eine Fähigkeit, die einen Feind tötet, erzeugt eine Lanze, die einem nahegelegenen Feind Schaden zufügt!" +lore1 = "Lanzen zielen auf alles, was du tötest, UND wenn diese Fähigkeit einen Feind tötet." +lore2 = "Opfere einen Teil deines Lebens, um die Lanzen zu erschaffen (das kann dich töten)" +lore3 = "Max. Lanzen: 1 + " +lore = ["Lanzen zielen auf alles, was du tötest, UND wenn diese Fähigkeit einen Feind tötet.", "Opfere einen Teil deines Lebens, um die Lanzen zu erschaffen (das kann dich töten)", "Max. Lanzen: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Glaskanone" - description = "Bonus auf unbewaffneten Schaden, je niedriger dein Rüstungswert" - lore1 = "x Schaden bei 0 Rüstung" - lore2 = "Bonus-Schaden pro Stufe" - lore = ["x Schaden bei 0 Rüstung", "Bonus-Schaden pro Stufe"] +name = "Glaskanone" +description = "Bonus auf unbewaffneten Schaden, je niedriger dein Rüstungswert" +lore1 = "x Schaden bei 0 Rüstung" +lore2 = "Bonus-Schaden pro Stufe" +lore = ["x Schaden bei 0 Rüstung", "Bonus-Schaden pro Stufe"] [unarmed.power] - name = "Unbewaffnete Stärke" - description = "Verbesserter unbewaffneter Schaden" - lore1 = "Schaden" - lore = ["Schaden"] +name = "Unbewaffnete Stärke" +description = "Verbesserter unbewaffneter Schaden" +lore1 = "Schaden" +lore = ["Schaden"] [unarmed.sucker_punch] - name = "Fausthieb" - description = "Sprint-Schläge, aber tödlicher." - lore1 = "Schaden" - lore2 = "Schaden erhöht sich mit deiner Geschwindigkeit beim Schlagen" - lore = ["Schaden", "Schaden erhöht sich mit deiner Geschwindigkeit beim Schlagen"] +name = "Fausthieb" +description = "Sprint-Schläge, aber tödlicher." +lore1 = "Schaden" +lore2 = "Schaden erhöht sich mit deiner Geschwindigkeit beim Schlagen" +lore = ["Schaden", "Schaden erhöht sich mit deiner Geschwindigkeit beim Schlagen"] diff --git a/src/main/resources/en_US.toml b/src/main/resources/en_US.toml index 8fb928ad9..e72ff824b 100644 --- a/src/main/resources/en_US.toml +++ b/src/main/resources/en_US.toml @@ -1,2604 +1,2616 @@ +# Color Codes: +# - Legacy format: &0..&f, &k..&o, &r (example: "&7Gray") +# - Hex format: &#RRGGBB or &x&R&R&G&G&B&B (example: "7FFAAMint") +# - MiniMessage also works (example: "<#55FFAA>Mint") +# # advancement [advancement] [advancement.challenge_move_1k] - title = "Gotta Move!" - description = "Walk over 1 Kilometer (1,000 blocks)" +title = "Gotta Move!" +description = "Walk over 1 Kilometer (1,000 blocks)" [advancement.challenge_sprint_5k] - title = "Sprint a 5K!" - description = "Walk over 5 Kilometers (5,000 blocks)" +title = "Sprint a 5K!" +description = "Walk over 5 Kilometers (5,000 blocks)" [advancement.challenge_sprint_50k] - title = "Zoom a 50K!" - description = "Walk over 50 Kilometers (50,000 blocks)" +title = "Zoom a 50K!" +description = "Walk over 50 Kilometers (50,000 blocks)" [advancement.challenge_sprint_500k] - title = "Traverse the Universe!!" - description = "Walk over 500 Kilometers (500,000 blocks)" +title = "Traverse the Universe!" +description = "Walk over 500 Kilometers (500,000 blocks)" [advancement.challenge_sprint_marathon] - title = "Sprint a (literal) Marathon!" - description = "Sprint over 42,195 Blocks!" +title = "Sprint a (literal) Marathon!" +description = "Sprint over 42,195 Blocks!" [advancement.challenge_place_1k] - title = "Foundation Layer" - description = "Place 1,000 blocks" +title = "Foundation Layer" +description = "Place 1,000 blocks" [advancement.challenge_place_5k] - title = "Rising Walls" - description = "Place 5,000 blocks" +title = "Rising Walls" +description = "Place 5,000 blocks" [advancement.challenge_place_50k] - title = "City Planner" - description = "Place 50,000 blocks" +title = "City Planner" +description = "Place 50,000 blocks" [advancement.challenge_place_500k] - title = "World Shaper" - description = "Place 500,000 blocks" +title = "World Shaper" +description = "Place 500,000 blocks" [advancement.challenge_place_5m] - title = "Symmetry's Acolyte" - description = "Place 5,000,000 blocks" +title = "Symmetry's Acolyte" +description = "Place 5,000,000 blocks" [advancement.challenge_chop_1k] - title = "Splinter Collector" - description = "Chop 1,000 blocks" +title = "Splinter Collector" +description = "Chop 1,000 blocks" [advancement.challenge_chop_5k] - title = "Timber!" - description = "Chop 5,000 blocks" +title = "Timber!" +description = "Chop 5,000 blocks" [advancement.challenge_chop_50k] - title = "Deforester" - description = "Chop 50,000 blocks" +title = "Deforester" +description = "Chop 50,000 blocks" [advancement.challenge_chop_500k] - title = "Paul Bunyan" - description = "Chop 500,000 blocks" +title = "Paul Bunyan" +description = "Chop 500,000 blocks" [advancement.challenge_chop_5m] - title = "Jackson the Dog" - description = "The bestest good Boy! (5 Million blocks)" +title = "Jackson the Dog" +description = "The bestest good Boy! (5 Million blocks)" [advancement.challenge_block_1k] - title = "Shield Bearer" - description = "Block 1,000 hits" +title = "Shield Bearer" +description = "Block 1,000 hits" [advancement.challenge_block_5k] - title = "Iron Wall" - description = "Block 5,000 hits" +title = "Iron Wall" +description = "Block 5,000 hits" [advancement.challenge_block_50k] - title = "Immovable Object" - description = "Block 50,000 hits" +title = "Immovable Object" +description = "Block 50,000 hits" [advancement.challenge_block_500k] - title = "Living Fortress" - description = "Block 500,000 hits" +title = "Living Fortress" +description = "Block 500,000 hits" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Block 5,000,000 hits" +title = "Die Hand Die Verletzt" +description = "Block 5,000,000 hits" [advancement.challenge_brew_1k] - title = "Bottoms Up" - description = "Consume 1,000 potions" +title = "Bottoms Up" +description = "Consume 1,000 potions" [advancement.challenge_brew_5k] - title = "Potion Addict" - description = "Consume 5,000 potions" +title = "Potion Addict" +description = "Consume 5,000 potions" [advancement.challenge_brew_50k] - title = "Elixir Connoisseur" - description = "Consume 50,000 potions" +title = "Elixir Connoisseur" +description = "Consume 50,000 potions" [advancement.challenge_brew_500k] - title = "Liquid Courage" - description = "Consume 500,000 potions" +title = "Liquid Courage" +description = "Consume 500,000 potions" [advancement.challenge_brew_5m] - title = "The Alchemist" - description = "Consume 5,000,000 potions" +title = "The Alchemist" +description = "Consume 5,000,000 potions" [advancement.challenge_brewsplash_1k] - title = "Glass Cannon" - description = "Splash 1,000 potions" +title = "Glass Cannon" +description = "Splash 1,000 potions" [advancement.challenge_brewsplash_5k] - title = "Area Denial" - description = "Splash 5,000 potions" +title = "Area Denial" +description = "Splash 5,000 potions" [advancement.challenge_brewsplash_50k] - title = "Chemical Warfare" - description = "Splash 50,000 potions" +title = "Chemical Warfare" +description = "Splash 50,000 potions" [advancement.challenge_brewsplash_500k] - title = "Rain of Reagents" - description = "Splash 500,000 potions" +title = "Rain of Reagents" +description = "Splash 500,000 potions" [advancement.challenge_brewsplash_5m] - title = "The Splash Meister" - description = "Splash 5,000,000 potions" +title = "The Splash Meister" +description = "Splash 5,000,000 potions" [advancement.challenge_craft_1k] - title = "Crafty Crafter!" - description = "Craft 1000 Items" +title = "Crafty Crafter!" +description = "Craft 1000 Items" [advancement.challenge_craft_5k] - title = "Cantankerous Crafter!" - description = "Craft 5000 Items" +title = "Cantankerous Crafter!" +description = "Craft 5000 Items" [advancement.challenge_craft_50k] - title = "Servile Crafter!" - description = "Craft 50,000 Items" +title = "Servile Crafter!" +description = "Craft 50,000 Items" [advancement.challenge_craft_500k] - title = "Cacophonous Crafter!" - description = "Craft 500,000 Items" +title = "Cacophonous Crafter!" +description = "Craft 500,000 Items" [advancement.challenge_craft_5m] - title = "Calamitous McCraftface" - description = "Craft 5,000,000 Items" +title = "Calamitous McCraftface" +description = "Craft 5,000,000 Items" [advancement.challenge_enchant_1k] - title = "Arcane Dabbler" - description = "Enchant 1,000 items" +title = "Arcane Dabbler" +description = "Enchant 1,000 items" [advancement.challenge_enchant_5k] - title = "Runeweaver" - description = "Enchant 5,000 items" +title = "Runeweaver" +description = "Enchant 5,000 items" [advancement.challenge_enchant_50k] - title = "Mystic Artisan" - description = "Enchant 50,000 items" +title = "Mystic Artisan" +description = "Enchant 50,000 items" [advancement.challenge_enchant_500k] - title = "Archmage" - description = "Enchant 500,000 items" +title = "Archmage" +description = "Enchant 500,000 items" [advancement.challenge_enchant_5m] - title = "Enigmatic Enchanter" - description = "Enchant 5,000,000 items" +title = "Enigmatic Enchanter" +description = "Enchant 5,000,000 items" [advancement.challenge_excavate_1k] - title = "Dirt Mover" - description = "Excavate 1,000 blocks" +title = "Dirt Mover" +description = "Excavate 1,000 blocks" [advancement.challenge_excavate_5k] - title = "Trench Digger" - description = "Excavate 5,000 blocks" +title = "Trench Digger" +description = "Excavate 5,000 blocks" [advancement.challenge_excavate_50k] - title = "Canyon Carver" - description = "Excavate 50,000 blocks" +title = "Canyon Carver" +description = "Excavate 50,000 blocks" [advancement.challenge_excavate_500k] - title = "Terrain Sculptor" - description = "Excavate 500,000 blocks" +title = "Terrain Sculptor" +description = "Excavate 500,000 blocks" [advancement.challenge_excavate_5m] - title = "Geological Anomaly" - description = "Excavate 5,000,000 blocks" +title = "Geological Anomaly" +description = "Excavate 5,000,000 blocks" [advancement.horrible_person] - title = "You're a Horrible Person" - description = "Unfathomable, really" +title = "You're a Horrible Person" +description = "Unfathomable, really" [advancement.challenge_turtle_egg_smasher] - title = "Turtle Egg Smasher!" - description = "Break 100 turtle eggs" +title = "Turtle Egg Smasher!" +description = "Break 100 turtle eggs" [advancement.challenge_turtle_egg_annihilator] - title = "Turtle Egg Annihilator!" - description = "Break 500 turtle eggs" +title = "Turtle Egg Annihilator!" +description = "Break 500 turtle eggs" [advancement.challenge_novice_hunter] - title = "Predator's Instinct" - description = "Kill 100 entities" +title = "Predator's Instinct" +description = "Kill 100 entities" [advancement.challenge_intermediate_hunter] - title = "Apex Predator" - description = "Kill 500 entities" +title = "Apex Predator" +description = "Kill 500 entities" [advancement.challenge_advanced_hunter] - title = "Extinction Event" - description = "Kill 5,000 entities" +title = "Extinction Event" +description = "Kill 5,000 entities" [advancement.challenge_creeper_conqueror] - title = "Creeper Conqueror!" - description = "Kill 50 creepers" +title = "Creeper Conqueror!" +description = "Kill 50 creepers" [advancement.challenge_creeper_annihilator] - title = "Creeper Annihilator!" - description = "Kill 200 creepers" +title = "Creeper Annihilator!" +description = "Kill 200 creepers" [advancement.challenge_pickaxe_1k] - title = "Rock Breaker" - description = "Break 1,000 blocks with a pickaxe" +title = "Rock Breaker" +description = "Break 1,000 blocks with a pickaxe" [advancement.challenge_pickaxe_5k] - title = "Tunnel Rat" - description = "Break 5,000 blocks with a pickaxe" +title = "Tunnel Rat" +description = "Break 5,000 blocks with a pickaxe" [advancement.challenge_pickaxe_50k] - title = "Dwarven Heritage" - description = "Break 50,000 blocks with a pickaxe" +title = "Dwarven Heritage" +description = "Break 50,000 blocks with a pickaxe" [advancement.challenge_pickaxe_500k] - title = "Core Breacher" - description = "Break 500,000 blocks with a pickaxe" +title = "Core Breacher" +description = "Break 500,000 blocks with a pickaxe" [advancement.challenge_pickaxe_5m] - title = "World Eater" - description = "Break 5,000,000 blocks with a pickaxe" +title = "World Eater" +description = "Break 5,000,000 blocks with a pickaxe" [advancement.challenge_eat_100] - title = "So much to eat!" - description = "Eat over 100 Items!" +title = "So much to eat!" +description = "Eat over 100 Items!" [advancement.challenge_eat_1000] - title = "Unquenchable Hunger!" - description = "Eat over 1,000 Items!" +title = "Unquenchable Hunger!" +description = "Eat over 1,000 Items!" [advancement.challenge_eat_10000] - title = "EVERLASTING HUNGER!" - description = "Eat over 10,000 Items!" +title = "EVERLASTING HUNGER!" +description = "Eat over 10,000 Items!" [advancement.challenge_harvest_100] - title = "Full Harvest" - description = "Harvest over 100 crops!" +title = "Full Harvest" +description = "Harvest over 100 crops!" [advancement.challenge_harvest_1000] - title = "Grand Harvest" - description = "Harvest over 1,000 crops!" +title = "Grand Harvest" +description = "Harvest over 1,000 crops!" [advancement.challenge_swim_1nm] - title = "Human Submarine" - description = "Swim 1 Nautical Mile (1,852 blocks)" +title = "Human Submarine" +description = "Swim 1 Nautical Mile (1,852 blocks)" [advancement.challenge_swim_5k] - title = "Deep Diver" - description = "Swim over 5,000 blocks" +title = "Deep Diver" +description = "Swim over 5,000 blocks" [advancement.challenge_swim_20k] - title = "Poseidon's Chosen" - description = "Swim over 20,000 blocks" +title = "Poseidon's Chosen" +description = "Swim over 20,000 blocks" [advancement.challenge_sneak_1k] - title = "Knee Pain" - description = "Sneak over a kilometer (1,000 blocks)" +title = "Knee Pain" +description = "Sneak over a kilometer (1,000 blocks)" [advancement.challenge_sneak_5k] - title = "Shadow Walker" - description = "Sneak over 5,000 blocks" +title = "Shadow Walker" +description = "Sneak over 5,000 blocks" [advancement.challenge_sneak_20k] - title = "Ghost" - description = "Sneak over 20,000 blocks" +title = "Ghost" +description = "Sneak over 20,000 blocks" [advancement.challenge_sword_100] - title = "First Blood" - description = "Land 100 hits with a sword" +title = "First Blood" +description = "Land 100 hits with a sword" [advancement.challenge_sword_1k] - title = "Blade Dancer" - description = "Land 1,000 hits with a sword" +title = "Blade Dancer" +description = "Land 1,000 hits with a sword" [advancement.challenge_sword_10k] - title = "Thousand Cuts" - description = "Land 10,000 hits with a sword" +title = "Thousand Cuts" +description = "Land 10,000 hits with a sword" [advancement.challenge_unarmed_100] - title = "Bar Brawler" - description = "Land 100 unarmed hits" +title = "Bar Brawler" +description = "Land 100 unarmed hits" [advancement.challenge_unarmed_1k] - title = "Iron Fists" - description = "Land 1,000 unarmed hits" +title = "Iron Fists" +description = "Land 1,000 unarmed hits" [advancement.challenge_unarmed_10k] - title = "One Punch" - description = "Land 10,000 unarmed hits" +title = "One Punch" +description = "Land 10,000 unarmed hits" [advancement.challenge_trag_1k] - title = "Blood Price" - description = "Receive 1,000 damage" +title = "Blood Price" +description = "Receive 1,000 damage" [advancement.challenge_trag_10k] - title = "Crimson Tide" - description = "Receive 10,000 damage" +title = "Crimson Tide" +description = "Receive 10,000 damage" [advancement.challenge_trag_100k] - title = "Avatar of Suffering" - description = "Receive 100,000 damage" +title = "Avatar of Suffering" +description = "Receive 100,000 damage" [advancement.challenge_ranged_100] - title = "Target Practice" - description = "Fire 100 projectiles" +title = "Target Practice" +description = "Fire 100 projectiles" [advancement.challenge_ranged_1k] - title = "Hawkeye" - description = "Fire 1,000 projectiles" +title = "Hawkeye" +description = "Fire 1,000 projectiles" [advancement.challenge_ranged_10k] - title = "Storm of Arrows" - description = "Fire 10,000 projectiles" +title = "Storm of Arrows" +description = "Fire 10,000 projectiles" [advancement.challenge_chronos_1h] - title = "Tick Tock" - description = "Spend 1 hour online" +title = "Tick Tock" +description = "Spend 1 hour online" [advancement.challenge_chronos_24h] - title = "Sands of Time" - description = "Spend 24 hours online" +title = "Sands of Time" +description = "Spend 24 hours online" [advancement.challenge_chronos_168h] - title = "Timeless" - description = "Spend 168 hours (1 week) online" +title = "Timeless" +description = "Spend 168 hours (1 week) online" [advancement.challenge_nether_50] - title = "Hell's Gatekeeper" - description = "Slay 50 nether creatures" +title = "Hell's Gatekeeper" +description = "Slay 50 nether creatures" [advancement.challenge_nether_500] - title = "Abyssal Warden" - description = "Slay 500 nether creatures" +title = "Abyssal Warden" +description = "Slay 500 nether creatures" [advancement.challenge_nether_5k] - title = "Lord of the Nether" - description = "Slay 5,000 nether creatures" +title = "Lord of the Nether" +description = "Slay 5,000 nether creatures" [advancement.challenge_rift_50] - title = "Spatial Anomaly" - description = "Teleport 50 times" +title = "Spatial Anomaly" +description = "Teleport 50 times" [advancement.challenge_rift_500] - title = "Void Walker" - description = "Teleport 500 times" +title = "Void Walker" +description = "Teleport 500 times" [advancement.challenge_rift_5k] - title = "Between Worlds" - description = "Teleport 5,000 times" +title = "Between Worlds" +description = "Teleport 5,000 times" [advancement.challenge_taming_10] - title = "Animal Whisperer" - description = "Breed 10 animals" +title = "Animal Whisperer" +description = "Breed 10 animals" [advancement.challenge_taming_50] - title = "Pack Leader" - description = "Breed 50 animals" +title = "Pack Leader" +description = "Breed 50 animals" [advancement.challenge_taming_500] - title = "Beastmaster" - description = "Breed 500 animals" +title = "Beastmaster" +description = "Breed 500 animals" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Speed Demon" - description = "Sprint over 5 Kilometers (5,000 blocks)" +title = "Speed Demon" +description = "Sprint over 5 Kilometers (5,000 blocks)" [advancement.challenge_sprint_dist_50k] - title = "Lightning Legs" - description = "Sprint over 50 Kilometers (50,000 blocks)" +title = "Lightning Legs" +description = "Sprint over 50 Kilometers (50,000 blocks)" [advancement.challenge_agility_swim_1k] - title = "Water Strider" - description = "Swim over 1 Kilometer (1,000 blocks)" +title = "Water Strider" +description = "Swim over 1 Kilometer (1,000 blocks)" [advancement.challenge_agility_swim_10k] - title = "Aquatic Voyager" - description = "Swim over 10 Kilometers (10,000 blocks)" +title = "Aquatic Voyager" +description = "Swim over 10 Kilometers (10,000 blocks)" [advancement.challenge_fly_1k] - title = "Sky Dancer" - description = "Fly over 1 Kilometer (1,000 blocks)" +title = "Sky Dancer" +description = "Fly over 1 Kilometer (1,000 blocks)" [advancement.challenge_fly_10k] - title = "Wind Rider" - description = "Fly over 10 Kilometers (10,000 blocks)" +title = "Wind Rider" +description = "Fly over 10 Kilometers (10,000 blocks)" [advancement.challenge_agility_sneak_500] - title = "Quiet Steps" - description = "Sneak over 500 blocks" +title = "Quiet Steps" +description = "Sneak over 500 blocks" [advancement.challenge_agility_sneak_5k] - title = "Phantom Steps" - description = "Sneak over 5 Kilometers (5,000 blocks)" +title = "Phantom Steps" +description = "Sneak over 5 Kilometers (5,000 blocks)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Demolition Crew" - description = "Break 500 blocks" +title = "Demolition Crew" +description = "Break 500 blocks" [advancement.challenge_demolish_5k] - title = "Wrecking Ball" - description = "Break 5,000 blocks" +title = "Wrecking Ball" +description = "Break 5,000 blocks" [advancement.challenge_value_placed_10k] - title = "Valuable Builder" - description = "Place blocks worth 10,000 value" +title = "Valuable Builder" +description = "Place blocks worth 10,000 value" [advancement.challenge_value_placed_100k] - title = "Master Architect" - description = "Place blocks worth 100,000 value" +title = "Master Architect" +description = "Place blocks worth 100,000 value" [advancement.challenge_demolish_val_5k] - title = "Salvage Expert" - description = "Salvage 5,000 block value from demolition" +title = "Salvage Expert" +description = "Salvage 5,000 block value from demolition" [advancement.challenge_demolish_val_50k] - title = "Total Deconstruction" - description = "Salvage 50,000 block value from demolition" +title = "Total Deconstruction" +description = "Salvage 50,000 block value from demolition" [advancement.challenge_high_build_100] - title = "Sky Builder" - description = "Place 100 blocks above Y=128" +title = "Sky Builder" +description = "Place 100 blocks above Y=128" [advancement.challenge_high_build_1k] - title = "Cloud Architect" - description = "Place 1,000 blocks above Y=128" +title = "Cloud Architect" +description = "Place 1,000 blocks above Y=128" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Axe Swinger" - description = "Swing your axe 500 times" +title = "Axe Swinger" +description = "Swing your axe 500 times" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Swing your axe 5,000 times" +title = "Berserker" +description = "Swing your axe 5,000 times" [advancement.challenge_axe_damage_1k] - title = "Cleaver" - description = "Deal 1,000 damage with axes" +title = "Cleaver" +description = "Deal 1,000 damage with axes" [advancement.challenge_axe_damage_10k] - title = "Executioner's Axe" - description = "Deal 10,000 damage with axes" +title = "Executioner's Axe" +description = "Deal 10,000 damage with axes" [advancement.challenge_axe_value_5k] - title = "Timber Merchant" - description = "Harvest 5,000 value worth of wood" +title = "Timber Merchant" +description = "Harvest 5,000 value worth of wood" [advancement.challenge_axe_value_50k] - title = "Logging Baron" - description = "Harvest 50,000 value worth of wood" +title = "Logging Baron" +description = "Harvest 50,000 value worth of wood" [advancement.challenge_leaves_500] - title = "Leaf Blower" - description = "Clear 500 leaf blocks with an axe" +title = "Leaf Blower" +description = "Clear 500 leaf blocks with an axe" [advancement.challenge_leaves_5k] - title = "Defoliator" - description = "Clear 5,000 leaf blocks with an axe" +title = "Defoliator" +description = "Clear 5,000 leaf blocks with an axe" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Damage Absorber" - description = "Block 1,000 damage with a shield" +title = "Damage Absorber" +description = "Block 1,000 damage with a shield" [advancement.challenge_block_dmg_10k] - title = "Human Shield" - description = "Block 10,000 damage with a shield" +title = "Human Shield" +description = "Block 10,000 damage with a shield" [advancement.challenge_block_proj_100] - title = "Arrow Deflector" - description = "Block 100 projectiles with a shield" +title = "Arrow Deflector" +description = "Block 100 projectiles with a shield" [advancement.challenge_block_proj_1k] - title = "Projectile Shield" - description = "Block 1,000 projectiles with a shield" +title = "Projectile Shield" +description = "Block 1,000 projectiles with a shield" [advancement.challenge_block_melee_500] - title = "Parry Master" - description = "Block 500 melee attacks with a shield" +title = "Parry Master" +description = "Block 500 melee attacks with a shield" [advancement.challenge_block_melee_5k] - title = "Iron Fortress" - description = "Block 5,000 melee attacks with a shield" +title = "Iron Fortress" +description = "Block 5,000 melee attacks with a shield" [advancement.challenge_block_heavy_50] - title = "Tank" - description = "Block 50 heavy attacks (over 5 damage)" +title = "Tank" +description = "Block 50 heavy attacks (over 5 damage)" [advancement.challenge_block_heavy_500] - title = "Immovable Object" - description = "Block 500 heavy attacks (over 5 damage)" +title = "Immovable Object" +description = "Block 500 heavy attacks (over 5 damage)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "Brewer's Setup" - description = "Place 10 brewing stands" +title = "Brewer's Setup" +description = "Place 10 brewing stands" [advancement.challenge_brew_stands_50] - title = "Potion Factory" - description = "Place 50 brewing stands" +title = "Potion Factory" +description = "Place 50 brewing stands" [advancement.challenge_brew_strong_25] - title = "Strong Brew" - description = "Consume 25 upgraded potions" +title = "Strong Brew" +description = "Consume 25 upgraded potions" [advancement.challenge_brew_strong_250] - title = "Maximum Potency" - description = "Consume 250 upgraded potions" +title = "Maximum Potency" +description = "Consume 250 upgraded potions" [advancement.challenge_brew_splash_hits_50] - title = "Splash Zone" - description = "Hit 50 entities with splash potions" +title = "Splash Zone" +description = "Hit 50 entities with splash potions" [advancement.challenge_brew_splash_hits_500] - title = "Plague Doctor" - description = "Hit 500 entities with splash potions" +title = "Plague Doctor" +description = "Hit 500 entities with splash potions" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Restless" - description = "Travel 1 Kilometer while active" +title = "Restless" +description = "Travel 1 Kilometer while active" [advancement.challenge_active_dist_10k] - title = "Pathfinder" - description = "Travel 10 Kilometers while active" +title = "Pathfinder" +description = "Travel 10 Kilometers while active" [advancement.challenge_active_dist_100k] - title = "Perpetual Motion" - description = "Travel 100 Kilometers while active" +title = "Perpetual Motion" +description = "Travel 100 Kilometers while active" [advancement.challenge_beds_10] - title = "Early Riser" - description = "Sleep in a bed 10 times" +title = "Early Riser" +description = "Sleep in a bed 10 times" [advancement.challenge_beds_100] - title = "Time Skipper" - description = "Sleep in a bed 100 times" +title = "Time Skipper" +description = "Sleep in a bed 100 times" [advancement.challenge_chronos_tp_50] - title = "Temporal Shift" - description = "Teleport 50 times" +title = "Temporal Shift" +description = "Teleport 50 times" [advancement.challenge_chronos_tp_500] - title = "Time Warp" - description = "Teleport 500 times" +title = "Time Warp" +description = "Teleport 500 times" [advancement.challenge_chronos_deaths_10] - title = "Mortal" - description = "Die 10 times" +title = "Mortal" +description = "Die 10 times" [advancement.challenge_chronos_deaths_100] - title = "Death Defier" - description = "Die 100 times" +title = "Death Defier" +description = "Die 100 times" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Craft Worth" - description = "Craft items worth 10,000 total value" +title = "Craft Worth" +description = "Craft items worth 10,000 total value" [advancement.challenge_craft_value_100k] - title = "Artisan" - description = "Craft items worth 100,000 total value" +title = "Artisan" +description = "Craft items worth 100,000 total value" [advancement.challenge_craft_tools_25] - title = "Toolsmith" - description = "Craft 25 tools" +title = "Toolsmith" +description = "Craft 25 tools" [advancement.challenge_craft_tools_250] - title = "Master Forger" - description = "Craft 250 tools" +title = "Master Forger" +description = "Craft 250 tools" [advancement.challenge_craft_armor_25] - title = "Armorsmith" - description = "Craft 25 pieces of armor" +title = "Armorsmith" +description = "Craft 25 pieces of armor" [advancement.challenge_craft_armor_250] - title = "Master Armorer" - description = "Craft 250 pieces of armor" +title = "Master Armorer" +description = "Craft 250 pieces of armor" [advancement.challenge_craft_mass_25k] - title = "Mass Producer" - description = "Craft 25,000 items" +title = "Mass Producer" +description = "Craft 25,000 items" [advancement.challenge_craft_mass_250k] - title = "Industrial Revolution" - description = "Craft 250,000 items" +title = "Industrial Revolution" +description = "Craft 250,000 items" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Collector" - description = "Discover 50 unique items" +title = "Collector" +description = "Discover 50 unique items" [advancement.challenge_discover_items_250] - title = "Cataloger" - description = "Discover 250 unique items" +title = "Cataloger" +description = "Discover 250 unique items" [advancement.challenge_discover_blocks_50] - title = "Surveyor" - description = "Discover 50 unique blocks" +title = "Surveyor" +description = "Discover 50 unique blocks" [advancement.challenge_discover_blocks_250] - title = "Geologist" - description = "Discover 250 unique blocks" +title = "Geologist" +description = "Discover 250 unique blocks" [advancement.challenge_discover_mobs_25] - title = "Observer" - description = "Discover 25 unique mobs" +title = "Observer" +description = "Discover 25 unique mobs" [advancement.challenge_discover_mobs_75] - title = "Naturalist" - description = "Discover 75 unique mobs" +title = "Naturalist" +description = "Discover 75 unique mobs" [advancement.challenge_discover_biomes_10] - title = "Wanderer" - description = "Discover 10 unique biomes" +title = "Wanderer" +description = "Discover 10 unique biomes" [advancement.challenge_discover_biomes_40] - title = "World Traveler" - description = "Discover 40 unique biomes" +title = "World Traveler" +description = "Discover 40 unique biomes" [advancement.challenge_discover_foods_10] - title = "Foodie" - description = "Discover 10 unique foods" +title = "Foodie" +description = "Discover 10 unique foods" [advancement.challenge_discover_foods_30] - title = "Culinary Master" - description = "Discover 30 unique foods" +title = "Culinary Master" +description = "Discover 30 unique foods" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Power Weaver" - description = "Accumulate 100 enchantment power" +title = "Power Weaver" +description = "Accumulate 100 enchantment power" [advancement.challenge_enchant_power_1k] - title = "Arcane Master" - description = "Accumulate 1,000 enchantment power" +title = "Arcane Master" +description = "Accumulate 1,000 enchantment power" [advancement.challenge_enchant_levels_1k] - title = "Level Spender" - description = "Spend 1,000 experience levels on enchanting" +title = "Level Spender" +description = "Spend 1,000 experience levels on enchanting" [advancement.challenge_enchant_levels_10k] - title = "XP Sink" - description = "Spend 10,000 experience levels on enchanting" +title = "XP Sink" +description = "Spend 10,000 experience levels on enchanting" [advancement.challenge_enchant_high_25] - title = "High Roller" - description = "Perform 25 max-level enchantments" +title = "High Roller" +description = "Perform 25 max-level enchantments" [advancement.challenge_enchant_high_250] - title = "Legendary Enchanter" - description = "Perform 250 max-level enchantments" +title = "Legendary Enchanter" +description = "Perform 250 max-level enchantments" [advancement.challenge_enchant_total_500] - title = "Level Burner" - description = "Spend 500 total levels across all enchantments" +title = "Level Burner" +description = "Spend 500 total levels across all enchantments" [advancement.challenge_enchant_total_5k] - title = "Arcane Investment" - description = "Spend 5,000 total levels across all enchantments" +title = "Arcane Investment" +description = "Spend 5,000 total levels across all enchantments" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Digger" - description = "Swing your shovel 500 times" +title = "Digger" +description = "Swing your shovel 500 times" [advancement.challenge_dig_swing_5k] - title = "Excavator" - description = "Swing your shovel 5,000 times" +title = "Excavator" +description = "Swing your shovel 5,000 times" [advancement.challenge_dig_damage_1k] - title = "Shovel Knight" - description = "Deal 1,000 damage with a shovel" +title = "Shovel Knight" +description = "Deal 1,000 damage with a shovel" [advancement.challenge_dig_damage_10k] - title = "Shovel Master" - description = "Deal 10,000 damage with a shovel" +title = "Shovel Master" +description = "Deal 10,000 damage with a shovel" [advancement.challenge_dig_value_5k] - title = "Dirt Merchant" - description = "Excavate 5,000 value worth of blocks" +title = "Dirt Merchant" +description = "Excavate 5,000 value worth of blocks" [advancement.challenge_dig_value_50k] - title = "Earth Baron" - description = "Excavate 50,000 value worth of blocks" +title = "Earth Baron" +description = "Excavate 50,000 value worth of blocks" [advancement.challenge_dig_gravel_500] - title = "Gravel Grinder" - description = "Dig 500 gravel, sand, or clay blocks" +title = "Gravel Grinder" +description = "Dig 500 gravel, sand, or clay blocks" [advancement.challenge_dig_gravel_5k] - title = "Sand Sifter" - description = "Dig 5,000 gravel, sand, or clay blocks" +title = "Sand Sifter" +description = "Dig 5,000 gravel, sand, or clay blocks" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Seed Sower" - description = "Plant 100 crops" +title = "Seed Sower" +description = "Plant 100 crops" [advancement.challenge_plant_1k] - title = "Green Thumb" - description = "Plant 1,000 crops" +title = "Green Thumb" +description = "Plant 1,000 crops" [advancement.challenge_plant_5k] - title = "Agricultural Baron" - description = "Plant 5,000 crops" +title = "Agricultural Baron" +description = "Plant 5,000 crops" [advancement.challenge_compost_50] - title = "Recycler" - description = "Compost 50 items" +title = "Recycler" +description = "Compost 50 items" [advancement.challenge_compost_500] - title = "Soil Enricher" - description = "Compost 500 items" +title = "Soil Enricher" +description = "Compost 500 items" [advancement.challenge_shear_50] - title = "Shearer" - description = "Shear 50 entities" +title = "Shearer" +description = "Shear 50 entities" [advancement.challenge_shear_250] - title = "Flock Master" - description = "Shear 250 entities" +title = "Flock Master" +description = "Shear 250 entities" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Slayer" - description = "Slay 500 creatures" +title = "Slayer" +description = "Slay 500 creatures" [advancement.challenge_kills_5k] - title = "Executioner" - description = "Slay 5,000 creatures" +title = "Executioner" +description = "Slay 5,000 creatures" [advancement.challenge_boss_1] - title = "Boss Challenger" - description = "Slay a boss mob" +title = "Boss Challenger" +description = "Slay a boss mob" [advancement.challenge_boss_10] - title = "Legend Killer" - description = "Slay 10 boss mobs" +title = "Legend Killer" +description = "Slay 10 boss mobs" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Withered" - description = "Endure 500 wither damage" +title = "Withered" +description = "Endure 500 wither damage" [advancement.challenge_wither_dmg_5k] - title = "Blight Survivor" - description = "Endure 5,000 wither damage" +title = "Blight Survivor" +description = "Endure 5,000 wither damage" [advancement.challenge_wither_skel_25] - title = "Bone Collector" - description = "Slay 25 wither skeletons" +title = "Bone Collector" +description = "Slay 25 wither skeletons" [advancement.challenge_wither_skel_250] - title = "Skeleton Bane" - description = "Slay 250 wither skeletons" +title = "Skeleton Bane" +description = "Slay 250 wither skeletons" [advancement.challenge_wither_boss_1] - title = "Wither Slayer" - description = "Defeat the Wither" +title = "Wither Slayer" +description = "Defeat the Wither" [advancement.challenge_wither_boss_10] - title = "Nether Dominator" - description = "Defeat the Wither 10 times" +title = "Nether Dominator" +description = "Defeat the Wither 10 times" [advancement.challenge_roses_10] - title = "Death Gardener" - description = "Break 10 wither roses" +title = "Death Gardener" +description = "Break 10 wither roses" [advancement.challenge_roses_100] - title = "Blight Collector" - description = "Break 100 wither roses" +title = "Blight Collector" +description = "Break 100 wither roses" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Miner's Arm" - description = "Swing your pickaxe 500 times" +title = "Miner's Arm" +description = "Swing your pickaxe 500 times" [advancement.challenge_pick_swing_5k] - title = "Tunnel Maker" - description = "Swing your pickaxe 5,000 times" +title = "Tunnel Maker" +description = "Swing your pickaxe 5,000 times" [advancement.challenge_pick_damage_1k] - title = "Pick Fighter" - description = "Deal 1,000 damage with a pickaxe" +title = "Pick Fighter" +description = "Deal 1,000 damage with a pickaxe" [advancement.challenge_pick_damage_10k] - title = "War Pick" - description = "Deal 10,000 damage with a pickaxe" +title = "War Pick" +description = "Deal 10,000 damage with a pickaxe" [advancement.challenge_pick_value_5k] - title = "Gem Finder" - description = "Mine 5,000 value worth of blocks" +title = "Gem Finder" +description = "Mine 5,000 value worth of blocks" [advancement.challenge_pick_value_50k] - title = "Ore Baron" - description = "Mine 50,000 value worth of blocks" +title = "Ore Baron" +description = "Mine 50,000 value worth of blocks" [advancement.challenge_pick_ores_500] - title = "Prospector" - description = "Mine 500 ore blocks" +title = "Prospector" +description = "Mine 500 ore blocks" [advancement.challenge_pick_ores_5k] - title = "Master Miner" - description = "Mine 5,000 ore blocks" +title = "Master Miner" +description = "Mine 5,000 ore blocks" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Sharp Shooter" - description = "Deal 1,000 ranged damage" +title = "Sharp Shooter" +description = "Deal 1,000 ranged damage" [advancement.challenge_ranged_dmg_10k] - title = "Lethal Archer" - description = "Deal 10,000 ranged damage" +title = "Lethal Archer" +description = "Deal 10,000 ranged damage" [advancement.challenge_ranged_dist_5k] - title = "Long Range" - description = "Fire projectiles covering 5,000 blocks total distance" +title = "Long Range" +description = "Fire projectiles covering 5,000 blocks total distance" [advancement.challenge_ranged_dist_50k] - title = "Mile Shooter" - description = "Fire projectiles covering 50,000 blocks total distance" +title = "Mile Shooter" +description = "Fire projectiles covering 50,000 blocks total distance" [advancement.challenge_ranged_kills_50] - title = "Bowman" - description = "Kill 50 mobs with ranged weapons" +title = "Bowman" +description = "Kill 50 mobs with ranged weapons" [advancement.challenge_ranged_kills_500] - title = "Master Archer" - description = "Kill 500 mobs with ranged weapons" +title = "Master Archer" +description = "Kill 500 mobs with ranged weapons" [advancement.challenge_longshot_25] - title = "Sniper" - description = "Land 25 long-range shots (over 30 blocks)" +title = "Sniper" +description = "Land 25 long-range shots (over 30 blocks)" [advancement.challenge_longshot_250] - title = "Eagle Eye" - description = "Land 250 long-range shots (over 30 blocks)" +title = "Eagle Eye" +description = "Land 250 long-range shots (over 30 blocks)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "Pearl Tosser" - description = "Throw 50 ender pearls" +title = "Pearl Tosser" +description = "Throw 50 ender pearls" [advancement.challenge_rift_pearls_500] - title = "Teleport Junkie" - description = "Throw 500 ender pearls" +title = "Teleport Junkie" +description = "Throw 500 ender pearls" [advancement.challenge_rift_enderman_50] - title = "Enderman Hunter" - description = "Slay 50 endermen" +title = "Enderman Hunter" +description = "Slay 50 endermen" [advancement.challenge_rift_enderman_500] - title = "Void Stalker" - description = "Slay 500 endermen" +title = "Void Stalker" +description = "Slay 500 endermen" [advancement.challenge_rift_dragon_500] - title = "Dragon Fighter" - description = "Deal 500 damage to the Ender Dragon" +title = "Dragon Fighter" +description = "Deal 500 damage to the Ender Dragon" [advancement.challenge_rift_dragon_5k] - title = "Dragonbane" - description = "Deal 5,000 damage to the Ender Dragon" +title = "Dragonbane" +description = "Deal 5,000 damage to the Ender Dragon" [advancement.challenge_rift_crystal_10] - title = "Crystal Breaker" - description = "Destroy 10 end crystals" +title = "Crystal Breaker" +description = "Destroy 10 end crystals" [advancement.challenge_rift_crystal_100] - title = "End Demolisher" - description = "Destroy 100 end crystals" +title = "End Demolisher" +description = "Destroy 100 end crystals" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Angler" - description = "Catch 25 fish" +title = "Angler" +description = "Catch 25 fish" [advancement.challenge_fish_250] - title = "Master Fisher" - description = "Catch 250 fish" +title = "Master Fisher" +description = "Catch 250 fish" [advancement.challenge_drowned_25] - title = "Drowned Hunter" - description = "Slay 25 drowned" +title = "Drowned Hunter" +description = "Slay 25 drowned" [advancement.challenge_drowned_250] - title = "Ocean Cleaner" - description = "Slay 250 drowned" +title = "Ocean Cleaner" +description = "Slay 250 drowned" [advancement.challenge_guardian_10] - title = "Guardian Slayer" - description = "Slay 10 guardians" +title = "Guardian Slayer" +description = "Slay 10 guardians" [advancement.challenge_guardian_100] - title = "Temple Raider" - description = "Slay 100 guardians" +title = "Temple Raider" +description = "Slay 100 guardians" [advancement.challenge_underwater_blocks_100] - title = "Underwater Miner" - description = "Break 100 blocks while underwater" +title = "Underwater Miner" +description = "Break 100 blocks while underwater" [advancement.challenge_underwater_blocks_1k] - title = "Aquatic Engineer" - description = "Break 1,000 blocks while underwater" +title = "Aquatic Engineer" +description = "Break 1,000 blocks while underwater" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Backstabber" - description = "Deal 500 damage while sneaking" +title = "Backstabber" +description = "Deal 500 damage while sneaking" [advancement.challenge_stealth_dmg_5k] - title = "Silent Killer" - description = "Deal 5,000 damage while sneaking" +title = "Silent Killer" +description = "Deal 5,000 damage while sneaking" [advancement.challenge_stealth_kills_10] - title = "Assassin" - description = "Kill 10 mobs while sneaking" +title = "Assassin" +description = "Kill 10 mobs while sneaking" [advancement.challenge_stealth_kills_100] - title = "Shadow Reaper" - description = "Kill 100 mobs while sneaking" +title = "Shadow Reaper" +description = "Kill 100 mobs while sneaking" [advancement.challenge_stealth_time_1h] - title = "Patient" - description = "Spend 1 hour sneaking (3,600 seconds)" +title = "Patient" +description = "Spend 1 hour sneaking (3,600 seconds)" [advancement.challenge_stealth_time_10h] - title = "Master of Shadows" - description = "Spend 10 hours sneaking (36,000 seconds)" +title = "Master of Shadows" +description = "Spend 10 hours sneaking (36,000 seconds)" [advancement.challenge_stealth_arrows_50] - title = "Silent Archer" - description = "Fire 50 arrows while sneaking" +title = "Silent Archer" +description = "Fire 50 arrows while sneaking" [advancement.challenge_stealth_arrows_500] - title = "Phantom Bowman" - description = "Fire 500 arrows while sneaking" +title = "Phantom Bowman" +description = "Fire 500 arrows while sneaking" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Blade Apprentice" - description = "Deal 1,000 damage with swords" +title = "Blade Apprentice" +description = "Deal 1,000 damage with swords" [advancement.challenge_sword_dmg_10k] - title = "Swordsman" - description = "Deal 10,000 damage with swords" +title = "Swordsman" +description = "Deal 10,000 damage with swords" [advancement.challenge_sword_kills_50] - title = "Duelist" - description = "Kill 50 mobs with swords" +title = "Duelist" +description = "Kill 50 mobs with swords" [advancement.challenge_sword_kills_500] - title = "Gladiator" - description = "Kill 500 mobs with swords" +title = "Gladiator" +description = "Kill 500 mobs with swords" [advancement.challenge_sword_crit_50] - title = "Critical Striker" - description = "Land 50 critical hits with swords" +title = "Critical Striker" +description = "Land 50 critical hits with swords" [advancement.challenge_sword_crit_500] - title = "Precision Master" - description = "Land 500 critical hits with swords" +title = "Precision Master" +description = "Land 500 critical hits with swords" [advancement.challenge_sword_heavy_25] - title = "Heavy Hitter" - description = "Land 25 heavy hits with swords (over 8 damage)" +title = "Heavy Hitter" +description = "Land 25 heavy hits with swords (over 8 damage)" [advancement.challenge_sword_heavy_250] - title = "Devastating Blow" - description = "Land 250 heavy hits with swords (over 8 damage)" +title = "Devastating Blow" +description = "Land 250 heavy hits with swords (over 8 damage)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Beast Trainer" - description = "Your pets deal 500 total damage" +title = "Beast Trainer" +description = "Your pets deal 500 total damage" [advancement.challenge_pet_dmg_5k] - title = "War Master" - description = "Your pets deal 5,000 total damage" +title = "War Master" +description = "Your pets deal 5,000 total damage" [advancement.challenge_tamed_10] - title = "Animal Friend" - description = "Tame 10 animals" +title = "Animal Friend" +description = "Tame 10 animals" [advancement.challenge_tamed_100] - title = "Zookeeper" - description = "Tame 100 animals" +title = "Zookeeper" +description = "Tame 100 animals" [advancement.challenge_pet_kills_25] - title = "Pack Tactics" - description = "Your pets slay 25 creatures" +title = "Pack Tactics" +description = "Your pets slay 25 creatures" [advancement.challenge_pet_kills_250] - title = "Alpha Commander" - description = "Your pets slay 250 creatures" +title = "Alpha Commander" +description = "Your pets slay 250 creatures" [advancement.challenge_taming_2500] - title = "Breeding Expert" - description = "Breed 2,500 animals" +title = "Breeding Expert" +description = "Breed 2,500 animals" [advancement.challenge_taming_25k] - title = "Genetics Master" - description = "Breed 25,000 animals" +title = "Genetics Master" +description = "Breed 25,000 animals" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Punching Bag" - description = "Receive 500 hits" +title = "Punching Bag" +description = "Receive 500 hits" [advancement.challenge_trag_hits_5k] - title = "Glutton for Punishment" - description = "Receive 5,000 hits" +title = "Glutton for Punishment" +description = "Receive 5,000 hits" [advancement.challenge_trag_deaths_10] - title = "Nine Lives" - description = "Die 10 times" +title = "Nine Lives" +description = "Die 10 times" [advancement.challenge_trag_deaths_100] - title = "Recurring Nightmare" - description = "Die 100 times" +title = "Recurring Nightmare" +description = "Die 100 times" [advancement.challenge_trag_fire_500] - title = "Burn Victim" - description = "Endure 500 fire damage" +title = "Burn Victim" +description = "Endure 500 fire damage" [advancement.challenge_trag_fire_5k] - title = "Phoenix" - description = "Endure 5,000 fire damage" +title = "Phoenix" +description = "Endure 5,000 fire damage" [advancement.challenge_trag_fall_500] - title = "Gravity Check" - description = "Endure 500 fall damage" +title = "Gravity Check" +description = "Endure 500 fall damage" [advancement.challenge_trag_fall_5k] - title = "Terminal Velocity" - description = "Endure 5,000 fall damage" +title = "Terminal Velocity" +description = "Endure 5,000 fall damage" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Brawler" - description = "Deal 1,000 damage with bare fists" +title = "Brawler" +description = "Deal 1,000 damage with bare fists" [advancement.challenge_unarmed_dmg_10k] - title = "Martial Artist" - description = "Deal 10,000 damage with bare fists" +title = "Martial Artist" +description = "Deal 10,000 damage with bare fists" [advancement.challenge_unarmed_kills_25] - title = "Bare Knuckle" - description = "Kill 25 mobs with bare fists" +title = "Bare Knuckle" +description = "Kill 25 mobs with bare fists" [advancement.challenge_unarmed_kills_250] - title = "Fist of Legend" - description = "Kill 250 mobs with bare fists" +title = "Fist of Legend" +description = "Kill 250 mobs with bare fists" [advancement.challenge_unarmed_crit_25] - title = "Critical Punch" - description = "Land 25 critical hits with bare fists" +title = "Critical Punch" +description = "Land 25 critical hits with bare fists" [advancement.challenge_unarmed_crit_250] - title = "Precision Fist" - description = "Land 250 critical hits with bare fists" +title = "Precision Fist" +description = "Land 250 critical hits with bare fists" [advancement.challenge_unarmed_heavy_25] - title = "Power Punch" - description = "Land 25 heavy hits with bare fists (over 6 damage)" +title = "Power Punch" +description = "Land 25 heavy hits with bare fists (over 6 damage)" [advancement.challenge_unarmed_heavy_250] - title = "Knockout King" - description = "Land 250 heavy hits with bare fists (over 6 damage)" +title = "Knockout King" +description = "Land 250 heavy hits with bare fists (over 6 damage)" # items [items] [items.bound_ender_peral] - name = "Reliquary Portkey" - usage1 = "Shift + Left Click to bind" - usage2 = "Right Click to access the bound Inventory" +name = "Reliquary Portkey" +usage1 = "Shift + Left Click to bind" +usage2 = "Right Click to access the bound Inventory" [items.bound_eye_of_ender] - name = "Ocular Anchor" - usage1 = "Right Click to consume and teleport to the bound location" - usage2 = "Shift + Left Click to bind to a block" +name = "Ocular Anchor" +usage1 = "Right Click to consume and teleport to the bound location" +usage2 = "Shift + Left Click to bind to a block" [items.bound_redstone_torch] - name = "Redstone Remote" - usage1 = "Right Click to create a 1-Tick Redstone pulse" - usage2 = "Shift + Left Click on a 'Target' Block to bind" +name = "Redstone Remote" +usage1 = "Right Click to create a 1-Tick Redstone pulse" +usage2 = "Shift + Left Click on a 'Target' Block to bind" [items.bound_snowball] - name = "Web Snare!" - usage1 = "Throw to create a temporary web trap at the location" +name = "Web Snare!" +usage1 = "Throw to create a temporary web trap at the location" [items.chrono_time_bottle] - name = "Time In A Bottle" - usage1 = "Passively stores time while in your inventory" - usage2 = "Right-click timed blocks or baby animals to spend stored time" - stored = "Stored Time" +name = "Time In A Bottle" +usage1 = "Passively stores time while in your inventory" +usage2 = "Right-click timed blocks or baby animals to spend stored time" +stored = "Stored Time" [items.chrono_time_bomb] - name = "Time Bomb" - usage1 = "Right-click to launch a chrono bolt that creates a temporal field" +name = "Time Bomb" +usage1 = "Right-click to launch a chrono bolt that creates a temporal field" [items.elevator_block] - name = "Elevator Block" - usage1 = "Jump to teleport up" - usage2 = "Shift to teleport down" - usage3 = "Minimum of 2 air blocks between the elevators" +name = "Elevator Block" +usage1 = "Jump to teleport up" +usage2 = "Shift to teleport down" +usage3 = "Minimum of 2 air blocks between the elevators" # snippets [snippets] [snippets.gui] - level = "Level" - knowledge = "knowledge" - power_used = "Power Used" - not_learned = "Not Learned" - xp = "XP to" - welcome = "Welcome!" - welcome_back = "Welcome back!" - xp_bonus_for_time = "XP for" - max_ability_power = "Maximum Ability Power" - unlock_this_by_clicking = "Unlock this by Right-Clicking: " - back = "Back" - unlearn_all = "Unlearn all" - unlearned_all = "Unlearned all" +level = "Level" +knowledge = "knowledge" +power_used = "Power Used" +not_learned = "Not Learned" +xp = "XP to" +welcome = "Welcome!" +welcome_back = "Welcome back!" +xp_bonus_for_time = "XP for" +max_ability_power = "Maximum Ability Power" +unlock_this_by_clicking = "Unlock this by Right-Clicking: " +back = "Back" +unlearn_all = "Unlearn all" +unlearned_all = "Unlearned all" [snippets.adapt_menu] - may_not_unlearn = "YOU MAY NOT UNLEARN" - may_unlearn = "YOU MAY LEARN/UNLEARN" - knowledge_cost = "Knowledge Cost" - knowledge_available = "Knowledge Available" - already_learned = "Already Learned" - unlearn_refund = "Click to Unlearn & Refund" - no_refunds = "HARDCORE, REFUNDS DISABLED" - knowledge = "knowledge" - click_learn = "Click to Learn" - no_knowledge = "(You don't have any Knowledge)" - you_only_have = "You only have" - how_to_level_up = "Level up skills to increase your max power." - not_enough_power = "Not enough power! Each Ability Level costs 1 power." - power = "power" - power_drain = "Power Drain" - learned = "Learned " - unlearned = "Unlearned " - activator_block = "Bookshelf" +may_not_unlearn = "YOU MAY NOT UNLEARN" +may_unlearn = "YOU MAY LEARN/UNLEARN" +knowledge_cost = "Knowledge Cost" +knowledge_available = "Knowledge Available" +already_learned = "Already Learned" +unlearn_refund = "Click to Unlearn & Refund" +no_refunds = "HARDCORE, REFUNDS DISABLED" +knowledge = "knowledge" +click_learn = "Click to Learn" +no_knowledge = "(You don't have any Knowledge)" +you_only_have = "You only have" +how_to_level_up = "Level up skills to increase your max power." +not_enough_power = "Not enough power! Each Ability Level costs 1 power." +power = "power" +power_drain = "Power Drain" +learned = "Learned " +unlearned = "Unlearned " +activator_block = "Bookshelf" [snippets.knowledge_orb] - contains = "contains" - knowledge = "knowledge" - rightclick = "Right-Click" - togainknowledge = "to gain this knowledge" - knowledge_orb = "Knowledge Orb" +contains = "contains" +knowledge = "knowledge" +rightclick = "Right-Click" +togainknowledge = "to gain this knowledge" +knowledge_orb = "Knowledge Orb" [snippets.experience_orb] - contains = "contains" - xp = "Experience" - rightclick = "Right-Click" - togainxp = "to gain this experience" - xporb = "Experience Orb" +contains = "contains" +xp = "Experience" +rightclick = "Right-Click" +togainxp = "to gain this experience" +xporb = "Experience Orb" # skill [skill] [skill.agility] - name = "Agility" - icon = "⇉" - description = "Agility is the ability to move quick and fluidly in the face of obstacles." +name = "Agility" +icon = "⇉" +description = "Agility is the ability to move quick and fluidly in the face of obstacles. Level up by sprinting, swimming, jumping, and climbing." [skill.architect] - name = "Architect" - icon = "⬧" - description = "Structures are the building blocks of the world. Reality is in your hands, yours to control." +name = "Architect" +icon = "⬧" +description = "Structures are the building blocks of the world. Reality is in your hands, yours to control. Level up by placing blocks." [skill.axes] - name = "Axes" - icon = "🪓" - description1 = "Why chop down trees, when you could chop " - description2 = "things" - description3 = "instead, same end result!" +name = "Axes" +icon = "🪓" +description1 = "Level up by chopping wood and fighting with axes. Why chop down trees, when you could chop " +description2 = "things" +description3 = "instead? Same end result!" [skill.brewing] - name = "Brewing" - icon = "❦" - description = "Double Bubble, Triple Bubble, Quadruple Bubble- I still cant put this potion into a cauldron" +name = "Brewing" +icon = "❦" +description = "Level up by brewing potions, then unlock recipes vanilla never gave you. Double bubble, triple bubble - still can't put a potion in a cauldron." [skill.blocking] - name = "Blocking" - icon = "🛡" - description = "Sticks and stones Won't break your bones, But a shield will." +name = "Blocking" +icon = "🛡" +description = "Sticks and stones won't break your bones, but a shield will. Level up by blocking hits with a shield." [skill.crafting] - name = "Crafting" - icon = "⌂" - description = "With no more pieces left to place, why not make another?" +name = "Crafting" +icon = "⌂" +description = "With no more pieces left to place, why not make another? Level up by crafting items." [skill.discovery] - name = "Discovery" - icon = "⚛" - description = "As your perception expands, your mind unravels to discover that which you did not." +name = "Discovery" +icon = "⚛" +description = "As your perception expands, your mind unravels to discover that which you did not. Level up by exploring and collecting experience." [skill.enchanting] - name = "Enchanting" - icon = "♰" - description = "What are you going on about? Prophecies, visions, superstitious jibber-jabber?" +name = "Enchanting" +icon = "♰" +description = "Bend glimmering knowledge to your will. Level up by enchanting gear at the enchanting table." [skill.excavation] - name = "Excavation" - icon = "ᛳ" - description = "Diggy Diggy Hole..." +name = "Excavation" +icon = "ᛳ" +description = "Diggy diggy hole... Level up by digging dirt, sand, gravel, and other soft ground with shovels." [skill.herbalism] - name = "Herbalism" - icon = "⚘" - description = "I can't find any plants, but I can find some seeds and- is that... Weed?" +name = "Herbalism" +icon = "⚘" +description = "Master the green and growing things. Level up by farming, harvesting crops, and gathering plants." [skill.hunter] - name = "Hunter" - icon = "☠" - description = "Hunting is about the journey not the outcome." +name = "Hunter" +icon = "☠" +description = "Hunting is about the journey, not the outcome. Level up by slaying mobs; its boons trigger when you are struck, at the cost of hunger." [skill.nether] - name = "Nether" - icon = "₪" - description = "From the depths of the Nether itself." +name = "Nether" +icon = "₪" +description = "From the depths of the Nether itself. Level up by braving the Nether and its monsters." [skill.pickaxe] - name = "Pickaxe" - icon = "⛏" - description = "Dwarves are the miners, but ive learned a thing or two in my time. IM SWEDISH" +name = "Pickaxe" +icon = "⛏" +description = "Dwarves are the miners, but I've learned a thing or two in my time. Level up by mining stone and ores with pickaxes." [skill.ranged] - name = "Ranged" - icon = "🏹" - description = "Distance is the key to victory, and the key to survival." +name = "Ranged" +icon = "🏹" +description = "Distance is the key to victory, and the key to survival. Level up by landing projectile hits." [skill.rift] - name = "Rift" - icon = "❍" - description = "The Rift is a caustic harness, but you have harnessed the harness." +name = "Rift" +icon = "❍" +description = "Harness the caustic energy of the Rift. Level up by teleporting and using ender items." [skill.seaborne] - name = "Seaborne" - icon = "🎣" - description = "With this skill, you may will the wonders of the water." +name = "Seaborne" +icon = "🎣" +description = "With this skill, you may will the wonders of the water. Level up by swimming and fishing." [skill.stealth] - name = "Stealth" - icon = "☯" - description = "The art of the unseen. Walk in the shadows." +name = "Stealth" +icon = "☯" +description = "The art of the unseen. Walk in the shadows. Level up by sneaking and striking while hidden." [skill.swords] - name = "Swords" - icon = "⚔" - description = "By the power of GreyStone!" +name = "Swords" +icon = "⚔" +description = "By the power of GreyStone! Level up by dealing damage with swords." [skill.taming] - name = "Taming" - icon = "♥" - description = "The parrots and the bees... and you?" +name = "Taming" +icon = "♥" +description = "The parrots and the bees... and you? Level up by taming, breeding, and fighting alongside your pets." [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Blood flows through the veins of the universe. Constricted by your hands." +name = "TragOul" +icon = "🗡" +description = "Blood flows through the veins of the universe, constricted by your hands. Level up by taking damage and surviving at low health." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Wind the Clock of the universe, experience the flow. Break the clock, become it." +name = "Chronos" +icon = "🕒" +description = "Wind the clock of the universe, experience the flow. Break the clock, become it. Level up by moving, sleeping, and surviving the passage of time." [skill.unarmed] - name = "Unarmed" - icon = "»" - description = "Without a weapon is not without strength." +name = "Unarmed" +icon = "»" +description = "Without a weapon is not without strength. Level up by fighting bare-handed." # agility [agility] [agility.armor_up] - name = "Armor-Up" - description = "Get more armor the longer you sprint!" - lore1 = "Max Armor" - lore2 = "Armor-Up Time" - lore = ["Max Armor", "Armor-Up Time"] +name = "Armor-Up" +description = "Get more armor the longer you sprint!" +lore1 = "Max Armor" +lore2 = "Armor-Up Time" +lore = ["Max Armor", "Armor-Up Time"] [agility.ladder_slide] - name = "Ladder Slide" - description = "Climb and slide ladders much faster in both directions." - lore1 = "Ladder speed multiplier" - lore2 = "Fast descent speed" - lore = ["Ladder speed multiplier", "Fast descent speed"] +name = "Ladder Slide" +description = "Climb and slide ladders much faster in both directions." +lore1 = "Ladder speed multiplier" +lore2 = "Fast descent speed" +lore = ["Ladder speed multiplier", "Fast descent speed"] [agility.super_jump] - name = "Super Jump" - description = "Exceptional Height Advantage." - lore1 = "Max Jump Height" - lore2 = "Sneak + Jump to Super Jump!" - lore = ["Max Jump Height", "Sneak + Jump to Super Jump!"] +name = "Super Jump" +description = "Sneak and jump to launch a super jump. Jump height scales with level." +lore1 = "Max Jump Height" +lore2 = "Sneak + Jump to Super Jump!" +lore = ["Max Jump Height", "Sneak + Jump to Super Jump!"] [agility.wall_jump] - name = "Wall Jump" - description = "Hold shift while mid-air against a wall to wall latch & jump!" - lore1 = "Max Jumps" - lore2 = "Jump Height" - lore = ["Max Jumps", "Jump Height"] +name = "Wall Jump" +description = "Hold shift while mid-air against a wall to wall latch & jump!" +lore1 = "Max Jumps" +lore2 = "Jump Height" +lore = ["Max Jumps", "Jump Height"] [agility.wind_up] - name = "Wind Up" - description = "Get faster the longer you sprint!" - lore1 = "Max Speed" - lore2 = "Windup Time" - lore = ["Max Speed", "Windup Time"] +name = "Wind Up" +description = "Get faster the longer you sprint!" +lore1 = "Max Speed" +lore2 = "Windup Time" +lore = ["Max Speed", "Windup Time"] [agility.parkour_momentum] - name = "Parkour Momentum" - description = "Chain sprint-jumps and land on ledges to build momentum for mobility boosts." - lore1 = "Max Momentum Stacks" - lore2 = "Max Speed Amplifier" - lore3 = "Max Jump Amplifier" - lore = ["Max Momentum Stacks", "Max Speed Amplifier", "Max Jump Amplifier"] +name = "Parkour Momentum" +description = "Chain sprint-jumps and land on ledges to build momentum for mobility boosts." +lore1 = "Max Momentum Stacks" +lore2 = "Max Speed Amplifier" +lore3 = "Max Jump Amplifier" +lore = ["Max Momentum Stacks", "Max Speed Amplifier", "Max Jump Amplifier"] [agility.roll_landing] - name = "Roll Landing" - description = "Timed crouch before landing converts part of fall damage into hunger cost." - lore1 = "Fall Damage Conversion" - lore2 = "Input Timing Window" - lore3 = "Roll Cooldown" - lore = ["Fall Damage Conversion", "Input Timing Window", "Roll Cooldown"] +name = "Roll Landing" +description = "Timed crouch before landing converts part of fall damage into hunger cost." +lore1 = "Fall Damage Conversion" +lore2 = "Input Timing Window" +lore3 = "Roll Cooldown" +lore = ["Fall Damage Conversion", "Input Timing Window", "Roll Cooldown"] # architect [architect] [architect.elevator] - name = "Elevator" - description = "This allows for you to build an elevator to teleport vertically fast!" - lore1 = "Unlocks elevator recipe: X=WOOL, Y=ENDER PEARL" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Unlocks elevator recipe: X=WOOL, Y=ENDER PEARL", "XXX", "XYX", "XXX"] +name = "Elevator" +description = "This allows for you to build an elevator to teleport vertically fast!" +lore1 = "Unlocks elevator recipe: X=WOOL, Y=ENDER PEARL" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Unlocks elevator recipe: X=WOOL, Y=ENDER PEARL", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Magic Foundation" - description = "This allows for you to sneak and place a temporary foundation beneath you!" - lore1 = "Magically create: " - lore2 = "Blocks beneath you!" - lore = ["Magically create: ", "Blocks beneath you!"] +name = "Magic Foundation" +description = "This allows for you to sneak and place a temporary foundation beneath you!" +lore1 = "Magically create: " +lore2 = "Blocks beneath you!" +lore = ["Magically create: ", "Blocks beneath you!"] [architect.glass] - name = "Silk-Touch Glass" - description = "This allows for you to essentially prevent the loss of glass blocks when you break them with an empty hand!" - lore1 = "Your hands gain silk touch for Glass" - lore = ["Your hands gain silk touch for Glass"] +name = "Silk-Touch Glass" +description = "Break glass blocks with an empty hand to pick them up without shattering them." +lore1 = "Your hands gain silk touch for Glass" +lore = ["Your hands gain silk touch for Glass"] [architect.wireless_redstone] - name = "Redstone Remote" - description = "This allows for you to use a redstone torch to toggle redstone, remotely!" - lore1 = "Target + Redstone Torch + Enderpearl = 1 Redstone Remote" - lore = ["Target + Redstone Torch + Enderpearl = 1 Redstone Remote"] +name = "Redstone Remote" +description = "This allows for you to use a redstone torch to toggle redstone, remotely!" +lore1 = "Target + Redstone Torch + Ender Pearl = 1 Redstone Remote" +lore = ["Target + Redstone Torch + Ender Pearl = 1 Redstone Remote"] [architect.placement] - name = "Builders Wand" - description = "Allows for you to place multiple blocks at once to activate Sneak, and hold a block that matches your looking block and place! Keep in mind, you may need to move a tad to trigger bounding the boxes!" - lore1 = "You need" - lore2 = "blocks in your hand to place this" - lore3 = "A Material Builders Wand" - lore = ["You need", "blocks in your hand to place this", "A Material Builders Wand"] +name = "Builders Wand" +description = "Sneak while holding a block that matches the surface you are looking at to place multiple blocks across it at once. You may need to move slightly to refresh the placement preview." +lore1 = "You need" +lore2 = "blocks in your hand to place this" +lore3 = "A Material Builders Wand" +lore = ["You need", "blocks in your hand to place this", "A Material Builders Wand"] [architect.smart_shape] - name = "Smart Shape" - description = "Sneak-punch blocks with an empty hand to rotate orientation." - lore1 = "Rotates directional and axis block states" - lore2 = "Requires empty main hand" - lore = ["Rotates directional and axis block states", "Requires empty main hand"] +name = "Smart Shape" +description = "Sneak-punch blocks with an empty hand to rotate orientation." +lore1 = "Rotates directional and axis block states" +lore2 = "Requires empty main hand" +lore = ["Rotates directional and axis block states", "Requires empty main hand"] # axe [axe] [axe.chop] - name = "Axe Chop" - description = "Chop down trees by right clicking the base log!" - lore1 = "Blocks Per Chop" - lore2 = "Chop Cooldown" - lore3 = "Tool Wear" - lore = ["Blocks Per Chop", "Chop Cooldown", "Tool Wear"] +name = "Axe Chop" +description = "Chop down trees by right clicking the base log!" +lore1 = "Blocks Per Chop" +lore2 = "Chop Cooldown" +lore3 = "Tool Wear" +lore = ["Blocks Per Chop", "Chop Cooldown", "Tool Wear"] [axe.log_swap] - name = "Lucy's Log-Swapper" - description = "Change the flavor of logs in a Crafting Table!" - lore1 = "8 Log of any kind + 1 sapling = 8 log of the sapling's type" - lore = ["8 Log of any kind + 1 sapling = 8 log of the sapling's type"] +name = "Lucy's Log-Swapper" +description = "Change the flavor of logs in a Crafting Table!" +lore1 = "8 Log of any kind + 1 sapling = 8 log of the sapling's type" +lore = ["8 Log of any kind + 1 sapling = 8 log of the sapling's type"] [axe.drop_to_inventory] - name = "Axe Drop-To-Inventory" +name = "Axe Drop-To-Inventory" [axe.ground_smash] - name = "Axe Ground Smash" - description = "Jump, then crouch and smash all nearby enemies." - lore1 = "Damage" - lore2 = "Block Radius" - lore3 = "Force" - lore4 = "Smash Cooldown" - lore = ["Damage", "Block Radius", "Force", "Smash Cooldown"] +name = "Axe Ground Smash" +description = "Jump, then crouch and smash all nearby enemies." +lore1 = "Damage" +lore2 = "Block Radius" +lore3 = "Force" +lore4 = "Smash Cooldown" +lore = ["Damage", "Block Radius", "Force", "Smash Cooldown"] [axe.leaf_miner] - name = "Leaf-miner" - description = "Allows you to break bulk leaves at once!" - lore1 = "Sneak, and mine LEAVES" - lore2 = "range of Leaf-mining" - lore3 = "You will not get the drops from the leaves (Exploit Prevention)" - lore = ["Sneak, and mine LEAVES", "range of Leaf-mining", "You will not get the drops from the leaves (Exploit Prevention)"] +name = "Leaf-miner" +description = "Allows you to break bulk leaves at once!" +lore1 = "Sneak, and mine LEAVES" +lore2 = "range of Leaf-mining" +lore3 = "You will not get the drops from the leaves (Exploit Prevention)" +lore = ["Sneak, and mine LEAVES", "range of Leaf-mining", "You will not get the drops from the leaves (Exploit Prevention)"] [axe.wood_miner] - name = "Wood-miner" - description = "Allows you to break bulk wood at once!" - lore1 = "Sneak, and mine WOOD/LOGS ( Not Planks )" - lore2 = "range of Wood-mining" - lore3 = "Works with Drop to inventory" - lore = ["Sneak, and mine WOOD/LOGS ( Not Planks )", "range of Wood-mining", "Works with Drop to inventory"] +name = "Wood-miner" +description = "Allows you to break bulk wood at once!" +lore1 = "Sneak, and mine WOOD/LOGS ( Not Planks )" +lore2 = "range of Wood-mining" +lore3 = "Works with Drop to inventory" +lore = ["Sneak, and mine WOOD/LOGS ( Not Planks )", "range of Wood-mining", "Works with Drop to inventory"] [axe.timber_mark] - name = "Timber Mark" - description = "Sneak-right-click a log with an axe to mark it, then break that log to fell connected wood." - lore1 = "Max Logs Broken" - lore2 = "Mark Duration" - lore = ["Max Logs Broken", "Mark Duration"] +name = "Timber Mark" +description = "Sneak-right-click a log with an axe to mark it, then break that log to fell connected wood." +lore1 = "Max Logs Broken" +lore2 = "Mark Duration" +lore = ["Max Logs Broken", "Mark Duration"] # brewing [brewing] [brewing.lingering] - name = "Lingering Brew" - description = "Brewed potions last longer!" - lore1 = "Duration" - lore2 = "Duration" - lore = ["Duration", "Duration"] +name = "Lingering Brew" +description = "Brewed potions last longer!" +lore1 = "Duration" +lore2 = "Duration" +lore = ["Duration", "Duration"] [brewing.super_heated] - name = "Super Heated Brew" - description = "Brewing stands work faster the hotter they are." - lore1 = "Per Touching Fire Block" - lore2 = "Per Touching Lava Block" - lore = ["Per Touching Fire Block", "Per Touching Lava Block"] +name = "Super Heated Brew" +description = "Brewing stands work faster the hotter they are." +lore1 = "Per Touching Fire Block" +lore2 = "Per Touching Lava Block" +lore = ["Per Touching Fire Block", "Per Touching Lava Block"] [brewing.darkness] - name = "Bottled Darkness" - description = "Not sure why you need this, but here you go!" - lore1 = "NightVision Potion + Black Concrete = Potion of Darkness (30 seconds)" - lore2 = "It Should be noted that This prevents the user from Sprinting!" - lore = ["NightVision Potion + Black Concrete = Potion of Darkness (30 seconds)", "It Should be noted that This prevents the user from Sprinting!"] +name = "Bottled Darkness" +description = "Unlocks brewing Potions of Darkness, which shroud vision and prevent sprinting." +lore1 = "NightVision Potion + Black Concrete = Potion of Darkness (30 seconds)" +lore2 = "Note: Darkness prevents the drinker from sprinting!" +lore = ["NightVision Potion + Black Concrete = Potion of Darkness (30 seconds)", "Note: Darkness prevents the drinker from sprinting!"] [brewing.haste] - name = "Bottled Haste" - description = "When Efficiency is not enough" - lore1 = "Speed Potion + Amethyst Shard = Potion of Haste (60 seconds)" - lore2 = "Speed Potion + Amethyst Block = Potion of Haste-2 (30 seconds)" - lore = ["Speed Potion + Amethyst Shard = Potion of Haste (60 seconds)", "Speed Potion + Amethyst Block = Potion of Haste-2 (30 seconds)"] +name = "Bottled Haste" +description = "Unlocks brewing Potions of Haste for faster mining, when Efficiency is not enough." +lore1 = "Speed Potion + Amethyst Shard = Potion of Haste (60 seconds)" +lore2 = "Speed Potion + Amethyst Block = Potion of Haste-2 (30 seconds)" +lore = ["Speed Potion + Amethyst Shard = Potion of Haste (60 seconds)", "Speed Potion + Amethyst Block = Potion of Haste-2 (30 seconds)"] [brewing.absorption] - name = "Bottled Absorption" - description = "Harden the body!" - lore1 = "Instant Heal + Quartz = Potion of Absorption (60 seconds)" - lore2 = "Instant Heal + Quartz Block = Potion of Absorption-2 (30 seconds)" - lore = ["Instant Heal + Quartz = Potion of Absorption (60 seconds)", "Instant Heal + Quartz Block = Potion of Absorption-2 (30 seconds)"] +name = "Bottled Absorption" +description = "Unlocks brewing Potions of Absorption for temporary bonus hearts." +lore1 = "Instant Heal + Quartz = Potion of Absorption (60 seconds)" +lore2 = "Instant Heal + Quartz Block = Potion of Absorption-2 (30 seconds)" +lore = ["Instant Heal + Quartz = Potion of Absorption (60 seconds)", "Instant Heal + Quartz Block = Potion of Absorption-2 (30 seconds)"] [brewing.fatigue] - name = "Bottled Fatigue" - description = "Weaken the body!" - lore1 = "Weakness Potion + Slime Ball = Potion of Fatigue (30 seconds)" - lore2 = "Weakness Potion + Slime Block = Potion of Fatigue-2 (15 seconds)" - lore = ["Weakness Potion + Slime Ball = Potion of Fatigue (30 seconds)", "Weakness Potion + Slime Block = Potion of Fatigue-2 (15 seconds)"] +name = "Bottled Fatigue" +description = "Unlocks brewing Potions of Mining Fatigue, which slow a target's digging and attacks." +lore1 = "Weakness Potion + Slime Ball = Potion of Fatigue (30 seconds)" +lore2 = "Weakness Potion + Slime Block = Potion of Fatigue-2 (15 seconds)" +lore = ["Weakness Potion + Slime Ball = Potion of Fatigue (30 seconds)", "Weakness Potion + Slime Block = Potion of Fatigue-2 (15 seconds)"] [brewing.hunger] - name = "Bottled Hunger" - description = "Feed the Insatiable!" - lore1 = "Awkward Potion + Rotten Flesh = Potion of Hunger (30 seconds)" - lore2 = "Weakness Potion + Rotten Flesh = Potion of Hunger-3 (15 seconds)" - lore = ["Awkward Potion + Rotten Flesh = Potion of Hunger (30 seconds)", "Weakness Potion + Rotten Flesh = Potion of Hunger-3 (15 seconds)"] +name = "Bottled Hunger" +description = "Unlocks brewing Potions of Hunger, which drain a target's food." +lore1 = "Awkward Potion + Rotten Flesh = Potion of Hunger (30 seconds)" +lore2 = "Weakness Potion + Rotten Flesh = Potion of Hunger-3 (15 seconds)" +lore = ["Awkward Potion + Rotten Flesh = Potion of Hunger (30 seconds)", "Weakness Potion + Rotten Flesh = Potion of Hunger-3 (15 seconds)"] [brewing.nausea] - name = "Bottled Nausea" - description = "You make me sick!" - lore1 = "Awkward Potion + Brown Mushroom = Potion of Nausea (16 seconds)" - lore2 = "Awkward Potion + Crimson Fungus = Potion of Nausea-2 (8 seconds)" - lore = ["Awkward Potion + Brown Mushroom = Potion of Nausea (16 seconds)", "Awkward Potion + Crimson Fungus = Potion of Nausea-2 (8 seconds)"] +name = "Bottled Nausea" +description = "Unlocks brewing Potions of Nausea, which warp a target's vision." +lore1 = "Awkward Potion + Brown Mushroom = Potion of Nausea (16 seconds)" +lore2 = "Awkward Potion + Crimson Fungus = Potion of Nausea-2 (8 seconds)" +lore = ["Awkward Potion + Brown Mushroom = Potion of Nausea (16 seconds)", "Awkward Potion + Crimson Fungus = Potion of Nausea-2 (8 seconds)"] [brewing.blindness] - name = "Bottled Blindness" - description = "You're a horrible person..." - lore1 = "Awkward Potion + Ink sack = Potion of Blindness (30 seconds)" - lore2 = "Awkward Potion + Glowing Ink Sack = Potion of Blindness-2 (15 seconds)" - lore = ["Awkward Potion + Ink sack = Potion of Blindness (30 seconds)", "Awkward Potion + Glowing Ink Sack = Potion of Blindness-2 (15 seconds)"] +name = "Bottled Blindness" +description = "Unlocks brewing Potions of Blindness, which shroud a target's sight." +lore1 = "Awkward Potion + Ink sack = Potion of Blindness (30 seconds)" +lore2 = "Awkward Potion + Glowing Ink Sack = Potion of Blindness-2 (15 seconds)" +lore = ["Awkward Potion + Ink sack = Potion of Blindness (30 seconds)", "Awkward Potion + Glowing Ink Sack = Potion of Blindness-2 (15 seconds)"] [brewing.resistance] - name = "Bottled Resistance" - description = "Fortification at its finest!" - lore1 = "Awkward Potion + Iron Ingot = Potion of Resistance (60 seconds)" - lore2 = "Awkward Potion + Iron Block = Potion of Resistance-2 (30 seconds)" - lore = ["Awkward Potion + Iron Ingot = Potion of Resistance (60 seconds)", "Awkward Potion + Iron Block = Potion of Resistance-2 (30 seconds)"] +name = "Bottled Resistance" +description = "Unlocks brewing Potions of Resistance, which reduce incoming damage." +lore1 = "Awkward Potion + Iron Ingot = Potion of Resistance (60 seconds)" +lore2 = "Awkward Potion + Iron Block = Potion of Resistance-2 (30 seconds)" +lore = ["Awkward Potion + Iron Ingot = Potion of Resistance (60 seconds)", "Awkward Potion + Iron Block = Potion of Resistance-2 (30 seconds)"] [brewing.health_boost] - name = "Bottled Life" - description = "When Maximum health is not enough..." - lore1 = "Instant-Healing Potion + Golden Apple = Potion of Health Boost (120 seconds)" - lore2 = "Instant-Healing Potion + Enchanted Golden Apple = Potion of Health Boost-2 (120 seconds)" - lore = ["Instant-Healing Potion + Golden Apple = Potion of Health Boost (120 seconds)", "Instant-Healing Potion + Enchanted Golden Apple = Potion of Health Boost-2 (120 seconds)"] +name = "Bottled Life" +description = "Unlocks brewing Potions of Health Boost for extra maximum hearts." +lore1 = "Instant-Healing Potion + Golden Apple = Potion of Health Boost (120 seconds)" +lore2 = "Instant-Healing Potion + Enchanted Golden Apple = Potion of Health Boost-2 (120 seconds)" +lore = ["Instant-Healing Potion + Golden Apple = Potion of Health Boost (120 seconds)", "Instant-Healing Potion + Enchanted Golden Apple = Potion of Health Boost-2 (120 seconds)"] [brewing.decay] - name = "Bottled Decay" - description = "Who knew Detritus would be so useful?" - lore1 = "Weakness Potion + Poisonous Potato = Potion of Wither (16 seconds)" - lore2 = "Weakness Potion + Crimson Roots = Potion of Wither-2 (8 seconds)" - lore = ["Weakness Potion + Poisonous Potato = Potion of Wither (16 seconds)", "Weakness Potion + Crimson Roots = Potion of Wither-2 (8 seconds)"] +name = "Bottled Decay" +description = "Unlocks brewing Potions of Wither, which afflict a target with decay." +lore1 = "Weakness Potion + Poisonous Potato = Potion of Wither (16 seconds)" +lore2 = "Weakness Potion + Crimson Roots = Potion of Wither-2 (8 seconds)" +lore = ["Weakness Potion + Poisonous Potato = Potion of Wither (16 seconds)", "Weakness Potion + Crimson Roots = Potion of Wither-2 (8 seconds)"] [brewing.saturation] - name = "Bottled Saturation" - description = "Ya know... Im not even Hungry..." - lore1 = "Regen Potion + Baked Potato = Potion of Saturation" - lore2 = "Regen Potion + Hay Bale = Potion of Saturation-2" - lore = ["Regen Potion + Baked Potato = Potion of Saturation", "Regen Potion + Hay Bale = Potion of Saturation-2"] +name = "Bottled Saturation" +description = "Unlocks brewing Potions of Saturation, which restore hunger." +lore1 = "Regen Potion + Baked Potato = Potion of Saturation" +lore2 = "Regen Potion + Hay Bale = Potion of Saturation-2" +lore = ["Regen Potion + Baked Potato = Potion of Saturation", "Regen Potion + Hay Bale = Potion of Saturation-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Chains of Mephistopheles" - description = "Allows you to craft Chainmail Armor" - lore1 = "The Crafting recipe is the same as any other, but with iron nuggets instead" - lore = ["The Crafting recipe is the same as any other, but with iron nuggets instead"] +name = "Chains of Mephistopheles" +description = "Allows you to craft Chainmail Armor" +lore1 = "The Crafting recipe is the same as any other, but with iron nuggets instead" +lore = ["The Crafting recipe is the same as any other, but with iron nuggets instead"] [blocking.horse_armorer] - name = "Craftable Horse Armor" - description = "Allows you to craft Horse Armor" - lore1 = "Surround a saddle with the material you want to use to craft the armor" - lore = ["Surround a saddle with the material you want to use to craft the armor"] +name = "Craftable Horse Armor" +description = "Allows you to craft Horse Armor" +lore1 = "Surround a saddle with the material you want to use to craft the armor" +lore = ["Surround a saddle with the material you want to use to craft the armor"] [blocking.saddle_crafter] - name = "Craftable Saddle" - description = "Craft a Saddle with Leather" - lore1 = "Recipe: 5 Leather:" - lore = ["Recipe: 5 Leather:"] +name = "Craftable Saddle" +description = "Craft a Saddle with Leather" +lore1 = "Recipe: 5 Leather:" +lore = ["Recipe: 5 Leather:"] [blocking.multi_armor] - name = "Multi-Armor" - description = "Bind Elytras to Armor" - lore1 = "This Is an amazing skill for traveling." - lore2 = "Dynamically merge and change Armor/Elytra on the Fly!" - lore3 = "To merge, shift click an item over another in your inventory." - lore4 = "To unbind Armor, Sneak-Drop the item, and it will disassemble." - lore5 = "If your MultiArmor is destroyed, you will lose all items in it." - lore6 = "I Don't need armor, It disappoints me..." - lore = ["This Is an amazing skill for traveling.", "Dynamically merge and change Armor/Elytra on the Fly!", "To merge, shift click an item over another in your inventory.", "To unbind Armor, Sneak-Drop the item, and it will disassemble.", "If your MultiArmor is destroyed, you will lose all items in it.", "I Don't need armor, It disappoints me..."] +name = "Multi-Armor" +description = "Bind an elytra to your chestplate and swap between them on the fly." +lore1 = "This Is an amazing skill for traveling." +lore2 = "Dynamically merge and change Armor/Elytra on the Fly!" +lore3 = "To merge, shift click an item over another in your inventory." +lore4 = "To unbind Armor, Sneak-Drop the item, and it will disassemble." +lore5 = "If your MultiArmor is destroyed, you will lose all items in it." +lore6 = "I Don't need armor, It disappoints me..." +lore = ["This Is an amazing skill for traveling.", "Dynamically merge and change Armor/Elytra on the Fly!", "To merge, shift click an item over another in your inventory.", "To unbind Armor, Sneak-Drop the item, and it will disassemble.", "If your MultiArmor is destroyed, you will lose all items in it.", "I Don't need armor, It disappoints me..."] [blocking.counter_guard] - name = "Counter Guard" - description = "Each blocked hit builds shield stacks. Your next proc consumes stacks to reflect damage to the attacker." - lore1 = "Max Stored Counter Stacks" - lore2 = "Reflect Proc Chance" - lore3 = "Base Reflect Damage" - lore = ["Max Stored Counter Stacks", "Reflect Proc Chance", "Base Reflect Damage"] +name = "Counter Guard" +description = "Each blocked hit builds shield stacks. Your next proc consumes stacks to reflect damage to the attacker." +lore1 = "Max Stored Counter Stacks" +lore2 = "Reflect Proc Chance" +lore3 = "Base Reflect Damage" +lore = ["Max Stored Counter Stacks", "Reflect Proc Chance", "Base Reflect Damage"] [blocking.bastion_stance] - name = "Bastion Stance" - description = "While sneaking and actively blocking with a shield, reduce knockback and incoming projectile pressure." - lore1 = "Knockback Resistance" - lore2 = "Projectile Damage Reduction" - lore3 = "Projectile Full-Block Chance" - lore = ["Knockback Resistance", "Projectile Damage Reduction", "Projectile Full-Block Chance"] +name = "Bastion Stance" +description = "While sneaking and actively blocking with a shield, reduce knockback and incoming projectile pressure." +lore1 = "Knockback Resistance" +lore2 = "Projectile Damage Reduction" +lore3 = "Projectile Full-Block Chance" +lore = ["Knockback Resistance", "Projectile Damage Reduction", "Projectile Full-Block Chance"] [blocking.mirror_block] - name = "Mirror Block" - description = "Blocking with a shield can reflect incoming projectiles with reduced follow-up force." - lore1 = "Projectile Reflect Chance" - lore2 = "Reflected Damage Factor" - lore3 = "Reflect Cooldown" - lore = ["Projectile Reflect Chance", "Reflected Damage Factor", "Reflect Cooldown"] +name = "Mirror Block" +description = "Blocking with a shield can reflect incoming projectiles with reduced follow-up force." +lore1 = "Projectile Reflect Chance" +lore2 = "Reflected Damage Factor" +lore3 = "Reflect Cooldown" +lore = ["Projectile Reflect Chance", "Reflected Damage Factor", "Reflect Cooldown"] [blocking.bulwark_bash] - name = "Bulwark Bash" - description = "Sprint-jump and land a shielded crit to trigger a bash shockwave." - lore1 = "Bash Range" - lore2 = "Bash Damage" - lore3 = "Bash Cooldown" - lore = ["Bash Range", "Bash Damage", "Bash Cooldown"] +name = "Bulwark Bash" +description = "Sprint-jump and land a shielded crit to trigger a bash shockwave." +lore1 = "Bash Range" +lore2 = "Bash Damage" +lore3 = "Bash Cooldown" +lore = ["Bash Range", "Bash Damage", "Bash Cooldown"] # crafting [crafting] [crafting.deconstruction] - name = "Deconstruction" - description = "Deconstruct blocks & items into salvageable base components!" - lore1 = "Drop any item on the ground." - lore2 = "Then, Sneak and Right-Click with Shears" - lore = ["Drop any item on the ground.", "Then, Sneak and Right-Click with Shears"] +name = "Deconstruction" +description = "Deconstruct blocks & items into salvageable base components!" +lore1 = "Drop any item on the ground." +lore2 = "Then, Sneak and Right-Click with Shears" +lore = ["Drop any item on the ground.", "Then, Sneak and Right-Click with Shears"] [crafting.xp] - name = "Crafting XP" - description = "Gain passive XP when crafting" - lore1 = "Gain XP when crafting" - lore = ["Gain XP when crafting"] +name = "Crafting XP" +description = "Gain passive XP when crafting" +lore1 = "Gain XP when crafting" +lore = ["Gain XP when crafting"] [crafting.reconstruction] - name = "Ore Reconstruction" - description = "Recraft ores from their base components!" - lore1 = "8 of the Drops and 1 Host = 1 Ore (shapeless)" - lore2 = "Drops must be smelted (if applicable)" - lore3 = "Not including: Scraps, Quarts, and Emeralds etc..." - lore4 = "Host = Encasement. ie: Stone, Netherack, Deepslate" - lore = ["8 of the Drops and 1 Host = 1 Ore (shapeless)", "Drops must be smelted (if applicable)", "Not including: Scraps, Quarts, and Emeralds etc...", "Host = Encasement. ie: Stone, Netherack, Deepslate"] +name = "Ore Reconstruction" +description = "Recraft ores from their base components!" +lore1 = "8 of the Drops and 1 Host = 1 Ore (shapeless)" +lore2 = "Drops must be smelted (if applicable)" +lore3 = "Not including: Scraps, Quarts, and Emeralds etc..." +lore4 = "Host = Encasement. ie: Stone, Netherack, Deepslate" +lore = ["8 of the Drops and 1 Host = 1 Ore (shapeless)", "Drops must be smelted (if applicable)", "Not including: Scraps, Quarts, and Emeralds etc...", "Host = Encasement. ie: Stone, Netherack, Deepslate"] [crafting.leather] - name = "Craftable Leather" - description = "Craft Leather from Rotten Flesh" - lore1 = "Just toss it(rotten flesh) on the campfire!" - lore = ["Just toss it(rotten flesh) on the campfire!"] +name = "Craftable Leather" +description = "Craft Leather from Rotten Flesh" +lore1 = "Just toss it(rotten flesh) on the campfire!" +lore = ["Just toss it(rotten flesh) on the campfire!"] [crafting.backpacks] - name = "A Boutilier's Backpacks!" - description = "This just Brings the Mojang Bundle into the game!" - lore1 = "You need to be in Survival to use this" - lore2 = "XLX : Leather, Lead, Leather" - lore3 = "XSX : Leather, Barrel Box, Leather" - lore4 = "XCX : Leather, Chest, Leather" - lore = ["You need to be in Survival to use this", "XLX : Leather, Lead, Leather", "XSX : Leather, Barrel Box, Leather", "XCX : Leather, Chest, Leather"] +name = "A Boutilier's Backpacks!" +description = "Unlocks crafting bundles to carry mixed item stacks in a single slot." +lore1 = "You need to be in Survival to use this" +lore2 = "XLX : Leather, Lead, Leather" +lore3 = "XSX : Leather, Barrel Box, Leather" +lore4 = "XCX : Leather, Chest, Leather" +lore = ["You need to be in Survival to use this", "XLX : Leather, Lead, Leather", "XSX : Leather, Barrel Box, Leather", "XCX : Leather, Chest, Leather"] [crafting.stations] - name = "Portable Tables!" - description = "Use a table in the palm of your hand!" - lore2 = "ANY ITEMS THAT YOU FORGET IN THE TABLE WHEN CLOSED ARE LOST FOREVER!" - lore3 = "Valid tables: Anvil, Crafting, Grindstone, Cartography, Stone-Cutter, Loom" - lore = ["ANY ITEMS THAT YOU FORGET IN THE TABLE WHEN CLOSED ARE LOST FOREVER!", "Valid tables: Anvil, Crafting, Grindstone, Cartography, Stone-Cutter, Loom"] +name = "Portable Tables!" +description = "Click the air while holding an anvil, crafting table, grindstone, stonecutter, cartography table, or loom to open it without placing it. Each open costs hunger." +lore2 = "ANY ITEMS THAT YOU FORGET IN THE TABLE WHEN CLOSED ARE LOST FOREVER!" +lore3 = "Valid tables: Anvil, Crafting, Grindstone, Cartography, Stone-Cutter, Loom" +lore = ["ANY ITEMS THAT YOU FORGET IN THE TABLE WHEN CLOSED ARE LOST FOREVER!", "Valid tables: Anvil, Crafting, Grindstone, Cartography, Stone-Cutter, Loom"] +lore4 = "Hunger consumed per portable station opened" [crafting.skulls] - name = "Craftable skulls!" - description = "Using Materials you can Craft Mob Skulls!" - lore1 = "Surround a Bone Block with the following to get a skull:" - lore2 = "Zombie: Rotting Flesh" - lore3 = "Skeleton: Bone" - lore4 = "Creeper: Gunpowder" - lore5 = "Wither: Nether Brick" - lore6 = "Dragon: Dragons Breath" - lore = ["Surround a Bone Block with the following to get a skull:", "Zombie: Rotting Flesh", "Skeleton: Bone", "Creeper: Gunpowder", "Wither: Nether Brick", "Dragon: Dragons Breath"] +name = "Craftable skulls!" +description = "Using Materials you can Craft Mob Skulls!" +lore1 = "Surround a Bone Block with the following to get a skull:" +lore2 = "Zombie: Rotting Flesh" +lore3 = "Skeleton: Bone" +lore4 = "Creeper: Gunpowder" +lore5 = "Wither: Nether Brick" +lore6 = "Dragon: Dragons Breath" +lore = ["Surround a Bone Block with the following to get a skull:", "Zombie: Rotting Flesh", "Skeleton: Bone", "Creeper: Gunpowder", "Wither: Nether Brick", "Dragon: Dragons Breath"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Time In A Bottle" - description = "Carry a temporal bottle that stores time and spend it to accelerate timed blocks, growables, and Ageable entities such as baby animals. Recipe (Shapeless): Swiftness Potion + Clock + Glass Bottle." - lore1 = "Stored seconds charged each tick" - lore2 = "Time acceleration per stored second" - lore3 = "Recipe (Shapeless): Swiftness Potion + Clock + Glass Bottle" - lore = ["Stored seconds charged each tick", "Time acceleration per stored second", "Recipe (Shapeless): Swiftness Potion + Clock + Glass Bottle"] +name = "Time In A Bottle" +description = "Carry a temporal bottle that stores time and spend it to accelerate timed blocks, growables, and Ageable entities such as baby animals. Recipe (Shapeless): Swiftness Potion + Clock + Glass Bottle." +lore1 = "Stored seconds charged each tick" +lore2 = "Time acceleration per stored second" +lore3 = "Recipe (Shapeless): Swiftness Potion + Clock + Glass Bottle" +lore = ["Stored seconds charged each tick", "Time acceleration per stored second", "Recipe (Shapeless): Swiftness Potion + Clock + Glass Bottle"] [chronos.aberrant_touch] - name = "Aberrant Touch" - description = "Melee attacks apply stacking slowness at the cost of hunger, with strict PvP caps, and root targets at 5 stacks." - lore1 = "Melee attacks apply stacking slowness" - lore2 = "PvE slowness duration cap" - lore3 = "PvP slowness amplifier cap" - lore = ["Melee attacks apply stacking slowness", "PvE slowness duration cap", "PvP slowness amplifier cap"] +name = "Aberrant Touch" +description = "Melee attacks apply stacking slowness at the cost of hunger, with strict PvP caps, and root targets at 5 stacks." +lore1 = "Melee attacks apply stacking slowness" +lore2 = "PvE slowness duration cap" +lore3 = "PvP slowness amplifier cap" +lore = ["Melee attacks apply stacking slowness", "PvE slowness duration cap", "PvP slowness amplifier cap"] [chronos.instant_recall] - name = "Instant Recall" - description = "Left or right click with a clock in hand to rewind to a recent snapshot with health and hunger restored." - lore1 = "Rewind duration" - lore2 = "Cooldown" - lore3 = "No inventory rollback" - lore = ["Rewind duration", "Cooldown", "No inventory rollback"] +name = "Instant Recall" +description = "Left or right click with a clock in hand to rewind to a recent snapshot with health and hunger restored. Costs the clock and half your remaining health, but never kills you." +lore1 = "Rewind duration" +lore2 = "Cooldown" +lore3 = "No inventory rollback" +lore = ["Rewind duration", "Cooldown", "No inventory rollback"] +lore_cost_clock = "Consumes the clock on recall" +lore_cost_health = "of remaining health lost on recall" [chronos.time_bomb] - name = "Time Bomb" - description = "Throw a crafted chrono bomb that creates a temporal field, slows entities, and freezes projectiles." - lore1 = "Temporal field radius" - lore2 = "Temporal field duration" - lore3 = "Bomb cooldown" - lore4 = "Recipe (Shapeless): Clock + Snowball + Diamond + Sand" - lore = ["Temporal field radius", "Temporal field duration", "Bomb cooldown", "Recipe (Shapeless): Clock + Snowball + Diamond + Sand"] +name = "Time Bomb" +description = "Throw a crafted chrono bomb that creates a temporal field, slows entities, and freezes projectiles." +lore1 = "Temporal field radius" +lore2 = "Temporal field duration" +lore3 = "Bomb cooldown" +lore4 = "Recipe (Shapeless): Clock + Snowball + Diamond + Sand" +lore = ["Temporal field radius", "Temporal field duration", "Bomb cooldown", "Recipe (Shapeless): Clock + Snowball + Diamond + Sand"] [chronos.temporal_echo] - name = "Temporal Echo" - description = "Projectile actions can replay once after a short delay at reduced strength." - lore1 = "Echo Delay" - lore2 = "Echo Velocity Factor" - lore3 = "Echo Cooldown" - lore = ["Echo Delay", "Echo Velocity Factor", "Echo Cooldown"] +name = "Temporal Echo" +description = "Projectile actions can replay once after a short delay at reduced strength." +lore1 = "Echo Delay" +lore2 = "Echo Velocity Factor" +lore3 = "Echo Cooldown" +lore = ["Echo Delay", "Echo Velocity Factor", "Echo Cooldown"] # discovery [discovery] [discovery.armor] - name = "World Armor" - description = "Passive armor depending on nearby block hardness." - lore1 = "Passive Armor" - lore2 = "Based on nearby block hardness" - lore3 = "Armor Strength:" - lore = ["Passive Armor", "Based on nearby block hardness", "Armor Strength:"] +name = "World Armor" +description = "Passive armor depending on nearby block hardness." +lore1 = "Passive Armor" +lore2 = "Based on nearby block hardness" +lore3 = "Armor Strength:" +lore = ["Passive Armor", "Based on nearby block hardness", "Armor Strength:"] [discovery.unity] - name = "Experimental Unity" - description = "Collecting Experience Orbs adds XP to random skills." - lore1 = "XP " - lore2 = "Per Orb" - lore = ["XP ", "Per Orb"] +name = "Experimental Unity" +description = "Collecting Experience Orbs adds XP to random skills." +lore1 = "XP " +lore2 = "Per Orb" +lore = ["XP ", "Per Orb"] [discovery.resist] - name = "Experimental Resistance" - description = "Consume experience to mitigate damage only when a hit would drop you below 5 hearts or kill you." - lore0 = "Triggers only at critical health (<= 5 hearts) once per 15 seconds" - lore1 = " Reduced Damage" - lore2 = "experience drained" - lore = ["Triggers only at critical health (<= 5 hearts) once per 15 seconds", " Reduced Damage", "experience drained"] +name = "Experimental Resistance" +description = "Consume experience to mitigate damage only when a hit would drop you below 5 hearts or kill you." +lore0 = "Triggers only at critical health (<= 5 hearts) once per 15 seconds" +lore1 = " Reduced Damage" +lore2 = "experience drained" +lore = ["Triggers only at critical health (<= 5 hearts) once per 15 seconds", " Reduced Damage", "experience drained"] [discovery.villager] - name = "Villager Attraction" - description = "Allows for you to get Better trades with villagers!" - lore1 = "This consumes XP per interaction with Villagers" - lore2 = "Chance Per interaction to consume XP, and enhance trades" - lore3 = "required XP drain per Interaction" - lore = ["This consumes XP per interaction with Villagers", "Chance Per interaction to consume XP, and enhance trades", "required XP drain per Interaction"] +name = "Villager Attraction" +description = "Get better trades with villagers, at the cost of XP per interaction." +lore1 = "This consumes XP per interaction with Villagers" +lore2 = "Chance Per interaction to consume XP, and enhance trades" +lore3 = "required XP drain per Interaction" +lore = ["This consumes XP per interaction with Villagers", "Chance Per interaction to consume XP, and enhance trades", "required XP drain per Interaction"] [discovery.better_mending] - name = "Better Mending" - description = "Sneak-left-click to spend your stored XP and directly mend the Mending item in your hand." - lore1 = "Durability Repaired per XP" - lore2 = "Max XP Spend per Click" - lore3 = "Mending Cooldown" - lore = ["Durability Repaired per XP", "Max XP Spend per Click", "Mending Cooldown"] +name = "Better Mending" +description = "Sneak-left-click to spend your stored XP and directly mend the Mending item in your hand." +lore1 = "Durability Repaired per XP" +lore2 = "Max XP Spend per Click" +lore3 = "Mending Cooldown" +lore = ["Durability Repaired per XP", "Max XP Spend per Click", "Mending Cooldown"] [discovery.archaeologist] - name = "Archaeologist" - description = "Brushing suspicious blocks can yield bonus archaeology rewards." - lore1 = "Bonus Reward Chance" - lore2 = "Rare Reward Chance" - lore3 = "Reward Cooldown" - lore = ["Bonus Reward Chance", "Rare Reward Chance", "Reward Cooldown"] +name = "Archaeologist" +description = "Brushing suspicious blocks can yield bonus archaeology rewards." +lore1 = "Bonus Reward Chance" +lore2 = "Rare Reward Chance" +lore3 = "Reward Cooldown" +lore = ["Bonus Reward Chance", "Rare Reward Chance", "Reward Cooldown"] [discovery.cartographer_pulse] - name = "Cartographer Pulse" - description = "Sneak-right-click with a compass to lock your compass toward a nearby structure." - lore1 = "Structure Search Range" - lore2 = "Pulse Cooldown" - lore = ["Structure Search Range", "Pulse Cooldown"] +name = "Cartographer Pulse" +description = "Sneak-right-click with a compass to lock it toward a nearby structure. Each pulse costs hunger." +lore1 = "Structure Search Range" +lore2 = "Pulse Cooldown" +lore = ["Structure Search Range", "Pulse Cooldown"] # enchanting +lore_cost_hunger = "Hunger cost per pulse" [enchanting] [enchanting.lapis_return] - name = "Lapis Return" - description = "At the cost of 1 more level of XP, and has a chance to give you free lapis in return" - lore1 = "For every level, it increases the cost of enchanting, by 1, but can return upwards of 3 Lapis" - lore = ["For every level, it increases the cost of enchanting, by 1, but can return upwards of 3 Lapis"] +name = "Lapis Return" +description = "Enchanting at a table has a chance to refund lapis, more at higher levels." +lore1 = "Chance to drop free lapis when you enchant; the amount scales with your level" +lore = ["Chance to drop free lapis when you enchant; the amount scales with your level"] [enchanting.quick_enchant] - name = "Quick-Click Enchant" - description = "Enchant items by clicking enchant books directly on them." - lore1 = "Max Combined Levels" - lore2 = "Cannot Enchant an item with more than " - lore3 = "power" - lore = ["Max Combined Levels", "Cannot Enchant an item with more than ", "power"] +name = "Quick-Click Enchant" +description = "Enchant items by clicking enchant books directly on them." +lore1 = "Max Combined Levels" +lore2 = "Cannot Enchant an item with more than " +lore3 = "power" +lore = ["Max Combined Levels", "Cannot Enchant an item with more than ", "power"] [enchanting.return] - name = "XP Return" - description = "Enchanting XP is returned to you when you enchant an item." - lore1 = "Experience spent has a chance to be refunded when you enchant an item" - lore2 = "Experience per Enchant" - lore = ["Experience spent has a chance to be refunded when you enchant an item", "Experience per Enchant"] +name = "XP Return" +description = "Enchanting XP is returned to you when you enchant an item." +lore1 = "Experience spent has a chance to be refunded when you enchant an item" +lore2 = "Experience per Enchant" +lore = ["Experience spent has a chance to be refunded when you enchant an item", "Experience per Enchant"] [enchanting.anvil_savant] - name = "Anvil Savant" - description = "Reduce anvil XP cost when combining, repairing, and renaming." - lore1 = "Anvil Cost Reduction" - lore = ["Anvil Cost Reduction"] +name = "Anvil Savant" +description = "Reduce anvil XP cost when combining, repairing, and renaming." +lore1 = "Anvil Cost Reduction" +lore = ["Anvil Cost Reduction"] [enchanting.offer_reroll] - name = "Offer Reroll" - description = "Sneak-right-click an enchanting table to reroll offers for lapis and XP." - lore1 = "Reroll Cooldown" - lore2 = "Lapis Cost" - lore = ["Reroll Cooldown", "Lapis Cost"] +name = "Offer Reroll" +description = "Sneak-right-click an enchanting table to reroll its offers. Each reroll costs lapis and XP levels." +lore1 = "Reroll Cooldown" +lore2 = "Lapis Cost" +lore = ["Reroll Cooldown", "Lapis Cost"] +lore_cost_xp = "XP Level Cost" [enchanting.bookshelf_attunement] - name = "Bookshelf Attunement" - description = "Gain virtual bookshelf power to improve enchanting table offer quality." - lore1 = "Virtual Bookshelf Power" - lore = ["Virtual Bookshelf Power"] +name = "Bookshelf Attunement" +description = "Gain virtual bookshelf power to improve enchanting table offer quality." +lore1 = "Virtual Bookshelf Power" +lore = ["Virtual Bookshelf Power"] [enchanting.grindstone_recovery] - name = "Grindstone Recovery" - description = "Disenchanting can recover one removed enchantment onto a book with bonus XP." - lore1 = "Recovery Chance" - lore2 = "Bonus XP" - lore3 = "Recovery Cooldown" - lore = ["Recovery Chance", "Bonus XP", "Recovery Cooldown"] +name = "Grindstone Recovery" +description = "Disenchanting can recover one removed enchantment onto a book with bonus XP." +lore1 = "Recovery Chance" +lore2 = "Bonus XP" +lore3 = "Recovery Cooldown" +lore = ["Recovery Chance", "Bonus XP", "Recovery Cooldown"] # excavation [excavation] [excavation.haste] - name = "Hasty Excavator" - description = "This will speed up the excavation process, with HASTE!" - lore1 = "Gain Haste while excavating" - lore2 = "x Levels of haste when you start mining ANY block." - lore = ["Gain Haste while excavating", "x Levels of haste when you start mining ANY block."] +name = "Hasty Excavator" +description = "This will speed up the excavation process, with HASTE!" +lore1 = "Gain Haste while excavating" +lore2 = "x Levels of haste when you start mining ANY block." +lore = ["Gain Haste while excavating", "x Levels of haste when you start mining ANY block."] [excavation.spelunker] - name = "Super-Seeing Spelunker!" - description = "See Ores with your eyes, but through the ground!" - lore1 = "Ore in your offhand, Glowberries in your main hand, and Sneak!" - lore2 = "Block Range: " - lore3 = "Consumes Glowberry on use" - lore = ["Ore in your offhand, Glowberries in your main hand, and Sneak!", "Block Range: ", "Consumes Glowberry on use"] +name = "Super-Seeing Spelunker!" +description = "Hold glow berries in your main hand to see ores through the ground." +lore1 = "Ore in your offhand, Glowberries in your main hand, and Sneak!" +lore2 = "Block Range: " +lore3 = "Consumes Glowberry on use" +lore = ["Ore in your offhand, Glowberries in your main hand, and Sneak!", "Block Range: ", "Consumes Glowberry on use"] [excavation.seismic_ping] - name = "Seismic Ping" - description = "Mining can emit directional pings that point toward nearby ore." - lore1 = "Scan Range" - lore2 = "Ping Chance" - lore3 = "Ping Cooldown" - lore = ["Scan Range", "Ping Chance", "Ping Cooldown"] +name = "Seismic Ping" +description = "Mining can emit directional pings that point toward nearby ore." +lore1 = "Scan Range" +lore2 = "Ping Chance" +lore3 = "Ping Cooldown" +lore = ["Scan Range", "Ping Chance", "Ping Cooldown"] [excavation.drop_to_inventory] - name = "Shovel Drop-To-Inventory" +name = "Shovel Drop-To-Inventory" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "Tackle's overdesigned opulent Leatherman" - lore1 = "Probably the most powerful of many allows you to" - lore2 = "dynamically merge and change tools on the fly, based on your needs." - lore3 = "To merge, shift click an item over another in your inventory." - lore4 = "To unbind tools, Sneak-Drop the item, and it will disassemble." - lore5 = "You can't break tools in this leatherman but you can't use broken tools" - lore6 = "total merge-able items." - lore7 = "You could use five or six tools, or just one!" - lore = ["Probably the most powerful of many allows you to", "dynamically merge and change tools on the fly, based on your needs.", "To merge, shift click an item over another in your inventory.", "To unbind tools, Sneak-Drop the item, and it will disassemble.", "You can't break tools in this leatherman but you can't use broken tools", "total merge-able items.", "You could use five or six tools, or just one!"] +name = "OMNI - T.O.O.L." +description = "Merge your tools into one omni-tool that swaps to the right tool for the job. Shift-click one tool onto another in your inventory to merge; sneak-drop to disassemble." +lore1 = "Merges your tools into a single omni-tool that" +lore2 = "dynamically swaps to the right tool on the fly, based on your needs." +lore3 = "To merge, shift click an item over another in your inventory." +lore4 = "To unbind tools, Sneak-Drop the item, and it will disassemble." +lore5 = "Merged tools never break, but tools at zero durability can't be used" +lore6 = "total merge-able items." +lore7 = "You could use five or six tools, or just one!" +lore = ["Merges your tools into a single omni-tool that", "dynamically swaps to the right tool on the fly, based on your needs.", "To merge, shift click an item over another in your inventory.", "To unbind tools, Sneak-Drop the item, and it will disassemble.", "Merged tools never break, but tools at zero durability can't be used", "total merge-able items.", "You could use five or six tools, or just one!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Growth Aura" - description = "Grow nature around you in an aura" - lore1 = "Block Radius" - lore2 = "Growth Aura Strength" - lore3 = "Food Cost" - lore = ["Block Radius", "Growth Aura Strength", "Food Cost"] +name = "Growth Aura" +description = "Grow nature around you in an aura" +lore1 = "Block Radius" +lore2 = "Growth Aura Strength" +lore3 = "Food Cost" +lore = ["Block Radius", "Growth Aura Strength", "Food Cost"] [herbalism.hippo] - name = "Herbalist's Hippo" - description = "Consuming food, gives you more saturation" - lore1 = "Food) additional saturation points on consumption" - lore = ["Food) additional saturation points on consumption"] +name = "Herbalist's Hippo" +description = "Consuming food, gives you more saturation" +lore1 = "Food) additional saturation points on consumption" +lore = ["Food) additional saturation points on consumption"] [herbalism.myconid] - name = "Herbalist's Myconid" - description = "Gives you the ability to craft Mycelium" - lore1 = "Any Dirt, and a Brown & Red Mushroom will craft Mycelium." - lore = ["Any Dirt, and a Brown & Red Mushroom will craft Mycelium."] +name = "Herbalist's Myconid" +description = "Gives you the ability to craft Mycelium" +lore1 = "Any Dirt, and a Brown & Red Mushroom will craft Mycelium." +lore = ["Any Dirt, and a Brown & Red Mushroom will craft Mycelium."] [herbalism.terralid] - name = "Herbalist's Terralid" - description = "Gives you the ability to craft Grass Blocks" - lore1 = "Three Seeds, over 3 Dirt, will craft 3 Grass Blocks." - lore = ["Three Seeds, over 3 Dirt, will craft 3 Grass Blocks."] +name = "Herbalist's Terralid" +description = "Gives you the ability to craft Grass Blocks" +lore1 = "Three Seeds, over 3 Dirt, will craft 3 Grass Blocks." +lore = ["Three Seeds, over 3 Dirt, will craft 3 Grass Blocks."] [herbalism.cobweb] - name = "Webby Creator" - description = "Gives you the ability to craft Cobwebs in a Crafting Table" - lore1 = "Nine String, will craft a Cobweb." - lore = ["Nine String, will craft a Cobweb."] +name = "Webby Creator" +description = "Gives you the ability to craft Cobwebs in a Crafting Table" +lore1 = "Nine String, will craft a Cobweb." +lore = ["Nine String, will craft a Cobweb."] [herbalism.mushroom_blocks] - name = "Mushroom Maker" - description = "Gives you the ability to craft Mushroom Blocks in a Crafting Table" - lore1 = "Four Mushrooms to make a block, or a block to make a stem." - lore = ["Four Mushrooms to make a block, or a block to make a stem."] +name = "Mushroom Maker" +description = "Gives you the ability to craft Mushroom Blocks in a Crafting Table" +lore1 = "Four Mushrooms to make a block, or a block to make a stem." +lore = ["Four Mushrooms to make a block, or a block to make a stem."] [herbalism.drop_to_inventory] - name = "Hoe Drop-To-Inventory" +name = "Hoe Drop-To-Inventory" [herbalism.hungry_shield] - name = "Hungry Shield" - description = "Take damage to your hunger before your health." - lore1 = "Resisted by Hunger" - lore = ["Resisted by Hunger"] +name = "Hungry Shield" +description = "Take damage to your hunger before your health." +lore1 = "Resisted by Hunger" +lore = ["Resisted by Hunger"] [herbalism.luck] - name = "Herbalist's Luck" - description = "When you break Grass/Flowers, you have a chance to get a random item" - lore0 = "Flowers = Food, and Grass = Seeds" - lore1 = "Chance to get an item from breaking Flowers" - lore2 = "Chance to get an item from breaking Grass" - lore = ["Flowers = Food, and Grass = Seeds", "Chance to get an item from breaking Flowers", "Chance to get an item from breaking Grass"] +name = "Herbalist's Luck" +description = "When you break Grass/Flowers, you have a chance to get a random item" +lore0 = "Flowers = Food, and Grass = Seeds" +lore1 = "Chance to get an item from breaking Flowers" +lore2 = "Chance to get an item from breaking Grass" +lore = ["Flowers = Food, and Grass = Seeds", "Chance to get an item from breaking Flowers", "Chance to get an item from breaking Grass"] [herbalism.replant] - name = "Harvest & Replant" - description = "Right click a crop with a hoe to harvest & replant it." - lore1 = "Blocks Replant Radius" - lore = ["Blocks Replant Radius"] +name = "Harvest & Replant" +description = "Right click a crop with a hoe to harvest & replant it." +lore1 = "Blocks Replant Radius" +lore = ["Blocks Replant Radius"] [herbalism.seed_sower] - name = "Seed Sower" - description = "Sneak-right-click with seeds to plant nearby farmland and soul-sand plots." - lore1 = "Plant Radius" - lore2 = "Max Crops Per Use" - lore3 = "Sowing Cooldown" - lore = ["Plant Radius", "Max Crops Per Use", "Sowing Cooldown"] +name = "Seed Sower" +description = "Sneak-right-click with seeds to plant nearby farmland and soul-sand plots." +lore1 = "Plant Radius" +lore2 = "Max Crops Per Use" +lore3 = "Sowing Cooldown" +lore = ["Plant Radius", "Max Crops Per Use", "Sowing Cooldown"] [herbalism.compost_cascade] - name = "Compost Cascade" - description = "Sneak-right-click a composter to consume nearby drops, mature crop harvests, leaves, and your inventory compostables." - lore1 = "Cascade Radius" - lore2 = "Max Items Processed" - lore3 = "Compost Fill Chance" - lore4 = "Cascade Cooldown" - lore = ["Cascade Radius", "Max Items Processed", "Compost Fill Chance", "Cascade Cooldown"] +name = "Compost Cascade" +description = "Sneak-right-click a composter to consume nearby drops, mature crop harvests, leaves, and your inventory compostables." +lore1 = "Cascade Radius" +lore2 = "Max Items Processed" +lore3 = "Compost Fill Chance" +lore4 = "Cascade Cooldown" +lore = ["Cascade Radius", "Max Items Processed", "Compost Fill Chance", "Cascade Cooldown"] [herbalism.rooted_footing] - name = "Rooted Footing" - description = "Permanent passive: protect farmland and convert part of fall damage into hunger while on natural ground." - lore1 = "Fall Damage Converted" - lore2 = "Food Per Damage" - lore3 = "Prevents Farmland Trample" - lore = ["Fall Damage Converted", "Food Per Damage", "Prevents Farmland Trample"] +name = "Rooted Footing" +description = "Permanent passive: protect farmland and convert part of fall damage into hunger while on natural ground." +lore1 = "Fall Damage Converted" +lore2 = "Food Per Damage" +lore3 = "Prevents Farmland Trample" +lore = ["Fall Damage Converted", "Food Per Damage", "Prevents Farmland Trample"] [herbalism.bee_shepherd] - name = "Bee Shepherd" - description = "Hold flowers near crops to pulse growth and draw nearby bees toward you." - lore1 = "Pulse Radius" - lore2 = "Growth Attempts" - lore3 = "Pulse Cooldown" - lore = ["Pulse Radius", "Growth Attempts", "Pulse Cooldown"] +name = "Bee Shepherd" +description = "Hold flowers near crops to pulse growth and draw nearby bees toward you." +lore1 = "Pulse Radius" +lore2 = "Growth Attempts" +lore3 = "Pulse Cooldown" +lore = ["Pulse Radius", "Growth Attempts", "Pulse Cooldown"] [herbalism.spore_bloom] - name = "Spore Bloom" - description = "Sneak-right-click mycelium with mushrooms to spread controlled bloom patches." - lore1 = "Bloom Attempts" - lore2 = "Bloom Radius" - lore3 = "Bloom Cooldown" - lore = ["Bloom Attempts", "Bloom Radius", "Bloom Cooldown"] +name = "Spore Bloom" +description = "Sneak-right-click mycelium with mushrooms to spread controlled bloom patches." +lore1 = "Bloom Attempts" +lore2 = "Bloom Radius" +lore3 = "Bloom Cooldown" +lore = ["Bloom Attempts", "Bloom Radius", "Bloom Cooldown"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenaline" - description = "Deal more damage the lower health you are (Melee)" - lore1 = "Max Damage" - lore = ["Max Damage"] +name = "Adrenaline" +description = "Deal more damage the lower health you are (Melee)" +lore1 = "Max Damage" +lore = ["Max Damage"] [hunter.penalty] - name = "" - description = "" - lore1 = "You will Gain Poison stacks if you run out of hunger" - lore = ["You will Gain Poison stacks if you run out of hunger"] +name = "Hunter's Penalty" +description = "Hunter boons drain hunger; running out of hunger builds Poison stacks." +lore1 = "Poison stacks if you run out of hunger" +lore = ["Poison stacks if you run out of hunger"] [hunter.drop_to_inventory] - name = "Items Drop-To-Inventory" - description = "When you Kill something / Break a block With a sword it teleports the drops into your inventory" - lore1 = "Whenever an item is dropped from a mob/block you break it goes into your inventory if it can." - lore = ["Whenever an item is dropped from a mob/block you break it goes into your inventory if it can."] +name = "Items Drop-To-Inventory" +description = "Kills and blocks broken with a sword in hand send their drops straight into your inventory." +lore1 = "Whenever an item is dropped from a mob/block you break it goes into your inventory if it can." +lore = ["Whenever an item is dropped from a mob/block you break it goes into your inventory if it can."] [hunter.trophy_skinner] - name = "Trophy Skinner" - description = "Precision kills can yield bonus trophy drops and occasional mob heads." - lore1 = "Bonus Trophy Chance" - lore2 = "Head Drop Chance" - lore3 = "Minimum Ranged Precision Distance" - lore = ["Bonus Trophy Chance", "Head Drop Chance", "Minimum Ranged Precision Distance"] +name = "Trophy Skinner" +description = "Precision kills can yield bonus trophy drops and occasional mob heads." +lore1 = "Bonus Trophy Chance" +lore2 = "Head Drop Chance" +lore3 = "Minimum Ranged Precision Distance" +lore = ["Bonus Trophy Chance", "Head Drop Chance", "Minimum Ranged Precision Distance"] [hunter.invisibility] - name = "Vanishing Step" - description = "When you are struck you gain invisibility, at the cost of hunger" - lore1 = "Gain passive invisibility when struck" - lore2 = "x Invisibility stacks for a 3 seconds on hit" - lore3 = "x Stacking hunger" - lore4 = "Hunger stacks duration and multiplier." - lore5 = "Invisibility duration" - lore = ["Gain passive invisibility when struck", "x Invisibility stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Invisibility duration"] +name = "Vanishing Step" +description = "When you are struck you gain invisibility, at the cost of hunger" +lore1 = "Gain passive invisibility when struck" +lore2 = "x Invisibility stacks for a 3 seconds on hit" +lore3 = "x Stacking hunger" +lore4 = "Hunger stacks duration and multiplier." +lore5 = "Invisibility duration" +lore = ["Gain passive invisibility when struck", "x Invisibility stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Invisibility duration"] [hunter.jump_boost] - name = "Hunter's Heights" - description = "When you are struck you gain jump-boost, at the cost of hunger" - lore1 = "Gain passive jump-boost when struck" - lore2 = "x Jump-Boost stacks for a 3 seconds on hit" - lore3 = "x Stacking hunger" - lore4 = "Hunger stacks duration and multiplier." - lore5 = "Jump-Boost stacks multiplier, not duration." - lore = ["Gain passive jump-boost when struck", "x Jump-Boost stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Jump-Boost stacks multiplier, not duration."] +name = "Hunter's Heights" +description = "When you are struck you gain jump-boost, at the cost of hunger" +lore1 = "Gain passive jump-boost when struck" +lore2 = "x Jump-Boost stacks for a 3 seconds on hit" +lore3 = "x Stacking hunger" +lore4 = "Hunger stacks duration and multiplier." +lore5 = "Jump-Boost stacks multiplier, not duration." +lore = ["Gain passive jump-boost when struck", "x Jump-Boost stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Jump-Boost stacks multiplier, not duration."] [hunter.luck] - name = "Hunter's Luck" - description = "When you are struck you gain luck, at the cost of hunger" - lore1 = "Gain passive luck when struck" - lore2 = "x Luck stacks for a 3 seconds on hit" - lore3 = "x Stacking hunger" - lore4 = "Hunger stacks duration and multiplier." - lore5 = "Luck stacks multiplier, not duration." - lore = ["Gain passive luck when struck", "x Luck stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Luck stacks multiplier, not duration."] +name = "Hunter's Luck" +description = "When you are struck you gain luck, at the cost of hunger" +lore1 = "Gain passive luck when struck" +lore2 = "x Luck stacks for a 3 seconds on hit" +lore3 = "x Stacking hunger" +lore4 = "Hunger stacks duration and multiplier." +lore5 = "Luck stacks multiplier, not duration." +lore = ["Gain passive luck when struck", "x Luck stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Luck stacks multiplier, not duration."] [hunter.regen] - name = "Hunter's Regen" - description = "When you are struck you gain regeneration, at the cost of hunger" - lore1 = "Gain passive regeneration when struck" - lore2 = "x Regeneration stacks for a 3 seconds on hit" - lore3 = "x Stacking hunger" - lore4 = "Hunger stacks duration and multiplier." - lore5 = "Regeneration stacks multiplier, not duration." - lore = ["Gain passive regeneration when struck", "x Regeneration stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Regeneration stacks multiplier, not duration."] +name = "Hunter's Regen" +description = "When you are struck you gain regeneration, at the cost of hunger" +lore1 = "Gain passive regeneration when struck" +lore2 = "x Regeneration stacks for a 3 seconds on hit" +lore3 = "x Stacking hunger" +lore4 = "Hunger stacks duration and multiplier." +lore5 = "Regeneration stacks multiplier, not duration." +lore = ["Gain passive regeneration when struck", "x Regeneration stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Regeneration stacks multiplier, not duration."] [hunter.resistance] - name = "Hunter's Resistance" - description = "When you are struck you gain resistance, at the cost of hunger" - lore1 = "Gain passive resistance when struck" - lore2 = "x Resistance stacks for a 3 seconds on hit" - lore3 = "x Stacking hunger" - lore4 = "Hunger stacks duration and multiplier." - lore5 = "Resistance stacks multiplier, not duration." - lore = ["Gain passive resistance when struck", "x Resistance stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Resistance stacks multiplier, not duration."] +name = "Hunter's Resistance" +description = "When you are struck you gain resistance, at the cost of hunger" +lore1 = "Gain passive resistance when struck" +lore2 = "x Resistance stacks for a 3 seconds on hit" +lore3 = "x Stacking hunger" +lore4 = "Hunger stacks duration and multiplier." +lore5 = "Resistance stacks multiplier, not duration." +lore = ["Gain passive resistance when struck", "x Resistance stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Resistance stacks multiplier, not duration."] [hunter.speed] - name = "Hunter's Speed" - description = "When you are struck you gain speed, at the cost of hunger" - lore1 = "Gain passive speed when struck" - lore2 = "x Speed stacks for a 3 seconds on hit" - lore3 = "x Stacking hunger" - lore4 = "Hunger stacks duration and multiplier." - lore5 = "Speed stacks multiplier, not duration." - lore = ["Gain passive speed when struck", "x Speed stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Speed stacks multiplier, not duration."] +name = "Hunter's Speed" +description = "When you are struck you gain speed, at the cost of hunger" +lore1 = "Gain passive speed when struck" +lore2 = "x Speed stacks for a 3 seconds on hit" +lore3 = "x Stacking hunger" +lore4 = "Hunger stacks duration and multiplier." +lore5 = "Speed stacks multiplier, not duration." +lore = ["Gain passive speed when struck", "x Speed stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Speed stacks multiplier, not duration."] [hunter.strength] - name = "Hunter's Strength" - description = "When you are struck you gain strength, at the cost of hunger" - lore1 = "Gain passive strength when struck" - lore2 = "x Strength stacks for a 3 seconds on hit" - lore3 = "x Stacking hunger" - lore4 = "Hunger stacks duration and multiplier." - lore5 = "Strength stacks multiplier, not duration." - lore = ["Gain passive strength when struck", "x Strength stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Strength stacks multiplier, not duration."] +name = "Hunter's Strength" +description = "When you are struck you gain strength, at the cost of hunger" +lore1 = "Gain passive strength when struck" +lore2 = "x Strength stacks for a 3 seconds on hit" +lore3 = "x Stacking hunger" +lore4 = "Hunger stacks duration and multiplier." +lore5 = "Strength stacks multiplier, not duration." +lore = ["Gain passive strength when struck", "x Strength stacks for a 3 seconds on hit", "x Stacking hunger", "Hunger stacks duration and multiplier.", "Strength stacks multiplier, not duration."] # nether [nether] [nether.skull_toss] - name = "Wither Skull Throw" - description1 = "Unleash your inner Wither by using" - description2 = "someones" - description3 = "head." - lore1 = "Seconds of cooldown between skull tosses." - lore2 = "Using Wither Skull: Toss a " - lore3 = "Wither Skull" - lore4 = "exploding on impact." - lore = ["Seconds of cooldown between skull tosses.", "Using Wither Skull: Toss a ", "Wither Skull", "exploding on impact."] +name = "Wither Skull Throw" +description1 = "Unleash your inner Wither by using" +description2 = "someone's" +description3 = "head." +lore1 = "Seconds of cooldown between skull tosses." +lore2 = "Using Wither Skull: Toss a " +lore3 = "Wither Skull" +lore4 = "exploding on impact." +lore = ["Seconds of cooldown between skull tosses.", "Using Wither Skull: Toss a ", "Wither Skull", "exploding on impact."] [nether.wither_resist] - name = "Wither Resistance" - description = "Resists withering through the power of Netherite." - lore1 = "chance to negate withering (per piece)." - lore2 = "Passive: Wearing Netherite Armor has a chance to negate " - lore3 = "withering." - lore = ["chance to negate withering (per piece).", "Passive: Wearing Netherite Armor has a chance to negate ", "withering."] +name = "Wither Resistance" +description = "Resists withering through the power of Netherite." +lore1 = "chance to negate withering (per piece)." +lore2 = "Passive: Wearing Netherite Armor has a chance to negate " +lore3 = "withering." +lore = ["chance to negate withering (per piece).", "Passive: Wearing Netherite Armor has a chance to negate ", "withering."] [nether.fire_resist] - name = "Fire Resistance" - description = "Resists fire by hardening your skin." - lore1 = "chance to negate the burning effect!" - lore = ["chance to negate the burning effect!"] +name = "Fire Resistance" +description = "Resists fire by hardening your skin." +lore1 = "chance to negate the burning effect!" +lore = ["chance to negate the burning effect!"] [nether.lava_walker] - name = "Lava Walker" - description = "Stride over lava in the Nether at the cost of hunger." - lore1 = "Lava Stride Speed" - lore2 = "Hunger Cost" - lore = ["Lava Stride Speed", "Hunger Cost"] +name = "Lava Walker" +description = "Stride over lava in the Nether at the cost of hunger." +lore1 = "Lava Stride Speed" +lore2 = "Hunger Cost" +lore = ["Lava Stride Speed", "Hunger Cost"] [nether.ghast_ward] - name = "Ghast Ward" - description = "Harden against ghast blasts and wither-skeleton ranged pressure in the Nether." - lore1 = "Ghast Projectile Reduction" - lore2 = "Explosion Reduction" - lore3 = "Wither Skeleton Projectile Reduction" - lore = ["Ghast Projectile Reduction", "Explosion Reduction", "Wither Skeleton Projectile Reduction"] +name = "Ghast Ward" +description = "Harden against ghast blasts and wither-skeleton ranged pressure in the Nether." +lore1 = "Ghast Projectile Reduction" +lore2 = "Explosion Reduction" +lore3 = "Wither Skeleton Projectile Reduction" +lore = ["Ghast Projectile Reduction", "Explosion Reduction", "Wither Skeleton Projectile Reduction"] [nether.blaze_leech] - name = "Blaze Leech" - description = "Fire interactions can trigger brief hunger and regeneration gains." - lore1 = "Leech Trigger Chance" - lore2 = "Regen Burst Duration" - lore3 = "Food Restored per Proc" - lore = ["Leech Trigger Chance", "Regen Burst Duration", "Food Restored per Proc"] +name = "Blaze Leech" +description = "Fire interactions can trigger brief hunger and regeneration gains." +lore1 = "Leech Trigger Chance" +lore2 = "Regen Burst Duration" +lore3 = "Food Restored per Proc" +lore = ["Leech Trigger Chance", "Regen Burst Duration", "Food Restored per Proc"] [nether.piglin_broker] - name = "Piglin Broker" - description = "Nearby barters can grant extra rolls and occasional premium bonus items." - lore1 = "Extra Roll Chance" - lore2 = "Rare Bonus Chance" - lore = ["Extra Roll Chance", "Rare Bonus Chance"] +name = "Piglin Broker" +description = "Nearby barters can grant extra rolls and occasional premium bonus items." +lore1 = "Extra Roll Chance" +lore2 = "Rare Bonus Chance" +lore = ["Extra Roll Chance", "Rare Bonus Chance"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Autosmelt" - description = "Allows you to smelt mined Vanilla ores" - lore1 = "Ores that can be smelted are smelted automatically" - lore2 = "% chance for an extra" - lore = ["Ores that can be smelted are smelted automatically", "% chance for an extra"] +name = "Autosmelt" +description = "Allows you to smelt mined Vanilla ores" +lore1 = "Ores that can be smelted are smelted automatically" +lore2 = "% chance for an extra" +lore = ["Ores that can be smelted are smelted automatically", "% chance for an extra"] [pickaxe.chisel] - name = "Ore Chisel" - description = "Right Click Ores to Chisel more ore out of them, at a severe durability cost." - lore1 = "Chance to Drop" - lore2 = "Tool Wear" - lore = ["Chance to Drop", "Tool Wear"] +name = "Ore Chisel" +description = "Right Click Ores to Chisel more ore out of them, at a severe durability cost." +lore1 = "Chance to Drop" +lore2 = "Tool Wear" +lore = ["Chance to Drop", "Tool Wear"] [pickaxe.drop_to_inventory] - name = "Pickaxe Drop-To-Inventory" - description = "When you break a block it teleports the item into your inventory" - lore1 = "Whenever an item is dropped from a block you break it goes into your inventory if it can." - lore = ["Whenever an item is dropped from a block you break it goes into your inventory if it can."] +name = "Pickaxe Drop-To-Inventory" +description = "Blocks you break send their drops straight into your inventory." +lore1 = "Whenever an item is dropped from a block you break it goes into your inventory if it can." +lore = ["Whenever an item is dropped from a block you break it goes into your inventory if it can."] [pickaxe.silk_spawner] - name = "Pickaxe Silk-Spawner" - description = "Makes Spawners drop when broken" - lore1 = "Makes Spawners breakable with silk touch." - lore2 = "Makes Spawners breakable while sneaking." - lore = ["Makes Spawners breakable with silk touch.", "Makes Spawners breakable while sneaking."] +name = "Pickaxe Silk-Spawner" +description = "Makes Spawners drop when broken" +lore1 = "Makes Spawners breakable with silk touch." +lore2 = "Makes Spawners breakable while sneaking." +lore = ["Makes Spawners breakable with silk touch.", "Makes Spawners breakable while sneaking."] [pickaxe.vein_miner] - name = "Veinminer" - description = "Allows you to break blocks in a Vein/Cluster of Vanilla ores" - lore1 = "Sneak, and mine ORES" - lore2 = "range of vein-mining" - lore3 = "This skill does NOT group all drops together!" - lore = ["Sneak, and mine ORES", "range of vein-mining", "This skill does NOT group all drops together!"] +name = "Veinminer" +description = "Allows you to break blocks in a Vein/Cluster of Vanilla ores" +lore1 = "Sneak, and mine ORES" +lore2 = "range of vein-mining" +lore3 = "This skill does NOT group all drops together!" +lore = ["Sneak, and mine ORES", "range of vein-mining", "This skill does NOT group all drops together!"] [pickaxe.quarry_sense] - name = "Quarry Sense" - description = "Sneak-right-click a block with an iron+ pickaxe to reveal nearby ores using temporary glowing markers." - lore1 = "Ore Scan Radius" - lore2 = "Durability Cost (% of Max Durability)" - lore3 = "Sense Cooldown" - lore = ["Ore Scan Radius", "Durability Cost (% of Max Durability)", "Sense Cooldown"] +name = "Quarry Sense" +description = "Sneak-right-click a block with an iron+ pickaxe to reveal nearby ores using temporary glowing markers." +lore1 = "Ore Scan Radius" +lore2 = "Durability Cost (% of Max Durability)" +lore3 = "Sense Cooldown" +lore = ["Ore Scan Radius", "Durability Cost (% of Max Durability)", "Sense Cooldown"] # ranged [ranged] [ranged.arrow_recovery] - name = "Arrow Recovery" - description = "Recover Arrows after you have killed an enemy." - lore1 = "Chance to Recover Arrows on Hit/Kill" - lore2 = "Chance: " - lore = ["Chance to Recover Arrows on Hit/Kill", "Chance: "] +name = "Arrow Recovery" +description = "Recover Arrows after you have killed an enemy." +lore1 = "Chance to Recover Arrows on Hit/Kill" +lore2 = "Chance: " +lore = ["Chance to Recover Arrows on Hit/Kill", "Chance: "] [ranged.web_shot] - name = "Web Snare" - description = "Surround cobwebs around your target when you hit them!" - lore1 = "8 Cobwebs around a Snowball, and throw!" - lore2 = "seconds of a cage, roughly." - lore = ["8 Cobwebs around a Snowball, and throw!", "seconds of a cage, roughly."] +name = "Web Snare" +description = "Surround cobwebs around your target when you hit them!" +lore1 = "8 Cobwebs around a Snowball, and throw!" +lore2 = "seconds of a cage, roughly." +lore = ["8 Cobwebs around a Snowball, and throw!", "seconds of a cage, roughly."] [ranged.force_shot] - name = "Force Shot" - description = "Shoot projectiles further, faster!" - advancementname = "Long Shot" - advancementlore = "Land a shot from over 30 blocks away!" - lore1 = "Projectile Speed" - lore = ["Projectile Speed"] +name = "Force Shot" +description = "Shoot projectiles further, faster!" +advancementname = "Long Shot" +advancementlore = "Land a shot from over 30 blocks away!" +lore1 = "Projectile Speed" +lore = ["Projectile Speed"] [ranged.trajectory_sight] - name = "Trajectory Sight" - description = "Sneak or draw a ranged weapon to preview projectile flight." - lore1 = "Prediction Range" - lore2 = "Prediction Detail" - lore = ["Prediction Range", "Prediction Detail"] +name = "Trajectory Sight" +description = "Sneak or draw a ranged weapon to preview projectile flight." +lore1 = "Prediction Range" +lore2 = "Prediction Detail" +lore = ["Prediction Range", "Prediction Detail"] [ranged.lunge_shot] - name = "Lunge Shot" - description = "While falling your arrows toss you in a random direction" - lore1 = "Random Burst Speed" - lore = ["Random Burst Speed"] +name = "Lunge Shot" +description = "While falling your arrows toss you in a random direction" +lore1 = "Random Burst Speed" +lore = ["Random Burst Speed"] [ranged.arrow_piercing] - name = "Arrow Piercing" - description = "Adds Piercing to projectiles! Shoot through things!" - lore1 = "Pierce Targets" - lore = ["Pierce Targets"] +name = "Arrow Piercing" +description = "Adds Piercing to projectiles! Shoot through things!" +lore1 = "Pierce Targets" +lore = ["Pierce Targets"] [ranged.floaters] - name = "Floaters" - description = "Projectiles have a chance to apply levitation and hold targets in the air." - lore1 = "Levitation Chance" - lore2 = "Levitation Duration" - lore3 = "Levitation Strength" - lore = ["Levitation Chance", "Levitation Duration", "Levitation Strength"] +name = "Floaters" +description = "Projectiles have a chance to apply levitation and hold targets in the air." +lore1 = "Levitation Chance" +lore2 = "Levitation Duration" +lore3 = "Levitation Strength" +lore = ["Levitation Chance", "Levitation Duration", "Levitation Strength"] [ranged.pinning_shot] - name = "Pinning Shot" - description = "Any player-fired projectile can pin targets with heavy slowness." - lore1 = "Pin Chance" - lore2 = "Pin Duration" - lore3 = "Reapply Cooldown" - lore = ["Pin Chance", "Pin Duration", "Reapply Cooldown"] +name = "Pinning Shot" +description = "Any player-fired projectile can pin targets with heavy slowness." +lore1 = "Pin Chance" +lore2 = "Pin Duration" +lore3 = "Reapply Cooldown" +lore = ["Pin Chance", "Pin Duration", "Reapply Cooldown"] [ranged.ricochet_bolt] - name = "Ricochet Bolt" - description = "Arrows ricochet off solid blocks with chained bounces." - lore1 = "Max Ricochets" - lore2 = "Speed Bonus Per Ricochet" - lore3 = "Bonus Damage Per Ricochet" - lore = ["Max Ricochets", "Speed Bonus Per Ricochet", "Bonus Damage Per Ricochet"] +name = "Ricochet Bolt" +description = "Projectiles ricochet off solid blocks with chained bounces." +lore1 = "Max Ricochets" +lore2 = "Speed Bonus Per Ricochet" +lore3 = "Bonus Damage Per Ricochet" +lore = ["Max Ricochets", "Speed Bonus Per Ricochet", "Bonus Damage Per Ricochet"] # rift [rift] [rift.remote_access] - name = "Remote Access" - description = "Pull from the void, and get into a marked container." - lore1 = "Enderpearl + Compass = Reliquary Portkey" - lore2 = "This item allows you to access containers remotely" - lore3 = "Once crafted look at item to see usage" - notcontainer = "That's not a container" - lore = ["Enderpearl + Compass = Reliquary Portkey", "This item allows you to access containers remotely", "Once crafted look at item to see usage"] +name = "Remote Access" +description = "Craft a Reliquary Portkey (ender pearl + compass), bind it to a container, and use it to open that container from anywhere." +lore1 = "Ender Pearl + Compass = Reliquary Portkey" +lore2 = "This item allows you to access containers remotely" +lore3 = "Once crafted look at item to see usage" +notcontainer = "That's not a container" +lore = ["Ender Pearl + Compass = Reliquary Portkey", "This item allows you to access containers remotely", "Once crafted look at item to see usage"] [rift.blink] - name = "Rift Blink" - description = "Short ranged instant teleportation, Just a blink away!" - lore1 = "Blocks on blink (2x Vertical)" - lore2 = "While Sprinting: Double tap Jump to " - lore3 = "Blink" - lore = ["Blocks on blink (2x Vertical)", "While Sprinting: Double tap Jump to ", "Blink"] +name = "Rift Blink" +description = "Short-range instant teleport. Double-tap jump while sprinting, right-click while sprinting, or click with an ender pearl in hand to blink. Each blink has a chance to consume an ender pearl." +lore1 = "Blocks on blink (2x Vertical)" +lore2 = "While Sprinting: Double tap Jump to " +lore3 = "Blink" +lore = ["Blocks on blink (2x Vertical)", "While Sprinting: Double tap Jump to ", "Blink"] +lore_cost_pearl = "chance to consume an ender pearl on blink" [rift.chest] - name = "Easy Enderchest" - description = "Open an enderchest by Left-clicking it in your hand." - lore1 = "Click an Enderchest in your hand to open (Just dont place it)" - lore = ["Click an Enderchest in your hand to open (Just dont place it)"] +name = "Easy Enderchest" +description = "Click while holding an ender chest to open your ender chest without placing it." +lore1 = "Click an Ender Chest in your hand to open it (just don't place it)" +lore = ["Click an Ender Chest in your hand to open it (just don't place it)"] [rift.descent] - name = "Anti-Levitation" - description = "Are you tired of being stuck in the air? This is the skill for you!" - lore1 = "Just Sneak to descend, and you will fall at a less than normal rate!" - lore2 = "Cooldown:" - lore = ["Just Sneak to descend, and you will fall at a less than normal rate!", "Cooldown:"] +name = "Anti-Levitation" +description = "Tap sneak while levitating to cancel Levitation and drift down gently with Slow Falling." +lore1 = "Just Sneak to descend, and you will fall at a less than normal rate!" +lore2 = "Cooldown:" +lore = ["Just Sneak to descend, and you will fall at a less than normal rate!", "Cooldown:"] [rift.gate] - name = "Rift Gate" - description = "Teleport to a marked location." - lore1 = "CRAFTING: Emerald + Amethyst shard + Ender Pearl" - lore2 = "Read before using!" - lore3 = "5s delay, " - lore4 = "you can die while you are in this animation" - lore = ["CRAFTING: Emerald + Amethyst shard + Ender Pearl", "Read before using!", "5s delay, ", "you can die while you are in this animation"] +name = "Rift Gate" +description = "Sneak-left-click with an Eye of Ender to bind it to your location, then right-click to teleport back after a 5 second channel." +lore1 = "CRAFTING: Emerald + Amethyst shard + Ender Pearl" +lore2 = "Read before using!" +lore3 = "5s delay, " +lore4 = "you can die while you are in this animation" +lore = ["CRAFTING: Emerald + Amethyst shard + Ender Pearl", "Read before using!", "5s delay, ", "you can die while you are in this animation"] [rift.resist] - name = "Rift Resistance" - description = "Gain Resistance when using Ender Items & Abilities" - lore1 = "+ Passive: Provides resistance when you use rift abilities, or Ender Items" - lore2 = "NOT Including Portable Enderchest, only things you can Consume" - lore = ["+ Passive: Provides resistance when you use rift abilities, or Ender Items", "NOT Including Portable Enderchest, only things you can Consume"] +name = "Rift Resistance" +description = "Gain Resistance when using Ender Items & Abilities" +lore1 = "+ Passive: Provides resistance when you use rift abilities, or Ender Items" +lore2 = "NOT Including Portable Enderchest, only things you can Consume" +lore = ["+ Passive: Provides resistance when you use rift abilities, or Ender Items", "NOT Including Portable Enderchest, only things you can Consume"] [rift.visage] - name = "Rift Visage" - description = "Prevents Endermen from becoming aggressive if you have Enderpearls in your inventory." - lore1 = "Endermen will not become aggressive if you have Enderpearls in your inventory." - lore = ["Endermen will not become aggressive if you have Enderpearls in your inventory."] +name = "Rift Visage" +description = "Prevents Endermen from becoming aggressive if you have Ender Pearls in your inventory." +lore1 = "Endermen will not become aggressive if you have Ender Pearls in your inventory." +lore = ["Endermen will not become aggressive if you have Ender Pearls in your inventory."] [rift.void_magnet] - name = "Void Magnet" - description = "Sneak to pull nearby item drops into your ender chest first, then inventory overflow." - lore1 = "Magnet Radius" - lore2 = "Max Items Per Pulse" - lore3 = "Pulse Delay" - lore = ["Magnet Radius", "Max Items Per Pulse", "Pulse Delay"] +name = "Void Magnet" +description = "Sneak to pull nearby item drops into your ender chest first, then inventory overflow." +lore1 = "Magnet Radius" +lore2 = "Max Items Per Pulse" +lore3 = "Pulse Delay" +lore = ["Magnet Radius", "Max Items Per Pulse", "Pulse Delay"] [rift.inflated_pocket_dimension] - name = "Inflated Pocket Dimension" - description = "Empty-hand right-click a block to pull matching stacks from your ender chest; sneak-drop to store items into it." - lore1 = "Right-click block to pull stack" - lore2 = "Building auto-refill from ender chest" - lore3 = "Sneak-drop stores item in ender chest" - lore = ["Right-click block to pull stack", "Building auto-refill from ender chest", "Sneak-drop stores item in ender chest"] +name = "Inflated Pocket Dimension" +description = "Empty-hand right-click a block to pull matching stacks from your ender chest; sneak-drop to store items into it." +lore1 = "Right-click block to pull stack" +lore2 = "Building auto-refill from ender chest" +lore3 = "Sneak-drop stores item in ender chest" +lore = ["Right-click block to pull stack", "Building auto-refill from ender chest", "Sneak-drop stores item in ender chest"] [rift.ender_taglock] - name = "Ender Taglock" - description = "Sneak-left-click entities with an ender pearl to bind them, then throw the tagged pearl to relocate only that target. The thrower is never teleported." - lore1 = "Level 1: Passive and hostile mobs" - lore2 = "Level 2: Villagers and large targets" - lore3 = "Level 3: Any target, including players" - lore4 = "Tagged Pearl Throw Cooldown" - lore = ["Level 1: Passive and hostile mobs", "Level 2: Villagers and large targets", "Level 3: Any target, including players", "Tagged Pearl Throw Cooldown"] +name = "Ender Taglock" +description = "Sneak-left-click entities with an ender pearl to bind them, then throw the tagged pearl to relocate only that target. The thrower is never teleported." +lore1 = "Level 1: Passive and hostile mobs" +lore2 = "Level 2: Villagers and large targets" +lore3 = "Level 3: Any target, including players" +lore4 = "Tagged Pearl Throw Cooldown" +lore = ["Level 1: Passive and hostile mobs", "Level 2: Villagers and large targets", "Level 3: Any target, including players", "Tagged Pearl Throw Cooldown"] # seaborn [seaborn] [seaborn.oxygen] - name = "Organic Oxygen Tank" - description = "Hold more oxygen in your tiny lungs!" - lore1 = "Oxygen Capacity Increase" - lore = ["Oxygen Capacity Increase"] +name = "Organic Oxygen Tank" +description = "Hold more oxygen in your tiny lungs!" +lore1 = "Oxygen Capacity Increase" +lore = ["Oxygen Capacity Increase"] [seaborn.fishers_fantasy] - name = "Fisher's Fantasy" - description = "Earn more XP from fishing, and get more fish!" - lore1 = "For each level there is a chance to get more XP and Fish!" - lore = ["For each level there is a chance to get more XP and Fish!"] +name = "Fisher's Fantasy" +description = "Earn more XP from fishing, and get more fish!" +lore1 = "For each level there is a chance to get more XP and Fish!" +lore = ["For each level there is a chance to get more XP and Fish!"] [seaborn.haste] - name = "Turtle Miner" - description = "While mining underwater you gain haste!" - lore1 = "Haste 3 is applied underwater while mining (stacks with AquaAffinity) after your water breathing effect wears off!" - lore = ["Haste 3 is applied underwater while mining (stacks with AquaAffinity) after your water breathing effect wears off!"] +name = "Turtle Miner" +description = "While mining underwater you gain haste!" +lore1 = "Haste 3 is applied underwater while mining (stacks with AquaAffinity) after your water breathing effect wears off!" +lore = ["Haste 3 is applied underwater while mining (stacks with AquaAffinity) after your water breathing effect wears off!"] [seaborn.night_vision] - name = "Turtle's Vision" - description = "While underwater, you gain Night Vision" - lore1 = "Simply gain Night Vision while underwater after your water breathing effect wears off!" - lore = ["Simply gain Night Vision while underwater after your water breathing effect wears off!"] +name = "Turtle's Vision" +description = "While underwater, you gain Night Vision" +lore1 = "Simply gain Night Vision while underwater after your water breathing effect wears off!" +lore = ["Simply gain Night Vision while underwater after your water breathing effect wears off!"] [seaborn.dolphin_grace] - name = "Dolphin's Grace" - description = "Swim like a dolphin, without the dolphins" - lore1 = "+ Passive: gain " - lore2 = "x speed (dolphins grace)" - lore3 = "precision german engineeeri- wait that's not right... Not Compatible with Depth Strider" - lore = ["+ Passive: gain ", "x speed (dolphins grace)", "precision german engineeeri- wait that's not right... Not Compatible with Depth Strider"] +name = "Dolphin's Grace" +description = "Swim like a dolphin, without the dolphins" +lore1 = "+ Passive: gain " +lore2 = "x speed (dolphins grace)" +lore3 = "precision german engineeeri- wait that's not right... Not Compatible with Depth Strider" +lore = ["+ Passive: gain ", "x speed (dolphins grace)", "precision german engineeeri- wait that's not right... Not Compatible with Depth Strider"] [seaborn.tidecaller] - name = "Tidecaller" - description = "Sneak while it is raining on you to surge forward in a water-themed blink." - lore1 = "Rain Blink Distance" - lore2 = "Rain Blink Cooldown" - lore = ["Rain Blink Distance", "Rain Blink Cooldown"] +name = "Tidecaller" +description = "Sneak while it is raining on you to surge forward in a water-themed blink." +lore1 = "Rain Blink Distance" +lore2 = "Rain Blink Cooldown" +lore = ["Rain Blink Distance", "Rain Blink Cooldown"] [seaborn.pressure_diver] - name = "Pressure Diver" - description = "Gain depth-based protection underwater and partially suppress mining fatigue pressure." - lore1 = "Minimum Depth Requirement" - lore2 = "Depth Damage Reduction" - lore3 = "Mining Fatigue Reduction Chance" - lore = ["Minimum Depth Requirement", "Depth Damage Reduction", "Mining Fatigue Reduction Chance"] +name = "Pressure Diver" +description = "Gain depth-based protection underwater and partially suppress mining fatigue pressure." +lore1 = "Minimum Depth Requirement" +lore2 = "Depth Damage Reduction" +lore3 = "Mining Fatigue Reduction Chance" +lore = ["Minimum Depth Requirement", "Depth Damage Reduction", "Mining Fatigue Reduction Chance"] # stealth [stealth] [stealth.ghost_armor] - name = "Ghost's Armor" - description = "Slow building armor when not taking damage, Lasts for 1 hit" - lore1 = "Max Armor" - lore2 = "Speed" - lore = ["Max Armor", "Speed"] +name = "Ghost's Armor" +description = "Slowly builds bonus armor while you avoid damage; it is consumed by the next hit." +lore1 = "Max Armor" +lore2 = "Speed" +lore = ["Max Armor", "Speed"] [stealth.night_vision] - name = "Stealth Vision" - description = "Gain night vision while sneaking" - lore1 = "Gain a burst of " - lore2 = "night vision" - lore3 = "while sneaking" - lore = ["Gain a burst of ", "night vision", "while sneaking"] +name = "Stealth Vision" +description = "Gain night vision while sneaking" +lore1 = "Gain a burst of " +lore2 = "night vision" +lore3 = "while sneaking" +lore = ["Gain a burst of ", "night vision", "while sneaking"] [stealth.snatch] - name = "Item Snatch" - description = "Snatch Dropped items instantly while sneaking!" - lore1 = "Snatch Radius" - lore = ["Snatch Radius"] +name = "Item Snatch" +description = "Snatch Dropped items instantly while sneaking!" +lore1 = "Snatch Radius" +lore = ["Snatch Radius"] [stealth.speed] - name = "Sneak Speed" - description = "Gain speed while sneaking" - lore1 = "Sneaking Speed" - lore = ["Sneaking Speed"] +name = "Sneak Speed" +description = "Gain speed while sneaking" +lore1 = "Sneaking Speed" +lore = ["Sneaking Speed"] [stealth.ender_veil] - name = "Enderveil" - description = "No more Pumpkins to prevent Enderman attacks" - lore1 = "Prevent enderman attacks while sneaking" - lore2 = "Prevent all enderman attacks" - lore = ["Prevent enderman attacks while sneaking", "Prevent all enderman attacks"] +name = "Enderveil" +description = "Look at Endermen freely - prevents Enderman aggression without wearing a pumpkin." +lore1 = "Prevent enderman attacks while sneaking" +lore2 = "Prevent all enderman attacks" +lore = ["Prevent enderman attacks while sneaking", "Prevent all enderman attacks"] [stealth.silent_step] - name = "Silent Step" - description = "Sneaking suppresses detection, dims your vision while hidden, and grants backstab damage on unseen hits." - lore1 = "Mob Detection Suppression Radius" - lore2 = "Mob Backstab Damage Bonus" - lore3 = "Player Backstab Damage Bonus" - lore = ["Mob Detection Suppression Radius", "Mob Backstab Damage Bonus", "Player Backstab Damage Bonus"] +name = "Silent Step" +description = "Sneaking suppresses detection, dims your vision while hidden, and grants backstab damage on unseen hits." +lore1 = "Mob Detection Suppression Radius" +lore2 = "Mob Backstab Damage Bonus" +lore3 = "Player Backstab Damage Bonus" +lore = ["Mob Detection Suppression Radius", "Mob Backstab Damage Bonus", "Player Backstab Damage Bonus"] [stealth.shadow_decoy] - name = "Shadow Decoy" - description = "Stop sneaking to spawn a decoy that pulls nearby mob aggression." - lore1 = "Decoy Duration" - lore2 = "Decoy Attraction Radius" - lore3 = "Decoy Cooldown" - lore = ["Decoy Duration", "Decoy Attraction Radius", "Decoy Cooldown"] +name = "Shadow Decoy" +description = "Stop sneaking to spawn a decoy that pulls nearby mob aggression." +lore1 = "Decoy Duration" +lore2 = "Decoy Attraction Radius" +lore3 = "Decoy Cooldown" +lore = ["Decoy Duration", "Decoy Attraction Radius", "Decoy Cooldown"] # sword [sword] [sword.machete] - name = "Machete" - description = "Cut through foliage with ease!" - lore1 = "Slash Radius" - lore2 = "Chop Cooldown" - lore3 = "Tool Wear" - lore = ["Slash Radius", "Chop Cooldown", "Tool Wear"] +name = "Machete" +description = "Cut through foliage with ease!" +lore1 = "Slash Radius" +lore2 = "Chop Cooldown" +lore3 = "Tool Wear" +lore = ["Slash Radius", "Chop Cooldown", "Tool Wear"] [sword.bloody_blade] - name = "Bloody Blade" - description = "Strikes with your sword, cause Bleeding!" - lore1 = "Striking a Living entity with your Sword causes Bleeding" - lore2 = "Bleed Duration" - lore3 = "Bleed Cooldown" - lore = ["Striking a Living entity with your Sword causes Bleeding", "Bleed Duration", "Bleed Cooldown"] +name = "Bloody Blade" +description = "Strikes with your sword, cause Bleeding!" +lore1 = "Striking a Living entity with your Sword causes Bleeding" +lore2 = "Bleed Duration" +lore3 = "Bleed Cooldown" +lore = ["Striking a Living entity with your Sword causes Bleeding", "Bleed Duration", "Bleed Cooldown"] [sword.poisoned_blade] - name = "Poisoned Blade" - description = "Strikes with your sword, cause Poison!" - lore1 = "Striking a Living entity with your Sword causes Poison" - lore2 = "Poison Duration" - lore3 = "Poison Cooldown" - lore = ["Striking a Living entity with your Sword causes Poison", "Poison Duration", "Poison Cooldown"] +name = "Poisoned Blade" +description = "Strikes with your sword, cause Poison!" +lore1 = "Striking a Living entity with your Sword causes Poison" +lore2 = "Poison Duration" +lore3 = "Poison Cooldown" +lore = ["Striking a Living entity with your Sword causes Poison", "Poison Duration", "Poison Cooldown"] [sword.dual_wield] - name = "Dual Wield Stance" - description = "Holding items in both hands grants bonus melee damage. Matching items grant the higher bonus." - lore1 = "Matching Item Bonus" - lore2 = "Mixed Item Bonus" - lore = ["Matching Item Bonus", "Mixed Item Bonus"] +name = "Dual Wield Stance" +description = "Holding items in both hands grants bonus melee damage. Matching items grant the higher bonus." +lore1 = "Matching Item Bonus" +lore2 = "Mixed Item Bonus" +lore = ["Matching Item Bonus", "Mixed Item Bonus"] [sword.executioners_edge] - name = "Executioner's Edge" - description = "Sword strikes deal extra damage to low-health targets." - lore1 = "Bonus Damage" - lore2 = "Health Threshold" - lore = ["Bonus Damage", "Health Threshold"] +name = "Executioner's Edge" +description = "Sword strikes deal extra damage to low-health targets." +lore1 = "Bonus Damage" +lore2 = "Health Threshold" +lore = ["Bonus Damage", "Health Threshold"] [sword.riposte_window] - name = "Riposte Window" - description = "Blocking with a shield arms a short riposte for your next strike." - lore1 = "Riposte Window" - lore2 = "Riposte Damage Bonus" - lore = ["Riposte Window", "Riposte Damage Bonus"] +name = "Riposte Window" +description = "Blocking with a shield arms a short riposte for your next strike." +lore1 = "Riposte Window" +lore2 = "Riposte Damage Bonus" +lore = ["Riposte Window", "Riposte Damage Bonus"] [sword.crimson_cyclone] - name = "Crimson Cyclone" - description = "Land a sword crit to unleash a bleeding area slash around your target." - lore1 = "Cyclone Radius" - lore2 = "Cyclone Damage" - lore3 = "Cyclone Cooldown" - lore = ["Cyclone Radius", "Cyclone Damage", "Cyclone Cooldown"] +name = "Crimson Cyclone" +description = "Land a sword crit to unleash a bleeding area slash around your target." +lore1 = "Cyclone Radius" +lore2 = "Cyclone Damage" +lore3 = "Cyclone Cooldown" +lore = ["Cyclone Radius", "Cyclone Damage", "Cyclone Cooldown"] # taming [taming] [taming.damage] - name = "Tame Damage" - description = "Increase your tamed animal damage dealt." - lore1 = "Increased Damage" - lore = ["Increased Damage"] +name = "Tame Damage" +description = "Increase your tamed animal damage dealt." +lore1 = "Increased Damage" +lore = ["Increased Damage"] [taming.health] - name = "Tame Health" - description = "Increase your tamed animal health." - lore1 = "Increased Health" - lore = ["Increased Health"] +name = "Tame Health" +description = "Increase your tamed animal health." +lore1 = "Increased Health" +lore = ["Increased Health"] [taming.regeneration] - name = "Tame Regeneration" - description = "Increase your tamed animal regeneration." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Tame Regeneration" +description = "Increase your tamed animal regeneration." +lore1 = "HP/s" +lore = ["HP/s"] [taming.pack_leader_aura] - name = "Pack Leader Aura" - description = "Nearby tamed companions gain speed and regeneration near their owner." - lore1 = "Aura Radius" - lore2 = "Aura Strength" - lore = ["Aura Radius", "Aura Strength"] +name = "Pack Leader Aura" +description = "Nearby tamed companions gain speed and regeneration near their owner." +lore1 = "Aura Radius" +lore2 = "Aura Strength" +lore = ["Aura Radius", "Aura Strength"] [taming.beast_recall] - name = "Beast Recall" - description = "Sneak-right-click with a lead to recall your nearest tamed companion to a safe nearby spot." - lore1 = "Recall Radius" - lore2 = "Recall Cooldown" - lore = ["Recall Radius", "Recall Cooldown"] +name = "Beast Recall" +description = "Sneak-right-click with a lead to recall your nearest tamed companion to a safe nearby spot. Each recall costs hunger." +lore1 = "Recall Radius" +lore2 = "Recall Cooldown" +lore = ["Recall Radius", "Recall Cooldown"] +lore_cost_hunger = "Hunger cost per recall" [taming.shared_pain] - name = "Shared Pain" - description = "Redirect a portion of your tamed companion's damage to yourself." - lore1 = "Redirected Damage" - lore2 = "Owner Health Floor" - lore = ["Redirected Damage", "Owner Health Floor"] +name = "Shared Pain" +description = "Redirect a portion of your tamed companion's damage to yourself." +lore1 = "Redirected Damage" +lore2 = "Owner Health Floor" +lore = ["Redirected Damage", "Owner Health Floor"] [taming.mounted_tactics] - name = "Mounted Tactics" - description = "Gain mount-specific combat and handling bonuses while riding." - lore1 = "Mounted Damage Bonus" - lore2 = "Mounted Damage Reduction" - lore = ["Mounted Damage Bonus", "Mounted Damage Reduction"] +name = "Mounted Tactics" +description = "Gain mount-specific combat and handling bonuses while riding." +lore1 = "Mounted Damage Bonus" +lore2 = "Mounted Damage Reduction" +lore = ["Mounted Damage Bonus", "Mounted Damage Reduction"] # tragoul [tragoul] [tragoul.thorns] - name = "Thorns" - description = "Reflect damage back to your attacker!" - lore1 = "Damage retaliated when struck" - lore = ["Damage retaliated when struck"] +name = "Thorns" +description = "Reflect damage back to your attacker!" +lore1 = "Damage retaliated when struck" +lore = ["Damage retaliated when struck"] [tragoul.globe] - name = "Globe of Pain" - description = "Divide the Damage you deal based on the number of enemies around you!" - lore1 = "The more enemies around you, the less damage you deal to each of them" - lore2 = "Range: " - lore3 = "Added Damage to all Entities: " - lore = ["The more enemies around you, the less damage you deal to each of them", "Range: ", "Added Damage to all Entities: "] +name = "Globe of Pain" +description = "Divide the Damage you deal based on the number of enemies around you!" +lore1 = "The more enemies around you, the less damage you deal to each of them" +lore2 = "Range: " +lore3 = "Added Damage to all Entities: " +lore = ["The more enemies around you, the less damage you deal to each of them", "Range: ", "Added Damage to all Entities: "] [tragoul.healing] - name = "Will of Pain" - description = "Regain health based on the damage you deal!" - lore1 = "Harming things has never felt so good! Heal from Damage Dealt" - lore2 = "There is a 3 Second damage window, for healing and a 1 Second cooldown " - lore3 = "Healing Per Damage Percent: " - lore = ["Harming things has never felt so good! Heal from Damage Dealt", "There is a 3 Second damage window, for healing and a 1 Second cooldown ", "Healing Per Damage Percent: "] +name = "Will of Pain" +description = "Regain health based on the damage you deal!" +lore1 = "Harming things has never felt so good! Heal from Damage Dealt" +lore2 = "There is a 3 Second damage window, for healing and a 1 Second cooldown " +lore3 = "Healing Per Damage Percent: " +lore = ["Harming things has never felt so good! Heal from Damage Dealt", "There is a 3 Second damage window, for healing and a 1 Second cooldown ", "Healing Per Damage Percent: "] [tragoul.lance] - name = "Corpse Lances" - description = "Killing an enemy or having an ability kill an enemy, spawns a lance that deals damage to a nearby enemy!" - lore1 = "Lances will Seek out from anything you kill, AND if This ability kills an enemy." - lore2 = "Sacrifice a portion of your life in order to create the lances (this can kill you)" - lore3 = "Max Lances: 1 + " - lore = ["Lances will Seek out from anything you kill, AND if This ability kills an enemy.", "Sacrifice a portion of your life in order to create the lances (this can kill you)", "Max Lances: 1 + "] +name = "Corpse Lances" +description = "Killing an enemy or having an ability kill an enemy, spawns a lance that deals damage to a nearby enemy!" +lore1 = "Lances will Seek out from anything you kill, AND if This ability kills an enemy." +lore2 = "Sacrifice a portion of your life in order to create the lances (this can kill you)" +lore3 = "Max Lances: 1 + " +lore = ["Lances will Seek out from anything you kill, AND if This ability kills an enemy.", "Sacrifice a portion of your life in order to create the lances (this can kill you)", "Max Lances: 1 + "] [tragoul.blood_pact] - name = "Blood Pact" - description = "Taking at least 2 hearts of damage can trigger random beneficial effects." - lore1 = "Proc Chance" - lore2 = "Buff Duration" - lore3 = "Proc Cooldown" - lore = ["Proc Chance", "Buff Duration", "Proc Cooldown"] +name = "Blood Pact" +description = "Taking at least 2 hearts of damage can trigger random beneficial effects." +lore1 = "Proc Chance" +lore2 = "Buff Duration" +lore3 = "Proc Cooldown" +lore = ["Proc Chance", "Buff Duration", "Proc Cooldown"] [tragoul.bone_harvest] - name = "Bone Harvest" - description = "Kills can spawn blood globes or bone snowball globes that grant buffs when picked up." - lore1 = "Globe Spawn Chance" - lore2 = "Globe Lifetime" - lore = ["Globe Spawn Chance", "Globe Lifetime"] +name = "Bone Harvest" +description = "Kills can spawn blood globes or bone snowball globes that grant buffs when picked up." +lore1 = "Globe Spawn Chance" +lore2 = "Globe Lifetime" +lore = ["Globe Spawn Chance", "Globe Lifetime"] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Glass Cannon" - description = "Bonus Unarmed Damage the lower your armor value is" - lore1 = "x Damage at 0 armor" - lore2 = "PerLevel Bonus Damage" - lore = ["x Damage at 0 armor", "PerLevel Bonus Damage"] +name = "Glass Cannon" +description = "Bonus Unarmed Damage the lower your armor value is" +lore1 = "x Damage at 0 armor" +lore2 = "PerLevel Bonus Damage" +lore = ["x Damage at 0 armor", "PerLevel Bonus Damage"] [unarmed.power] - name = "Unarmed Power" - description = "Improved Unarmed Damage" - lore1 = "Damage" - lore = ["Damage"] +name = "Unarmed Power" +description = "Your bare-handed strikes deal more damage." +lore1 = "Damage" +lore = ["Damage"] [unarmed.sucker_punch] - name = "Sucker Punch" - description = "Sprint punches, but more deadly." - lore1 = "Damage" - lore2 = "Damage increases by with your speed while punching" - lore = ["Damage", "Damage increases by with your speed while punching"] +name = "Sucker Punch" +description = "Sprint punches, but more deadly." +lore1 = "Damage" +lore2 = "Damage increases by with your speed while punching" +lore = ["Damage", "Damage increases by with your speed while punching"] [unarmed.battering_charge] - name = "Battering Charge" - description = "Sprint into enemies with fists or a shield to deal impact damage." - lore1 = "Impact Damage Bonus" - lore2 = "Impact Knockback" - lore3 = "Charge Cooldown" - lore = ["Impact Damage Bonus", "Impact Knockback", "Charge Cooldown"] +name = "Battering Charge" +description = "Sprint into enemies with fists or a shield to deal impact damage." +lore1 = "Impact Damage Bonus" +lore2 = "Impact Knockback" +lore3 = "Charge Cooldown" +lore = ["Impact Damage Bonus", "Impact Knockback", "Charge Cooldown"] [unarmed.combo_chain] - name = "Combo Chain" - description = "Consecutive unarmed hits build combo stacks that increase punch damage." - lore1 = "Max Combo Stacks" - lore2 = "Damage Per Stack" - lore3 = "Combo Window" - lore = ["Max Combo Stacks", "Damage Per Stack", "Combo Window"] +name = "Combo Chain" +description = "Consecutive unarmed hits build combo stacks that increase punch damage." +lore1 = "Max Combo Stacks" +lore2 = "Damage Per Stack" +lore3 = "Combo Window" +lore = ["Max Combo Stacks", "Damage Per Stack", "Combo Window"] # ============================================================ # Adaptation-Level Achievements @@ -2607,1179 +2619,1810 @@ # --- AGILITY --- [advancement.challenge_agility_armor_up_30min] - title = "Iron Jogger" - description = "Sprint with armor bonus for 30 cumulative minutes" +title = "Iron Jogger" +description = "Sprint with armor bonus for 30 cumulative minutes" [advancement.challenge_agility_armor_up_5hr] - title = "Marathon Knight" - description = "Sprint with armor bonus for 5 cumulative hours" +title = "Marathon Knight" +description = "Sprint with armor bonus for 5 cumulative hours" [advancement.challenge_agility_ladder_500] - title = "Elevator Operator" - description = "Climb 500 ladder blocks" +title = "Elevator Operator" +description = "Climb 500 ladder blocks" [advancement.challenge_agility_ladder_10k] - title = "Vertical Expressway" - description = "Climb 10,000 ladder blocks" +title = "Vertical Expressway" +description = "Climb 10,000 ladder blocks" [advancement.challenge_agility_super_jump_100] - title = "Leap of Faith" - description = "Perform 100 super jumps" +title = "Leap of Faith" +description = "Perform 100 super jumps" [advancement.challenge_agility_super_jump_5k] - title = "Frog God" - description = "Perform 5,000 super jumps" +title = "Frog God" +description = "Perform 5,000 super jumps" [advancement.challenge_agility_wall_jump_500] - title = "Spider-Player" - description = "Perform 500 wall jumps" +title = "Spider-Player" +description = "Perform 500 wall jumps" [advancement.challenge_agility_parkour_master] - title = "Parkour Master" - description = "Chain 5+ wall jumps without touching the ground" +title = "Parkour Master" +description = "Chain 5+ wall jumps without touching the ground" [advancement.challenge_agility_wind_up_10min] - title = "Second Wind" - description = "Spend 10 cumulative minutes at max sprint speed" +title = "Second Wind" +description = "Spend 10 cumulative minutes at max sprint speed" [advancement.challenge_agility_wind_up_2hr] - title = "Sonic Boom" - description = "Spend 2 cumulative hours at max sprint speed" +title = "Sonic Boom" +description = "Spend 2 cumulative hours at max sprint speed" [advancement.challenge_agility_parkour_500] - title = "Freerunner" - description = "Land 500 ledge jumps" +title = "Freerunner" +description = "Land 500 ledge jumps" [advancement.challenge_agility_roll_100] - title = "Tuck and Roll" - description = "Prevent 100 hearts of fall damage" +title = "Tuck and Roll" +description = "Prevent 100 hearts of fall damage" [advancement.challenge_agility_roll_1000] - title = "Stuntman" - description = "Prevent 1,000 hearts of fall damage" +title = "Stuntman" +description = "Prevent 1,000 hearts of fall damage" [advancement.challenge_agility_fearless] - title = "Fearless" - description = "Roll-land from a 30+ block fall" +title = "Fearless" +description = "Roll-land from a 30+ block fall" # --- ARCHITECT --- [advancement.challenge_architect_elevator_100] - title = "Going Up" - description = "Use elevators 100 times" +title = "Going Up" +description = "Use elevators 100 times" [advancement.challenge_architect_elevator_penthouse] - title = "Penthouse Express" - description = "Take a single elevator trip of 50+ blocks" +title = "Penthouse Express" +description = "Take a single elevator trip of 50+ blocks" [advancement.challenge_architect_foundation_1k] - title = "Scaffold Master" - description = "Place 1,000 foundation blocks" +title = "Scaffold Master" +description = "Place 1,000 foundation blocks" [advancement.challenge_architect_foundation_10k] - title = "Walking on Air" - description = "Place 10,000 foundation blocks" +title = "Walking on Air" +description = "Place 10,000 foundation blocks" [advancement.challenge_architect_glass_200] - title = "Glazier" - description = "Recover 200 glass blocks" +title = "Glazier" +description = "Recover 200 glass blocks" [advancement.challenge_architect_glass_5k] - title = "Crystal Clear" - description = "Recover 5,000 glass blocks" +title = "Crystal Clear" +description = "Recover 5,000 glass blocks" [advancement.challenge_architect_wireless_100] - title = "Remote Control" - description = "Send 100 wireless redstone pulses" +title = "Remote Control" +description = "Send 100 wireless redstone pulses" [advancement.challenge_architect_wireless_5k] - title = "Nikola" - description = "Send 5,000 wireless redstone pulses" +title = "Nikola" +description = "Send 5,000 wireless redstone pulses" [advancement.challenge_architect_placement_1k] - title = "Speed Builder" - description = "Place 1,000 blocks via multi-placement" +title = "Speed Builder" +description = "Place 1,000 blocks via multi-placement" [advancement.challenge_architect_placement_25k] - title = "Architect's Touch" - description = "Place 25,000 blocks via multi-placement" +title = "Architect's Touch" +description = "Place 25,000 blocks via multi-placement" [advancement.challenge_architect_smart_shape_200] - title = "Fine Tuning" - description = "Rotate 200 blocks" +title = "Fine Tuning" +description = "Rotate 200 blocks" [advancement.challenge_architect_smart_shape_5k] - title = "Perfectionist" - description = "Rotate 5,000 blocks" +title = "Perfectionist" +description = "Rotate 5,000 blocks" # --- AXE --- [advancement.challenge_axe_chop_100] - title = "Lumberjack" - description = "Fell 100 trees" +title = "Lumberjack" +description = "Fell 100 trees" [advancement.challenge_axe_chop_2500] - title = "Deforestation" - description = "Fell 2,500 trees" +title = "Deforestation" +description = "Fell 2,500 trees" [advancement.challenge_axe_chop_one_swing] - title = "One Swing Wonder" - description = "Fell a tree yielding 30+ logs in one chop" +title = "One Swing Wonder" +description = "Fell a tree yielding 30+ logs in one chop" [advancement.challenge_axe_log_swap_500] - title = "Log Alchemist" - description = "Convert 500 logs" +title = "Log Alchemist" +description = "Convert 500 logs" [advancement.challenge_axe_dti_5k] - title = "Clean Logger" - description = "Catch 5,000 items to inventory" +title = "Clean Logger" +description = "Catch 5,000 items to inventory" [advancement.challenge_axe_ground_smash_500] - title = "Quake" - description = "Hit 500 enemies with ground smash" +title = "Quake" +description = "Hit 500 enemies with ground smash" [advancement.challenge_axe_ground_smash_5] - title = "Seismic Slam" - description = "Hit 5+ enemies in a single ground smash" +title = "Seismic Slam" +description = "Hit 5+ enemies in a single ground smash" [advancement.challenge_axe_leaf_5k] - title = "Autumn Breeze" - description = "Veinmine 5,000 leaves" +title = "Autumn Breeze" +description = "Veinmine 5,000 leaves" [advancement.challenge_axe_wood_vein_2500] - title = "Chain Reaction" - description = "Veinmine 2,500 logs" +title = "Chain Reaction" +description = "Veinmine 2,500 logs" [advancement.challenge_axe_wood_vein_cascade] - title = "Cascade Logger" - description = "Veinmine 15+ logs in a single activation" +title = "Cascade Logger" +description = "Veinmine 15+ logs in a single activation" [advancement.challenge_axe_timber_200] - title = "Mark of the Woodsman" - description = "Fell 200 marked trees" +title = "Mark of the Woodsman" +description = "Fell 200 marked trees" [advancement.challenge_axe_timber_40] - title = "Marked for Removal" - description = "Fell a marked tree yielding 40+ logs" +title = "Marked for Removal" +description = "Fell a marked tree yielding 40+ logs" # --- BLOCKING --- [advancement.challenge_blocking_chain_25] - title = "Medieval Smith" - description = "Craft 25 chainmail pieces" +title = "Medieval Smith" +description = "Craft 25 chainmail pieces" [advancement.challenge_blocking_horse_armor_10] - title = "Equine Outfitter" - description = "Craft 10 horse armors" +title = "Equine Outfitter" +description = "Craft 10 horse armors" [advancement.challenge_blocking_saddle_25] - title = "Saddler" - description = "Craft 25 saddles" +title = "Saddler" +description = "Craft 25 saddles" [advancement.challenge_blocking_multi_200] - title = "Hybrid Warrior" - description = "Perform 200 elytra-armor auto-swaps" +title = "Hybrid Warrior" +description = "Perform 200 elytra-armor auto-swaps" [advancement.challenge_blocking_multi_5k] - title = "Ironbird" - description = "Perform 5,000 elytra-armor auto-swaps" +title = "Ironbird" +description = "Perform 5,000 elytra-armor auto-swaps" [advancement.challenge_blocking_counter_500] - title = "Payback" - description = "Reflect 500 total damage via counter guard" +title = "Payback" +description = "Reflect 500 total damage via counter guard" [advancement.challenge_blocking_counter_max] - title = "Full Counter" - description = "Reach maximum counter stacks and release" +title = "Full Counter" +description = "Reach maximum counter stacks and release" [advancement.challenge_blocking_bastion_500] - title = "Immovable Object" - description = "Soften 500 projectiles while bracing" +title = "Immovable Object" +description = "Soften 500 projectiles while bracing" [advancement.challenge_blocking_bastion_10] - title = "Arrow Sponge" - description = "Soften 10 projectiles in a single bracing session" +title = "Arrow Sponge" +description = "Soften 10 projectiles in a single bracing session" [advancement.challenge_blocking_mirror_100] - title = "Return to Sender" - description = "Reflect 100 projectiles" +title = "Return to Sender" +description = "Reflect 100 projectiles" [advancement.challenge_blocking_mirror_3in5] - title = "Bullet Time" - description = "Reflect 3 projectiles within 5 seconds" +title = "Bullet Time" +description = "Reflect 3 projectiles within 5 seconds" [advancement.challenge_blocking_bulwark_500] - title = "Shield Charge" - description = "Bash 500 enemies" +title = "Shield Charge" +description = "Bash 500 enemies" [advancement.challenge_blocking_bulwark_4] - title = "Bowling Strike" - description = "Hit 4+ enemies in a single bash" +title = "Bowling Strike" +description = "Hit 4+ enemies in a single bash" # --- BREWING --- [advancement.challenge_brewing_lingering_200] - title = "Slow Sipper" - description = "Benefit from 200 extended potions" +title = "Slow Sipper" +description = "Benefit from 200 extended potions" [advancement.challenge_brewing_lingering_5k] - title = "Everlasting Elixir" - description = "Benefit from 5,000 extended potions" +title = "Everlasting Elixir" +description = "Benefit from 5,000 extended potions" [advancement.challenge_brewing_super_heated_100] - title = "Volcanic Brewer" - description = "Complete 100 superheated brews" +title = "Volcanic Brewer" +description = "Complete 100 superheated brews" [advancement.challenge_brewing_super_heated_2500] - title = "Hell's Kitchen" - description = "Complete 2,500 superheated brews" +title = "Hell's Kitchen" +description = "Complete 2,500 superheated brews" [advancement.challenge_brewing_darkness_25] - title = "Lights Out" - description = "Brew 25 darkness potions" +title = "Lights Out" +description = "Brew 25 darkness potions" [advancement.challenge_brewing_haste_25] - title = "Hyperactive" - description = "Brew 25 haste potions" +title = "Hyperactive" +description = "Brew 25 haste potions" [advancement.challenge_brewing_absorption_25] - title = "Extra Padding" - description = "Brew 25 absorption potions" +title = "Extra Padding" +description = "Brew 25 absorption potions" [advancement.challenge_brewing_fatigue_25] - title = "Exhaustion Expert" - description = "Brew 25 fatigue potions" +title = "Exhaustion Expert" +description = "Brew 25 fatigue potions" [advancement.challenge_brewing_hunger_25] - title = "Famished" - description = "Brew 25 hunger potions" +title = "Famished" +description = "Brew 25 hunger potions" [advancement.challenge_brewing_nausea_25] - title = "Dizzy Concoction" - description = "Brew 25 nausea potions" +title = "Dizzy Concoction" +description = "Brew 25 nausea potions" [advancement.challenge_brewing_blindness_25] - title = "Lights Off" - description = "Brew 25 blindness potions" +title = "Lights Off" +description = "Brew 25 blindness potions" [advancement.challenge_brewing_resistance_25] - title = "Ironbrew" - description = "Brew 25 resistance potions" +title = "Ironbrew" +description = "Brew 25 resistance potions" [advancement.challenge_brewing_health_boost_25] - title = "Vitality Vial" - description = "Brew 25 health boost potions" +title = "Vitality Vial" +description = "Brew 25 health boost potions" [advancement.challenge_brewing_decay_25] - title = "Necrotoxin" - description = "Brew 25 decay potions" +title = "Necrotoxin" +description = "Brew 25 decay potions" [advancement.challenge_brewing_saturation_25] - title = "Feast in a Bottle" - description = "Brew 25 saturation potions" +title = "Feast in a Bottle" +description = "Brew 25 saturation potions" # --- CHRONOS --- [advancement.challenge_chronos_bottle_1k] - title = "Chronomancer" - description = "Spend 1,000 time charges" +title = "Chronomancer" +description = "Spend 1,000 time charges" [advancement.challenge_chronos_bottle_25k] - title = "Time Lord" - description = "Spend 25,000 time charges" +title = "Time Lord" +description = "Spend 25,000 time charges" [advancement.challenge_chronos_aberrant_500] - title = "Temporal Drain" - description = "Apply 500 slowness stacks" +title = "Temporal Drain" +description = "Apply 500 slowness stacks" [advancement.challenge_chronos_aberrant_frozen] - title = "Frozen in Time" - description = "Apply 5+ slowness stacks to a single target" +title = "Frozen in Time" +description = "Apply 5+ slowness stacks to a single target" [advancement.challenge_chronos_recall_50] - title = "Second Chance" - description = "Recall 50 times" +title = "Second Chance" +description = "Recall 50 times" [advancement.challenge_chronos_recall_1k] - title = "Groundhog Day" - description = "Recall 1,000 times" +title = "Groundhog Day" +description = "Recall 1,000 times" [advancement.challenge_chronos_recall_cheat_death] - title = "Cheating Death" - description = "Recall when below 2 hearts, restoring to above 8" +title = "Cheating Death" +description = "Recall when below 2 hearts, restoring to above 8" [advancement.challenge_chronos_bomb_freeze_50] - title = "Time Stop" - description = "Freeze 50 projectiles" +title = "Time Stop" +description = "Freeze 50 projectiles" [advancement.challenge_chronos_bomb_crowd_8] - title = "Crowd Control" - description = "Slow 8+ entities with one time bomb" +title = "Crowd Control" +description = "Slow 8+ entities with one time bomb" [advancement.challenge_chronos_echo_200] - title = "Double Tap" - description = "Land 200 echo hits" +title = "Double Tap" +description = "Land 200 echo hits" # --- CRAFTING --- [advancement.challenge_crafting_decon_200] - title = "Salvager" - description = "Deconstruct 200 items" +title = "Salvager" +description = "Deconstruct 200 items" [advancement.challenge_crafting_decon_5k] - title = "Recycling Plant" - description = "Deconstruct 5,000 items" +title = "Recycling Plant" +description = "Deconstruct 5,000 items" [advancement.challenge_crafting_xp_1k] - title = "Artisan" - description = "Craft 1,000 items" +title = "Artisan" +description = "Craft 1,000 items" [advancement.challenge_crafting_xp_25k] - title = "Master Crafter" - description = "Craft 25,000 items" +title = "Master Crafter" +description = "Craft 25,000 items" [advancement.challenge_crafting_recon_100] - title = "Reverse Engineering" - description = "Reconstruct 100 ores" +title = "Reverse Engineering" +description = "Reconstruct 100 ores" [advancement.challenge_crafting_leather_100] - title = "Tanner" - description = "Craft 100 leather from rotten flesh" +title = "Tanner" +description = "Craft 100 leather from rotten flesh" [advancement.challenge_crafting_backpack_25] - title = "Pack Rat" - description = "Craft 25 bundles" +title = "Pack Rat" +description = "Craft 25 bundles" [advancement.challenge_crafting_stations_200] - title = "On the Go" - description = "Use portable stations 200 times" +title = "On the Go" +description = "Use portable stations 200 times" [advancement.challenge_crafting_stations_5k] - title = "Mobile Workshop" - description = "Use portable stations 5,000 times" +title = "Mobile Workshop" +description = "Use portable stations 5,000 times" [advancement.challenge_crafting_skulls_10] - title = "Skull Collector" - description = "Craft 10 mob skulls" +title = "Skull Collector" +description = "Craft 10 mob skulls" [advancement.challenge_crafting_skulls_100] - title = "Headhunter" - description = "Craft 100 mob skulls" +title = "Headhunter" +description = "Craft 100 mob skulls" # --- DISCOVERY --- [advancement.challenge_discovery_unity_5k] - title = "Renaissance Player" - description = "Distribute 5,000 XP orbs to random skills" +title = "Renaissance Player" +description = "Distribute 5,000 XP orbs to random skills" [advancement.challenge_discovery_unity_50k] - title = "Jack of All Trades" - description = "Distribute 50,000 XP orbs" +title = "Jack of All Trades" +description = "Distribute 50,000 XP orbs" [advancement.challenge_discovery_xp_resist_25] - title = "Emergency Reserves" - description = "Mitigate lethal damage 25 times using XP" +title = "Emergency Reserves" +description = "Mitigate lethal damage 25 times using XP" [advancement.challenge_discovery_xp_resist_250] - title = "XP Shield" - description = "Mitigate lethal damage 250 times" +title = "XP Shield" +description = "Mitigate lethal damage 250 times" [advancement.challenge_discovery_xp_resist_clutch] - title = "Clutch" - description = "Survive a hit that would have dealt 15+ hearts" +title = "Clutch" +description = "Survive a hit that would have dealt 15+ hearts" [advancement.challenge_discovery_villager_100] - title = "Silver Tongue" - description = "Complete 100 improved villager trades" +title = "Silver Tongue" +description = "Complete 100 improved villager trades" [advancement.challenge_discovery_villager_2500] - title = "Master Negotiator" - description = "Complete 2,500 improved trades" +title = "Master Negotiator" +description = "Complete 2,500 improved trades" [advancement.challenge_discovery_armor_1hr] - title = "One with the Earth" - description = "Have environmental armor for 1 cumulative hour" +title = "One with the Earth" +description = "Have environmental armor for 1 cumulative hour" [advancement.challenge_discovery_armor_24hr] - title = "Stone Skin" - description = "Have environmental armor for 24 cumulative hours" +title = "Stone Skin" +description = "Have environmental armor for 24 cumulative hours" [advancement.challenge_discovery_mending_10k] - title = "Master Mender" - description = "Restore 10,000 durability via direct mending" +title = "Master Mender" +description = "Restore 10,000 durability via direct mending" [advancement.challenge_discovery_mending_100k] - title = "Good as New" - description = "Restore 100,000 durability" +title = "Good as New" +description = "Restore 100,000 durability" [advancement.challenge_discovery_archaeologist_50] - title = "Relic Hunter" - description = "Find 50 bonus archaeology items" +title = "Relic Hunter" +description = "Find 50 bonus archaeology items" [advancement.challenge_discovery_archaeologist_500] - title = "Indiana Jones" - description = "Find 500 bonus items" +title = "Indiana Jones" +description = "Find 500 bonus items" [advancement.challenge_discovery_cartographer_100] - title = "Wayfinder" - description = "Pulse toward 100 structures" +title = "Wayfinder" +description = "Pulse toward 100 structures" [advancement.challenge_discovery_cartographer_1k] - title = "Explorer Extraordinaire" - description = "Pulse toward 1,000 structures" +title = "Explorer Extraordinaire" +description = "Pulse toward 1,000 structures" # --- ENCHANTING --- [advancement.challenge_enchanting_lapis_100] - title = "Penny Pincher" - description = "Save 100 lapis from enchanting" +title = "Penny Pincher" +description = "Save 100 lapis from enchanting" [advancement.challenge_enchanting_lapis_2500] - title = "Blue Miser" - description = "Save 2,500 lapis" +title = "Blue Miser" +description = "Save 2,500 lapis" [advancement.challenge_enchanting_quick_100] - title = "Bookworm" - description = "Apply 100 books directly" +title = "Bookworm" +description = "Apply 100 books directly" [advancement.challenge_enchanting_quick_1k] - title = "Walking Enchant Table" - description = "Apply 1,000 books directly" +title = "Walking Enchant Table" +description = "Apply 1,000 books directly" [advancement.challenge_enchanting_xp_100] - title = "Thrifty Enchanter" - description = "Save 100 XP levels from enchanting" +title = "Thrifty Enchanter" +description = "Save 100 XP levels from enchanting" [advancement.challenge_enchanting_anvil_200] - title = "Anvil Whisperer" - description = "Save 200 levels at anvil" +title = "Anvil Whisperer" +description = "Save 200 levels at anvil" [advancement.challenge_enchanting_anvil_5k] - title = "Blacksmith Prodigy" - description = "Save 5,000 levels at anvil" +title = "Blacksmith Prodigy" +description = "Save 5,000 levels at anvil" [advancement.challenge_enchanting_reroll_100] - title = "Picky Enchanter" - description = "Reroll enchantment offers 100 times" +title = "Picky Enchanter" +description = "Reroll enchantment offers 100 times" [advancement.challenge_enchanting_reroll_1k] - title = "Never Satisfied" - description = "Reroll enchantment offers 1,000 times" +title = "Never Satisfied" +description = "Reroll enchantment offers 1,000 times" [advancement.challenge_enchanting_bookshelf_100] - title = "Library Card" - description = "Enchant 100 items with virtual bookshelf bonus" +title = "Library Card" +description = "Enchant 100 items with virtual bookshelf bonus" [advancement.challenge_enchanting_grindstone_50] - title = "Salvage Specialist" - description = "Recover 50 enchantments at the grindstone" +title = "Salvage Specialist" +description = "Recover 50 enchantments at the grindstone" [advancement.challenge_enchanting_grindstone_500] - title = "Nothing Wasted" - description = "Recover 500 enchantments" +title = "Nothing Wasted" +description = "Recover 500 enchantments" # --- EXCAVATION --- [advancement.challenge_excavation_haste_5k] - title = "Dig Faster" - description = "Break 5,000 blocks while hasted" +title = "Dig Faster" +description = "Break 5,000 blocks while hasted" [advancement.challenge_excavation_haste_50k] - title = "Tunneling Machine" - description = "Break 50,000 blocks while hasted" +title = "Tunneling Machine" +description = "Break 50,000 blocks while hasted" [advancement.challenge_excavation_spelunker_1k] - title = "X-Ray Vision" - description = "Reveal 1,000 ores" +title = "X-Ray Vision" +description = "Reveal 1,000 ores" [advancement.challenge_excavation_spelunker_25k] - title = "All-Seeing Eye" - description = "Reveal 25,000 ores" +title = "All-Seeing Eye" +description = "Reveal 25,000 ores" [advancement.challenge_excavation_dti_10k] - title = "Clean Digger" - description = "Catch 10,000 items to inventory" +title = "Clean Digger" +description = "Catch 10,000 items to inventory" [advancement.challenge_excavation_omni_1k] - title = "Swiss Army Miner" - description = "Perform 1,000 auto-tool-swaps" +title = "Swiss Army Miner" +description = "Perform 1,000 auto-tool-swaps" [advancement.challenge_excavation_omni_25k] - title = "Tool Belt Master" - description = "Perform 25,000 auto-tool-swaps" +title = "Tool Belt Master" +description = "Perform 25,000 auto-tool-swaps" [advancement.challenge_excavation_seismic_200] - title = "Echolocation" - description = "Trigger 200 seismic pings" +title = "Echolocation" +description = "Trigger 200 seismic pings" # --- HERBALISM --- [advancement.challenge_herbalism_growth_1k] - title = "Green Thumb" - description = "Grow 1,000 blocks via growth aura" +title = "Green Thumb" +description = "Grow 1,000 blocks via growth aura" [advancement.challenge_herbalism_growth_25k] - title = "Nature's Blessing" - description = "Grow 25,000 blocks" +title = "Nature's Blessing" +description = "Grow 25,000 blocks" [advancement.challenge_herbalism_hippo_500] - title = "Glutton" - description = "Gain 500 bonus saturation" +title = "Glutton" +description = "Gain 500 bonus saturation" [advancement.challenge_herbalism_myconid_100] - title = "Fungus Among Us" - description = "Craft 100 mycelium" +title = "Fungus Among Us" +description = "Craft 100 mycelium" [advancement.challenge_herbalism_terralid_200] - title = "Landscaper" - description = "Craft 200 grass blocks" +title = "Landscaper" +description = "Craft 200 grass blocks" [advancement.challenge_herbalism_cobweb_100] - title = "Silk Weaver" - description = "Craft 100 cobwebs" +title = "Silk Weaver" +description = "Craft 100 cobwebs" [advancement.challenge_herbalism_mushroom_100] - title = "Mooshroom Builder" - description = "Craft 100 mushroom blocks" +title = "Mooshroom Builder" +description = "Craft 100 mushroom blocks" [advancement.challenge_herbalism_dti_10k] - title = "Clean Harvest" - description = "Catch 10,000 items to inventory" +title = "Clean Harvest" +description = "Catch 10,000 items to inventory" [advancement.challenge_herbalism_shield_500] - title = "Thick Skin" - description = "Absorb 500 damage via hunger" +title = "Thick Skin" +description = "Absorb 500 damage via hunger" [advancement.challenge_herbalism_shield_5k] - title = "Insatiable Tank" - description = "Absorb 5,000 damage via hunger" +title = "Insatiable Tank" +description = "Absorb 5,000 damage via hunger" [advancement.challenge_herbalism_luck_100] - title = "Lucky Find" - description = "Get 100 lucky drops" +title = "Lucky Find" +description = "Get 100 lucky drops" [advancement.challenge_herbalism_luck_2500] - title = "Four Leaf Clover" - description = "Get 2,500 lucky drops" +title = "Four Leaf Clover" +description = "Get 2,500 lucky drops" [advancement.challenge_herbalism_replant_500] - title = "Sustainable Farmer" - description = "Replant 500 crops" +title = "Sustainable Farmer" +description = "Replant 500 crops" [advancement.challenge_herbalism_replant_25k] - title = "Industrial Agriculture" - description = "Replant 25,000 crops" +title = "Industrial Agriculture" +description = "Replant 25,000 crops" [advancement.challenge_herbalism_seed_1k] - title = "Johnny Appleseed" - description = "Plant 1,000 seeds via AOE" +title = "Johnny Appleseed" +description = "Plant 1,000 seeds via AOE" [advancement.challenge_herbalism_seed_25k] - title = "Field of Dreams" - description = "Plant 25,000 seeds via AOE" +title = "Field of Dreams" +description = "Plant 25,000 seeds via AOE" [advancement.challenge_herbalism_rooted_500] - title = "Gentle Step" - description = "Protect 500 farmland blocks" +title = "Gentle Step" +description = "Protect 500 farmland blocks" [advancement.challenge_herbalism_compost_1k] - title = "Composting Champion" - description = "Compost 1,000 items" +title = "Composting Champion" +description = "Compost 1,000 items" [advancement.challenge_herbalism_compost_25k] - title = "Zero Waste" - description = "Compost 25,000 items" +title = "Zero Waste" +description = "Compost 25,000 items" [advancement.challenge_herbalism_bee_100] - title = "Queen Bee" - description = "Attract 100 bees" +title = "Queen Bee" +description = "Attract 100 bees" [advancement.challenge_herbalism_spore_500] - title = "Spore Carrier" - description = "Spread 500 blocks via spore bloom" +title = "Spore Carrier" +description = "Spread 500 blocks via spore bloom" # --- HUNTER --- [advancement.challenge_hunter_adrenaline_100] - title = "Cornered Beast" - description = "Kill 100 mobs below 30% health" +title = "Cornered Beast" +description = "Kill 100 mobs below 30% health" [advancement.challenge_hunter_adrenaline_2500] - title = "Berserker" - description = "Kill 2,500 mobs in adrenaline" +title = "Berserker" +description = "Kill 2,500 mobs in adrenaline" [advancement.challenge_hunter_dti_10k] - title = "Clean Kill" - description = "Catch 10,000 items to inventory" +title = "Clean Kill" +description = "Catch 10,000 items to inventory" [advancement.challenge_hunter_invis_200] - title = "Ghost Protocol" - description = "Trigger invisibility 200 times from kills" +title = "Ghost Protocol" +description = "Trigger invisibility 200 times from kills" [advancement.challenge_hunter_jump_200] - title = "Spring Loaded" - description = "Trigger jump boost 200 times from kills" +title = "Spring Loaded" +description = "Trigger jump boost 200 times from kills" [advancement.challenge_hunter_luck_200] - title = "Lucky Shot" - description = "Trigger luck 200 times from kills" +title = "Lucky Shot" +description = "Trigger luck 200 times from kills" [advancement.challenge_hunter_regen_500] - title = "Wolverine" - description = "Regenerate 500 hearts from kill regen" +title = "Wolverine" +description = "Regenerate 500 hearts from kill regen" [advancement.challenge_hunter_resistance_500] - title = "Thick Hide" - description = "Trigger resistance 500 times from kills" +title = "Thick Hide" +description = "Trigger resistance 500 times from kills" [advancement.challenge_hunter_speed_200] - title = "Fight or Flight" - description = "Trigger speed 200 times from kills" +title = "Fight or Flight" +description = "Trigger speed 200 times from kills" [advancement.challenge_hunter_strength_200] - title = "Rage" - description = "Trigger strength 200 times from kills" +title = "Rage" +description = "Trigger strength 200 times from kills" [advancement.challenge_hunter_trophy_50] - title = "Taxidermist" - description = "Collect 50 trophy drops" +title = "Taxidermist" +description = "Collect 50 trophy drops" [advancement.challenge_hunter_trophy_heads_100] - title = "Trophy Room" - description = "Collect 100 mob heads" +title = "Trophy Room" +description = "Collect 100 mob heads" # --- NETHER --- [advancement.challenge_nether_wither_100] - title = "Wither-Proof" - description = "Negate 100 wither effects" +title = "Wither-Proof" +description = "Negate 100 wither effects" [advancement.challenge_nether_wither_1k] - title = "Death Defiant" - description = "Negate 1,000 wither effects" +title = "Death Defiant" +description = "Negate 1,000 wither effects" [advancement.challenge_nether_fire_200] - title = "Firewalker" - description = "Negate 200 fire damage instances" +title = "Firewalker" +description = "Negate 200 fire damage instances" [advancement.challenge_nether_fire_5k] - title = "Salamander" - description = "Negate 5,000 fire damage instances" +title = "Salamander" +description = "Negate 5,000 fire damage instances" [advancement.challenge_nether_skull_100] - title = "Skull Chucker" - description = "Throw 100 wither skulls" +title = "Skull Chucker" +description = "Throw 100 wither skulls" [advancement.challenge_nether_skull_kills_50] - title = "Wither Lord" - description = "Kill 50 mobs with wither skulls" +title = "Wither Lord" +description = "Kill 50 mobs with wither skulls" [advancement.challenge_nether_skull_long_bomb] - title = "Long Bomb" - description = "Hit a target 40+ blocks away with a skull" +title = "Long Bomb" +description = "Hit a target 40+ blocks away with a skull" [advancement.challenge_nether_lava_1k] - title = "Strider" - description = "Walk 1,000 blocks on lava" +title = "Strider" +description = "Walk 1,000 blocks on lava" [advancement.challenge_nether_lava_25k] - title = "Lava Surfer" - description = "Walk 25,000 blocks on lava" +title = "Lava Surfer" +description = "Walk 25,000 blocks on lava" [advancement.challenge_nether_ghast_500] - title = "Nether Veteran" - description = "Mitigate 500 nether mob damage" +title = "Nether Veteran" +description = "Mitigate 500 nether mob damage" [advancement.challenge_nether_blaze_200] - title = "Pyrophyte" - description = "Gain 200 hearts from fire damage" +title = "Pyrophyte" +description = "Gain 200 hearts from fire damage" [advancement.challenge_nether_blaze_2500] - title = "Flame Eater" - description = "Gain 2,500 hearts from fire damage" +title = "Flame Eater" +description = "Gain 2,500 hearts from fire damage" [advancement.challenge_nether_piglin_100] - title = "Gold Broker" - description = "Complete 100 improved piglin barters" +title = "Gold Broker" +description = "Complete 100 improved piglin barters" [advancement.challenge_nether_piglin_2500] - title = "Piglin Mogul" - description = "Complete 2,500 improved barters" +title = "Piglin Mogul" +description = "Complete 2,500 improved barters" # --- PICKAXE --- [advancement.challenge_pickaxe_autosmelt_1k] - title = "Walking Furnace" - description = "Auto-smelt 1,000 ores" +title = "Walking Furnace" +description = "Auto-smelt 1,000 ores" [advancement.challenge_pickaxe_autosmelt_25k] - title = "Smelter Supreme" - description = "Auto-smelt 25,000 ores" +title = "Smelter Supreme" +description = "Auto-smelt 25,000 ores" [advancement.challenge_pickaxe_chisel_500] - title = "Precision Miner" - description = "Chisel 500 extra ore drops" +title = "Precision Miner" +description = "Chisel 500 extra ore drops" [advancement.challenge_pickaxe_dti_25k] - title = "Clean Miner" - description = "Catch 25,000 items to inventory" +title = "Clean Miner" +description = "Catch 25,000 items to inventory" [advancement.challenge_pickaxe_spawner_10] - title = "Spawner Thief" - description = "Collect 10 spawners with silk touch" +title = "Spawner Thief" +description = "Collect 10 spawners with silk touch" [advancement.challenge_pickaxe_spawner_50] - title = "Zoo Keeper" - description = "Collect 50 spawners" +title = "Zoo Keeper" +description = "Collect 50 spawners" [advancement.challenge_pickaxe_veinminer_2500] - title = "Vein Ripper" - description = "Veinmine 2,500 ores" +title = "Vein Ripper" +description = "Veinmine 2,500 ores" [advancement.challenge_pickaxe_veinminer_20] - title = "Mother Lode" - description = "Veinmine 20+ ores in a single go" +title = "Mother Lode" +description = "Veinmine 20+ ores in a single go" [advancement.challenge_pickaxe_quarry_200] - title = "Prospector" - description = "Perform 200 ore scans" +title = "Prospector" +description = "Perform 200 ore scans" # --- RANGED --- [advancement.challenge_ranged_arrow_500] - title = "Frugal Fletcher" - description = "Recover 500 arrows" +title = "Frugal Fletcher" +description = "Recover 500 arrows" [advancement.challenge_ranged_arrow_10k] - title = "Infinite Quiver" - description = "Recover 10,000 arrows" +title = "Infinite Quiver" +description = "Recover 10,000 arrows" [advancement.challenge_ranged_web_200] - title = "Web Slinger" - description = "Trap 200 mobs with web bombs" +title = "Web Slinger" +description = "Trap 200 mobs with web bombs" [advancement.challenge_ranged_force_500] - title = "Sniper" - description = "Land 500 long-range hits (30+ blocks)" +title = "Sniper" +description = "Land 500 long-range hits (30+ blocks)" [advancement.challenge_ranged_lunge_200] - title = "Air Support" - description = "Perform 200 aerial lunges" +title = "Air Support" +description = "Perform 200 aerial lunges" [advancement.challenge_ranged_lunge_2500] - title = "Rocket Jump" - description = "Perform 2,500 aerial lunges" +title = "Rocket Jump" +description = "Perform 2,500 aerial lunges" [advancement.challenge_ranged_piercing_500] - title = "Shish Kebab" - description = "Pierce 500 extra targets" +title = "Shish Kebab" +description = "Pierce 500 extra targets" [advancement.challenge_ranged_piercing_4] - title = "Collateral" - description = "Hit 4+ enemies with a single arrow" +title = "Collateral" +description = "Hit 4+ enemies with a single arrow" [advancement.challenge_ranged_floaters_200] - title = "Up You Go" - description = "Levitate 200 targets" +title = "Up You Go" +description = "Levitate 200 targets" [advancement.challenge_ranged_pinning_300] - title = "Nailed Down" - description = "Pin 300 targets" +title = "Nailed Down" +description = "Pin 300 targets" [advancement.challenge_ranged_trajectory_100] - title = "Calculated" - description = "Kill 100 mobs while trajectory preview is active" +title = "Calculated" +description = "Kill 100 mobs while trajectory preview is active" [advancement.challenge_ranged_ricochet_kills_50] - title = "Trick Shot" - description = "Kill 50 mobs with ricocheted arrows" +title = "Trick Shot" +description = "Kill 50 mobs with ricocheted projectiles" [advancement.challenge_ranged_ricochet_kills_500] - title = "Pinball Wizard" - description = "Kill 500 mobs with ricocheted arrows" +title = "Pinball Wizard" +description = "Kill 500 mobs with ricocheted projectiles" # --- RIFT --- [advancement.challenge_rift_blink_500] - title = "Phase Shift" - description = "Blink 500 times" +title = "Phase Shift" +description = "Blink 500 times" [advancement.challenge_rift_blink_5k] - title = "Rift Walker" - description = "Blink 5,000 total blocks of distance" +title = "Rift Walker" +description = "Blink 5,000 total blocks of distance" [advancement.challenge_rift_enderchest_200] - title = "Pocket Dimension" - description = "Open your ender chest remotely 200 times" +title = "Pocket Dimension" +description = "Open your ender chest remotely 200 times" [advancement.challenge_rift_descent_100] - title = "Grounded" - description = "Cancel 100 levitation effects" +title = "Grounded" +description = "Cancel 100 levitation effects" [advancement.challenge_rift_descent_1k] - title = "Anti-Gravity" - description = "Cancel 1,000 levitation effects" +title = "Anti-Gravity" +description = "Cancel 1,000 levitation effects" [advancement.challenge_rift_gate_100] - title = "Gatekeeper" - description = "Teleport via rift gates 100 times" +title = "Gatekeeper" +description = "Teleport via rift gates 100 times" [advancement.challenge_rift_gate_50k_dist] - title = "Rift Network" - description = "Teleport 50,000 cumulative blocks via rift gates" +title = "Rift Network" +description = "Teleport 50,000 cumulative blocks via rift gates" [advancement.challenge_rift_resist_200] - title = "Void-Touched" - description = "Trigger rift resistance 200 times" +title = "Void-Touched" +description = "Trigger rift resistance 200 times" [advancement.challenge_rift_visage_100] - title = "Eye Contact" - description = "Survive 100 enderman stares" +title = "Eye Contact" +description = "Survive 100 enderman stares" [advancement.challenge_rift_visage_1k] - title = "Enderman Whisperer" - description = "Survive 1,000 enderman stares" +title = "Enderman Whisperer" +description = "Survive 1,000 enderman stares" [advancement.challenge_rift_access_100] - title = "Dimensional Pocket" - description = "Open containers remotely 100 times" +title = "Dimensional Pocket" +description = "Open containers remotely 100 times" [advancement.challenge_rift_access_2500] - title = "Astral Reach" - description = "Open containers remotely 2,500 times" +title = "Astral Reach" +description = "Open containers remotely 2,500 times" [advancement.challenge_rift_void_magnet_5k] - title = "Vacuum Cleaner" - description = "Pull 5,000 items from range" +title = "Vacuum Cleaner" +description = "Pull 5,000 items from range" [advancement.challenge_rift_void_magnet_50k] - title = "Black Hole" - description = "Pull 50,000 items from range" +title = "Black Hole" +description = "Pull 50,000 items from range" [advancement.challenge_rift_taglock_100] - title = "Voodoo Doll" - description = "Tag 100 entities into ender pearls" +title = "Voodoo Doll" +description = "Tag 100 entities into ender pearls" [advancement.challenge_rift_taglock_500] - title = "Puppet Master" - description = "Successfully taglock-teleport 500 entities" +title = "Puppet Master" +description = "Successfully taglock-teleport 500 entities" [advancement.challenge_rift_pocket_5k] - title = "Bottomless Pockets" - description = "Pull 5,000 items from ender chest during building" +title = "Bottomless Pockets" +description = "Pull 5,000 items from ender chest during building" [advancement.challenge_rift_pocket_store_10k] - title = "Ender Hoarder" - description = "Store 10,000 items into ender chest via sneak-drop" +title = "Ender Hoarder" +description = "Store 10,000 items into ender chest via sneak-drop" # --- SEABORNE --- [advancement.challenge_seaborne_fish_500] - title = "Master Angler" - description = "Catch 500 bonus fish" +title = "Master Angler" +description = "Catch 500 bonus fish" [advancement.challenge_seaborne_fish_5k] - title = "Old Man and the Sea" - description = "Catch 5,000 bonus fish" +title = "Old Man and the Sea" +description = "Catch 5,000 bonus fish" [advancement.challenge_seaborne_speed_10k] - title = "Dolphin" - description = "Swim 10,000 blocks" +title = "Dolphin" +description = "Swim 10,000 blocks" [advancement.challenge_seaborne_speed_100k] - title = "Aquaman" - description = "Swim 100,000 blocks" +title = "Aquaman" +description = "Swim 100,000 blocks" [advancement.challenge_seaborne_vision_72k] - title = "Deep Sea Explorer" - description = "Spend 1 cumulative hour underwater with vision" +title = "Deep Sea Explorer" +description = "Spend 1 cumulative hour underwater with vision" [advancement.challenge_seaborne_mining_2500] - title = "Submarine Miner" - description = "Mine 2,500 blocks underwater" +title = "Submarine Miner" +description = "Mine 2,500 blocks underwater" [advancement.challenge_seaborne_mining_25k] - title = "Atlantean Engineer" - description = "Mine 25,000 blocks underwater" +title = "Atlantean Engineer" +description = "Mine 25,000 blocks underwater" [advancement.challenge_seaborne_oxygen_12k] - title = "Iron Lungs" - description = "Gain 10 cumulative minutes of bonus air" +title = "Iron Lungs" +description = "Gain 10 cumulative minutes of bonus air" [advancement.challenge_seaborne_tidecaller_200] - title = "Tidal Force" - description = "Perform 200 tide dashes" +title = "Tidal Force" +description = "Perform 200 tide dashes" [advancement.challenge_seaborne_tidecaller_5k] - title = "Poseidon's Sprint" - description = "Perform 5,000 tide dashes" +title = "Poseidon's Sprint" +description = "Perform 5,000 tide dashes" [advancement.challenge_seaborne_pressure_1k] - title = "Deep Diver" - description = "Mine 1,000 blocks below sea level" +title = "Deep Diver" +description = "Mine 1,000 blocks below sea level" # --- STEALTH --- [advancement.challenge_stealth_ghost_100] - title = "Phantom Shield" - description = "Build max ghost armor 100 times" +title = "Phantom Shield" +description = "Build max ghost armor 100 times" [advancement.challenge_stealth_ghost_500] - title = "Glass Cannon Reversed" - description = "Absorb 500 hits with ghost armor" +title = "Glass Cannon Reversed" +description = "Absorb 500 hits with ghost armor" [advancement.challenge_stealth_sight_72k] - title = "Creature of the Night" - description = "Spend 1 hour sneaking with night vision" +title = "Creature of the Night" +description = "Spend 1 hour sneaking with night vision" [advancement.challenge_stealth_snatch_2500] - title = "Sticky Fingers" - description = "Snatch 2,500 items from mobs" +title = "Sticky Fingers" +description = "Snatch 2,500 items from mobs" [advancement.challenge_stealth_snatch_25k] - title = "Pickpocket" - description = "Snatch 25,000 items from mobs" +title = "Pickpocket" +description = "Snatch 25,000 items from mobs" [advancement.challenge_stealth_speed_5k] - title = "Swift Shadow" - description = "Traverse 5,000 blocks while speed-sneaking" +title = "Swift Shadow" +description = "Traverse 5,000 blocks while speed-sneaking" [advancement.challenge_stealth_ender_veil_200] - title = "Invisible to the Void" - description = "Avoid 200 enderman aggro triggers" +title = "Invisible to the Void" +description = "Avoid 200 enderman aggro triggers" [advancement.challenge_stealth_silent_200] - title = "Assassin" - description = "Land 200 backstabs" +title = "Assassin" +description = "Land 200 backstabs" [advancement.challenge_stealth_silent_5in10] - title = "Unseen Blade" - description = "Land 5 backstabs within 10 seconds" +title = "Unseen Blade" +description = "Land 5 backstabs within 10 seconds" [advancement.challenge_stealth_decoy_100] - title = "Body Double" - description = "Spawn 100 shadow decoys" +title = "Body Double" +description = "Spawn 100 shadow decoys" [advancement.challenge_stealth_decoy_distract_500] - title = "Master of Misdirection" - description = "Distract 500 mobs with decoys" +title = "Master of Misdirection" +description = "Distract 500 mobs with decoys" # --- SWORDS --- [advancement.challenge_swords_machete_2500] - title = "Jungle Explorer" - description = "Cut 2,500 foliage blocks" +title = "Jungle Explorer" +description = "Cut 2,500 foliage blocks" [advancement.challenge_swords_machete_25k] - title = "Trailblazer" - description = "Cut 25,000 foliage blocks" +title = "Trailblazer" +description = "Cut 25,000 foliage blocks" [advancement.challenge_swords_bloody_500] - title = "Bloodletter" - description = "Deal 500 hearts of bleed damage" +title = "Bloodletter" +description = "Deal 500 hearts of bleed damage" [advancement.challenge_swords_bloody_kills_100] - title = "Death by a Thousand Cuts" - description = "Kill 100 mobs with bleed alone" +title = "Death by a Thousand Cuts" +description = "Kill 100 mobs with bleed alone" [advancement.challenge_swords_poison_500] - title = "Venomous" - description = "Apply poison 500 times" +title = "Venomous" +description = "Apply poison 500 times" [advancement.challenge_swords_poison_kills_50] - title = "Toxic Blade" - description = "Kill 50 mobs with poison" +title = "Toxic Blade" +description = "Kill 50 mobs with poison" [advancement.challenge_swords_execute_200] - title = "Executioner" - description = "Execute 200 low-health mobs" +title = "Executioner" +description = "Execute 200 low-health mobs" [advancement.challenge_swords_execute_2500] - title = "Judge, Jury, Executioner" - description = "Execute 2,500 mobs" +title = "Judge, Jury, Executioner" +description = "Execute 2,500 mobs" [advancement.challenge_swords_execute_5in10] - title = "Mercy is Weakness" - description = "Execute 5 mobs in 10 seconds" +title = "Mercy is Weakness" +description = "Execute 5 mobs in 10 seconds" [advancement.challenge_swords_dual_1k] - title = "Ambidextrous" - description = "Deal 1,000 bonus dual-wield damage" +title = "Ambidextrous" +description = "Deal 1,000 bonus dual-wield damage" [advancement.challenge_swords_dual_25k] - title = "Twin Fangs" - description = "Deal 25,000 bonus dual-wield damage" +title = "Twin Fangs" +description = "Deal 25,000 bonus dual-wield damage" [advancement.challenge_swords_riposte_200] - title = "En Garde" - description = "Land 200 ripostes" +title = "En Garde" +description = "Land 200 ripostes" [advancement.challenge_swords_riposte_2500] - title = "Fencer" - description = "Land 2,500 ripostes" +title = "Fencer" +description = "Land 2,500 ripostes" [advancement.challenge_swords_riposte_3in5] - title = "Parry King" - description = "Land 3 ripostes within 5 seconds" +title = "Parry King" +description = "Land 3 ripostes within 5 seconds" [advancement.challenge_swords_cyclone_500] - title = "Whirlwind" - description = "Hit 500 mobs with crimson cyclone" +title = "Whirlwind" +description = "Hit 500 mobs with crimson cyclone" [advancement.challenge_swords_cyclone_5k] - title = "Storm of Blades" - description = "Hit 5,000 mobs with crimson cyclone" +title = "Storm of Blades" +description = "Hit 5,000 mobs with crimson cyclone" [advancement.challenge_swords_cyclone_6] - title = "Blender" - description = "Hit 6+ mobs with a single cyclone activation" +title = "Blender" +description = "Hit 6+ mobs with a single cyclone activation" # --- TAMING --- [advancement.challenge_taming_damage_500] - title = "Alpha Trainer" - description = "Your pets kill 500 mobs" +title = "Alpha Trainer" +description = "Your pets kill 500 mobs" [advancement.challenge_taming_damage_5k] - title = "Beast Master" - description = "Your pets kill 5,000 mobs" +title = "Beast Master" +description = "Your pets kill 5,000 mobs" [advancement.challenge_taming_health_boost_1728k] - title = "Guardian of Beasts" - description = "Keep pets health-boosted for 24 cumulative hours" +title = "Guardian of Beasts" +description = "Keep pets health-boosted for 24 cumulative hours" [advancement.challenge_taming_regen_1k] - title = "Healing Touch" - description = "Pets regenerate 1,000 hearts" +title = "Healing Touch" +description = "Pets regenerate 1,000 hearts" [advancement.challenge_taming_recall_100] - title = "Come Here Boy" - description = "Recall pets 100 times" +title = "Come Here Boy" +description = "Recall pets 100 times" [advancement.challenge_taming_recall_1k] - title = "Teleporting Kennel" - description = "Recall pets 1,000 times" +title = "Teleporting Kennel" +description = "Recall pets 1,000 times" [advancement.challenge_taming_pack_72k] - title = "Pack Leader" - description = "Keep pack leader aura active for 1 cumulative hour" +title = "Pack Leader" +description = "Keep pack leader aura active for 1 cumulative hour" [advancement.challenge_taming_shared_500] - title = "Loyal Shield" - description = "Take 500 hearts of damage for your pets" +title = "Loyal Shield" +description = "Take 500 hearts of damage for your pets" [advancement.challenge_taming_shared_5k] - title = "Sacrifice" - description = "Take 5,000 hearts of damage for your pets" +title = "Sacrifice" +description = "Take 5,000 hearts of damage for your pets" [advancement.challenge_taming_mounted_200] - title = "Cavalry" - description = "Kill 200 mobs while mounted" +title = "Cavalry" +description = "Kill 200 mobs while mounted" [advancement.challenge_taming_mounted_50k] - title = "Knight Errant" - description = "Travel 50,000 blocks while mounted" +title = "Knight Errant" +description = "Travel 50,000 blocks while mounted" # --- TRAG'OUL --- [advancement.challenge_tragoul_thorns_500] - title = "Living Cactus" - description = "Reflect 500 damage via blood thorns" +title = "Living Cactus" +description = "Reflect 500 damage via blood thorns" [advancement.challenge_tragoul_thorns_5k] - title = "Iron Maiden" - description = "Reflect 5,000 damage" +title = "Iron Maiden" +description = "Reflect 5,000 damage" [advancement.challenge_tragoul_thorns_kill] - title = "Don't Touch Me" - description = "Kill a mob purely from reflected damage" +title = "Don't Touch Me" +description = "Kill a mob purely from reflected damage" [advancement.challenge_tragoul_globe_1k] - title = "Equal Opportunity" - description = "Spread damage to 1,000 mobs via globe" +title = "Equal Opportunity" +description = "Spread damage to 1,000 mobs via globe" [advancement.challenge_tragoul_globe_5] - title = "Pain Dividend" - description = "Spread damage to 5+ mobs in one hit" +title = "Pain Dividend" +description = "Spread damage to 5+ mobs in one hit" [advancement.challenge_tragoul_healing_500] - title = "Vampire" - description = "Steal 500 hearts via life leech" +title = "Vampire" +description = "Steal 500 hearts via life leech" [advancement.challenge_tragoul_healing_10k] - title = "Blood Drinker" - description = "Steal 10,000 hearts" +title = "Blood Drinker" +description = "Steal 10,000 hearts" [advancement.challenge_tragoul_lance_200] - title = "Impaler" - description = "Spawn 200 blood lances" +title = "Impaler" +description = "Spawn 200 blood lances" [advancement.challenge_tragoul_lance_kills_100] - title = "Chain Killer" - description = "Kill 100 mobs with blood lances" +title = "Chain Killer" +description = "Kill 100 mobs with blood lances" [advancement.challenge_tragoul_pact_200] - title = "Blood Price" - description = "Sacrifice 200 hearts for power" +title = "Blood Price" +description = "Sacrifice 200 hearts for power" [advancement.challenge_tragoul_pact_kills_500] - title = "Pact Keeper" - description = "Kill 500 mobs while blood-empowered" +title = "Pact Keeper" +description = "Kill 500 mobs while blood-empowered" [advancement.challenge_tragoul_pact_all_in] - title = "All In" - description = "Activate blood pact below 3 hearts and kill" +title = "All In" +description = "Activate blood pact below 3 hearts and kill" [advancement.challenge_tragoul_bone_500] - title = "Bone Collector" - description = "Collect 500 bone orbs" +title = "Bone Collector" +description = "Collect 500 bone orbs" [advancement.challenge_tragoul_bone_5k] - title = "Harvester" - description = "Collect 5,000 bone orbs" +title = "Harvester" +description = "Collect 5,000 bone orbs" # --- UNARMED --- [advancement.challenge_unarmed_glass_100] - title = "Birthday Suit Brawler" - description = "Kill 100 mobs with no armor" +title = "Birthday Suit Brawler" +description = "Kill 100 mobs with no armor" [advancement.challenge_unarmed_glass_500] - title = "True Glass Cannon" - description = "Kill 500 mobs with no armor" +title = "True Glass Cannon" +description = "Kill 500 mobs with no armor" [advancement.challenge_unarmed_power_500] - title = "Bare Knuckle" - description = "Kill 500 mobs with bare fists" +title = "Bare Knuckle" +description = "Kill 500 mobs with bare fists" [advancement.challenge_unarmed_power_5k] - title = "One Punch" - description = "Kill 5,000 mobs with bare fists" +title = "One Punch" +description = "Kill 5,000 mobs with bare fists" [advancement.challenge_unarmed_sucker_500] - title = "Sucker Punch" - description = "Land 500 sprint punches" +title = "Sucker Punch" +description = "Land 500 sprint punches" [advancement.challenge_unarmed_knockout] - title = "Knockout Artist" - description = "One-shot kill 50 mobs with a sucker punch" +title = "Knockout Artist" +description = "One-shot kill 50 mobs with a sucker punch" [advancement.challenge_unarmed_combo_5k] - title = "Street Fighter" - description = "Land 5,000 total combo hits" +title = "Street Fighter" +description = "Land 5,000 total combo hits" [advancement.challenge_unarmed_combo_10] - title = "Combo Breaker" - description = "Reach a 10-hit combo" +title = "Combo Breaker" +description = "Reach a 10-hit combo" [advancement.challenge_unarmed_combo_25] - title = "Infinite Combo" - description = "Reach a 25-hit combo" +title = "Infinite Combo" +description = "Reach a 25-hit combo" [advancement.challenge_unarmed_charge_300] - title = "Battering Ram" - description = "Charge into 300 enemies" +title = "Battering Ram" +description = "Charge into 300 enemies" [advancement.challenge_unarmed_charge_kills_100] - title = "Unstoppable Force" - description = "Kill 100 mobs with battering charge" +title = "Unstoppable Force" +description = "Kill 100 mobs with battering charge" + +[excavation.tunneler] +name = "Tunneler" +description = "Sneak while digging soft blocks to carve a whole plane at once." +lore1 = "Bonus Blocks Per Dig" +lore2 = "Extra Durability Per Bonus Block" +lore = ["Bonus Blocks Per Dig", "Extra Durability Per Bonus Block"] + +[excavation.treasure_hunter] +name = "Treasure Hunter" +description = "Digging sand, gravel, mud, or clay can unearth archaeology treasure." +lore1 = "Treasure Chance" +lore2 = "Treasures roll from a weighted archaeology table" +lore = ["Treasure Chance", "Treasures roll from a weighted archaeology table"] + +[excavation.soft_fall] +name = "Soft Fall" +description = "Landing on soft diggable ground reduces fall damage, up to full negation." +lore1 = "Fall Damage Reduction" +lore2 = "Applies when landing on dirt, sand, gravel, clay, mud, or soul sand" +lore = ["Fall Damage Reduction", "Applies when landing on dirt, sand, gravel, clay, mud, or soul sand"] + +[excavation.earth_mover] +name = "Earth Mover" +description = "Sneak-right-click the air with a shovel to fling a wave of earth that knocks back and slows hostile mobs. Each wave costs hunger." +lore1 = "Wave Radius" +lore2 = "Knockback Force" +lore3 = "Slow Duration" +lore4 = "Wave Cooldown" +lore = ["Wave Radius", "Knockback Force", "Slow Duration", "Wave Cooldown", "Hunger Cost"] +lore5 = "Hunger Cost" + +[excavation.dowsing] +name = "Dowsing" +description = "Sneaking with a shovel pings the nearest hidden cave, water, or lava pocket with a directional trail." +lore1 = "Dowsing Range" +lore2 = "Dowsing Cooldown" +lore = ["Dowsing Range", "Dowsing Cooldown"] + +[excavation.burrow] +name = "Burrow" +description = "Sneak-right-click soft ground with a shovel to rapidly dig straight down, stopping before hazards. Each burrow costs hunger and tool durability." +lore1 = "Max Burrow Depth" +lore2 = "Durability Per Block" +lore3 = "Burrow Cooldown" +lore = ["Max Burrow Depth", "Durability Per Block", "Burrow Cooldown", "Hunger Cost"] +lore4 = "Hunger Cost" + +[excavation.grave_digger] +name = "Grave Digger" +description = "Digging earthen ground can unearth bone loot, and rarely disturbs a hostile grave." +lore1 = "Bone Loot Chance" +lore2 = "Disturbed Grave Chance" +lore = ["Bone Loot Chance", "Disturbed Grave Chance"] + +[excavation.mudlark] +name = "Mudlark" +description = "Bonus drops from muddy blocks, plus haste while digging in water or rain." +lore1 = "Bonus Drop Chance" +lore2 = "x Levels of haste while digging wet" +lore = ["Bonus Drop Chance", "x Levels of haste while digging wet"] + +[advancement.challenge_excavation_tunneler_10k] +title = "Boring Machine" +description = "Tunnel 10,000 bonus blocks" + +[advancement.challenge_excavation_treasure_500] +title = "Amateur Archaeologist" +description = "Unearth 500 treasures" + +[advancement.challenge_excavation_softfall_1k] +title = "Pillow Earth" +description = "Soak 1,000 fall damage on soft ground" + +[advancement.challenge_excavation_earthmover_250] +title = "Landslide" +description = "Unleash 250 earth waves" + +[advancement.challenge_excavation_dowsing_200] +title = "Water Witcher" +description = "Dowse 200 hidden pockets" + +[advancement.challenge_excavation_burrow_100] +title = "Going Down" +description = "Dig 100 burrows" + +[advancement.challenge_excavation_gravedigger_300] +title = "Rest In Pieces" +description = "Unearth 300 bone troves" + +[advancement.challenge_excavation_mudlark_1k] +title = "Riverbank Forager" +description = "Collect 1,000 bonus muddy drops" + +# chronos (new adaptations) + +[chronos.stasis_field] +name = "Stasis Field" +description = "Sneak and right click with an amethyst shard to deploy a stasis bubble that freezes projectiles in midair and locks down mobs inside. Consumes the shard on cast." +lore1 = "Stasis bubble radius" +lore2 = "Stasis bubble duration" +lore3 = "Cooldown" +lore4 = "Sneak + Right Click with an Amethyst Shard" +lore = ["Stasis bubble radius", "Stasis bubble duration", "Cooldown", "Sneak + Right Click with an Amethyst Shard"] +lore_cost_shard = "Consumes the amethyst shard on cast" + +[chronos.rewind] +name = "Rewind" +description = "Sneak and swap hands to mark a moment in time, then do it again within the window to snap back with health and hunger restored. Each rewind costs hunger." +lore1 = "Rewind window" +lore2 = "Cooldown after a rewind" +lore3 = "Sneak + Swap Hands to mark, repeat to rewind" +lore = ["Rewind window", "Cooldown after a rewind", "Sneak + Swap Hands to mark, repeat to rewind"] +lore_cost_hunger = "Hunger cost per rewind" + +[chronos.borrowed_time] +name = "Borrowed Time" +description = "A portion of incoming damage is deferred and quietly drained back once per second over the following seconds." +lore1 = "Damage deferred" +lore2 = "Payback window" +lore3 = "Deferred damage cannot be deferred again" +lore = ["Damage deferred", "Payback window", "Deferred damage cannot be deferred again"] + +[chronos.overtime] +name = "Overtime" +description = "Beneficial potion effects applied to you last longer, scaled by adaptation level." +lore1 = "Extra effect duration" +lore2 = "Maximum bonus per effect" +lore3 = "Only beneficial effects are extended" +lore = ["Extra effect duration", "Maximum bonus per effect", "Only beneficial effects are extended"] + +[chronos.accelerate] +name = "Accelerate" +description = "Passively accelerate time around you, occasionally growing nearby crops and fast-forwarding furnaces, smokers, blast furnaces, and brewing stands." +lore1 = "Aura radius" +lore2 = "Crop growth chance per pulse" +lore3 = "of a cook or brew fast-forwarded per pulse hit" +lore = ["Aura radius", "Crop growth chance per pulse", "of a cook or brew fast-forwarded per pulse hit"] + +[chronos.hourglass_guard] +name = "Hourglass Guard" +description = "A killing blow instead leaves you at half a heart, granting brief invulnerability and slowing nearby enemies, on a long cooldown." +lore1 = "Invulnerability after a save" +lore2 = "Cooldown" +lore3 = "Nearby enemies are briefly slowed" +lore = ["Invulnerability after a save", "Cooldown", "Nearby enemies are briefly slowed"] + +[chronos.pocket_watch] +name = "Pocket Watch" +description = "Sneak while falling with a clock in your inventory to drift in slow motion for a limited, level scaled duration each airtime." +lore1 = "Slow fall budget per airtime" +lore2 = "Budget refills on landing" +lore3 = "Hold Sneak while falling with a Clock in your inventory" +lore = ["Slow fall budget per airtime", "Budget refills on landing", "Hold Sneak while falling with a Clock in your inventory"] + +[chronos.deja_vu] +name = "Deja Vu" +description = "Your body remembers recent pain; taking the same kind of damage again within a short window hurts noticeably less." +lore1 = "Repeat damage absorbed" +lore2 = "Damage memory window" +lore3 = "The memory refreshes on every hit of the same kind" +lore = ["Repeat damage absorbed", "Damage memory window", "The memory refreshes on every hit of the same kind"] + +[advancement.challenge_chronos_stasis_50] +title = "Suspended Animation" +description = "Deploy 50 stasis fields" + +[advancement.challenge_chronos_stasis_500] +title = "Master of Stillness" +description = "Deploy 500 stasis fields" + +[advancement.challenge_chronos_rewind_50] +title = "Do Over" +description = "Complete 50 rewinds" + +[advancement.challenge_chronos_rewind_500] +title = "Unstuck in Time" +description = "Complete 500 rewinds" + +[advancement.challenge_chronos_borrowed_2500] +title = "Pay It Later" +description = "Defer 2,500 damage to the future" + +[advancement.challenge_chronos_overtime_1k] +title = "Overtime Pay" +description = "Gain 1,000 bonus seconds of potion duration" + +[advancement.challenge_chronos_accelerate_1k] +title = "Fast Forward" +description = "Accelerate 1,000 blocks" + +[advancement.challenge_chronos_hourglass_10] +title = "Out of Time" +description = "Cheat death 10 times with the hourglass" + +[advancement.challenge_chronos_pocket_watch_500] +title = "Falling Slowly" +description = "Drift through 500 seconds of slowed falls" + +[advancement.challenge_chronos_deja_vu_500] +title = "Haven't We Met?" +description = "Absorb 500 familiar damage through deja vu" + +[unarmed.disarm] +name = "Disarm" +description = "Bare-hand hits can knock the held item out of players and mobs alike, and mobs may have a worn armor piece knocked loose too." +lore1 = "Disarm Chance" +lore2 = "Per-Target Cooldown" +lore3 = "chance a disarmed mob also drops a worn armor piece" +lore = ["Disarm Chance", "Per-Target Cooldown", "chance a disarmed mob also drops a worn armor piece"] + +[unarmed.flurry] +name = "Flurry" +description = "Rapid consecutive bare-hand hits build flurry stacks that add bonus damage." +lore1 = "Max Flurry Stacks" +lore2 = "Damage Per Stack" +lore3 = "Flurry Window" +lore = ["Max Flurry Stacks", "Damage Per Stack", "Flurry Window"] + +[unarmed.pressure_point] +name = "Pressure Point" +description = "Bare-hand hits apply stacking slowness, with weakness at higher levels." +lore1 = "Max Slowness Stacks" +lore2 = "Max Weakness Stacks" +lore3 = "Weakness unlocks at higher levels" +lore = ["Max Slowness Stacks", "Max Weakness Stacks", "Weakness unlocks at higher levels"] + +[unarmed.shockwave_clap] +name = "Shockwave Clap" +description = "Sneak and punch the air to clap a shockwave that knocks back enemies in a cone. Each clap costs hunger." +lore1 = "Shockwave Range" +lore2 = "Knockback Force" +lore3 = "Clap Cooldown" +lore = ["Shockwave Range", "Knockback Force", "Clap Cooldown", "Hunger Cost"] +lore4 = "Hunger Cost" + +[unarmed.iron_fists] +name = "Iron Fists" +description = "Bare fists hit harder and punch through soft blocks faster." +lore1 = "Flat Punch Damage" +lore2 = "Soft Block Punch Haste" +lore = ["Flat Punch Damage", "Soft Block Punch Haste"] + +[unarmed.grapple] +name = "Grapple" +description = "Sneak-punch a mob to grab it, then hurl it where you look. Each throw adds exhaustion." +lore1 = "Hurl Force" +lore2 = "Grapple Cooldown" +lore3 = "Hit again or release sneak to hurl" +lore = ["Hurl Force", "Grapple Cooldown", "Hit again or release sneak to hurl", "Exhaustion per Throw"] +lore4 = "Exhaustion per Throw" + +[unarmed.second_wind] +name = "Second Wind" +description = "Bare-hand kills restore hunger and grant a short regeneration burst." +lore1 = "Hunger Restored" +lore2 = "Regeneration Duration" +lore = ["Hunger Restored", "Regeneration Duration"] + +[unarmed.meditation] +name = "Meditation" +description = "Meditate while sneaking, still, and empty-handed to slowly build absorption hearts." +lore1 = "Max Absorption" +lore2 = "Absorption Per Pulse" +lore3 = "Combat Lockout" +lore = ["Max Absorption", "Absorption Per Pulse", "Combat Lockout"] + +[advancement.challenge_unarmed_disarm_100] +title = "Butterfingers" +description = "Disarm 100 enemies with your bare hands" + +[advancement.challenge_unarmed_disarm_1k] +title = "Weapon Repossessor" +description = "Disarm 1,000 enemies with your bare hands" + +[advancement.challenge_unarmed_flurry_1k] +title = "Hands of Fury" +description = "Land 1,000 flurry hits" + +[advancement.challenge_unarmed_flurry_10k] +title = "Hurricane Hands" +description = "Land 10,000 flurry hits" + +[advancement.challenge_unarmed_pressure_500] +title = "Nerve Striker" +description = "Land 500 pressure point strikes" + +[advancement.challenge_unarmed_pressure_5k] +title = "Anatomy Scholar" +description = "Land 5,000 pressure point strikes" + +[advancement.challenge_unarmed_clap_250] +title = "Thunderclap" +description = "Knock back 250 enemies with shockwave claps" + +[advancement.challenge_unarmed_clap_2500] +title = "Sonic Boom" +description = "Knock back 2,500 enemies with shockwave claps" + +[advancement.challenge_unarmed_iron_1k] +title = "Knuckles of Steel" +description = "Land 1,000 iron fist punches" + +[advancement.challenge_unarmed_iron_10k] +title = "Living Anvil" +description = "Land 10,000 iron fist punches" + +[advancement.challenge_unarmed_grapple_100] +title = "Suplex City" +description = "Hurl 100 grabbed mobs" + +[advancement.challenge_unarmed_grapple_1k] +title = "Mob Launcher" +description = "Hurl 1,000 grabbed mobs" + +[advancement.challenge_unarmed_second_wind_100] +title = "Battle Snack" +description = "Trigger second wind 100 times" + +[advancement.challenge_unarmed_second_wind_1k] +title = "Endless Stamina" +description = "Trigger second wind 1,000 times" + +[advancement.challenge_unarmed_meditate_500] +title = "Inner Peace" +description = "Build 500 absorption health through meditation" + +[advancement.challenge_unarmed_meditate_5k] +title = "Transcendence" +description = "Build 5,000 absorption health through meditation" + +[architect.scaffolder] +name = "Scaffolder" +description = "Sneak-place blocks as temporary scaffolds that dissolve on their own and refund the block to you!" +lore1 = "Sneak-placed blocks dissolve automatically" +lore2 = "Seconds before a scaffold dissolves and refunds" +lore = ["Sneak-placed blocks dissolve automatically", "Seconds before a scaffold dissolves and refunds"] + +[architect.supply_line] +name = "Supply Line" +description = "When the stack in your hand runs out, it refills automatically from shulker boxes or bundles in your inventory!" +lore1 = "Hand auto-refills from shulkers and bundles" +lore2 = "Refills per minute" +lore = ["Hand auto-refills from shulkers and bundles", "Refills per minute"] + +[architect.steady_hands] +name = "Steady Hands" +description = "While bridging over open air you take no knockback, shrug off falls, and place with a steadier rhythm!" +lore1 = "No knockback while bridging" +lore2 = "Blocks of fall damage shielded" +lore = ["No knockback while bridging", "Blocks of fall damage shielded"] + +[architect.chalk_line] +name = "Chalk Line" +description = "Sneak-right-click a block while holding any block to snap a straight particle guide line along the axis you face. Do it again to clear it!" +lore1 = "Particle guide line for aligning builds" +lore2 = "Seconds the guide line stays visible" +lore = ["Particle guide line for aligning builds", "Seconds the guide line stays visible"] + +[architect.demolition] +name = "Mason's Eraser" +description = "Erase your own recent placements near-instantly and the block pops right back as a drop!" +lore1 = "Your fresh placements break near-instantly" +lore2 = "Seconds a placement counts as fresh" +lore = ["Your fresh placements break near-instantly", "Seconds a placement counts as fresh"] + +[architect.stonecutter_savant] +name = "Stonecutter Savant" +description = "Sneak-punch the air with an empty hand to open a stonecutter wherever you are, as long as you carry a stonecutter!" +lore1 = "Portable stonecutter on demand" +lore2 = "Seconds of cooldown between uses" +lore3 = "Requires a stonecutter item in your inventory" +lore4 = "Requires a stonecutter in your offhand" +lore = ["Portable stonecutter on demand", "Seconds of cooldown between uses", "Requires a stonecutter item in your inventory", "Requires a stonecutter in your offhand"] + +[advancement.challenge_architect_scaffolder_500] +title = "Temporary Measures" +description = "Place 500 temporary scaffolds" + +[advancement.challenge_architect_scaffolder_5k] +title = "Master Rigger" +description = "Place 5,000 temporary scaffolds" + +[advancement.challenge_architect_supply_line_100] +title = "Keep It Coming" +description = "Auto-refill your hand 100 times" + +[advancement.challenge_architect_supply_line_1k] +title = "Quartermaster" +description = "Auto-refill your hand 1,000 times" + +[advancement.challenge_architect_steady_hands_500] +title = "Nerves of Steel" +description = "Place 500 blocks while bridging" + +[advancement.challenge_architect_steady_hands_5k] +title = "Sky Walker" +description = "Place 5,000 blocks while bridging" + +[advancement.challenge_architect_chalk_line_50] +title = "Straight and True" +description = "Snap 50 chalk lines" + +[advancement.challenge_architect_chalk_line_500] +title = "Surveyor" +description = "Snap 500 chalk lines" + +[advancement.challenge_architect_demolition_500] +title = "Controlled Demolition" +description = "Demolish 500 of your own placements" + +[advancement.challenge_architect_demolition_5k] +title = "Wrecking Crew" +description = "Demolish 5,000 of your own placements" + +[advancement.challenge_architect_stonecutter_savant_50] +title = "Cutting Corners" +description = "Open 50 portable stonecutters" + +[advancement.challenge_architect_stonecutter_savant_500] +title = "Master Mason" +description = "Open 500 portable stonecutters" + +[pickaxe.tunnel_bore] +name = "Tunnel Bore" +description = "Sneak and mine stone-type blocks to bore out a whole tunnel face at once" +lore1 = "Sneak, and mine STONE" +lore2 = "tunnel face bored per block" +lore3 = "extra durability per bonus block" +lore = ["Sneak, and mine STONE", "tunnel face bored per block", "extra durability per bonus block"] + +[pickaxe.deep_core] +name = "Deep Core" +description = "Mining deepslate grants Haste so it digs like normal stone" +lore1 = "Mine DEEPSLATE to gain Haste" +lore2 = "Haste level while mining deepslate" +lore = ["Mine DEEPSLATE to gain Haste", "Haste level while mining deepslate"] + +[pickaxe.obsidian_rush] +name = "Obsidian Rush" +description = "Mining obsidian with a diamond or netherite pickaxe grants a strong Haste burst" +lore1 = "Mine OBSIDIAN with a diamond+ pickaxe" +lore2 = "Haste level while mining obsidian" +lore3 = "Also works on crying obsidian!" +lore = ["Mine OBSIDIAN with a diamond+ pickaxe", "Haste level while mining obsidian", "Also works on crying obsidian!"] + +[pickaxe.unbreakable_pact] +name = "Unbreakable Pact" +description = "Your pickaxe refuses to break, surviving at 1 durability instead" +lore1 = "Pickaxes never break, stopping at 1 durability" +lore2 = "chance to ignore durability loss entirely" +lore = ["Pickaxes never break, stopping at 1 durability", "chance to ignore durability loss entirely"] + +[pickaxe.repair_rhythm] +name = "Repair Rhythm" +description = "Sustained mining has a chance to restore durability to your pickaxe" +lore1 = "Each broken block can restore 1-2 durability" +lore2 = "chance to repair per broken block" +lore = ["Each broken block can restore 1-2 durability", "chance to repair per broken block"] + +[pickaxe.gem_polish] +name = "Gem Polish" +description = "Mining gem ores grants bonus XP orbs and a chance for an extra gem" +lore1 = "Mine diamond, emerald, lapis or amethyst" +lore2 = "chance for an extra matching gem" +lore3 = "bonus XP per gem ore mined" +lore = ["Mine diamond, emerald, lapis or amethyst", "chance for an extra matching gem", "bonus XP per gem ore mined"] + +[pickaxe.stone_skin] +name = "Stone Skin" +description = "Breaking stone-type blocks builds short-lived stacking damage resistance" +lore1 = "Mine stone to build Stone Skin stacks" +lore2 = "maximum Resistance level" +lore = ["Mine stone to build Stone Skin stacks", "maximum Resistance level"] + +[advancement.challenge_pickaxe_tunnelbore_10k] +title = "Boring Machine" +description = "Bore 10,000 bonus blocks while tunneling" + +[advancement.challenge_pickaxe_deepcore_5k] +title = "Core Sampler" +description = "Mine 5,000 deepslate blocks with Deep Core" + +[advancement.challenge_pickaxe_obsidianrush_1k] +title = "Black Gold" +description = "Mine 1,000 obsidian blocks with Obsidian Rush" + +[advancement.challenge_pickaxe_pact_100] +title = "Pact Keeper" +description = "Save your pickaxe from breaking 100 times" + +[advancement.challenge_pickaxe_rhythm_5k] +title = "Perpetual Pick" +description = "Restore 5,000 durability through Repair Rhythm" + +[advancement.challenge_pickaxe_gempolish_500] +title = "Lapidary" +description = "Polish out 500 extra gems" + +[advancement.challenge_pickaxe_stoneskin_10k] +title = "Hide of Granite" +description = "Build 10,000 Stone Skin stacks" + +[tragoul.corpse_explosion] +name = "Corpse Explosion" +description = "Mobs you kill detonate in a blood nova that damages nearby hostile mobs!" +lore1 = "Kills detonate the corpse, damaging nearby hostile mobs" +lore2 = "Nova Radius" +lore3 = "of the victim's max health added as nova damage" +lore = ["Kills detonate the corpse, damaging nearby hostile mobs", "Nova Radius", "of the victim's max health added as nova damage"] + +[tragoul.soul_siphon] +name = "Soul Siphon" +description = "Your melee hits drain life from your victims, healing you for part of the damage dealt!" +lore1 = "of melee damage dealt returned as health" +lore2 = "max health restored per second" +lore = ["of melee damage dealt returned as health", "max health restored per second"] + +[tragoul.skeletal_servant] +name = "Skeletal Servant" +description = "Sneak and right-click with bones to raise temporary skeletal servants, one living servant per level! Servants spawn with level-scaled random gear, inherit your other Tragoul perks, and immediately hunt any player who attacked you in the last few seconds. Summoning at the cap recycles your oldest servant. Consumes bones on summon, fewer at higher levels." +lore1 = "Sneak + Right-Click with bones in hand to summon a servant" +lore2 = "Servant Lifetime" +lore3 = "Bones consumed per summon" +lore4 = "Summon Cooldown" +lore5 = "Max living servants" +lore6 = "Servants gear up with your level, inherit your Tragoul perks, and prioritize players who attack you" +lore = ["Sneak + Right-Click with bones in hand to summon a servant", "Servant Lifetime", "Bones consumed per summon", "Summon Cooldown", "Max living servants", "Servants gear up with your level, inherit your Tragoul perks, and prioritize players who attack you"] + +[tragoul.marrow_armor] +name = "Marrow Armor" +description = "Bones in your inventory shatter to absorb part of incoming hits!" +lore1 = "Consumes 1 bone to absorb part of a hit" +lore2 = "of the hit absorbed per bone" +lore3 = "Internal Cooldown" +lore = ["Consumes 1 bone to absorb part of a hit", "of the hit absorbed per bone", "Internal Cooldown"] + +[tragoul.curse_of_frailty] +name = "Curse of Frailty" +description = "Enemies that dare strike you are cursed with weakness, and slowness at higher levels!" +lore1 = "Attackers are cursed with Weakness" +lore2 = "Curse Duration" +lore3 = "Attackers are also cursed with Slowness" +lore = ["Attackers are cursed with Weakness", "Curse Duration", "Attackers are also cursed with Slowness"] + +[tragoul.death_sense] +name = "Death Sense" +description = "Sense weakened hostile mobs near you - dying prey briefly glows through walls!" +lore1 = "Weakened hostile mobs near you briefly glow" +lore2 = "health or lower marks a mob as dying prey" +lore3 = "Sense Radius" +lore = ["Weakened hostile mobs near you briefly glow", "health or lower marks a mob as dying prey", "Sense Radius"] + +[tragoul.plague_bearer] +name = "Plague Bearer" +description = "Mobs that die poisoned or withered by you spread the affliction to nearby hostile mobs!" +lore1 = "Your poison and wither spread on death" +lore2 = "Spread Radius" +lore3 = "Spread Effect Duration" +lore = ["Your poison and wither spread on death", "Spread Radius", "Spread Effect Duration"] + +[tragoul.last_rites] +name = "Last Rites" +description = "A killing blow leaves you at 1 HP as a fleeting spirit instead of dying!" +lore1 = "Death is denied - you linger as a spirit at 1 HP" +lore2 = "Spirit Duration" +lore3 = "Cooldown" +lore = ["Death is denied - you linger as a spirit at 1 HP", "Spirit Duration", "Cooldown"] + +[advancement.challenge_tragoul_corpse_500] +title = "Walking Detonator" +description = "Detonate 500 hostile mobs with blood novas" + +[advancement.challenge_tragoul_corpse_5k] +title = "Field of Gore" +description = "Detonate 5,000 hostile mobs with blood novas" + +[advancement.challenge_tragoul_siphon_500] +title = "Soul Drinker" +description = "Siphon 500 health from your victims" + +[advancement.challenge_tragoul_siphon_10k] +title = "Hollowing Hunger" +description = "Siphon 10,000 health from your victims" + +[advancement.challenge_tragoul_servant_50] +title = "Necromancer" +description = "Summon 50 skeletal servants" + +[advancement.challenge_tragoul_servant_500] +title = "Lord of Bones" +description = "Summon 500 skeletal servants" + +[advancement.challenge_tragoul_marrow_500] +title = "Bone Shield" +description = "Absorb 500 damage with marrow armor" + +[advancement.challenge_tragoul_marrow_5k] +title = "Ossified" +description = "Absorb 5,000 damage with marrow armor" + +[advancement.challenge_tragoul_frailty_100] +title = "Brittle Touch" +description = "Curse 100 attackers with frailty" + +[advancement.challenge_tragoul_frailty_1k] +title = "Wasting Word" +description = "Curse 1,000 attackers with frailty" + +[advancement.challenge_tragoul_death_sense_1k] +title = "Reaper's Eye" +description = "Sense 1,000 dying prey through walls" + +[advancement.challenge_tragoul_plague_100] +title = "Patient Zero" +description = "Spread your plague to 100 hostile mobs" + +[advancement.challenge_tragoul_plague_1k] +title = "Pandemic" +description = "Spread your plague to 1,000 hostile mobs" + +[advancement.challenge_tragoul_last_rites_5] +title = "Not Today" +description = "Defy death 5 times" + +[advancement.challenge_tragoul_last_rites_50] +title = "Unfinished Business" +description = "Defy death 50 times" + +[snippets.xp] +inspired = "Inspired! Fresh activity bonus" diff --git a/src/main/resources/es_ES.toml b/src/main/resources/es_ES.toml index d25a59ab0..789cd53d7 100644 --- a/src/main/resources/es_ES.toml +++ b/src/main/resources/es_ES.toml @@ -2,2060 +2,2060 @@ [advancement] [advancement.challenge_move_1k] - title = "¡A moverse!" - description = "Camina mas de 1 kilometro (1,000 bloques)" +title = "¡A moverse!" +description = "Camina mas de 1 kilometro (1,000 bloques)" [advancement.challenge_sprint_5k] - title = "¡Corre un 5K!" - description = "Camina mas de 5 kilometros (5,000 bloques)" +title = "¡Corre un 5K!" +description = "Camina mas de 5 kilometros (5,000 bloques)" [advancement.challenge_sprint_50k] - title = "¡Zoom de 50K!" - description = "Camina mas de 50 kilometros (50,000 bloques)" +title = "¡Zoom de 50K!" +description = "Camina mas de 50 kilometros (50,000 bloques)" [advancement.challenge_sprint_500k] - title = "¡¡Atraviesa el Universo!!" - description = "Camina mas de 500 kilometros (500,000 bloques)" +title = "¡¡Atraviesa el Universo!!" +description = "Camina mas de 500 kilometros (500,000 bloques)" [advancement.challenge_sprint_marathon] - title = "¡Corre un maraton (literal)!" - description = "¡Esprintea mas de 42,195 bloques!" +title = "¡Corre un maraton (literal)!" +description = "¡Esprintea mas de 42,195 bloques!" [advancement.challenge_place_1k] - title = "¡Constructor novato!" - description = "Coloca 1,000 bloques" +title = "¡Constructor novato!" +description = "Coloca 1,000 bloques" [advancement.challenge_place_5k] - title = "¡Constructor intermedio!" - description = "Coloca 5,000 bloques" +title = "¡Constructor intermedio!" +description = "Coloca 5,000 bloques" [advancement.challenge_place_50k] - title = "¡Constructor avanzado!" - description = "Coloca 50,000 bloques" +title = "¡Constructor avanzado!" +description = "Coloca 50,000 bloques" [advancement.challenge_place_500k] - title = "¡Maestro constructor!" - description = "Coloca 500,000 bloques" +title = "¡Maestro constructor!" +description = "Coloca 500,000 bloques" [advancement.challenge_place_5m] - title = "¡Acolito de la Simetria!" - description = "¡LA REALIDAD ES TU PATIO DE JUEGOS! (5 millones de bloques)" +title = "¡Acolito de la Simetria!" +description = "¡LA REALIDAD ES TU PATIO DE JUEGOS! (5 millones de bloques)" [advancement.challenge_chop_1k] - title = "¡Lenador novato!" - description = "Tala 1,000 bloques" +title = "¡Lenador novato!" +description = "Tala 1,000 bloques" [advancement.challenge_chop_5k] - title = "¡Lenador intermedio!" - description = "Tala 5,000 bloques" +title = "¡Lenador intermedio!" +description = "Tala 5,000 bloques" [advancement.challenge_chop_50k] - title = "¡Lenador avanzado!" - description = "Tala 50,000 bloques" +title = "¡Lenador avanzado!" +description = "Tala 50,000 bloques" [advancement.challenge_chop_500k] - title = "¡Maestro lenador!" - description = "Tala 500,000 bloques" +title = "¡Maestro lenador!" +description = "Tala 500,000 bloques" [advancement.challenge_chop_5m] - title = "Jackson el Perro" - description = "¡El mejor chico bueno! (5 millones de bloques)" +title = "Jackson el Perro" +description = "¡El mejor chico bueno! (5 millones de bloques)" [advancement.challenge_block_1k] - title = "¡Apenas bloqueando!" - description = "Bloquea 1000 golpes" +title = "¡Apenas bloqueando!" +description = "Bloquea 1000 golpes" [advancement.challenge_block_5k] - title = "¡Bloquear es divertido!" - description = "Bloquea 5000 golpes" +title = "¡Bloquear es divertido!" +description = "Bloquea 5000 golpes" [advancement.challenge_block_50k] - title = "¡Bloquear es mi vida!" - description = "Bloquea 50,000 golpes" +title = "¡Bloquear es mi vida!" +description = "Bloquea 50,000 golpes" [advancement.challenge_block_500k] - title = "¡Bloquear es mi proposito!" - description = "Bloquea 500,000 golpes" +title = "¡Bloquear es mi proposito!" +description = "Bloquea 500,000 golpes" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Bloquea 5,000,000 golpes" +title = "Die Hand Die Verletzt" +description = "Bloquea 5,000,000 golpes" [advancement.challenge_brew_1k] - title = "¡Alquimista novato!" - description = "Consume 1000 pociones" +title = "¡Alquimista novato!" +description = "Consume 1000 pociones" [advancement.challenge_brew_5k] - title = "¡Alquimista intermedio!" - description = "Consume 5000 pociones" +title = "¡Alquimista intermedio!" +description = "Consume 5000 pociones" [advancement.challenge_brew_50k] - title = "¡Alquimista avanzado!" - description = "Consume 50,000 pociones" +title = "¡Alquimista avanzado!" +description = "Consume 50,000 pociones" [advancement.challenge_brew_500k] - title = "¡Maestro alquimista!" - description = "Consume 500,000 pociones" +title = "¡Maestro alquimista!" +description = "Consume 500,000 pociones" [advancement.challenge_brew_5m] - title = "El Alquimista" - description = "Consume 5,000,000 pociones" +title = "El Alquimista" +description = "Consume 5,000,000 pociones" [advancement.challenge_brewsplash_1k] - title = "¡Salpicador de pociones novato!" - description = "Salpica 1000 pociones" +title = "¡Salpicador de pociones novato!" +description = "Salpica 1000 pociones" [advancement.challenge_brewsplash_5k] - title = "¡Salpicador de pociones intermedio!" - description = "Salpica 5000 pociones" +title = "¡Salpicador de pociones intermedio!" +description = "Salpica 5000 pociones" [advancement.challenge_brewsplash_50k] - title = "¡Salpicador de pociones avanzado!" - description = "Salpica 50,000 pociones" +title = "¡Salpicador de pociones avanzado!" +description = "Salpica 50,000 pociones" [advancement.challenge_brewsplash_500k] - title = "¡Maestro salpicador de pociones!" - description = "Salpica 500,000 pociones" +title = "¡Maestro salpicador de pociones!" +description = "Salpica 500,000 pociones" [advancement.challenge_brewsplash_5m] - title = "El Maestro de las Salpicaduras" - description = "Salpica 5,000,000 pociones" +title = "El Maestro de las Salpicaduras" +description = "Salpica 5,000,000 pociones" [advancement.challenge_craft_1k] - title = "¡Artesano astuto!" - description = "Fabrica 1000 objetos" +title = "¡Artesano astuto!" +description = "Fabrica 1000 objetos" [advancement.challenge_craft_5k] - title = "¡Artesano cascarrabias!" - description = "Fabrica 5000 objetos" +title = "¡Artesano cascarrabias!" +description = "Fabrica 5000 objetos" [advancement.challenge_craft_50k] - title = "¡Artesano servil!" - description = "Fabrica 50,000 objetos" +title = "¡Artesano servil!" +description = "Fabrica 50,000 objetos" [advancement.challenge_craft_500k] - title = "¡Artesano cacofonico!" - description = "Fabrica 500,000 objetos" +title = "¡Artesano cacofonico!" +description = "Fabrica 500,000 objetos" [advancement.challenge_craft_5m] - title = "McCraftface Calamitoso" - description = "Fabrica 5,000,000 objetos" +title = "McCraftface Calamitoso" +description = "Fabrica 5,000,000 objetos" [advancement.challenge_enchant_1k] - title = "¡Encantador novato!" - description = "Encanta 1000 objetos" +title = "¡Encantador novato!" +description = "Encanta 1000 objetos" [advancement.challenge_enchant_5k] - title = "¡Encantador intermedio!" - description = "Encanta 5000 objetos" +title = "¡Encantador intermedio!" +description = "Encanta 5000 objetos" [advancement.challenge_enchant_50k] - title = "¡Encantador avanzado!" - description = "Encanta 50,000 objetos" +title = "¡Encantador avanzado!" +description = "Encanta 50,000 objetos" [advancement.challenge_enchant_500k] - title = "¡Maestro encantador!" - description = "Encanta 500,000 objetos" +title = "¡Maestro encantador!" +description = "Encanta 500,000 objetos" [advancement.challenge_enchant_5m] - title = "Encantador Enigmatico" - description = "Encanta 5,000,000 objetos" +title = "Encantador Enigmatico" +description = "Encanta 5,000,000 objetos" [advancement.challenge_excavate_1k] - title = "¡Excavador entusiasta!" - description = "Excava 1000 bloques" +title = "¡Excavador entusiasta!" +description = "Excava 1000 bloques" [advancement.challenge_excavate_5k] - title = "¡Excavador intermedio!" - description = "Excava 5000 bloques" +title = "¡Excavador intermedio!" +description = "Excava 5000 bloques" [advancement.challenge_excavate_50k] - title = "¡Excavador avanzado!" - description = "Excava 50,000 bloques" +title = "¡Excavador avanzado!" +description = "Excava 50,000 bloques" [advancement.challenge_excavate_500k] - title = "¡Maestro excavador!" - description = "Excava 500,000 bloques" +title = "¡Maestro excavador!" +description = "Excava 500,000 bloques" [advancement.challenge_excavate_5m] - title = "Excavador Enigmatico" - description = "Excava 5,000,000 bloques" +title = "Excavador Enigmatico" +description = "Excava 5,000,000 bloques" [advancement.horrible_person] - title = "Eres una persona horrible" - description = "Incomprensible, de verdad" +title = "Eres una persona horrible" +description = "Incomprensible, de verdad" [advancement.challenge_turtle_egg_smasher] - title = "¡Rompe-huevos de tortuga!" - description = "Rompe 100 huevos de tortuga" +title = "¡Rompe-huevos de tortuga!" +description = "Rompe 100 huevos de tortuga" [advancement.challenge_turtle_egg_annihilator] - title = "¡Aniquilador de huevos de tortuga!" - description = "Rompe 500 huevos de tortuga" +title = "¡Aniquilador de huevos de tortuga!" +description = "Rompe 500 huevos de tortuga" [advancement.challenge_novice_hunter] - title = "¡Cazador novato!" - description = "Mata 100 entidades" +title = "¡Cazador novato!" +description = "Mata 100 entidades" [advancement.challenge_intermediate_hunter] - title = "¡Cazador intermedio!" - description = "Mata 500 entidades" +title = "¡Cazador intermedio!" +description = "Mata 500 entidades" [advancement.challenge_advanced_hunter] - title = "¡Cazador avanzado!" - description = "Mata 5000 entidades" +title = "¡Cazador avanzado!" +description = "Mata 5000 entidades" [advancement.challenge_creeper_conqueror] - title = "¡Conquistador de Creepers!" - description = "Mata 50 creepers" +title = "¡Conquistador de Creepers!" +description = "Mata 50 creepers" [advancement.challenge_creeper_annihilator] - title = "¡Aniquilador de Creepers!" - description = "Mata 200 creepers" +title = "¡Aniquilador de Creepers!" +description = "Mata 200 creepers" [advancement.challenge_pickaxe_1k] - title = "Minero novato" - description = "Rompe 1000 bloques" +title = "Minero novato" +description = "Rompe 1000 bloques" [advancement.challenge_pickaxe_5k] - title = "Minero habil" - description = "Rompe 5000 bloques" +title = "Minero habil" +description = "Rompe 5000 bloques" [advancement.challenge_pickaxe_50k] - title = "Minero experto" - description = "Rompe 50,000 bloques" +title = "Minero experto" +description = "Rompe 50,000 bloques" [advancement.challenge_pickaxe_500k] - title = "Maestro minero" - description = "Rompe 500,000 bloques" +title = "Maestro minero" +description = "Rompe 500,000 bloques" [advancement.challenge_pickaxe_5m] - title = "Minero legendario" - description = "Rompe 5,000,000 bloques" +title = "Minero legendario" +description = "Rompe 5,000,000 bloques" [advancement.challenge_eat_100] - title = "¡Tanto para comer!" - description = "¡Come mas de 100 objetos!" +title = "¡Tanto para comer!" +description = "¡Come mas de 100 objetos!" [advancement.challenge_eat_1000] - title = "¡Hambre insaciable!" - description = "¡Come mas de 1,000 objetos!" +title = "¡Hambre insaciable!" +description = "¡Come mas de 1,000 objetos!" [advancement.challenge_eat_10000] - title = "¡HAMBRE ETERNA!" - description = "¡Come mas de 10,000 objetos!" +title = "¡HAMBRE ETERNA!" +description = "¡Come mas de 10,000 objetos!" [advancement.challenge_harvest_100] - title = "Cosecha completa" - description = "¡Cosecha mas de 100 cultivos!" +title = "Cosecha completa" +description = "¡Cosecha mas de 100 cultivos!" [advancement.challenge_harvest_1000] - title = "Gran cosecha" - description = "¡Cosecha mas de 1,000 cultivos!" +title = "Gran cosecha" +description = "¡Cosecha mas de 1,000 cultivos!" [advancement.challenge_swim_1nm] - title = "¡Submarino humano!" - description = "Nada 1 milla nautica (1,852 bloques)" +title = "¡Submarino humano!" +description = "Nada 1 milla nautica (1,852 bloques)" [advancement.challenge_sneak_1k] - title = "Dolor de rodillas" - description = "Agachate mas de un kilometro (1,000 bloques)" +title = "Dolor de rodillas" +description = "Agachate mas de un kilometro (1,000 bloques)" [advancement.challenge_sneak_5k] - title = "Caminante de Sombras" - description = "Agachate mas de 5,000 bloques" +title = "Caminante de Sombras" +description = "Agachate mas de 5,000 bloques" [advancement.challenge_sneak_20k] - title = "Fantasma" - description = "Agachate mas de 20,000 bloques" +title = "Fantasma" +description = "Agachate mas de 20,000 bloques" [advancement.challenge_swim_5k] - title = "Buceador Profundo" - description = "Nada mas de 5,000 bloques" +title = "Buceador Profundo" +description = "Nada mas de 5,000 bloques" [advancement.challenge_swim_20k] - title = "Elegido de Poseidon" - description = "Nada mas de 20,000 bloques" +title = "Elegido de Poseidon" +description = "Nada mas de 20,000 bloques" [advancement.challenge_sword_100] - title = "Primera Sangre" - description = "Golpea 100 veces con una espada" +title = "Primera Sangre" +description = "Golpea 100 veces con una espada" [advancement.challenge_sword_1k] - title = "Danzante de Espadas" - description = "Golpea 1,000 veces con una espada" +title = "Danzante de Espadas" +description = "Golpea 1,000 veces con una espada" [advancement.challenge_sword_10k] - title = "Mil Cortes" - description = "Golpea 10,000 veces con una espada" +title = "Mil Cortes" +description = "Golpea 10,000 veces con una espada" [advancement.challenge_unarmed_100] - title = "Peleador Callejero" - description = "Golpea 100 veces sin armas" +title = "Peleador Callejero" +description = "Golpea 100 veces sin armas" [advancement.challenge_unarmed_1k] - title = "Punos de Hierro" - description = "Golpea 1,000 veces sin armas" +title = "Punos de Hierro" +description = "Golpea 1,000 veces sin armas" [advancement.challenge_unarmed_10k] - title = "One Punch" - description = "Golpea 10,000 veces sin armas" +title = "One Punch" +description = "Golpea 10,000 veces sin armas" [advancement.challenge_trag_1k] - title = "Precio de Sangre" - description = "Recibe 1,000 de dano" +title = "Precio de Sangre" +description = "Recibe 1,000 de dano" [advancement.challenge_trag_10k] - title = "Marea Carmesi" - description = "Recibe 10,000 de dano" +title = "Marea Carmesi" +description = "Recibe 10,000 de dano" [advancement.challenge_trag_100k] - title = "Avatar del Sufrimiento" - description = "Recibe 100,000 de dano" +title = "Avatar del Sufrimiento" +description = "Recibe 100,000 de dano" [advancement.challenge_ranged_100] - title = "Practica de Tiro" - description = "Dispara 100 proyectiles" +title = "Practica de Tiro" +description = "Dispara 100 proyectiles" [advancement.challenge_ranged_1k] - title = "Ojo de Halcon" - description = "Dispara 1,000 proyectiles" +title = "Ojo de Halcon" +description = "Dispara 1,000 proyectiles" [advancement.challenge_ranged_10k] - title = "Tormenta de Flechas" - description = "Dispara 10,000 proyectiles" +title = "Tormenta de Flechas" +description = "Dispara 10,000 proyectiles" [advancement.challenge_chronos_1h] - title = "Tic Tac" - description = "Pasa 1 hora en linea" +title = "Tic Tac" +description = "Pasa 1 hora en linea" [advancement.challenge_chronos_24h] - title = "Arenas del Tiempo" - description = "Pasa 24 horas en linea" +title = "Arenas del Tiempo" +description = "Pasa 24 horas en linea" [advancement.challenge_chronos_168h] - title = "Eterno" - description = "Pasa 168 horas (1 semana) en linea" +title = "Eterno" +description = "Pasa 168 horas (1 semana) en linea" [advancement.challenge_nether_50] - title = "Portero del Infierno" - description = "Mata 50 criaturas del Nether" +title = "Portero del Infierno" +description = "Mata 50 criaturas del Nether" [advancement.challenge_nether_500] - title = "Guardian del Abismo" - description = "Mata 500 criaturas del Nether" +title = "Guardian del Abismo" +description = "Mata 500 criaturas del Nether" [advancement.challenge_nether_5k] - title = "Senor del Nether" - description = "Mata 5,000 criaturas del Nether" +title = "Senor del Nether" +description = "Mata 5,000 criaturas del Nether" [advancement.challenge_rift_50] - title = "Anomalia Espacial" - description = "Teletransportate 50 veces" +title = "Anomalia Espacial" +description = "Teletransportate 50 veces" [advancement.challenge_rift_500] - title = "Caminante del Vacio" - description = "Teletransportate 500 veces" +title = "Caminante del Vacio" +description = "Teletransportate 500 veces" [advancement.challenge_rift_5k] - title = "Entre Mundos" - description = "Teletransportate 5,000 veces" +title = "Entre Mundos" +description = "Teletransportate 5,000 veces" [advancement.challenge_taming_10] - title = "Susurrador de Animales" - description = "Cria 10 animales" +title = "Susurrador de Animales" +description = "Cria 10 animales" [advancement.challenge_taming_50] - title = "Lider de la Manada" - description = "Cria 50 animales" +title = "Lider de la Manada" +description = "Cria 50 animales" [advancement.challenge_taming_500] - title = "Maestro de Bestias" - description = "Cria 500 animales" +title = "Maestro de Bestias" +description = "Cria 500 animales" # Agility [advancement.challenge_sprint_dist_5k] - title = "Demonio Veloz" - description = "Corre mas de 5 Kilometros (5.000 bloques)" +title = "Demonio Veloz" +description = "Corre mas de 5 Kilometros (5.000 bloques)" [advancement.challenge_sprint_dist_50k] - title = "Piernas de Rayo" - description = "Corre mas de 50 Kilometros (50.000 bloques)" +title = "Piernas de Rayo" +description = "Corre mas de 50 Kilometros (50.000 bloques)" [advancement.challenge_agility_swim_1k] - title = "Zancudo Acuatico" - description = "Nada mas de 1 Kilometro (1.000 bloques)" +title = "Zancudo Acuatico" +description = "Nada mas de 1 Kilometro (1.000 bloques)" [advancement.challenge_agility_swim_10k] - title = "Viajero Acuatico" - description = "Nada mas de 10 Kilometros (10.000 bloques)" +title = "Viajero Acuatico" +description = "Nada mas de 10 Kilometros (10.000 bloques)" [advancement.challenge_fly_1k] - title = "Danzarin del Cielo" - description = "Vuela mas de 1 Kilometro (1.000 bloques)" +title = "Danzarin del Cielo" +description = "Vuela mas de 1 Kilometro (1.000 bloques)" [advancement.challenge_fly_10k] - title = "Jinete del Viento" - description = "Vuela mas de 10 Kilometros (10.000 bloques)" +title = "Jinete del Viento" +description = "Vuela mas de 10 Kilometros (10.000 bloques)" [advancement.challenge_agility_sneak_500] - title = "Pasos Silenciosos" - description = "Camina agachado mas de 500 bloques" +title = "Pasos Silenciosos" +description = "Camina agachado mas de 500 bloques" [advancement.challenge_agility_sneak_5k] - title = "Pasos Fantasma" - description = "Camina agachado mas de 5 Kilometros (5.000 bloques)" +title = "Pasos Fantasma" +description = "Camina agachado mas de 5 Kilometros (5.000 bloques)" # Architect [advancement.challenge_demolish_500] - title = "Equipo de Demolicion" - description = "Rompe 500 bloques" +title = "Equipo de Demolicion" +description = "Rompe 500 bloques" [advancement.challenge_demolish_5k] - title = "Bola de Demolicion" - description = "Rompe 5.000 bloques" +title = "Bola de Demolicion" +description = "Rompe 5.000 bloques" [advancement.challenge_value_placed_10k] - title = "Constructor Valioso" - description = "Coloca bloques con un valor de 10.000" +title = "Constructor Valioso" +description = "Coloca bloques con un valor de 10.000" [advancement.challenge_value_placed_100k] - title = "Maestro Arquitecto" - description = "Coloca bloques con un valor de 100.000" +title = "Maestro Arquitecto" +description = "Coloca bloques con un valor de 100.000" [advancement.challenge_demolish_val_5k] - title = "Experto en Salvamento" - description = "Recupera 5.000 de valor de bloques por demolicion" +title = "Experto en Salvamento" +description = "Recupera 5.000 de valor de bloques por demolicion" [advancement.challenge_demolish_val_50k] - title = "Deconstruccion Total" - description = "Recupera 50.000 de valor de bloques por demolicion" +title = "Deconstruccion Total" +description = "Recupera 50.000 de valor de bloques por demolicion" [advancement.challenge_high_build_100] - title = "Constructor Celestial" - description = "Coloca 100 bloques por encima de Y=128" +title = "Constructor Celestial" +description = "Coloca 100 bloques por encima de Y=128" [advancement.challenge_high_build_1k] - title = "Arquitecto de las Nubes" - description = "Coloca 1.000 bloques por encima de Y=128" +title = "Arquitecto de las Nubes" +description = "Coloca 1.000 bloques por encima de Y=128" # Axes [advancement.challenge_axe_swing_500] - title = "Hachero" - description = "Balancea tu hacha 500 veces" +title = "Hachero" +description = "Balancea tu hacha 500 veces" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Balancea tu hacha 5.000 veces" +title = "Berserker" +description = "Balancea tu hacha 5.000 veces" [advancement.challenge_axe_damage_1k] - title = "Cuchilla" - description = "Inflige 1.000 de dano con hachas" +title = "Cuchilla" +description = "Inflige 1.000 de dano con hachas" [advancement.challenge_axe_damage_10k] - title = "Hacha del Verdugo" - description = "Inflige 10.000 de dano con hachas" +title = "Hacha del Verdugo" +description = "Inflige 10.000 de dano con hachas" [advancement.challenge_axe_value_5k] - title = "Comerciante de Madera" - description = "Cosecha madera con un valor de 5.000" +title = "Comerciante de Madera" +description = "Cosecha madera con un valor de 5.000" [advancement.challenge_axe_value_50k] - title = "Baron Maderero" - description = "Cosecha madera con un valor de 50.000" +title = "Baron Maderero" +description = "Cosecha madera con un valor de 50.000" [advancement.challenge_leaves_500] - title = "Soplador de Hojas" - description = "Despeja 500 bloques de hojas con un hacha" +title = "Soplador de Hojas" +description = "Despeja 500 bloques de hojas con un hacha" [advancement.challenge_leaves_5k] - title = "Defoliador" - description = "Despeja 5.000 bloques de hojas con un hacha" +title = "Defoliador" +description = "Despeja 5.000 bloques de hojas con un hacha" # Blocking [advancement.challenge_block_dmg_1k] - title = "Absorbente de Dano" - description = "Bloquea 1.000 de dano con un escudo" +title = "Absorbente de Dano" +description = "Bloquea 1.000 de dano con un escudo" [advancement.challenge_block_dmg_10k] - title = "Escudo Humano" - description = "Bloquea 10.000 de dano con un escudo" +title = "Escudo Humano" +description = "Bloquea 10.000 de dano con un escudo" [advancement.challenge_block_proj_100] - title = "Deflector de Flechas" - description = "Bloquea 100 proyectiles con un escudo" +title = "Deflector de Flechas" +description = "Bloquea 100 proyectiles con un escudo" [advancement.challenge_block_proj_1k] - title = "Escudo Anti-Proyectiles" - description = "Bloquea 1.000 proyectiles con un escudo" +title = "Escudo Anti-Proyectiles" +description = "Bloquea 1.000 proyectiles con un escudo" [advancement.challenge_block_melee_500] - title = "Maestro del Parry" - description = "Bloquea 500 ataques cuerpo a cuerpo con un escudo" +title = "Maestro del Parry" +description = "Bloquea 500 ataques cuerpo a cuerpo con un escudo" [advancement.challenge_block_melee_5k] - title = "Fortaleza de Hierro" - description = "Bloquea 5.000 ataques cuerpo a cuerpo con un escudo" +title = "Fortaleza de Hierro" +description = "Bloquea 5.000 ataques cuerpo a cuerpo con un escudo" [advancement.challenge_block_heavy_50] - title = "Tanque" - description = "Bloquea 50 ataques pesados (mas de 5 de dano)" +title = "Tanque" +description = "Bloquea 50 ataques pesados (mas de 5 de dano)" [advancement.challenge_block_heavy_500] - title = "Objeto Inamovible" - description = "Bloquea 500 ataques pesados (mas de 5 de dano)" +title = "Objeto Inamovible" +description = "Bloquea 500 ataques pesados (mas de 5 de dano)" # Brewing [advancement.challenge_brew_stands_10] - title = "Preparacion del Alquimista" - description = "Coloca 10 soportes para pociones" +title = "Preparacion del Alquimista" +description = "Coloca 10 soportes para pociones" [advancement.challenge_brew_stands_50] - title = "Fabrica de Pociones" - description = "Coloca 50 soportes para pociones" +title = "Fabrica de Pociones" +description = "Coloca 50 soportes para pociones" [advancement.challenge_brew_strong_25] - title = "Brebaje Fuerte" - description = "Consume 25 pociones mejoradas" +title = "Brebaje Fuerte" +description = "Consume 25 pociones mejoradas" [advancement.challenge_brew_strong_250] - title = "Potencia Maxima" - description = "Consume 250 pociones mejoradas" +title = "Potencia Maxima" +description = "Consume 250 pociones mejoradas" [advancement.challenge_brew_splash_hits_50] - title = "Zona de Salpicadura" - description = "Golpea a 50 entidades con pociones arrojadizas" +title = "Zona de Salpicadura" +description = "Golpea a 50 entidades con pociones arrojadizas" [advancement.challenge_brew_splash_hits_500] - title = "Doctor de la Peste" - description = "Golpea a 500 entidades con pociones arrojadizas" +title = "Doctor de la Peste" +description = "Golpea a 500 entidades con pociones arrojadizas" # Chronos [advancement.challenge_active_dist_1k] - title = "Inquieto" - description = "Viaja 1 Kilometro mientras estas activo" +title = "Inquieto" +description = "Viaja 1 Kilometro mientras estas activo" [advancement.challenge_active_dist_10k] - title = "Explorador" - description = "Viaja 10 Kilometros mientras estas activo" +title = "Explorador" +description = "Viaja 10 Kilometros mientras estas activo" [advancement.challenge_active_dist_100k] - title = "Movimiento Perpetuo" - description = "Viaja 100 Kilometros mientras estas activo" +title = "Movimiento Perpetuo" +description = "Viaja 100 Kilometros mientras estas activo" [advancement.challenge_beds_10] - title = "Madrugador" - description = "Duerme en una cama 10 veces" +title = "Madrugador" +description = "Duerme en una cama 10 veces" [advancement.challenge_beds_100] - title = "Saltador del Tiempo" - description = "Duerme en una cama 100 veces" +title = "Saltador del Tiempo" +description = "Duerme en una cama 100 veces" [advancement.challenge_chronos_tp_50] - title = "Cambio Temporal" - description = "Teletransportate 50 veces" +title = "Cambio Temporal" +description = "Teletransportate 50 veces" [advancement.challenge_chronos_tp_500] - title = "Distorsion Temporal" - description = "Teletransportate 500 veces" +title = "Distorsion Temporal" +description = "Teletransportate 500 veces" [advancement.challenge_chronos_deaths_10] - title = "Mortal" - description = "Muere 10 veces" +title = "Mortal" +description = "Muere 10 veces" [advancement.challenge_chronos_deaths_100] - title = "Desafiante de la Muerte" - description = "Muere 100 veces" +title = "Desafiante de la Muerte" +description = "Muere 100 veces" # Crafting [advancement.challenge_craft_value_10k] - title = "Valor Artesanal" - description = "Fabrica objetos con un valor total de 10.000" +title = "Valor Artesanal" +description = "Fabrica objetos con un valor total de 10.000" [advancement.challenge_craft_value_100k] - title = "Artesano" - description = "Fabrica objetos con un valor total de 100.000" +title = "Artesano" +description = "Fabrica objetos con un valor total de 100.000" [advancement.challenge_craft_tools_25] - title = "Herrero de Herramientas" - description = "Fabrica 25 herramientas" +title = "Herrero de Herramientas" +description = "Fabrica 25 herramientas" [advancement.challenge_craft_tools_250] - title = "Maestro Forjador" - description = "Fabrica 250 herramientas" +title = "Maestro Forjador" +description = "Fabrica 250 herramientas" [advancement.challenge_craft_armor_25] - title = "Herrero de Armaduras" - description = "Fabrica 25 piezas de armadura" +title = "Herrero de Armaduras" +description = "Fabrica 25 piezas de armadura" [advancement.challenge_craft_armor_250] - title = "Maestro Armero" - description = "Fabrica 250 piezas de armadura" +title = "Maestro Armero" +description = "Fabrica 250 piezas de armadura" [advancement.challenge_craft_mass_25k] - title = "Productor en Masa" - description = "Fabrica 25.000 objetos" +title = "Productor en Masa" +description = "Fabrica 25.000 objetos" [advancement.challenge_craft_mass_250k] - title = "Revolucion Industrial" - description = "Fabrica 250.000 objetos" +title = "Revolucion Industrial" +description = "Fabrica 250.000 objetos" # Discovery [advancement.challenge_discover_items_50] - title = "Coleccionista" - description = "Descubre 50 objetos unicos" +title = "Coleccionista" +description = "Descubre 50 objetos unicos" [advancement.challenge_discover_items_250] - title = "Catalogador" - description = "Descubre 250 objetos unicos" +title = "Catalogador" +description = "Descubre 250 objetos unicos" [advancement.challenge_discover_blocks_50] - title = "Topografo" - description = "Descubre 50 bloques unicos" +title = "Topografo" +description = "Descubre 50 bloques unicos" [advancement.challenge_discover_blocks_250] - title = "Geologo" - description = "Descubre 250 bloques unicos" +title = "Geologo" +description = "Descubre 250 bloques unicos" [advancement.challenge_discover_mobs_25] - title = "Observador" - description = "Descubre 25 criaturas unicas" +title = "Observador" +description = "Descubre 25 criaturas unicas" [advancement.challenge_discover_mobs_75] - title = "Naturalista" - description = "Descubre 75 criaturas unicas" +title = "Naturalista" +description = "Descubre 75 criaturas unicas" [advancement.challenge_discover_biomes_10] - title = "Errante" - description = "Descubre 10 biomas unicos" +title = "Errante" +description = "Descubre 10 biomas unicos" [advancement.challenge_discover_biomes_40] - title = "Trotamundos" - description = "Descubre 40 biomas unicos" +title = "Trotamundos" +description = "Descubre 40 biomas unicos" [advancement.challenge_discover_foods_10] - title = "Gourmet" - description = "Descubre 10 alimentos unicos" +title = "Gourmet" +description = "Descubre 10 alimentos unicos" [advancement.challenge_discover_foods_30] - title = "Maestro Culinario" - description = "Descubre 30 alimentos unicos" +title = "Maestro Culinario" +description = "Descubre 30 alimentos unicos" # Enchanting [advancement.challenge_enchant_power_100] - title = "Tejedor de Poder" - description = "Acumula 100 de poder de encantamiento" +title = "Tejedor de Poder" +description = "Acumula 100 de poder de encantamiento" [advancement.challenge_enchant_power_1k] - title = "Maestro Arcano" - description = "Acumula 1.000 de poder de encantamiento" +title = "Maestro Arcano" +description = "Acumula 1.000 de poder de encantamiento" [advancement.challenge_enchant_levels_1k] - title = "Gastador de Niveles" - description = "Gasta 1.000 niveles de experiencia en encantamientos" +title = "Gastador de Niveles" +description = "Gasta 1.000 niveles de experiencia en encantamientos" [advancement.challenge_enchant_levels_10k] - title = "Sumidero de XP" - description = "Gasta 10.000 niveles de experiencia en encantamientos" +title = "Sumidero de XP" +description = "Gasta 10.000 niveles de experiencia en encantamientos" [advancement.challenge_enchant_high_25] - title = "Apostador Fuerte" - description = "Realiza 25 encantamientos de nivel maximo" +title = "Apostador Fuerte" +description = "Realiza 25 encantamientos de nivel maximo" [advancement.challenge_enchant_high_250] - title = "Encantador Legendario" - description = "Realiza 250 encantamientos de nivel maximo" +title = "Encantador Legendario" +description = "Realiza 250 encantamientos de nivel maximo" [advancement.challenge_enchant_total_500] - title = "Quemador de Niveles" - description = "Gasta 500 niveles totales en encantamientos" +title = "Quemador de Niveles" +description = "Gasta 500 niveles totales en encantamientos" [advancement.challenge_enchant_total_5k] - title = "Inversion Arcana" - description = "Gasta 5.000 niveles totales en encantamientos" +title = "Inversion Arcana" +description = "Gasta 5.000 niveles totales en encantamientos" # Excavation [advancement.challenge_dig_swing_500] - title = "Cavador" - description = "Balancea tu pala 500 veces" +title = "Cavador" +description = "Balancea tu pala 500 veces" [advancement.challenge_dig_swing_5k] - title = "Excavador" - description = "Balancea tu pala 5.000 veces" +title = "Excavador" +description = "Balancea tu pala 5.000 veces" [advancement.challenge_dig_damage_1k] - title = "Caballero de la Pala" - description = "Inflige 1.000 de dano con una pala" +title = "Caballero de la Pala" +description = "Inflige 1.000 de dano con una pala" [advancement.challenge_dig_damage_10k] - title = "Maestro de la Pala" - description = "Inflige 10.000 de dano con una pala" +title = "Maestro de la Pala" +description = "Inflige 10.000 de dano con una pala" [advancement.challenge_dig_value_5k] - title = "Comerciante de Tierra" - description = "Excava bloques con un valor de 5.000" +title = "Comerciante de Tierra" +description = "Excava bloques con un valor de 5.000" [advancement.challenge_dig_value_50k] - title = "Baron de la Tierra" - description = "Excava bloques con un valor de 50.000" +title = "Baron de la Tierra" +description = "Excava bloques con un valor de 50.000" [advancement.challenge_dig_gravel_500] - title = "Triturador de Grava" - description = "Excava 500 bloques de grava, arena o arcilla" +title = "Triturador de Grava" +description = "Excava 500 bloques de grava, arena o arcilla" [advancement.challenge_dig_gravel_5k] - title = "Cernidor de Arena" - description = "Excava 5.000 bloques de grava, arena o arcilla" +title = "Cernidor de Arena" +description = "Excava 5.000 bloques de grava, arena o arcilla" # Herbalism [advancement.challenge_plant_100] - title = "Sembrador" - description = "Planta 100 cultivos" +title = "Sembrador" +description = "Planta 100 cultivos" [advancement.challenge_plant_1k] - title = "Mano Verde" - description = "Planta 1.000 cultivos" +title = "Mano Verde" +description = "Planta 1.000 cultivos" [advancement.challenge_plant_5k] - title = "Baron Agricola" - description = "Planta 5.000 cultivos" +title = "Baron Agricola" +description = "Planta 5.000 cultivos" [advancement.challenge_compost_50] - title = "Reciclador" - description = "Composta 50 objetos" +title = "Reciclador" +description = "Composta 50 objetos" [advancement.challenge_compost_500] - title = "Enriquecedor del Suelo" - description = "Composta 500 objetos" +title = "Enriquecedor del Suelo" +description = "Composta 500 objetos" [advancement.challenge_shear_50] - title = "Esquilador" - description = "Esquila 50 entidades" +title = "Esquilador" +description = "Esquila 50 entidades" [advancement.challenge_shear_250] - title = "Maestro del Rebano" - description = "Esquila 250 entidades" +title = "Maestro del Rebano" +description = "Esquila 250 entidades" # Hunter [advancement.challenge_kills_500] - title = "Cazador" - description = "Elimina 500 criaturas" +title = "Cazador" +description = "Elimina 500 criaturas" [advancement.challenge_kills_5k] - title = "Verdugo" - description = "Elimina 5.000 criaturas" +title = "Verdugo" +description = "Elimina 5.000 criaturas" [advancement.challenge_boss_1] - title = "Retador de Jefes" - description = "Derrota a un jefe" +title = "Retador de Jefes" +description = "Derrota a un jefe" [advancement.challenge_boss_10] - title = "Asesino de Leyendas" - description = "Derrota a 10 jefes" +title = "Asesino de Leyendas" +description = "Derrota a 10 jefes" # Nether [advancement.challenge_wither_dmg_500] - title = "Marchito" - description = "Soporta 500 de dano de Wither" +title = "Marchito" +description = "Soporta 500 de dano de Wither" [advancement.challenge_wither_dmg_5k] - title = "Superviviente de la Plaga" - description = "Soporta 5.000 de dano de Wither" +title = "Superviviente de la Plaga" +description = "Soporta 5.000 de dano de Wither" [advancement.challenge_wither_skel_25] - title = "Coleccionista de Huesos" - description = "Elimina 25 esqueletos Wither" +title = "Coleccionista de Huesos" +description = "Elimina 25 esqueletos Wither" [advancement.challenge_wither_skel_250] - title = "Azote de Esqueletos" - description = "Elimina 250 esqueletos Wither" +title = "Azote de Esqueletos" +description = "Elimina 250 esqueletos Wither" [advancement.challenge_wither_boss_1] - title = "Destructor del Wither" - description = "Derrota al Wither" +title = "Destructor del Wither" +description = "Derrota al Wither" [advancement.challenge_wither_boss_10] - title = "Dominador del Nether" - description = "Derrota al Wither 10 veces" +title = "Dominador del Nether" +description = "Derrota al Wither 10 veces" [advancement.challenge_roses_10] - title = "Jardinero de la Muerte" - description = "Rompe 10 rosas de Wither" +title = "Jardinero de la Muerte" +description = "Rompe 10 rosas de Wither" [advancement.challenge_roses_100] - title = "Coleccionista de Plaga" - description = "Rompe 100 rosas de Wither" +title = "Coleccionista de Plaga" +description = "Rompe 100 rosas de Wither" # Pickaxes [advancement.challenge_pick_swing_500] - title = "Brazo de Minero" - description = "Balancea tu pico 500 veces" +title = "Brazo de Minero" +description = "Balancea tu pico 500 veces" [advancement.challenge_pick_swing_5k] - title = "Constructor de Tuneles" - description = "Balancea tu pico 5.000 veces" +title = "Constructor de Tuneles" +description = "Balancea tu pico 5.000 veces" [advancement.challenge_pick_damage_1k] - title = "Luchador de Pico" - description = "Inflige 1.000 de dano con un pico" +title = "Luchador de Pico" +description = "Inflige 1.000 de dano con un pico" [advancement.challenge_pick_damage_10k] - title = "Pico de Guerra" - description = "Inflige 10.000 de dano con un pico" +title = "Pico de Guerra" +description = "Inflige 10.000 de dano con un pico" [advancement.challenge_pick_value_5k] - title = "Buscador de Gemas" - description = "Mina bloques con un valor de 5.000" +title = "Buscador de Gemas" +description = "Mina bloques con un valor de 5.000" [advancement.challenge_pick_value_50k] - title = "Baron del Mineral" - description = "Mina bloques con un valor de 50.000" +title = "Baron del Mineral" +description = "Mina bloques con un valor de 50.000" [advancement.challenge_pick_ores_500] - title = "Prospector" - description = "Mina 500 bloques de mineral" +title = "Prospector" +description = "Mina 500 bloques de mineral" [advancement.challenge_pick_ores_5k] - title = "Minero Maestro" - description = "Mina 5.000 bloques de mineral" +title = "Minero Maestro" +description = "Mina 5.000 bloques de mineral" # Ranged [advancement.challenge_ranged_dmg_1k] - title = "Tirador Certero" - description = "Inflige 1.000 de dano a distancia" +title = "Tirador Certero" +description = "Inflige 1.000 de dano a distancia" [advancement.challenge_ranged_dmg_10k] - title = "Arquero Letal" - description = "Inflige 10.000 de dano a distancia" +title = "Arquero Letal" +description = "Inflige 10.000 de dano a distancia" [advancement.challenge_ranged_dist_5k] - title = "Largo Alcance" - description = "Dispara proyectiles cubriendo 5.000 bloques de distancia total" +title = "Largo Alcance" +description = "Dispara proyectiles cubriendo 5.000 bloques de distancia total" [advancement.challenge_ranged_dist_50k] - title = "Tirador de Millas" - description = "Dispara proyectiles cubriendo 50.000 bloques de distancia total" +title = "Tirador de Millas" +description = "Dispara proyectiles cubriendo 50.000 bloques de distancia total" [advancement.challenge_ranged_kills_50] - title = "Arquero" - description = "Mata a 50 criaturas con armas a distancia" +title = "Arquero" +description = "Mata a 50 criaturas con armas a distancia" [advancement.challenge_ranged_kills_500] - title = "Maestro Arquero" - description = "Mata a 500 criaturas con armas a distancia" +title = "Maestro Arquero" +description = "Mata a 500 criaturas con armas a distancia" [advancement.challenge_longshot_25] - title = "Francotirador" - description = "Acierta 25 tiros de larga distancia (mas de 30 bloques)" +title = "Francotirador" +description = "Acierta 25 tiros de larga distancia (mas de 30 bloques)" [advancement.challenge_longshot_250] - title = "Ojo de Aguila" - description = "Acierta 250 tiros de larga distancia (mas de 30 bloques)" +title = "Ojo de Aguila" +description = "Acierta 250 tiros de larga distancia (mas de 30 bloques)" # Rift [advancement.challenge_rift_pearls_50] - title = "Lanzador de Perlas" - description = "Lanza 50 perlas de Ender" +title = "Lanzador de Perlas" +description = "Lanza 50 perlas de Ender" [advancement.challenge_rift_pearls_500] - title = "Adicto al Teletransporte" - description = "Lanza 500 perlas de Ender" +title = "Adicto al Teletransporte" +description = "Lanza 500 perlas de Ender" [advancement.challenge_rift_enderman_50] - title = "Cazador de Enderman" - description = "Elimina 50 endermen" +title = "Cazador de Enderman" +description = "Elimina 50 endermen" [advancement.challenge_rift_enderman_500] - title = "Acechador del Vacio" - description = "Elimina 500 endermen" +title = "Acechador del Vacio" +description = "Elimina 500 endermen" [advancement.challenge_rift_dragon_500] - title = "Luchador de Dragones" - description = "Inflige 500 de dano al Dragon del End" +title = "Luchador de Dragones" +description = "Inflige 500 de dano al Dragon del End" [advancement.challenge_rift_dragon_5k] - title = "Azote de Dragones" - description = "Inflige 5.000 de dano al Dragon del End" +title = "Azote de Dragones" +description = "Inflige 5.000 de dano al Dragon del End" [advancement.challenge_rift_crystal_10] - title = "Rompe Cristales" - description = "Destruye 10 cristales del End" +title = "Rompe Cristales" +description = "Destruye 10 cristales del End" [advancement.challenge_rift_crystal_100] - title = "Demoledor del End" - description = "Destruye 100 cristales del End" +title = "Demoledor del End" +description = "Destruye 100 cristales del End" # Seaborne [advancement.challenge_fish_25] - title = "Pescador" - description = "Atrapa 25 peces" +title = "Pescador" +description = "Atrapa 25 peces" [advancement.challenge_fish_250] - title = "Maestro Pescador" - description = "Atrapa 250 peces" +title = "Maestro Pescador" +description = "Atrapa 250 peces" [advancement.challenge_drowned_25] - title = "Cazador de Ahogados" - description = "Elimina 25 ahogados" +title = "Cazador de Ahogados" +description = "Elimina 25 ahogados" [advancement.challenge_drowned_250] - title = "Limpiador del Oceano" - description = "Elimina 250 ahogados" +title = "Limpiador del Oceano" +description = "Elimina 250 ahogados" [advancement.challenge_guardian_10] - title = "Cazador de Guardianes" - description = "Elimina 10 guardianes" +title = "Cazador de Guardianes" +description = "Elimina 10 guardianes" [advancement.challenge_guardian_100] - title = "Saqueador de Templos" - description = "Elimina 100 guardianes" +title = "Saqueador de Templos" +description = "Elimina 100 guardianes" [advancement.challenge_underwater_blocks_100] - title = "Minero Submarino" - description = "Rompe 100 bloques bajo el agua" +title = "Minero Submarino" +description = "Rompe 100 bloques bajo el agua" [advancement.challenge_underwater_blocks_1k] - title = "Ingeniero Acuatico" - description = "Rompe 1.000 bloques bajo el agua" +title = "Ingeniero Acuatico" +description = "Rompe 1.000 bloques bajo el agua" # Stealth [advancement.challenge_stealth_dmg_500] - title = "Apunalador" - description = "Inflige 500 de dano mientras te agachas" +title = "Apunalador" +description = "Inflige 500 de dano mientras te agachas" [advancement.challenge_stealth_dmg_5k] - title = "Asesino Silencioso" - description = "Inflige 5.000 de dano mientras te agachas" +title = "Asesino Silencioso" +description = "Inflige 5.000 de dano mientras te agachas" [advancement.challenge_stealth_kills_10] - title = "Asesino" - description = "Mata a 10 criaturas mientras te agachas" +title = "Asesino" +description = "Mata a 10 criaturas mientras te agachas" [advancement.challenge_stealth_kills_100] - title = "Segador de Sombras" - description = "Mata a 100 criaturas mientras te agachas" +title = "Segador de Sombras" +description = "Mata a 100 criaturas mientras te agachas" [advancement.challenge_stealth_time_1h] - title = "Paciente" - description = "Pasa 1 hora agachado (3.600 segundos)" +title = "Paciente" +description = "Pasa 1 hora agachado (3.600 segundos)" [advancement.challenge_stealth_time_10h] - title = "Maestro de las Sombras" - description = "Pasa 10 horas agachado (36.000 segundos)" +title = "Maestro de las Sombras" +description = "Pasa 10 horas agachado (36.000 segundos)" [advancement.challenge_stealth_arrows_50] - title = "Arquero Silencioso" - description = "Dispara 50 flechas mientras te agachas" +title = "Arquero Silencioso" +description = "Dispara 50 flechas mientras te agachas" [advancement.challenge_stealth_arrows_500] - title = "Arquero Fantasma" - description = "Dispara 500 flechas mientras te agachas" +title = "Arquero Fantasma" +description = "Dispara 500 flechas mientras te agachas" # Swords [advancement.challenge_sword_dmg_1k] - title = "Aprendiz de Espada" - description = "Inflige 1.000 de dano con espadas" +title = "Aprendiz de Espada" +description = "Inflige 1.000 de dano con espadas" [advancement.challenge_sword_dmg_10k] - title = "Espadachin" - description = "Inflige 10.000 de dano con espadas" +title = "Espadachin" +description = "Inflige 10.000 de dano con espadas" [advancement.challenge_sword_kills_50] - title = "Duelista" - description = "Mata a 50 criaturas con espadas" +title = "Duelista" +description = "Mata a 50 criaturas con espadas" [advancement.challenge_sword_kills_500] - title = "Gladiador" - description = "Mata a 500 criaturas con espadas" +title = "Gladiador" +description = "Mata a 500 criaturas con espadas" [advancement.challenge_sword_crit_50] - title = "Golpe Critico" - description = "Conecta 50 golpes criticos con espadas" +title = "Golpe Critico" +description = "Conecta 50 golpes criticos con espadas" [advancement.challenge_sword_crit_500] - title = "Maestro de la Precision" - description = "Conecta 500 golpes criticos con espadas" +title = "Maestro de la Precision" +description = "Conecta 500 golpes criticos con espadas" [advancement.challenge_sword_heavy_25] - title = "Golpeador Pesado" - description = "Conecta 25 golpes pesados con espadas (mas de 8 de dano)" +title = "Golpeador Pesado" +description = "Conecta 25 golpes pesados con espadas (mas de 8 de dano)" [advancement.challenge_sword_heavy_250] - title = "Golpe Devastador" - description = "Conecta 250 golpes pesados con espadas (mas de 8 de dano)" +title = "Golpe Devastador" +description = "Conecta 250 golpes pesados con espadas (mas de 8 de dano)" # Taming [advancement.challenge_pet_dmg_500] - title = "Entrenador de Bestias" - description = "Tus mascotas infligen 500 de dano total" +title = "Entrenador de Bestias" +description = "Tus mascotas infligen 500 de dano total" [advancement.challenge_pet_dmg_5k] - title = "Maestro de Guerra" - description = "Tus mascotas infligen 5.000 de dano total" +title = "Maestro de Guerra" +description = "Tus mascotas infligen 5.000 de dano total" [advancement.challenge_tamed_10] - title = "Amigo de los Animales" - description = "Domestica 10 animales" +title = "Amigo de los Animales" +description = "Domestica 10 animales" [advancement.challenge_tamed_100] - title = "Cuidador del Zoologico" - description = "Domestica 100 animales" +title = "Cuidador del Zoologico" +description = "Domestica 100 animales" [advancement.challenge_pet_kills_25] - title = "Tactica de Manada" - description = "Tus mascotas eliminan 25 criaturas" +title = "Tactica de Manada" +description = "Tus mascotas eliminan 25 criaturas" [advancement.challenge_pet_kills_250] - title = "Comandante Alfa" - description = "Tus mascotas eliminan 250 criaturas" +title = "Comandante Alfa" +description = "Tus mascotas eliminan 250 criaturas" [advancement.challenge_taming_2500] - title = "Experto en Cria" - description = "Cria 2.500 animales" +title = "Experto en Cria" +description = "Cria 2.500 animales" [advancement.challenge_taming_25k] - title = "Maestro Genetista" - description = "Cria 25.000 animales" +title = "Maestro Genetista" +description = "Cria 25.000 animales" # TragOul [advancement.challenge_trag_hits_500] - title = "Saco de Golpes" - description = "Recibe 500 golpes" +title = "Saco de Golpes" +description = "Recibe 500 golpes" [advancement.challenge_trag_hits_5k] - title = "Adicto al Castigo" - description = "Recibe 5.000 golpes" +title = "Adicto al Castigo" +description = "Recibe 5.000 golpes" [advancement.challenge_trag_deaths_10] - title = "Siete Vidas" - description = "Muere 10 veces" +title = "Siete Vidas" +description = "Muere 10 veces" [advancement.challenge_trag_deaths_100] - title = "Pesadilla Recurrente" - description = "Muere 100 veces" +title = "Pesadilla Recurrente" +description = "Muere 100 veces" [advancement.challenge_trag_fire_500] - title = "Victima del Fuego" - description = "Soporta 500 de dano por fuego" +title = "Victima del Fuego" +description = "Soporta 500 de dano por fuego" [advancement.challenge_trag_fire_5k] - title = "Fenix" - description = "Soporta 5.000 de dano por fuego" +title = "Fenix" +description = "Soporta 5.000 de dano por fuego" [advancement.challenge_trag_fall_500] - title = "Prueba de Gravedad" - description = "Soporta 500 de dano por caida" +title = "Prueba de Gravedad" +description = "Soporta 500 de dano por caida" [advancement.challenge_trag_fall_5k] - title = "Velocidad Terminal" - description = "Soporta 5.000 de dano por caida" +title = "Velocidad Terminal" +description = "Soporta 5.000 de dano por caida" # Unarmed [advancement.challenge_unarmed_dmg_1k] - title = "Peleador" - description = "Inflige 1.000 de dano a punos limpios" +title = "Peleador" +description = "Inflige 1.000 de dano a punos limpios" [advancement.challenge_unarmed_dmg_10k] - title = "Artista Marcial" - description = "Inflige 10.000 de dano a punos limpios" +title = "Artista Marcial" +description = "Inflige 10.000 de dano a punos limpios" [advancement.challenge_unarmed_kills_25] - title = "Punos Desnudos" - description = "Mata a 25 criaturas a punos limpios" +title = "Punos Desnudos" +description = "Mata a 25 criaturas a punos limpios" [advancement.challenge_unarmed_kills_250] - title = "Puno de Leyenda" - description = "Mata a 250 criaturas a punos limpios" +title = "Puno de Leyenda" +description = "Mata a 250 criaturas a punos limpios" [advancement.challenge_unarmed_crit_25] - title = "Golpe Critico" - description = "Conecta 25 golpes criticos a punos limpios" +title = "Golpe Critico" +description = "Conecta 25 golpes criticos a punos limpios" [advancement.challenge_unarmed_crit_250] - title = "Puno de Precision" - description = "Conecta 250 golpes criticos a punos limpios" +title = "Puno de Precision" +description = "Conecta 250 golpes criticos a punos limpios" [advancement.challenge_unarmed_heavy_25] - title = "Golpe de Poder" - description = "Conecta 25 golpes pesados a punos limpios (mas de 6 de dano)" +title = "Golpe de Poder" +description = "Conecta 25 golpes pesados a punos limpios (mas de 6 de dano)" [advancement.challenge_unarmed_heavy_250] - title = "Rey del Nocaut" - description = "Conecta 250 golpes pesados a punos limpios (mas de 6 de dano)" +title = "Rey del Nocaut" +description = "Conecta 250 golpes pesados a punos limpios (mas de 6 de dano)" # items [items] [items.bound_ender_peral] - name = "Traslador Relicario" - usage1 = "Shift + Clic izquierdo para vincular" - usage2 = "Clic derecho para acceder al inventario vinculado" +name = "Traslador Relicario" +usage1 = "Shift + Clic izquierdo para vincular" +usage2 = "Clic derecho para acceder al inventario vinculado" [items.bound_eye_of_ender] - name = "Ancla Ocular" - usage1 = "Clic derecho para consumir y teletransportarte a la ubicacion vinculada" - usage2 = "Shift + Clic izquierdo para vincular a un bloque" +name = "Ancla Ocular" +usage1 = "Clic derecho para consumir y teletransportarte a la ubicacion vinculada" +usage2 = "Shift + Clic izquierdo para vincular a un bloque" [items.bound_redstone_torch] - name = "Control Remoto de Redstone" - usage1 = "Clic derecho para crear un pulso de Redstone de 1 tick" - usage2 = "Shift + Clic izquierdo en un bloque 'Objetivo' para vincular" +name = "Control Remoto de Redstone" +usage1 = "Clic derecho para crear un pulso de Redstone de 1 tick" +usage2 = "Shift + Clic izquierdo en un bloque 'Objetivo' para vincular" [items.bound_snowball] - name = "¡Trampa de Telarana!" - usage1 = "Lanza para crear una trampa de telarana temporal en la ubicacion" +name = "¡Trampa de Telarana!" +usage1 = "Lanza para crear una trampa de telarana temporal en la ubicacion" [items.chrono_time_bottle] - name = "Tiempo en una Botella" - usage1 = "Almacena tiempo pasivamente mientras esta en tu inventario" - usage2 = "Clic derecho en bloques con temporizador o animales bebe para gastar tiempo almacenado" - stored = "Tiempo almacenado" +name = "Tiempo en una Botella" +usage1 = "Almacena tiempo pasivamente mientras esta en tu inventario" +usage2 = "Clic derecho en bloques con temporizador o animales bebe para gastar tiempo almacenado" +stored = "Tiempo almacenado" [items.chrono_time_bomb] - name = "Bomba de Tiempo" - usage1 = "Clic derecho para lanzar un rayo crono que crea un campo temporal" +name = "Bomba de Tiempo" +usage1 = "Clic derecho para lanzar un rayo crono que crea un campo temporal" [items.elevator_block] - name = "Bloque de Ascensor" - usage1 = "Salta para teletransportarte hacia arriba" - usage2 = "Agachate para teletransportarte hacia abajo" - usage3 = "Minimo 2 bloques de aire entre los ascensores" +name = "Bloque de Ascensor" +usage1 = "Salta para teletransportarte hacia arriba" +usage2 = "Agachate para teletransportarte hacia abajo" +usage3 = "Minimo 2 bloques de aire entre los ascensores" # snippets [snippets] [snippets.gui] - level = "Nivel" - knowledge = "conocimiento" - power_used = "Poder utilizado" - not_learned = "No aprendido" - xp = "XP para" - welcome = "¡Bienvenido!" - welcome_back = "¡Bienvenido de nuevo!" - xp_bonus_for_time = "XP por" - max_ability_power = "Poder maximo de habilidad" - unlock_this_by_clicking = "Desbloquea esto con Clic derecho: " - back = "Atras" - unlearn_all = "Desaprender todo" - unlearned_all = "Se desaprendio todo" +level = "Nivel" +knowledge = "conocimiento" +power_used = "Poder utilizado" +not_learned = "No aprendido" +xp = "XP para" +welcome = "¡Bienvenido!" +welcome_back = "¡Bienvenido de nuevo!" +xp_bonus_for_time = "XP por" +max_ability_power = "Poder maximo de habilidad" +unlock_this_by_clicking = "Desbloquea esto con Clic derecho: " +back = "Atras" +unlearn_all = "Desaprender todo" +unlearned_all = "Se desaprendio todo" [snippets.adapt_menu] - may_not_unlearn = "NO PUEDES DESAPRENDER" - may_unlearn = "PUEDES APRENDER/DESAPRENDER" - knowledge_cost = "Coste de conocimiento" - knowledge_available = "Conocimiento disponible" - already_learned = "Ya aprendido" - unlearn_refund = "Clic para desaprender y reembolsar" - no_refunds = "HARDCORE, REEMBOLSOS DESACTIVADOS" - knowledge = "conocimiento" - click_learn = "Clic para aprender" - no_knowledge = "(No tienes ningun conocimiento)" - you_only_have = "Solo tienes" - how_to_level_up = "Sube de nivel las habilidades para aumentar tu poder maximo." - not_enough_power = "¡No tienes suficiente poder! Cada nivel de habilidad cuesta 1 poder." - power = "poder" - power_drain = "Consumo de poder" - learned = "Aprendido " - unlearned = "Desaprendido " - activator_block = "Estanteria" +may_not_unlearn = "NO PUEDES DESAPRENDER" +may_unlearn = "PUEDES APRENDER/DESAPRENDER" +knowledge_cost = "Coste de conocimiento" +knowledge_available = "Conocimiento disponible" +already_learned = "Ya aprendido" +unlearn_refund = "Clic para desaprender y reembolsar" +no_refunds = "HARDCORE, REEMBOLSOS DESACTIVADOS" +knowledge = "conocimiento" +click_learn = "Clic para aprender" +no_knowledge = "(No tienes ningun conocimiento)" +you_only_have = "Solo tienes" +how_to_level_up = "Sube de nivel las habilidades para aumentar tu poder maximo." +not_enough_power = "¡No tienes suficiente poder! Cada nivel de habilidad cuesta 1 poder." +power = "poder" +power_drain = "Consumo de poder" +learned = "Aprendido " +unlearned = "Desaprendido " +activator_block = "Estanteria" [snippets.knowledge_orb] - contains = "contiene" - knowledge = "conocimiento" - rightclick = "Clic derecho" - togainknowledge = "para obtener este conocimiento" - knowledge_orb = "Orbe de Conocimiento" +contains = "contiene" +knowledge = "conocimiento" +rightclick = "Clic derecho" +togainknowledge = "para obtener este conocimiento" +knowledge_orb = "Orbe de Conocimiento" [snippets.experience_orb] - contains = "contiene" - xp = "Experiencia" - rightclick = "Clic derecho" - togainxp = "para obtener esta experiencia" - xporb = "Orbe de Experiencia" +contains = "contiene" +xp = "Experiencia" +rightclick = "Clic derecho" +togainxp = "para obtener esta experiencia" +xporb = "Orbe de Experiencia" # skill [skill] [skill.agility] - name = "Agilidad" - icon = "⇉" - description = "La agilidad es la capacidad de moverse rapida y fluidamente ante los obstaculos." +name = "Agilidad" +icon = "⇉" +description = "La agilidad es la capacidad de moverse rapida y fluidamente ante los obstaculos." [skill.architect] - name = "Arquitecto" - icon = "⬧" - description = "Las estructuras son los bloques de construccion del mundo. La realidad esta en tus manos, tuya para controlar." +name = "Arquitecto" +icon = "⬧" +description = "Las estructuras son los bloques de construccion del mundo. La realidad esta en tus manos, tuya para controlar." [skill.axes] - name = "Hachas" - icon = "🪓" - description1 = "Para que talar arboles, cuando podrias cortar " - description2 = "cosas" - description3 = "en su lugar, ¡el mismo resultado!" +name = "Hachas" +icon = "🪓" +description1 = "Para que talar arboles, cuando podrias cortar " +description2 = "cosas" +description3 = "en su lugar, ¡el mismo resultado!" [skill.brewing] - name = "Alquimia" - icon = "❦" - description = "Burbuja doble, burbuja triple, burbuja cuadruple... Aun no puedo poner esta pocion en un caldero" +name = "Alquimia" +icon = "❦" +description = "Burbuja doble, burbuja triple, burbuja cuadruple... Aun no puedo poner esta pocion en un caldero" [skill.blocking] - name = "Bloqueo" - icon = "🛡" - description = "Palos y piedras no romperan tus huesos, pero un escudo si." +name = "Bloqueo" +icon = "🛡" +description = "Palos y piedras no romperan tus huesos, pero un escudo si." [skill.crafting] - name = "Fabricacion" - icon = "⌂" - description = "Sin mas piezas que colocar, ¿por que no hacer otra?" +name = "Fabricacion" +icon = "⌂" +description = "Sin mas piezas que colocar, ¿por que no hacer otra?" [skill.discovery] - name = "Descubrimiento" - icon = "⚛" - description = "A medida que tu percepcion se expande, tu mente se despliega para descubrir aquello que no conocias." +name = "Descubrimiento" +icon = "⚛" +description = "A medida que tu percepcion se expande, tu mente se despliega para descubrir aquello que no conocias." [skill.enchanting] - name = "Encantamiento" - icon = "♰" - description = "¿De que vas? ¿Profecias, visiones, chácharas supersticiosas?" +name = "Encantamiento" +icon = "♰" +description = "¿De que vas? ¿Profecias, visiones, chácharas supersticiosas?" [skill.excavation] - name = "Excavacion" - icon = "ᛳ" - description = "Cava cava cava..." +name = "Excavacion" +icon = "ᛳ" +description = "Cava cava cava..." [skill.herbalism] - name = "Herbalismo" - icon = "⚘" - description = "No encuentro ninguna planta, pero puedo encontrar semillas y... ¿eso es... hierba?" +name = "Herbalismo" +icon = "⚘" +description = "No encuentro ninguna planta, pero puedo encontrar semillas y... ¿eso es... hierba?" [skill.hunter] - name = "Cazador" - icon = "☠" - description = "La caza trata del viaje, no del resultado." +name = "Cazador" +icon = "☠" +description = "La caza trata del viaje, no del resultado." [skill.nether] - name = "Nether" - icon = "₪" - description = "Desde las profundidades del mismisimo Nether." +name = "Nether" +icon = "₪" +description = "Desde las profundidades del mismisimo Nether." [skill.pickaxe] - name = "Pico" - icon = "⛏" - description = "Los enanos son los mineros, pero he aprendido un par de cosas con el tiempo. ¡SOY SUECO!" +name = "Pico" +icon = "⛏" +description = "Los enanos son los mineros, pero he aprendido un par de cosas con el tiempo. ¡SOY SUECO!" [skill.ranged] - name = "A distancia" - icon = "🏹" - description = "La distancia es la clave de la victoria, y la clave de la supervivencia." +name = "A distancia" +icon = "🏹" +description = "La distancia es la clave de la victoria, y la clave de la supervivencia." [skill.rift] - name = "Grieta" - icon = "❍" - description = "La Grieta es un arnes caustico, pero tu has dominado el arnes." +name = "Grieta" +icon = "❍" +description = "La Grieta es un arnes caustico, pero tu has dominado el arnes." [skill.seaborne] - name = "Maritimo" - icon = "🎣" - description = "Con esta habilidad, podras dominar las maravillas del agua." +name = "Maritimo" +icon = "🎣" +description = "Con esta habilidad, podras dominar las maravillas del agua." [skill.stealth] - name = "Sigilo" - icon = "☯" - description = "El arte de lo invisible. Camina entre las sombras." +name = "Sigilo" +icon = "☯" +description = "El arte de lo invisible. Camina entre las sombras." [skill.swords] - name = "Espadas" - icon = "⚔" - description = "¡Por el poder de PiedraGris!" +name = "Espadas" +icon = "⚔" +description = "¡Por el poder de PiedraGris!" [skill.taming] - name = "Domesticacion" - icon = "♥" - description = "Los loros y las abejas... ¿y tu?" +name = "Domesticacion" +icon = "♥" +description = "Los loros y las abejas... ¿y tu?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "La sangre fluye por las venas del universo. Constreñida por tus manos." +name = "TragOul" +icon = "🗡" +description = "La sangre fluye por las venas del universo. Constreñida por tus manos." [skill.chronos] - name = "Cronos" - icon = "🕒" - description = "Da cuerda al reloj del universo, experimenta el fluir. Rompe el reloj, conviertete en el." +name = "Cronos" +icon = "🕒" +description = "Da cuerda al reloj del universo, experimenta el fluir. Rompe el reloj, conviertete en el." [skill.unarmed] - name = "Desarmado" - icon = "»" - description = "Sin arma no significa sin fuerza." +name = "Desarmado" +icon = "»" +description = "Sin arma no significa sin fuerza." # agility [agility] [agility.armor_up] - name = "Blindaje" - description = "¡Consigue mas armadura cuanto mas tiempo esprintees!" - lore1 = "Armadura maxima" - lore2 = "Tiempo de blindaje" - lore = ["Armadura maxima", "Tiempo de blindaje"] +name = "Blindaje" +description = "¡Consigue mas armadura cuanto mas tiempo esprintees!" +lore1 = "Armadura maxima" +lore2 = "Tiempo de blindaje" +lore = ["Armadura maxima", "Tiempo de blindaje"] [agility.ladder_slide] - name = "Deslizamiento en Escalera" - description = "Sube y deslizate por las escaleras mucho mas rapido en ambas direcciones." - lore1 = "Multiplicador de velocidad en escalera" - lore2 = "Velocidad de descenso rapido" - lore = ["Multiplicador de velocidad en escalera", "Velocidad de descenso rapido"] +name = "Deslizamiento en Escalera" +description = "Sube y deslizate por las escaleras mucho mas rapido en ambas direcciones." +lore1 = "Multiplicador de velocidad en escalera" +lore2 = "Velocidad de descenso rapido" +lore = ["Multiplicador de velocidad en escalera", "Velocidad de descenso rapido"] [agility.super_jump] - name = "Super Salto" - description = "Ventaja de altura excepcional." - lore1 = "Altura maxima de salto" - lore2 = "¡Agachate + Salta para hacer un Super Salto!" - lore = ["Altura maxima de salto", "¡Agachate + Salta para hacer un Super Salto!"] +name = "Super Salto" +description = "Ventaja de altura excepcional." +lore1 = "Altura maxima de salto" +lore2 = "¡Agachate + Salta para hacer un Super Salto!" +lore = ["Altura maxima de salto", "¡Agachate + Salta para hacer un Super Salto!"] [agility.wall_jump] - name = "Salto de Pared" - description = "¡Manten Shift en el aire contra una pared para agarrarte y saltar!" - lore1 = "Saltos maximos" - lore2 = "Altura de salto" - lore = ["Saltos maximos", "Altura de salto"] +name = "Salto de Pared" +description = "¡Manten Shift en el aire contra una pared para agarrarte y saltar!" +lore1 = "Saltos maximos" +lore2 = "Altura de salto" +lore = ["Saltos maximos", "Altura de salto"] [agility.wind_up] - name = "Aceleracion" - description = "¡Ve mas rapido cuanto mas tiempo esprintees!" - lore1 = "Velocidad maxima" - lore2 = "Tiempo de aceleracion" - lore = ["Velocidad maxima", "Tiempo de aceleracion"] +name = "Aceleracion" +description = "¡Ve mas rapido cuanto mas tiempo esprintees!" +lore1 = "Velocidad maxima" +lore2 = "Tiempo de aceleracion" +lore = ["Velocidad maxima", "Tiempo de aceleracion"] # architect [architect] [architect.elevator] - name = "Ascensor" - description = "¡Esto te permite construir un ascensor para teletransportarte verticalmente rapido!" - lore1 = "Desbloquea la receta del ascensor: X=LANA, Y=PERLA DE ENDER" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Desbloquea la receta del ascensor: X=LANA, Y=PERLA DE ENDER", "XXX", "XYX", "XXX"] +name = "Ascensor" +description = "¡Esto te permite construir un ascensor para teletransportarte verticalmente rapido!" +lore1 = "Desbloquea la receta del ascensor: X=LANA, Y=PERLA DE ENDER" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Desbloquea la receta del ascensor: X=LANA, Y=PERLA DE ENDER", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Cimiento Magico" - description = "¡Esto te permite agacharte y colocar un cimiento temporal debajo de ti!" - lore1 = "Crea magicamente: " - lore2 = "¡Bloques debajo de ti!" - lore = ["Crea magicamente: ", "¡Bloques debajo de ti!"] +name = "Cimiento Magico" +description = "¡Esto te permite agacharte y colocar un cimiento temporal debajo de ti!" +lore1 = "Crea magicamente: " +lore2 = "¡Bloques debajo de ti!" +lore = ["Crea magicamente: ", "¡Bloques debajo de ti!"] [architect.glass] - name = "Vidrio con Toque de Seda" - description = "¡Esto te permite evitar la perdida de bloques de vidrio cuando los rompes con la mano vacia!" - lore1 = "Tus manos obtienen toque de seda para el vidrio" - lore = ["Tus manos obtienen toque de seda para el vidrio"] +name = "Vidrio con Toque de Seda" +description = "¡Esto te permite evitar la perdida de bloques de vidrio cuando los rompes con la mano vacia!" +lore1 = "Tus manos obtienen toque de seda para el vidrio" +lore = ["Tus manos obtienen toque de seda para el vidrio"] [architect.wireless_redstone] - name = "Control Remoto de Redstone" - description = "¡Esto te permite usar una antorcha de redstone para activar redstone de forma remota!" - lore1 = "Objetivo + Antorcha de Redstone + Perla de Ender = 1 Control Remoto de Redstone" - lore = ["Objetivo + Antorcha de Redstone + Perla de Ender = 1 Control Remoto de Redstone"] +name = "Control Remoto de Redstone" +description = "¡Esto te permite usar una antorcha de redstone para activar redstone de forma remota!" +lore1 = "Objetivo + Antorcha de Redstone + Perla de Ender = 1 Control Remoto de Redstone" +lore = ["Objetivo + Antorcha de Redstone + Perla de Ender = 1 Control Remoto de Redstone"] [architect.placement] - name = "Varita de Constructor" - description = "¡Te permite colocar multiples bloques a la vez! Para activar, agachate y sostén un bloque que coincida con el bloque al que miras y coloca. Ten en cuenta que puede que necesites moverte un poco para activar el limite de las cajas." - lore1 = "Necesitas" - lore2 = "bloques en tu mano para colocar esto" - lore3 = "Una Varita de Constructor de Material" - lore = ["Necesitas", "bloques en tu mano para colocar esto", "Una Varita de Constructor de Material"] +name = "Varita de Constructor" +description = "¡Te permite colocar multiples bloques a la vez! Para activar, agachate y sostén un bloque que coincida con el bloque al que miras y coloca. Ten en cuenta que puede que necesites moverte un poco para activar el limite de las cajas." +lore1 = "Necesitas" +lore2 = "bloques en tu mano para colocar esto" +lore3 = "Una Varita de Constructor de Material" +lore = ["Necesitas", "bloques en tu mano para colocar esto", "Una Varita de Constructor de Material"] # axe [axe] [axe.chop] - name = "Corte de Hacha" - description = "¡Tala arboles haciendo clic derecho en el tronco base!" - lore1 = "Bloques por corte" - lore2 = "Tiempo de recarga del corte" - lore3 = "Desgaste de herramienta" - lore = ["Bloques por corte", "Tiempo de recarga del corte", "Desgaste de herramienta"] +name = "Corte de Hacha" +description = "¡Tala arboles haciendo clic derecho en el tronco base!" +lore1 = "Bloques por corte" +lore2 = "Tiempo de recarga del corte" +lore3 = "Desgaste de herramienta" +lore = ["Bloques por corte", "Tiempo de recarga del corte", "Desgaste de herramienta"] [axe.log_swap] - name = "Intercambiador de Troncos de Lucy" - description = "¡Cambia el tipo de troncos en una mesa de crafteo!" - lore1 = "8 troncos de cualquier tipo + 1 brote = 8 troncos del tipo del brote" - lore = ["8 troncos de cualquier tipo + 1 brote = 8 troncos del tipo del brote"] +name = "Intercambiador de Troncos de Lucy" +description = "¡Cambia el tipo de troncos en una mesa de crafteo!" +lore1 = "8 troncos de cualquier tipo + 1 brote = 8 troncos del tipo del brote" +lore = ["8 troncos de cualquier tipo + 1 brote = 8 troncos del tipo del brote"] [axe.drop_to_inventory] - name = "Hacha: Soltar al Inventario" +name = "Hacha: Soltar al Inventario" [axe.ground_smash] - name = "Golpe de Tierra con Hacha" - description = "Salta, luego agachate y golpea a todos los enemigos cercanos." - lore1 = "Dano" - lore2 = "Radio de bloques" - lore3 = "Fuerza" - lore4 = "Tiempo de recarga del golpe" - lore = ["Dano", "Radio de bloques", "Fuerza", "Tiempo de recarga del golpe"] +name = "Golpe de Tierra con Hacha" +description = "Salta, luego agachate y golpea a todos los enemigos cercanos." +lore1 = "Dano" +lore2 = "Radio de bloques" +lore3 = "Fuerza" +lore4 = "Tiempo de recarga del golpe" +lore = ["Dano", "Radio de bloques", "Fuerza", "Tiempo de recarga del golpe"] [axe.leaf_miner] - name = "Minero de Hojas" - description = "¡Te permite romper hojas en masa de una vez!" - lore1 = "Agachate y mina HOJAS" - lore2 = "Rango de minado de hojas" - lore3 = "No obtendras los drops de las hojas (prevencion de exploits)" - lore = ["Agachate y mina HOJAS", "Rango de minado de hojas", "No obtendras los drops de las hojas (prevencion de exploits)"] +name = "Minero de Hojas" +description = "¡Te permite romper hojas en masa de una vez!" +lore1 = "Agachate y mina HOJAS" +lore2 = "Rango de minado de hojas" +lore3 = "No obtendras los drops de las hojas (prevencion de exploits)" +lore = ["Agachate y mina HOJAS", "Rango de minado de hojas", "No obtendras los drops de las hojas (prevencion de exploits)"] [axe.wood_miner] - name = "Minero de Madera" - description = "¡Te permite romper madera en masa de una vez!" - lore1 = "Agachate y mina MADERA/TRONCOS (no tablones)" - lore2 = "Rango de minado de madera" - lore3 = "Funciona con Soltar al Inventario" - lore = ["Agachate y mina MADERA/TRONCOS (no tablones)", "Rango de minado de madera", "Funciona con Soltar al Inventario"] +name = "Minero de Madera" +description = "¡Te permite romper madera en masa de una vez!" +lore1 = "Agachate y mina MADERA/TRONCOS (no tablones)" +lore2 = "Rango de minado de madera" +lore3 = "Funciona con Soltar al Inventario" +lore = ["Agachate y mina MADERA/TRONCOS (no tablones)", "Rango de minado de madera", "Funciona con Soltar al Inventario"] # brewing [brewing] [brewing.lingering] - name = "Brebaje Persistente" - description = "¡Las pociones preparadas duran mas!" - lore1 = "Duracion" - lore2 = "Duracion" - lore = ["Duracion", "Duracion"] +name = "Brebaje Persistente" +description = "¡Las pociones preparadas duran mas!" +lore1 = "Duracion" +lore2 = "Duracion" +lore = ["Duracion", "Duracion"] [brewing.super_heated] - name = "Brebaje Supercaliente" - description = "Los soportes para pociones funcionan mas rapido cuanto mas calientes esten." - lore1 = "Por bloque de fuego en contacto" - lore2 = "Por bloque de lava en contacto" - lore = ["Por bloque de fuego en contacto", "Por bloque de lava en contacto"] +name = "Brebaje Supercaliente" +description = "Los soportes para pociones funcionan mas rapido cuanto mas calientes esten." +lore1 = "Por bloque de fuego en contacto" +lore2 = "Por bloque de lava en contacto" +lore = ["Por bloque de fuego en contacto", "Por bloque de lava en contacto"] [brewing.darkness] - name = "Oscuridad Embotellada" - description = "No estoy seguro de por que necesitas esto, ¡pero aqui tienes!" - lore1 = "Pocion de Vision Nocturna + Concreto Negro = Pocion de Oscuridad (30 segundos)" - lore2 = "¡Cabe senalar que esto impide que el usuario esprintee!" - lore = ["Pocion de Vision Nocturna + Concreto Negro = Pocion de Oscuridad (30 segundos)", "¡Cabe senalar que esto impide que el usuario esprintee!"] +name = "Oscuridad Embotellada" +description = "No estoy seguro de por que necesitas esto, ¡pero aqui tienes!" +lore1 = "Pocion de Vision Nocturna + Concreto Negro = Pocion de Oscuridad (30 segundos)" +lore2 = "¡Cabe senalar que esto impide que el usuario esprintee!" +lore = ["Pocion de Vision Nocturna + Concreto Negro = Pocion de Oscuridad (30 segundos)", "¡Cabe senalar que esto impide que el usuario esprintee!"] [brewing.haste] - name = "Prisa Embotellada" - description = "Cuando la eficiencia no es suficiente" - lore1 = "Pocion de Velocidad + Fragmento de Amatista = Pocion de Prisa (60 segundos)" - lore2 = "Pocion de Velocidad + Bloque de Amatista = Pocion de Prisa-2 (30 segundos)" - lore = ["Pocion de Velocidad + Fragmento de Amatista = Pocion de Prisa (60 segundos)", "Pocion de Velocidad + Bloque de Amatista = Pocion de Prisa-2 (30 segundos)"] +name = "Prisa Embotellada" +description = "Cuando la eficiencia no es suficiente" +lore1 = "Pocion de Velocidad + Fragmento de Amatista = Pocion de Prisa (60 segundos)" +lore2 = "Pocion de Velocidad + Bloque de Amatista = Pocion de Prisa-2 (30 segundos)" +lore = ["Pocion de Velocidad + Fragmento de Amatista = Pocion de Prisa (60 segundos)", "Pocion de Velocidad + Bloque de Amatista = Pocion de Prisa-2 (30 segundos)"] [brewing.absorption] - name = "Absorcion Embotellada" - description = "¡Endurece el cuerpo!" - lore1 = "Curacion Instantanea + Cuarzo = Pocion de Absorcion (60 segundos)" - lore2 = "Curacion Instantanea + Bloque de Cuarzo = Pocion de Absorcion-2 (30 segundos)" - lore = ["Curacion Instantanea + Cuarzo = Pocion de Absorcion (60 segundos)", "Curacion Instantanea + Bloque de Cuarzo = Pocion de Absorcion-2 (30 segundos)"] +name = "Absorcion Embotellada" +description = "¡Endurece el cuerpo!" +lore1 = "Curacion Instantanea + Cuarzo = Pocion de Absorcion (60 segundos)" +lore2 = "Curacion Instantanea + Bloque de Cuarzo = Pocion de Absorcion-2 (30 segundos)" +lore = ["Curacion Instantanea + Cuarzo = Pocion de Absorcion (60 segundos)", "Curacion Instantanea + Bloque de Cuarzo = Pocion de Absorcion-2 (30 segundos)"] [brewing.fatigue] - name = "Fatiga Embotellada" - description = "¡Debilita el cuerpo!" - lore1 = "Pocion de Debilidad + Bola de Slime = Pocion de Fatiga (30 segundos)" - lore2 = "Pocion de Debilidad + Bloque de Slime = Pocion de Fatiga-2 (15 segundos)" - lore = ["Pocion de Debilidad + Bola de Slime = Pocion de Fatiga (30 segundos)", "Pocion de Debilidad + Bloque de Slime = Pocion de Fatiga-2 (15 segundos)"] +name = "Fatiga Embotellada" +description = "¡Debilita el cuerpo!" +lore1 = "Pocion de Debilidad + Bola de Slime = Pocion de Fatiga (30 segundos)" +lore2 = "Pocion de Debilidad + Bloque de Slime = Pocion de Fatiga-2 (15 segundos)" +lore = ["Pocion de Debilidad + Bola de Slime = Pocion de Fatiga (30 segundos)", "Pocion de Debilidad + Bloque de Slime = Pocion de Fatiga-2 (15 segundos)"] [brewing.hunger] - name = "Hambre Embotellada" - description = "¡Alimenta al insaciable!" - lore1 = "Pocion Rara + Carne Podrida = Pocion de Hambre (30 segundos)" - lore2 = "Pocion de Debilidad + Carne Podrida = Pocion de Hambre-3 (15 segundos)" - lore = ["Pocion Rara + Carne Podrida = Pocion de Hambre (30 segundos)", "Pocion de Debilidad + Carne Podrida = Pocion de Hambre-3 (15 segundos)"] +name = "Hambre Embotellada" +description = "¡Alimenta al insaciable!" +lore1 = "Pocion Rara + Carne Podrida = Pocion de Hambre (30 segundos)" +lore2 = "Pocion de Debilidad + Carne Podrida = Pocion de Hambre-3 (15 segundos)" +lore = ["Pocion Rara + Carne Podrida = Pocion de Hambre (30 segundos)", "Pocion de Debilidad + Carne Podrida = Pocion de Hambre-3 (15 segundos)"] [brewing.nausea] - name = "Nauseas Embotelladas" - description = "¡Me das asco!" - lore1 = "Pocion Rara + Champiñon Marron = Pocion de Nauseas (16 segundos)" - lore2 = "Pocion Rara + Hongo Carmesi = Pocion de Nauseas-2 (8 segundos)" - lore = ["Pocion Rara + Champiñon Marron = Pocion de Nauseas (16 segundos)", "Pocion Rara + Hongo Carmesi = Pocion de Nauseas-2 (8 segundos)"] +name = "Nauseas Embotelladas" +description = "¡Me das asco!" +lore1 = "Pocion Rara + Champiñon Marron = Pocion de Nauseas (16 segundos)" +lore2 = "Pocion Rara + Hongo Carmesi = Pocion de Nauseas-2 (8 segundos)" +lore = ["Pocion Rara + Champiñon Marron = Pocion de Nauseas (16 segundos)", "Pocion Rara + Hongo Carmesi = Pocion de Nauseas-2 (8 segundos)"] [brewing.blindness] - name = "Ceguera Embotellada" - description = "Eres una persona horrible..." - lore1 = "Pocion Rara + Saco de Tinta = Pocion de Ceguera (30 segundos)" - lore2 = "Pocion Rara + Saco de Tinta Brillante = Pocion de Ceguera-2 (15 segundos)" - lore = ["Pocion Rara + Saco de Tinta = Pocion de Ceguera (30 segundos)", "Pocion Rara + Saco de Tinta Brillante = Pocion de Ceguera-2 (15 segundos)"] +name = "Ceguera Embotellada" +description = "Eres una persona horrible..." +lore1 = "Pocion Rara + Saco de Tinta = Pocion de Ceguera (30 segundos)" +lore2 = "Pocion Rara + Saco de Tinta Brillante = Pocion de Ceguera-2 (15 segundos)" +lore = ["Pocion Rara + Saco de Tinta = Pocion de Ceguera (30 segundos)", "Pocion Rara + Saco de Tinta Brillante = Pocion de Ceguera-2 (15 segundos)"] [brewing.resistance] - name = "Resistencia Embotellada" - description = "¡Fortificacion en su maxima expresion!" - lore1 = "Pocion Rara + Lingote de Hierro = Pocion de Resistencia (60 segundos)" - lore2 = "Pocion Rara + Bloque de Hierro = Pocion de Resistencia-2 (30 segundos)" - lore = ["Pocion Rara + Lingote de Hierro = Pocion de Resistencia (60 segundos)", "Pocion Rara + Bloque de Hierro = Pocion de Resistencia-2 (30 segundos)"] +name = "Resistencia Embotellada" +description = "¡Fortificacion en su maxima expresion!" +lore1 = "Pocion Rara + Lingote de Hierro = Pocion de Resistencia (60 segundos)" +lore2 = "Pocion Rara + Bloque de Hierro = Pocion de Resistencia-2 (30 segundos)" +lore = ["Pocion Rara + Lingote de Hierro = Pocion de Resistencia (60 segundos)", "Pocion Rara + Bloque de Hierro = Pocion de Resistencia-2 (30 segundos)"] [brewing.health_boost] - name = "Vida Embotellada" - description = "Cuando la salud maxima no es suficiente..." - lore1 = "Pocion de Curacion Instantanea + Manzana Dorada = Pocion de Mejora de Salud (120 segundos)" - lore2 = "Pocion de Curacion Instantanea + Manzana Dorada Encantada = Pocion de Mejora de Salud-2 (120 segundos)" - lore = ["Pocion de Curacion Instantanea + Manzana Dorada = Pocion de Mejora de Salud (120 segundos)", "Pocion de Curacion Instantanea + Manzana Dorada Encantada = Pocion de Mejora de Salud-2 (120 segundos)"] +name = "Vida Embotellada" +description = "Cuando la salud maxima no es suficiente..." +lore1 = "Pocion de Curacion Instantanea + Manzana Dorada = Pocion de Mejora de Salud (120 segundos)" +lore2 = "Pocion de Curacion Instantanea + Manzana Dorada Encantada = Pocion de Mejora de Salud-2 (120 segundos)" +lore = ["Pocion de Curacion Instantanea + Manzana Dorada = Pocion de Mejora de Salud (120 segundos)", "Pocion de Curacion Instantanea + Manzana Dorada Encantada = Pocion de Mejora de Salud-2 (120 segundos)"] [brewing.decay] - name = "Deterioro Embotellado" - description = "¿Quien sabia que el detritus seria tan util?" - lore1 = "Pocion de Debilidad + Patata Venenosa = Pocion de Wither (16 segundos)" - lore2 = "Pocion de Debilidad + Raices Carmesi = Pocion de Wither-2 (8 segundos)" - lore = ["Pocion de Debilidad + Patata Venenosa = Pocion de Wither (16 segundos)", "Pocion de Debilidad + Raices Carmesi = Pocion de Wither-2 (8 segundos)"] +name = "Deterioro Embotellado" +description = "¿Quien sabia que el detritus seria tan util?" +lore1 = "Pocion de Debilidad + Patata Venenosa = Pocion de Wither (16 segundos)" +lore2 = "Pocion de Debilidad + Raices Carmesi = Pocion de Wither-2 (8 segundos)" +lore = ["Pocion de Debilidad + Patata Venenosa = Pocion de Wither (16 segundos)", "Pocion de Debilidad + Raices Carmesi = Pocion de Wither-2 (8 segundos)"] [brewing.saturation] - name = "Saturacion Embotellada" - description = "Sabes... ni siquiera tengo hambre..." - lore1 = "Pocion de Regeneracion + Patata al Horno = Pocion de Saturacion" - lore2 = "Pocion de Regeneracion + Fardo de Heno = Pocion de Saturacion-2" - lore = ["Pocion de Regeneracion + Patata al Horno = Pocion de Saturacion", "Pocion de Regeneracion + Fardo de Heno = Pocion de Saturacion-2"] +name = "Saturacion Embotellada" +description = "Sabes... ni siquiera tengo hambre..." +lore1 = "Pocion de Regeneracion + Patata al Horno = Pocion de Saturacion" +lore2 = "Pocion de Regeneracion + Fardo de Heno = Pocion de Saturacion-2" +lore = ["Pocion de Regeneracion + Patata al Horno = Pocion de Saturacion", "Pocion de Regeneracion + Fardo de Heno = Pocion de Saturacion-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Cadenas de Mefistofeles" - description = "Te permite fabricar armadura de cota de malla" - lore1 = "La receta de fabricacion es la misma que cualquier otra, pero con pepitas de hierro en su lugar" - lore = ["La receta de fabricacion es la misma que cualquier otra, pero con pepitas de hierro en su lugar"] +name = "Cadenas de Mefistofeles" +description = "Te permite fabricar armadura de cota de malla" +lore1 = "La receta de fabricacion es la misma que cualquier otra, pero con pepitas de hierro en su lugar" +lore = ["La receta de fabricacion es la misma que cualquier otra, pero con pepitas de hierro en su lugar"] [blocking.horse_armorer] - name = "Armadura de Caballo Fabricable" - description = "Te permite fabricar armadura de caballo" - lore1 = "Rodea una silla de montar con el material que quieras usar para fabricar la armadura" - lore = ["Rodea una silla de montar con el material que quieras usar para fabricar la armadura"] +name = "Armadura de Caballo Fabricable" +description = "Te permite fabricar armadura de caballo" +lore1 = "Rodea una silla de montar con el material que quieras usar para fabricar la armadura" +lore = ["Rodea una silla de montar con el material que quieras usar para fabricar la armadura"] [blocking.saddle_crafter] - name = "Silla de Montar Fabricable" - description = "Fabrica una silla de montar con cuero" - lore1 = "Receta: 5 Cuero:" - lore = ["Receta: 5 Cuero:"] +name = "Silla de Montar Fabricable" +description = "Fabrica una silla de montar con cuero" +lore1 = "Receta: 5 Cuero:" +lore = ["Receta: 5 Cuero:"] [blocking.multi_armor] - name = "Multi-Armadura" - description = "Vincula elitros a la armadura" - lore1 = "Esta es una habilidad increible para viajar." - lore2 = "¡Fusiona y cambia dinamicamente Armadura/Elitros sobre la marcha!" - lore3 = "Para fusionar, haz Shift+clic en un objeto sobre otro en tu inventario." - lore4 = "Para desvincular la armadura, suelta el objeto agachado y se desmontara." - lore5 = "Si tu Multi-Armadura es destruida, perderas todos los objetos dentro de ella." - lore6 = "No necesito armadura, me decepciona..." - lore = ["Esta es una habilidad increible para viajar.", "¡Fusiona y cambia dinamicamente Armadura/Elitros sobre la marcha!", "Para fusionar, haz Shift+clic en un objeto sobre otro en tu inventario.", "Para desvincular la armadura, suelta el objeto agachado y se desmontara.", "Si tu Multi-Armadura es destruida, perderas todos los objetos dentro de ella.", "No necesito armadura, me decepciona..."] +name = "Multi-Armadura" +description = "Vincula elitros a la armadura" +lore1 = "Esta es una habilidad increible para viajar." +lore2 = "¡Fusiona y cambia dinamicamente Armadura/Elitros sobre la marcha!" +lore3 = "Para fusionar, haz Shift+clic en un objeto sobre otro en tu inventario." +lore4 = "Para desvincular la armadura, suelta el objeto agachado y se desmontara." +lore5 = "Si tu Multi-Armadura es destruida, perderas todos los objetos dentro de ella." +lore6 = "No necesito armadura, me decepciona..." +lore = ["Esta es una habilidad increible para viajar.", "¡Fusiona y cambia dinamicamente Armadura/Elitros sobre la marcha!", "Para fusionar, haz Shift+clic en un objeto sobre otro en tu inventario.", "Para desvincular la armadura, suelta el objeto agachado y se desmontara.", "Si tu Multi-Armadura es destruida, perderas todos los objetos dentro de ella.", "No necesito armadura, me decepciona..."] # crafting [crafting] [crafting.deconstruction] - name = "Deconstruccion" - description = "¡Deconstruye bloques y objetos en componentes basicos recuperables!" - lore1 = "Suelta cualquier objeto al suelo." - lore2 = "Luego, agachate y haz clic derecho con tijeras" - lore = ["Suelta cualquier objeto al suelo.", "Luego, agachate y haz clic derecho con tijeras"] +name = "Deconstruccion" +description = "¡Deconstruye bloques y objetos en componentes basicos recuperables!" +lore1 = "Suelta cualquier objeto al suelo." +lore2 = "Luego, agachate y haz clic derecho con tijeras" +lore = ["Suelta cualquier objeto al suelo.", "Luego, agachate y haz clic derecho con tijeras"] [crafting.xp] - name = "XP de Fabricacion" - description = "Gana XP pasiva al fabricar" - lore1 = "Gana XP al fabricar" - lore = ["Gana XP al fabricar"] +name = "XP de Fabricacion" +description = "Gana XP pasiva al fabricar" +lore1 = "Gana XP al fabricar" +lore = ["Gana XP al fabricar"] [crafting.reconstruction] - name = "Reconstruccion de Minerales" - description = "¡Recrea minerales a partir de sus componentes basicos!" - lore1 = "8 de los drops y 1 anfitrion = 1 mineral (sin forma)" - lore2 = "Los drops deben estar fundidos (si aplica)" - lore3 = "No incluye: Restos, Cuarzo, Esmeraldas, etc..." - lore4 = "Anfitrion = Envoltorio. ej: Piedra, Netherrack, Pizarra Profunda" - lore = ["8 de los drops y 1 anfitrion = 1 mineral (sin forma)", "Los drops deben estar fundidos (si aplica)", "No incluye: Restos, Cuarzo, Esmeraldas, etc...", "Anfitrion = Envoltorio. ej: Piedra, Netherrack, Pizarra Profunda"] +name = "Reconstruccion de Minerales" +description = "¡Recrea minerales a partir de sus componentes basicos!" +lore1 = "8 de los drops y 1 anfitrion = 1 mineral (sin forma)" +lore2 = "Los drops deben estar fundidos (si aplica)" +lore3 = "No incluye: Restos, Cuarzo, Esmeraldas, etc..." +lore4 = "Anfitrion = Envoltorio. ej: Piedra, Netherrack, Pizarra Profunda" +lore = ["8 de los drops y 1 anfitrion = 1 mineral (sin forma)", "Los drops deben estar fundidos (si aplica)", "No incluye: Restos, Cuarzo, Esmeraldas, etc...", "Anfitrion = Envoltorio. ej: Piedra, Netherrack, Pizarra Profunda"] [crafting.leather] - name = "Cuero Fabricable" - description = "Fabrica cuero a partir de carne podrida" - lore1 = "¡Solo tirala (carne podrida) en la fogata!" - lore = ["¡Solo tirala (carne podrida) en la fogata!"] +name = "Cuero Fabricable" +description = "Fabrica cuero a partir de carne podrida" +lore1 = "¡Solo tirala (carne podrida) en la fogata!" +lore = ["¡Solo tirala (carne podrida) en la fogata!"] [crafting.backpacks] - name = "¡Mochilas de Boutilier!" - description = "¡Esto simplemente trae el Bundle de Mojang al juego!" - lore1 = "Necesitas estar en Supervivencia para usar esto" - lore2 = "XLX : Cuero, Rienda, Cuero" - lore3 = "XSX : Cuero, Barril, Cuero" - lore4 = "XCX : Cuero, Cofre, Cuero" - lore = ["Necesitas estar en Supervivencia para usar esto", "XLX : Cuero, Rienda, Cuero", "XSX : Cuero, Barril, Cuero", "XCX : Cuero, Cofre, Cuero"] +name = "¡Mochilas de Boutilier!" +description = "¡Esto simplemente trae el Bundle de Mojang al juego!" +lore1 = "Necesitas estar en Supervivencia para usar esto" +lore2 = "XLX : Cuero, Rienda, Cuero" +lore3 = "XSX : Cuero, Barril, Cuero" +lore4 = "XCX : Cuero, Cofre, Cuero" +lore = ["Necesitas estar en Supervivencia para usar esto", "XLX : Cuero, Rienda, Cuero", "XSX : Cuero, Barril, Cuero", "XCX : Cuero, Cofre, Cuero"] [crafting.stations] - name = "¡Mesas Portatiles!" - description = "¡Usa una mesa en la palma de tu mano!" - lore2 = "¡CUALQUIER OBJETO QUE OLVIDES EN LA MESA AL CERRARLA SE PIERDE PARA SIEMPRE!" - lore3 = "Mesas validas: Yunque, Fabricacion, Piedra de Afilar, Cartografia, Cortapiedras, Telar" - lore = ["¡CUALQUIER OBJETO QUE OLVIDES EN LA MESA AL CERRARLA SE PIERDE PARA SIEMPRE!", "Mesas validas: Yunque, Fabricacion, Piedra de Afilar, Cartografia, Cortapiedras, Telar"] +name = "¡Mesas Portatiles!" +description = "¡Usa una mesa en la palma de tu mano!" +lore2 = "¡CUALQUIER OBJETO QUE OLVIDES EN LA MESA AL CERRARLA SE PIERDE PARA SIEMPRE!" +lore3 = "Mesas validas: Yunque, Fabricacion, Piedra de Afilar, Cartografia, Cortapiedras, Telar" +lore = ["¡CUALQUIER OBJETO QUE OLVIDES EN LA MESA AL CERRARLA SE PIERDE PARA SIEMPRE!", "Mesas validas: Yunque, Fabricacion, Piedra de Afilar, Cartografia, Cortapiedras, Telar"] [crafting.skulls] - name = "¡Calaveras fabricables!" - description = "¡Usando materiales puedes fabricar calaveras de mobs!" - lore1 = "Rodea un bloque de hueso con lo siguiente para obtener una calavera:" - lore2 = "Zombi: Carne Podrida" - lore3 = "Esqueleto: Hueso" - lore4 = "Creeper: Polvora" - lore5 = "Wither: Ladrillo del Nether" - lore6 = "Dragon: Aliento de Dragon" - lore = ["Rodea un bloque de hueso con lo siguiente para obtener una calavera:", "Zombi: Carne Podrida", "Esqueleto: Hueso", "Creeper: Polvora", "Wither: Ladrillo del Nether", "Dragon: Aliento de Dragon"] +name = "¡Calaveras fabricables!" +description = "¡Usando materiales puedes fabricar calaveras de mobs!" +lore1 = "Rodea un bloque de hueso con lo siguiente para obtener una calavera:" +lore2 = "Zombi: Carne Podrida" +lore3 = "Esqueleto: Hueso" +lore4 = "Creeper: Polvora" +lore5 = "Wither: Ladrillo del Nether" +lore6 = "Dragon: Aliento de Dragon" +lore = ["Rodea un bloque de hueso con lo siguiente para obtener una calavera:", "Zombi: Carne Podrida", "Esqueleto: Hueso", "Creeper: Polvora", "Wither: Ladrillo del Nether", "Dragon: Aliento de Dragon"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Tiempo en una Botella" - description = "Lleva una botella temporal que almacena tiempo y gastalo para acelerar bloques con temporizador, cultivables y entidades envejecibles como animales bebe. Receta (Sin forma): Pocion de Velocidad + Reloj + Frasco de Vidrio." - lore1 = "Segundos almacenados cargados cada tick" - lore2 = "Aceleracion temporal por segundo almacenado" - lore3 = "Receta (Sin forma): Pocion de Velocidad + Reloj + Frasco de Vidrio" - lore = ["Segundos almacenados cargados cada tick", "Aceleracion temporal por segundo almacenado", "Receta (Sin forma): Pocion de Velocidad + Reloj + Frasco de Vidrio"] +name = "Tiempo en una Botella" +description = "Lleva una botella temporal que almacena tiempo y gastalo para acelerar bloques con temporizador, cultivables y entidades envejecibles como animales bebe. Receta (Sin forma): Pocion de Velocidad + Reloj + Frasco de Vidrio." +lore1 = "Segundos almacenados cargados cada tick" +lore2 = "Aceleracion temporal por segundo almacenado" +lore3 = "Receta (Sin forma): Pocion de Velocidad + Reloj + Frasco de Vidrio" +lore = ["Segundos almacenados cargados cada tick", "Aceleracion temporal por segundo almacenado", "Receta (Sin forma): Pocion de Velocidad + Reloj + Frasco de Vidrio"] [chronos.aberrant_touch] - name = "Toque Aberrante" - description = "Los ataques cuerpo a cuerpo aplican lentitud acumulable a costa de hambre, con limites estrictos en PvP, y inmovilizan al objetivo a 5 acumulaciones." - lore1 = "Los ataques cuerpo a cuerpo aplican lentitud acumulable" - lore2 = "Duracion maxima de lentitud en PvE" - lore3 = "Amplificador maximo de lentitud en PvP" - lore = ["Los ataques cuerpo a cuerpo aplican lentitud acumulable", "Duracion maxima de lentitud en PvE", "Amplificador maximo de lentitud en PvP"] +name = "Toque Aberrante" +description = "Los ataques cuerpo a cuerpo aplican lentitud acumulable a costa de hambre, con limites estrictos en PvP, y inmovilizan al objetivo a 5 acumulaciones." +lore1 = "Los ataques cuerpo a cuerpo aplican lentitud acumulable" +lore2 = "Duracion maxima de lentitud en PvE" +lore3 = "Amplificador maximo de lentitud en PvP" +lore = ["Los ataques cuerpo a cuerpo aplican lentitud acumulable", "Duracion maxima de lentitud en PvE", "Amplificador maximo de lentitud en PvP"] [chronos.instant_recall] - name = "Retorno Instantaneo" - description = "Haz clic izquierdo o derecho con un reloj en mano para rebobinar a una instantanea reciente con salud y hambre restauradas." - lore1 = "Duracion del rebobinado" - lore2 = "Tiempo de recarga" - lore3 = "Sin reversion de inventario" - lore = ["Duracion del rebobinado", "Tiempo de recarga", "Sin reversion de inventario"] +name = "Retorno Instantaneo" +description = "Haz clic izquierdo o derecho con un reloj en mano para rebobinar a una instantanea reciente con salud y hambre restauradas." +lore1 = "Duracion del rebobinado" +lore2 = "Tiempo de recarga" +lore3 = "Sin reversion de inventario" +lore = ["Duracion del rebobinado", "Tiempo de recarga", "Sin reversion de inventario"] [chronos.time_bomb] - name = "Bomba de Tiempo" - description = "Lanza una bomba crono fabricada que crea un campo temporal, ralentiza entidades y congela proyectiles." - lore1 = "Radio del campo temporal" - lore2 = "Duracion del campo temporal" - lore3 = "Tiempo de recarga de la bomba" - lore4 = "Receta (Sin forma): Reloj + Bola de Nieve + Diamante + Arena" - lore = ["Radio del campo temporal", "Duracion del campo temporal", "Tiempo de recarga de la bomba", "Receta (Sin forma): Reloj + Bola de Nieve + Diamante + Arena"] +name = "Bomba de Tiempo" +description = "Lanza una bomba crono fabricada que crea un campo temporal, ralentiza entidades y congela proyectiles." +lore1 = "Radio del campo temporal" +lore2 = "Duracion del campo temporal" +lore3 = "Tiempo de recarga de la bomba" +lore4 = "Receta (Sin forma): Reloj + Bola de Nieve + Diamante + Arena" +lore = ["Radio del campo temporal", "Duracion del campo temporal", "Tiempo de recarga de la bomba", "Receta (Sin forma): Reloj + Bola de Nieve + Diamante + Arena"] # discovery [discovery] [discovery.armor] - name = "Armadura del Mundo" - description = "Armadura pasiva dependiendo de la dureza de los bloques cercanos." - lore1 = "Armadura pasiva" - lore2 = "Basada en la dureza de los bloques cercanos" - lore3 = "Fuerza de armadura:" - lore = ["Armadura pasiva", "Basada en la dureza de los bloques cercanos", "Fuerza de armadura:"] +name = "Armadura del Mundo" +description = "Armadura pasiva dependiendo de la dureza de los bloques cercanos." +lore1 = "Armadura pasiva" +lore2 = "Basada en la dureza de los bloques cercanos" +lore3 = "Fuerza de armadura:" +lore = ["Armadura pasiva", "Basada en la dureza de los bloques cercanos", "Fuerza de armadura:"] [discovery.unity] - name = "Unidad Experimental" - description = "Recoger orbes de experiencia anade XP a habilidades aleatorias." - lore1 = "XP " - lore2 = "Por orbe" - lore = ["XP ", "Por orbe"] +name = "Unidad Experimental" +description = "Recoger orbes de experiencia anade XP a habilidades aleatorias." +lore1 = "XP " +lore2 = "Por orbe" +lore = ["XP ", "Por orbe"] [discovery.resist] - name = "Resistencia Experimental" - description = "Consume experiencia para mitigar dano solo cuando un golpe te dejaria por debajo de 5 corazones o te mataria." - lore0 = "Se activa solo con salud critica (<= 5 corazones) una vez cada 15 segundos" - lore1 = " Dano reducido" - lore2 = "experiencia drenada" - lore = ["Se activa solo con salud critica (<= 5 corazones) una vez cada 15 segundos", " Dano reducido", "experiencia drenada"] +name = "Resistencia Experimental" +description = "Consume experiencia para mitigar dano solo cuando un golpe te dejaria por debajo de 5 corazones o te mataria." +lore0 = "Se activa solo con salud critica (<= 5 corazones) una vez cada 15 segundos" +lore1 = " Dano reducido" +lore2 = "experiencia drenada" +lore = ["Se activa solo con salud critica (<= 5 corazones) una vez cada 15 segundos", " Dano reducido", "experiencia drenada"] [discovery.villager] - name = "Atraccion de Aldeanos" - description = "¡Te permite obtener mejores intercambios con los aldeanos!" - lore1 = "Esto consume XP por interaccion con los aldeanos" - lore2 = "Probabilidad por interaccion de consumir XP y mejorar intercambios" - lore3 = "Drenaje de XP requerido por interaccion" - lore = ["Esto consume XP por interaccion con los aldeanos", "Probabilidad por interaccion de consumir XP y mejorar intercambios", "Drenaje de XP requerido por interaccion"] +name = "Atraccion de Aldeanos" +description = "¡Te permite obtener mejores intercambios con los aldeanos!" +lore1 = "Esto consume XP por interaccion con los aldeanos" +lore2 = "Probabilidad por interaccion de consumir XP y mejorar intercambios" +lore3 = "Drenaje de XP requerido por interaccion" +lore = ["Esto consume XP por interaccion con los aldeanos", "Probabilidad por interaccion de consumir XP y mejorar intercambios", "Drenaje de XP requerido por interaccion"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Retorno de Lapislazuli" - description = "A costa de 1 nivel mas de XP, tiene la posibilidad de darte lapislazuli gratis a cambio" - lore1 = "Por cada nivel, aumenta el coste de encantamiento en 1, pero puede devolver hasta 3 lapislazuli" - lore = ["Por cada nivel, aumenta el coste de encantamiento en 1, pero puede devolver hasta 3 lapislazuli"] +name = "Retorno de Lapislazuli" +description = "A costa de 1 nivel mas de XP, tiene la posibilidad de darte lapislazuli gratis a cambio" +lore1 = "Por cada nivel, aumenta el coste de encantamiento en 1, pero puede devolver hasta 3 lapislazuli" +lore = ["Por cada nivel, aumenta el coste de encantamiento en 1, pero puede devolver hasta 3 lapislazuli"] [enchanting.quick_enchant] - name = "Encantamiento Rapido" - description = "Encanta objetos haciendo clic con libros de encantamiento directamente sobre ellos." - lore1 = "Niveles maximos combinados" - lore2 = "No se puede encantar un objeto con mas de " - lore3 = "poder" - lore = ["Niveles maximos combinados", "No se puede encantar un objeto con mas de ", "poder"] +name = "Encantamiento Rapido" +description = "Encanta objetos haciendo clic con libros de encantamiento directamente sobre ellos." +lore1 = "Niveles maximos combinados" +lore2 = "No se puede encantar un objeto con mas de " +lore3 = "poder" +lore = ["Niveles maximos combinados", "No se puede encantar un objeto con mas de ", "poder"] [enchanting.return] - name = "Retorno de XP" - description = "La XP de encantamiento se te devuelve cuando encantas un objeto." - lore1 = "La experiencia gastada tiene una posibilidad de ser reembolsada cuando encantas un objeto" - lore2 = "Experiencia por encantamiento" - lore = ["La experiencia gastada tiene una posibilidad de ser reembolsada cuando encantas un objeto", "Experiencia por encantamiento"] +name = "Retorno de XP" +description = "La XP de encantamiento se te devuelve cuando encantas un objeto." +lore1 = "La experiencia gastada tiene una posibilidad de ser reembolsada cuando encantas un objeto" +lore2 = "Experiencia por encantamiento" +lore = ["La experiencia gastada tiene una posibilidad de ser reembolsada cuando encantas un objeto", "Experiencia por encantamiento"] # excavation [excavation] [excavation.haste] - name = "Excavador Veloz" - description = "¡Esto acelerara el proceso de excavacion con PRISA!" - lore1 = "Gana prisa mientras excavas" - lore2 = "x Niveles de prisa cuando empiezas a minar CUALQUIER bloque." - lore = ["Gana prisa mientras excavas", "x Niveles de prisa cuando empiezas a minar CUALQUIER bloque."] +name = "Excavador Veloz" +description = "¡Esto acelerara el proceso de excavacion con PRISA!" +lore1 = "Gana prisa mientras excavas" +lore2 = "x Niveles de prisa cuando empiezas a minar CUALQUIER bloque." +lore = ["Gana prisa mientras excavas", "x Niveles de prisa cuando empiezas a minar CUALQUIER bloque."] [excavation.spelunker] - name = "¡Espeleologo Supervidente!" - description = "¡Ve minerales con tus ojos, pero a traves del suelo!" - lore1 = "¡Mineral en tu mano secundaria, Bayas Luminosas en tu mano principal, y agachate!" - lore2 = "Rango de bloques: " - lore3 = "Consume Baya Luminosa al usar" - lore = ["¡Mineral en tu mano secundaria, Bayas Luminosas en tu mano principal, y agachate!", "Rango de bloques: ", "Consume Baya Luminosa al usar"] +name = "¡Espeleologo Supervidente!" +description = "¡Ve minerales con tus ojos, pero a traves del suelo!" +lore1 = "¡Mineral en tu mano secundaria, Bayas Luminosas en tu mano principal, y agachate!" +lore2 = "Rango de bloques: " +lore3 = "Consume Baya Luminosa al usar" +lore = ["¡Mineral en tu mano secundaria, Bayas Luminosas en tu mano principal, y agachate!", "Rango de bloques: ", "Consume Baya Luminosa al usar"] [excavation.drop_to_inventory] - name = "Pala: Soltar al Inventario" +name = "Pala: Soltar al Inventario" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "La Multiherramienta opulenta y sobrediseñada de Tackle" - lore1 = "Probablemente la mas poderosa de todas, te permite" - lore2 = "fusionar y cambiar herramientas dinamicamente sobre la marcha, segun tus necesidades." - lore3 = "Para fusionar, haz Shift+clic en un objeto sobre otro en tu inventario." - lore4 = "Para desvincular herramientas, suelta el objeto agachado y se desmontara." - lore5 = "No puedes romper herramientas en esta multiherramienta pero no puedes usar herramientas rotas" - lore6 = "total de objetos fusionables." - lore7 = "¡Puedes usar cinco o seis herramientas, o solo una!" - lore = ["Probablemente la mas poderosa de todas, te permite", "fusionar y cambiar herramientas dinamicamente sobre la marcha, segun tus necesidades.", "Para fusionar, haz Shift+clic en un objeto sobre otro en tu inventario.", "Para desvincular herramientas, suelta el objeto agachado y se desmontara.", "No puedes romper herramientas en esta multiherramienta pero no puedes usar herramientas rotas", "total de objetos fusionables.", "¡Puedes usar cinco o seis herramientas, o solo una!"] +name = "OMNI - T.O.O.L." +description = "La Multiherramienta opulenta y sobrediseñada de Tackle" +lore1 = "Probablemente la mas poderosa de todas, te permite" +lore2 = "fusionar y cambiar herramientas dinamicamente sobre la marcha, segun tus necesidades." +lore3 = "Para fusionar, haz Shift+clic en un objeto sobre otro en tu inventario." +lore4 = "Para desvincular herramientas, suelta el objeto agachado y se desmontara." +lore5 = "No puedes romper herramientas en esta multiherramienta pero no puedes usar herramientas rotas" +lore6 = "total de objetos fusionables." +lore7 = "¡Puedes usar cinco o seis herramientas, o solo una!" +lore = ["Probablemente la mas poderosa de todas, te permite", "fusionar y cambiar herramientas dinamicamente sobre la marcha, segun tus necesidades.", "Para fusionar, haz Shift+clic en un objeto sobre otro en tu inventario.", "Para desvincular herramientas, suelta el objeto agachado y se desmontara.", "No puedes romper herramientas en esta multiherramienta pero no puedes usar herramientas rotas", "total de objetos fusionables.", "¡Puedes usar cinco o seis herramientas, o solo una!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Aura de Crecimiento" - description = "Haz crecer la naturaleza a tu alrededor en un aura" - lore1 = "Radio de bloques" - lore2 = "Fuerza del Aura de Crecimiento" - lore3 = "Coste de comida" - lore = ["Radio de bloques", "Fuerza del Aura de Crecimiento", "Coste de comida"] +name = "Aura de Crecimiento" +description = "Haz crecer la naturaleza a tu alrededor en un aura" +lore1 = "Radio de bloques" +lore2 = "Fuerza del Aura de Crecimiento" +lore3 = "Coste de comida" +lore = ["Radio de bloques", "Fuerza del Aura de Crecimiento", "Coste de comida"] [herbalism.hippo] - name = "Hipopotamo del Herbalista" - description = "Consumir comida te da mas saturacion" - lore1 = "Comida) puntos de saturacion adicionales al consumir" - lore = ["Comida) puntos de saturacion adicionales al consumir"] +name = "Hipopotamo del Herbalista" +description = "Consumir comida te da mas saturacion" +lore1 = "Comida) puntos de saturacion adicionales al consumir" +lore = ["Comida) puntos de saturacion adicionales al consumir"] [herbalism.myconid] - name = "Miconido del Herbalista" - description = "Te da la capacidad de fabricar micelio" - lore1 = "Cualquier tierra, y un champiñon marron y rojo fabricaran micelio." - lore = ["Cualquier tierra, y un champiñon marron y rojo fabricaran micelio."] +name = "Miconido del Herbalista" +description = "Te da la capacidad de fabricar micelio" +lore1 = "Cualquier tierra, y un champiñon marron y rojo fabricaran micelio." +lore = ["Cualquier tierra, y un champiñon marron y rojo fabricaran micelio."] [herbalism.terralid] - name = "Terralido del Herbalista" - description = "Te da la capacidad de fabricar bloques de cesped" - lore1 = "Tres semillas sobre 3 tierra fabricaran 3 bloques de cesped." - lore = ["Tres semillas sobre 3 tierra fabricaran 3 bloques de cesped."] +name = "Terralido del Herbalista" +description = "Te da la capacidad de fabricar bloques de cesped" +lore1 = "Tres semillas sobre 3 tierra fabricaran 3 bloques de cesped." +lore = ["Tres semillas sobre 3 tierra fabricaran 3 bloques de cesped."] [herbalism.cobweb] - name = "Creador de Telaranas" - description = "Te da la capacidad de fabricar telaranas en una mesa de crafteo" - lore1 = "Nueve hilos fabricaran una telarana." - lore = ["Nueve hilos fabricaran una telarana."] +name = "Creador de Telaranas" +description = "Te da la capacidad de fabricar telaranas en una mesa de crafteo" +lore1 = "Nueve hilos fabricaran una telarana." +lore = ["Nueve hilos fabricaran una telarana."] [herbalism.mushroom_blocks] - name = "Fabricante de Hongos" - description = "Te da la capacidad de fabricar bloques de hongo en una mesa de crafteo" - lore1 = "Cuatro hongos para hacer un bloque, o un bloque para hacer un tallo." - lore = ["Cuatro hongos para hacer un bloque, o un bloque para hacer un tallo."] +name = "Fabricante de Hongos" +description = "Te da la capacidad de fabricar bloques de hongo en una mesa de crafteo" +lore1 = "Cuatro hongos para hacer un bloque, o un bloque para hacer un tallo." +lore = ["Cuatro hongos para hacer un bloque, o un bloque para hacer un tallo."] [herbalism.drop_to_inventory] - name = "Azada: Soltar al Inventario" +name = "Azada: Soltar al Inventario" [herbalism.hungry_shield] - name = "Escudo Hambriento" - description = "Recibe dano a tu hambre antes que a tu salud." - lore1 = "Resistido por el hambre" - lore = ["Resistido por el hambre"] +name = "Escudo Hambriento" +description = "Recibe dano a tu hambre antes que a tu salud." +lore1 = "Resistido por el hambre" +lore = ["Resistido por el hambre"] [herbalism.luck] - name = "Suerte del Herbalista" - description = "Cuando rompes hierba/flores, tienes la posibilidad de obtener un objeto aleatorio" - lore0 = "Flores = Comida, y Hierba = Semillas" - lore1 = "Probabilidad de obtener un objeto al romper flores" - lore2 = "Probabilidad de obtener un objeto al romper hierba" - lore = ["Flores = Comida, y Hierba = Semillas", "Probabilidad de obtener un objeto al romper flores", "Probabilidad de obtener un objeto al romper hierba"] +name = "Suerte del Herbalista" +description = "Cuando rompes hierba/flores, tienes la posibilidad de obtener un objeto aleatorio" +lore0 = "Flores = Comida, y Hierba = Semillas" +lore1 = "Probabilidad de obtener un objeto al romper flores" +lore2 = "Probabilidad de obtener un objeto al romper hierba" +lore = ["Flores = Comida, y Hierba = Semillas", "Probabilidad de obtener un objeto al romper flores", "Probabilidad de obtener un objeto al romper hierba"] [herbalism.replant] - name = "Cosechar y Replantar" - description = "Haz clic derecho en un cultivo con una azada para cosechar y replantar." - lore1 = "Radio de replantado en bloques" - lore = ["Radio de replantado en bloques"] +name = "Cosechar y Replantar" +description = "Haz clic derecho en un cultivo con una azada para cosechar y replantar." +lore1 = "Radio de replantado en bloques" +lore = ["Radio de replantado en bloques"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenalina" - description = "Inflige mas dano cuanto menos vida tengas (cuerpo a cuerpo)" - lore1 = "Dano maximo" - lore = ["Dano maximo"] +name = "Adrenalina" +description = "Inflige mas dano cuanto menos vida tengas (cuerpo a cuerpo)" +lore1 = "Dano maximo" +lore = ["Dano maximo"] [hunter.penalty] - name = "" - description = "" - lore1 = "Ganaras acumulaciones de veneno si te quedas sin hambre" - lore = ["Ganaras acumulaciones de veneno si te quedas sin hambre"] +name = "" +description = "" +lore1 = "Ganaras acumulaciones de veneno si te quedas sin hambre" +lore = ["Ganaras acumulaciones de veneno si te quedas sin hambre"] [hunter.drop_to_inventory] - name = "Objetos: Soltar al Inventario" - description = "Cuando matas algo / rompes un bloque con una espada, los drops se teletransportan a tu inventario" - lore1 = "Cada vez que un objeto cae de un mob/bloque que rompes, va a tu inventario si es posible." - lore = ["Cada vez que un objeto cae de un mob/bloque que rompes, va a tu inventario si es posible."] +name = "Objetos: Soltar al Inventario" +description = "Cuando matas algo / rompes un bloque con una espada, los drops se teletransportan a tu inventario" +lore1 = "Cada vez que un objeto cae de un mob/bloque que rompes, va a tu inventario si es posible." +lore = ["Cada vez que un objeto cae de un mob/bloque que rompes, va a tu inventario si es posible."] [hunter.invisibility] - name = "Paso Fugaz" - description = "Cuando te golpean obtienes invisibilidad, a costa de hambre" - lore1 = "Obtén invisibilidad pasiva al ser golpeado" - lore2 = "x Acumulaciones de invisibilidad durante 3 segundos al ser golpeado" - lore3 = "x Hambre acumulable" - lore4 = "Duracion y multiplicador de acumulaciones de hambre." - lore5 = "Duracion de la invisibilidad" - lore = ["Obtén invisibilidad pasiva al ser golpeado", "x Acumulaciones de invisibilidad durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Duracion de la invisibilidad"] +name = "Paso Fugaz" +description = "Cuando te golpean obtienes invisibilidad, a costa de hambre" +lore1 = "Obtén invisibilidad pasiva al ser golpeado" +lore2 = "x Acumulaciones de invisibilidad durante 3 segundos al ser golpeado" +lore3 = "x Hambre acumulable" +lore4 = "Duracion y multiplicador de acumulaciones de hambre." +lore5 = "Duracion de la invisibilidad" +lore = ["Obtén invisibilidad pasiva al ser golpeado", "x Acumulaciones de invisibilidad durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Duracion de la invisibilidad"] [hunter.jump_boost] - name = "Alturas del Cazador" - description = "Cuando te golpean obtienes impulso de salto, a costa de hambre" - lore1 = "Obtén impulso de salto pasivo al ser golpeado" - lore2 = "x Acumulaciones de impulso de salto durante 3 segundos al ser golpeado" - lore3 = "x Hambre acumulable" - lore4 = "Duracion y multiplicador de acumulaciones de hambre." - lore5 = "Multiplicador de acumulaciones de impulso de salto, no duracion." - lore = ["Obtén impulso de salto pasivo al ser golpeado", "x Acumulaciones de impulso de salto durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de impulso de salto, no duracion."] +name = "Alturas del Cazador" +description = "Cuando te golpean obtienes impulso de salto, a costa de hambre" +lore1 = "Obtén impulso de salto pasivo al ser golpeado" +lore2 = "x Acumulaciones de impulso de salto durante 3 segundos al ser golpeado" +lore3 = "x Hambre acumulable" +lore4 = "Duracion y multiplicador de acumulaciones de hambre." +lore5 = "Multiplicador de acumulaciones de impulso de salto, no duracion." +lore = ["Obtén impulso de salto pasivo al ser golpeado", "x Acumulaciones de impulso de salto durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de impulso de salto, no duracion."] [hunter.luck] - name = "Suerte del Cazador" - description = "Cuando te golpean obtienes suerte, a costa de hambre" - lore1 = "Obtén suerte pasiva al ser golpeado" - lore2 = "x Acumulaciones de suerte durante 3 segundos al ser golpeado" - lore3 = "x Hambre acumulable" - lore4 = "Duracion y multiplicador de acumulaciones de hambre." - lore5 = "Multiplicador de acumulaciones de suerte, no duracion." - lore = ["Obtén suerte pasiva al ser golpeado", "x Acumulaciones de suerte durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de suerte, no duracion."] +name = "Suerte del Cazador" +description = "Cuando te golpean obtienes suerte, a costa de hambre" +lore1 = "Obtén suerte pasiva al ser golpeado" +lore2 = "x Acumulaciones de suerte durante 3 segundos al ser golpeado" +lore3 = "x Hambre acumulable" +lore4 = "Duracion y multiplicador de acumulaciones de hambre." +lore5 = "Multiplicador de acumulaciones de suerte, no duracion." +lore = ["Obtén suerte pasiva al ser golpeado", "x Acumulaciones de suerte durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de suerte, no duracion."] [hunter.regen] - name = "Regeneracion del Cazador" - description = "Cuando te golpean obtienes regeneracion, a costa de hambre" - lore1 = "Obtén regeneracion pasiva al ser golpeado" - lore2 = "x Acumulaciones de regeneracion durante 3 segundos al ser golpeado" - lore3 = "x Hambre acumulable" - lore4 = "Duracion y multiplicador de acumulaciones de hambre." - lore5 = "Multiplicador de acumulaciones de regeneracion, no duracion." - lore = ["Obtén regeneracion pasiva al ser golpeado", "x Acumulaciones de regeneracion durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de regeneracion, no duracion."] +name = "Regeneracion del Cazador" +description = "Cuando te golpean obtienes regeneracion, a costa de hambre" +lore1 = "Obtén regeneracion pasiva al ser golpeado" +lore2 = "x Acumulaciones de regeneracion durante 3 segundos al ser golpeado" +lore3 = "x Hambre acumulable" +lore4 = "Duracion y multiplicador de acumulaciones de hambre." +lore5 = "Multiplicador de acumulaciones de regeneracion, no duracion." +lore = ["Obtén regeneracion pasiva al ser golpeado", "x Acumulaciones de regeneracion durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de regeneracion, no duracion."] [hunter.resistance] - name = "Resistencia del Cazador" - description = "Cuando te golpean obtienes resistencia, a costa de hambre" - lore1 = "Obtén resistencia pasiva al ser golpeado" - lore2 = "x Acumulaciones de resistencia durante 3 segundos al ser golpeado" - lore3 = "x Hambre acumulable" - lore4 = "Duracion y multiplicador de acumulaciones de hambre." - lore5 = "Multiplicador de acumulaciones de resistencia, no duracion." - lore = ["Obtén resistencia pasiva al ser golpeado", "x Acumulaciones de resistencia durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de resistencia, no duracion."] +name = "Resistencia del Cazador" +description = "Cuando te golpean obtienes resistencia, a costa de hambre" +lore1 = "Obtén resistencia pasiva al ser golpeado" +lore2 = "x Acumulaciones de resistencia durante 3 segundos al ser golpeado" +lore3 = "x Hambre acumulable" +lore4 = "Duracion y multiplicador de acumulaciones de hambre." +lore5 = "Multiplicador de acumulaciones de resistencia, no duracion." +lore = ["Obtén resistencia pasiva al ser golpeado", "x Acumulaciones de resistencia durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de resistencia, no duracion."] [hunter.speed] - name = "Velocidad del Cazador" - description = "Cuando te golpean obtienes velocidad, a costa de hambre" - lore1 = "Obtén velocidad pasiva al ser golpeado" - lore2 = "x Acumulaciones de velocidad durante 3 segundos al ser golpeado" - lore3 = "x Hambre acumulable" - lore4 = "Duracion y multiplicador de acumulaciones de hambre." - lore5 = "Multiplicador de acumulaciones de velocidad, no duracion." - lore = ["Obtén velocidad pasiva al ser golpeado", "x Acumulaciones de velocidad durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de velocidad, no duracion."] +name = "Velocidad del Cazador" +description = "Cuando te golpean obtienes velocidad, a costa de hambre" +lore1 = "Obtén velocidad pasiva al ser golpeado" +lore2 = "x Acumulaciones de velocidad durante 3 segundos al ser golpeado" +lore3 = "x Hambre acumulable" +lore4 = "Duracion y multiplicador de acumulaciones de hambre." +lore5 = "Multiplicador de acumulaciones de velocidad, no duracion." +lore = ["Obtén velocidad pasiva al ser golpeado", "x Acumulaciones de velocidad durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de velocidad, no duracion."] [hunter.strength] - name = "Fuerza del Cazador" - description = "Cuando te golpean obtienes fuerza, a costa de hambre" - lore1 = "Obtén fuerza pasiva al ser golpeado" - lore2 = "x Acumulaciones de fuerza durante 3 segundos al ser golpeado" - lore3 = "x Hambre acumulable" - lore4 = "Duracion y multiplicador de acumulaciones de hambre." - lore5 = "Multiplicador de acumulaciones de fuerza, no duracion." - lore = ["Obtén fuerza pasiva al ser golpeado", "x Acumulaciones de fuerza durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de fuerza, no duracion."] +name = "Fuerza del Cazador" +description = "Cuando te golpean obtienes fuerza, a costa de hambre" +lore1 = "Obtén fuerza pasiva al ser golpeado" +lore2 = "x Acumulaciones de fuerza durante 3 segundos al ser golpeado" +lore3 = "x Hambre acumulable" +lore4 = "Duracion y multiplicador de acumulaciones de hambre." +lore5 = "Multiplicador de acumulaciones de fuerza, no duracion." +lore = ["Obtén fuerza pasiva al ser golpeado", "x Acumulaciones de fuerza durante 3 segundos al ser golpeado", "x Hambre acumulable", "Duracion y multiplicador de acumulaciones de hambre.", "Multiplicador de acumulaciones de fuerza, no duracion."] # nether [nether] [nether.skull_toss] - name = "Lanzamiento de Cabeza de Wither" - description1 = "Desata tu Wither interior usando la" - description2 = "cabeza de" - description3 = "alguien." - lore1 = "Segundos de recarga entre lanzamientos de cabeza." - lore2 = "Usando Cabeza de Wither: Lanza una " - lore3 = "Cabeza de Wither" - lore4 = "que explota al impactar." - lore = ["Segundos de recarga entre lanzamientos de cabeza.", "Usando Cabeza de Wither: Lanza una ", "Cabeza de Wither", "que explota al impactar."] +name = "Lanzamiento de Cabeza de Wither" +description1 = "Desata tu Wither interior usando la" +description2 = "cabeza de" +description3 = "alguien." +lore1 = "Segundos de recarga entre lanzamientos de cabeza." +lore2 = "Usando Cabeza de Wither: Lanza una " +lore3 = "Cabeza de Wither" +lore4 = "que explota al impactar." +lore = ["Segundos de recarga entre lanzamientos de cabeza.", "Usando Cabeza de Wither: Lanza una ", "Cabeza de Wither", "que explota al impactar."] [nether.wither_resist] - name = "Resistencia al Wither" - description = "Resiste el marchitamiento mediante el poder de la Netherita." - lore1 = "probabilidad de negar el marchitamiento (por pieza)." - lore2 = "Pasiva: Llevar armadura de Netherita tiene probabilidad de negar " - lore3 = "el marchitamiento." - lore = ["probabilidad de negar el marchitamiento (por pieza).", "Pasiva: Llevar armadura de Netherita tiene probabilidad de negar ", "el marchitamiento."] +name = "Resistencia al Wither" +description = "Resiste el marchitamiento mediante el poder de la Netherita." +lore1 = "probabilidad de negar el marchitamiento (por pieza)." +lore2 = "Pasiva: Llevar armadura de Netherita tiene probabilidad de negar " +lore3 = "el marchitamiento." +lore = ["probabilidad de negar el marchitamiento (por pieza).", "Pasiva: Llevar armadura de Netherita tiene probabilidad de negar ", "el marchitamiento."] [nether.fire_resist] - name = "Resistencia al Fuego" - description = "Resiste el fuego endureciendo tu piel." - lore1 = "probabilidad de negar el efecto de quemadura!" - lore = ["probabilidad de negar el efecto de quemadura!"] +name = "Resistencia al Fuego" +description = "Resiste el fuego endureciendo tu piel." +lore1 = "probabilidad de negar el efecto de quemadura!" +lore = ["probabilidad de negar el efecto de quemadura!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Fundicion Automatica" - description = "Te permite fundir minerales vanilla extraidos" - lore1 = "Los minerales que se pueden fundir se funden automaticamente" - lore2 = "% de probabilidad de un extra" - lore = ["Los minerales que se pueden fundir se funden automaticamente", "% de probabilidad de un extra"] +name = "Fundicion Automatica" +description = "Te permite fundir minerales vanilla extraidos" +lore1 = "Los minerales que se pueden fundir se funden automaticamente" +lore2 = "% de probabilidad de un extra" +lore = ["Los minerales que se pueden fundir se funden automaticamente", "% de probabilidad de un extra"] [pickaxe.chisel] - name = "Cincel de Minerales" - description = "Haz clic derecho en minerales para cincelar mas mineral de ellos, con un gran coste de durabilidad." - lore1 = "Probabilidad de soltar" - lore2 = "Desgaste de herramienta" - lore = ["Probabilidad de soltar", "Desgaste de herramienta"] +name = "Cincel de Minerales" +description = "Haz clic derecho en minerales para cincelar mas mineral de ellos, con un gran coste de durabilidad." +lore1 = "Probabilidad de soltar" +lore2 = "Desgaste de herramienta" +lore = ["Probabilidad de soltar", "Desgaste de herramienta"] [pickaxe.drop_to_inventory] - name = "Pico: Soltar al Inventario" - description = "Cuando rompes un bloque, el objeto se teletransporta a tu inventario" - lore1 = "Cada vez que un objeto cae de un bloque que rompes, va a tu inventario si es posible." - lore = ["Cada vez que un objeto cae de un bloque que rompes, va a tu inventario si es posible."] +name = "Pico: Soltar al Inventario" +description = "Cuando rompes un bloque, el objeto se teletransporta a tu inventario" +lore1 = "Cada vez que un objeto cae de un bloque que rompes, va a tu inventario si es posible." +lore = ["Cada vez que un objeto cae de un bloque que rompes, va a tu inventario si es posible."] [pickaxe.silk_spawner] - name = "Pico Toque de Seda para Generadores" - description = "Hace que los generadores se suelten al romperse" - lore1 = "Hace que los generadores sean rompibles con toque de seda." - lore2 = "Hace que los generadores sean rompibles al agacharte." - lore = ["Hace que los generadores sean rompibles con toque de seda.", "Hace que los generadores sean rompibles al agacharte."] +name = "Pico Toque de Seda para Generadores" +description = "Hace que los generadores se suelten al romperse" +lore1 = "Hace que los generadores sean rompibles con toque de seda." +lore2 = "Hace que los generadores sean rompibles al agacharte." +lore = ["Hace que los generadores sean rompibles con toque de seda.", "Hace que los generadores sean rompibles al agacharte."] [pickaxe.vein_miner] - name = "Minero de Vetas" - description = "Te permite romper bloques en una veta/grupo de minerales vanilla" - lore1 = "Agachate y mina MINERALES" - lore2 = "Rango de minado de vetas" - lore3 = "¡Esta habilidad NO agrupa todos los drops!" - lore = ["Agachate y mina MINERALES", "Rango de minado de vetas", "¡Esta habilidad NO agrupa todos los drops!"] +name = "Minero de Vetas" +description = "Te permite romper bloques en una veta/grupo de minerales vanilla" +lore1 = "Agachate y mina MINERALES" +lore2 = "Rango de minado de vetas" +lore3 = "¡Esta habilidad NO agrupa todos los drops!" +lore = ["Agachate y mina MINERALES", "Rango de minado de vetas", "¡Esta habilidad NO agrupa todos los drops!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Recuperacion de Flechas" - description = "Recupera flechas despues de matar a un enemigo." - lore1 = "Probabilidad de recuperar flechas al golpear/matar" - lore2 = "Probabilidad: " - lore = ["Probabilidad de recuperar flechas al golpear/matar", "Probabilidad: "] +name = "Recuperacion de Flechas" +description = "Recupera flechas despues de matar a un enemigo." +lore1 = "Probabilidad de recuperar flechas al golpear/matar" +lore2 = "Probabilidad: " +lore = ["Probabilidad de recuperar flechas al golpear/matar", "Probabilidad: "] [ranged.web_shot] - name = "Trampa de Telarana" - description = "¡Rodea de telaranas a tu objetivo cuando lo golpeas!" - lore1 = "8 telaranas alrededor de una bola de nieve, ¡y lanza!" - lore2 = "segundos de jaula, aproximadamente." - lore = ["8 telaranas alrededor de una bola de nieve, ¡y lanza!", "segundos de jaula, aproximadamente."] +name = "Trampa de Telarana" +description = "¡Rodea de telaranas a tu objetivo cuando lo golpeas!" +lore1 = "8 telaranas alrededor de una bola de nieve, ¡y lanza!" +lore2 = "segundos de jaula, aproximadamente." +lore = ["8 telaranas alrededor de una bola de nieve, ¡y lanza!", "segundos de jaula, aproximadamente."] [ranged.force_shot] - name = "Tiro de Fuerza" - description = "¡Dispara proyectiles mas lejos y mas rapido!" - advancementname = "Tiro Largo" - advancementlore = "¡Acierta un disparo desde mas de 30 bloques de distancia!" - lore1 = "Velocidad de proyectil" - lore = ["Velocidad de proyectil"] +name = "Tiro de Fuerza" +description = "¡Dispara proyectiles mas lejos y mas rapido!" +advancementname = "Tiro Largo" +advancementlore = "¡Acierta un disparo desde mas de 30 bloques de distancia!" +lore1 = "Velocidad de proyectil" +lore = ["Velocidad de proyectil"] [ranged.lunge_shot] - name = "Tiro de Embestida" - description = "Mientras caes, tus flechas te lanzan en una direccion aleatoria" - lore1 = "Velocidad de rafaga aleatoria" - lore = ["Velocidad de rafaga aleatoria"] +name = "Tiro de Embestida" +description = "Mientras caes, tus flechas te lanzan en una direccion aleatoria" +lore1 = "Velocidad de rafaga aleatoria" +lore = ["Velocidad de rafaga aleatoria"] [ranged.arrow_piercing] - name = "Perforacion de Flechas" - description = "¡Anade perforacion a los proyectiles! ¡Dispara a traves de cosas!" - lore1 = "Objetivos perforados" - lore = ["Objetivos perforados"] +name = "Perforacion de Flechas" +description = "¡Anade perforacion a los proyectiles! ¡Dispara a traves de cosas!" +lore1 = "Objetivos perforados" +lore = ["Objetivos perforados"] # rift [rift] [rift.remote_access] - name = "Acceso Remoto" - description = "Extrae del vacio y accede a un contenedor marcado." - lore1 = "Perla de Ender + Brujula = Traslador Relicario" - lore2 = "Este objeto te permite acceder a contenedores de forma remota" - lore3 = "Una vez fabricado, mira el objeto para ver su uso" - notcontainer = "Eso no es un contenedor" - lore = ["Perla de Ender + Brujula = Traslador Relicario", "Este objeto te permite acceder a contenedores de forma remota", "Una vez fabricado, mira el objeto para ver su uso"] +name = "Acceso Remoto" +description = "Extrae del vacio y accede a un contenedor marcado." +lore1 = "Perla de Ender + Brujula = Traslador Relicario" +lore2 = "Este objeto te permite acceder a contenedores de forma remota" +lore3 = "Una vez fabricado, mira el objeto para ver su uso" +notcontainer = "Eso no es un contenedor" +lore = ["Perla de Ender + Brujula = Traslador Relicario", "Este objeto te permite acceder a contenedores de forma remota", "Una vez fabricado, mira el objeto para ver su uso"] [rift.blink] - name = "Parpadeo de Grieta" - description = "¡Teletransportacion instantanea de corto alcance, a solo un parpadeo!" - lore1 = "Bloques al parpadear (2x vertical)" - lore2 = "Mientras esprinteas: Doble toque en saltar para " - lore3 = "Parpadear" - lore = ["Bloques al parpadear (2x vertical)", "Mientras esprinteas: Doble toque en saltar para ", "Parpadear"] +name = "Parpadeo de Grieta" +description = "¡Teletransportacion instantanea de corto alcance, a solo un parpadeo!" +lore1 = "Bloques al parpadear (2x vertical)" +lore2 = "Mientras esprinteas: Doble toque en saltar para " +lore3 = "Parpadear" +lore = ["Bloques al parpadear (2x vertical)", "Mientras esprinteas: Doble toque en saltar para ", "Parpadear"] [rift.chest] - name = "Cofre de Ender Facil" - description = "Abre un cofre de Ender haciendo clic izquierdo con el en tu mano." - lore1 = "Haz clic en un cofre de Ender en tu mano para abrirlo (no lo coloques)" - lore = ["Haz clic en un cofre de Ender en tu mano para abrirlo (no lo coloques)"] +name = "Cofre de Ender Facil" +description = "Abre un cofre de Ender haciendo clic izquierdo con el en tu mano." +lore1 = "Haz clic en un cofre de Ender en tu mano para abrirlo (no lo coloques)" +lore = ["Haz clic en un cofre de Ender en tu mano para abrirlo (no lo coloques)"] [rift.descent] - name = "Anti-Levitacion" - description = "¿Estas cansado de estar atrapado en el aire? ¡Esta es la habilidad para ti!" - lore1 = "¡Simplemente agachate para descender y caeras a un ritmo menor al normal!" - lore2 = "Tiempo de recarga:" - lore = ["¡Simplemente agachate para descender y caeras a un ritmo menor al normal!", "Tiempo de recarga:"] +name = "Anti-Levitacion" +description = "¿Estas cansado de estar atrapado en el aire? ¡Esta es la habilidad para ti!" +lore1 = "¡Simplemente agachate para descender y caeras a un ritmo menor al normal!" +lore2 = "Tiempo de recarga:" +lore = ["¡Simplemente agachate para descender y caeras a un ritmo menor al normal!", "Tiempo de recarga:"] [rift.gate] - name = "Portal de Grieta" - description = "Teletransportate a una ubicacion marcada." - lore1 = "FABRICACION: Esmeralda + Fragmento de Amatista + Perla de Ender" - lore2 = "¡Lee antes de usar!" - lore3 = "5s de retraso, " - lore4 = "puedes morir mientras estas en esta animacion" - lore = ["FABRICACION: Esmeralda + Fragmento de Amatista + Perla de Ender", "¡Lee antes de usar!", "5s de retraso, ", "puedes morir mientras estas en esta animacion"] +name = "Portal de Grieta" +description = "Teletransportate a una ubicacion marcada." +lore1 = "FABRICACION: Esmeralda + Fragmento de Amatista + Perla de Ender" +lore2 = "¡Lee antes de usar!" +lore3 = "5s de retraso, " +lore4 = "puedes morir mientras estas en esta animacion" +lore = ["FABRICACION: Esmeralda + Fragmento de Amatista + Perla de Ender", "¡Lee antes de usar!", "5s de retraso, ", "puedes morir mientras estas en esta animacion"] [rift.resist] - name = "Resistencia de Grieta" - description = "Obtén resistencia al usar objetos y habilidades de Ender" - lore1 = "+ Pasiva: Proporciona resistencia cuando usas habilidades de grieta u objetos de Ender" - lore2 = "NO incluye el cofre de Ender portatil, solo las cosas que puedes consumir" - lore = ["+ Pasiva: Proporciona resistencia cuando usas habilidades de grieta u objetos de Ender", "NO incluye el cofre de Ender portatil, solo las cosas que puedes consumir"] +name = "Resistencia de Grieta" +description = "Obtén resistencia al usar objetos y habilidades de Ender" +lore1 = "+ Pasiva: Proporciona resistencia cuando usas habilidades de grieta u objetos de Ender" +lore2 = "NO incluye el cofre de Ender portatil, solo las cosas que puedes consumir" +lore = ["+ Pasiva: Proporciona resistencia cuando usas habilidades de grieta u objetos de Ender", "NO incluye el cofre de Ender portatil, solo las cosas que puedes consumir"] [rift.visage] - name = "Rostro de Grieta" - description = "Evita que los Enderman se vuelvan agresivos si tienes perlas de Ender en tu inventario." - lore1 = "Los Enderman no se volveran agresivos si tienes perlas de Ender en tu inventario." - lore = ["Los Enderman no se volveran agresivos si tienes perlas de Ender en tu inventario."] +name = "Rostro de Grieta" +description = "Evita que los Enderman se vuelvan agresivos si tienes perlas de Ender en tu inventario." +lore1 = "Los Enderman no se volveran agresivos si tienes perlas de Ender en tu inventario." +lore = ["Los Enderman no se volveran agresivos si tienes perlas de Ender en tu inventario."] # seaborn [seaborn] [seaborn.oxygen] - name = "Tanque de Oxigeno Organico" - description = "¡Almacena mas oxigeno en tus pequenos pulmones!" - lore1 = "Aumento de capacidad de oxigeno" - lore = ["Aumento de capacidad de oxigeno"] +name = "Tanque de Oxigeno Organico" +description = "¡Almacena mas oxigeno en tus pequenos pulmones!" +lore1 = "Aumento de capacidad de oxigeno" +lore = ["Aumento de capacidad de oxigeno"] [seaborn.fishers_fantasy] - name = "Fantasia del Pescador" - description = "¡Gana mas XP pescando y consigue mas peces!" - lore1 = "¡Por cada nivel hay posibilidad de obtener mas XP y peces!" - lore = ["¡Por cada nivel hay posibilidad de obtener mas XP y peces!"] +name = "Fantasia del Pescador" +description = "¡Gana mas XP pescando y consigue mas peces!" +lore1 = "¡Por cada nivel hay posibilidad de obtener mas XP y peces!" +lore = ["¡Por cada nivel hay posibilidad de obtener mas XP y peces!"] [seaborn.haste] - name = "Minero Tortuga" - description = "¡Mientras minas bajo el agua obtienes prisa!" - lore1 = "¡Prisa 3 se aplica bajo el agua mientras minas (se acumula con Afinidad Acuatica) despues de que desaparece el efecto de respiracion acuatica!" - lore = ["¡Prisa 3 se aplica bajo el agua mientras minas (se acumula con Afinidad Acuatica) despues de que desaparece el efecto de respiracion acuatica!"] +name = "Minero Tortuga" +description = "¡Mientras minas bajo el agua obtienes prisa!" +lore1 = "¡Prisa 3 se aplica bajo el agua mientras minas (se acumula con Afinidad Acuatica) despues de que desaparece el efecto de respiracion acuatica!" +lore = ["¡Prisa 3 se aplica bajo el agua mientras minas (se acumula con Afinidad Acuatica) despues de que desaparece el efecto de respiracion acuatica!"] [seaborn.night_vision] - name = "Vision de Tortuga" - description = "Mientras estas bajo el agua, obtienes vision nocturna" - lore1 = "¡Simplemente obtén vision nocturna bajo el agua despues de que desaparezca el efecto de respiracion acuatica!" - lore = ["¡Simplemente obtén vision nocturna bajo el agua despues de que desaparezca el efecto de respiracion acuatica!"] +name = "Vision de Tortuga" +description = "Mientras estas bajo el agua, obtienes vision nocturna" +lore1 = "¡Simplemente obtén vision nocturna bajo el agua despues de que desaparezca el efecto de respiracion acuatica!" +lore = ["¡Simplemente obtén vision nocturna bajo el agua despues de que desaparezca el efecto de respiracion acuatica!"] [seaborn.dolphin_grace] - name = "Gracia del Delfin" - description = "Nada como un delfin, sin los delfines" - lore1 = "+ Pasiva: obtén " - lore2 = "x velocidad (gracia de delfin)" - lore3 = "Precision de ingenieria aleman... espera, eso no esta bien... No compatible con Agilidad Acuatica" - lore = ["+ Pasiva: obtén ", "x velocidad (gracia de delfin)", "Precision de ingenieria aleman... espera, eso no esta bien... No compatible con Agilidad Acuatica"] +name = "Gracia del Delfin" +description = "Nada como un delfin, sin los delfines" +lore1 = "+ Pasiva: obtén " +lore2 = "x velocidad (gracia de delfin)" +lore3 = "Precision de ingenieria aleman... espera, eso no esta bien... No compatible con Agilidad Acuatica" +lore = ["+ Pasiva: obtén ", "x velocidad (gracia de delfin)", "Precision de ingenieria aleman... espera, eso no esta bien... No compatible con Agilidad Acuatica"] # stealth [stealth] [stealth.ghost_armor] - name = "Armadura Fantasma" - description = "Armadura que se construye lentamente cuando no recibes dano, dura 1 golpe" - lore1 = "Armadura maxima" - lore2 = "Velocidad" - lore = ["Armadura maxima", "Velocidad"] +name = "Armadura Fantasma" +description = "Armadura que se construye lentamente cuando no recibes dano, dura 1 golpe" +lore1 = "Armadura maxima" +lore2 = "Velocidad" +lore = ["Armadura maxima", "Velocidad"] [stealth.night_vision] - name = "Vision Sigilosa" - description = "Obtén vision nocturna mientras te agachas" - lore1 = "Obtén una rafaga de " - lore2 = "vision nocturna" - lore3 = "mientras te agachas" - lore = ["Obtén una rafaga de ", "vision nocturna", "mientras te agachas"] +name = "Vision Sigilosa" +description = "Obtén vision nocturna mientras te agachas" +lore1 = "Obtén una rafaga de " +lore2 = "vision nocturna" +lore3 = "mientras te agachas" +lore = ["Obtén una rafaga de ", "vision nocturna", "mientras te agachas"] [stealth.snatch] - name = "Arrebato de Objetos" - description = "¡Atrapa objetos caidos instantaneamente mientras te agachas!" - lore1 = "Radio de arrebato" - lore = ["Radio de arrebato"] +name = "Arrebato de Objetos" +description = "¡Atrapa objetos caidos instantaneamente mientras te agachas!" +lore1 = "Radio de arrebato" +lore = ["Radio de arrebato"] [stealth.speed] - name = "Velocidad Sigilosa" - description = "Obtén velocidad mientras te agachas" - lore1 = "Velocidad al agacharse" - lore = ["Velocidad al agacharse"] +name = "Velocidad Sigilosa" +description = "Obtén velocidad mientras te agachas" +lore1 = "Velocidad al agacharse" +lore = ["Velocidad al agacharse"] [stealth.ender_veil] - name = "Velo de Ender" - description = "No mas calabazas para evitar los ataques de Enderman" - lore1 = "Evita los ataques de Enderman mientras te agachas" - lore2 = "Evita todos los ataques de Enderman" - lore = ["Evita los ataques de Enderman mientras te agachas", "Evita todos los ataques de Enderman"] +name = "Velo de Ender" +description = "No mas calabazas para evitar los ataques de Enderman" +lore1 = "Evita los ataques de Enderman mientras te agachas" +lore2 = "Evita todos los ataques de Enderman" +lore = ["Evita los ataques de Enderman mientras te agachas", "Evita todos los ataques de Enderman"] # sword [sword] [sword.machete] - name = "Machete" - description = "¡Corta el follaje con facilidad!" - lore1 = "Radio de corte" - lore2 = "Tiempo de recarga del corte" - lore3 = "Desgaste de herramienta" - lore = ["Radio de corte", "Tiempo de recarga del corte", "Desgaste de herramienta"] +name = "Machete" +description = "¡Corta el follaje con facilidad!" +lore1 = "Radio de corte" +lore2 = "Tiempo de recarga del corte" +lore3 = "Desgaste de herramienta" +lore = ["Radio de corte", "Tiempo de recarga del corte", "Desgaste de herramienta"] [sword.bloody_blade] - name = "Hoja Sangrienta" - description = "¡Los golpes con tu espada causan sangrado!" - lore1 = "Golpear a una entidad viva con tu espada causa sangrado" - lore2 = "Duracion del sangrado" - lore3 = "Tiempo de recarga del sangrado" - lore = ["Golpear a una entidad viva con tu espada causa sangrado", "Duracion del sangrado", "Tiempo de recarga del sangrado"] +name = "Hoja Sangrienta" +description = "¡Los golpes con tu espada causan sangrado!" +lore1 = "Golpear a una entidad viva con tu espada causa sangrado" +lore2 = "Duracion del sangrado" +lore3 = "Tiempo de recarga del sangrado" +lore = ["Golpear a una entidad viva con tu espada causa sangrado", "Duracion del sangrado", "Tiempo de recarga del sangrado"] [sword.poisoned_blade] - name = "Hoja Envenenada" - description = "¡Los golpes con tu espada causan veneno!" - lore1 = "Golpear a una entidad viva con tu espada causa veneno" - lore2 = "Duracion del veneno" - lore3 = "Tiempo de recarga del veneno" - lore = ["Golpear a una entidad viva con tu espada causa veneno", "Duracion del veneno", "Tiempo de recarga del veneno"] +name = "Hoja Envenenada" +description = "¡Los golpes con tu espada causan veneno!" +lore1 = "Golpear a una entidad viva con tu espada causa veneno" +lore2 = "Duracion del veneno" +lore3 = "Tiempo de recarga del veneno" +lore = ["Golpear a una entidad viva con tu espada causa veneno", "Duracion del veneno", "Tiempo de recarga del veneno"] # taming [taming] [taming.damage] - name = "Dano de Domesticacion" - description = "Aumenta el dano infligido por tu animal domesticado." - lore1 = "Dano aumentado" - lore = ["Dano aumentado"] +name = "Dano de Domesticacion" +description = "Aumenta el dano infligido por tu animal domesticado." +lore1 = "Dano aumentado" +lore = ["Dano aumentado"] [taming.health] - name = "Salud de Domesticacion" - description = "Aumenta la salud de tu animal domesticado." - lore1 = "Salud aumentada" - lore = ["Salud aumentada"] +name = "Salud de Domesticacion" +description = "Aumenta la salud de tu animal domesticado." +lore1 = "Salud aumentada" +lore = ["Salud aumentada"] [taming.regeneration] - name = "Regeneracion de Domesticacion" - description = "Aumenta la regeneracion de tu animal domesticado." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Regeneracion de Domesticacion" +description = "Aumenta la regeneracion de tu animal domesticado." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Espinas" - description = "¡Refleja dano a tu atacante!" - lore1 = "Dano devuelto al ser golpeado" - lore = ["Dano devuelto al ser golpeado"] +name = "Espinas" +description = "¡Refleja dano a tu atacante!" +lore1 = "Dano devuelto al ser golpeado" +lore = ["Dano devuelto al ser golpeado"] [tragoul.globe] - name = "Globo de Dolor" - description = "¡Divide el dano que infliges segun la cantidad de enemigos a tu alrededor!" - lore1 = "Cuantos mas enemigos te rodean, menos dano infliges a cada uno" - lore2 = "Rango: " - lore3 = "Dano anadido a todas las entidades: " - lore = ["Cuantos mas enemigos te rodean, menos dano infliges a cada uno", "Rango: ", "Dano anadido a todas las entidades: "] +name = "Globo de Dolor" +description = "¡Divide el dano que infliges segun la cantidad de enemigos a tu alrededor!" +lore1 = "Cuantos mas enemigos te rodean, menos dano infliges a cada uno" +lore2 = "Rango: " +lore3 = "Dano anadido a todas las entidades: " +lore = ["Cuantos mas enemigos te rodean, menos dano infliges a cada uno", "Rango: ", "Dano anadido a todas las entidades: "] [tragoul.healing] - name = "Voluntad del Dolor" - description = "¡Recupera salud basandote en el dano que infliges!" - lore1 = "¡Hacer dano nunca se habia sentido tan bien! Curate con el dano infligido" - lore2 = "Hay una ventana de dano de 3 segundos para curacion y 1 segundo de recarga " - lore3 = "Curacion por porcentaje de dano: " - lore = ["¡Hacer dano nunca se habia sentido tan bien! Curate con el dano infligido", "Hay una ventana de dano de 3 segundos para curacion y 1 segundo de recarga ", "Curacion por porcentaje de dano: "] +name = "Voluntad del Dolor" +description = "¡Recupera salud basandote en el dano que infliges!" +lore1 = "¡Hacer dano nunca se habia sentido tan bien! Curate con el dano infligido" +lore2 = "Hay una ventana de dano de 3 segundos para curacion y 1 segundo de recarga " +lore3 = "Curacion por porcentaje de dano: " +lore = ["¡Hacer dano nunca se habia sentido tan bien! Curate con el dano infligido", "Hay una ventana de dano de 3 segundos para curacion y 1 segundo de recarga ", "Curacion por porcentaje de dano: "] [tragoul.lance] - name = "Lanzas de Cadaver" - description = "¡Matar a un enemigo o que una habilidad mate a un enemigo genera una lanza que dana a un enemigo cercano!" - lore1 = "Las lanzas buscaran desde cualquier cosa que mates, Y si esta habilidad mata a un enemigo." - lore2 = "Sacrifica una parte de tu vida para crear las lanzas (esto puede matarte)" - lore3 = "Lanzas maximas: 1 + " - lore = ["Las lanzas buscaran desde cualquier cosa que mates, Y si esta habilidad mata a un enemigo.", "Sacrifica una parte de tu vida para crear las lanzas (esto puede matarte)", "Lanzas maximas: 1 + "] +name = "Lanzas de Cadaver" +description = "¡Matar a un enemigo o que una habilidad mate a un enemigo genera una lanza que dana a un enemigo cercano!" +lore1 = "Las lanzas buscaran desde cualquier cosa que mates, Y si esta habilidad mata a un enemigo." +lore2 = "Sacrifica una parte de tu vida para crear las lanzas (esto puede matarte)" +lore3 = "Lanzas maximas: 1 + " +lore = ["Las lanzas buscaran desde cualquier cosa que mates, Y si esta habilidad mata a un enemigo.", "Sacrifica una parte de tu vida para crear las lanzas (esto puede matarte)", "Lanzas maximas: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Canon de Cristal" - description = "Dano extra sin armas cuanto menor sea tu valor de armadura" - lore1 = "x Dano a 0 de armadura" - lore2 = "Bonificacion de dano por nivel" - lore = ["x Dano a 0 de armadura", "Bonificacion de dano por nivel"] +name = "Canon de Cristal" +description = "Dano extra sin armas cuanto menor sea tu valor de armadura" +lore1 = "x Dano a 0 de armadura" +lore2 = "Bonificacion de dano por nivel" +lore = ["x Dano a 0 de armadura", "Bonificacion de dano por nivel"] [unarmed.power] - name = "Poder Desarmado" - description = "Dano sin armas mejorado" - lore1 = "Dano" - lore = ["Dano"] +name = "Poder Desarmado" +description = "Dano sin armas mejorado" +lore1 = "Dano" +lore = ["Dano"] [unarmed.sucker_punch] - name = "Golpe Traicionero" - description = "Golpes al esprintear, pero mas letales." - lore1 = "Dano" - lore2 = "El dano aumenta con tu velocidad mientras golpeas" - lore = ["Dano", "El dano aumenta con tu velocidad mientras golpeas"] +name = "Golpe Traicionero" +description = "Golpes al esprintear, pero mas letales." +lore1 = "Dano" +lore2 = "El dano aumenta con tu velocidad mientras golpeas" +lore = ["Dano", "El dano aumenta con tu velocidad mientras golpeas"] diff --git a/src/main/resources/fi_FI.toml b/src/main/resources/fi_FI.toml index a38e94ac4..fd268029b 100644 --- a/src/main/resources/fi_FI.toml +++ b/src/main/resources/fi_FI.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "Täytyy liikkua!" - description = "Kävele yli 1 kilometri (1 000 lohkoa)" +title = "Täytyy liikkua!" +description = "Kävele yli 1 kilometri (1 000 lohkoa)" [advancement.challenge_sprint_5k] - title = "Juokse 5K!" - description = "Kävele yli 5 kilometriä (5 000 lohkoa)" +title = "Juokse 5K!" +description = "Kävele yli 5 kilometriä (5 000 lohkoa)" [advancement.challenge_sprint_50k] - title = "Zoomaa 50K!" - description = "Kävele yli 50 kilometriä (50 000 lohkoa)" +title = "Zoomaa 50K!" +description = "Kävele yli 50 kilometriä (50 000 lohkoa)" [advancement.challenge_sprint_500k] - title = "Halki maailmankaikkeuden!!" - description = "Kävele yli 500 kilometriä (500 000 lohkoa)" +title = "Halki maailmankaikkeuden!!" +description = "Kävele yli 500 kilometriä (500 000 lohkoa)" [advancement.challenge_sprint_marathon] - title = "Juokse (kirjaimellinen) maraton!" - description = "Juokse yli 42 195 lohkoa!" +title = "Juokse (kirjaimellinen) maraton!" +description = "Juokse yli 42 195 lohkoa!" [advancement.challenge_place_1k] - title = "Aloitteleva rakentaja!" - description = "Aseta 1 000 lohkoa" +title = "Aloitteleva rakentaja!" +description = "Aseta 1 000 lohkoa" [advancement.challenge_place_5k] - title = "Keskitason rakentaja!" - description = "Aseta 5 000 lohkoa" +title = "Keskitason rakentaja!" +description = "Aseta 5 000 lohkoa" [advancement.challenge_place_50k] - title = "Edistynyt rakentaja!" - description = "Aseta 50 000 lohkoa" +title = "Edistynyt rakentaja!" +description = "Aseta 50 000 lohkoa" [advancement.challenge_place_500k] - title = "Rakennusmestari!" - description = "Aseta 500 000 lohkoa" +title = "Rakennusmestari!" +description = "Aseta 500 000 lohkoa" [advancement.challenge_place_5m] - title = "Symmetrian akolyytti!" - description = "TODELLISUUS ON LEIKKIKENTTÄSI! (5 miljoonaa lohkoa)" +title = "Symmetrian akolyytti!" +description = "TODELLISUUS ON LEIKKIKENTTÄSI! (5 miljoonaa lohkoa)" [advancement.challenge_chop_1k] - title = "Aloitteleva metsuri!" - description = "Kaada 1 000 lohkoa" +title = "Aloitteleva metsuri!" +description = "Kaada 1 000 lohkoa" [advancement.challenge_chop_5k] - title = "Keskitason metsuri!" - description = "Kaada 5 000 lohkoa" +title = "Keskitason metsuri!" +description = "Kaada 5 000 lohkoa" [advancement.challenge_chop_50k] - title = "Edistynyt metsuri!" - description = "Kaada 50 000 lohkoa" +title = "Edistynyt metsuri!" +description = "Kaada 50 000 lohkoa" [advancement.challenge_chop_500k] - title = "Mestarimetsuri!" - description = "Kaada 500 000 lohkoa" +title = "Mestarimetsuri!" +description = "Kaada 500 000 lohkoa" [advancement.challenge_chop_5m] - title = "Jackson-koira" - description = "Maailman paras poika! (5 miljoonaa lohkoa)" +title = "Jackson-koira" +description = "Maailman paras poika! (5 miljoonaa lohkoa)" [advancement.challenge_block_1k] - title = "Tuskin torjuttu!" - description = "Torju 1000 osumaa" +title = "Tuskin torjuttu!" +description = "Torju 1000 osumaa" [advancement.challenge_block_5k] - title = "Torjuminen on hauskaa!" - description = "Torju 5000 osumaa" +title = "Torjuminen on hauskaa!" +description = "Torju 5000 osumaa" [advancement.challenge_block_50k] - title = "Torjuminen on elämäni!" - description = "Torju 50 000 osumaa" +title = "Torjuminen on elämäni!" +description = "Torju 50 000 osumaa" [advancement.challenge_block_500k] - title = "Torjuminen on tarkoitukseni!" - description = "Torju 500 000 osumaa" +title = "Torjuminen on tarkoitukseni!" +description = "Torju 500 000 osumaa" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Torju 5 000 000 osumaa" +title = "Die Hand Die Verletzt" +description = "Torju 5 000 000 osumaa" [advancement.challenge_brew_1k] - title = "Aloitteleva alkemisti!" - description = "Juo 1000 taikajuomaa" +title = "Aloitteleva alkemisti!" +description = "Juo 1000 taikajuomaa" [advancement.challenge_brew_5k] - title = "Keskitason alkemisti!" - description = "Juo 5000 taikajuomaa" +title = "Keskitason alkemisti!" +description = "Juo 5000 taikajuomaa" [advancement.challenge_brew_50k] - title = "Edistynyt alkemisti!" - description = "Juo 50 000 taikajuomaa" +title = "Edistynyt alkemisti!" +description = "Juo 50 000 taikajuomaa" [advancement.challenge_brew_500k] - title = "Mestarialkemisti!" - description = "Juo 500 000 taikajuomaa" +title = "Mestarialkemisti!" +description = "Juo 500 000 taikajuomaa" [advancement.challenge_brew_5m] - title = "Alkemisti" - description = "Juo 5 000 000 taikajuomaa" +title = "Alkemisti" +description = "Juo 5 000 000 taikajuomaa" [advancement.challenge_brewsplash_1k] - title = "Aloitteleva roiskuttaja!" - description = "Roiskuta 1000 taikajuomaa" +title = "Aloitteleva roiskuttaja!" +description = "Roiskuta 1000 taikajuomaa" [advancement.challenge_brewsplash_5k] - title = "Keskitason roiskuttaja!" - description = "Roiskuta 5000 taikajuomaa" +title = "Keskitason roiskuttaja!" +description = "Roiskuta 5000 taikajuomaa" [advancement.challenge_brewsplash_50k] - title = "Edistynyt roiskuttaja!" - description = "Roiskuta 50 000 taikajuomaa" +title = "Edistynyt roiskuttaja!" +description = "Roiskuta 50 000 taikajuomaa" [advancement.challenge_brewsplash_500k] - title = "Mestarioiskuttaja!" - description = "Roiskuta 500 000 taikajuomaa" +title = "Mestarioiskuttaja!" +description = "Roiskuta 500 000 taikajuomaa" [advancement.challenge_brewsplash_5m] - title = "Roiskumestari" - description = "Roiskuta 5 000 000 taikajuomaa" +title = "Roiskumestari" +description = "Roiskuta 5 000 000 taikajuomaa" [advancement.challenge_craft_1k] - title = "Taitava nikkaroija!" - description = "Valmista 1000 esinettä" +title = "Taitava nikkaroija!" +description = "Valmista 1000 esinettä" [advancement.challenge_craft_5k] - title = "Äkäinen nikkaroija!" - description = "Valmista 5000 esinettä" +title = "Äkäinen nikkaroija!" +description = "Valmista 5000 esinettä" [advancement.challenge_craft_50k] - title = "Uuttera nikkaroija!" - description = "Valmista 50 000 esinettä" +title = "Uuttera nikkaroija!" +description = "Valmista 50 000 esinettä" [advancement.challenge_craft_500k] - title = "Korviahuumaava nikkaroija!" - description = "Valmista 500 000 esinettä" +title = "Korviahuumaava nikkaroija!" +description = "Valmista 500 000 esinettä" [advancement.challenge_craft_5m] - title = "Tuhoisa McNikkaroija" - description = "Valmista 5 000 000 esinettä" +title = "Tuhoisa McNikkaroija" +description = "Valmista 5 000 000 esinettä" [advancement.challenge_enchant_1k] - title = "Aloitteleva lumooja!" - description = "Lumoa 1000 esinettä" +title = "Aloitteleva lumooja!" +description = "Lumoa 1000 esinettä" [advancement.challenge_enchant_5k] - title = "Keskitason lumooja!" - description = "Lumoa 5000 esinettä" +title = "Keskitason lumooja!" +description = "Lumoa 5000 esinettä" [advancement.challenge_enchant_50k] - title = "Edistynyt lumooja!" - description = "Lumoa 50 000 esinettä" +title = "Edistynyt lumooja!" +description = "Lumoa 50 000 esinettä" [advancement.challenge_enchant_500k] - title = "Mestarilumooja!" - description = "Lumoa 500 000 esinettä" +title = "Mestarilumooja!" +description = "Lumoa 500 000 esinettä" [advancement.challenge_enchant_5m] - title = "Arvoituksellinen lumooja" - description = "Lumoa 5 000 000 esinettä" +title = "Arvoituksellinen lumooja" +description = "Lumoa 5 000 000 esinettä" [advancement.challenge_excavate_1k] - title = "Innokas kaivaja!" - description = "Kaiva 1000 lohkoa" +title = "Innokas kaivaja!" +description = "Kaiva 1000 lohkoa" [advancement.challenge_excavate_5k] - title = "Keskitason kaivaja!" - description = "Kaiva 5000 lohkoa" +title = "Keskitason kaivaja!" +description = "Kaiva 5000 lohkoa" [advancement.challenge_excavate_50k] - title = "Edistynyt kaivaja!" - description = "Kaiva 50 000 lohkoa" +title = "Edistynyt kaivaja!" +description = "Kaiva 50 000 lohkoa" [advancement.challenge_excavate_500k] - title = "Mestarikaivaja!" - description = "Kaiva 500 000 lohkoa" +title = "Mestarikaivaja!" +description = "Kaiva 500 000 lohkoa" [advancement.challenge_excavate_5m] - title = "Arvoituksellinen kaivaja" - description = "Kaiva 5 000 000 lohkoa" +title = "Arvoituksellinen kaivaja" +description = "Kaiva 5 000 000 lohkoa" [advancement.horrible_person] - title = "Olet kauhea ihminen" - description = "Aivan käsittämätöntä, todella" +title = "Olet kauhea ihminen" +description = "Aivan käsittämätöntä, todella" [advancement.challenge_turtle_egg_smasher] - title = "Kilpikonnanmunien murskaja!" - description = "Riko 100 kilpikonnan munaa" +title = "Kilpikonnanmunien murskaja!" +description = "Riko 100 kilpikonnan munaa" [advancement.challenge_turtle_egg_annihilator] - title = "Kilpikonnanmunien tuhoaja!" - description = "Riko 500 kilpikonnan munaa" +title = "Kilpikonnanmunien tuhoaja!" +description = "Riko 500 kilpikonnan munaa" [advancement.challenge_novice_hunter] - title = "Aloitteleva metsästäjä!" - description = "Tapa 100 olentoa" +title = "Aloitteleva metsästäjä!" +description = "Tapa 100 olentoa" [advancement.challenge_intermediate_hunter] - title = "Keskitason metsästäjä!" - description = "Tapa 500 olentoa" +title = "Keskitason metsästäjä!" +description = "Tapa 500 olentoa" [advancement.challenge_advanced_hunter] - title = "Edistynyt metsästäjä!" - description = "Tapa 5000 olentoa" +title = "Edistynyt metsästäjä!" +description = "Tapa 5000 olentoa" [advancement.challenge_creeper_conqueror] - title = "Creeperien valloittaja!" - description = "Tapa 50 creeperiä" +title = "Creeperien valloittaja!" +description = "Tapa 50 creeperiä" [advancement.challenge_creeper_annihilator] - title = "Creeperien tuhoaja!" - description = "Tapa 200 creeperiä" +title = "Creeperien tuhoaja!" +description = "Tapa 200 creeperiä" [advancement.challenge_pickaxe_1k] - title = "Aloitteleva kaivosmies" - description = "Riko 1000 lohkoa" +title = "Aloitteleva kaivosmies" +description = "Riko 1000 lohkoa" [advancement.challenge_pickaxe_5k] - title = "Taitava kaivosmies" - description = "Riko 5000 lohkoa" +title = "Taitava kaivosmies" +description = "Riko 5000 lohkoa" [advancement.challenge_pickaxe_50k] - title = "Asiantunteva kaivosmies" - description = "Riko 50 000 lohkoa" +title = "Asiantunteva kaivosmies" +description = "Riko 50 000 lohkoa" [advancement.challenge_pickaxe_500k] - title = "Mestarikaivosmies" - description = "Riko 500 000 lohkoa" +title = "Mestarikaivosmies" +description = "Riko 500 000 lohkoa" [advancement.challenge_pickaxe_5m] - title = "Legendaarinen kaivosmies" - description = "Riko 5 000 000 lohkoa" +title = "Legendaarinen kaivosmies" +description = "Riko 5 000 000 lohkoa" [advancement.challenge_eat_100] - title = "Niin paljon syötävää!" - description = "Syö yli 100 esinettä!" +title = "Niin paljon syötävää!" +description = "Syö yli 100 esinettä!" [advancement.challenge_eat_1000] - title = "Sammumaton nälkä!" - description = "Syö yli 1 000 esinettä!" +title = "Sammumaton nälkä!" +description = "Syö yli 1 000 esinettä!" [advancement.challenge_eat_10000] - title = "IKUINEN NÄLKÄ!" - description = "Syö yli 10 000 esinettä!" +title = "IKUINEN NÄLKÄ!" +description = "Syö yli 10 000 esinettä!" [advancement.challenge_harvest_100] - title = "Täysi sato" - description = "Korjaa yli 100 satoa!" +title = "Täysi sato" +description = "Korjaa yli 100 satoa!" [advancement.challenge_harvest_1000] - title = "Suursato" - description = "Korjaa yli 1 000 satoa!" +title = "Suursato" +description = "Korjaa yli 1 000 satoa!" [advancement.challenge_swim_1nm] - title = "Ihmissukellusvene!" - description = "Ui 1 meripeninkulmaa (1 852 lohkoa)" +title = "Ihmissukellusvene!" +description = "Ui 1 meripeninkulmaa (1 852 lohkoa)" [advancement.challenge_sneak_1k] - title = "Polvikipu" - description = "Hiivi yli kilometri (1 000 lohkoa)" +title = "Polvikipu" +description = "Hiivi yli kilometri (1 000 lohkoa)" [advancement.challenge_sneak_5k] - title = "Varjokulkija" - description = "Hiivi yli 5 000 lohkoa" +title = "Varjokulkija" +description = "Hiivi yli 5 000 lohkoa" [advancement.challenge_sneak_20k] - title = "Haamu" - description = "Hiivi yli 20 000 lohkoa" +title = "Haamu" +description = "Hiivi yli 20 000 lohkoa" [advancement.challenge_swim_5k] - title = "Syvasukeltaja" - description = "Ui yli 5 000 lohkoa" +title = "Syvasukeltaja" +description = "Ui yli 5 000 lohkoa" [advancement.challenge_swim_20k] - title = "Poseidonin Valittu" - description = "Ui yli 20 000 lohkoa" +title = "Poseidonin Valittu" +description = "Ui yli 20 000 lohkoa" [advancement.challenge_sword_100] - title = "Ensiveri" - description = "Iske 100 kertaa miekalla" +title = "Ensiveri" +description = "Iske 100 kertaa miekalla" [advancement.challenge_sword_1k] - title = "Miekkatanssija" - description = "Iske 1 000 kertaa miekalla" +title = "Miekkatanssija" +description = "Iske 1 000 kertaa miekalla" [advancement.challenge_sword_10k] - title = "Tuhat Viiltoa" - description = "Iske 10 000 kertaa miekalla" +title = "Tuhat Viiltoa" +description = "Iske 10 000 kertaa miekalla" [advancement.challenge_unarmed_100] - title = "Baari-tappelija" - description = "Iske 100 kertaa paljain kasin" +title = "Baari-tappelija" +description = "Iske 100 kertaa paljain kasin" [advancement.challenge_unarmed_1k] - title = "Rautanyrkit" - description = "Iske 1 000 kertaa paljain kasin" +title = "Rautanyrkit" +description = "Iske 1 000 kertaa paljain kasin" [advancement.challenge_unarmed_10k] - title = "Yksi Isku" - description = "Iske 10 000 kertaa paljain kasin" +title = "Yksi Isku" +description = "Iske 10 000 kertaa paljain kasin" [advancement.challenge_trag_1k] - title = "Veren Hinta" - description = "Ota vastaan 1 000 vahinkoa" +title = "Veren Hinta" +description = "Ota vastaan 1 000 vahinkoa" [advancement.challenge_trag_10k] - title = "Purppuravyory" - description = "Ota vastaan 10 000 vahinkoa" +title = "Purppuravyory" +description = "Ota vastaan 10 000 vahinkoa" [advancement.challenge_trag_100k] - title = "Karsimyksen Avatar" - description = "Ota vastaan 100 000 vahinkoa" +title = "Karsimyksen Avatar" +description = "Ota vastaan 100 000 vahinkoa" [advancement.challenge_ranged_100] - title = "Tauluharjoitus" - description = "Ammu 100 ammusta" +title = "Tauluharjoitus" +description = "Ammu 100 ammusta" [advancement.challenge_ranged_1k] - title = "Haukansilma" - description = "Ammu 1 000 ammusta" +title = "Haukansilma" +description = "Ammu 1 000 ammusta" [advancement.challenge_ranged_10k] - title = "Nuolimyrsky" - description = "Ammu 10 000 ammusta" +title = "Nuolimyrsky" +description = "Ammu 10 000 ammusta" [advancement.challenge_chronos_1h] - title = "Tik Tak" - description = "Vieta 1 tunti verkossa" +title = "Tik Tak" +description = "Vieta 1 tunti verkossa" [advancement.challenge_chronos_24h] - title = "Ajan Hiekat" - description = "Vieta 24 tuntia verkossa" +title = "Ajan Hiekat" +description = "Vieta 24 tuntia verkossa" [advancement.challenge_chronos_168h] - title = "Ajaton" - description = "Vieta 168 tuntia (1 viikko) verkossa" +title = "Ajaton" +description = "Vieta 168 tuntia (1 viikko) verkossa" [advancement.challenge_nether_50] - title = "Helvetin Portinvartija" - description = "Tapa 50 nether-olentoa" +title = "Helvetin Portinvartija" +description = "Tapa 50 nether-olentoa" [advancement.challenge_nether_500] - title = "Syvyyksien Vartija" - description = "Tapa 500 nether-olentoa" +title = "Syvyyksien Vartija" +description = "Tapa 500 nether-olentoa" [advancement.challenge_nether_5k] - title = "Netherin Herra" - description = "Tapa 5 000 nether-olentoa" +title = "Netherin Herra" +description = "Tapa 5 000 nether-olentoa" [advancement.challenge_rift_50] - title = "Avaruusanomalia" - description = "Teleporttaa 50 kertaa" +title = "Avaruusanomalia" +description = "Teleporttaa 50 kertaa" [advancement.challenge_rift_500] - title = "Tyhjon Kulkija" - description = "Teleporttaa 500 kertaa" +title = "Tyhjon Kulkija" +description = "Teleporttaa 500 kertaa" [advancement.challenge_rift_5k] - title = "Maailmojen Valilla" - description = "Teleporttaa 5 000 kertaa" +title = "Maailmojen Valilla" +description = "Teleporttaa 5 000 kertaa" [advancement.challenge_taming_10] - title = "Elainten Kuiskaaja" - description = "Jalosta 10 elaintta" +title = "Elainten Kuiskaaja" +description = "Jalosta 10 elaintta" [advancement.challenge_taming_50] - title = "Lauman Johtaja" - description = "Jalosta 50 elaintta" +title = "Lauman Johtaja" +description = "Jalosta 50 elaintta" [advancement.challenge_taming_500] - title = "Petojen Herra" - description = "Jalosta 500 elaintta" +title = "Petojen Herra" +description = "Jalosta 500 elaintta" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Nopeuden Demoni" - description = "Juokse yli 5 Kilometria (5,000 lohkoa)" +title = "Nopeuden Demoni" +description = "Juokse yli 5 Kilometria (5,000 lohkoa)" [advancement.challenge_sprint_dist_50k] - title = "Salamajalat" - description = "Juokse yli 50 Kilometria (50,000 lohkoa)" +title = "Salamajalat" +description = "Juokse yli 50 Kilometria (50,000 lohkoa)" [advancement.challenge_agility_swim_1k] - title = "Vedenkävijä" - description = "Ui yli 1 Kilometri (1,000 lohkoa)" +title = "Vedenkävijä" +description = "Ui yli 1 Kilometri (1,000 lohkoa)" [advancement.challenge_agility_swim_10k] - title = "Meren Kulkija" - description = "Ui yli 10 Kilometria (10,000 lohkoa)" +title = "Meren Kulkija" +description = "Ui yli 10 Kilometria (10,000 lohkoa)" [advancement.challenge_fly_1k] - title = "Taivaan Tanssija" - description = "Lennä yli 1 Kilometri (1,000 lohkoa)" +title = "Taivaan Tanssija" +description = "Lennä yli 1 Kilometri (1,000 lohkoa)" [advancement.challenge_fly_10k] - title = "Tuulen Ratsastaja" - description = "Lennä yli 10 Kilometria (10,000 lohkoa)" +title = "Tuulen Ratsastaja" +description = "Lennä yli 10 Kilometria (10,000 lohkoa)" [advancement.challenge_agility_sneak_500] - title = "Hiljaiset Askeleet" - description = "Hiivy yli 500 lohkoa" +title = "Hiljaiset Askeleet" +description = "Hiivy yli 500 lohkoa" [advancement.challenge_agility_sneak_5k] - title = "Haamuaskeleet" - description = "Hiivy yli 5 Kilometria (5,000 lohkoa)" +title = "Haamuaskeleet" +description = "Hiivy yli 5 Kilometria (5,000 lohkoa)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Purkuporukka" - description = "Riko 500 lohkoa" +title = "Purkuporukka" +description = "Riko 500 lohkoa" [advancement.challenge_demolish_5k] - title = "Tuhoamispallo" - description = "Riko 5,000 lohkoa" +title = "Tuhoamispallo" +description = "Riko 5,000 lohkoa" [advancement.challenge_value_placed_10k] - title = "Arvokas Rakentaja" - description = "Aseta lohkoja 10,000 arvon edestä" +title = "Arvokas Rakentaja" +description = "Aseta lohkoja 10,000 arvon edestä" [advancement.challenge_value_placed_100k] - title = "Mestariarkkitehti" - description = "Aseta lohkoja 100,000 arvon edestä" +title = "Mestariarkkitehti" +description = "Aseta lohkoja 100,000 arvon edestä" [advancement.challenge_demolish_val_5k] - title = "Kierrätysasiantuntija" - description = "Pelasta 5,000 lohkoarvoa purkamisesta" +title = "Kierrätysasiantuntija" +description = "Pelasta 5,000 lohkoarvoa purkamisesta" [advancement.challenge_demolish_val_50k] - title = "Täysi Purku" - description = "Pelasta 50,000 lohkoarvoa purkamisesta" +title = "Täysi Purku" +description = "Pelasta 50,000 lohkoarvoa purkamisesta" [advancement.challenge_high_build_100] - title = "Taivaanrakentaja" - description = "Aseta 100 lohkoa Y=128 yläpuolelle" +title = "Taivaanrakentaja" +description = "Aseta 100 lohkoa Y=128 yläpuolelle" [advancement.challenge_high_build_1k] - title = "Pilviarkkitehti" - description = "Aseta 1,000 lohkoa Y=128 yläpuolelle" +title = "Pilviarkkitehti" +description = "Aseta 1,000 lohkoa Y=128 yläpuolelle" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Kirveen Heiluttaja" - description = "Heiluta kirvestä 500 kertaa" +title = "Kirveen Heiluttaja" +description = "Heiluta kirvestä 500 kertaa" [advancement.challenge_axe_swing_5k] - title = "Berserki" - description = "Heiluta kirvestä 5,000 kertaa" +title = "Berserki" +description = "Heiluta kirvestä 5,000 kertaa" [advancement.challenge_axe_damage_1k] - title = "Halkaisija" - description = "Tee 1,000 vahinkoa kirveellä" +title = "Halkaisija" +description = "Tee 1,000 vahinkoa kirveellä" [advancement.challenge_axe_damage_10k] - title = "Pyövelin Kirves" - description = "Tee 10,000 vahinkoa kirveellä" +title = "Pyövelin Kirves" +description = "Tee 10,000 vahinkoa kirveellä" [advancement.challenge_axe_value_5k] - title = "Puukauppias" - description = "Kerää puuta 5,000 arvon edestä" +title = "Puukauppias" +description = "Kerää puuta 5,000 arvon edestä" [advancement.challenge_axe_value_50k] - title = "Metsäparoni" - description = "Kerää puuta 50,000 arvon edestä" +title = "Metsäparoni" +description = "Kerää puuta 50,000 arvon edestä" [advancement.challenge_leaves_500] - title = "Lehtipuhallin" - description = "Raivaa 500 lehtilohkoa kirveellä" +title = "Lehtipuhallin" +description = "Raivaa 500 lehtilohkoa kirveellä" [advancement.challenge_leaves_5k] - title = "Lehdenpudottaja" - description = "Raivaa 5,000 lehtilohkoa kirveellä" +title = "Lehdenpudottaja" +description = "Raivaa 5,000 lehtilohkoa kirveellä" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Vahingon Imeijä" - description = "Torju 1,000 vahinkoa kilvellä" +title = "Vahingon Imeijä" +description = "Torju 1,000 vahinkoa kilvellä" [advancement.challenge_block_dmg_10k] - title = "Elävä Kilpi" - description = "Torju 10,000 vahinkoa kilvellä" +title = "Elävä Kilpi" +description = "Torju 10,000 vahinkoa kilvellä" [advancement.challenge_block_proj_100] - title = "Nuolenkääntäjä" - description = "Torju 100 ammusta kilvellä" +title = "Nuolenkääntäjä" +description = "Torju 100 ammusta kilvellä" [advancement.challenge_block_proj_1k] - title = "Ammuskilpi" - description = "Torju 1,000 ammusta kilvellä" +title = "Ammuskilpi" +description = "Torju 1,000 ammusta kilvellä" [advancement.challenge_block_melee_500] - title = "Torjuntamestari" - description = "Torju 500 lähitaisteluhyökkäystä kilvellä" +title = "Torjuntamestari" +description = "Torju 500 lähitaisteluhyökkäystä kilvellä" [advancement.challenge_block_melee_5k] - title = "Rautainen Linnake" - description = "Torju 5,000 lähitaisteluhyökkäystä kilvellä" +title = "Rautainen Linnake" +description = "Torju 5,000 lähitaisteluhyökkäystä kilvellä" [advancement.challenge_block_heavy_50] - title = "Panssarivaunu" - description = "Torju 50 raskasta hyökkäystä (yli 5 vahinkoa)" +title = "Panssarivaunu" +description = "Torju 50 raskasta hyökkäystä (yli 5 vahinkoa)" [advancement.challenge_block_heavy_500] - title = "Järkkymätön Esine" - description = "Torju 500 raskasta hyökkäystä (yli 5 vahinkoa)" +title = "Järkkymätön Esine" +description = "Torju 500 raskasta hyökkäystä (yli 5 vahinkoa)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "Panimon Varusteet" - description = "Aseta 10 panemoteline" +title = "Panimon Varusteet" +description = "Aseta 10 panemoteline" [advancement.challenge_brew_stands_50] - title = "Liemitehdas" - description = "Aseta 50 panemotelinetta" +title = "Liemitehdas" +description = "Aseta 50 panemotelinetta" [advancement.challenge_brew_strong_25] - title = "Vahva Keitos" - description = "Juo 25 tehostettua liemea" +title = "Vahva Keitos" +description = "Juo 25 tehostettua liemea" [advancement.challenge_brew_strong_250] - title = "Maksimiteho" - description = "Juo 250 tehostettua liemea" +title = "Maksimiteho" +description = "Juo 250 tehostettua liemea" [advancement.challenge_brew_splash_hits_50] - title = "Roiskealue" - description = "Osuma 50 olentoon roiskejuomilla" +title = "Roiskealue" +description = "Osuma 50 olentoon roiskejuomilla" [advancement.challenge_brew_splash_hits_500] - title = "Ruttohtori" - description = "Osuma 500 olentoon roiskejuomilla" +title = "Ruttohtori" +description = "Osuma 500 olentoon roiskejuomilla" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Levoton" - description = "Matkusta 1 Kilometri aktiivisena" +title = "Levoton" +description = "Matkusta 1 Kilometri aktiivisena" [advancement.challenge_active_dist_10k] - title = "Tienraivaaja" - description = "Matkusta 10 Kilometria aktiivisena" +title = "Tienraivaaja" +description = "Matkusta 10 Kilometria aktiivisena" [advancement.challenge_active_dist_100k] - title = "Ikiliikkuja" - description = "Matkusta 100 Kilometria aktiivisena" +title = "Ikiliikkuja" +description = "Matkusta 100 Kilometria aktiivisena" [advancement.challenge_beds_10] - title = "Aikainen Herääjä" - description = "Nuku sängyssä 10 kertaa" +title = "Aikainen Herääjä" +description = "Nuku sängyssä 10 kertaa" [advancement.challenge_beds_100] - title = "Ajan Ohittaja" - description = "Nuku sängyssä 100 kertaa" +title = "Ajan Ohittaja" +description = "Nuku sängyssä 100 kertaa" [advancement.challenge_chronos_tp_50] - title = "Ajallinen Siirtymä" - description = "Teleporttaa 50 kertaa" +title = "Ajallinen Siirtymä" +description = "Teleporttaa 50 kertaa" [advancement.challenge_chronos_tp_500] - title = "Ajan Vääristymä" - description = "Teleporttaa 500 kertaa" +title = "Ajan Vääristymä" +description = "Teleporttaa 500 kertaa" [advancement.challenge_chronos_deaths_10] - title = "Kuolevainen" - description = "Kuole 10 kertaa" +title = "Kuolevainen" +description = "Kuole 10 kertaa" [advancement.challenge_chronos_deaths_100] - title = "Kuoleman Uhmaaja" - description = "Kuole 100 kertaa" +title = "Kuoleman Uhmaaja" +description = "Kuole 100 kertaa" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Käsityön Arvo" - description = "Valmista esineitä 10,000 kokonaisarvon edestä" +title = "Käsityön Arvo" +description = "Valmista esineitä 10,000 kokonaisarvon edestä" [advancement.challenge_craft_value_100k] - title = "Taitokäsityöläinen" - description = "Valmista esineitä 100,000 kokonaisarvon edestä" +title = "Taitokäsityöläinen" +description = "Valmista esineitä 100,000 kokonaisarvon edestä" [advancement.challenge_craft_tools_25] - title = "Työkaluseppä" - description = "Valmista 25 työkalua" +title = "Työkaluseppä" +description = "Valmista 25 työkalua" [advancement.challenge_craft_tools_250] - title = "Mestaritakoja" - description = "Valmista 250 työkalua" +title = "Mestaritakoja" +description = "Valmista 250 työkalua" [advancement.challenge_craft_armor_25] - title = "Haarniskaseppä" - description = "Valmista 25 haarniskakappaletta" +title = "Haarniskaseppä" +description = "Valmista 25 haarniskakappaletta" [advancement.challenge_craft_armor_250] - title = "Mestarihaarniskaseppä" - description = "Valmista 250 haarniskakappaletta" +title = "Mestarihaarniskaseppä" +description = "Valmista 250 haarniskakappaletta" [advancement.challenge_craft_mass_25k] - title = "Massatuottaja" - description = "Valmista 25,000 esinettä" +title = "Massatuottaja" +description = "Valmista 25,000 esinettä" [advancement.challenge_craft_mass_250k] - title = "Teollinen Vallankumous" - description = "Valmista 250,000 esinettä" +title = "Teollinen Vallankumous" +description = "Valmista 250,000 esinettä" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Keräilijä" - description = "Löydä 50 ainutlaatuista esinettä" +title = "Keräilijä" +description = "Löydä 50 ainutlaatuista esinettä" [advancement.challenge_discover_items_250] - title = "Luetteloija" - description = "Löydä 250 ainutlaatuista esinettä" +title = "Luetteloija" +description = "Löydä 250 ainutlaatuista esinettä" [advancement.challenge_discover_blocks_50] - title = "Kartoittaja" - description = "Löydä 50 ainutlaatuista lohkoa" +title = "Kartoittaja" +description = "Löydä 50 ainutlaatuista lohkoa" [advancement.challenge_discover_blocks_250] - title = "Geologi" - description = "Löydä 250 ainutlaatuista lohkoa" +title = "Geologi" +description = "Löydä 250 ainutlaatuista lohkoa" [advancement.challenge_discover_mobs_25] - title = "Tarkkailija" - description = "Löydä 25 ainutlaatuista olentoa" +title = "Tarkkailija" +description = "Löydä 25 ainutlaatuista olentoa" [advancement.challenge_discover_mobs_75] - title = "Luonnontutkija" - description = "Löydä 75 ainutlaatuista olentoa" +title = "Luonnontutkija" +description = "Löydä 75 ainutlaatuista olentoa" [advancement.challenge_discover_biomes_10] - title = "Vaeltaja" - description = "Löydä 10 ainutlaatuista biomia" +title = "Vaeltaja" +description = "Löydä 10 ainutlaatuista biomia" [advancement.challenge_discover_biomes_40] - title = "Maailmanmatkaaja" - description = "Löydä 40 ainutlaatuista biomia" +title = "Maailmanmatkaaja" +description = "Löydä 40 ainutlaatuista biomia" [advancement.challenge_discover_foods_10] - title = "Herkkusuu" - description = "Löydä 10 ainutlaatuista ruokaa" +title = "Herkkusuu" +description = "Löydä 10 ainutlaatuista ruokaa" [advancement.challenge_discover_foods_30] - title = "Keittiömestari" - description = "Löydä 30 ainutlaatuista ruokaa" +title = "Keittiömestari" +description = "Löydä 30 ainutlaatuista ruokaa" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Voiman Kutoja" - description = "Kerää 100 lumoamisvoimaa" +title = "Voiman Kutoja" +description = "Kerää 100 lumoamisvoimaa" [advancement.challenge_enchant_power_1k] - title = "Taikuuden Mestari" - description = "Kerää 1,000 lumoamisvoimaa" +title = "Taikuuden Mestari" +description = "Kerää 1,000 lumoamisvoimaa" [advancement.challenge_enchant_levels_1k] - title = "Tasojen Kuluttaja" - description = "Käytä 1,000 kokemustasoa lumoamiseen" +title = "Tasojen Kuluttaja" +description = "Käytä 1,000 kokemustasoa lumoamiseen" [advancement.challenge_enchant_levels_10k] - title = "XP-nielu" - description = "Käytä 10,000 kokemustasoa lumoamiseen" +title = "XP-nielu" +description = "Käytä 10,000 kokemustasoa lumoamiseen" [advancement.challenge_enchant_high_25] - title = "Suurpelaaja" - description = "Suorita 25 maksimitason lumoamista" +title = "Suurpelaaja" +description = "Suorita 25 maksimitason lumoamista" [advancement.challenge_enchant_high_250] - title = "Legendaarinen Lumooja" - description = "Suorita 250 maksimitason lumoamista" +title = "Legendaarinen Lumooja" +description = "Suorita 250 maksimitason lumoamista" [advancement.challenge_enchant_total_500] - title = "Tasojen Polttaja" - description = "Käytä 500 kokonaistasoa kaikkiin lumouksiin" +title = "Tasojen Polttaja" +description = "Käytä 500 kokonaistasoa kaikkiin lumouksiin" [advancement.challenge_enchant_total_5k] - title = "Taikainvestointi" - description = "Käytä 5,000 kokonaistasoa kaikkiin lumouksiin" +title = "Taikainvestointi" +description = "Käytä 5,000 kokonaistasoa kaikkiin lumouksiin" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Kaivaja" - description = "Heiluta lapiota 500 kertaa" +title = "Kaivaja" +description = "Heiluta lapiota 500 kertaa" [advancement.challenge_dig_swing_5k] - title = "Kaivinkone" - description = "Heiluta lapiota 5,000 kertaa" +title = "Kaivinkone" +description = "Heiluta lapiota 5,000 kertaa" [advancement.challenge_dig_damage_1k] - title = "Lapiosoturi" - description = "Tee 1,000 vahinkoa lapiolla" +title = "Lapiosoturi" +description = "Tee 1,000 vahinkoa lapiolla" [advancement.challenge_dig_damage_10k] - title = "Lapiomestari" - description = "Tee 10,000 vahinkoa lapiolla" +title = "Lapiomestari" +description = "Tee 10,000 vahinkoa lapiolla" [advancement.challenge_dig_value_5k] - title = "Maakauppias" - description = "Kaiva lohkoja 5,000 arvon edestä" +title = "Maakauppias" +description = "Kaiva lohkoja 5,000 arvon edestä" [advancement.challenge_dig_value_50k] - title = "Maaparoni" - description = "Kaiva lohkoja 50,000 arvon edestä" +title = "Maaparoni" +description = "Kaiva lohkoja 50,000 arvon edestä" [advancement.challenge_dig_gravel_500] - title = "Soranjauhaja" - description = "Kaiva 500 sora-, hiekka- tai savilohkoa" +title = "Soranjauhaja" +description = "Kaiva 500 sora-, hiekka- tai savilohkoa" [advancement.challenge_dig_gravel_5k] - title = "Hiekanseuloja" - description = "Kaiva 5,000 sora-, hiekka- tai savilohkoa" +title = "Hiekanseuloja" +description = "Kaiva 5,000 sora-, hiekka- tai savilohkoa" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Siementen Kylväjä" - description = "Istuta 100 kasvia" +title = "Siementen Kylväjä" +description = "Istuta 100 kasvia" [advancement.challenge_plant_1k] - title = "Vihreä Peukalo" - description = "Istuta 1,000 kasvia" +title = "Vihreä Peukalo" +description = "Istuta 1,000 kasvia" [advancement.challenge_plant_5k] - title = "Maatalousparoni" - description = "Istuta 5,000 kasvia" +title = "Maatalousparoni" +description = "Istuta 5,000 kasvia" [advancement.challenge_compost_50] - title = "Kierrättäjä" - description = "Kompostoi 50 esinettä" +title = "Kierrättäjä" +description = "Kompostoi 50 esinettä" [advancement.challenge_compost_500] - title = "Maan Rikastuttaja" - description = "Kompostoi 500 esinettä" +title = "Maan Rikastuttaja" +description = "Kompostoi 500 esinettä" [advancement.challenge_shear_50] - title = "Keritsijä" - description = "Keritä 50 olentoa" +title = "Keritsijä" +description = "Keritä 50 olentoa" [advancement.challenge_shear_250] - title = "Lauman Herra" - description = "Keritä 250 olentoa" +title = "Lauman Herra" +description = "Keritä 250 olentoa" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Tappaja" - description = "Tuhoa 500 olentoa" +title = "Tappaja" +description = "Tuhoa 500 olentoa" [advancement.challenge_kills_5k] - title = "Pyöveli" - description = "Tuhoa 5,000 olentoa" +title = "Pyöveli" +description = "Tuhoa 5,000 olentoa" [advancement.challenge_boss_1] - title = "Pomohaaastaja" - description = "Tuhoa pomo-olento" +title = "Pomohaaastaja" +description = "Tuhoa pomo-olento" [advancement.challenge_boss_10] - title = "Legendantuhoaja" - description = "Tuhoa 10 pomo-olentoa" +title = "Legendantuhoaja" +description = "Tuhoa 10 pomo-olentoa" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Kuihtunut" - description = "Kestä 500 kuihtumis-vahinkoa" +title = "Kuihtunut" +description = "Kestä 500 kuihtumis-vahinkoa" [advancement.challenge_wither_dmg_5k] - title = "Taudin Selviytyjä" - description = "Kestä 5,000 kuihtumis-vahinkoa" +title = "Taudin Selviytyjä" +description = "Kestä 5,000 kuihtumis-vahinkoa" [advancement.challenge_wither_skel_25] - title = "Luunkerääjä" - description = "Tuhoa 25 wither-luurankoa" +title = "Luunkerääjä" +description = "Tuhoa 25 wither-luurankoa" [advancement.challenge_wither_skel_250] - title = "Luurankojen Vitsaus" - description = "Tuhoa 250 wither-luurankoa" +title = "Luurankojen Vitsaus" +description = "Tuhoa 250 wither-luurankoa" [advancement.challenge_wither_boss_1] - title = "Witherin Tuhoaja" - description = "Voita Wither" +title = "Witherin Tuhoaja" +description = "Voita Wither" [advancement.challenge_wither_boss_10] - title = "Netherin Hallitsija" - description = "Voita Wither 10 kertaa" +title = "Netherin Hallitsija" +description = "Voita Wither 10 kertaa" [advancement.challenge_roses_10] - title = "Kuoleman Puutarhuri" - description = "Riko 10 wither-ruusua" +title = "Kuoleman Puutarhuri" +description = "Riko 10 wither-ruusua" [advancement.challenge_roses_100] - title = "Taudin Kerääjä" - description = "Riko 100 wither-ruusua" +title = "Taudin Kerääjä" +description = "Riko 100 wither-ruusua" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Kaivosmiehen Käsi" - description = "Heiluta hakuukirvestä 500 kertaa" +title = "Kaivosmiehen Käsi" +description = "Heiluta hakuukirvestä 500 kertaa" [advancement.challenge_pick_swing_5k] - title = "Tunnelinkaivaja" - description = "Heiluta hakuukirvestä 5,000 kertaa" +title = "Tunnelinkaivaja" +description = "Heiluta hakuukirvestä 5,000 kertaa" [advancement.challenge_pick_damage_1k] - title = "Hakutaistelija" - description = "Tee 1,000 vahinkoa hakuukirveellä" +title = "Hakutaistelija" +description = "Tee 1,000 vahinkoa hakuukirveellä" [advancement.challenge_pick_damage_10k] - title = "Sotahakku" - description = "Tee 10,000 vahinkoa hakuukirveellä" +title = "Sotahakku" +description = "Tee 10,000 vahinkoa hakuukirveellä" [advancement.challenge_pick_value_5k] - title = "Jalokivenetsijä" - description = "Louhii lohkoja 5,000 arvon edestä" +title = "Jalokivenetsijä" +description = "Louhii lohkoja 5,000 arvon edestä" [advancement.challenge_pick_value_50k] - title = "Malmiparoni" - description = "Louhii lohkoja 50,000 arvon edestä" +title = "Malmiparoni" +description = "Louhii lohkoja 50,000 arvon edestä" [advancement.challenge_pick_ores_500] - title = "Malminetsijä" - description = "Louhii 500 malmilohkoa" +title = "Malminetsijä" +description = "Louhii 500 malmilohkoa" [advancement.challenge_pick_ores_5k] - title = "Mestarikaivostyöläinen" - description = "Louhii 5,000 malmilohkoa" +title = "Mestarikaivostyöläinen" +description = "Louhii 5,000 malmilohkoa" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Tarkka-ampuja" - description = "Tee 1,000 etävahinkoa" +title = "Tarkka-ampuja" +description = "Tee 1,000 etävahinkoa" [advancement.challenge_ranged_dmg_10k] - title = "Tappava Jousimies" - description = "Tee 10,000 etävahinkoa" +title = "Tappava Jousimies" +description = "Tee 10,000 etävahinkoa" [advancement.challenge_ranged_dist_5k] - title = "Pitkä Kantama" - description = "Ammu ammuksia 5,000 lohkon kokonaismatkalle" +title = "Pitkä Kantama" +description = "Ammu ammuksia 5,000 lohkon kokonaismatkalle" [advancement.challenge_ranged_dist_50k] - title = "Mailia-ampuja" - description = "Ammu ammuksia 50,000 lohkon kokonaismatkalle" +title = "Mailia-ampuja" +description = "Ammu ammuksia 50,000 lohkon kokonaismatkalle" [advancement.challenge_ranged_kills_50] - title = "Jousimies" - description = "Tapa 50 olentoa etäaseilla" +title = "Jousimies" +description = "Tapa 50 olentoa etäaseilla" [advancement.challenge_ranged_kills_500] - title = "Mestarijousimies" - description = "Tapa 500 olentoa etäaseilla" +title = "Mestarijousimies" +description = "Tapa 500 olentoa etäaseilla" [advancement.challenge_longshot_25] - title = "Tarkkuuskivääri" - description = "Osuma 25 pitkän matkan osumaa (yli 30 lohkoa)" +title = "Tarkkuuskivääri" +description = "Osuma 25 pitkän matkan osumaa (yli 30 lohkoa)" [advancement.challenge_longshot_250] - title = "Kotkansilmä" - description = "Osuma 250 pitkän matkan osumaa (yli 30 lohkoa)" +title = "Kotkansilmä" +description = "Osuma 250 pitkän matkan osumaa (yli 30 lohkoa)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "Helmenheittäjä" - description = "Heitä 50 enderhelmeä" +title = "Helmenheittäjä" +description = "Heitä 50 enderhelmeä" [advancement.challenge_rift_pearls_500] - title = "Teleporttiaddikti" - description = "Heitä 500 enderhelmeä" +title = "Teleporttiaddikti" +description = "Heitä 500 enderhelmeä" [advancement.challenge_rift_enderman_50] - title = "Enderman-metsästäjä" - description = "Tuhoa 50 endermania" +title = "Enderman-metsästäjä" +description = "Tuhoa 50 endermania" [advancement.challenge_rift_enderman_500] - title = "Tyhjyyden Vainooja" - description = "Tuhoa 500 endermania" +title = "Tyhjyyden Vainooja" +description = "Tuhoa 500 endermania" [advancement.challenge_rift_dragon_500] - title = "Lohikäärmetaistelija" - description = "Tee 500 vahinkoa Enderilohikäärmeelle" +title = "Lohikäärmetaistelija" +description = "Tee 500 vahinkoa Enderilohikäärmeelle" [advancement.challenge_rift_dragon_5k] - title = "Lohikäärmeen Vitsaus" - description = "Tee 5,000 vahinkoa Enderilohikäärmeelle" +title = "Lohikäärmeen Vitsaus" +description = "Tee 5,000 vahinkoa Enderilohikäärmeelle" [advancement.challenge_rift_crystal_10] - title = "Kristallinmurskaaja" - description = "Tuhoa 10 enderkristallia" +title = "Kristallinmurskaaja" +description = "Tuhoa 10 enderkristallia" [advancement.challenge_rift_crystal_100] - title = "Endin Tuhoaja" - description = "Tuhoa 100 enderkristallia" +title = "Endin Tuhoaja" +description = "Tuhoa 100 enderkristallia" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Onkija" - description = "Pyydystä 25 kalaa" +title = "Onkija" +description = "Pyydystä 25 kalaa" [advancement.challenge_fish_250] - title = "Mestarikalastaja" - description = "Pyydystä 250 kalaa" +title = "Mestarikalastaja" +description = "Pyydystä 250 kalaa" [advancement.challenge_drowned_25] - title = "Hukkuneiden Metsästäjä" - description = "Tuhoa 25 hukkunutta" +title = "Hukkuneiden Metsästäjä" +description = "Tuhoa 25 hukkunutta" [advancement.challenge_drowned_250] - title = "Valtameren Puhdistaja" - description = "Tuhoa 250 hukkunutta" +title = "Valtameren Puhdistaja" +description = "Tuhoa 250 hukkunutta" [advancement.challenge_guardian_10] - title = "Vartijantappaja" - description = "Tuhoa 10 vartijaa" +title = "Vartijantappaja" +description = "Tuhoa 10 vartijaa" [advancement.challenge_guardian_100] - title = "Temppelinhyökkääjä" - description = "Tuhoa 100 vartijaa" +title = "Temppelinhyökkääjä" +description = "Tuhoa 100 vartijaa" [advancement.challenge_underwater_blocks_100] - title = "Vedenalainen Kaivaja" - description = "Riko 100 lohkoa veden alla" +title = "Vedenalainen Kaivaja" +description = "Riko 100 lohkoa veden alla" [advancement.challenge_underwater_blocks_1k] - title = "Vesi-insinööri" - description = "Riko 1,000 lohkoa veden alla" +title = "Vesi-insinööri" +description = "Riko 1,000 lohkoa veden alla" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Selkäänpuukottaja" - description = "Tee 500 vahinkoa hiipien" +title = "Selkäänpuukottaja" +description = "Tee 500 vahinkoa hiipien" [advancement.challenge_stealth_dmg_5k] - title = "Hiljainen Tappaja" - description = "Tee 5,000 vahinkoa hiipien" +title = "Hiljainen Tappaja" +description = "Tee 5,000 vahinkoa hiipien" [advancement.challenge_stealth_kills_10] - title = "Salamurhaaja" - description = "Tapa 10 olentoa hiipien" +title = "Salamurhaaja" +description = "Tapa 10 olentoa hiipien" [advancement.challenge_stealth_kills_100] - title = "Varjojen Niittäjä" - description = "Tapa 100 olentoa hiipien" +title = "Varjojen Niittäjä" +description = "Tapa 100 olentoa hiipien" [advancement.challenge_stealth_time_1h] - title = "Kärsivällinen" - description = "Vietä 1 tunti hiipien (3,600 sekuntia)" +title = "Kärsivällinen" +description = "Vietä 1 tunti hiipien (3,600 sekuntia)" [advancement.challenge_stealth_time_10h] - title = "Varjojen Mestari" - description = "Vietä 10 tuntia hiipien (36,000 sekuntia)" +title = "Varjojen Mestari" +description = "Vietä 10 tuntia hiipien (36,000 sekuntia)" [advancement.challenge_stealth_arrows_50] - title = "Hiljainen Jousimies" - description = "Ammu 50 nuolta hiipien" +title = "Hiljainen Jousimies" +description = "Ammu 50 nuolta hiipien" [advancement.challenge_stealth_arrows_500] - title = "Haamujousimies" - description = "Ammu 500 nuolta hiipien" +title = "Haamujousimies" +description = "Ammu 500 nuolta hiipien" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Miekan Oppilas" - description = "Tee 1,000 vahinkoa miekalla" +title = "Miekan Oppilas" +description = "Tee 1,000 vahinkoa miekalla" [advancement.challenge_sword_dmg_10k] - title = "Miekkamies" - description = "Tee 10,000 vahinkoa miekalla" +title = "Miekkamies" +description = "Tee 10,000 vahinkoa miekalla" [advancement.challenge_sword_kills_50] - title = "Kaksintaistelija" - description = "Tapa 50 olentoa miekalla" +title = "Kaksintaistelija" +description = "Tapa 50 olentoa miekalla" [advancement.challenge_sword_kills_500] - title = "Gladiaattori" - description = "Tapa 500 olentoa miekalla" +title = "Gladiaattori" +description = "Tapa 500 olentoa miekalla" [advancement.challenge_sword_crit_50] - title = "Kriittinen Iskijä" - description = "Osuma 50 kriittistä osumaa miekalla" +title = "Kriittinen Iskijä" +description = "Osuma 50 kriittistä osumaa miekalla" [advancement.challenge_sword_crit_500] - title = "Tarkkuusmestari" - description = "Osuma 500 kriittistä osumaa miekalla" +title = "Tarkkuusmestari" +description = "Osuma 500 kriittistä osumaa miekalla" [advancement.challenge_sword_heavy_25] - title = "Raskas Lyönti" - description = "Osuma 25 raskasta osumaa miekalla (yli 8 vahinkoa)" +title = "Raskas Lyönti" +description = "Osuma 25 raskasta osumaa miekalla (yli 8 vahinkoa)" [advancement.challenge_sword_heavy_250] - title = "Tuhoisa Isku" - description = "Osuma 250 raskasta osumaa miekalla (yli 8 vahinkoa)" +title = "Tuhoisa Isku" +description = "Osuma 250 raskasta osumaa miekalla (yli 8 vahinkoa)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Pedon Kouluttaja" - description = "Lemmikkisi tekevät yhteensä 500 vahinkoa" +title = "Pedon Kouluttaja" +description = "Lemmikkisi tekevät yhteensä 500 vahinkoa" [advancement.challenge_pet_dmg_5k] - title = "Sodanjohtaja" - description = "Lemmikkisi tekevät yhteensä 5,000 vahinkoa" +title = "Sodanjohtaja" +description = "Lemmikkisi tekevät yhteensä 5,000 vahinkoa" [advancement.challenge_tamed_10] - title = "Eläinten Ystävä" - description = "Kesytä 10 eläintä" +title = "Eläinten Ystävä" +description = "Kesytä 10 eläintä" [advancement.challenge_tamed_100] - title = "Eläintarhanhoitaja" - description = "Kesytä 100 eläintä" +title = "Eläintarhanhoitaja" +description = "Kesytä 100 eläintä" [advancement.challenge_pet_kills_25] - title = "Laumataktiikka" - description = "Lemmikkisi tappavat 25 olentoa" +title = "Laumataktiikka" +description = "Lemmikkisi tappavat 25 olentoa" [advancement.challenge_pet_kills_250] - title = "Alfakomentaja" - description = "Lemmikkisi tappavat 250 olentoa" +title = "Alfakomentaja" +description = "Lemmikkisi tappavat 250 olentoa" [advancement.challenge_taming_2500] - title = "Jalostusasiantuntija" - description = "Jalosta 2,500 eläintä" +title = "Jalostusasiantuntija" +description = "Jalosta 2,500 eläintä" [advancement.challenge_taming_25k] - title = "Genetiikan Mestari" - description = "Jalosta 25,000 eläintä" +title = "Genetiikan Mestari" +description = "Jalosta 25,000 eläintä" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Nyrkkeilysäkki" - description = "Vastaanota 500 osumaa" +title = "Nyrkkeilysäkki" +description = "Vastaanota 500 osumaa" [advancement.challenge_trag_hits_5k] - title = "Rangaistuksen Rakastaja" - description = "Vastaanota 5,000 osumaa" +title = "Rangaistuksen Rakastaja" +description = "Vastaanota 5,000 osumaa" [advancement.challenge_trag_deaths_10] - title = "Yhdeksän Henkeä" - description = "Kuole 10 kertaa" +title = "Yhdeksän Henkeä" +description = "Kuole 10 kertaa" [advancement.challenge_trag_deaths_100] - title = "Toistuva Painajainen" - description = "Kuole 100 kertaa" +title = "Toistuva Painajainen" +description = "Kuole 100 kertaa" [advancement.challenge_trag_fire_500] - title = "Palon Uhri" - description = "Kestä 500 tulivahinkoa" +title = "Palon Uhri" +description = "Kestä 500 tulivahinkoa" [advancement.challenge_trag_fire_5k] - title = "Feeniks" - description = "Kestä 5,000 tulivahinkoa" +title = "Feeniks" +description = "Kestä 5,000 tulivahinkoa" [advancement.challenge_trag_fall_500] - title = "Painovoiman Testi" - description = "Kestä 500 putoamisvahinkoa" +title = "Painovoiman Testi" +description = "Kestä 500 putoamisvahinkoa" [advancement.challenge_trag_fall_5k] - title = "Loppunopeus" - description = "Kestä 5,000 putoamisvahinkoa" +title = "Loppunopeus" +description = "Kestä 5,000 putoamisvahinkoa" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Tappelija" - description = "Tee 1,000 vahinkoa paljain nyrkein" +title = "Tappelija" +description = "Tee 1,000 vahinkoa paljain nyrkein" [advancement.challenge_unarmed_dmg_10k] - title = "Taistelulajien Mestari" - description = "Tee 10,000 vahinkoa paljain nyrkein" +title = "Taistelulajien Mestari" +description = "Tee 10,000 vahinkoa paljain nyrkein" [advancement.challenge_unarmed_kills_25] - title = "Paljaat Nyrkiy" - description = "Tapa 25 olentoa paljain nyrkein" +title = "Paljaat Nyrkiy" +description = "Tapa 25 olentoa paljain nyrkein" [advancement.challenge_unarmed_kills_250] - title = "Legendan Nyrkki" - description = "Tapa 250 olentoa paljain nyrkein" +title = "Legendan Nyrkki" +description = "Tapa 250 olentoa paljain nyrkein" [advancement.challenge_unarmed_crit_25] - title = "Kriittinen Lyönti" - description = "Osuma 25 kriittistä osumaa paljain nyrkein" +title = "Kriittinen Lyönti" +description = "Osuma 25 kriittistä osumaa paljain nyrkein" [advancement.challenge_unarmed_crit_250] - title = "Tarkka Nyrkki" - description = "Osuma 250 kriittistä osumaa paljain nyrkein" +title = "Tarkka Nyrkki" +description = "Osuma 250 kriittistä osumaa paljain nyrkein" [advancement.challenge_unarmed_heavy_25] - title = "Voimaisku" - description = "Osuma 25 raskasta osumaa paljain nyrkein (yli 6 vahinkoa)" +title = "Voimaisku" +description = "Osuma 25 raskasta osumaa paljain nyrkein (yli 6 vahinkoa)" [advancement.challenge_unarmed_heavy_250] - title = "Tyrmäyskuningas" - description = "Osuma 250 raskasta osumaa paljain nyrkein (yli 6 vahinkoa)" +title = "Tyrmäyskuningas" +description = "Osuma 250 raskasta osumaa paljain nyrkein (yli 6 vahinkoa)" # items [items] [items.bound_ender_peral] - name = "Reliikki-avainportti" - usage1 = "Shift + Vasen klikkaus sitomiseen" - usage2 = "Oikea klikkaus päästäksesi sidottuun varastoon" +name = "Reliikki-avainportti" +usage1 = "Shift + Vasen klikkaus sitomiseen" +usage2 = "Oikea klikkaus päästäksesi sidottuun varastoon" [items.bound_eye_of_ender] - name = "Silmäankkuri" - usage1 = "Oikea klikkaus kuluttaaksesi ja teleportataksesi sidottuun sijaintiin" - usage2 = "Shift + Vasen klikkaus sitoaksesi lohkoon" +name = "Silmäankkuri" +usage1 = "Oikea klikkaus kuluttaaksesi ja teleportataksesi sidottuun sijaintiin" +usage2 = "Shift + Vasen klikkaus sitoaksesi lohkoon" [items.bound_redstone_torch] - name = "Redstonekaukosäädin" - usage1 = "Oikea klikkaus lähettääksesi 1 tikin redstone-pulssin" - usage2 = "Shift + Vasen klikkaus 'Kohde'-lohkoon sitoaksesi" +name = "Redstonekaukosäädin" +usage1 = "Oikea klikkaus lähettääksesi 1 tikin redstone-pulssin" +usage2 = "Shift + Vasen klikkaus 'Kohde'-lohkoon sitoaksesi" [items.bound_snowball] - name = "Seittiansa!" - usage1 = "Heitä luodaksesi väliaikainen seittiansa kohteeseen" +name = "Seittiansa!" +usage1 = "Heitä luodaksesi väliaikainen seittiansa kohteeseen" [items.chrono_time_bottle] - name = "Aikaa pullossa" - usage1 = "Kerää passiivisesti aikaa ollessaan varastossasi" - usage2 = "Oikea-klikkaa ajastettuja lohkoja tai eläinten poikasia käyttääksesi varastoitua aikaa" - stored = "Varastoitu aika" +name = "Aikaa pullossa" +usage1 = "Kerää passiivisesti aikaa ollessaan varastossasi" +usage2 = "Oikea-klikkaa ajastettuja lohkoja tai eläinten poikasia käyttääksesi varastoitua aikaa" +stored = "Varastoitu aika" [items.chrono_time_bomb] - name = "Aikapommi" - usage1 = "Oikea-klikkaa laukaistaksesi aikasalama, joka luo ajallisen kentän" +name = "Aikapommi" +usage1 = "Oikea-klikkaa laukaistaksesi aikasalama, joka luo ajallisen kentän" [items.elevator_block] - name = "Hissilohko" - usage1 = "Hyppää teleportataksesi ylös" - usage2 = "Kyykisty teleportataksesi alas" - usage3 = "Vähintään 2 ilmalohkoa hissien välillä" +name = "Hissilohko" +usage1 = "Hyppää teleportataksesi ylös" +usage2 = "Kyykisty teleportataksesi alas" +usage3 = "Vähintään 2 ilmalohkoa hissien välillä" # snippets [snippets] [snippets.gui] - level = "Taso" - knowledge = "tietämys" - power_used = "Voimaa käytetty" - not_learned = "Ei opittu" - xp = "XP:tä" - welcome = "Tervetuloa!" - welcome_back = "Tervetuloa takaisin!" - xp_bonus_for_time = "XP:tä" - max_ability_power = "Kykytehon enimmäismäärä" - unlock_this_by_clicking = "Avaa tämä oikea-klikkaamalla: " - back = "Takaisin" - unlearn_all = "Unohda kaikki" - unlearned_all = "Kaikki unohdettu" +level = "Taso" +knowledge = "tietämys" +power_used = "Voimaa käytetty" +not_learned = "Ei opittu" +xp = "XP:tä" +welcome = "Tervetuloa!" +welcome_back = "Tervetuloa takaisin!" +xp_bonus_for_time = "XP:tä" +max_ability_power = "Kykytehon enimmäismäärä" +unlock_this_by_clicking = "Avaa tämä oikea-klikkaamalla: " +back = "Takaisin" +unlearn_all = "Unohda kaikki" +unlearned_all = "Kaikki unohdettu" [snippets.adapt_menu] - may_not_unlearn = "ET VOI UNOHTAA" - may_unlearn = "VOIT OPPIA/UNOHTAA" - knowledge_cost = "Tietämyksen hinta" - knowledge_available = "Tietämystä saatavilla" - already_learned = "Jo opittu" - unlearn_refund = "Klikkaa unohtaaksesi ja saadaksesi hyvityksen" - no_refunds = "HARDCORE, HYVITYKSET POIS KÄYTÖSTÄ" - knowledge = "tietämys" - click_learn = "Klikkaa oppiaksesi" - no_knowledge = "(Sinulla ei ole tietämystä)" - you_only_have = "Sinulla on vain" - how_to_level_up = "Nosta taitotasoja kasvattaaksesi maksimitehoa." - not_enough_power = "Tehoa ei ole tarpeeksi! Jokainen kykytaso maksaa 1 tehon." - power = "teho" - power_drain = "Tehon kulutus" - learned = "Opittu " - unlearned = "Unohdettu " - activator_block = "Kirjahylly" +may_not_unlearn = "ET VOI UNOHTAA" +may_unlearn = "VOIT OPPIA/UNOHTAA" +knowledge_cost = "Tietämyksen hinta" +knowledge_available = "Tietämystä saatavilla" +already_learned = "Jo opittu" +unlearn_refund = "Klikkaa unohtaaksesi ja saadaksesi hyvityksen" +no_refunds = "HARDCORE, HYVITYKSET POIS KÄYTÖSTÄ" +knowledge = "tietämys" +click_learn = "Klikkaa oppiaksesi" +no_knowledge = "(Sinulla ei ole tietämystä)" +you_only_have = "Sinulla on vain" +how_to_level_up = "Nosta taitotasoja kasvattaaksesi maksimitehoa." +not_enough_power = "Tehoa ei ole tarpeeksi! Jokainen kykytaso maksaa 1 tehon." +power = "teho" +power_drain = "Tehon kulutus" +learned = "Opittu " +unlearned = "Unohdettu " +activator_block = "Kirjahylly" [snippets.knowledge_orb] - contains = "sisältää" - knowledge = "tietämys" - rightclick = "Oikea-klikkaa" - togainknowledge = "saadaksesi tämän tietämyksen" - knowledge_orb = "Tietämyspallo" +contains = "sisältää" +knowledge = "tietämys" +rightclick = "Oikea-klikkaa" +togainknowledge = "saadaksesi tämän tietämyksen" +knowledge_orb = "Tietämyspallo" [snippets.experience_orb] - contains = "sisältää" - xp = "Kokemus" - rightclick = "Oikea-klikkaa" - togainxp = "saadaksesi tämän kokemuksen" - xporb = "Kokemuspallo" +contains = "sisältää" +xp = "Kokemus" +rightclick = "Oikea-klikkaa" +togainxp = "saadaksesi tämän kokemuksen" +xporb = "Kokemuspallo" # skill [skill] [skill.agility] - name = "Ketteryys" - icon = "⇉" - description = "Ketteryys on kykyä liikkua nopeasti ja sujuvasti esteiden edessä." +name = "Ketteryys" +icon = "⇉" +description = "Ketteryys on kykyä liikkua nopeasti ja sujuvasti esteiden edessä." [skill.architect] - name = "Arkkitehti" - icon = "⬧" - description = "Rakenteet ovat maailman rakennuspalikoita. Todellisuus on käsissäsi, sinun hallittavanasi." +name = "Arkkitehti" +icon = "⬧" +description = "Rakenteet ovat maailman rakennuspalikoita. Todellisuus on käsissäsi, sinun hallittavanasi." [skill.axes] - name = "Kirveet" - icon = "🪓" - description1 = "Miksi kaataa puita, kun voit kaataa " - description2 = "asioita" - description3 = "sen sijaan, sama lopputulos!" +name = "Kirveet" +icon = "🪓" +description1 = "Miksi kaataa puita, kun voit kaataa " +description2 = "asioita" +description3 = "sen sijaan, sama lopputulos!" [skill.brewing] - name = "Panimotaito" - icon = "❦" - description = "Tuplakupla, triplakupla, nelinkertainen kupla - en silti saa tätä taikajuomaa kattilaan" +name = "Panimotaito" +icon = "❦" +description = "Tuplakupla, triplakupla, nelinkertainen kupla - en silti saa tätä taikajuomaa kattilaan" [skill.blocking] - name = "Torjunta" - icon = "🛡" - description = "Kepit ja kivet eivät murskaa luitasi, mutta kilpi kyllä." +name = "Torjunta" +icon = "🛡" +description = "Kepit ja kivet eivät murskaa luitasi, mutta kilpi kyllä." [skill.crafting] - name = "Valmistus" - icon = "⌂" - description = "Kun palasia ei ole enää jäljellä, miksi et tekisi lisää?" +name = "Valmistus" +icon = "⌂" +description = "Kun palasia ei ole enää jäljellä, miksi et tekisi lisää?" [skill.discovery] - name = "Löytöretki" - icon = "⚛" - description = "Kun havaintokykysi laajenee, mielesi avautuu löytämään sen, mitä et ennen huomannut." +name = "Löytöretki" +icon = "⚛" +description = "Kun havaintokykysi laajenee, mielesi avautuu löytämään sen, mitä et ennen huomannut." [skill.enchanting] - name = "Lumoaminen" - icon = "♰" - description = "Mistä sinä oikein puhut? Ennustukset, näyt, taikauskoinen höpötys?" +name = "Lumoaminen" +icon = "♰" +description = "Mistä sinä oikein puhut? Ennustukset, näyt, taikauskoinen höpötys?" [skill.excavation] - name = "Kaivaminen" - icon = "ᛳ" - description = "Kaiva kaiva koloa..." +name = "Kaivaminen" +icon = "ᛳ" +description = "Kaiva kaiva koloa..." [skill.herbalism] - name = "Yrttitaito" - icon = "⚘" - description = "En löydä kasveja, mutta löydän siemeniä ja - onko tuo... rikkaruohoa?" +name = "Yrttitaito" +icon = "⚘" +description = "En löydä kasveja, mutta löydän siemeniä ja - onko tuo... rikkaruohoa?" [skill.hunter] - name = "Metsästäjä" - icon = "☠" - description = "Metsästyksessä on kyse matkasta, ei lopputuloksesta." +name = "Metsästäjä" +icon = "☠" +description = "Metsästyksessä on kyse matkasta, ei lopputuloksesta." [skill.nether] - name = "Nether" - icon = "₪" - description = "Netherin syvyyksistä itsestään." +name = "Nether" +icon = "₪" +description = "Netherin syvyyksistä itsestään." [skill.pickaxe] - name = "Hakku" - icon = "⛏" - description = "Kääpiöt ovat kaivosmiehiä, mutta olen oppinut pari juttua aikanani. OLEN RUOTSALAINEN" +name = "Hakku" +icon = "⛏" +description = "Kääpiöt ovat kaivosmiehiä, mutta olen oppinut pari juttua aikanani. OLEN RUOTSALAINEN" [skill.ranged] - name = "Kantama" - icon = "🏹" - description = "Etäisyys on avain voittoon ja avain selviytymiseen." +name = "Kantama" +icon = "🏹" +description = "Etäisyys on avain voittoon ja avain selviytymiseen." [skill.rift] - name = "Repeämä" - icon = "❍" - description = "Repeämä on syövyttävä valjastus, mutta sinä olet valjastanut valjastuksen." +name = "Repeämä" +icon = "❍" +description = "Repeämä on syövyttävä valjastus, mutta sinä olet valjastanut valjastuksen." [skill.seaborne] - name = "Merenkulkija" - icon = "🎣" - description = "Tällä taidolla voit hallita veden ihmeitä." +name = "Merenkulkija" +icon = "🎣" +description = "Tällä taidolla voit hallita veden ihmeitä." [skill.stealth] - name = "Hiipimistaito" - icon = "☯" - description = "Näkymättömyyden taide. Kulje varjoissa." +name = "Hiipimistaito" +icon = "☯" +description = "Näkymättömyyden taide. Kulje varjoissa." [skill.swords] - name = "Miekat" - icon = "⚔" - description = "Harmaakiven voimalla!" +name = "Miekat" +icon = "⚔" +description = "Harmaakiven voimalla!" [skill.taming] - name = "Kesyttäminen" - icon = "♥" - description = "Papukaijat ja mehiläiset... entä sinä?" +name = "Kesyttäminen" +icon = "♥" +description = "Papukaijat ja mehiläiset... entä sinä?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Veri virtaa maailmankaikkeuden suonissa. Käsiesi puristuksessa." +name = "TragOul" +icon = "🗡" +description = "Veri virtaa maailmankaikkeuden suonissa. Käsiesi puristuksessa." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Vedä maailmankaikkeuden kelloa, koe virtaus. Riko kello, tule kelloksi." +name = "Chronos" +icon = "🕒" +description = "Vedä maailmankaikkeuden kelloa, koe virtaus. Riko kello, tule kelloksi." [skill.unarmed] - name = "Aseeton" - icon = "»" - description = "Ilman asetta ei tarkoita ilman voimaa." +name = "Aseeton" +icon = "»" +description = "Ilman asetta ei tarkoita ilman voimaa." # agility [agility] [agility.armor_up] - name = "Panssarointi" - description = "Saat enemmän panssaria mitä pidempään juokset!" - lore1 = "Maksimipanssari" - lore2 = "Panssaroinnin kertymisaika" - lore = ["Maksimipanssari", "Panssaroinnin kertymisaika"] +name = "Panssarointi" +description = "Saat enemmän panssaria mitä pidempään juokset!" +lore1 = "Maksimipanssari" +lore2 = "Panssaroinnin kertymisaika" +lore = ["Maksimipanssari", "Panssaroinnin kertymisaika"] [agility.ladder_slide] - name = "Tikasliuku" - description = "Kiipeä ja liu'u tikkailla paljon nopeammin molempiin suuntiin." - lore1 = "Tikasnopeuskerroin" - lore2 = "Nopea laskeutumisnopeus" - lore = ["Tikasnopeuskerroin", "Nopea laskeutumisnopeus"] +name = "Tikasliuku" +description = "Kiipeä ja liu'u tikkailla paljon nopeammin molempiin suuntiin." +lore1 = "Tikasnopeuskerroin" +lore2 = "Nopea laskeutumisnopeus" +lore = ["Tikasnopeuskerroin", "Nopea laskeutumisnopeus"] [agility.super_jump] - name = "Superhyppy" - description = "Poikkeuksellinen korkeusetu." - lore1 = "Maksimihyppykorkeus" - lore2 = "Kyykky + Hyppy tehdäksesi Superhypyn!" - lore = ["Maksimihyppykorkeus", "Kyykky + Hyppy tehdäksesi Superhypyn!"] +name = "Superhyppy" +description = "Poikkeuksellinen korkeusetu." +lore1 = "Maksimihyppykorkeus" +lore2 = "Kyykky + Hyppy tehdäksesi Superhypyn!" +lore = ["Maksimihyppykorkeus", "Kyykky + Hyppy tehdäksesi Superhypyn!"] [agility.wall_jump] - name = "Seinähyppy" - description = "Pidä kyykkyä painettuna ilmassa seinää vasten tarttuaksesi ja hypätäksesi!" - lore1 = "Maksimihypyt" - lore2 = "Hyppykorkeus" - lore = ["Maksimihypyt", "Hyppykorkeus"] +name = "Seinähyppy" +description = "Pidä kyykkyä painettuna ilmassa seinää vasten tarttuaksesi ja hypätäksesi!" +lore1 = "Maksimihypyt" +lore2 = "Hyppykorkeus" +lore = ["Maksimihypyt", "Hyppykorkeus"] [agility.wind_up] - name = "Kiihdytys" - description = "Nopeudu mitä pidempään juokset!" - lore1 = "Maksiminopeus" - lore2 = "Kiihdytysaika" - lore = ["Maksiminopeus", "Kiihdytysaika"] +name = "Kiihdytys" +description = "Nopeudu mitä pidempään juokset!" +lore1 = "Maksiminopeus" +lore2 = "Kiihdytysaika" +lore = ["Maksiminopeus", "Kiihdytysaika"] # architect [architect] [architect.elevator] - name = "Hissi" - description = "Tämä mahdollistaa hissin rakentamisen pystysuuntaiseen nopeaan teleporttaukseen!" - lore1 = "Avaa hissiresepti: X=VILLA, Y=ENDERHELMI" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Avaa hissiresepti: X=VILLA, Y=ENDERHELMI", "XXX", "XYX", "XXX"] +name = "Hissi" +description = "Tämä mahdollistaa hissin rakentamisen pystysuuntaiseen nopeaan teleporttaukseen!" +lore1 = "Avaa hissiresepti: X=VILLA, Y=ENDERHELMI" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Avaa hissiresepti: X=VILLA, Y=ENDERHELMI", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Maaginen perusta" - description = "Tämä mahdollistaa kyykistymisen ja väliaikaisen perustan luomisen allesi!" - lore1 = "Luo maagisesti: " - lore2 = "lohkoa allesi!" - lore = ["Luo maagisesti: ", "lohkoa allesi!"] +name = "Maaginen perusta" +description = "Tämä mahdollistaa kyykistymisen ja väliaikaisen perustan luomisen allesi!" +lore1 = "Luo maagisesti: " +lore2 = "lohkoa allesi!" +lore = ["Luo maagisesti: ", "lohkoa allesi!"] [architect.glass] - name = "Silkkikosketus-lasi" - description = "Tämä estää lasilohkojen häviämisen, kun rikot ne tyhjällä kädellä!" - lore1 = "Kätesi saavat silkkikosketuksen lasille" - lore = ["Kätesi saavat silkkikosketuksen lasille"] +name = "Silkkikosketus-lasi" +description = "Tämä estää lasilohkojen häviämisen, kun rikot ne tyhjällä kädellä!" +lore1 = "Kätesi saavat silkkikosketuksen lasille" +lore = ["Kätesi saavat silkkikosketuksen lasille"] [architect.wireless_redstone] - name = "Redstonekaukosäädin" - description = "Tämä mahdollistaa redstone-soihdun käytön redstonen kytkemiseen etänä!" - lore1 = "Kohde + Redstone-soihtu + Enderhelmi = 1 Redstonekaukosäädin" - lore = ["Kohde + Redstone-soihtu + Enderhelmi = 1 Redstonekaukosäädin"] +name = "Redstonekaukosäädin" +description = "Tämä mahdollistaa redstone-soihdun käytön redstonen kytkemiseen etänä!" +lore1 = "Kohde + Redstone-soihtu + Enderhelmi = 1 Redstonekaukosäädin" +lore = ["Kohde + Redstone-soihtu + Enderhelmi = 1 Redstonekaukosäädin"] [architect.placement] - name = "Rakentajan sauva" - description = "Mahdollistaa useiden lohkojen asettamisen kerralla! Aktivoi kyykistymällä ja pidä kädessäsi lohkoa, joka vastaa katsomasi lohkoa, ja aseta! Muista, saatat joutua liikkumaan hieman rajauksen käynnistämiseksi!" - lore1 = "Tarvitset" - lore2 = "lohkoa kädessäsi tämän asettamiseen" - lore3 = "Materiaalinen rakentajan sauva" - lore = ["Tarvitset", "lohkoa kädessäsi tämän asettamiseen", "Materiaalinen rakentajan sauva"] +name = "Rakentajan sauva" +description = "Mahdollistaa useiden lohkojen asettamisen kerralla! Aktivoi kyykistymällä ja pidä kädessäsi lohkoa, joka vastaa katsomasi lohkoa, ja aseta! Muista, saatat joutua liikkumaan hieman rajauksen käynnistämiseksi!" +lore1 = "Tarvitset" +lore2 = "lohkoa kädessäsi tämän asettamiseen" +lore3 = "Materiaalinen rakentajan sauva" +lore = ["Tarvitset", "lohkoa kädessäsi tämän asettamiseen", "Materiaalinen rakentajan sauva"] # axe [axe] [axe.chop] - name = "Kirveenhakkuu" - description = "Kaada puita oikea-klikkaamalla alinta tukkia!" - lore1 = "Lohkoja per hakkuu" - lore2 = "Hakkuun jäähtymisaika" - lore3 = "Työkalun kuluminen" - lore = ["Lohkoja per hakkuu", "Hakkuun jäähtymisaika", "Työkalun kuluminen"] +name = "Kirveenhakkuu" +description = "Kaada puita oikea-klikkaamalla alinta tukkia!" +lore1 = "Lohkoja per hakkuu" +lore2 = "Hakkuun jäähtymisaika" +lore3 = "Työkalun kuluminen" +lore = ["Lohkoja per hakkuu", "Hakkuun jäähtymisaika", "Työkalun kuluminen"] [axe.log_swap] - name = "Lucyn tukkinvaihtaja" - description = "Vaihda tukkien lajia valmistuspöydässä!" - lore1 = "8 tukkia mitä tahansa + 1 taimi = 8 tukkia taimen lajia" - lore = ["8 tukkia mitä tahansa + 1 taimi = 8 tukkia taimen lajia"] +name = "Lucyn tukkinvaihtaja" +description = "Vaihda tukkien lajia valmistuspöydässä!" +lore1 = "8 tukkia mitä tahansa + 1 taimi = 8 tukkia taimen lajia" +lore = ["8 tukkia mitä tahansa + 1 taimi = 8 tukkia taimen lajia"] [axe.drop_to_inventory] - name = "Kirves: pudota varastoon" +name = "Kirves: pudota varastoon" [axe.ground_smash] - name = "Kirveenmurskaus" - description = "Hyppää, kyykisty ja murskaa kaikki läheiset viholliset." - lore1 = "Vahinko" - lore2 = "Lohkon säde" - lore3 = "Voima" - lore4 = "Murskauksen jäähtymisaika" - lore = ["Vahinko", "Lohkon säde", "Voima", "Murskauksen jäähtymisaika"] +name = "Kirveenmurskaus" +description = "Hyppää, kyykisty ja murskaa kaikki läheiset viholliset." +lore1 = "Vahinko" +lore2 = "Lohkon säde" +lore3 = "Voima" +lore4 = "Murskauksen jäähtymisaika" +lore = ["Vahinko", "Lohkon säde", "Voima", "Murskauksen jäähtymisaika"] [axe.leaf_miner] - name = "Lehtilouhija" - description = "Mahdollistaa useiden lehtien rikkomisen kerralla!" - lore1 = "Kyykisty ja louhi LEHTIÄ" - lore2 = "lehtilouhinnan kantama" - lore3 = "Et saa pudotuksia lehdistä (huijauksenesto)" - lore = ["Kyykisty ja louhi LEHTIÄ", "lehtilouhinnan kantama", "Et saa pudotuksia lehdistä (huijauksenesto)"] +name = "Lehtilouhija" +description = "Mahdollistaa useiden lehtien rikkomisen kerralla!" +lore1 = "Kyykisty ja louhi LEHTIÄ" +lore2 = "lehtilouhinnan kantama" +lore3 = "Et saa pudotuksia lehdistä (huijauksenesto)" +lore = ["Kyykisty ja louhi LEHTIÄ", "lehtilouhinnan kantama", "Et saa pudotuksia lehdistä (huijauksenesto)"] [axe.wood_miner] - name = "Puulouhija" - description = "Mahdollistaa useiden puulohkojen rikkomisen kerralla!" - lore1 = "Kyykisty ja louhi PUITA/TUKKEJA (ei lankkuja)" - lore2 = "puulouhinnan kantama" - lore3 = "Toimii pudota varastoon -toiminnon kanssa" - lore = ["Kyykisty ja louhi PUITA/TUKKEJA (ei lankkuja)", "puulouhinnan kantama", "Toimii pudota varastoon -toiminnon kanssa"] +name = "Puulouhija" +description = "Mahdollistaa useiden puulohkojen rikkomisen kerralla!" +lore1 = "Kyykisty ja louhi PUITA/TUKKEJA (ei lankkuja)" +lore2 = "puulouhinnan kantama" +lore3 = "Toimii pudota varastoon -toiminnon kanssa" +lore = ["Kyykisty ja louhi PUITA/TUKKEJA (ei lankkuja)", "puulouhinnan kantama", "Toimii pudota varastoon -toiminnon kanssa"] # brewing [brewing] [brewing.lingering] - name = "Viipyvä panimo" - description = "Pannut taikajuomat kestävät pidempään!" - lore1 = "Kesto" - lore2 = "Kesto" - lore = ["Kesto", "Kesto"] +name = "Viipyvä panimo" +description = "Pannut taikajuomat kestävät pidempään!" +lore1 = "Kesto" +lore2 = "Kesto" +lore = ["Kesto", "Kesto"] [brewing.super_heated] - name = "Ylikuumennettu panimo" - description = "Panimojalat toimivat sitä nopeammin mitä kuumempia ne ovat." - lore1 = "Koskettavaa tulilohkoa kohden" - lore2 = "Koskettavaa laavalohkoa kohden" - lore = ["Koskettavaa tulilohkoa kohden", "Koskettavaa laavalohkoa kohden"] +name = "Ylikuumennettu panimo" +description = "Panimojalat toimivat sitä nopeammin mitä kuumempia ne ovat." +lore1 = "Koskettavaa tulilohkoa kohden" +lore2 = "Koskettavaa laavalohkoa kohden" +lore = ["Koskettavaa tulilohkoa kohden", "Koskettavaa laavalohkoa kohden"] [brewing.darkness] - name = "Pullotettu pimeys" - description = "Miksi tarvitset tätä, mutta ole hyvä!" - lore1 = "Yönäkötaikajuoma + musta betoni = Pimeyden taikajuoma (30 sekuntia)" - lore2 = "Huomaa, että tämä estää käyttäjää juoksemasta!" - lore = ["Yönäkötaikajuoma + musta betoni = Pimeyden taikajuoma (30 sekuntia)", "Huomaa, että tämä estää käyttäjää juoksemasta!"] +name = "Pullotettu pimeys" +description = "Miksi tarvitset tätä, mutta ole hyvä!" +lore1 = "Yönäkötaikajuoma + musta betoni = Pimeyden taikajuoma (30 sekuntia)" +lore2 = "Huomaa, että tämä estää käyttäjää juoksemasta!" +lore = ["Yönäkötaikajuoma + musta betoni = Pimeyden taikajuoma (30 sekuntia)", "Huomaa, että tämä estää käyttäjää juoksemasta!"] [brewing.haste] - name = "Pullotettu kiire" - description = "Kun tehokkuus ei riitä" - lore1 = "Nopeustaikajuoma + ametistinsiru = Kiireen taikajuoma (60 sekuntia)" - lore2 = "Nopeustaikajuoma + ametistilohko = Kiireen taikajuoma-2 (30 sekuntia)" - lore = ["Nopeustaikajuoma + ametistinsiru = Kiireen taikajuoma (60 sekuntia)", "Nopeustaikajuoma + ametistilohko = Kiireen taikajuoma-2 (30 sekuntia)"] +name = "Pullotettu kiire" +description = "Kun tehokkuus ei riitä" +lore1 = "Nopeustaikajuoma + ametistinsiru = Kiireen taikajuoma (60 sekuntia)" +lore2 = "Nopeustaikajuoma + ametistilohko = Kiireen taikajuoma-2 (30 sekuntia)" +lore = ["Nopeustaikajuoma + ametistinsiru = Kiireen taikajuoma (60 sekuntia)", "Nopeustaikajuoma + ametistilohko = Kiireen taikajuoma-2 (30 sekuntia)"] [brewing.absorption] - name = "Pullotettu imeytyminen" - description = "Koveta keho!" - lore1 = "Pikaparannus + kvartsi = Imeytymisen taikajuoma (60 sekuntia)" - lore2 = "Pikaparannus + kvartsilohko = Imeytymisen taikajuoma-2 (30 sekuntia)" - lore = ["Pikaparannus + kvartsi = Imeytymisen taikajuoma (60 sekuntia)", "Pikaparannus + kvartsilohko = Imeytymisen taikajuoma-2 (30 sekuntia)"] +name = "Pullotettu imeytyminen" +description = "Koveta keho!" +lore1 = "Pikaparannus + kvartsi = Imeytymisen taikajuoma (60 sekuntia)" +lore2 = "Pikaparannus + kvartsilohko = Imeytymisen taikajuoma-2 (30 sekuntia)" +lore = ["Pikaparannus + kvartsi = Imeytymisen taikajuoma (60 sekuntia)", "Pikaparannus + kvartsilohko = Imeytymisen taikajuoma-2 (30 sekuntia)"] [brewing.fatigue] - name = "Pullotettu väsymys" - description = "Heikennä kehoa!" - lore1 = "Heikkoustaikajuoma + limapallo = Väsymyksen taikajuoma (30 sekuntia)" - lore2 = "Heikkoustaikajuoma + limalohko = Väsymyksen taikajuoma-2 (15 sekuntia)" - lore = ["Heikkoustaikajuoma + limapallo = Väsymyksen taikajuoma (30 sekuntia)", "Heikkoustaikajuoma + limalohko = Väsymyksen taikajuoma-2 (15 sekuntia)"] +name = "Pullotettu väsymys" +description = "Heikennä kehoa!" +lore1 = "Heikkoustaikajuoma + limapallo = Väsymyksen taikajuoma (30 sekuntia)" +lore2 = "Heikkoustaikajuoma + limalohko = Väsymyksen taikajuoma-2 (15 sekuntia)" +lore = ["Heikkoustaikajuoma + limapallo = Väsymyksen taikajuoma (30 sekuntia)", "Heikkoustaikajuoma + limalohko = Väsymyksen taikajuoma-2 (15 sekuntia)"] [brewing.hunger] - name = "Pullotettu nälkä" - description = "Ruoki kyltymätöntä!" - lore1 = "Hankala taikajuoma + mätäliha = Nälän taikajuoma (30 sekuntia)" - lore2 = "Heikkoustaikajuoma + mätäliha = Nälän taikajuoma-3 (15 sekuntia)" - lore = ["Hankala taikajuoma + mätäliha = Nälän taikajuoma (30 sekuntia)", "Heikkoustaikajuoma + mätäliha = Nälän taikajuoma-3 (15 sekuntia)"] +name = "Pullotettu nälkä" +description = "Ruoki kyltymätöntä!" +lore1 = "Hankala taikajuoma + mätäliha = Nälän taikajuoma (30 sekuntia)" +lore2 = "Heikkoustaikajuoma + mätäliha = Nälän taikajuoma-3 (15 sekuntia)" +lore = ["Hankala taikajuoma + mätäliha = Nälän taikajuoma (30 sekuntia)", "Heikkoustaikajuoma + mätäliha = Nälän taikajuoma-3 (15 sekuntia)"] [brewing.nausea] - name = "Pullotettu pahoinvointi" - description = "Teet minut kipeäksi!" - lore1 = "Hankala taikajuoma + ruskea sieni = Pahoinvoinnin taikajuoma (16 sekuntia)" - lore2 = "Hankala taikajuoma + karmiinisieni = Pahoinvoinnin taikajuoma-2 (8 sekuntia)" - lore = ["Hankala taikajuoma + ruskea sieni = Pahoinvoinnin taikajuoma (16 sekuntia)", "Hankala taikajuoma + karmiinisieni = Pahoinvoinnin taikajuoma-2 (8 sekuntia)"] +name = "Pullotettu pahoinvointi" +description = "Teet minut kipeäksi!" +lore1 = "Hankala taikajuoma + ruskea sieni = Pahoinvoinnin taikajuoma (16 sekuntia)" +lore2 = "Hankala taikajuoma + karmiinisieni = Pahoinvoinnin taikajuoma-2 (8 sekuntia)" +lore = ["Hankala taikajuoma + ruskea sieni = Pahoinvoinnin taikajuoma (16 sekuntia)", "Hankala taikajuoma + karmiinisieni = Pahoinvoinnin taikajuoma-2 (8 sekuntia)"] [brewing.blindness] - name = "Pullotettu sokeus" - description = "Olet kauhea ihminen..." - lore1 = "Hankala taikajuoma + mustekassipussi = Sokeuden taikajuoma (30 sekuntia)" - lore2 = "Hankala taikajuoma + hehkuva mustekassipussi = Sokeuden taikajuoma-2 (15 sekuntia)" - lore = ["Hankala taikajuoma + mustekassipussi = Sokeuden taikajuoma (30 sekuntia)", "Hankala taikajuoma + hehkuva mustekassipussi = Sokeuden taikajuoma-2 (15 sekuntia)"] +name = "Pullotettu sokeus" +description = "Olet kauhea ihminen..." +lore1 = "Hankala taikajuoma + mustekassipussi = Sokeuden taikajuoma (30 sekuntia)" +lore2 = "Hankala taikajuoma + hehkuva mustekassipussi = Sokeuden taikajuoma-2 (15 sekuntia)" +lore = ["Hankala taikajuoma + mustekassipussi = Sokeuden taikajuoma (30 sekuntia)", "Hankala taikajuoma + hehkuva mustekassipussi = Sokeuden taikajuoma-2 (15 sekuntia)"] [brewing.resistance] - name = "Pullotettu vastustuskyky" - description = "Linnoitusta parhaimmillaan!" - lore1 = "Hankala taikajuoma + rautaharkko = Vastustuksen taikajuoma (60 sekuntia)" - lore2 = "Hankala taikajuoma + rautalohko = Vastustuksen taikajuoma-2 (30 sekuntia)" - lore = ["Hankala taikajuoma + rautaharkko = Vastustuksen taikajuoma (60 sekuntia)", "Hankala taikajuoma + rautalohko = Vastustuksen taikajuoma-2 (30 sekuntia)"] +name = "Pullotettu vastustuskyky" +description = "Linnoitusta parhaimmillaan!" +lore1 = "Hankala taikajuoma + rautaharkko = Vastustuksen taikajuoma (60 sekuntia)" +lore2 = "Hankala taikajuoma + rautalohko = Vastustuksen taikajuoma-2 (30 sekuntia)" +lore = ["Hankala taikajuoma + rautaharkko = Vastustuksen taikajuoma (60 sekuntia)", "Hankala taikajuoma + rautalohko = Vastustuksen taikajuoma-2 (30 sekuntia)"] [brewing.health_boost] - name = "Pullotettu elämä" - description = "Kun maksimiterveys ei riitä..." - lore1 = "Pikaparannus-taikajuoma + kultaomena = Terveyslisän taikajuoma (120 sekuntia)" - lore2 = "Pikaparannus-taikajuoma + lumottu kultaomena = Terveyslisän taikajuoma-2 (120 sekuntia)" - lore = ["Pikaparannus-taikajuoma + kultaomena = Terveyslisän taikajuoma (120 sekuntia)", "Pikaparannus-taikajuoma + lumottu kultaomena = Terveyslisän taikajuoma-2 (120 sekuntia)"] +name = "Pullotettu elämä" +description = "Kun maksimiterveys ei riitä..." +lore1 = "Pikaparannus-taikajuoma + kultaomena = Terveyslisän taikajuoma (120 sekuntia)" +lore2 = "Pikaparannus-taikajuoma + lumottu kultaomena = Terveyslisän taikajuoma-2 (120 sekuntia)" +lore = ["Pikaparannus-taikajuoma + kultaomena = Terveyslisän taikajuoma (120 sekuntia)", "Pikaparannus-taikajuoma + lumottu kultaomena = Terveyslisän taikajuoma-2 (120 sekuntia)"] [brewing.decay] - name = "Pullotettu rappeutuminen" - description = "Kuka tiesi, että jäte olisi niin hyödyllistä?" - lore1 = "Heikkoustaikajuoma + myrkkyperuna = Witherin taikajuoma (16 sekuntia)" - lore2 = "Heikkoustaikajuoma + karmiinijuuret = Witherin taikajuoma-2 (8 sekuntia)" - lore = ["Heikkoustaikajuoma + myrkkyperuna = Witherin taikajuoma (16 sekuntia)", "Heikkoustaikajuoma + karmiinijuuret = Witherin taikajuoma-2 (8 sekuntia)"] +name = "Pullotettu rappeutuminen" +description = "Kuka tiesi, että jäte olisi niin hyödyllistä?" +lore1 = "Heikkoustaikajuoma + myrkkyperuna = Witherin taikajuoma (16 sekuntia)" +lore2 = "Heikkoustaikajuoma + karmiinijuuret = Witherin taikajuoma-2 (8 sekuntia)" +lore = ["Heikkoustaikajuoma + myrkkyperuna = Witherin taikajuoma (16 sekuntia)", "Heikkoustaikajuoma + karmiinijuuret = Witherin taikajuoma-2 (8 sekuntia)"] [brewing.saturation] - name = "Pullotettu kylläisyys" - description = "Tiedätkö mitä... Minulla ei ole edes nälkä..." - lore1 = "Uudistumistaikajuoma + uuniperuna = Kylläisyyden taikajuoma" - lore2 = "Uudistumistaikajuoma + heinäpaali = Kylläisyyden taikajuoma-2" - lore = ["Uudistumistaikajuoma + uuniperuna = Kylläisyyden taikajuoma", "Uudistumistaikajuoma + heinäpaali = Kylläisyyden taikajuoma-2"] +name = "Pullotettu kylläisyys" +description = "Tiedätkö mitä... Minulla ei ole edes nälkä..." +lore1 = "Uudistumistaikajuoma + uuniperuna = Kylläisyyden taikajuoma" +lore2 = "Uudistumistaikajuoma + heinäpaali = Kylläisyyden taikajuoma-2" +lore = ["Uudistumistaikajuoma + uuniperuna = Kylläisyyden taikajuoma", "Uudistumistaikajuoma + heinäpaali = Kylläisyyden taikajuoma-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Mefistofeleen ketjut" - description = "Mahdollistaa rengaspanssarin valmistamisen" - lore1 = "Valmistusresepti on sama kuin muillakin, mutta rautahipuilla" - lore = ["Valmistusresepti on sama kuin muillakin, mutta rautahipuilla"] +name = "Mefistofeleen ketjut" +description = "Mahdollistaa rengaspanssarin valmistamisen" +lore1 = "Valmistusresepti on sama kuin muillakin, mutta rautahipuilla" +lore = ["Valmistusresepti on sama kuin muillakin, mutta rautahipuilla"] [blocking.horse_armorer] - name = "Valmistettava hevosenhaarniska" - description = "Mahdollistaa hevosenhaarniskan valmistamisen" - lore1 = "Ympäröi satula haluamallasi materiaalilla haarniskan valmistamiseen" - lore = ["Ympäröi satula haluamallasi materiaalilla haarniskan valmistamiseen"] +name = "Valmistettava hevosenhaarniska" +description = "Mahdollistaa hevosenhaarniskan valmistamisen" +lore1 = "Ympäröi satula haluamallasi materiaalilla haarniskan valmistamiseen" +lore = ["Ympäröi satula haluamallasi materiaalilla haarniskan valmistamiseen"] [blocking.saddle_crafter] - name = "Valmistettava satula" - description = "Valmista satula nahasta" - lore1 = "Resepti: 5 nahkaa:" - lore = ["Resepti: 5 nahkaa:"] +name = "Valmistettava satula" +description = "Valmista satula nahasta" +lore1 = "Resepti: 5 nahkaa:" +lore = ["Resepti: 5 nahkaa:"] [blocking.multi_armor] - name = "Monipanssari" - description = "Sido Elytrat panssariin" - lore1 = "Tämä on uskomaton taito matkustamiseen." - lore2 = "Yhdistä ja vaihda panssaria/Elytraa dynaamisesti lennossa!" - lore3 = "Yhdistääksesi, shift-klikkaa esine toisen päälle varastossasi." - lore4 = "Irrottaaksesi panssarin, kyykky-pudota esine niin se puretaan." - lore5 = "Jos monipanssarisi tuhoutuu, menetät kaikki siinä olevat esineet." - lore6 = "En tarvitse panssaria, se on pettymys..." - lore = ["Tämä on uskomaton taito matkustamiseen.", "Yhdistä ja vaihda panssaria/Elytraa dynaamisesti lennossa!", "Yhdistääksesi, shift-klikkaa esine toisen päälle varastossasi.", "Irrottaaksesi panssarin, kyykky-pudota esine niin se puretaan.", "Jos monipanssarisi tuhoutuu, menetät kaikki siinä olevat esineet.", "En tarvitse panssaria, se on pettymys..."] +name = "Monipanssari" +description = "Sido Elytrat panssariin" +lore1 = "Tämä on uskomaton taito matkustamiseen." +lore2 = "Yhdistä ja vaihda panssaria/Elytraa dynaamisesti lennossa!" +lore3 = "Yhdistääksesi, shift-klikkaa esine toisen päälle varastossasi." +lore4 = "Irrottaaksesi panssarin, kyykky-pudota esine niin se puretaan." +lore5 = "Jos monipanssarisi tuhoutuu, menetät kaikki siinä olevat esineet." +lore6 = "En tarvitse panssaria, se on pettymys..." +lore = ["Tämä on uskomaton taito matkustamiseen.", "Yhdistä ja vaihda panssaria/Elytraa dynaamisesti lennossa!", "Yhdistääksesi, shift-klikkaa esine toisen päälle varastossasi.", "Irrottaaksesi panssarin, kyykky-pudota esine niin se puretaan.", "Jos monipanssarisi tuhoutuu, menetät kaikki siinä olevat esineet.", "En tarvitse panssaria, se on pettymys..."] # crafting [crafting] [crafting.deconstruction] - name = "Purkaminen" - description = "Pura lohkot ja esineet pelastettaviksi perusosiksi!" - lore1 = "Pudota mikä tahansa esine maahan." - lore2 = "Sitten kyykisty ja oikea-klikkaa saksilla" - lore = ["Pudota mikä tahansa esine maahan.", "Sitten kyykisty ja oikea-klikkaa saksilla"] +name = "Purkaminen" +description = "Pura lohkot ja esineet pelastettaviksi perusosiksi!" +lore1 = "Pudota mikä tahansa esine maahan." +lore2 = "Sitten kyykisty ja oikea-klikkaa saksilla" +lore = ["Pudota mikä tahansa esine maahan.", "Sitten kyykisty ja oikea-klikkaa saksilla"] [crafting.xp] - name = "Valmistus-XP" - description = "Saat passiivista XP:tä valmistaessasi" - lore1 = "Saat XP:tä valmistaessasi" - lore = ["Saat XP:tä valmistaessasi"] +name = "Valmistus-XP" +description = "Saat passiivista XP:tä valmistaessasi" +lore1 = "Saat XP:tä valmistaessasi" +lore = ["Saat XP:tä valmistaessasi"] [crafting.reconstruction] - name = "Malmin jälleenrakennus" - description = "Valmista malmeja uudelleen niiden perusosista!" - lore1 = "8 pudotusta ja 1 isäntä = 1 malmi (muodoton)" - lore2 = "Pudotukset on sulatettava (jos sovellettavissa)" - lore3 = "Ei sisällä: romua, kvartsia, smaragdeja jne..." - lore4 = "Isäntä = kotelo, esim.: kivi, netherrack, syvälupaus" - lore = ["8 pudotusta ja 1 isäntä = 1 malmi (muodoton)", "Pudotukset on sulatettava (jos sovellettavissa)", "Ei sisällä: romua, kvartsia, smaragdeja jne...", "Isäntä = kotelo, esim.: kivi, netherrack, syvälupaus"] +name = "Malmin jälleenrakennus" +description = "Valmista malmeja uudelleen niiden perusosista!" +lore1 = "8 pudotusta ja 1 isäntä = 1 malmi (muodoton)" +lore2 = "Pudotukset on sulatettava (jos sovellettavissa)" +lore3 = "Ei sisällä: romua, kvartsia, smaragdeja jne..." +lore4 = "Isäntä = kotelo, esim.: kivi, netherrack, syvälupaus" +lore = ["8 pudotusta ja 1 isäntä = 1 malmi (muodoton)", "Pudotukset on sulatettava (jos sovellettavissa)", "Ei sisällä: romua, kvartsia, smaragdeja jne...", "Isäntä = kotelo, esim.: kivi, netherrack, syvälupaus"] [crafting.leather] - name = "Valmistettava nahka" - description = "Valmista nahkaa mätälihasta" - lore1 = "Heitä se (mätäliha) nuotiolle!" - lore = ["Heitä se (mätäliha) nuotiolle!"] +name = "Valmistettava nahka" +description = "Valmista nahkaa mätälihasta" +lore1 = "Heitä se (mätäliha) nuotiolle!" +lore = ["Heitä se (mätäliha) nuotiolle!"] [crafting.backpacks] - name = "Boutilierin reput!" - description = "Tämä tuo Mojangin nipun peliin!" - lore1 = "Sinun on oltava selviytymistilassa käyttääksesi tätä" - lore2 = "XLX : Nahka, talutushihna, nahka" - lore3 = "XSX : Nahka, tynnyri, nahka" - lore4 = "XCX : Nahka, arkku, nahka" - lore = ["Sinun on oltava selviytymistilassa käyttääksesi tätä", "XLX : Nahka, talutushihna, nahka", "XSX : Nahka, tynnyri, nahka", "XCX : Nahka, arkku, nahka"] +name = "Boutilierin reput!" +description = "Tämä tuo Mojangin nipun peliin!" +lore1 = "Sinun on oltava selviytymistilassa käyttääksesi tätä" +lore2 = "XLX : Nahka, talutushihna, nahka" +lore3 = "XSX : Nahka, tynnyri, nahka" +lore4 = "XCX : Nahka, arkku, nahka" +lore = ["Sinun on oltava selviytymistilassa käyttääksesi tätä", "XLX : Nahka, talutushihna, nahka", "XSX : Nahka, tynnyri, nahka", "XCX : Nahka, arkku, nahka"] [crafting.stations] - name = "Kannettavat pöydät!" - description = "Käytä pöytää kämmenelläsi!" - lore2 = "KAIKKI ESINEET, JOTKA UNOHDAT PÖYTÄÄN SEN SULKEUTUESSA, KATOAVAT IKUISESTI!" - lore3 = "Kelvolliset pöydät: alasin, valmistuspöytä, hiomakivi, kartanpiirtäjänpöytä, kivenleikkuri, kangaspuut" - lore = ["KAIKKI ESINEET, JOTKA UNOHDAT PÖYTÄÄN SEN SULKEUTUESSA, KATOAVAT IKUISESTI!", "Kelvolliset pöydät: alasin, valmistuspöytä, hiomakivi, kartanpiirtäjänpöytä, kivenleikkuri, kangaspuut"] +name = "Kannettavat pöydät!" +description = "Käytä pöytää kämmenelläsi!" +lore2 = "KAIKKI ESINEET, JOTKA UNOHDAT PÖYTÄÄN SEN SULKEUTUESSA, KATOAVAT IKUISESTI!" +lore3 = "Kelvolliset pöydät: alasin, valmistuspöytä, hiomakivi, kartanpiirtäjänpöytä, kivenleikkuri, kangaspuut" +lore = ["KAIKKI ESINEET, JOTKA UNOHDAT PÖYTÄÄN SEN SULKEUTUESSA, KATOAVAT IKUISESTI!", "Kelvolliset pöydät: alasin, valmistuspöytä, hiomakivi, kartanpiirtäjänpöytä, kivenleikkuri, kangaspuut"] [crafting.skulls] - name = "Valmistettavat kallot!" - description = "Materiaalien avulla voit valmistaa hirviöiden kalloja!" - lore1 = "Ympäröi luulohko seuraavilla saadaksesi kallon:" - lore2 = "Zombi: mätäliha" - lore3 = "Luuranko: luu" - lore4 = "Creeper: ruuti" - lore5 = "Wither: nether-tiili" - lore6 = "Lohikäärme: lohikäärmeen henkäys" - lore = ["Ympäröi luulohko seuraavilla saadaksesi kallon:", "Zombi: mätäliha", "Luuranko: luu", "Creeper: ruuti", "Wither: nether-tiili", "Lohikäärme: lohikäärmeen henkäys"] +name = "Valmistettavat kallot!" +description = "Materiaalien avulla voit valmistaa hirviöiden kalloja!" +lore1 = "Ympäröi luulohko seuraavilla saadaksesi kallon:" +lore2 = "Zombi: mätäliha" +lore3 = "Luuranko: luu" +lore4 = "Creeper: ruuti" +lore5 = "Wither: nether-tiili" +lore6 = "Lohikäärme: lohikäärmeen henkäys" +lore = ["Ympäröi luulohko seuraavilla saadaksesi kallon:", "Zombi: mätäliha", "Luuranko: luu", "Creeper: ruuti", "Wither: nether-tiili", "Lohikäärme: lohikäärmeen henkäys"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Aikaa pullossa" - description = "Kanna ajallista pulloa, joka varastoi aikaa ja kuluta sitä nopeuttaaksesi ajastettuja lohkoja, kasvavia ja ikääntyviä olentoja kuten eläinten poikasia. Resepti (muodoton): nopeustaikajuoma + kello + lasipullo." - lore1 = "Varastoidut sekunnit ladattu per tikki" - lore2 = "Ajan kiihdytys per varastoitu sekunti" - lore3 = "Resepti (muodoton): nopeustaikajuoma + kello + lasipullo" - lore = ["Varastoidut sekunnit ladattu per tikki", "Ajan kiihdytys per varastoitu sekunti", "Resepti (muodoton): nopeustaikajuoma + kello + lasipullo"] +name = "Aikaa pullossa" +description = "Kanna ajallista pulloa, joka varastoi aikaa ja kuluta sitä nopeuttaaksesi ajastettuja lohkoja, kasvavia ja ikääntyviä olentoja kuten eläinten poikasia. Resepti (muodoton): nopeustaikajuoma + kello + lasipullo." +lore1 = "Varastoidut sekunnit ladattu per tikki" +lore2 = "Ajan kiihdytys per varastoitu sekunti" +lore3 = "Resepti (muodoton): nopeustaikajuoma + kello + lasipullo" +lore = ["Varastoidut sekunnit ladattu per tikki", "Ajan kiihdytys per varastoitu sekunti", "Resepti (muodoton): nopeustaikajuoma + kello + lasipullo"] [chronos.aberrant_touch] - name = "Poikkeava kosketus" - description = "Lähitaisteluiskut lisäävät pinoutuvaa hitautta nälän kustannuksella, tiukoilla PvP-rajoituksilla, ja juurruttavat kohteet 5 pinolla." - lore1 = "Lähitaisteluiskut lisäävät pinoutuvaa hitautta" - lore2 = "PvE-hitauden keston enimmäisraja" - lore3 = "PvP-hitauden vahvuuden enimmäisraja" - lore = ["Lähitaisteluiskut lisäävät pinoutuvaa hitautta", "PvE-hitauden keston enimmäisraja", "PvP-hitauden vahvuuden enimmäisraja"] +name = "Poikkeava kosketus" +description = "Lähitaisteluiskut lisäävät pinoutuvaa hitautta nälän kustannuksella, tiukoilla PvP-rajoituksilla, ja juurruttavat kohteet 5 pinolla." +lore1 = "Lähitaisteluiskut lisäävät pinoutuvaa hitautta" +lore2 = "PvE-hitauden keston enimmäisraja" +lore3 = "PvP-hitauden vahvuuden enimmäisraja" +lore = ["Lähitaisteluiskut lisäävät pinoutuvaa hitautta", "PvE-hitauden keston enimmäisraja", "PvP-hitauden vahvuuden enimmäisraja"] [chronos.instant_recall] - name = "Pikakelaus" - description = "Vasen tai oikea klikkaus kellolla kädessä kelataksesi takaisin äskettäiseen tilanteeseen terveys ja nälkä palautettuna." - lore1 = "Kelauksen kesto" - lore2 = "Jäähtymisaika" - lore3 = "Ei varaston palautusta" - lore = ["Kelauksen kesto", "Jäähtymisaika", "Ei varaston palautusta"] +name = "Pikakelaus" +description = "Vasen tai oikea klikkaus kellolla kädessä kelataksesi takaisin äskettäiseen tilanteeseen terveys ja nälkä palautettuna." +lore1 = "Kelauksen kesto" +lore2 = "Jäähtymisaika" +lore3 = "Ei varaston palautusta" +lore = ["Kelauksen kesto", "Jäähtymisaika", "Ei varaston palautusta"] [chronos.time_bomb] - name = "Aikapommi" - description = "Heitä valmistettu aikapommi, joka luo ajallisen kentän, hidastaa olentoja ja jäädyttää ammuksia." - lore1 = "Ajallisen kentän säde" - lore2 = "Ajallisen kentän kesto" - lore3 = "Pommin jäähtymisaika" - lore4 = "Resepti (muodoton): kello + lumipallo + timantti + hiekka" - lore = ["Ajallisen kentän säde", "Ajallisen kentän kesto", "Pommin jäähtymisaika", "Resepti (muodoton): kello + lumipallo + timantti + hiekka"] +name = "Aikapommi" +description = "Heitä valmistettu aikapommi, joka luo ajallisen kentän, hidastaa olentoja ja jäädyttää ammuksia." +lore1 = "Ajallisen kentän säde" +lore2 = "Ajallisen kentän kesto" +lore3 = "Pommin jäähtymisaika" +lore4 = "Resepti (muodoton): kello + lumipallo + timantti + hiekka" +lore = ["Ajallisen kentän säde", "Ajallisen kentän kesto", "Pommin jäähtymisaika", "Resepti (muodoton): kello + lumipallo + timantti + hiekka"] # discovery [discovery] [discovery.armor] - name = "Maailmanpanssari" - description = "Passiivinen panssari riippuen läheisten lohkojen kovuudesta." - lore1 = "Passiivinen panssari" - lore2 = "Perustuu läheisten lohkojen kovuuteen" - lore3 = "Panssarin vahvuus:" - lore = ["Passiivinen panssari", "Perustuu läheisten lohkojen kovuuteen", "Panssarin vahvuus:"] +name = "Maailmanpanssari" +description = "Passiivinen panssari riippuen läheisten lohkojen kovuudesta." +lore1 = "Passiivinen panssari" +lore2 = "Perustuu läheisten lohkojen kovuuteen" +lore3 = "Panssarin vahvuus:" +lore = ["Passiivinen panssari", "Perustuu läheisten lohkojen kovuuteen", "Panssarin vahvuus:"] [discovery.unity] - name = "Kokeellinen yhtenäisyys" - description = "Kokemuspallojen kerääminen lisää XP:tä satunnaisiin taitoihin." - lore1 = "XP " - lore2 = "per pallo" - lore = ["XP ", "per pallo"] +name = "Kokeellinen yhtenäisyys" +description = "Kokemuspallojen kerääminen lisää XP:tä satunnaisiin taitoihin." +lore1 = "XP " +lore2 = "per pallo" +lore = ["XP ", "per pallo"] [discovery.resist] - name = "Kokeellinen vastustuskyky" - description = "Kuluta kokemusta lievittääksesi vahinkoa vain kun isku pudottaisi sinut alle 5 sydämen tai tappaisi sinut." - lore0 = "Aktivoituu vain kriittisellä terveydellä (<= 5 sydäntä) kerran 15 sekunnissa" - lore1 = " Vähennetty vahinko" - lore2 = "kokemusta kulutettu" - lore = ["Aktivoituu vain kriittisellä terveydellä (<= 5 sydäntä) kerran 15 sekunnissa", " Vähennetty vahinko", "kokemusta kulutettu"] +name = "Kokeellinen vastustuskyky" +description = "Kuluta kokemusta lievittääksesi vahinkoa vain kun isku pudottaisi sinut alle 5 sydämen tai tappaisi sinut." +lore0 = "Aktivoituu vain kriittisellä terveydellä (<= 5 sydäntä) kerran 15 sekunnissa" +lore1 = " Vähennetty vahinko" +lore2 = "kokemusta kulutettu" +lore = ["Aktivoituu vain kriittisellä terveydellä (<= 5 sydäntä) kerran 15 sekunnissa", " Vähennetty vahinko", "kokemusta kulutettu"] [discovery.villager] - name = "Kyläläisten vetovoima" - description = "Mahdollistaa paremmat kaupat kyläläisten kanssa!" - lore1 = "Tämä kuluttaa XP:tä per vuorovaikutus kyläläisten kanssa" - lore2 = "Mahdollisuus per vuorovaikutus kuluttaa XP:tä ja parantaa kauppoja" - lore3 = "vaadittu XP:n kulutus per vuorovaikutus" - lore = ["Tämä kuluttaa XP:tä per vuorovaikutus kyläläisten kanssa", "Mahdollisuus per vuorovaikutus kuluttaa XP:tä ja parantaa kauppoja", "vaadittu XP:n kulutus per vuorovaikutus"] +name = "Kyläläisten vetovoima" +description = "Mahdollistaa paremmat kaupat kyläläisten kanssa!" +lore1 = "Tämä kuluttaa XP:tä per vuorovaikutus kyläläisten kanssa" +lore2 = "Mahdollisuus per vuorovaikutus kuluttaa XP:tä ja parantaa kauppoja" +lore3 = "vaadittu XP:n kulutus per vuorovaikutus" +lore = ["Tämä kuluttaa XP:tä per vuorovaikutus kyläläisten kanssa", "Mahdollisuus per vuorovaikutus kuluttaa XP:tä ja parantaa kauppoja", "vaadittu XP:n kulutus per vuorovaikutus"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Lapislazulin palautus" - description = "Yhden ylimääräisen XP-tason hinnalla, sinulla on mahdollisuus saada ilmaista lapislazulia takaisin" - lore1 = "Jokaista tasoa kohden lumoamisen hinta nousee yhdellä, mutta voi palauttaa jopa 3 lapislazulia" - lore = ["Jokaista tasoa kohden lumoamisen hinta nousee yhdellä, mutta voi palauttaa jopa 3 lapislazulia"] +name = "Lapislazulin palautus" +description = "Yhden ylimääräisen XP-tason hinnalla, sinulla on mahdollisuus saada ilmaista lapislazulia takaisin" +lore1 = "Jokaista tasoa kohden lumoamisen hinta nousee yhdellä, mutta voi palauttaa jopa 3 lapislazulia" +lore = ["Jokaista tasoa kohden lumoamisen hinta nousee yhdellä, mutta voi palauttaa jopa 3 lapislazulia"] [enchanting.quick_enchant] - name = "Pikaklikkaus-lumoaminen" - description = "Lumoa esineitä klikkaamalla lumouskirjoja suoraan niiden päälle." - lore1 = "Yhdistettyjen tasojen enimmäismäärä" - lore2 = "Esinettä ei voi luomita enempää kuin " - lore3 = "tehoa" - lore = ["Yhdistettyjen tasojen enimmäismäärä", "Esinettä ei voi luomita enempää kuin ", "tehoa"] +name = "Pikaklikkaus-lumoaminen" +description = "Lumoa esineitä klikkaamalla lumouskirjoja suoraan niiden päälle." +lore1 = "Yhdistettyjen tasojen enimmäismäärä" +lore2 = "Esinettä ei voi luomita enempää kuin " +lore3 = "tehoa" +lore = ["Yhdistettyjen tasojen enimmäismäärä", "Esinettä ei voi luomita enempää kuin ", "tehoa"] [enchanting.return] - name = "XP:n palautus" - description = "Luomis-XP palautetaan sinulle kun luomat esineen." - lore1 = "Käytetyllä kokemuksella on mahdollisuus palautua kun luomat esineen" - lore2 = "Kokemusta per luomus" - lore = ["Käytetyllä kokemuksella on mahdollisuus palautua kun luomat esineen", "Kokemusta per luomus"] +name = "XP:n palautus" +description = "Luomis-XP palautetaan sinulle kun luomat esineen." +lore1 = "Käytetyllä kokemuksella on mahdollisuus palautua kun luomat esineen" +lore2 = "Kokemusta per luomus" +lore = ["Käytetyllä kokemuksella on mahdollisuus palautua kun luomat esineen", "Kokemusta per luomus"] # excavation [excavation] [excavation.haste] - name = "Kiireinen kaivaja" - description = "Tämä nopeuttaa kaivamista KIIREellä!" - lore1 = "Saat kiire-efektin kaivaessasi" - lore2 = "x tasoa kiire-efektiä kun alat louhia MITÄ TAHANSA lohkoa." - lore = ["Saat kiire-efektin kaivaessasi", "x tasoa kiire-efektiä kun alat louhia MITÄ TAHANSA lohkoa."] +name = "Kiireinen kaivaja" +description = "Tämä nopeuttaa kaivamista KIIREellä!" +lore1 = "Saat kiire-efektin kaivaessasi" +lore2 = "x tasoa kiire-efektiä kun alat louhia MITÄ TAHANSA lohkoa." +lore = ["Saat kiire-efektin kaivaessasi", "x tasoa kiire-efektiä kun alat louhia MITÄ TAHANSA lohkoa."] [excavation.spelunker] - name = "Supernäköinen luolastotutkija!" - description = "Näe malmit silmilläsi, maan läpi!" - lore1 = "Malmi sivukädessäsi, hehkumarjat pääkädessäsi ja kyykisty!" - lore2 = "Lohkojen kantama: " - lore3 = "Kuluttaa hehkumarjan käytettäessä" - lore = ["Malmi sivukädessäsi, hehkumarjat pääkädessäsi ja kyykisty!", "Lohkojen kantama: ", "Kuluttaa hehkumarjan käytettäessä"] +name = "Supernäköinen luolastotutkija!" +description = "Näe malmit silmilläsi, maan läpi!" +lore1 = "Malmi sivukädessäsi, hehkumarjat pääkädessäsi ja kyykisty!" +lore2 = "Lohkojen kantama: " +lore3 = "Kuluttaa hehkumarjan käytettäessä" +lore = ["Malmi sivukädessäsi, hehkumarjat pääkädessäsi ja kyykisty!", "Lohkojen kantama: ", "Kuluttaa hehkumarjan käytettäessä"] [excavation.drop_to_inventory] - name = "Lapio: pudota varastoon" +name = "Lapio: pudota varastoon" [excavation.omni_tool] - name = "OMNI-T.Y.Ö.K.A.L.U." - description = "Tacklen ylisuunniteltu ylellinen monitoimityökalu" - lore1 = "Luultavasti tehokkain monista, antaa sinun" - lore2 = "dynaamisesti yhdistää ja vaihtaa työkaluja lennossa tarpeidesi mukaan." - lore3 = "Yhdistääksesi, shift-klikkaa esine toisen päälle varastossasi." - lore4 = "Irrottaaksesi työkalut, kyykky-pudota esine niin se puretaan." - lore5 = "Et voi rikkoa työkaluja tässä monitoimityökalussa, mutta et voi käyttää rikkinäisiä työkaluja" - lore6 = "yhdistettäviä esineitä yhteensä." - lore7 = "Voisit käyttää viittä tai kuutta työkalua, tai vain yhtä!" - lore = ["Luultavasti tehokkain monista, antaa sinun", "dynaamisesti yhdistää ja vaihtaa työkaluja lennossa tarpeidesi mukaan.", "Yhdistääksesi, shift-klikkaa esine toisen päälle varastossasi.", "Irrottaaksesi työkalut, kyykky-pudota esine niin se puretaan.", "Et voi rikkoa työkaluja tässä monitoimityökalussa, mutta et voi käyttää rikkinäisiä työkaluja", "yhdistettäviä esineitä yhteensä.", "Voisit käyttää viittä tai kuutta työkalua, tai vain yhtä!"] +name = "OMNI-T.Y.Ö.K.A.L.U." +description = "Tacklen ylisuunniteltu ylellinen monitoimityökalu" +lore1 = "Luultavasti tehokkain monista, antaa sinun" +lore2 = "dynaamisesti yhdistää ja vaihtaa työkaluja lennossa tarpeidesi mukaan." +lore3 = "Yhdistääksesi, shift-klikkaa esine toisen päälle varastossasi." +lore4 = "Irrottaaksesi työkalut, kyykky-pudota esine niin se puretaan." +lore5 = "Et voi rikkoa työkaluja tässä monitoimityökalussa, mutta et voi käyttää rikkinäisiä työkaluja" +lore6 = "yhdistettäviä esineitä yhteensä." +lore7 = "Voisit käyttää viittä tai kuutta työkalua, tai vain yhtä!" +lore = ["Luultavasti tehokkain monista, antaa sinun", "dynaamisesti yhdistää ja vaihtaa työkaluja lennossa tarpeidesi mukaan.", "Yhdistääksesi, shift-klikkaa esine toisen päälle varastossasi.", "Irrottaaksesi työkalut, kyykky-pudota esine niin se puretaan.", "Et voi rikkoa työkaluja tässä monitoimityökalussa, mutta et voi käyttää rikkinäisiä työkaluja", "yhdistettäviä esineitä yhteensä.", "Voisit käyttää viittä tai kuutta työkalua, tai vain yhtä!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Kasvuaura" - description = "Kasvata luontoa ympärilläsi auralla" - lore1 = "Lohkon säde" - lore2 = "Kasvuauran vahvuus" - lore3 = "Ruokakustannus" - lore = ["Lohkon säde", "Kasvuauran vahvuus", "Ruokakustannus"] +name = "Kasvuaura" +description = "Kasvata luontoa ympärilläsi auralla" +lore1 = "Lohkon säde" +lore2 = "Kasvuauran vahvuus" +lore3 = "Ruokakustannus" +lore = ["Lohkon säde", "Kasvuauran vahvuus", "Ruokakustannus"] [herbalism.hippo] - name = "Yrttiläisen virtahepo" - description = "Ruoan syöminen antaa sinulle enemmän kylläisyyttä" - lore1 = "Ruoka) lisäkylläisyyspisteitä kulutuksessa" - lore = ["Ruoka) lisäkylläisyyspisteitä kulutuksessa"] +name = "Yrttiläisen virtahepo" +description = "Ruoan syöminen antaa sinulle enemmän kylläisyyttä" +lore1 = "Ruoka) lisäkylläisyyspisteitä kulutuksessa" +lore = ["Ruoka) lisäkylläisyyspisteitä kulutuksessa"] [herbalism.myconid] - name = "Yrttiläisen mykonidi" - description = "Antaa sinulle kyvyn valmistaa myseliumia" - lore1 = "Mikä tahansa multa ja ruskea & punainen sieni valmistaa myseliumia." - lore = ["Mikä tahansa multa ja ruskea & punainen sieni valmistaa myseliumia."] +name = "Yrttiläisen mykonidi" +description = "Antaa sinulle kyvyn valmistaa myseliumia" +lore1 = "Mikä tahansa multa ja ruskea & punainen sieni valmistaa myseliumia." +lore = ["Mikä tahansa multa ja ruskea & punainen sieni valmistaa myseliumia."] [herbalism.terralid] - name = "Yrttiläisen terralidi" - description = "Antaa sinulle kyvyn valmistaa ruoholohkoja" - lore1 = "Kolme siementä 3 mullan päällä valmistaa 3 ruoholohkoa." - lore = ["Kolme siementä 3 mullan päällä valmistaa 3 ruoholohkoa."] +name = "Yrttiläisen terralidi" +description = "Antaa sinulle kyvyn valmistaa ruoholohkoja" +lore1 = "Kolme siementä 3 mullan päällä valmistaa 3 ruoholohkoa." +lore = ["Kolme siementä 3 mullan päällä valmistaa 3 ruoholohkoa."] [herbalism.cobweb] - name = "Seitinkutoja" - description = "Antaa sinulle kyvyn valmistaa hämähäkinseittejä valmistuspöydässä" - lore1 = "Yhdeksän lankaa valmistaa hämähäkinseitin." - lore = ["Yhdeksän lankaa valmistaa hämähäkinseitin."] +name = "Seitinkutoja" +description = "Antaa sinulle kyvyn valmistaa hämähäkinseittejä valmistuspöydässä" +lore1 = "Yhdeksän lankaa valmistaa hämähäkinseitin." +lore = ["Yhdeksän lankaa valmistaa hämähäkinseitin."] [herbalism.mushroom_blocks] - name = "Sienentekijä" - description = "Antaa sinulle kyvyn valmistaa sienilohkoja valmistuspöydässä" - lore1 = "Neljä sientä lohkon tekemiseen, tai lohko varren tekemiseen." - lore = ["Neljä sientä lohkon tekemiseen, tai lohko varren tekemiseen."] +name = "Sienentekijä" +description = "Antaa sinulle kyvyn valmistaa sienilohkoja valmistuspöydässä" +lore1 = "Neljä sientä lohkon tekemiseen, tai lohko varren tekemiseen." +lore = ["Neljä sientä lohkon tekemiseen, tai lohko varren tekemiseen."] [herbalism.drop_to_inventory] - name = "Kuokka: pudota varastoon" +name = "Kuokka: pudota varastoon" [herbalism.hungry_shield] - name = "Nälkäkilpi" - description = "Ota vahinkoa nälkääsi ennen terveyttäsi." - lore1 = "Nälän vastustama" - lore = ["Nälän vastustama"] +name = "Nälkäkilpi" +description = "Ota vahinkoa nälkääsi ennen terveyttäsi." +lore1 = "Nälän vastustama" +lore = ["Nälän vastustama"] [herbalism.luck] - name = "Yrttiläisen onni" - description = "Kun rikot ruohoa/kukkia, sinulla on mahdollisuus saada satunnainen esine" - lore0 = "Kukat = ruokaa, ja ruoho = siemeniä" - lore1 = "Mahdollisuus saada esine kukkien rikkomisesta" - lore2 = "Mahdollisuus saada esine ruohon rikkomisesta" - lore = ["Kukat = ruokaa, ja ruoho = siemeniä", "Mahdollisuus saada esine kukkien rikkomisesta", "Mahdollisuus saada esine ruohon rikkomisesta"] +name = "Yrttiläisen onni" +description = "Kun rikot ruohoa/kukkia, sinulla on mahdollisuus saada satunnainen esine" +lore0 = "Kukat = ruokaa, ja ruoho = siemeniä" +lore1 = "Mahdollisuus saada esine kukkien rikkomisesta" +lore2 = "Mahdollisuus saada esine ruohon rikkomisesta" +lore = ["Kukat = ruokaa, ja ruoho = siemeniä", "Mahdollisuus saada esine kukkien rikkomisesta", "Mahdollisuus saada esine ruohon rikkomisesta"] [herbalism.replant] - name = "Korjaa ja istuta uudelleen" - description = "Oikea-klikkaa satoa kuokalla korjataksesi ja istuttaaksesi se uudelleen." - lore1 = "Uudelleenistutuksen lohkosäde" - lore = ["Uudelleenistutuksen lohkosäde"] +name = "Korjaa ja istuta uudelleen" +description = "Oikea-klikkaa satoa kuokalla korjataksesi ja istuttaaksesi se uudelleen." +lore1 = "Uudelleenistutuksen lohkosäde" +lore = ["Uudelleenistutuksen lohkosäde"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenaliini" - description = "Tee enemmän vahinkoa mitä vähemmän terveyttä sinulla on (lähitaistelu)" - lore1 = "Maksimivahinko" - lore = ["Maksimivahinko"] +name = "Adrenaliini" +description = "Tee enemmän vahinkoa mitä vähemmän terveyttä sinulla on (lähitaistelu)" +lore1 = "Maksimivahinko" +lore = ["Maksimivahinko"] [hunter.penalty] - name = "" - description = "" - lore1 = "Saat myrkkykertymiä, jos nälkäsi loppuu" - lore = ["Saat myrkkykertymiä, jos nälkäsi loppuu"] +name = "" +description = "" +lore1 = "Saat myrkkykertymiä, jos nälkäsi loppuu" +lore = ["Saat myrkkykertymiä, jos nälkäsi loppuu"] [hunter.drop_to_inventory] - name = "Esineet: pudota varastoon" - description = "Kun tapat jotain / rikot lohkon miekalla, pudotukset teleporttaavat varastoosi" - lore1 = "Aina kun esine putoaa hirviöstä/rikkomastasi lohkosta, se menee varastoosi jos mahdollista." - lore = ["Aina kun esine putoaa hirviöstä/rikkomastasi lohkosta, se menee varastoosi jos mahdollista."] +name = "Esineet: pudota varastoon" +description = "Kun tapat jotain / rikot lohkon miekalla, pudotukset teleporttaavat varastoosi" +lore1 = "Aina kun esine putoaa hirviöstä/rikkomastasi lohkosta, se menee varastoosi jos mahdollista." +lore = ["Aina kun esine putoaa hirviöstä/rikkomastasi lohkosta, se menee varastoosi jos mahdollista."] [hunter.invisibility] - name = "Katoava askel" - description = "Kun sinua lyödään, saat näkymättömyyttä nälän kustannuksella" - lore1 = "Saat passiivista näkymättömyyttä iskusta" - lore2 = "x näkymättömyyskertymää 3 sekuntia iskun jälkeen" - lore3 = "x kertyvä nälkä" - lore4 = "Nälkäkertymien kesto ja kerroin." - lore5 = "Näkymättömyyden kesto" - lore = ["Saat passiivista näkymättömyyttä iskusta", "x näkymättömyyskertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Näkymättömyyden kesto"] +name = "Katoava askel" +description = "Kun sinua lyödään, saat näkymättömyyttä nälän kustannuksella" +lore1 = "Saat passiivista näkymättömyyttä iskusta" +lore2 = "x näkymättömyyskertymää 3 sekuntia iskun jälkeen" +lore3 = "x kertyvä nälkä" +lore4 = "Nälkäkertymien kesto ja kerroin." +lore5 = "Näkymättömyyden kesto" +lore = ["Saat passiivista näkymättömyyttä iskusta", "x näkymättömyyskertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Näkymättömyyden kesto"] [hunter.jump_boost] - name = "Metsästäjän korkeudet" - description = "Kun sinua lyödään, saat hyppytehostusta nälän kustannuksella" - lore1 = "Saat passiivista hyppytehostusta iskusta" - lore2 = "x hyppytehostuskertymää 3 sekuntia iskun jälkeen" - lore3 = "x kertyvä nälkä" - lore4 = "Nälkäkertymien kesto ja kerroin." - lore5 = "Hyppytehostuksen kertymäkerroin, ei kesto." - lore = ["Saat passiivista hyppytehostusta iskusta", "x hyppytehostuskertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Hyppytehostuksen kertymäkerroin, ei kesto."] +name = "Metsästäjän korkeudet" +description = "Kun sinua lyödään, saat hyppytehostusta nälän kustannuksella" +lore1 = "Saat passiivista hyppytehostusta iskusta" +lore2 = "x hyppytehostuskertymää 3 sekuntia iskun jälkeen" +lore3 = "x kertyvä nälkä" +lore4 = "Nälkäkertymien kesto ja kerroin." +lore5 = "Hyppytehostuksen kertymäkerroin, ei kesto." +lore = ["Saat passiivista hyppytehostusta iskusta", "x hyppytehostuskertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Hyppytehostuksen kertymäkerroin, ei kesto."] [hunter.luck] - name = "Metsästäjän tuuri" - description = "Kun sinua lyödään, saat onnea nälän kustannuksella" - lore1 = "Saat passiivista onnea iskusta" - lore2 = "x onnenkertymää 3 sekuntia iskun jälkeen" - lore3 = "x kertyvä nälkä" - lore4 = "Nälkäkertymien kesto ja kerroin." - lore5 = "Onnenkertymien kerroin, ei kesto." - lore = ["Saat passiivista onnea iskusta", "x onnenkertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Onnenkertymien kerroin, ei kesto."] +name = "Metsästäjän tuuri" +description = "Kun sinua lyödään, saat onnea nälän kustannuksella" +lore1 = "Saat passiivista onnea iskusta" +lore2 = "x onnenkertymää 3 sekuntia iskun jälkeen" +lore3 = "x kertyvä nälkä" +lore4 = "Nälkäkertymien kesto ja kerroin." +lore5 = "Onnenkertymien kerroin, ei kesto." +lore = ["Saat passiivista onnea iskusta", "x onnenkertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Onnenkertymien kerroin, ei kesto."] [hunter.regen] - name = "Metsästäjän uudistuminen" - description = "Kun sinua lyödään, saat uudistumista nälän kustannuksella" - lore1 = "Saat passiivista uudistumista iskusta" - lore2 = "x uudistumiskertymää 3 sekuntia iskun jälkeen" - lore3 = "x kertyvä nälkä" - lore4 = "Nälkäkertymien kesto ja kerroin." - lore5 = "Uudistumiskertymien kerroin, ei kesto." - lore = ["Saat passiivista uudistumista iskusta", "x uudistumiskertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Uudistumiskertymien kerroin, ei kesto."] +name = "Metsästäjän uudistuminen" +description = "Kun sinua lyödään, saat uudistumista nälän kustannuksella" +lore1 = "Saat passiivista uudistumista iskusta" +lore2 = "x uudistumiskertymää 3 sekuntia iskun jälkeen" +lore3 = "x kertyvä nälkä" +lore4 = "Nälkäkertymien kesto ja kerroin." +lore5 = "Uudistumiskertymien kerroin, ei kesto." +lore = ["Saat passiivista uudistumista iskusta", "x uudistumiskertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Uudistumiskertymien kerroin, ei kesto."] [hunter.resistance] - name = "Metsästäjän vastustuskyky" - description = "Kun sinua lyödään, saat vastustuskykyä nälän kustannuksella" - lore1 = "Saat passiivista vastustuskykyä iskusta" - lore2 = "x vastustuskykykertymää 3 sekuntia iskun jälkeen" - lore3 = "x kertyvä nälkä" - lore4 = "Nälkäkertymien kesto ja kerroin." - lore5 = "Vastustuskykykertymien kerroin, ei kesto." - lore = ["Saat passiivista vastustuskykyä iskusta", "x vastustuskykykertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Vastustuskykykertymien kerroin, ei kesto."] +name = "Metsästäjän vastustuskyky" +description = "Kun sinua lyödään, saat vastustuskykyä nälän kustannuksella" +lore1 = "Saat passiivista vastustuskykyä iskusta" +lore2 = "x vastustuskykykertymää 3 sekuntia iskun jälkeen" +lore3 = "x kertyvä nälkä" +lore4 = "Nälkäkertymien kesto ja kerroin." +lore5 = "Vastustuskykykertymien kerroin, ei kesto." +lore = ["Saat passiivista vastustuskykyä iskusta", "x vastustuskykykertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Vastustuskykykertymien kerroin, ei kesto."] [hunter.speed] - name = "Metsästäjän nopeus" - description = "Kun sinua lyödään, saat nopeutta nälän kustannuksella" - lore1 = "Saat passiivista nopeutta iskusta" - lore2 = "x nopeuskertymää 3 sekuntia iskun jälkeen" - lore3 = "x kertyvä nälkä" - lore4 = "Nälkäkertymien kesto ja kerroin." - lore5 = "Nopeuskertymien kerroin, ei kesto." - lore = ["Saat passiivista nopeutta iskusta", "x nopeuskertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Nopeuskertymien kerroin, ei kesto."] +name = "Metsästäjän nopeus" +description = "Kun sinua lyödään, saat nopeutta nälän kustannuksella" +lore1 = "Saat passiivista nopeutta iskusta" +lore2 = "x nopeuskertymää 3 sekuntia iskun jälkeen" +lore3 = "x kertyvä nälkä" +lore4 = "Nälkäkertymien kesto ja kerroin." +lore5 = "Nopeuskertymien kerroin, ei kesto." +lore = ["Saat passiivista nopeutta iskusta", "x nopeuskertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Nopeuskertymien kerroin, ei kesto."] [hunter.strength] - name = "Metsästäjän voima" - description = "Kun sinua lyödään, saat voimaa nälän kustannuksella" - lore1 = "Saat passiivista voimaa iskusta" - lore2 = "x voimakertymää 3 sekuntia iskun jälkeen" - lore3 = "x kertyvä nälkä" - lore4 = "Nälkäkertymien kesto ja kerroin." - lore5 = "Voimakertymien kerroin, ei kesto." - lore = ["Saat passiivista voimaa iskusta", "x voimakertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Voimakertymien kerroin, ei kesto."] +name = "Metsästäjän voima" +description = "Kun sinua lyödään, saat voimaa nälän kustannuksella" +lore1 = "Saat passiivista voimaa iskusta" +lore2 = "x voimakertymää 3 sekuntia iskun jälkeen" +lore3 = "x kertyvä nälkä" +lore4 = "Nälkäkertymien kesto ja kerroin." +lore5 = "Voimakertymien kerroin, ei kesto." +lore = ["Saat passiivista voimaa iskusta", "x voimakertymää 3 sekuntia iskun jälkeen", "x kertyvä nälkä", "Nälkäkertymien kesto ja kerroin.", "Voimakertymien kerroin, ei kesto."] # nether [nether] [nether.skull_toss] - name = "Wither-kallon heitto" - description1 = "Vapauta sisäinen Witherisi käyttämällä" - description2 = "jonkun" - description3 = "päätä." - lore1 = "Sekuntia jäähtymisaikaa kallon heittojen välillä." - lore2 = "Wither-kallon käyttö: Heitä " - lore3 = "Wither-kallo" - lore4 = "räjähtää osuessaan." - lore = ["Sekuntia jäähtymisaikaa kallon heittojen välillä.", "Wither-kallon käyttö: Heitä ", "Wither-kallo", "räjähtää osuessaan."] +name = "Wither-kallon heitto" +description1 = "Vapauta sisäinen Witherisi käyttämällä" +description2 = "jonkun" +description3 = "päätä." +lore1 = "Sekuntia jäähtymisaikaa kallon heittojen välillä." +lore2 = "Wither-kallon käyttö: Heitä " +lore3 = "Wither-kallo" +lore4 = "räjähtää osuessaan." +lore = ["Sekuntia jäähtymisaikaa kallon heittojen välillä.", "Wither-kallon käyttö: Heitä ", "Wither-kallo", "räjähtää osuessaan."] [nether.wither_resist] - name = "Wither-vastustus" - description = "Vastustaa witheriä netheriittipanssarin voimalla." - lore1 = "mahdollisuus estää witheri (per osa)." - lore2 = "Passiivinen: netheriittipanssarin käyttö antaa mahdollisuuden estää " - lore3 = "witheri." - lore = ["mahdollisuus estää witheri (per osa).", "Passiivinen: netheriittipanssarin käyttö antaa mahdollisuuden estää ", "witheri."] +name = "Wither-vastustus" +description = "Vastustaa witheriä netheriittipanssarin voimalla." +lore1 = "mahdollisuus estää witheri (per osa)." +lore2 = "Passiivinen: netheriittipanssarin käyttö antaa mahdollisuuden estää " +lore3 = "witheri." +lore = ["mahdollisuus estää witheri (per osa).", "Passiivinen: netheriittipanssarin käyttö antaa mahdollisuuden estää ", "witheri."] [nether.fire_resist] - name = "Tulenkestävyys" - description = "Vastustaa tulta kovettamalla ihosi." - lore1 = "mahdollisuus estää palaminen!" - lore = ["mahdollisuus estää palaminen!"] +name = "Tulenkestävyys" +description = "Vastustaa tulta kovettamalla ihosi." +lore1 = "mahdollisuus estää palaminen!" +lore = ["mahdollisuus estää palaminen!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Automaattisulatus" - description = "Mahdollistaa louhittujen perusmalmien sulattamisen" - lore1 = "Sulatettavat malmit sulatetaan automaattisesti" - lore2 = "% mahdollisuus ylimääräiseen" - lore = ["Sulatettavat malmit sulatetaan automaattisesti", "% mahdollisuus ylimääräiseen"] +name = "Automaattisulatus" +description = "Mahdollistaa louhittujen perusmalmien sulattamisen" +lore1 = "Sulatettavat malmit sulatetaan automaattisesti" +lore2 = "% mahdollisuus ylimääräiseen" +lore = ["Sulatettavat malmit sulatetaan automaattisesti", "% mahdollisuus ylimääräiseen"] [pickaxe.chisel] - name = "Malmitaltta" - description = "Oikea-klikkaa malmeja talttataksesi niistä lisää malmia, kovilla kestävyyskustannuksilla." - lore1 = "Mahdollisuus pudottaa" - lore2 = "Työkalun kuluminen" - lore = ["Mahdollisuus pudottaa", "Työkalun kuluminen"] +name = "Malmitaltta" +description = "Oikea-klikkaa malmeja talttataksesi niistä lisää malmia, kovilla kestävyyskustannuksilla." +lore1 = "Mahdollisuus pudottaa" +lore2 = "Työkalun kuluminen" +lore = ["Mahdollisuus pudottaa", "Työkalun kuluminen"] [pickaxe.drop_to_inventory] - name = "Hakku: pudota varastoon" - description = "Kun rikot lohkon, se teleporttaa esineen varastoosi" - lore1 = "Aina kun esine putoaa rikkomastasi lohkosta, se menee varastoosi jos mahdollista." - lore = ["Aina kun esine putoaa rikkomastasi lohkosta, se menee varastoosi jos mahdollista."] +name = "Hakku: pudota varastoon" +description = "Kun rikot lohkon, se teleporttaa esineen varastoosi" +lore1 = "Aina kun esine putoaa rikkomastasi lohkosta, se menee varastoosi jos mahdollista." +lore = ["Aina kun esine putoaa rikkomastasi lohkosta, se menee varastoosi jos mahdollista."] [pickaxe.silk_spawner] - name = "Hakun silkkispawneri" - description = "Spawnerit putoavat rikkoutuessaan" - lore1 = "Spawnerit voi rikkoa silkkikosketuksella." - lore2 = "Spawnerit voi rikkoa kyykistymällä." - lore = ["Spawnerit voi rikkoa silkkikosketuksella.", "Spawnerit voi rikkoa kyykistymällä."] +name = "Hakun silkkispawneri" +description = "Spawnerit putoavat rikkoutuessaan" +lore1 = "Spawnerit voi rikkoa silkkikosketuksella." +lore2 = "Spawnerit voi rikkoa kyykistymällä." +lore = ["Spawnerit voi rikkoa silkkikosketuksella.", "Spawnerit voi rikkoa kyykistymällä."] [pickaxe.vein_miner] - name = "Suonilouhija" - description = "Mahdollistaa lohkojen rikkomisen malmisuonesta/ryhmästä" - lore1 = "Kyykisty ja louhi MALMEJA" - lore2 = "suonilouhinnan kantama" - lore3 = "Tämä taito EI ryhmitä kaikkia pudotuksia yhteen!" - lore = ["Kyykisty ja louhi MALMEJA", "suonilouhinnan kantama", "Tämä taito EI ryhmitä kaikkia pudotuksia yhteen!"] +name = "Suonilouhija" +description = "Mahdollistaa lohkojen rikkomisen malmisuonesta/ryhmästä" +lore1 = "Kyykisty ja louhi MALMEJA" +lore2 = "suonilouhinnan kantama" +lore3 = "Tämä taito EI ryhmitä kaikkia pudotuksia yhteen!" +lore = ["Kyykisty ja louhi MALMEJA", "suonilouhinnan kantama", "Tämä taito EI ryhmitä kaikkia pudotuksia yhteen!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Nuolen palautus" - description = "Palauta nuolet vihollisen tappamisen jälkeen." - lore1 = "Mahdollisuus palauttaa nuolia osumasta/taposta" - lore2 = "Mahdollisuus: " - lore = ["Mahdollisuus palauttaa nuolia osumasta/taposta", "Mahdollisuus: "] +name = "Nuolen palautus" +description = "Palauta nuolet vihollisen tappamisen jälkeen." +lore1 = "Mahdollisuus palauttaa nuolia osumasta/taposta" +lore2 = "Mahdollisuus: " +lore = ["Mahdollisuus palauttaa nuolia osumasta/taposta", "Mahdollisuus: "] [ranged.web_shot] - name = "Seittiansa" - description = "Ympäröi kohde hämähäkinseiteillä osuessasi!" - lore1 = "8 hämähäkinseittiä lumipallon ympärille ja heitä!" - lore2 = "sekuntia häkkiä, suunnilleen." - lore = ["8 hämähäkinseittiä lumipallon ympärille ja heitä!", "sekuntia häkkiä, suunnilleen."] +name = "Seittiansa" +description = "Ympäröi kohde hämähäkinseiteillä osuessasi!" +lore1 = "8 hämähäkinseittiä lumipallon ympärille ja heitä!" +lore2 = "sekuntia häkkiä, suunnilleen." +lore = ["8 hämähäkinseittiä lumipallon ympärille ja heitä!", "sekuntia häkkiä, suunnilleen."] [ranged.force_shot] - name = "Voimalaukaus" - description = "Ammu ammuksia pidemmälle, nopeammin!" - advancementname = "Pitkä laukaus" - advancementlore = "Osu yli 30 lohkon päästä!" - lore1 = "Ammuksen nopeus" - lore = ["Ammuksen nopeus"] +name = "Voimalaukaus" +description = "Ammu ammuksia pidemmälle, nopeammin!" +advancementname = "Pitkä laukaus" +advancementlore = "Osu yli 30 lohkon päästä!" +lore1 = "Ammuksen nopeus" +lore = ["Ammuksen nopeus"] [ranged.lunge_shot] - name = "Syöksylaukaus" - description = "Pudotessasi nuolesi heittävät sinua satunnaiseen suuntaan" - lore1 = "Satunnainen purskausnopeus" - lore = ["Satunnainen purskausnopeus"] +name = "Syöksylaukaus" +description = "Pudotessasi nuolesi heittävät sinua satunnaiseen suuntaan" +lore1 = "Satunnainen purskausnopeus" +lore = ["Satunnainen purskausnopeus"] [ranged.arrow_piercing] - name = "Nuolen lävistys" - description = "Lisää lävistys ammuksiin! Ammu asioiden läpi!" - lore1 = "Lävistyskohteet" - lore = ["Lävistyskohteet"] +name = "Nuolen lävistys" +description = "Lisää lävistys ammuksiin! Ammu asioiden läpi!" +lore1 = "Lävistyskohteet" +lore = ["Lävistyskohteet"] # rift [rift] [rift.remote_access] - name = "Etäkäyttö" - description = "Vedä tyhjyydestä ja pääse merkittyyn säiliöön." - lore1 = "Enderhelmi + kompassi = Reliikkiavainportti" - lore2 = "Tämä esine mahdollistaa säiliöiden etäkäytön" - lore3 = "Valmistuksen jälkeen katso esinettä nähdäksesi käyttöohjeet" - notcontainer = "Tuo ei ole säiliö" - lore = ["Enderhelmi + kompassi = Reliikkiavainportti", "Tämä esine mahdollistaa säiliöiden etäkäytön", "Valmistuksen jälkeen katso esinettä nähdäksesi käyttöohjeet"] +name = "Etäkäyttö" +description = "Vedä tyhjyydestä ja pääse merkittyyn säiliöön." +lore1 = "Enderhelmi + kompassi = Reliikkiavainportti" +lore2 = "Tämä esine mahdollistaa säiliöiden etäkäytön" +lore3 = "Valmistuksen jälkeen katso esinettä nähdäksesi käyttöohjeet" +notcontainer = "Tuo ei ole säiliö" +lore = ["Enderhelmi + kompassi = Reliikkiavainportti", "Tämä esine mahdollistaa säiliöiden etäkäytön", "Valmistuksen jälkeen katso esinettä nähdäksesi käyttöohjeet"] [rift.blink] - name = "Repeämävälähdys" - description = "Lyhyen kantaman välitön teleporttaus, vain silmänräpäys!" - lore1 = "Lohkoja per välähdys (2x pystysuunnassa)" - lore2 = "Juostessa: Kaksoisnapauta hyppyä " - lore3 = "välähtääksesi" - lore = ["Lohkoja per välähdys (2x pystysuunnassa)", "Juostessa: Kaksoisnapauta hyppyä ", "välähtääksesi"] +name = "Repeämävälähdys" +description = "Lyhyen kantaman välitön teleporttaus, vain silmänräpäys!" +lore1 = "Lohkoja per välähdys (2x pystysuunnassa)" +lore2 = "Juostessa: Kaksoisnapauta hyppyä " +lore3 = "välähtääksesi" +lore = ["Lohkoja per välähdys (2x pystysuunnassa)", "Juostessa: Kaksoisnapauta hyppyä ", "välähtääksesi"] [rift.chest] - name = "Helppo enderarkku" - description = "Avaa enderarkku vasemmalla klikkauksella sitä kädessäsi." - lore1 = "Klikkaa enderarkkua kädessäsi avataksesi (älä vain aseta sitä)" - lore = ["Klikkaa enderarkkua kädessäsi avataksesi (älä vain aseta sitä)"] +name = "Helppo enderarkku" +description = "Avaa enderarkku vasemmalla klikkauksella sitä kädessäsi." +lore1 = "Klikkaa enderarkkua kädessäsi avataksesi (älä vain aseta sitä)" +lore = ["Klikkaa enderarkkua kädessäsi avataksesi (älä vain aseta sitä)"] [rift.descent] - name = "Leijumisenvastaisuus" - description = "Oletko kyllästynyt olemaan jumissa ilmassa? Tämä taito on sinua varten!" - lore1 = "Kyykisty vain laskeutaaksesi, ja putoat normaalia hitaammin!" - lore2 = "Jäähtymisaika:" - lore = ["Kyykisty vain laskeutaaksesi, ja putoat normaalia hitaammin!", "Jäähtymisaika:"] +name = "Leijumisenvastaisuus" +description = "Oletko kyllästynyt olemaan jumissa ilmassa? Tämä taito on sinua varten!" +lore1 = "Kyykisty vain laskeutaaksesi, ja putoat normaalia hitaammin!" +lore2 = "Jäähtymisaika:" +lore = ["Kyykisty vain laskeutaaksesi, ja putoat normaalia hitaammin!", "Jäähtymisaika:"] [rift.gate] - name = "Repeämäportti" - description = "Teleporttaa merkittyyn sijaintiin." - lore1 = "VALMISTUS: Smaragdi + ametistinsiru + enderhelmi" - lore2 = "Lue ennen käyttöä!" - lore3 = "5 sekunnin viive, " - lore4 = "voit kuolla tämän animaation aikana" - lore = ["VALMISTUS: Smaragdi + ametistinsiru + enderhelmi", "Lue ennen käyttöä!", "5 sekunnin viive, ", "voit kuolla tämän animaation aikana"] +name = "Repeämäportti" +description = "Teleporttaa merkittyyn sijaintiin." +lore1 = "VALMISTUS: Smaragdi + ametistinsiru + enderhelmi" +lore2 = "Lue ennen käyttöä!" +lore3 = "5 sekunnin viive, " +lore4 = "voit kuolla tämän animaation aikana" +lore = ["VALMISTUS: Smaragdi + ametistinsiru + enderhelmi", "Lue ennen käyttöä!", "5 sekunnin viive, ", "voit kuolla tämän animaation aikana"] [rift.resist] - name = "Repeämävastustus" - description = "Saat vastustuskykyä käyttäessäsi ender-esineitä ja -kykyjä" - lore1 = "+ Passiivinen: Antaa vastustuskykyä kun käytät repeämäkykyjä tai ender-esineitä" - lore2 = "EI sisällä kannettavaa enderarkkua, vain kulutettavia esineitä" - lore = ["+ Passiivinen: Antaa vastustuskykyä kun käytät repeämäkykyjä tai ender-esineitä", "EI sisällä kannettavaa enderarkkua, vain kulutettavia esineitä"] +name = "Repeämävastustus" +description = "Saat vastustuskykyä käyttäessäsi ender-esineitä ja -kykyjä" +lore1 = "+ Passiivinen: Antaa vastustuskykyä kun käytät repeämäkykyjä tai ender-esineitä" +lore2 = "EI sisällä kannettavaa enderarkkua, vain kulutettavia esineitä" +lore = ["+ Passiivinen: Antaa vastustuskykyä kun käytät repeämäkykyjä tai ender-esineitä", "EI sisällä kannettavaa enderarkkua, vain kulutettavia esineitä"] [rift.visage] - name = "Repeämänaamio" - description = "Estää endermiehiä muuttumasta aggressiivisiksi, jos sinulla on enderhelmiä varastossasi." - lore1 = "Endermiehet eivät muutu aggressiivisiksi, jos sinulla on enderhelmiä varastossasi." - lore = ["Endermiehet eivät muutu aggressiivisiksi, jos sinulla on enderhelmiä varastossasi."] +name = "Repeämänaamio" +description = "Estää endermiehiä muuttumasta aggressiivisiksi, jos sinulla on enderhelmiä varastossasi." +lore1 = "Endermiehet eivät muutu aggressiivisiksi, jos sinulla on enderhelmiä varastossasi." +lore = ["Endermiehet eivät muutu aggressiivisiksi, jos sinulla on enderhelmiä varastossasi."] # seaborn [seaborn] [seaborn.oxygen] - name = "Orgaaninen happitankki" - description = "Pidä enemmän happea pienissä keuhkoissasi!" - lore1 = "Happikapasiteetin lisäys" - lore = ["Happikapasiteetin lisäys"] +name = "Orgaaninen happitankki" +description = "Pidä enemmän happea pienissä keuhkoissasi!" +lore1 = "Happikapasiteetin lisäys" +lore = ["Happikapasiteetin lisäys"] [seaborn.fishers_fantasy] - name = "Kalastajan fantasia" - description = "Ansaitse enemmän XP:tä kalastuksesta ja saa enemmän kalaa!" - lore1 = "Jokaisella tasolla on mahdollisuus saada lisää XP:tä ja kalaa!" - lore = ["Jokaisella tasolla on mahdollisuus saada lisää XP:tä ja kalaa!"] +name = "Kalastajan fantasia" +description = "Ansaitse enemmän XP:tä kalastuksesta ja saa enemmän kalaa!" +lore1 = "Jokaisella tasolla on mahdollisuus saada lisää XP:tä ja kalaa!" +lore = ["Jokaisella tasolla on mahdollisuus saada lisää XP:tä ja kalaa!"] [seaborn.haste] - name = "Kilpikonnakaivaja" - description = "Veden alla louhiessasi saat kiire-efektin!" - lore1 = "Kiire 3 aktivoituu veden alla louhittaessa (pinoutuu vesitehon kanssa) vedenhengitysefektin päätyttyä!" - lore = ["Kiire 3 aktivoituu veden alla louhittaessa (pinoutuu vesitehon kanssa) vedenhengitysefektin päätyttyä!"] +name = "Kilpikonnakaivaja" +description = "Veden alla louhiessasi saat kiire-efektin!" +lore1 = "Kiire 3 aktivoituu veden alla louhittaessa (pinoutuu vesitehon kanssa) vedenhengitysefektin päätyttyä!" +lore = ["Kiire 3 aktivoituu veden alla louhittaessa (pinoutuu vesitehon kanssa) vedenhengitysefektin päätyttyä!"] [seaborn.night_vision] - name = "Kilpikonnan näkö" - description = "Veden alla saat yönäön" - lore1 = "Saat yönäön veden alla vedenhengitysefektin päätyttyä!" - lore = ["Saat yönäön veden alla vedenhengitysefektin päätyttyä!"] +name = "Kilpikonnan näkö" +description = "Veden alla saat yönäön" +lore1 = "Saat yönäön veden alla vedenhengitysefektin päätyttyä!" +lore = ["Saat yönäön veden alla vedenhengitysefektin päätyttyä!"] [seaborn.dolphin_grace] - name = "Delfiinin sulous" - description = "Ui kuin delfiini, ilman delfiinejä" - lore1 = "+ Passiivinen: saat " - lore2 = "x nopeutta (delfiinin sulous)" - lore3 = "tarkkaa saksalaista insinööritaitoa- hetkinen, tuo ei ole oikein... Ei yhteensopiva syvyyden kulkijan kanssa" - lore = ["+ Passiivinen: saat ", "x nopeutta (delfiinin sulous)", "tarkkaa saksalaista insinööritaitoa- hetkinen, tuo ei ole oikein... Ei yhteensopiva syvyyden kulkijan kanssa"] +name = "Delfiinin sulous" +description = "Ui kuin delfiini, ilman delfiinejä" +lore1 = "+ Passiivinen: saat " +lore2 = "x nopeutta (delfiinin sulous)" +lore3 = "tarkkaa saksalaista insinööritaitoa- hetkinen, tuo ei ole oikein... Ei yhteensopiva syvyyden kulkijan kanssa" +lore = ["+ Passiivinen: saat ", "x nopeutta (delfiinin sulous)", "tarkkaa saksalaista insinööritaitoa- hetkinen, tuo ei ole oikein... Ei yhteensopiva syvyyden kulkijan kanssa"] # stealth [stealth] [stealth.ghost_armor] - name = "Haamupanssari" - description = "Hitaasti kertyvä panssari kun et ota vahinkoa, kestää 1 iskun" - lore1 = "Maksimipanssari" - lore2 = "Nopeus" - lore = ["Maksimipanssari", "Nopeus"] +name = "Haamupanssari" +description = "Hitaasti kertyvä panssari kun et ota vahinkoa, kestää 1 iskun" +lore1 = "Maksimipanssari" +lore2 = "Nopeus" +lore = ["Maksimipanssari", "Nopeus"] [stealth.night_vision] - name = "Hiipimisen yönäkö" - description = "Saat yönäön hiipimisen aikana" - lore1 = "Saat purskauksen " - lore2 = "yönäköä" - lore3 = "hiipimisen aikana" - lore = ["Saat purskauksen ", "yönäköä", "hiipimisen aikana"] +name = "Hiipimisen yönäkö" +description = "Saat yönäön hiipimisen aikana" +lore1 = "Saat purskauksen " +lore2 = "yönäköä" +lore3 = "hiipimisen aikana" +lore = ["Saat purskauksen ", "yönäköä", "hiipimisen aikana"] [stealth.snatch] - name = "Esinenappaus" - description = "Nappaa pudonneet esineet heti hiipimisen aikana!" - lore1 = "Nappaussäde" - lore = ["Nappaussäde"] +name = "Esinenappaus" +description = "Nappaa pudonneet esineet heti hiipimisen aikana!" +lore1 = "Nappaussäde" +lore = ["Nappaussäde"] [stealth.speed] - name = "Hiipimisnopeus" - description = "Saat nopeutta hiipimisen aikana" - lore1 = "Hiipimisnopeus" - lore = ["Hiipimisnopeus"] +name = "Hiipimisnopeus" +description = "Saat nopeutta hiipimisen aikana" +lore1 = "Hiipimisnopeus" +lore = ["Hiipimisnopeus"] [stealth.ender_veil] - name = "Enderverhoa" - description = "Ei enää kurpitsoja endermiesiskujen estämiseen" - lore1 = "Estä endermiesiskut hiipimisen aikana" - lore2 = "Estä kaikki endermiesiskut" - lore = ["Estä endermiesiskut hiipimisen aikana", "Estä kaikki endermiesiskut"] +name = "Enderverhoa" +description = "Ei enää kurpitsoja endermiesiskujen estämiseen" +lore1 = "Estä endermiesiskut hiipimisen aikana" +lore2 = "Estä kaikki endermiesiskut" +lore = ["Estä endermiesiskut hiipimisen aikana", "Estä kaikki endermiesiskut"] # sword [sword] [sword.machete] - name = "Viidakkoveitsi" - description = "Leikkaa kasvillisuuden läpi helposti!" - lore1 = "Viiltosäde" - lore2 = "Hakkuun jäähtymisaika" - lore3 = "Työkalun kuluminen" - lore = ["Viiltosäde", "Hakkuun jäähtymisaika", "Työkalun kuluminen"] +name = "Viidakkoveitsi" +description = "Leikkaa kasvillisuuden läpi helposti!" +lore1 = "Viiltosäde" +lore2 = "Hakkuun jäähtymisaika" +lore3 = "Työkalun kuluminen" +lore = ["Viiltosäde", "Hakkuun jäähtymisaika", "Työkalun kuluminen"] [sword.bloody_blade] - name = "Verinen terä" - description = "Iskut miekallasi aiheuttavat verenvuotoa!" - lore1 = "Elävän olennon lyöminen miekallasi aiheuttaa verenvuotoa" - lore2 = "Verenvuodon kesto" - lore3 = "Verenvuodon jäähtymisaika" - lore = ["Elävän olennon lyöminen miekallasi aiheuttaa verenvuotoa", "Verenvuodon kesto", "Verenvuodon jäähtymisaika"] +name = "Verinen terä" +description = "Iskut miekallasi aiheuttavat verenvuotoa!" +lore1 = "Elävän olennon lyöminen miekallasi aiheuttaa verenvuotoa" +lore2 = "Verenvuodon kesto" +lore3 = "Verenvuodon jäähtymisaika" +lore = ["Elävän olennon lyöminen miekallasi aiheuttaa verenvuotoa", "Verenvuodon kesto", "Verenvuodon jäähtymisaika"] [sword.poisoned_blade] - name = "Myrkytetty terä" - description = "Iskut miekallasi aiheuttavat myrkytyksen!" - lore1 = "Elävän olennon lyöminen miekallasi aiheuttaa myrkytyksen" - lore2 = "Myrkytyksen kesto" - lore3 = "Myrkytyksen jäähtymisaika" - lore = ["Elävän olennon lyöminen miekallasi aiheuttaa myrkytyksen", "Myrkytyksen kesto", "Myrkytyksen jäähtymisaika"] +name = "Myrkytetty terä" +description = "Iskut miekallasi aiheuttavat myrkytyksen!" +lore1 = "Elävän olennon lyöminen miekallasi aiheuttaa myrkytyksen" +lore2 = "Myrkytyksen kesto" +lore3 = "Myrkytyksen jäähtymisaika" +lore = ["Elävän olennon lyöminen miekallasi aiheuttaa myrkytyksen", "Myrkytyksen kesto", "Myrkytyksen jäähtymisaika"] # taming [taming] [taming.damage] - name = "Kesyn vahinko" - description = "Kasvata kesytettyjen eläintesi aiheuttamaa vahinkoa." - lore1 = "Kasvanut vahinko" - lore = ["Kasvanut vahinko"] +name = "Kesyn vahinko" +description = "Kasvata kesytettyjen eläintesi aiheuttamaa vahinkoa." +lore1 = "Kasvanut vahinko" +lore = ["Kasvanut vahinko"] [taming.health] - name = "Kesyn terveys" - description = "Kasvata kesytettyjen eläintesi terveyttä." - lore1 = "Kasvanut terveys" - lore = ["Kasvanut terveys"] +name = "Kesyn terveys" +description = "Kasvata kesytettyjen eläintesi terveyttä." +lore1 = "Kasvanut terveys" +lore = ["Kasvanut terveys"] [taming.regeneration] - name = "Kesyn uudistuminen" - description = "Kasvata kesytettyjen eläintesi uudistumista." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Kesyn uudistuminen" +description = "Kasvata kesytettyjen eläintesi uudistumista." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Piikit" - description = "Heijasta vahinko takaisin hyökkääjääsi!" - lore1 = "Vahinko palautettu iskusta" - lore = ["Vahinko palautettu iskusta"] +name = "Piikit" +description = "Heijasta vahinko takaisin hyökkääjääsi!" +lore1 = "Vahinko palautettu iskusta" +lore = ["Vahinko palautettu iskusta"] [tragoul.globe] - name = "Tuskien pallo" - description = "Jaa aiheuttamasi vahinko ympärilläsi olevien vihollisten määrän perusteella!" - lore1 = "Mitä enemmän vihollisia ympärilläsi, sitä vähemmän vahinkoa kukin saa" - lore2 = "Kantama: " - lore3 = "Lisävahinko kaikille olennoille: " - lore = ["Mitä enemmän vihollisia ympärilläsi, sitä vähemmän vahinkoa kukin saa", "Kantama: ", "Lisävahinko kaikille olennoille: "] +name = "Tuskien pallo" +description = "Jaa aiheuttamasi vahinko ympärilläsi olevien vihollisten määrän perusteella!" +lore1 = "Mitä enemmän vihollisia ympärilläsi, sitä vähemmän vahinkoa kukin saa" +lore2 = "Kantama: " +lore3 = "Lisävahinko kaikille olennoille: " +lore = ["Mitä enemmän vihollisia ympärilläsi, sitä vähemmän vahinkoa kukin saa", "Kantama: ", "Lisävahinko kaikille olennoille: "] [tragoul.healing] - name = "Tuskan tahto" - description = "Saat terveyttä takaisin aiheuttamasi vahingon perusteella!" - lore1 = "Vahingoittaminen ei ole koskaan tuntunut näin hyvältä! Parannu aiheuttamastasi vahingosta" - lore2 = "3 sekunnin vahinkoikkuna parantumiselle ja 1 sekunnin jäähtymisaika " - lore3 = "Parantuminen per vahinkoprosentti: " - lore = ["Vahingoittaminen ei ole koskaan tuntunut näin hyvältä! Parannu aiheuttamastasi vahingosta", "3 sekunnin vahinkoikkuna parantumiselle ja 1 sekunnin jäähtymisaika ", "Parantuminen per vahinkoprosentti: "] +name = "Tuskan tahto" +description = "Saat terveyttä takaisin aiheuttamasi vahingon perusteella!" +lore1 = "Vahingoittaminen ei ole koskaan tuntunut näin hyvältä! Parannu aiheuttamastasi vahingosta" +lore2 = "3 sekunnin vahinkoikkuna parantumiselle ja 1 sekunnin jäähtymisaika " +lore3 = "Parantuminen per vahinkoprosentti: " +lore = ["Vahingoittaminen ei ole koskaan tuntunut näin hyvältä! Parannu aiheuttamastasi vahingosta", "3 sekunnin vahinkoikkuna parantumiselle ja 1 sekunnin jäähtymisaika ", "Parantuminen per vahinkoprosentti: "] [tragoul.lance] - name = "Ruumiskeihäät" - description = "Vihollisen tappaminen tai kyvyn tappaessa vihollisen synnyttää keihään, joka vahingoittaa läheistä vihollista!" - lore1 = "Keihäät etsivät kohteen kaikesta tappamastasi, JA jos tämä kyky tappaa vihollisen." - lore2 = "Uhraa osa elämästäsi keihäiden luomiseen (tämä voi tappaa sinut)" - lore3 = "Maksimikeihäät: 1 + " - lore = ["Keihäät etsivät kohteen kaikesta tappamastasi, JA jos tämä kyky tappaa vihollisen.", "Uhraa osa elämästäsi keihäiden luomiseen (tämä voi tappaa sinut)", "Maksimikeihäät: 1 + "] +name = "Ruumiskeihäät" +description = "Vihollisen tappaminen tai kyvyn tappaessa vihollisen synnyttää keihään, joka vahingoittaa läheistä vihollista!" +lore1 = "Keihäät etsivät kohteen kaikesta tappamastasi, JA jos tämä kyky tappaa vihollisen." +lore2 = "Uhraa osa elämästäsi keihäiden luomiseen (tämä voi tappaa sinut)" +lore3 = "Maksimikeihäät: 1 + " +lore = ["Keihäät etsivät kohteen kaikesta tappamastasi, JA jos tämä kyky tappaa vihollisen.", "Uhraa osa elämästäsi keihäiden luomiseen (tämä voi tappaa sinut)", "Maksimikeihäät: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Lasitykki" - description = "Bonusvahinko aseettomana mitä pienempi panssariarvosi on" - lore1 = "x vahinko 0 panssarilla" - lore2 = "Tasokohtainen bonusvahinko" - lore = ["x vahinko 0 panssarilla", "Tasokohtainen bonusvahinko"] +name = "Lasitykki" +description = "Bonusvahinko aseettomana mitä pienempi panssariarvosi on" +lore1 = "x vahinko 0 panssarilla" +lore2 = "Tasokohtainen bonusvahinko" +lore = ["x vahinko 0 panssarilla", "Tasokohtainen bonusvahinko"] [unarmed.power] - name = "Aseettoman voima" - description = "Parannettu aseeton vahinko" - lore1 = "Vahinko" - lore = ["Vahinko"] +name = "Aseettoman voima" +description = "Parannettu aseeton vahinko" +lore1 = "Vahinko" +lore = ["Vahinko"] [unarmed.sucker_punch] - name = "Yllätysisku" - description = "Juoksulyönnit, mutta tappavampia." - lore1 = "Vahinko" - lore2 = "Vahinko kasvaa nopeuden mukaan lyönnin aikana" - lore = ["Vahinko", "Vahinko kasvaa nopeuden mukaan lyönnin aikana"] +name = "Yllätysisku" +description = "Juoksulyönnit, mutta tappavampia." +lore1 = "Vahinko" +lore2 = "Vahinko kasvaa nopeuden mukaan lyönnin aikana" +lore = ["Vahinko", "Vahinko kasvaa nopeuden mukaan lyönnin aikana"] diff --git a/src/main/resources/fr_FR.toml b/src/main/resources/fr_FR.toml index 9d2d66658..79968889c 100644 --- a/src/main/resources/fr_FR.toml +++ b/src/main/resources/fr_FR.toml @@ -2,2060 +2,2060 @@ [advancement] [advancement.challenge_move_1k] - title = "Faut bouger !" - description = "Marchez plus d'1 kilometre (1 000 blocs)" +title = "Faut bouger !" +description = "Marchez plus d'1 kilometre (1 000 blocs)" [advancement.challenge_sprint_5k] - title = "Sprintez un 5K !" - description = "Marchez plus de 5 kilometres (5 000 blocs)" +title = "Sprintez un 5K !" +description = "Marchez plus de 5 kilometres (5 000 blocs)" [advancement.challenge_sprint_50k] - title = "Foncez un 50K !" - description = "Marchez plus de 50 kilometres (50 000 blocs)" +title = "Foncez un 50K !" +description = "Marchez plus de 50 kilometres (50 000 blocs)" [advancement.challenge_sprint_500k] - title = "Traversez l'Univers !!" - description = "Marchez plus de 500 kilometres (500 000 blocs)" +title = "Traversez l'Univers !!" +description = "Marchez plus de 500 kilometres (500 000 blocs)" [advancement.challenge_sprint_marathon] - title = "Sprintez un marathon (au sens propre) !" - description = "Sprintez plus de 42 195 blocs !" +title = "Sprintez un marathon (au sens propre) !" +description = "Sprintez plus de 42 195 blocs !" [advancement.challenge_place_1k] - title = "Constructeur novice !" - description = "Placez 1 000 blocs" +title = "Constructeur novice !" +description = "Placez 1 000 blocs" [advancement.challenge_place_5k] - title = "Constructeur intermediaire !" - description = "Placez 5 000 blocs" +title = "Constructeur intermediaire !" +description = "Placez 5 000 blocs" [advancement.challenge_place_50k] - title = "Constructeur avance !" - description = "Placez 50 000 blocs" +title = "Constructeur avance !" +description = "Placez 50 000 blocs" [advancement.challenge_place_500k] - title = "Maitre batisseur !" - description = "Placez 500 000 blocs" +title = "Maitre batisseur !" +description = "Placez 500 000 blocs" [advancement.challenge_place_5m] - title = "Acolyte de la Symetrie !" - description = "LA REALITE EST VOTRE TERRAIN DE JEU ! (5 millions de blocs)" +title = "Acolyte de la Symetrie !" +description = "LA REALITE EST VOTRE TERRAIN DE JEU ! (5 millions de blocs)" [advancement.challenge_chop_1k] - title = "Bucheron novice !" - description = "Coupez 1 000 blocs" +title = "Bucheron novice !" +description = "Coupez 1 000 blocs" [advancement.challenge_chop_5k] - title = "Bucheron intermediaire !" - description = "Coupez 5 000 blocs" +title = "Bucheron intermediaire !" +description = "Coupez 5 000 blocs" [advancement.challenge_chop_50k] - title = "Bucheron avance !" - description = "Coupez 50 000 blocs" +title = "Bucheron avance !" +description = "Coupez 50 000 blocs" [advancement.challenge_chop_500k] - title = "Maitre bucheron !" - description = "Coupez 500 000 blocs" +title = "Maitre bucheron !" +description = "Coupez 500 000 blocs" [advancement.challenge_chop_5m] - title = "Jackson le chien" - description = "Le meilleur toutou ! (5 millions de blocs)" +title = "Jackson le chien" +description = "Le meilleur toutou ! (5 millions de blocs)" [advancement.challenge_block_1k] - title = "A peine un blocage !" - description = "Bloquez 1 000 coups" +title = "A peine un blocage !" +description = "Bloquez 1 000 coups" [advancement.challenge_block_5k] - title = "Bloquer c'est amusant !" - description = "Bloquez 5 000 coups" +title = "Bloquer c'est amusant !" +description = "Bloquez 5 000 coups" [advancement.challenge_block_50k] - title = "Bloquer c'est ma vie !" - description = "Bloquez 50 000 coups" +title = "Bloquer c'est ma vie !" +description = "Bloquez 50 000 coups" [advancement.challenge_block_500k] - title = "Bloquer c'est ma raison d'etre !" - description = "Bloquez 500 000 coups" +title = "Bloquer c'est ma raison d'etre !" +description = "Bloquez 500 000 coups" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Bloquez 5 000 000 de coups" +title = "Die Hand Die Verletzt" +description = "Bloquez 5 000 000 de coups" [advancement.challenge_brew_1k] - title = "Alchimiste novice !" - description = "Consommez 1 000 potions" +title = "Alchimiste novice !" +description = "Consommez 1 000 potions" [advancement.challenge_brew_5k] - title = "Alchimiste intermediaire !" - description = "Consommez 5 000 potions" +title = "Alchimiste intermediaire !" +description = "Consommez 5 000 potions" [advancement.challenge_brew_50k] - title = "Alchimiste avance !" - description = "Consommez 50 000 potions" +title = "Alchimiste avance !" +description = "Consommez 50 000 potions" [advancement.challenge_brew_500k] - title = "Maitre alchimiste !" - description = "Consommez 500 000 potions" +title = "Maitre alchimiste !" +description = "Consommez 500 000 potions" [advancement.challenge_brew_5m] - title = "L'Alchimiste" - description = "Consommez 5 000 000 de potions" +title = "L'Alchimiste" +description = "Consommez 5 000 000 de potions" [advancement.challenge_brewsplash_1k] - title = "Lanceur de potions novice !" - description = "Lancez 1 000 potions a eclaboussures" +title = "Lanceur de potions novice !" +description = "Lancez 1 000 potions a eclaboussures" [advancement.challenge_brewsplash_5k] - title = "Lanceur de potions intermediaire !" - description = "Lancez 5 000 potions a eclaboussures" +title = "Lanceur de potions intermediaire !" +description = "Lancez 5 000 potions a eclaboussures" [advancement.challenge_brewsplash_50k] - title = "Lanceur de potions avance !" - description = "Lancez 50 000 potions a eclaboussures" +title = "Lanceur de potions avance !" +description = "Lancez 50 000 potions a eclaboussures" [advancement.challenge_brewsplash_500k] - title = "Maitre lanceur de potions !" - description = "Lancez 500 000 potions a eclaboussures" +title = "Maitre lanceur de potions !" +description = "Lancez 500 000 potions a eclaboussures" [advancement.challenge_brewsplash_5m] - title = "Le Maitre des eclaboussures" - description = "Lancez 5 000 000 de potions a eclaboussures" +title = "Le Maitre des eclaboussures" +description = "Lancez 5 000 000 de potions a eclaboussures" [advancement.challenge_craft_1k] - title = "Artisan astucieux !" - description = "Fabriquez 1 000 objets" +title = "Artisan astucieux !" +description = "Fabriquez 1 000 objets" [advancement.challenge_craft_5k] - title = "Artisan acariatre !" - description = "Fabriquez 5 000 objets" +title = "Artisan acariatre !" +description = "Fabriquez 5 000 objets" [advancement.challenge_craft_50k] - title = "Artisan servile !" - description = "Fabriquez 50 000 objets" +title = "Artisan servile !" +description = "Fabriquez 50 000 objets" [advancement.challenge_craft_500k] - title = "Artisan cacophonique !" - description = "Fabriquez 500 000 objets" +title = "Artisan cacophonique !" +description = "Fabriquez 500 000 objets" [advancement.challenge_craft_5m] - title = "McCraftface calamiteux" - description = "Fabriquez 5 000 000 d'objets" +title = "McCraftface calamiteux" +description = "Fabriquez 5 000 000 d'objets" [advancement.challenge_enchant_1k] - title = "Enchanteur novice !" - description = "Enchantez 1 000 objets" +title = "Enchanteur novice !" +description = "Enchantez 1 000 objets" [advancement.challenge_enchant_5k] - title = "Enchanteur intermediaire !" - description = "Enchantez 5 000 objets" +title = "Enchanteur intermediaire !" +description = "Enchantez 5 000 objets" [advancement.challenge_enchant_50k] - title = "Enchanteur avance !" - description = "Enchantez 50 000 objets" +title = "Enchanteur avance !" +description = "Enchantez 50 000 objets" [advancement.challenge_enchant_500k] - title = "Maitre enchanteur !" - description = "Enchantez 500 000 objets" +title = "Maitre enchanteur !" +description = "Enchantez 500 000 objets" [advancement.challenge_enchant_5m] - title = "Enchanteur enigmatique" - description = "Enchantez 5 000 000 d'objets" +title = "Enchanteur enigmatique" +description = "Enchantez 5 000 000 d'objets" [advancement.challenge_excavate_1k] - title = "Excavateur enthousiaste !" - description = "Excavez 1 000 blocs" +title = "Excavateur enthousiaste !" +description = "Excavez 1 000 blocs" [advancement.challenge_excavate_5k] - title = "Excavateur intermediaire !" - description = "Excavez 5 000 blocs" +title = "Excavateur intermediaire !" +description = "Excavez 5 000 blocs" [advancement.challenge_excavate_50k] - title = "Excavateur avance !" - description = "Excavez 50 000 blocs" +title = "Excavateur avance !" +description = "Excavez 50 000 blocs" [advancement.challenge_excavate_500k] - title = "Maitre excavateur !" - description = "Excavez 500 000 blocs" +title = "Maitre excavateur !" +description = "Excavez 500 000 blocs" [advancement.challenge_excavate_5m] - title = "Excavateur enigmatique" - description = "Excavez 5 000 000 de blocs" +title = "Excavateur enigmatique" +description = "Excavez 5 000 000 de blocs" [advancement.horrible_person] - title = "Vous etes une personne horrible" - description = "Insondable, vraiment" +title = "Vous etes une personne horrible" +description = "Insondable, vraiment" [advancement.challenge_turtle_egg_smasher] - title = "Briseur d'oeufs de tortue !" - description = "Cassez 100 oeufs de tortue" +title = "Briseur d'oeufs de tortue !" +description = "Cassez 100 oeufs de tortue" [advancement.challenge_turtle_egg_annihilator] - title = "Annihilateur d'oeufs de tortue !" - description = "Cassez 500 oeufs de tortue" +title = "Annihilateur d'oeufs de tortue !" +description = "Cassez 500 oeufs de tortue" [advancement.challenge_novice_hunter] - title = "Chasseur novice !" - description = "Tuez 100 entites" +title = "Chasseur novice !" +description = "Tuez 100 entites" [advancement.challenge_intermediate_hunter] - title = "Chasseur intermediaire !" - description = "Tuez 500 entites" +title = "Chasseur intermediaire !" +description = "Tuez 500 entites" [advancement.challenge_advanced_hunter] - title = "Chasseur avance !" - description = "Tuez 5 000 entites" +title = "Chasseur avance !" +description = "Tuez 5 000 entites" [advancement.challenge_creeper_conqueror] - title = "Conquerant de Creepers !" - description = "Tuez 50 Creepers" +title = "Conquerant de Creepers !" +description = "Tuez 50 Creepers" [advancement.challenge_creeper_annihilator] - title = "Annihilateur de Creepers !" - description = "Tuez 200 Creepers" +title = "Annihilateur de Creepers !" +description = "Tuez 200 Creepers" [advancement.challenge_pickaxe_1k] - title = "Mineur novice" - description = "Cassez 1 000 blocs" +title = "Mineur novice" +description = "Cassez 1 000 blocs" [advancement.challenge_pickaxe_5k] - title = "Mineur qualifie" - description = "Cassez 5 000 blocs" +title = "Mineur qualifie" +description = "Cassez 5 000 blocs" [advancement.challenge_pickaxe_50k] - title = "Mineur expert" - description = "Cassez 50 000 blocs" +title = "Mineur expert" +description = "Cassez 50 000 blocs" [advancement.challenge_pickaxe_500k] - title = "Maitre mineur" - description = "Cassez 500 000 blocs" +title = "Maitre mineur" +description = "Cassez 500 000 blocs" [advancement.challenge_pickaxe_5m] - title = "Mineur legendaire" - description = "Cassez 5 000 000 de blocs" +title = "Mineur legendaire" +description = "Cassez 5 000 000 de blocs" [advancement.challenge_eat_100] - title = "Tant a manger !" - description = "Mangez plus de 100 objets !" +title = "Tant a manger !" +description = "Mangez plus de 100 objets !" [advancement.challenge_eat_1000] - title = "Faim insatiable !" - description = "Mangez plus de 1 000 objets !" +title = "Faim insatiable !" +description = "Mangez plus de 1 000 objets !" [advancement.challenge_eat_10000] - title = "FAIM ETERNELLE !" - description = "Mangez plus de 10 000 objets !" +title = "FAIM ETERNELLE !" +description = "Mangez plus de 10 000 objets !" [advancement.challenge_harvest_100] - title = "Pleine recolte" - description = "Recoltez plus de 100 cultures !" +title = "Pleine recolte" +description = "Recoltez plus de 100 cultures !" [advancement.challenge_harvest_1000] - title = "Grande recolte" - description = "Recoltez plus de 1 000 cultures !" +title = "Grande recolte" +description = "Recoltez plus de 1 000 cultures !" [advancement.challenge_swim_1nm] - title = "Sous-marin humain !" - description = "Nagez 1 mille nautique (1 852 blocs)" +title = "Sous-marin humain !" +description = "Nagez 1 mille nautique (1 852 blocs)" [advancement.challenge_sneak_1k] - title = "Mal aux genoux" - description = "Accroupissez-vous sur plus d'un kilometre (1 000 blocs)" +title = "Mal aux genoux" +description = "Accroupissez-vous sur plus d'un kilometre (1 000 blocs)" [advancement.challenge_sneak_5k] - title = "Marcheur de l'Ombre" - description = "Accroupissez-vous sur plus de 5 000 blocs" +title = "Marcheur de l'Ombre" +description = "Accroupissez-vous sur plus de 5 000 blocs" [advancement.challenge_sneak_20k] - title = "Fantome" - description = "Accroupissez-vous sur plus de 20 000 blocs" +title = "Fantome" +description = "Accroupissez-vous sur plus de 20 000 blocs" [advancement.challenge_swim_5k] - title = "Plongeur des Abysses" - description = "Nagez sur plus de 5 000 blocs" +title = "Plongeur des Abysses" +description = "Nagez sur plus de 5 000 blocs" [advancement.challenge_swim_20k] - title = "Elu de Poseidon" - description = "Nagez sur plus de 20 000 blocs" +title = "Elu de Poseidon" +description = "Nagez sur plus de 20 000 blocs" [advancement.challenge_sword_100] - title = "Premier Sang" - description = "Infligez 100 coups avec une epee" +title = "Premier Sang" +description = "Infligez 100 coups avec une epee" [advancement.challenge_sword_1k] - title = "Danseur de Lames" - description = "Infligez 1 000 coups avec une epee" +title = "Danseur de Lames" +description = "Infligez 1 000 coups avec une epee" [advancement.challenge_sword_10k] - title = "Mille Entailles" - description = "Infligez 10 000 coups avec une epee" +title = "Mille Entailles" +description = "Infligez 10 000 coups avec une epee" [advancement.challenge_unarmed_100] - title = "Bagarreur" - description = "Infligez 100 coups a mains nues" +title = "Bagarreur" +description = "Infligez 100 coups a mains nues" [advancement.challenge_unarmed_1k] - title = "Poings de Fer" - description = "Infligez 1 000 coups a mains nues" +title = "Poings de Fer" +description = "Infligez 1 000 coups a mains nues" [advancement.challenge_unarmed_10k] - title = "One Punch" - description = "Infligez 10 000 coups a mains nues" +title = "One Punch" +description = "Infligez 10 000 coups a mains nues" [advancement.challenge_trag_1k] - title = "Prix du Sang" - description = "Subissez 1 000 degats" +title = "Prix du Sang" +description = "Subissez 1 000 degats" [advancement.challenge_trag_10k] - title = "Maree Pourpre" - description = "Subissez 10 000 degats" +title = "Maree Pourpre" +description = "Subissez 10 000 degats" [advancement.challenge_trag_100k] - title = "Avatar de la Souffrance" - description = "Subissez 100 000 degats" +title = "Avatar de la Souffrance" +description = "Subissez 100 000 degats" [advancement.challenge_ranged_100] - title = "Exercice de Tir" - description = "Tirez 100 projectiles" +title = "Exercice de Tir" +description = "Tirez 100 projectiles" [advancement.challenge_ranged_1k] - title = "Oeil de Faucon" - description = "Tirez 1 000 projectiles" +title = "Oeil de Faucon" +description = "Tirez 1 000 projectiles" [advancement.challenge_ranged_10k] - title = "Tempete de Fleches" - description = "Tirez 10 000 projectiles" +title = "Tempete de Fleches" +description = "Tirez 10 000 projectiles" [advancement.challenge_chronos_1h] - title = "Tic Tac" - description = "Passez 1 heure en ligne" +title = "Tic Tac" +description = "Passez 1 heure en ligne" [advancement.challenge_chronos_24h] - title = "Sables du Temps" - description = "Passez 24 heures en ligne" +title = "Sables du Temps" +description = "Passez 24 heures en ligne" [advancement.challenge_chronos_168h] - title = "Intemporel" - description = "Passez 168 heures (1 semaine) en ligne" +title = "Intemporel" +description = "Passez 168 heures (1 semaine) en ligne" [advancement.challenge_nether_50] - title = "Gardien de l'Enfer" - description = "Tuez 50 creatures du Nether" +title = "Gardien de l'Enfer" +description = "Tuez 50 creatures du Nether" [advancement.challenge_nether_500] - title = "Gardien de l'Abime" - description = "Tuez 500 creatures du Nether" +title = "Gardien de l'Abime" +description = "Tuez 500 creatures du Nether" [advancement.challenge_nether_5k] - title = "Seigneur du Nether" - description = "Tuez 5 000 creatures du Nether" +title = "Seigneur du Nether" +description = "Tuez 5 000 creatures du Nether" [advancement.challenge_rift_50] - title = "Anomalie Spatiale" - description = "Teleportez-vous 50 fois" +title = "Anomalie Spatiale" +description = "Teleportez-vous 50 fois" [advancement.challenge_rift_500] - title = "Marcheur du Vide" - description = "Teleportez-vous 500 fois" +title = "Marcheur du Vide" +description = "Teleportez-vous 500 fois" [advancement.challenge_rift_5k] - title = "Entre les Mondes" - description = "Teleportez-vous 5 000 fois" +title = "Entre les Mondes" +description = "Teleportez-vous 5 000 fois" [advancement.challenge_taming_10] - title = "Murmureur d'Animaux" - description = "Elevez 10 animaux" +title = "Murmureur d'Animaux" +description = "Elevez 10 animaux" [advancement.challenge_taming_50] - title = "Chef de Meute" - description = "Elevez 50 animaux" +title = "Chef de Meute" +description = "Elevez 50 animaux" [advancement.challenge_taming_500] - title = "Maitre des Betes" - description = "Elevez 500 animaux" +title = "Maitre des Betes" +description = "Elevez 500 animaux" # Agility [advancement.challenge_sprint_dist_5k] - title = "Demon de la Vitesse" - description = "Sprintez sur plus de 5 Kilometres (5.000 blocs)" +title = "Demon de la Vitesse" +description = "Sprintez sur plus de 5 Kilometres (5.000 blocs)" [advancement.challenge_sprint_dist_50k] - title = "Jambes de Foudre" - description = "Sprintez sur plus de 50 Kilometres (50.000 blocs)" +title = "Jambes de Foudre" +description = "Sprintez sur plus de 50 Kilometres (50.000 blocs)" [advancement.challenge_agility_swim_1k] - title = "Marcheur des Eaux" - description = "Nagez sur plus de 1 Kilometre (1.000 blocs)" +title = "Marcheur des Eaux" +description = "Nagez sur plus de 1 Kilometre (1.000 blocs)" [advancement.challenge_agility_swim_10k] - title = "Voyageur Aquatique" - description = "Nagez sur plus de 10 Kilometres (10.000 blocs)" +title = "Voyageur Aquatique" +description = "Nagez sur plus de 10 Kilometres (10.000 blocs)" [advancement.challenge_fly_1k] - title = "Danseur du Ciel" - description = "Volez sur plus de 1 Kilometre (1.000 blocs)" +title = "Danseur du Ciel" +description = "Volez sur plus de 1 Kilometre (1.000 blocs)" [advancement.challenge_fly_10k] - title = "Cavalier du Vent" - description = "Volez sur plus de 10 Kilometres (10.000 blocs)" +title = "Cavalier du Vent" +description = "Volez sur plus de 10 Kilometres (10.000 blocs)" [advancement.challenge_agility_sneak_500] - title = "Pas Discrets" - description = "Deplacez-vous furtivement sur plus de 500 blocs" +title = "Pas Discrets" +description = "Deplacez-vous furtivement sur plus de 500 blocs" [advancement.challenge_agility_sneak_5k] - title = "Pas Fantomes" - description = "Deplacez-vous furtivement sur plus de 5 Kilometres (5.000 blocs)" +title = "Pas Fantomes" +description = "Deplacez-vous furtivement sur plus de 5 Kilometres (5.000 blocs)" # Architect [advancement.challenge_demolish_500] - title = "Equipe de Demolition" - description = "Cassez 500 blocs" +title = "Equipe de Demolition" +description = "Cassez 500 blocs" [advancement.challenge_demolish_5k] - title = "Boule de Demolition" - description = "Cassez 5.000 blocs" +title = "Boule de Demolition" +description = "Cassez 5.000 blocs" [advancement.challenge_value_placed_10k] - title = "Batisseur de Valeur" - description = "Placez des blocs d'une valeur de 10.000" +title = "Batisseur de Valeur" +description = "Placez des blocs d'une valeur de 10.000" [advancement.challenge_value_placed_100k] - title = "Maitre Architecte" - description = "Placez des blocs d'une valeur de 100.000" +title = "Maitre Architecte" +description = "Placez des blocs d'une valeur de 100.000" [advancement.challenge_demolish_val_5k] - title = "Expert en Recuperation" - description = "Recuperez 5.000 de valeur de blocs par demolition" +title = "Expert en Recuperation" +description = "Recuperez 5.000 de valeur de blocs par demolition" [advancement.challenge_demolish_val_50k] - title = "Deconstruction Totale" - description = "Recuperez 50.000 de valeur de blocs par demolition" +title = "Deconstruction Totale" +description = "Recuperez 50.000 de valeur de blocs par demolition" [advancement.challenge_high_build_100] - title = "Batisseur Celeste" - description = "Placez 100 blocs au-dessus de Y=128" +title = "Batisseur Celeste" +description = "Placez 100 blocs au-dessus de Y=128" [advancement.challenge_high_build_1k] - title = "Architecte des Nuages" - description = "Placez 1.000 blocs au-dessus de Y=128" +title = "Architecte des Nuages" +description = "Placez 1.000 blocs au-dessus de Y=128" # Axes [advancement.challenge_axe_swing_500] - title = "Bucheron" - description = "Balancez votre hache 500 fois" +title = "Bucheron" +description = "Balancez votre hache 500 fois" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Balancez votre hache 5.000 fois" +title = "Berserker" +description = "Balancez votre hache 5.000 fois" [advancement.challenge_axe_damage_1k] - title = "Fendeur" - description = "Infligez 1.000 degats avec des haches" +title = "Fendeur" +description = "Infligez 1.000 degats avec des haches" [advancement.challenge_axe_damage_10k] - title = "Hache du Bourreau" - description = "Infligez 10.000 degats avec des haches" +title = "Hache du Bourreau" +description = "Infligez 10.000 degats avec des haches" [advancement.challenge_axe_value_5k] - title = "Marchand de Bois" - description = "Recoltez du bois d'une valeur de 5.000" +title = "Marchand de Bois" +description = "Recoltez du bois d'une valeur de 5.000" [advancement.challenge_axe_value_50k] - title = "Baron du Bois" - description = "Recoltez du bois d'une valeur de 50.000" +title = "Baron du Bois" +description = "Recoltez du bois d'une valeur de 50.000" [advancement.challenge_leaves_500] - title = "Souffleur de Feuilles" - description = "Eliminez 500 blocs de feuilles avec une hache" +title = "Souffleur de Feuilles" +description = "Eliminez 500 blocs de feuilles avec une hache" [advancement.challenge_leaves_5k] - title = "Defoliateur" - description = "Eliminez 5.000 blocs de feuilles avec une hache" +title = "Defoliateur" +description = "Eliminez 5.000 blocs de feuilles avec une hache" # Blocking [advancement.challenge_block_dmg_1k] - title = "Absorbeur de Degats" - description = "Bloquez 1.000 degats avec un bouclier" +title = "Absorbeur de Degats" +description = "Bloquez 1.000 degats avec un bouclier" [advancement.challenge_block_dmg_10k] - title = "Bouclier Humain" - description = "Bloquez 10.000 degats avec un bouclier" +title = "Bouclier Humain" +description = "Bloquez 10.000 degats avec un bouclier" [advancement.challenge_block_proj_100] - title = "Deflecteur de Fleches" - description = "Bloquez 100 projectiles avec un bouclier" +title = "Deflecteur de Fleches" +description = "Bloquez 100 projectiles avec un bouclier" [advancement.challenge_block_proj_1k] - title = "Bouclier Anti-Projectiles" - description = "Bloquez 1.000 projectiles avec un bouclier" +title = "Bouclier Anti-Projectiles" +description = "Bloquez 1.000 projectiles avec un bouclier" [advancement.challenge_block_melee_500] - title = "Maitre de la Parade" - description = "Bloquez 500 attaques de melee avec un bouclier" +title = "Maitre de la Parade" +description = "Bloquez 500 attaques de melee avec un bouclier" [advancement.challenge_block_melee_5k] - title = "Forteresse de Fer" - description = "Bloquez 5.000 attaques de melee avec un bouclier" +title = "Forteresse de Fer" +description = "Bloquez 5.000 attaques de melee avec un bouclier" [advancement.challenge_block_heavy_50] - title = "Tank" - description = "Bloquez 50 attaques lourdes (plus de 5 degats)" +title = "Tank" +description = "Bloquez 50 attaques lourdes (plus de 5 degats)" [advancement.challenge_block_heavy_500] - title = "Objet Immuable" - description = "Bloquez 500 attaques lourdes (plus de 5 degats)" +title = "Objet Immuable" +description = "Bloquez 500 attaques lourdes (plus de 5 degats)" # Brewing [advancement.challenge_brew_stands_10] - title = "Installation du Brasseur" - description = "Placez 10 alambics" +title = "Installation du Brasseur" +description = "Placez 10 alambics" [advancement.challenge_brew_stands_50] - title = "Fabrique de Potions" - description = "Placez 50 alambics" +title = "Fabrique de Potions" +description = "Placez 50 alambics" [advancement.challenge_brew_strong_25] - title = "Breuvage Puissant" - description = "Consommez 25 potions ameliorees" +title = "Breuvage Puissant" +description = "Consommez 25 potions ameliorees" [advancement.challenge_brew_strong_250] - title = "Puissance Maximale" - description = "Consommez 250 potions ameliorees" +title = "Puissance Maximale" +description = "Consommez 250 potions ameliorees" [advancement.challenge_brew_splash_hits_50] - title = "Zone d'Eclaboussure" - description = "Touchez 50 entites avec des potions jetables" +title = "Zone d'Eclaboussure" +description = "Touchez 50 entites avec des potions jetables" [advancement.challenge_brew_splash_hits_500] - title = "Medecin de Peste" - description = "Touchez 500 entites avec des potions jetables" +title = "Medecin de Peste" +description = "Touchez 500 entites avec des potions jetables" # Chronos [advancement.challenge_active_dist_1k] - title = "Agite" - description = "Parcourez 1 Kilometre en etant actif" +title = "Agite" +description = "Parcourez 1 Kilometre en etant actif" [advancement.challenge_active_dist_10k] - title = "Eclaireur" - description = "Parcourez 10 Kilometres en etant actif" +title = "Eclaireur" +description = "Parcourez 10 Kilometres en etant actif" [advancement.challenge_active_dist_100k] - title = "Mouvement Perpetuel" - description = "Parcourez 100 Kilometres en etant actif" +title = "Mouvement Perpetuel" +description = "Parcourez 100 Kilometres en etant actif" [advancement.challenge_beds_10] - title = "Leve-tot" - description = "Dormez dans un lit 10 fois" +title = "Leve-tot" +description = "Dormez dans un lit 10 fois" [advancement.challenge_beds_100] - title = "Sauteur de Temps" - description = "Dormez dans un lit 100 fois" +title = "Sauteur de Temps" +description = "Dormez dans un lit 100 fois" [advancement.challenge_chronos_tp_50] - title = "Decalage Temporel" - description = "Teleportez-vous 50 fois" +title = "Decalage Temporel" +description = "Teleportez-vous 50 fois" [advancement.challenge_chronos_tp_500] - title = "Distorsion Temporelle" - description = "Teleportez-vous 500 fois" +title = "Distorsion Temporelle" +description = "Teleportez-vous 500 fois" [advancement.challenge_chronos_deaths_10] - title = "Mortel" - description = "Mourez 10 fois" +title = "Mortel" +description = "Mourez 10 fois" [advancement.challenge_chronos_deaths_100] - title = "Defieur de la Mort" - description = "Mourez 100 fois" +title = "Defieur de la Mort" +description = "Mourez 100 fois" # Crafting [advancement.challenge_craft_value_10k] - title = "Valeur Artisanale" - description = "Fabriquez des objets d'une valeur totale de 10.000" +title = "Valeur Artisanale" +description = "Fabriquez des objets d'une valeur totale de 10.000" [advancement.challenge_craft_value_100k] - title = "Artisan" - description = "Fabriquez des objets d'une valeur totale de 100.000" +title = "Artisan" +description = "Fabriquez des objets d'une valeur totale de 100.000" [advancement.challenge_craft_tools_25] - title = "Forgeur d'Outils" - description = "Fabriquez 25 outils" +title = "Forgeur d'Outils" +description = "Fabriquez 25 outils" [advancement.challenge_craft_tools_250] - title = "Maitre Forgeron" - description = "Fabriquez 250 outils" +title = "Maitre Forgeron" +description = "Fabriquez 250 outils" [advancement.challenge_craft_armor_25] - title = "Forgeur d'Armures" - description = "Fabriquez 25 pieces d'armure" +title = "Forgeur d'Armures" +description = "Fabriquez 25 pieces d'armure" [advancement.challenge_craft_armor_250] - title = "Maitre Armurier" - description = "Fabriquez 250 pieces d'armure" +title = "Maitre Armurier" +description = "Fabriquez 250 pieces d'armure" [advancement.challenge_craft_mass_25k] - title = "Producteur en Masse" - description = "Fabriquez 25.000 objets" +title = "Producteur en Masse" +description = "Fabriquez 25.000 objets" [advancement.challenge_craft_mass_250k] - title = "Revolution Industrielle" - description = "Fabriquez 250.000 objets" +title = "Revolution Industrielle" +description = "Fabriquez 250.000 objets" # Discovery [advancement.challenge_discover_items_50] - title = "Collectionneur" - description = "Decouvrez 50 objets uniques" +title = "Collectionneur" +description = "Decouvrez 50 objets uniques" [advancement.challenge_discover_items_250] - title = "Catalogueur" - description = "Decouvrez 250 objets uniques" +title = "Catalogueur" +description = "Decouvrez 250 objets uniques" [advancement.challenge_discover_blocks_50] - title = "Arpenteur" - description = "Decouvrez 50 blocs uniques" +title = "Arpenteur" +description = "Decouvrez 50 blocs uniques" [advancement.challenge_discover_blocks_250] - title = "Geologue" - description = "Decouvrez 250 blocs uniques" +title = "Geologue" +description = "Decouvrez 250 blocs uniques" [advancement.challenge_discover_mobs_25] - title = "Observateur" - description = "Decouvrez 25 creatures uniques" +title = "Observateur" +description = "Decouvrez 25 creatures uniques" [advancement.challenge_discover_mobs_75] - title = "Naturaliste" - description = "Decouvrez 75 creatures uniques" +title = "Naturaliste" +description = "Decouvrez 75 creatures uniques" [advancement.challenge_discover_biomes_10] - title = "Vagabond" - description = "Decouvrez 10 biomes uniques" +title = "Vagabond" +description = "Decouvrez 10 biomes uniques" [advancement.challenge_discover_biomes_40] - title = "Globe-Trotteur" - description = "Decouvrez 40 biomes uniques" +title = "Globe-Trotteur" +description = "Decouvrez 40 biomes uniques" [advancement.challenge_discover_foods_10] - title = "Gourmet" - description = "Decouvrez 10 aliments uniques" +title = "Gourmet" +description = "Decouvrez 10 aliments uniques" [advancement.challenge_discover_foods_30] - title = "Maitre Culinaire" - description = "Decouvrez 30 aliments uniques" +title = "Maitre Culinaire" +description = "Decouvrez 30 aliments uniques" # Enchanting [advancement.challenge_enchant_power_100] - title = "Tisseur de Pouvoir" - description = "Accumulez 100 de puissance d'enchantement" +title = "Tisseur de Pouvoir" +description = "Accumulez 100 de puissance d'enchantement" [advancement.challenge_enchant_power_1k] - title = "Maitre des Arcanes" - description = "Accumulez 1.000 de puissance d'enchantement" +title = "Maitre des Arcanes" +description = "Accumulez 1.000 de puissance d'enchantement" [advancement.challenge_enchant_levels_1k] - title = "Depensier de Niveaux" - description = "Depensez 1.000 niveaux d'experience en enchantements" +title = "Depensier de Niveaux" +description = "Depensez 1.000 niveaux d'experience en enchantements" [advancement.challenge_enchant_levels_10k] - title = "Gouffre a XP" - description = "Depensez 10.000 niveaux d'experience en enchantements" +title = "Gouffre a XP" +description = "Depensez 10.000 niveaux d'experience en enchantements" [advancement.challenge_enchant_high_25] - title = "Flambeur" - description = "Effectuez 25 enchantements de niveau maximum" +title = "Flambeur" +description = "Effectuez 25 enchantements de niveau maximum" [advancement.challenge_enchant_high_250] - title = "Enchanteur Legendaire" - description = "Effectuez 250 enchantements de niveau maximum" +title = "Enchanteur Legendaire" +description = "Effectuez 250 enchantements de niveau maximum" [advancement.challenge_enchant_total_500] - title = "Bruleur de Niveaux" - description = "Depensez 500 niveaux au total en enchantements" +title = "Bruleur de Niveaux" +description = "Depensez 500 niveaux au total en enchantements" [advancement.challenge_enchant_total_5k] - title = "Investissement Arcanique" - description = "Depensez 5.000 niveaux au total en enchantements" +title = "Investissement Arcanique" +description = "Depensez 5.000 niveaux au total en enchantements" # Excavation [advancement.challenge_dig_swing_500] - title = "Pelleteur" - description = "Balancez votre pelle 500 fois" +title = "Pelleteur" +description = "Balancez votre pelle 500 fois" [advancement.challenge_dig_swing_5k] - title = "Excavateur" - description = "Balancez votre pelle 5.000 fois" +title = "Excavateur" +description = "Balancez votre pelle 5.000 fois" [advancement.challenge_dig_damage_1k] - title = "Chevalier a la Pelle" - description = "Infligez 1.000 degats avec une pelle" +title = "Chevalier a la Pelle" +description = "Infligez 1.000 degats avec une pelle" [advancement.challenge_dig_damage_10k] - title = "Maitre de la Pelle" - description = "Infligez 10.000 degats avec une pelle" +title = "Maitre de la Pelle" +description = "Infligez 10.000 degats avec une pelle" [advancement.challenge_dig_value_5k] - title = "Marchand de Terre" - description = "Excavez des blocs d'une valeur de 5.000" +title = "Marchand de Terre" +description = "Excavez des blocs d'une valeur de 5.000" [advancement.challenge_dig_value_50k] - title = "Baron de la Terre" - description = "Excavez des blocs d'une valeur de 50.000" +title = "Baron de la Terre" +description = "Excavez des blocs d'une valeur de 50.000" [advancement.challenge_dig_gravel_500] - title = "Broyeur de Gravier" - description = "Creusez 500 blocs de gravier, sable ou argile" +title = "Broyeur de Gravier" +description = "Creusez 500 blocs de gravier, sable ou argile" [advancement.challenge_dig_gravel_5k] - title = "Tamiseur de Sable" - description = "Creusez 5.000 blocs de gravier, sable ou argile" +title = "Tamiseur de Sable" +description = "Creusez 5.000 blocs de gravier, sable ou argile" # Herbalism [advancement.challenge_plant_100] - title = "Semeur" - description = "Plantez 100 cultures" +title = "Semeur" +description = "Plantez 100 cultures" [advancement.challenge_plant_1k] - title = "Main Verte" - description = "Plantez 1.000 cultures" +title = "Main Verte" +description = "Plantez 1.000 cultures" [advancement.challenge_plant_5k] - title = "Baron Agricole" - description = "Plantez 5.000 cultures" +title = "Baron Agricole" +description = "Plantez 5.000 cultures" [advancement.challenge_compost_50] - title = "Recycleur" - description = "Compostez 50 objets" +title = "Recycleur" +description = "Compostez 50 objets" [advancement.challenge_compost_500] - title = "Enrichisseur de Sol" - description = "Compostez 500 objets" +title = "Enrichisseur de Sol" +description = "Compostez 500 objets" [advancement.challenge_shear_50] - title = "Tondeur" - description = "Tondez 50 entites" +title = "Tondeur" +description = "Tondez 50 entites" [advancement.challenge_shear_250] - title = "Maitre du Troupeau" - description = "Tondez 250 entites" +title = "Maitre du Troupeau" +description = "Tondez 250 entites" # Hunter [advancement.challenge_kills_500] - title = "Tueur" - description = "Abattez 500 creatures" +title = "Tueur" +description = "Abattez 500 creatures" [advancement.challenge_kills_5k] - title = "Bourreau" - description = "Abattez 5.000 creatures" +title = "Bourreau" +description = "Abattez 5.000 creatures" [advancement.challenge_boss_1] - title = "Defieur de Boss" - description = "Terrassez un boss" +title = "Defieur de Boss" +description = "Terrassez un boss" [advancement.challenge_boss_10] - title = "Tueur de Legendes" - description = "Terrassez 10 boss" +title = "Tueur de Legendes" +description = "Terrassez 10 boss" # Nether [advancement.challenge_wither_dmg_500] - title = "Fletri" - description = "Endurez 500 degats de Wither" +title = "Fletri" +description = "Endurez 500 degats de Wither" [advancement.challenge_wither_dmg_5k] - title = "Survivant du Fleau" - description = "Endurez 5.000 degats de Wither" +title = "Survivant du Fleau" +description = "Endurez 5.000 degats de Wither" [advancement.challenge_wither_skel_25] - title = "Collectionneur d'Os" - description = "Abattez 25 squelettes Wither" +title = "Collectionneur d'Os" +description = "Abattez 25 squelettes Wither" [advancement.challenge_wither_skel_250] - title = "Fleau des Squelettes" - description = "Abattez 250 squelettes Wither" +title = "Fleau des Squelettes" +description = "Abattez 250 squelettes Wither" [advancement.challenge_wither_boss_1] - title = "Pourfendeur du Wither" - description = "Vainquez le Wither" +title = "Pourfendeur du Wither" +description = "Vainquez le Wither" [advancement.challenge_wither_boss_10] - title = "Dominateur du Nether" - description = "Vainquez le Wither 10 fois" +title = "Dominateur du Nether" +description = "Vainquez le Wither 10 fois" [advancement.challenge_roses_10] - title = "Jardinier de la Mort" - description = "Cassez 10 roses de Wither" +title = "Jardinier de la Mort" +description = "Cassez 10 roses de Wither" [advancement.challenge_roses_100] - title = "Collectionneur de Fleau" - description = "Cassez 100 roses de Wither" +title = "Collectionneur de Fleau" +description = "Cassez 100 roses de Wither" # Pickaxes [advancement.challenge_pick_swing_500] - title = "Bras de Mineur" - description = "Balancez votre pioche 500 fois" +title = "Bras de Mineur" +description = "Balancez votre pioche 500 fois" [advancement.challenge_pick_swing_5k] - title = "Creuseur de Tunnels" - description = "Balancez votre pioche 5.000 fois" +title = "Creuseur de Tunnels" +description = "Balancez votre pioche 5.000 fois" [advancement.challenge_pick_damage_1k] - title = "Combattant a la Pioche" - description = "Infligez 1.000 degats avec une pioche" +title = "Combattant a la Pioche" +description = "Infligez 1.000 degats avec une pioche" [advancement.challenge_pick_damage_10k] - title = "Pioche de Guerre" - description = "Infligez 10.000 degats avec une pioche" +title = "Pioche de Guerre" +description = "Infligez 10.000 degats avec une pioche" [advancement.challenge_pick_value_5k] - title = "Chercheur de Gemmes" - description = "Minez des blocs d'une valeur de 5.000" +title = "Chercheur de Gemmes" +description = "Minez des blocs d'une valeur de 5.000" [advancement.challenge_pick_value_50k] - title = "Baron du Minerai" - description = "Minez des blocs d'une valeur de 50.000" +title = "Baron du Minerai" +description = "Minez des blocs d'une valeur de 50.000" [advancement.challenge_pick_ores_500] - title = "Prospecteur" - description = "Minez 500 blocs de minerai" +title = "Prospecteur" +description = "Minez 500 blocs de minerai" [advancement.challenge_pick_ores_5k] - title = "Maitre Mineur" - description = "Minez 5.000 blocs de minerai" +title = "Maitre Mineur" +description = "Minez 5.000 blocs de minerai" # Ranged [advancement.challenge_ranged_dmg_1k] - title = "Tireur d'Elite" - description = "Infligez 1.000 degats a distance" +title = "Tireur d'Elite" +description = "Infligez 1.000 degats a distance" [advancement.challenge_ranged_dmg_10k] - title = "Archer Mortel" - description = "Infligez 10.000 degats a distance" +title = "Archer Mortel" +description = "Infligez 10.000 degats a distance" [advancement.challenge_ranged_dist_5k] - title = "Longue Portee" - description = "Tirez des projectiles couvrant 5.000 blocs de distance totale" +title = "Longue Portee" +description = "Tirez des projectiles couvrant 5.000 blocs de distance totale" [advancement.challenge_ranged_dist_50k] - title = "Tireur au Kilometre" - description = "Tirez des projectiles couvrant 50.000 blocs de distance totale" +title = "Tireur au Kilometre" +description = "Tirez des projectiles couvrant 50.000 blocs de distance totale" [advancement.challenge_ranged_kills_50] - title = "Archer" - description = "Tuez 50 creatures avec des armes a distance" +title = "Archer" +description = "Tuez 50 creatures avec des armes a distance" [advancement.challenge_ranged_kills_500] - title = "Maitre Archer" - description = "Tuez 500 creatures avec des armes a distance" +title = "Maitre Archer" +description = "Tuez 500 creatures avec des armes a distance" [advancement.challenge_longshot_25] - title = "Tireur Embusque" - description = "Reussissez 25 tirs a longue portee (plus de 30 blocs)" +title = "Tireur Embusque" +description = "Reussissez 25 tirs a longue portee (plus de 30 blocs)" [advancement.challenge_longshot_250] - title = "Oeil d'Aigle" - description = "Reussissez 250 tirs a longue portee (plus de 30 blocs)" +title = "Oeil d'Aigle" +description = "Reussissez 250 tirs a longue portee (plus de 30 blocs)" # Rift [advancement.challenge_rift_pearls_50] - title = "Lanceur de Perles" - description = "Lancez 50 perles de l'Ender" +title = "Lanceur de Perles" +description = "Lancez 50 perles de l'Ender" [advancement.challenge_rift_pearls_500] - title = "Accro a la Teleportation" - description = "Lancez 500 perles de l'Ender" +title = "Accro a la Teleportation" +description = "Lancez 500 perles de l'Ender" [advancement.challenge_rift_enderman_50] - title = "Chasseur d'Endermen" - description = "Abattez 50 endermen" +title = "Chasseur d'Endermen" +description = "Abattez 50 endermen" [advancement.challenge_rift_enderman_500] - title = "Rodeur du Vide" - description = "Abattez 500 endermen" +title = "Rodeur du Vide" +description = "Abattez 500 endermen" [advancement.challenge_rift_dragon_500] - title = "Combattant de Dragon" - description = "Infligez 500 degats au Dragon de l'Ender" +title = "Combattant de Dragon" +description = "Infligez 500 degats au Dragon de l'Ender" [advancement.challenge_rift_dragon_5k] - title = "Fleau du Dragon" - description = "Infligez 5.000 degats au Dragon de l'Ender" +title = "Fleau du Dragon" +description = "Infligez 5.000 degats au Dragon de l'Ender" [advancement.challenge_rift_crystal_10] - title = "Briseur de Cristaux" - description = "Detruisez 10 cristaux de l'End" +title = "Briseur de Cristaux" +description = "Detruisez 10 cristaux de l'End" [advancement.challenge_rift_crystal_100] - title = "Demolisseur de l'End" - description = "Detruisez 100 cristaux de l'End" +title = "Demolisseur de l'End" +description = "Detruisez 100 cristaux de l'End" # Seaborne [advancement.challenge_fish_25] - title = "Pecheur" - description = "Attrapez 25 poissons" +title = "Pecheur" +description = "Attrapez 25 poissons" [advancement.challenge_fish_250] - title = "Maitre Pecheur" - description = "Attrapez 250 poissons" +title = "Maitre Pecheur" +description = "Attrapez 250 poissons" [advancement.challenge_drowned_25] - title = "Chasseur de Noyes" - description = "Abattez 25 noyes" +title = "Chasseur de Noyes" +description = "Abattez 25 noyes" [advancement.challenge_drowned_250] - title = "Nettoyeur de l'Ocean" - description = "Abattez 250 noyes" +title = "Nettoyeur de l'Ocean" +description = "Abattez 250 noyes" [advancement.challenge_guardian_10] - title = "Tueur de Gardiens" - description = "Abattez 10 gardiens" +title = "Tueur de Gardiens" +description = "Abattez 10 gardiens" [advancement.challenge_guardian_100] - title = "Pilleur de Temple" - description = "Abattez 100 gardiens" +title = "Pilleur de Temple" +description = "Abattez 100 gardiens" [advancement.challenge_underwater_blocks_100] - title = "Mineur Sous-Marin" - description = "Cassez 100 blocs sous l'eau" +title = "Mineur Sous-Marin" +description = "Cassez 100 blocs sous l'eau" [advancement.challenge_underwater_blocks_1k] - title = "Ingenieur Aquatique" - description = "Cassez 1.000 blocs sous l'eau" +title = "Ingenieur Aquatique" +description = "Cassez 1.000 blocs sous l'eau" # Stealth [advancement.challenge_stealth_dmg_500] - title = "Poignardeur" - description = "Infligez 500 degats en etant accroupi" +title = "Poignardeur" +description = "Infligez 500 degats en etant accroupi" [advancement.challenge_stealth_dmg_5k] - title = "Tueur Silencieux" - description = "Infligez 5.000 degats en etant accroupi" +title = "Tueur Silencieux" +description = "Infligez 5.000 degats en etant accroupi" [advancement.challenge_stealth_kills_10] - title = "Assassin" - description = "Tuez 10 creatures en etant accroupi" +title = "Assassin" +description = "Tuez 10 creatures en etant accroupi" [advancement.challenge_stealth_kills_100] - title = "Faucheur des Ombres" - description = "Tuez 100 creatures en etant accroupi" +title = "Faucheur des Ombres" +description = "Tuez 100 creatures en etant accroupi" [advancement.challenge_stealth_time_1h] - title = "Patient" - description = "Passez 1 heure accroupi (3.600 secondes)" +title = "Patient" +description = "Passez 1 heure accroupi (3.600 secondes)" [advancement.challenge_stealth_time_10h] - title = "Maitre des Ombres" - description = "Passez 10 heures accroupi (36.000 secondes)" +title = "Maitre des Ombres" +description = "Passez 10 heures accroupi (36.000 secondes)" [advancement.challenge_stealth_arrows_50] - title = "Archer Silencieux" - description = "Tirez 50 fleches en etant accroupi" +title = "Archer Silencieux" +description = "Tirez 50 fleches en etant accroupi" [advancement.challenge_stealth_arrows_500] - title = "Archer Fantome" - description = "Tirez 500 fleches en etant accroupi" +title = "Archer Fantome" +description = "Tirez 500 fleches en etant accroupi" # Swords [advancement.challenge_sword_dmg_1k] - title = "Apprenti de la Lame" - description = "Infligez 1.000 degats avec des epees" +title = "Apprenti de la Lame" +description = "Infligez 1.000 degats avec des epees" [advancement.challenge_sword_dmg_10k] - title = "Epeeiste" - description = "Infligez 10.000 degats avec des epees" +title = "Epeeiste" +description = "Infligez 10.000 degats avec des epees" [advancement.challenge_sword_kills_50] - title = "Duelliste" - description = "Tuez 50 creatures avec des epees" +title = "Duelliste" +description = "Tuez 50 creatures avec des epees" [advancement.challenge_sword_kills_500] - title = "Gladiateur" - description = "Tuez 500 creatures avec des epees" +title = "Gladiateur" +description = "Tuez 500 creatures avec des epees" [advancement.challenge_sword_crit_50] - title = "Frappeur Critique" - description = "Placez 50 coups critiques avec des epees" +title = "Frappeur Critique" +description = "Placez 50 coups critiques avec des epees" [advancement.challenge_sword_crit_500] - title = "Maitre de la Precision" - description = "Placez 500 coups critiques avec des epees" +title = "Maitre de la Precision" +description = "Placez 500 coups critiques avec des epees" [advancement.challenge_sword_heavy_25] - title = "Frappeur Lourd" - description = "Placez 25 coups lourds avec des epees (plus de 8 degats)" +title = "Frappeur Lourd" +description = "Placez 25 coups lourds avec des epees (plus de 8 degats)" [advancement.challenge_sword_heavy_250] - title = "Coup Devastateur" - description = "Placez 250 coups lourds avec des epees (plus de 8 degats)" +title = "Coup Devastateur" +description = "Placez 250 coups lourds avec des epees (plus de 8 degats)" # Taming [advancement.challenge_pet_dmg_500] - title = "Dresseur de Betes" - description = "Vos animaux infligent 500 degats au total" +title = "Dresseur de Betes" +description = "Vos animaux infligent 500 degats au total" [advancement.challenge_pet_dmg_5k] - title = "Maitre de Guerre" - description = "Vos animaux infligent 5.000 degats au total" +title = "Maitre de Guerre" +description = "Vos animaux infligent 5.000 degats au total" [advancement.challenge_tamed_10] - title = "Ami des Animaux" - description = "Apprivoisez 10 animaux" +title = "Ami des Animaux" +description = "Apprivoisez 10 animaux" [advancement.challenge_tamed_100] - title = "Gardien de Zoo" - description = "Apprivoisez 100 animaux" +title = "Gardien de Zoo" +description = "Apprivoisez 100 animaux" [advancement.challenge_pet_kills_25] - title = "Tactique de Meute" - description = "Vos animaux abattent 25 creatures" +title = "Tactique de Meute" +description = "Vos animaux abattent 25 creatures" [advancement.challenge_pet_kills_250] - title = "Commandant Alpha" - description = "Vos animaux abattent 250 creatures" +title = "Commandant Alpha" +description = "Vos animaux abattent 250 creatures" [advancement.challenge_taming_2500] - title = "Expert en Elevage" - description = "Elevez 2.500 animaux" +title = "Expert en Elevage" +description = "Elevez 2.500 animaux" [advancement.challenge_taming_25k] - title = "Maitre Geneticien" - description = "Elevez 25.000 animaux" +title = "Maitre Geneticien" +description = "Elevez 25.000 animaux" # TragOul [advancement.challenge_trag_hits_500] - title = "Sac de Frappe" - description = "Recevez 500 coups" +title = "Sac de Frappe" +description = "Recevez 500 coups" [advancement.challenge_trag_hits_5k] - title = "Masochiste" - description = "Recevez 5.000 coups" +title = "Masochiste" +description = "Recevez 5.000 coups" [advancement.challenge_trag_deaths_10] - title = "Neuf Vies" - description = "Mourez 10 fois" +title = "Neuf Vies" +description = "Mourez 10 fois" [advancement.challenge_trag_deaths_100] - title = "Cauchemar Recurrent" - description = "Mourez 100 fois" +title = "Cauchemar Recurrent" +description = "Mourez 100 fois" [advancement.challenge_trag_fire_500] - title = "Victime du Feu" - description = "Endurez 500 degats de feu" +title = "Victime du Feu" +description = "Endurez 500 degats de feu" [advancement.challenge_trag_fire_5k] - title = "Phenix" - description = "Endurez 5.000 degats de feu" +title = "Phenix" +description = "Endurez 5.000 degats de feu" [advancement.challenge_trag_fall_500] - title = "Test de Gravite" - description = "Endurez 500 degats de chute" +title = "Test de Gravite" +description = "Endurez 500 degats de chute" [advancement.challenge_trag_fall_5k] - title = "Vitesse Terminale" - description = "Endurez 5.000 degats de chute" +title = "Vitesse Terminale" +description = "Endurez 5.000 degats de chute" # Unarmed [advancement.challenge_unarmed_dmg_1k] - title = "Bagarreur" - description = "Infligez 1.000 degats a mains nues" +title = "Bagarreur" +description = "Infligez 1.000 degats a mains nues" [advancement.challenge_unarmed_dmg_10k] - title = "Artiste Martial" - description = "Infligez 10.000 degats a mains nues" +title = "Artiste Martial" +description = "Infligez 10.000 degats a mains nues" [advancement.challenge_unarmed_kills_25] - title = "Poings Nus" - description = "Tuez 25 creatures a mains nues" +title = "Poings Nus" +description = "Tuez 25 creatures a mains nues" [advancement.challenge_unarmed_kills_250] - title = "Poing de Legende" - description = "Tuez 250 creatures a mains nues" +title = "Poing de Legende" +description = "Tuez 250 creatures a mains nues" [advancement.challenge_unarmed_crit_25] - title = "Coup de Poing Critique" - description = "Placez 25 coups critiques a mains nues" +title = "Coup de Poing Critique" +description = "Placez 25 coups critiques a mains nues" [advancement.challenge_unarmed_crit_250] - title = "Poing de Precision" - description = "Placez 250 coups critiques a mains nues" +title = "Poing de Precision" +description = "Placez 250 coups critiques a mains nues" [advancement.challenge_unarmed_heavy_25] - title = "Coup de Poing Puissant" - description = "Placez 25 coups lourds a mains nues (plus de 6 degats)" +title = "Coup de Poing Puissant" +description = "Placez 25 coups lourds a mains nues (plus de 6 degats)" [advancement.challenge_unarmed_heavy_250] - title = "Roi du Knockout" - description = "Placez 250 coups lourds a mains nues (plus de 6 degats)" +title = "Roi du Knockout" +description = "Placez 250 coups lourds a mains nues (plus de 6 degats)" # items [items] [items.bound_ender_peral] - name = "Portoloin reliquaire" - usage1 = "Maj + Clic gauche pour lier" - usage2 = "Clic droit pour acceder a l'inventaire lie" +name = "Portoloin reliquaire" +usage1 = "Maj + Clic gauche pour lier" +usage2 = "Clic droit pour acceder a l'inventaire lie" [items.bound_eye_of_ender] - name = "Ancre oculaire" - usage1 = "Clic droit pour consommer et se teleporter a l'emplacement lie" - usage2 = "Maj + Clic gauche pour lier a un bloc" +name = "Ancre oculaire" +usage1 = "Clic droit pour consommer et se teleporter a l'emplacement lie" +usage2 = "Maj + Clic gauche pour lier a un bloc" [items.bound_redstone_torch] - name = "Telecommande Redstone" - usage1 = "Clic droit pour creer une impulsion Redstone d'1 tick" - usage2 = "Maj + Clic gauche sur un bloc 'Cible' pour lier" +name = "Telecommande Redstone" +usage1 = "Clic droit pour creer une impulsion Redstone d'1 tick" +usage2 = "Maj + Clic gauche sur un bloc 'Cible' pour lier" [items.bound_snowball] - name = "Piege de toile !" - usage1 = "Lancez pour creer un piege de toile temporaire a cet emplacement" +name = "Piege de toile !" +usage1 = "Lancez pour creer un piege de toile temporaire a cet emplacement" [items.chrono_time_bottle] - name = "Temps en bouteille" - usage1 = "Stocke passivement du temps dans votre inventaire" - usage2 = "Clic droit sur des blocs temporises ou des bebes animaux pour depenser le temps stocke" - stored = "Temps stocke" +name = "Temps en bouteille" +usage1 = "Stocke passivement du temps dans votre inventaire" +usage2 = "Clic droit sur des blocs temporises ou des bebes animaux pour depenser le temps stocke" +stored = "Temps stocke" [items.chrono_time_bomb] - name = "Bombe temporelle" - usage1 = "Clic droit pour lancer un projectile chrono qui cree un champ temporel" +name = "Bombe temporelle" +usage1 = "Clic droit pour lancer un projectile chrono qui cree un champ temporel" [items.elevator_block] - name = "Bloc d'ascenseur" - usage1 = "Sautez pour vous teleporter vers le haut" - usage2 = "Accroupissez-vous pour vous teleporter vers le bas" - usage3 = "Minimum de 2 blocs d'air entre les ascenseurs" +name = "Bloc d'ascenseur" +usage1 = "Sautez pour vous teleporter vers le haut" +usage2 = "Accroupissez-vous pour vous teleporter vers le bas" +usage3 = "Minimum de 2 blocs d'air entre les ascenseurs" # snippets [snippets] [snippets.gui] - level = "Niveau" - knowledge = "connaissance" - power_used = "Puissance utilisee" - not_learned = "Non appris" - xp = "XP pour" - welcome = "Bienvenue !" - welcome_back = "Content de vous revoir !" - xp_bonus_for_time = "XP pour" - max_ability_power = "Puissance de capacite maximale" - unlock_this_by_clicking = "Debloquez ceci par Clic droit : " - back = "Retour" - unlearn_all = "Tout desapprendre" - unlearned_all = "Tout desappris" +level = "Niveau" +knowledge = "connaissance" +power_used = "Puissance utilisee" +not_learned = "Non appris" +xp = "XP pour" +welcome = "Bienvenue !" +welcome_back = "Content de vous revoir !" +xp_bonus_for_time = "XP pour" +max_ability_power = "Puissance de capacite maximale" +unlock_this_by_clicking = "Debloquez ceci par Clic droit : " +back = "Retour" +unlearn_all = "Tout desapprendre" +unlearned_all = "Tout desappris" [snippets.adapt_menu] - may_not_unlearn = "VOUS NE POUVEZ PAS DESAPPRENDRE" - may_unlearn = "VOUS POUVEZ APPRENDRE/DESAPPRENDRE" - knowledge_cost = "Cout en connaissance" - knowledge_available = "Connaissance disponible" - already_learned = "Deja appris" - unlearn_refund = "Cliquez pour desapprendre et rembourser" - no_refunds = "HARDCORE, REMBOURSEMENTS DESACTIVES" - knowledge = "connaissance" - click_learn = "Cliquez pour apprendre" - no_knowledge = "(Vous n'avez aucune connaissance)" - you_only_have = "Vous n'avez que" - how_to_level_up = "Montez vos competences en niveau pour augmenter votre puissance maximale." - not_enough_power = "Pas assez de puissance ! Chaque niveau de capacite coute 1 puissance." - power = "puissance" - power_drain = "Drain de puissance" - learned = "Appris " - unlearned = "Desappris " - activator_block = "Bibliotheque" +may_not_unlearn = "VOUS NE POUVEZ PAS DESAPPRENDRE" +may_unlearn = "VOUS POUVEZ APPRENDRE/DESAPPRENDRE" +knowledge_cost = "Cout en connaissance" +knowledge_available = "Connaissance disponible" +already_learned = "Deja appris" +unlearn_refund = "Cliquez pour desapprendre et rembourser" +no_refunds = "HARDCORE, REMBOURSEMENTS DESACTIVES" +knowledge = "connaissance" +click_learn = "Cliquez pour apprendre" +no_knowledge = "(Vous n'avez aucune connaissance)" +you_only_have = "Vous n'avez que" +how_to_level_up = "Montez vos competences en niveau pour augmenter votre puissance maximale." +not_enough_power = "Pas assez de puissance ! Chaque niveau de capacite coute 1 puissance." +power = "puissance" +power_drain = "Drain de puissance" +learned = "Appris " +unlearned = "Desappris " +activator_block = "Bibliotheque" [snippets.knowledge_orb] - contains = "contient" - knowledge = "connaissance" - rightclick = "Clic droit" - togainknowledge = "pour obtenir cette connaissance" - knowledge_orb = "Orbe de connaissance" +contains = "contient" +knowledge = "connaissance" +rightclick = "Clic droit" +togainknowledge = "pour obtenir cette connaissance" +knowledge_orb = "Orbe de connaissance" [snippets.experience_orb] - contains = "contient" - xp = "Experience" - rightclick = "Clic droit" - togainxp = "pour obtenir cette experience" - xporb = "Orbe d'experience" +contains = "contient" +xp = "Experience" +rightclick = "Clic droit" +togainxp = "pour obtenir cette experience" +xporb = "Orbe d'experience" # skill [skill] [skill.agility] - name = "Agilite" - icon = "⇉" - description = "L'agilite est la capacite a se deplacer rapidement et avec fluidite face aux obstacles." +name = "Agilite" +icon = "⇉" +description = "L'agilite est la capacite a se deplacer rapidement et avec fluidite face aux obstacles." [skill.architect] - name = "Architecte" - icon = "⬧" - description = "Les structures sont les fondations du monde. La realite est entre vos mains, a vous de la controler." +name = "Architecte" +icon = "⬧" +description = "Les structures sont les fondations du monde. La realite est entre vos mains, a vous de la controler." [skill.axes] - name = "Haches" - icon = "🪓" - description1 = "Pourquoi abattre des arbres, quand on peut abattre des " - description2 = "choses" - description3 = "a la place, meme resultat !" +name = "Haches" +icon = "🪓" +description1 = "Pourquoi abattre des arbres, quand on peut abattre des " +description2 = "choses" +description3 = "a la place, meme resultat !" [skill.brewing] - name = "Alchimie" - icon = "❦" - description = "Double bulle, triple bulle, quadruple bulle - je n'arrive toujours pas a mettre cette potion dans un chaudron" +name = "Alchimie" +icon = "❦" +description = "Double bulle, triple bulle, quadruple bulle - je n'arrive toujours pas a mettre cette potion dans un chaudron" [skill.blocking] - name = "Blocage" - icon = "🛡" - description = "Les batons et les cailloux ne casseront pas vos os, mais un bouclier, si." +name = "Blocage" +icon = "🛡" +description = "Les batons et les cailloux ne casseront pas vos os, mais un bouclier, si." [skill.crafting] - name = "Artisanat" - icon = "⌂" - description = "Plus de pieces a placer ? Pourquoi ne pas en fabriquer d'autres ?" +name = "Artisanat" +icon = "⌂" +description = "Plus de pieces a placer ? Pourquoi ne pas en fabriquer d'autres ?" [skill.discovery] - name = "Decouverte" - icon = "⚛" - description = "A mesure que votre perception s'elargit, votre esprit se devoile pour decouvrir ce que vous ignoriez." +name = "Decouverte" +icon = "⚛" +description = "A mesure que votre perception s'elargit, votre esprit se devoile pour decouvrir ce que vous ignoriez." [skill.enchanting] - name = "Enchantement" - icon = "♰" - description = "De quoi parlez-vous ? Propheties, visions, superstitions en tout genre ?" +name = "Enchantement" +icon = "♰" +description = "De quoi parlez-vous ? Propheties, visions, superstitions en tout genre ?" [skill.excavation] - name = "Excavation" - icon = "ᛳ" - description = "On creuse, on creuse un trou..." +name = "Excavation" +icon = "ᛳ" +description = "On creuse, on creuse un trou..." [skill.herbalism] - name = "Herboristerie" - icon = "⚘" - description = "Je ne trouve aucune plante, mais je trouve des graines et... c'est de la mauvaise herbe ?" +name = "Herboristerie" +icon = "⚘" +description = "Je ne trouve aucune plante, mais je trouve des graines et... c'est de la mauvaise herbe ?" [skill.hunter] - name = "Chasseur" - icon = "☠" - description = "La chasse, c'est le voyage, pas le resultat." +name = "Chasseur" +icon = "☠" +description = "La chasse, c'est le voyage, pas le resultat." [skill.nether] - name = "Nether" - icon = "₪" - description = "Des profondeurs du Nether lui-meme." +name = "Nether" +icon = "₪" +description = "Des profondeurs du Nether lui-meme." [skill.pickaxe] - name = "Pioche" - icon = "⛏" - description = "Les nains sont les mineurs, mais j'ai appris un truc ou deux avec le temps. JE SUIS SUEDOIS" +name = "Pioche" +icon = "⛏" +description = "Les nains sont les mineurs, mais j'ai appris un truc ou deux avec le temps. JE SUIS SUEDOIS" [skill.ranged] - name = "A distance" - icon = "🏹" - description = "La distance est la cle de la victoire, et la cle de la survie." +name = "A distance" +icon = "🏹" +description = "La distance est la cle de la victoire, et la cle de la survie." [skill.rift] - name = "Faille" - icon = "❍" - description = "La Faille est un harnais caustique, mais vous avez dompte le harnais." +name = "Faille" +icon = "❍" +description = "La Faille est un harnais caustique, mais vous avez dompte le harnais." [skill.seaborne] - name = "Maritime" - icon = "🎣" - description = "Avec cette competence, vous pourrez dompter les merveilles de l'eau." +name = "Maritime" +icon = "🎣" +description = "Avec cette competence, vous pourrez dompter les merveilles de l'eau." [skill.stealth] - name = "Furtivite" - icon = "☯" - description = "L'art de l'invisible. Marchez dans les ombres." +name = "Furtivite" +icon = "☯" +description = "L'art de l'invisible. Marchez dans les ombres." [skill.swords] - name = "Epees" - icon = "⚔" - description = "Par le pouvoir de GreyStone !" +name = "Epees" +icon = "⚔" +description = "Par le pouvoir de GreyStone !" [skill.taming] - name = "Apprivoisement" - icon = "♥" - description = "Les perroquets et les abeilles... et vous ?" +name = "Apprivoisement" +icon = "♥" +description = "Les perroquets et les abeilles... et vous ?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Le sang coule dans les veines de l'univers. Contraint par vos mains." +name = "TragOul" +icon = "🗡" +description = "Le sang coule dans les veines de l'univers. Contraint par vos mains." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Remontez l'horloge de l'univers, vivez le flux. Brisez l'horloge, devenez-la." +name = "Chronos" +icon = "🕒" +description = "Remontez l'horloge de l'univers, vivez le flux. Brisez l'horloge, devenez-la." [skill.unarmed] - name = "Mains nues" - icon = "»" - description = "Sans arme ne veut pas dire sans force." +name = "Mains nues" +icon = "»" +description = "Sans arme ne veut pas dire sans force." # agility [agility] [agility.armor_up] - name = "Armure renforcee" - description = "Gagnez plus d'armure plus vous sprintez longtemps !" - lore1 = "Armure maximale" - lore2 = "Temps de montee en armure" - lore = ["Armure maximale", "Temps de montee en armure"] +name = "Armure renforcee" +description = "Gagnez plus d'armure plus vous sprintez longtemps !" +lore1 = "Armure maximale" +lore2 = "Temps de montee en armure" +lore = ["Armure maximale", "Temps de montee en armure"] [agility.ladder_slide] - name = "Glissade sur echelle" - description = "Grimpez et glissez sur les echelles beaucoup plus vite dans les deux directions." - lore1 = "Multiplicateur de vitesse sur echelle" - lore2 = "Vitesse de descente rapide" - lore = ["Multiplicateur de vitesse sur echelle", "Vitesse de descente rapide"] +name = "Glissade sur echelle" +description = "Grimpez et glissez sur les echelles beaucoup plus vite dans les deux directions." +lore1 = "Multiplicateur de vitesse sur echelle" +lore2 = "Vitesse de descente rapide" +lore = ["Multiplicateur de vitesse sur echelle", "Vitesse de descente rapide"] [agility.super_jump] - name = "Super saut" - description = "Avantage de hauteur exceptionnel." - lore1 = "Hauteur de saut maximale" - lore2 = "Accroupi + Saut pour faire un Super Saut !" - lore = ["Hauteur de saut maximale", "Accroupi + Saut pour faire un Super Saut !"] +name = "Super saut" +description = "Avantage de hauteur exceptionnel." +lore1 = "Hauteur de saut maximale" +lore2 = "Accroupi + Saut pour faire un Super Saut !" +lore = ["Hauteur de saut maximale", "Accroupi + Saut pour faire un Super Saut !"] [agility.wall_jump] - name = "Saut mural" - description = "Maintenez Maj en l'air contre un mur pour vous y accrocher et sauter !" - lore1 = "Sauts maximum" - lore2 = "Hauteur du saut" - lore = ["Sauts maximum", "Hauteur du saut"] +name = "Saut mural" +description = "Maintenez Maj en l'air contre un mur pour vous y accrocher et sauter !" +lore1 = "Sauts maximum" +lore2 = "Hauteur du saut" +lore = ["Sauts maximum", "Hauteur du saut"] [agility.wind_up] - name = "Montee en puissance" - description = "Accelerez plus vous sprintez longtemps !" - lore1 = "Vitesse maximale" - lore2 = "Temps de montee en puissance" - lore = ["Vitesse maximale", "Temps de montee en puissance"] +name = "Montee en puissance" +description = "Accelerez plus vous sprintez longtemps !" +lore1 = "Vitesse maximale" +lore2 = "Temps de montee en puissance" +lore = ["Vitesse maximale", "Temps de montee en puissance"] # architect [architect] [architect.elevator] - name = "Ascenseur" - description = "Cela vous permet de construire un ascenseur pour vous teleporter verticalement rapidement !" - lore1 = "Debloque la recette d'ascenseur : X=LAINE, Y=PERLE DE L'ENDER" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Debloque la recette d'ascenseur : X=LAINE, Y=PERLE DE L'ENDER", "XXX", "XYX", "XXX"] +name = "Ascenseur" +description = "Cela vous permet de construire un ascenseur pour vous teleporter verticalement rapidement !" +lore1 = "Debloque la recette d'ascenseur : X=LAINE, Y=PERLE DE L'ENDER" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Debloque la recette d'ascenseur : X=LAINE, Y=PERLE DE L'ENDER", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Fondation magique" - description = "Cela vous permet de vous accroupir et de placer une fondation temporaire sous vous !" - lore1 = "Creez magiquement : " - lore2 = "Blocs sous vous !" - lore = ["Creez magiquement : ", "Blocs sous vous !"] +name = "Fondation magique" +description = "Cela vous permet de vous accroupir et de placer une fondation temporaire sous vous !" +lore1 = "Creez magiquement : " +lore2 = "Blocs sous vous !" +lore = ["Creez magiquement : ", "Blocs sous vous !"] [architect.glass] - name = "Verre toucher de soie" - description = "Cela vous permet d'eviter la perte de blocs de verre lorsque vous les cassez a main nue !" - lore1 = "Vos mains obtiennent Toucher de soie pour le verre" - lore = ["Vos mains obtiennent Toucher de soie pour le verre"] +name = "Verre toucher de soie" +description = "Cela vous permet d'eviter la perte de blocs de verre lorsque vous les cassez a main nue !" +lore1 = "Vos mains obtiennent Toucher de soie pour le verre" +lore = ["Vos mains obtiennent Toucher de soie pour le verre"] [architect.wireless_redstone] - name = "Telecommande Redstone" - description = "Cela vous permet d'utiliser une torche de redstone pour activer la redstone a distance !" - lore1 = "Cible + Torche de Redstone + Perle de l'Ender = 1 Telecommande Redstone" - lore = ["Cible + Torche de Redstone + Perle de l'Ender = 1 Telecommande Redstone"] +name = "Telecommande Redstone" +description = "Cela vous permet d'utiliser une torche de redstone pour activer la redstone a distance !" +lore1 = "Cible + Torche de Redstone + Perle de l'Ender = 1 Telecommande Redstone" +lore = ["Cible + Torche de Redstone + Perle de l'Ender = 1 Telecommande Redstone"] [architect.placement] - name = "Baguette du batisseur" - description = "Vous permet de placer plusieurs blocs a la fois ! Accroupissez-vous, tenez un bloc identique a celui que vous visez et placez ! Vous devrez peut-etre bouger un peu pour declencher la delimitation des zones !" - lore1 = "Vous avez besoin de" - lore2 = "blocs dans votre main pour placer ceci" - lore3 = "Une baguette de batisseur en materiaux" - lore = ["Vous avez besoin de", "blocs dans votre main pour placer ceci", "Une baguette de batisseur en materiaux"] +name = "Baguette du batisseur" +description = "Vous permet de placer plusieurs blocs a la fois ! Accroupissez-vous, tenez un bloc identique a celui que vous visez et placez ! Vous devrez peut-etre bouger un peu pour declencher la delimitation des zones !" +lore1 = "Vous avez besoin de" +lore2 = "blocs dans votre main pour placer ceci" +lore3 = "Une baguette de batisseur en materiaux" +lore = ["Vous avez besoin de", "blocs dans votre main pour placer ceci", "Une baguette de batisseur en materiaux"] # axe [axe] [axe.chop] - name = "Coup de hache" - description = "Abattez des arbres en faisant un clic droit sur la buche de base !" - lore1 = "Blocs par coup" - lore2 = "Temps de recharge du coup" - lore3 = "Usure de l'outil" - lore = ["Blocs par coup", "Temps de recharge du coup", "Usure de l'outil"] +name = "Coup de hache" +description = "Abattez des arbres en faisant un clic droit sur la buche de base !" +lore1 = "Blocs par coup" +lore2 = "Temps de recharge du coup" +lore3 = "Usure de l'outil" +lore = ["Blocs par coup", "Temps de recharge du coup", "Usure de l'outil"] [axe.log_swap] - name = "Echangeur de buches de Lucy" - description = "Changez le type de buches dans une table d'artisanat !" - lore1 = "8 buches de n'importe quel type + 1 pousse = 8 buches du type de la pousse" - lore = ["8 buches de n'importe quel type + 1 pousse = 8 buches du type de la pousse"] +name = "Echangeur de buches de Lucy" +description = "Changez le type de buches dans une table d'artisanat !" +lore1 = "8 buches de n'importe quel type + 1 pousse = 8 buches du type de la pousse" +lore = ["8 buches de n'importe quel type + 1 pousse = 8 buches du type de la pousse"] [axe.drop_to_inventory] - name = "Hache : depot direct en inventaire" +name = "Hache : depot direct en inventaire" [axe.ground_smash] - name = "Frappe au sol a la hache" - description = "Sautez, puis accroupissez-vous et ecrasez tous les ennemis proches." - lore1 = "Degats" - lore2 = "Rayon en blocs" - lore3 = "Force" - lore4 = "Temps de recharge de la frappe" - lore = ["Degats", "Rayon en blocs", "Force", "Temps de recharge de la frappe"] +name = "Frappe au sol a la hache" +description = "Sautez, puis accroupissez-vous et ecrasez tous les ennemis proches." +lore1 = "Degats" +lore2 = "Rayon en blocs" +lore3 = "Force" +lore4 = "Temps de recharge de la frappe" +lore = ["Degats", "Rayon en blocs", "Force", "Temps de recharge de la frappe"] [axe.leaf_miner] - name = "Mineur de feuilles" - description = "Vous permet de casser des feuilles en masse !" - lore1 = "Accroupissez-vous et minez les FEUILLES" - lore2 = "portee du minage de feuilles" - lore3 = "Vous n'obtiendrez pas les drops des feuilles (prevention d'exploit)" - lore = ["Accroupissez-vous et minez les FEUILLES", "portee du minage de feuilles", "Vous n'obtiendrez pas les drops des feuilles (prevention d'exploit)"] +name = "Mineur de feuilles" +description = "Vous permet de casser des feuilles en masse !" +lore1 = "Accroupissez-vous et minez les FEUILLES" +lore2 = "portee du minage de feuilles" +lore3 = "Vous n'obtiendrez pas les drops des feuilles (prevention d'exploit)" +lore = ["Accroupissez-vous et minez les FEUILLES", "portee du minage de feuilles", "Vous n'obtiendrez pas les drops des feuilles (prevention d'exploit)"] [axe.wood_miner] - name = "Mineur de bois" - description = "Vous permet de casser du bois en masse !" - lore1 = "Accroupissez-vous et minez le BOIS/BUCHES (pas les planches)" - lore2 = "portee du minage de bois" - lore3 = "Fonctionne avec le depot direct en inventaire" - lore = ["Accroupissez-vous et minez le BOIS/BUCHES (pas les planches)", "portee du minage de bois", "Fonctionne avec le depot direct en inventaire"] +name = "Mineur de bois" +description = "Vous permet de casser du bois en masse !" +lore1 = "Accroupissez-vous et minez le BOIS/BUCHES (pas les planches)" +lore2 = "portee du minage de bois" +lore3 = "Fonctionne avec le depot direct en inventaire" +lore = ["Accroupissez-vous et minez le BOIS/BUCHES (pas les planches)", "portee du minage de bois", "Fonctionne avec le depot direct en inventaire"] # brewing [brewing] [brewing.lingering] - name = "Infusion persistante" - description = "Les potions que vous preparez durent plus longtemps !" - lore1 = "Duree" - lore2 = "Duree" - lore = ["Duree", "Duree"] +name = "Infusion persistante" +description = "Les potions que vous preparez durent plus longtemps !" +lore1 = "Duree" +lore2 = "Duree" +lore = ["Duree", "Duree"] [brewing.super_heated] - name = "Infusion surchauffee" - description = "Les alambics fonctionnent plus vite plus ils sont chauds." - lore1 = "Par bloc de feu adjacent" - lore2 = "Par bloc de lave adjacent" - lore = ["Par bloc de feu adjacent", "Par bloc de lave adjacent"] +name = "Infusion surchauffee" +description = "Les alambics fonctionnent plus vite plus ils sont chauds." +lore1 = "Par bloc de feu adjacent" +lore2 = "Par bloc de lave adjacent" +lore = ["Par bloc de feu adjacent", "Par bloc de lave adjacent"] [brewing.darkness] - name = "Tenebres en bouteille" - description = "On ne sait pas pourquoi vous en avez besoin, mais voila !" - lore1 = "Potion de vision nocturne + Beton noir = Potion de tenebres (30 secondes)" - lore2 = "A noter que cela empeche l'utilisateur de sprinter !" - lore = ["Potion de vision nocturne + Beton noir = Potion de tenebres (30 secondes)", "A noter que cela empeche l'utilisateur de sprinter !"] +name = "Tenebres en bouteille" +description = "On ne sait pas pourquoi vous en avez besoin, mais voila !" +lore1 = "Potion de vision nocturne + Beton noir = Potion de tenebres (30 secondes)" +lore2 = "A noter que cela empeche l'utilisateur de sprinter !" +lore = ["Potion de vision nocturne + Beton noir = Potion de tenebres (30 secondes)", "A noter que cela empeche l'utilisateur de sprinter !"] [brewing.haste] - name = "Hate en bouteille" - description = "Quand l'efficacite ne suffit pas" - lore1 = "Potion de vitesse + Eclat d'amethyste = Potion de hate (60 secondes)" - lore2 = "Potion de vitesse + Bloc d'amethyste = Potion de hate-2 (30 secondes)" - lore = ["Potion de vitesse + Eclat d'amethyste = Potion de hate (60 secondes)", "Potion de vitesse + Bloc d'amethyste = Potion de hate-2 (30 secondes)"] +name = "Hate en bouteille" +description = "Quand l'efficacite ne suffit pas" +lore1 = "Potion de vitesse + Eclat d'amethyste = Potion de hate (60 secondes)" +lore2 = "Potion de vitesse + Bloc d'amethyste = Potion de hate-2 (30 secondes)" +lore = ["Potion de vitesse + Eclat d'amethyste = Potion de hate (60 secondes)", "Potion de vitesse + Bloc d'amethyste = Potion de hate-2 (30 secondes)"] [brewing.absorption] - name = "Absorption en bouteille" - description = "Renforcez le corps !" - lore1 = "Soin instantane + Quartz = Potion d'absorption (60 secondes)" - lore2 = "Soin instantane + Bloc de quartz = Potion d'absorption-2 (30 secondes)" - lore = ["Soin instantane + Quartz = Potion d'absorption (60 secondes)", "Soin instantane + Bloc de quartz = Potion d'absorption-2 (30 secondes)"] +name = "Absorption en bouteille" +description = "Renforcez le corps !" +lore1 = "Soin instantane + Quartz = Potion d'absorption (60 secondes)" +lore2 = "Soin instantane + Bloc de quartz = Potion d'absorption-2 (30 secondes)" +lore = ["Soin instantane + Quartz = Potion d'absorption (60 secondes)", "Soin instantane + Bloc de quartz = Potion d'absorption-2 (30 secondes)"] [brewing.fatigue] - name = "Fatigue en bouteille" - description = "Affaiblissez le corps !" - lore1 = "Potion de faiblesse + Boule de slime = Potion de fatigue (30 secondes)" - lore2 = "Potion de faiblesse + Bloc de slime = Potion de fatigue-2 (15 secondes)" - lore = ["Potion de faiblesse + Boule de slime = Potion de fatigue (30 secondes)", "Potion de faiblesse + Bloc de slime = Potion de fatigue-2 (15 secondes)"] +name = "Fatigue en bouteille" +description = "Affaiblissez le corps !" +lore1 = "Potion de faiblesse + Boule de slime = Potion de fatigue (30 secondes)" +lore2 = "Potion de faiblesse + Bloc de slime = Potion de fatigue-2 (15 secondes)" +lore = ["Potion de faiblesse + Boule de slime = Potion de fatigue (30 secondes)", "Potion de faiblesse + Bloc de slime = Potion de fatigue-2 (15 secondes)"] [brewing.hunger] - name = "Faim en bouteille" - description = "Nourrissez l'insatiable !" - lore1 = "Potion bizarre + Chair putrefiee = Potion de faim (30 secondes)" - lore2 = "Potion de faiblesse + Chair putrefiee = Potion de faim-3 (15 secondes)" - lore = ["Potion bizarre + Chair putrefiee = Potion de faim (30 secondes)", "Potion de faiblesse + Chair putrefiee = Potion de faim-3 (15 secondes)"] +name = "Faim en bouteille" +description = "Nourrissez l'insatiable !" +lore1 = "Potion bizarre + Chair putrefiee = Potion de faim (30 secondes)" +lore2 = "Potion de faiblesse + Chair putrefiee = Potion de faim-3 (15 secondes)" +lore = ["Potion bizarre + Chair putrefiee = Potion de faim (30 secondes)", "Potion de faiblesse + Chair putrefiee = Potion de faim-3 (15 secondes)"] [brewing.nausea] - name = "Nausee en bouteille" - description = "Vous me rendez malade !" - lore1 = "Potion bizarre + Champignon brun = Potion de nausee (16 secondes)" - lore2 = "Potion bizarre + Champignon cramoisi = Potion de nausee-2 (8 secondes)" - lore = ["Potion bizarre + Champignon brun = Potion de nausee (16 secondes)", "Potion bizarre + Champignon cramoisi = Potion de nausee-2 (8 secondes)"] +name = "Nausee en bouteille" +description = "Vous me rendez malade !" +lore1 = "Potion bizarre + Champignon brun = Potion de nausee (16 secondes)" +lore2 = "Potion bizarre + Champignon cramoisi = Potion de nausee-2 (8 secondes)" +lore = ["Potion bizarre + Champignon brun = Potion de nausee (16 secondes)", "Potion bizarre + Champignon cramoisi = Potion de nausee-2 (8 secondes)"] [brewing.blindness] - name = "Cecite en bouteille" - description = "Vous etes une personne horrible..." - lore1 = "Potion bizarre + Poche d'encre = Potion de cecite (30 secondes)" - lore2 = "Potion bizarre + Poche d'encre lumineuse = Potion de cecite-2 (15 secondes)" - lore = ["Potion bizarre + Poche d'encre = Potion de cecite (30 secondes)", "Potion bizarre + Poche d'encre lumineuse = Potion de cecite-2 (15 secondes)"] +name = "Cecite en bouteille" +description = "Vous etes une personne horrible..." +lore1 = "Potion bizarre + Poche d'encre = Potion de cecite (30 secondes)" +lore2 = "Potion bizarre + Poche d'encre lumineuse = Potion de cecite-2 (15 secondes)" +lore = ["Potion bizarre + Poche d'encre = Potion de cecite (30 secondes)", "Potion bizarre + Poche d'encre lumineuse = Potion de cecite-2 (15 secondes)"] [brewing.resistance] - name = "Resistance en bouteille" - description = "La fortification a son summum !" - lore1 = "Potion bizarre + Lingot de fer = Potion de resistance (60 secondes)" - lore2 = "Potion bizarre + Bloc de fer = Potion de resistance-2 (30 secondes)" - lore = ["Potion bizarre + Lingot de fer = Potion de resistance (60 secondes)", "Potion bizarre + Bloc de fer = Potion de resistance-2 (30 secondes)"] +name = "Resistance en bouteille" +description = "La fortification a son summum !" +lore1 = "Potion bizarre + Lingot de fer = Potion de resistance (60 secondes)" +lore2 = "Potion bizarre + Bloc de fer = Potion de resistance-2 (30 secondes)" +lore = ["Potion bizarre + Lingot de fer = Potion de resistance (60 secondes)", "Potion bizarre + Bloc de fer = Potion de resistance-2 (30 secondes)"] [brewing.health_boost] - name = "Vie en bouteille" - description = "Quand la sante maximale ne suffit pas..." - lore1 = "Potion de soin instantane + Pomme doree = Potion de boost de sante (120 secondes)" - lore2 = "Potion de soin instantane + Pomme doree enchantee = Potion de boost de sante-2 (120 secondes)" - lore = ["Potion de soin instantane + Pomme doree = Potion de boost de sante (120 secondes)", "Potion de soin instantane + Pomme doree enchantee = Potion de boost de sante-2 (120 secondes)"] +name = "Vie en bouteille" +description = "Quand la sante maximale ne suffit pas..." +lore1 = "Potion de soin instantane + Pomme doree = Potion de boost de sante (120 secondes)" +lore2 = "Potion de soin instantane + Pomme doree enchantee = Potion de boost de sante-2 (120 secondes)" +lore = ["Potion de soin instantane + Pomme doree = Potion de boost de sante (120 secondes)", "Potion de soin instantane + Pomme doree enchantee = Potion de boost de sante-2 (120 secondes)"] [brewing.decay] - name = "Decomposition en bouteille" - description = "Qui aurait cru que les detritus seraient si utiles ?" - lore1 = "Potion de faiblesse + Pomme de terre empoisonnee = Potion de Wither (16 secondes)" - lore2 = "Potion de faiblesse + Racines cramoisies = Potion de Wither-2 (8 secondes)" - lore = ["Potion de faiblesse + Pomme de terre empoisonnee = Potion de Wither (16 secondes)", "Potion de faiblesse + Racines cramoisies = Potion de Wither-2 (8 secondes)"] +name = "Decomposition en bouteille" +description = "Qui aurait cru que les detritus seraient si utiles ?" +lore1 = "Potion de faiblesse + Pomme de terre empoisonnee = Potion de Wither (16 secondes)" +lore2 = "Potion de faiblesse + Racines cramoisies = Potion de Wither-2 (8 secondes)" +lore = ["Potion de faiblesse + Pomme de terre empoisonnee = Potion de Wither (16 secondes)", "Potion de faiblesse + Racines cramoisies = Potion de Wither-2 (8 secondes)"] [brewing.saturation] - name = "Saturation en bouteille" - description = "Tu sais quoi... j'ai meme pas faim..." - lore1 = "Potion de regeneration + Pomme de terre cuite = Potion de saturation" - lore2 = "Potion de regeneration + Botte de foin = Potion de saturation-2" - lore = ["Potion de regeneration + Pomme de terre cuite = Potion de saturation", "Potion de regeneration + Botte de foin = Potion de saturation-2"] +name = "Saturation en bouteille" +description = "Tu sais quoi... j'ai meme pas faim..." +lore1 = "Potion de regeneration + Pomme de terre cuite = Potion de saturation" +lore2 = "Potion de regeneration + Botte de foin = Potion de saturation-2" +lore = ["Potion de regeneration + Pomme de terre cuite = Potion de saturation", "Potion de regeneration + Botte de foin = Potion de saturation-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Chaines de Mephistopheles" - description = "Vous permet de fabriquer une armure en cotte de mailles" - lore1 = "La recette de fabrication est la meme que les autres, mais avec des pepites de fer a la place" - lore = ["La recette de fabrication est la meme que les autres, mais avec des pepites de fer a la place"] +name = "Chaines de Mephistopheles" +description = "Vous permet de fabriquer une armure en cotte de mailles" +lore1 = "La recette de fabrication est la meme que les autres, mais avec des pepites de fer a la place" +lore = ["La recette de fabrication est la meme que les autres, mais avec des pepites de fer a la place"] [blocking.horse_armorer] - name = "Armure de cheval fabricable" - description = "Vous permet de fabriquer une armure de cheval" - lore1 = "Entourez une selle avec le materiau que vous souhaitez utiliser pour fabriquer l'armure" - lore = ["Entourez une selle avec le materiau que vous souhaitez utiliser pour fabriquer l'armure"] +name = "Armure de cheval fabricable" +description = "Vous permet de fabriquer une armure de cheval" +lore1 = "Entourez une selle avec le materiau que vous souhaitez utiliser pour fabriquer l'armure" +lore = ["Entourez une selle avec le materiau que vous souhaitez utiliser pour fabriquer l'armure"] [blocking.saddle_crafter] - name = "Selle fabricable" - description = "Fabriquez une selle avec du cuir" - lore1 = "Recette : 5 Cuir :" - lore = ["Recette : 5 Cuir :"] +name = "Selle fabricable" +description = "Fabriquez une selle avec du cuir" +lore1 = "Recette : 5 Cuir :" +lore = ["Recette : 5 Cuir :"] [blocking.multi_armor] - name = "Multi-armure" - description = "Liez des Elytres a une armure" - lore1 = "C'est une competence incroyable pour voyager." - lore2 = "Fusionnez et changez dynamiquement Armure/Elytre a la volee !" - lore3 = "Pour fusionner, Maj-cliquez un objet sur un autre dans votre inventaire." - lore4 = "Pour delier l'armure, lacher l'objet en etant accroupi, et il se desassemblera." - lore5 = "Si votre Multi-Armure est detruite, vous perdrez tous les objets qu'elle contient." - lore6 = "Je n'ai pas besoin d'armure, ca me decoit..." - lore = ["C'est une competence incroyable pour voyager.", "Fusionnez et changez dynamiquement Armure/Elytre a la volee !", "Pour fusionner, Maj-cliquez un objet sur un autre dans votre inventaire.", "Pour delier l'armure, lacher l'objet en etant accroupi, et il se desassemblera.", "Si votre Multi-Armure est detruite, vous perdrez tous les objets qu'elle contient.", "Je n'ai pas besoin d'armure, ca me decoit..."] +name = "Multi-armure" +description = "Liez des Elytres a une armure" +lore1 = "C'est une competence incroyable pour voyager." +lore2 = "Fusionnez et changez dynamiquement Armure/Elytre a la volee !" +lore3 = "Pour fusionner, Maj-cliquez un objet sur un autre dans votre inventaire." +lore4 = "Pour delier l'armure, lacher l'objet en etant accroupi, et il se desassemblera." +lore5 = "Si votre Multi-Armure est detruite, vous perdrez tous les objets qu'elle contient." +lore6 = "Je n'ai pas besoin d'armure, ca me decoit..." +lore = ["C'est une competence incroyable pour voyager.", "Fusionnez et changez dynamiquement Armure/Elytre a la volee !", "Pour fusionner, Maj-cliquez un objet sur un autre dans votre inventaire.", "Pour delier l'armure, lacher l'objet en etant accroupi, et il se desassemblera.", "Si votre Multi-Armure est detruite, vous perdrez tous les objets qu'elle contient.", "Je n'ai pas besoin d'armure, ca me decoit..."] # crafting [crafting] [crafting.deconstruction] - name = "Deconstruction" - description = "Deconstruisez des blocs et objets en composants de base recuperables !" - lore1 = "Lachez n'importe quel objet au sol." - lore2 = "Puis, accroupissez-vous et faites un clic droit avec des cisailles" - lore = ["Lachez n'importe quel objet au sol.", "Puis, accroupissez-vous et faites un clic droit avec des cisailles"] +name = "Deconstruction" +description = "Deconstruisez des blocs et objets en composants de base recuperables !" +lore1 = "Lachez n'importe quel objet au sol." +lore2 = "Puis, accroupissez-vous et faites un clic droit avec des cisailles" +lore = ["Lachez n'importe quel objet au sol.", "Puis, accroupissez-vous et faites un clic droit avec des cisailles"] [crafting.xp] - name = "XP d'artisanat" - description = "Gagnez de l'XP passive en fabriquant" - lore1 = "Gagnez de l'XP en fabriquant" - lore = ["Gagnez de l'XP en fabriquant"] +name = "XP d'artisanat" +description = "Gagnez de l'XP passive en fabriquant" +lore1 = "Gagnez de l'XP en fabriquant" +lore = ["Gagnez de l'XP en fabriquant"] [crafting.reconstruction] - name = "Reconstruction de minerai" - description = "Refabriquez des minerais a partir de leurs composants de base !" - lore1 = "8 des drops et 1 hote = 1 minerai (sans forme)" - lore2 = "Les drops doivent etre fondus (le cas echeant)" - lore3 = "Non inclus : debris, quartz, emeraudes, etc." - lore4 = "Hote = enveloppe. Ex : Pierre, Netherrack, Ardoise des abimes" - lore = ["8 des drops et 1 hote = 1 minerai (sans forme)", "Les drops doivent etre fondus (le cas echeant)", "Non inclus : debris, quartz, emeraudes, etc.", "Hote = enveloppe. Ex : Pierre, Netherrack, Ardoise des abimes"] +name = "Reconstruction de minerai" +description = "Refabriquez des minerais a partir de leurs composants de base !" +lore1 = "8 des drops et 1 hote = 1 minerai (sans forme)" +lore2 = "Les drops doivent etre fondus (le cas echeant)" +lore3 = "Non inclus : debris, quartz, emeraudes, etc." +lore4 = "Hote = enveloppe. Ex : Pierre, Netherrack, Ardoise des abimes" +lore = ["8 des drops et 1 hote = 1 minerai (sans forme)", "Les drops doivent etre fondus (le cas echeant)", "Non inclus : debris, quartz, emeraudes, etc.", "Hote = enveloppe. Ex : Pierre, Netherrack, Ardoise des abimes"] [crafting.leather] - name = "Cuir fabricable" - description = "Fabriquez du cuir a partir de chair putrefiee" - lore1 = "Jetez-la (la chair putrefiee) sur le feu de camp !" - lore = ["Jetez-la (la chair putrefiee) sur le feu de camp !"] +name = "Cuir fabricable" +description = "Fabriquez du cuir a partir de chair putrefiee" +lore1 = "Jetez-la (la chair putrefiee) sur le feu de camp !" +lore = ["Jetez-la (la chair putrefiee) sur le feu de camp !"] [crafting.backpacks] - name = "Sacs a dos du Boutilier !" - description = "Cela ajoute simplement le Baluchon de Mojang au jeu !" - lore1 = "Vous devez etre en mode Survie pour l'utiliser" - lore2 = "XLX : Cuir, Laisse, Cuir" - lore3 = "XSX : Cuir, Tonneau, Cuir" - lore4 = "XCX : Cuir, Coffre, Cuir" - lore = ["Vous devez etre en mode Survie pour l'utiliser", "XLX : Cuir, Laisse, Cuir", "XSX : Cuir, Tonneau, Cuir", "XCX : Cuir, Coffre, Cuir"] +name = "Sacs a dos du Boutilier !" +description = "Cela ajoute simplement le Baluchon de Mojang au jeu !" +lore1 = "Vous devez etre en mode Survie pour l'utiliser" +lore2 = "XLX : Cuir, Laisse, Cuir" +lore3 = "XSX : Cuir, Tonneau, Cuir" +lore4 = "XCX : Cuir, Coffre, Cuir" +lore = ["Vous devez etre en mode Survie pour l'utiliser", "XLX : Cuir, Laisse, Cuir", "XSX : Cuir, Tonneau, Cuir", "XCX : Cuir, Coffre, Cuir"] [crafting.stations] - name = "Tables portables !" - description = "Utilisez une table dans la paume de votre main !" - lore2 = "TOUS LES OBJETS OUBLIES DANS LA TABLE A SA FERMETURE SONT PERDUS A JAMAIS !" - lore3 = "Tables valides : Enclume, Etabli, Meule, Table de cartographie, Tailleur de pierre, Metier a tisser" - lore = ["TOUS LES OBJETS OUBLIES DANS LA TABLE A SA FERMETURE SONT PERDUS A JAMAIS !", "Tables valides : Enclume, Etabli, Meule, Table de cartographie, Tailleur de pierre, Metier a tisser"] +name = "Tables portables !" +description = "Utilisez une table dans la paume de votre main !" +lore2 = "TOUS LES OBJETS OUBLIES DANS LA TABLE A SA FERMETURE SONT PERDUS A JAMAIS !" +lore3 = "Tables valides : Enclume, Etabli, Meule, Table de cartographie, Tailleur de pierre, Metier a tisser" +lore = ["TOUS LES OBJETS OUBLIES DANS LA TABLE A SA FERMETURE SONT PERDUS A JAMAIS !", "Tables valides : Enclume, Etabli, Meule, Table de cartographie, Tailleur de pierre, Metier a tisser"] [crafting.skulls] - name = "Cranes fabricables !" - description = "En utilisant des materiaux, vous pouvez fabriquer des cranes de monstres !" - lore1 = "Entourez un bloc d'os avec les elements suivants pour obtenir un crane :" - lore2 = "Zombie : Chair putrefiee" - lore3 = "Squelette : Os" - lore4 = "Creeper : Poudre a canon" - lore5 = "Wither : Brique du Nether" - lore6 = "Dragon : Souffle du dragon" - lore = ["Entourez un bloc d'os avec les elements suivants pour obtenir un crane :", "Zombie : Chair putrefiee", "Squelette : Os", "Creeper : Poudre a canon", "Wither : Brique du Nether", "Dragon : Souffle du dragon"] +name = "Cranes fabricables !" +description = "En utilisant des materiaux, vous pouvez fabriquer des cranes de monstres !" +lore1 = "Entourez un bloc d'os avec les elements suivants pour obtenir un crane :" +lore2 = "Zombie : Chair putrefiee" +lore3 = "Squelette : Os" +lore4 = "Creeper : Poudre a canon" +lore5 = "Wither : Brique du Nether" +lore6 = "Dragon : Souffle du dragon" +lore = ["Entourez un bloc d'os avec les elements suivants pour obtenir un crane :", "Zombie : Chair putrefiee", "Squelette : Os", "Creeper : Poudre a canon", "Wither : Brique du Nether", "Dragon : Souffle du dragon"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Temps en bouteille" - description = "Transportez une bouteille temporelle qui stocke du temps et depensez-le pour accelerer les blocs temporises, les cultures et les entites vieillissantes comme les bebes animaux. Recette (sans forme) : Potion de rapidite + Horloge + Fiole en verre." - lore1 = "Secondes stockees chargees a chaque tick" - lore2 = "Acceleration temporelle par seconde stockee" - lore3 = "Recette (sans forme) : Potion de rapidite + Horloge + Fiole en verre" - lore = ["Secondes stockees chargees a chaque tick", "Acceleration temporelle par seconde stockee", "Recette (sans forme) : Potion de rapidite + Horloge + Fiole en verre"] +name = "Temps en bouteille" +description = "Transportez une bouteille temporelle qui stocke du temps et depensez-le pour accelerer les blocs temporises, les cultures et les entites vieillissantes comme les bebes animaux. Recette (sans forme) : Potion de rapidite + Horloge + Fiole en verre." +lore1 = "Secondes stockees chargees a chaque tick" +lore2 = "Acceleration temporelle par seconde stockee" +lore3 = "Recette (sans forme) : Potion de rapidite + Horloge + Fiole en verre" +lore = ["Secondes stockees chargees a chaque tick", "Acceleration temporelle par seconde stockee", "Recette (sans forme) : Potion de rapidite + Horloge + Fiole en verre"] [chronos.aberrant_touch] - name = "Toucher aberrant" - description = "Les attaques en melee appliquent une lenteur cumulative au cout de la faim, avec des plafonds stricts en JcJ, et immobilisent les cibles a 5 charges." - lore1 = "Les attaques en melee appliquent une lenteur cumulative" - lore2 = "Plafond de duree de lenteur en JcE" - lore3 = "Plafond d'amplificateur de lenteur en JcJ" - lore = ["Les attaques en melee appliquent une lenteur cumulative", "Plafond de duree de lenteur en JcE", "Plafond d'amplificateur de lenteur en JcJ"] +name = "Toucher aberrant" +description = "Les attaques en melee appliquent une lenteur cumulative au cout de la faim, avec des plafonds stricts en JcJ, et immobilisent les cibles a 5 charges." +lore1 = "Les attaques en melee appliquent une lenteur cumulative" +lore2 = "Plafond de duree de lenteur en JcE" +lore3 = "Plafond d'amplificateur de lenteur en JcJ" +lore = ["Les attaques en melee appliquent une lenteur cumulative", "Plafond de duree de lenteur en JcE", "Plafond d'amplificateur de lenteur en JcJ"] [chronos.instant_recall] - name = "Rappel instantane" - description = "Clic gauche ou droit avec une horloge en main pour revenir a un instantane recent avec sante et faim restaurees." - lore1 = "Duree du retour en arriere" - lore2 = "Temps de recharge" - lore3 = "Pas de retour d'inventaire" - lore = ["Duree du retour en arriere", "Temps de recharge", "Pas de retour d'inventaire"] +name = "Rappel instantane" +description = "Clic gauche ou droit avec une horloge en main pour revenir a un instantane recent avec sante et faim restaurees." +lore1 = "Duree du retour en arriere" +lore2 = "Temps de recharge" +lore3 = "Pas de retour d'inventaire" +lore = ["Duree du retour en arriere", "Temps de recharge", "Pas de retour d'inventaire"] [chronos.time_bomb] - name = "Bombe temporelle" - description = "Lancez une bombe chrono fabriquee qui cree un champ temporel, ralentit les entites et gele les projectiles." - lore1 = "Rayon du champ temporel" - lore2 = "Duree du champ temporel" - lore3 = "Temps de recharge de la bombe" - lore4 = "Recette (sans forme) : Horloge + Boule de neige + Diamant + Sable" - lore = ["Rayon du champ temporel", "Duree du champ temporel", "Temps de recharge de la bombe", "Recette (sans forme) : Horloge + Boule de neige + Diamant + Sable"] +name = "Bombe temporelle" +description = "Lancez une bombe chrono fabriquee qui cree un champ temporel, ralentit les entites et gele les projectiles." +lore1 = "Rayon du champ temporel" +lore2 = "Duree du champ temporel" +lore3 = "Temps de recharge de la bombe" +lore4 = "Recette (sans forme) : Horloge + Boule de neige + Diamant + Sable" +lore = ["Rayon du champ temporel", "Duree du champ temporel", "Temps de recharge de la bombe", "Recette (sans forme) : Horloge + Boule de neige + Diamant + Sable"] # discovery [discovery] [discovery.armor] - name = "Armure du monde" - description = "Armure passive selon la durete des blocs environnants." - lore1 = "Armure passive" - lore2 = "Basee sur la durete des blocs a proximite" - lore3 = "Force de l'armure :" - lore = ["Armure passive", "Basee sur la durete des blocs a proximite", "Force de l'armure :"] +name = "Armure du monde" +description = "Armure passive selon la durete des blocs environnants." +lore1 = "Armure passive" +lore2 = "Basee sur la durete des blocs a proximite" +lore3 = "Force de l'armure :" +lore = ["Armure passive", "Basee sur la durete des blocs a proximite", "Force de l'armure :"] [discovery.unity] - name = "Unite experimentale" - description = "Ramasser des orbes d'experience ajoute de l'XP a des competences aleatoires." - lore1 = "XP " - lore2 = "Par orbe" - lore = ["XP ", "Par orbe"] +name = "Unite experimentale" +description = "Ramasser des orbes d'experience ajoute de l'XP a des competences aleatoires." +lore1 = "XP " +lore2 = "Par orbe" +lore = ["XP ", "Par orbe"] [discovery.resist] - name = "Resistance experimentale" - description = "Consommez de l'experience pour attenuer les degats uniquement quand un coup vous ferait tomber en dessous de 5 coeurs ou vous tuerait." - lore0 = "Se declenche uniquement a sante critique (<= 5 coeurs) une fois toutes les 15 secondes" - lore1 = " Degats reduits" - lore2 = "experience drainnee" - lore = ["Se declenche uniquement a sante critique (<= 5 coeurs) une fois toutes les 15 secondes", " Degats reduits", "experience drainnee"] +name = "Resistance experimentale" +description = "Consommez de l'experience pour attenuer les degats uniquement quand un coup vous ferait tomber en dessous de 5 coeurs ou vous tuerait." +lore0 = "Se declenche uniquement a sante critique (<= 5 coeurs) une fois toutes les 15 secondes" +lore1 = " Degats reduits" +lore2 = "experience drainnee" +lore = ["Se declenche uniquement a sante critique (<= 5 coeurs) une fois toutes les 15 secondes", " Degats reduits", "experience drainnee"] [discovery.villager] - name = "Attraction villageoise" - description = "Vous permet d'obtenir de meilleurs echanges avec les villageois !" - lore1 = "Cela consomme de l'XP par interaction avec les villageois" - lore2 = "Chance par interaction de consommer de l'XP et d'ameliorer les echanges" - lore3 = "XP requise drainee par interaction" - lore = ["Cela consomme de l'XP par interaction avec les villageois", "Chance par interaction de consommer de l'XP et d'ameliorer les echanges", "XP requise drainee par interaction"] +name = "Attraction villageoise" +description = "Vous permet d'obtenir de meilleurs echanges avec les villageois !" +lore1 = "Cela consomme de l'XP par interaction avec les villageois" +lore2 = "Chance par interaction de consommer de l'XP et d'ameliorer les echanges" +lore3 = "XP requise drainee par interaction" +lore = ["Cela consomme de l'XP par interaction avec les villageois", "Chance par interaction de consommer de l'XP et d'ameliorer les echanges", "XP requise drainee par interaction"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Retour de lapis" - description = "Au cout d'1 niveau d'XP supplementaire, avec une chance de vous donner du lapis gratuit en retour" - lore1 = "Pour chaque niveau, le cout d'enchantement augmente de 1, mais peut rendre jusqu'a 3 lapis" - lore = ["Pour chaque niveau, le cout d'enchantement augmente de 1, mais peut rendre jusqu'a 3 lapis"] +name = "Retour de lapis" +description = "Au cout d'1 niveau d'XP supplementaire, avec une chance de vous donner du lapis gratuit en retour" +lore1 = "Pour chaque niveau, le cout d'enchantement augmente de 1, mais peut rendre jusqu'a 3 lapis" +lore = ["Pour chaque niveau, le cout d'enchantement augmente de 1, mais peut rendre jusqu'a 3 lapis"] [enchanting.quick_enchant] - name = "Enchantement rapide au clic" - description = "Enchantez des objets en cliquant directement sur eux avec des livres d'enchantement." - lore1 = "Niveaux combines maximum" - lore2 = "Impossible d'enchanter un objet avec plus de " - lore3 = "puissance" - lore = ["Niveaux combines maximum", "Impossible d'enchanter un objet avec plus de ", "puissance"] +name = "Enchantement rapide au clic" +description = "Enchantez des objets en cliquant directement sur eux avec des livres d'enchantement." +lore1 = "Niveaux combines maximum" +lore2 = "Impossible d'enchanter un objet avec plus de " +lore3 = "puissance" +lore = ["Niveaux combines maximum", "Impossible d'enchanter un objet avec plus de ", "puissance"] [enchanting.return] - name = "Retour d'XP" - description = "L'XP d'enchantement vous est rendue lorsque vous enchantez un objet." - lore1 = "L'experience depensee a une chance d'etre remboursee lorsque vous enchantez un objet" - lore2 = "Experience par enchantement" - lore = ["L'experience depensee a une chance d'etre remboursee lorsque vous enchantez un objet", "Experience par enchantement"] +name = "Retour d'XP" +description = "L'XP d'enchantement vous est rendue lorsque vous enchantez un objet." +lore1 = "L'experience depensee a une chance d'etre remboursee lorsque vous enchantez un objet" +lore2 = "Experience par enchantement" +lore = ["L'experience depensee a une chance d'etre remboursee lorsque vous enchantez un objet", "Experience par enchantement"] # excavation [excavation] [excavation.haste] - name = "Excavateur presse" - description = "Cela accelerera le processus d'excavation, avec de la HATE !" - lore1 = "Gagnez la hate en excavant" - lore2 = "x niveaux de hate quand vous commencez a miner N'IMPORTE QUEL bloc." - lore = ["Gagnez la hate en excavant", "x niveaux de hate quand vous commencez a miner N'IMPORTE QUEL bloc."] +name = "Excavateur presse" +description = "Cela accelerera le processus d'excavation, avec de la HATE !" +lore1 = "Gagnez la hate en excavant" +lore2 = "x niveaux de hate quand vous commencez a miner N'IMPORTE QUEL bloc." +lore = ["Gagnez la hate en excavant", "x niveaux de hate quand vous commencez a miner N'IMPORTE QUEL bloc."] [excavation.spelunker] - name = "Speleonologue super-voyant !" - description = "Voyez les minerais avec vos yeux, mais a travers le sol !" - lore1 = "Minerai dans votre main secondaire, baies lumineuses dans votre main principale, et accroupissez-vous !" - lore2 = "Portee en blocs : " - lore3 = "Consomme une baie lumineuse a l'utilisation" - lore = ["Minerai dans votre main secondaire, baies lumineuses dans votre main principale, et accroupissez-vous !", "Portee en blocs : ", "Consomme une baie lumineuse a l'utilisation"] +name = "Speleonologue super-voyant !" +description = "Voyez les minerais avec vos yeux, mais a travers le sol !" +lore1 = "Minerai dans votre main secondaire, baies lumineuses dans votre main principale, et accroupissez-vous !" +lore2 = "Portee en blocs : " +lore3 = "Consomme une baie lumineuse a l'utilisation" +lore = ["Minerai dans votre main secondaire, baies lumineuses dans votre main principale, et accroupissez-vous !", "Portee en blocs : ", "Consomme une baie lumineuse a l'utilisation"] [excavation.drop_to_inventory] - name = "Pelle : depot direct en inventaire" +name = "Pelle : depot direct en inventaire" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "Le Leatherman luxueux et surconcu de Tackle" - lore1 = "Probablement le plus puissant, il vous permet de" - lore2 = "fusionner et changer dynamiquement vos outils a la volee, selon vos besoins." - lore3 = "Pour fusionner, Maj-cliquez un objet sur un autre dans votre inventaire." - lore4 = "Pour delier les outils, lachez l'objet en etant accroupi, et il se desassemblera." - lore5 = "Vous ne pouvez pas casser les outils dans ce Leatherman mais vous ne pouvez pas utiliser des outils casses" - lore6 = "objets fusionnables au total." - lore7 = "Vous pourriez utiliser cinq ou six outils, ou juste un seul !" - lore = ["Probablement le plus puissant, il vous permet de", "fusionner et changer dynamiquement vos outils a la volee, selon vos besoins.", "Pour fusionner, Maj-cliquez un objet sur un autre dans votre inventaire.", "Pour delier les outils, lachez l'objet en etant accroupi, et il se desassemblera.", "Vous ne pouvez pas casser les outils dans ce Leatherman mais vous ne pouvez pas utiliser des outils casses", "objets fusionnables au total.", "Vous pourriez utiliser cinq ou six outils, ou juste un seul !"] +name = "OMNI - T.O.O.L." +description = "Le Leatherman luxueux et surconcu de Tackle" +lore1 = "Probablement le plus puissant, il vous permet de" +lore2 = "fusionner et changer dynamiquement vos outils a la volee, selon vos besoins." +lore3 = "Pour fusionner, Maj-cliquez un objet sur un autre dans votre inventaire." +lore4 = "Pour delier les outils, lachez l'objet en etant accroupi, et il se desassemblera." +lore5 = "Vous ne pouvez pas casser les outils dans ce Leatherman mais vous ne pouvez pas utiliser des outils casses" +lore6 = "objets fusionnables au total." +lore7 = "Vous pourriez utiliser cinq ou six outils, ou juste un seul !" +lore = ["Probablement le plus puissant, il vous permet de", "fusionner et changer dynamiquement vos outils a la volee, selon vos besoins.", "Pour fusionner, Maj-cliquez un objet sur un autre dans votre inventaire.", "Pour delier les outils, lachez l'objet en etant accroupi, et il se desassemblera.", "Vous ne pouvez pas casser les outils dans ce Leatherman mais vous ne pouvez pas utiliser des outils casses", "objets fusionnables au total.", "Vous pourriez utiliser cinq ou six outils, ou juste un seul !"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Aura de croissance" - description = "Faites pousser la nature autour de vous dans une aura" - lore1 = "Rayon en blocs" - lore2 = "Force de l'aura de croissance" - lore3 = "Cout en nourriture" - lore = ["Rayon en blocs", "Force de l'aura de croissance", "Cout en nourriture"] +name = "Aura de croissance" +description = "Faites pousser la nature autour de vous dans une aura" +lore1 = "Rayon en blocs" +lore2 = "Force de l'aura de croissance" +lore3 = "Cout en nourriture" +lore = ["Rayon en blocs", "Force de l'aura de croissance", "Cout en nourriture"] [herbalism.hippo] - name = "Hippopotame de l'herboriste" - description = "Consommer de la nourriture vous donne plus de saturation" - lore1 = "Nourriture) points de saturation supplementaires a la consommation" - lore = ["Nourriture) points de saturation supplementaires a la consommation"] +name = "Hippopotame de l'herboriste" +description = "Consommer de la nourriture vous donne plus de saturation" +lore1 = "Nourriture) points de saturation supplementaires a la consommation" +lore = ["Nourriture) points de saturation supplementaires a la consommation"] [herbalism.myconid] - name = "Myconide de l'herboriste" - description = "Vous donne la capacite de fabriquer du mycelium" - lore1 = "N'importe quelle terre, plus un champignon brun et un rouge, fabriqueront du mycelium." - lore = ["N'importe quelle terre, plus un champignon brun et un rouge, fabriqueront du mycelium."] +name = "Myconide de l'herboriste" +description = "Vous donne la capacite de fabriquer du mycelium" +lore1 = "N'importe quelle terre, plus un champignon brun et un rouge, fabriqueront du mycelium." +lore = ["N'importe quelle terre, plus un champignon brun et un rouge, fabriqueront du mycelium."] [herbalism.terralid] - name = "Terralide de l'herboriste" - description = "Vous donne la capacite de fabriquer des blocs d'herbe" - lore1 = "Trois graines, au-dessus de 3 terres, fabriqueront 3 blocs d'herbe." - lore = ["Trois graines, au-dessus de 3 terres, fabriqueront 3 blocs d'herbe."] +name = "Terralide de l'herboriste" +description = "Vous donne la capacite de fabriquer des blocs d'herbe" +lore1 = "Trois graines, au-dessus de 3 terres, fabriqueront 3 blocs d'herbe." +lore = ["Trois graines, au-dessus de 3 terres, fabriqueront 3 blocs d'herbe."] [herbalism.cobweb] - name = "Createur de toiles" - description = "Vous donne la capacite de fabriquer des toiles d'araignee dans une table d'artisanat" - lore1 = "Neuf ficelles fabriqueront une toile d'araignee." - lore = ["Neuf ficelles fabriqueront une toile d'araignee."] +name = "Createur de toiles" +description = "Vous donne la capacite de fabriquer des toiles d'araignee dans une table d'artisanat" +lore1 = "Neuf ficelles fabriqueront une toile d'araignee." +lore = ["Neuf ficelles fabriqueront une toile d'araignee."] [herbalism.mushroom_blocks] - name = "Fabricant de champignons" - description = "Vous donne la capacite de fabriquer des blocs de champignon dans une table d'artisanat" - lore1 = "Quatre champignons pour faire un bloc, ou un bloc pour faire une tige." - lore = ["Quatre champignons pour faire un bloc, ou un bloc pour faire une tige."] +name = "Fabricant de champignons" +description = "Vous donne la capacite de fabriquer des blocs de champignon dans une table d'artisanat" +lore1 = "Quatre champignons pour faire un bloc, ou un bloc pour faire une tige." +lore = ["Quatre champignons pour faire un bloc, ou un bloc pour faire une tige."] [herbalism.drop_to_inventory] - name = "Houe : depot direct en inventaire" +name = "Houe : depot direct en inventaire" [herbalism.hungry_shield] - name = "Bouclier affame" - description = "Subissez des degats a votre faim avant votre sante." - lore1 = "Resiste par la faim" - lore = ["Resiste par la faim"] +name = "Bouclier affame" +description = "Subissez des degats a votre faim avant votre sante." +lore1 = "Resiste par la faim" +lore = ["Resiste par la faim"] [herbalism.luck] - name = "Chance de l'herboriste" - description = "Quand vous cassez de l'herbe/des fleurs, vous avez une chance d'obtenir un objet aleatoire" - lore0 = "Fleurs = Nourriture, et Herbe = Graines" - lore1 = "Chance d'obtenir un objet en cassant des fleurs" - lore2 = "Chance d'obtenir un objet en cassant de l'herbe" - lore = ["Fleurs = Nourriture, et Herbe = Graines", "Chance d'obtenir un objet en cassant des fleurs", "Chance d'obtenir un objet en cassant de l'herbe"] +name = "Chance de l'herboriste" +description = "Quand vous cassez de l'herbe/des fleurs, vous avez une chance d'obtenir un objet aleatoire" +lore0 = "Fleurs = Nourriture, et Herbe = Graines" +lore1 = "Chance d'obtenir un objet en cassant des fleurs" +lore2 = "Chance d'obtenir un objet en cassant de l'herbe" +lore = ["Fleurs = Nourriture, et Herbe = Graines", "Chance d'obtenir un objet en cassant des fleurs", "Chance d'obtenir un objet en cassant de l'herbe"] [herbalism.replant] - name = "Recolte et replantation" - description = "Clic droit sur une culture avec une houe pour la recolter et la replanter." - lore1 = "Rayon de replantation en blocs" - lore = ["Rayon de replantation en blocs"] +name = "Recolte et replantation" +description = "Clic droit sur une culture avec une houe pour la recolter et la replanter." +lore1 = "Rayon de replantation en blocs" +lore = ["Rayon de replantation en blocs"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenaline" - description = "Infligez plus de degats plus votre sante est basse (melee)" - lore1 = "Degats maximum" - lore = ["Degats maximum"] +name = "Adrenaline" +description = "Infligez plus de degats plus votre sante est basse (melee)" +lore1 = "Degats maximum" +lore = ["Degats maximum"] [hunter.penalty] - name = "" - description = "" - lore1 = "Vous gagnerez des charges de poison si vous n'avez plus de faim" - lore = ["Vous gagnerez des charges de poison si vous n'avez plus de faim"] +name = "" +description = "" +lore1 = "Vous gagnerez des charges de poison si vous n'avez plus de faim" +lore = ["Vous gagnerez des charges de poison si vous n'avez plus de faim"] [hunter.drop_to_inventory] - name = "Objets : depot direct en inventaire" - description = "Quand vous tuez quelque chose / cassez un bloc avec une epee, les drops sont teleportes dans votre inventaire" - lore1 = "Chaque fois qu'un objet tombe d'un mob/bloc que vous cassez, il va dans votre inventaire si possible." - lore = ["Chaque fois qu'un objet tombe d'un mob/bloc que vous cassez, il va dans votre inventaire si possible."] +name = "Objets : depot direct en inventaire" +description = "Quand vous tuez quelque chose / cassez un bloc avec une epee, les drops sont teleportes dans votre inventaire" +lore1 = "Chaque fois qu'un objet tombe d'un mob/bloc que vous cassez, il va dans votre inventaire si possible." +lore = ["Chaque fois qu'un objet tombe d'un mob/bloc que vous cassez, il va dans votre inventaire si possible."] [hunter.invisibility] - name = "Pas evanescent" - description = "Quand vous etes frappe, vous gagnez l'invisibilite, au cout de la faim" - lore1 = "Gagnez l'invisibilite passive quand vous etes frappe" - lore2 = "x charges d'invisibilite pendant 3 secondes quand vous etes touche" - lore3 = "x faim cumulative" - lore4 = "La faim cumule duree et multiplicateur." - lore5 = "Duree d'invisibilite" - lore = ["Gagnez l'invisibilite passive quand vous etes frappe", "x charges d'invisibilite pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "Duree d'invisibilite"] +name = "Pas evanescent" +description = "Quand vous etes frappe, vous gagnez l'invisibilite, au cout de la faim" +lore1 = "Gagnez l'invisibilite passive quand vous etes frappe" +lore2 = "x charges d'invisibilite pendant 3 secondes quand vous etes touche" +lore3 = "x faim cumulative" +lore4 = "La faim cumule duree et multiplicateur." +lore5 = "Duree d'invisibilite" +lore = ["Gagnez l'invisibilite passive quand vous etes frappe", "x charges d'invisibilite pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "Duree d'invisibilite"] [hunter.jump_boost] - name = "Hauteurs du chasseur" - description = "Quand vous etes frappe, vous gagnez un boost de saut, au cout de la faim" - lore1 = "Gagnez un boost de saut passif quand vous etes frappe" - lore2 = "x charges de boost de saut pendant 3 secondes quand vous etes touche" - lore3 = "x faim cumulative" - lore4 = "La faim cumule duree et multiplicateur." - lore5 = "Le boost de saut cumule le multiplicateur, pas la duree." - lore = ["Gagnez un boost de saut passif quand vous etes frappe", "x charges de boost de saut pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "Le boost de saut cumule le multiplicateur, pas la duree."] +name = "Hauteurs du chasseur" +description = "Quand vous etes frappe, vous gagnez un boost de saut, au cout de la faim" +lore1 = "Gagnez un boost de saut passif quand vous etes frappe" +lore2 = "x charges de boost de saut pendant 3 secondes quand vous etes touche" +lore3 = "x faim cumulative" +lore4 = "La faim cumule duree et multiplicateur." +lore5 = "Le boost de saut cumule le multiplicateur, pas la duree." +lore = ["Gagnez un boost de saut passif quand vous etes frappe", "x charges de boost de saut pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "Le boost de saut cumule le multiplicateur, pas la duree."] [hunter.luck] - name = "Chance du chasseur" - description = "Quand vous etes frappe, vous gagnez de la chance, au cout de la faim" - lore1 = "Gagnez de la chance passive quand vous etes frappe" - lore2 = "x charges de chance pendant 3 secondes quand vous etes touche" - lore3 = "x faim cumulative" - lore4 = "La faim cumule duree et multiplicateur." - lore5 = "La chance cumule le multiplicateur, pas la duree." - lore = ["Gagnez de la chance passive quand vous etes frappe", "x charges de chance pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La chance cumule le multiplicateur, pas la duree."] +name = "Chance du chasseur" +description = "Quand vous etes frappe, vous gagnez de la chance, au cout de la faim" +lore1 = "Gagnez de la chance passive quand vous etes frappe" +lore2 = "x charges de chance pendant 3 secondes quand vous etes touche" +lore3 = "x faim cumulative" +lore4 = "La faim cumule duree et multiplicateur." +lore5 = "La chance cumule le multiplicateur, pas la duree." +lore = ["Gagnez de la chance passive quand vous etes frappe", "x charges de chance pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La chance cumule le multiplicateur, pas la duree."] [hunter.regen] - name = "Regeneration du chasseur" - description = "Quand vous etes frappe, vous gagnez de la regeneration, au cout de la faim" - lore1 = "Gagnez de la regeneration passive quand vous etes frappe" - lore2 = "x charges de regeneration pendant 3 secondes quand vous etes touche" - lore3 = "x faim cumulative" - lore4 = "La faim cumule duree et multiplicateur." - lore5 = "La regeneration cumule le multiplicateur, pas la duree." - lore = ["Gagnez de la regeneration passive quand vous etes frappe", "x charges de regeneration pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La regeneration cumule le multiplicateur, pas la duree."] +name = "Regeneration du chasseur" +description = "Quand vous etes frappe, vous gagnez de la regeneration, au cout de la faim" +lore1 = "Gagnez de la regeneration passive quand vous etes frappe" +lore2 = "x charges de regeneration pendant 3 secondes quand vous etes touche" +lore3 = "x faim cumulative" +lore4 = "La faim cumule duree et multiplicateur." +lore5 = "La regeneration cumule le multiplicateur, pas la duree." +lore = ["Gagnez de la regeneration passive quand vous etes frappe", "x charges de regeneration pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La regeneration cumule le multiplicateur, pas la duree."] [hunter.resistance] - name = "Resistance du chasseur" - description = "Quand vous etes frappe, vous gagnez de la resistance, au cout de la faim" - lore1 = "Gagnez de la resistance passive quand vous etes frappe" - lore2 = "x charges de resistance pendant 3 secondes quand vous etes touche" - lore3 = "x faim cumulative" - lore4 = "La faim cumule duree et multiplicateur." - lore5 = "La resistance cumule le multiplicateur, pas la duree." - lore = ["Gagnez de la resistance passive quand vous etes frappe", "x charges de resistance pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La resistance cumule le multiplicateur, pas la duree."] +name = "Resistance du chasseur" +description = "Quand vous etes frappe, vous gagnez de la resistance, au cout de la faim" +lore1 = "Gagnez de la resistance passive quand vous etes frappe" +lore2 = "x charges de resistance pendant 3 secondes quand vous etes touche" +lore3 = "x faim cumulative" +lore4 = "La faim cumule duree et multiplicateur." +lore5 = "La resistance cumule le multiplicateur, pas la duree." +lore = ["Gagnez de la resistance passive quand vous etes frappe", "x charges de resistance pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La resistance cumule le multiplicateur, pas la duree."] [hunter.speed] - name = "Vitesse du chasseur" - description = "Quand vous etes frappe, vous gagnez de la vitesse, au cout de la faim" - lore1 = "Gagnez de la vitesse passive quand vous etes frappe" - lore2 = "x charges de vitesse pendant 3 secondes quand vous etes touche" - lore3 = "x faim cumulative" - lore4 = "La faim cumule duree et multiplicateur." - lore5 = "La vitesse cumule le multiplicateur, pas la duree." - lore = ["Gagnez de la vitesse passive quand vous etes frappe", "x charges de vitesse pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La vitesse cumule le multiplicateur, pas la duree."] +name = "Vitesse du chasseur" +description = "Quand vous etes frappe, vous gagnez de la vitesse, au cout de la faim" +lore1 = "Gagnez de la vitesse passive quand vous etes frappe" +lore2 = "x charges de vitesse pendant 3 secondes quand vous etes touche" +lore3 = "x faim cumulative" +lore4 = "La faim cumule duree et multiplicateur." +lore5 = "La vitesse cumule le multiplicateur, pas la duree." +lore = ["Gagnez de la vitesse passive quand vous etes frappe", "x charges de vitesse pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La vitesse cumule le multiplicateur, pas la duree."] [hunter.strength] - name = "Force du chasseur" - description = "Quand vous etes frappe, vous gagnez de la force, au cout de la faim" - lore1 = "Gagnez de la force passive quand vous etes frappe" - lore2 = "x charges de force pendant 3 secondes quand vous etes touche" - lore3 = "x faim cumulative" - lore4 = "La faim cumule duree et multiplicateur." - lore5 = "La force cumule le multiplicateur, pas la duree." - lore = ["Gagnez de la force passive quand vous etes frappe", "x charges de force pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La force cumule le multiplicateur, pas la duree."] +name = "Force du chasseur" +description = "Quand vous etes frappe, vous gagnez de la force, au cout de la faim" +lore1 = "Gagnez de la force passive quand vous etes frappe" +lore2 = "x charges de force pendant 3 secondes quand vous etes touche" +lore3 = "x faim cumulative" +lore4 = "La faim cumule duree et multiplicateur." +lore5 = "La force cumule le multiplicateur, pas la duree." +lore = ["Gagnez de la force passive quand vous etes frappe", "x charges de force pendant 3 secondes quand vous etes touche", "x faim cumulative", "La faim cumule duree et multiplicateur.", "La force cumule le multiplicateur, pas la duree."] # nether [nether] [nether.skull_toss] - name = "Lancer de crane de Wither" - description1 = "Liberez votre Wither interieur en utilisant" - description2 = "la tete de" - description3 = "quelqu'un." - lore1 = "Secondes de temps de recharge entre les lancers de crane." - lore2 = "Avec un crane de Wither : Lancez un " - lore3 = "Crane de Wither" - lore4 = "qui explose a l'impact." - lore = ["Secondes de temps de recharge entre les lancers de crane.", "Avec un crane de Wither : Lancez un ", "Crane de Wither", "qui explose a l'impact."] +name = "Lancer de crane de Wither" +description1 = "Liberez votre Wither interieur en utilisant" +description2 = "la tete de" +description3 = "quelqu'un." +lore1 = "Secondes de temps de recharge entre les lancers de crane." +lore2 = "Avec un crane de Wither : Lancez un " +lore3 = "Crane de Wither" +lore4 = "qui explose a l'impact." +lore = ["Secondes de temps de recharge entre les lancers de crane.", "Avec un crane de Wither : Lancez un ", "Crane de Wither", "qui explose a l'impact."] [nether.wither_resist] - name = "Resistance au Wither" - description = "Resiste au fletrissement grace au pouvoir de la Netherite." - lore1 = "chance d'annuler le fletrissement (par piece)." - lore2 = "Passif : Porter une armure en Netherite a une chance d'annuler " - lore3 = "le fletrissement." - lore = ["chance d'annuler le fletrissement (par piece).", "Passif : Porter une armure en Netherite a une chance d'annuler ", "le fletrissement."] +name = "Resistance au Wither" +description = "Resiste au fletrissement grace au pouvoir de la Netherite." +lore1 = "chance d'annuler le fletrissement (par piece)." +lore2 = "Passif : Porter une armure en Netherite a une chance d'annuler " +lore3 = "le fletrissement." +lore = ["chance d'annuler le fletrissement (par piece).", "Passif : Porter une armure en Netherite a une chance d'annuler ", "le fletrissement."] [nether.fire_resist] - name = "Resistance au feu" - description = "Resiste au feu en durcissant votre peau." - lore1 = "chance d'annuler l'effet de brulure !" - lore = ["chance d'annuler l'effet de brulure !"] +name = "Resistance au feu" +description = "Resiste au feu en durcissant votre peau." +lore1 = "chance d'annuler l'effet de brulure !" +lore = ["chance d'annuler l'effet de brulure !"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Fonte automatique" - description = "Vous permet de fondre les minerais standards mines" - lore1 = "Les minerais pouvant etre fondus le sont automatiquement" - lore2 = "% de chance d'en obtenir un supplementaire" - lore = ["Les minerais pouvant etre fondus le sont automatiquement", "% de chance d'en obtenir un supplementaire"] +name = "Fonte automatique" +description = "Vous permet de fondre les minerais standards mines" +lore1 = "Les minerais pouvant etre fondus le sont automatiquement" +lore2 = "% de chance d'en obtenir un supplementaire" +lore = ["Les minerais pouvant etre fondus le sont automatiquement", "% de chance d'en obtenir un supplementaire"] [pickaxe.chisel] - name = "Ciseau a minerai" - description = "Clic droit sur les minerais pour en extraire davantage, au prix d'une forte usure de durabilite." - lore1 = "Chance de drop" - lore2 = "Usure de l'outil" - lore = ["Chance de drop", "Usure de l'outil"] +name = "Ciseau a minerai" +description = "Clic droit sur les minerais pour en extraire davantage, au prix d'une forte usure de durabilite." +lore1 = "Chance de drop" +lore2 = "Usure de l'outil" +lore = ["Chance de drop", "Usure de l'outil"] [pickaxe.drop_to_inventory] - name = "Pioche : depot direct en inventaire" - description = "Quand vous cassez un bloc, l'objet est teleporte dans votre inventaire" - lore1 = "Chaque fois qu'un objet tombe d'un bloc que vous cassez, il va dans votre inventaire si possible." - lore = ["Chaque fois qu'un objet tombe d'un bloc que vous cassez, il va dans votre inventaire si possible."] +name = "Pioche : depot direct en inventaire" +description = "Quand vous cassez un bloc, l'objet est teleporte dans votre inventaire" +lore1 = "Chaque fois qu'un objet tombe d'un bloc que vous cassez, il va dans votre inventaire si possible." +lore = ["Chaque fois qu'un objet tombe d'un bloc que vous cassez, il va dans votre inventaire si possible."] [pickaxe.silk_spawner] - name = "Pioche toucher de soie sur les generateurs" - description = "Les generateurs de monstres tombent quand ils sont casses" - lore1 = "Rend les generateurs cassables avec Toucher de soie." - lore2 = "Rend les generateurs cassables en etant accroupi." - lore = ["Rend les generateurs cassables avec Toucher de soie.", "Rend les generateurs cassables en etant accroupi."] +name = "Pioche toucher de soie sur les generateurs" +description = "Les generateurs de monstres tombent quand ils sont casses" +lore1 = "Rend les generateurs cassables avec Toucher de soie." +lore2 = "Rend les generateurs cassables en etant accroupi." +lore = ["Rend les generateurs cassables avec Toucher de soie.", "Rend les generateurs cassables en etant accroupi."] [pickaxe.vein_miner] - name = "Mineur de filon" - description = "Vous permet de casser des blocs dans un filon/groupe de minerais standards" - lore1 = "Accroupissez-vous et minez les MINERAIS" - lore2 = "portee du minage de filon" - lore3 = "Cette competence ne regroupe PAS tous les drops ensemble !" - lore = ["Accroupissez-vous et minez les MINERAIS", "portee du minage de filon", "Cette competence ne regroupe PAS tous les drops ensemble !"] +name = "Mineur de filon" +description = "Vous permet de casser des blocs dans un filon/groupe de minerais standards" +lore1 = "Accroupissez-vous et minez les MINERAIS" +lore2 = "portee du minage de filon" +lore3 = "Cette competence ne regroupe PAS tous les drops ensemble !" +lore = ["Accroupissez-vous et minez les MINERAIS", "portee du minage de filon", "Cette competence ne regroupe PAS tous les drops ensemble !"] # ranged [ranged] [ranged.arrow_recovery] - name = "Recuperation de fleches" - description = "Recuperez des fleches apres avoir tue un ennemi." - lore1 = "Chance de recuperer des fleches en touchant/tuant" - lore2 = "Chance : " - lore = ["Chance de recuperer des fleches en touchant/tuant", "Chance : "] +name = "Recuperation de fleches" +description = "Recuperez des fleches apres avoir tue un ennemi." +lore1 = "Chance de recuperer des fleches en touchant/tuant" +lore2 = "Chance : " +lore = ["Chance de recuperer des fleches en touchant/tuant", "Chance : "] [ranged.web_shot] - name = "Piege de toile" - description = "Entourez votre cible de toiles d'araignee quand vous la touchez !" - lore1 = "8 toiles d'araignee autour d'une boule de neige, et lancez !" - lore2 = "secondes de cage, environ." - lore = ["8 toiles d'araignee autour d'une boule de neige, et lancez !", "secondes de cage, environ."] +name = "Piege de toile" +description = "Entourez votre cible de toiles d'araignee quand vous la touchez !" +lore1 = "8 toiles d'araignee autour d'une boule de neige, et lancez !" +lore2 = "secondes de cage, environ." +lore = ["8 toiles d'araignee autour d'une boule de neige, et lancez !", "secondes de cage, environ."] [ranged.force_shot] - name = "Tir puissant" - description = "Tirez des projectiles plus loin, plus vite !" - advancementname = "Tir longue distance" - advancementlore = "Touchez une cible a plus de 30 blocs de distance !" - lore1 = "Vitesse du projectile" - lore = ["Vitesse du projectile"] +name = "Tir puissant" +description = "Tirez des projectiles plus loin, plus vite !" +advancementname = "Tir longue distance" +advancementlore = "Touchez une cible a plus de 30 blocs de distance !" +lore1 = "Vitesse du projectile" +lore = ["Vitesse du projectile"] [ranged.lunge_shot] - name = "Tir en fente" - description = "En chute, vos fleches vous propulsent dans une direction aleatoire" - lore1 = "Vitesse de propulsion aleatoire" - lore = ["Vitesse de propulsion aleatoire"] +name = "Tir en fente" +description = "En chute, vos fleches vous propulsent dans une direction aleatoire" +lore1 = "Vitesse de propulsion aleatoire" +lore = ["Vitesse de propulsion aleatoire"] [ranged.arrow_piercing] - name = "Fleche percante" - description = "Ajoute le percage aux projectiles ! Tirez a travers les choses !" - lore1 = "Cibles percees" - lore = ["Cibles percees"] +name = "Fleche percante" +description = "Ajoute le percage aux projectiles ! Tirez a travers les choses !" +lore1 = "Cibles percees" +lore = ["Cibles percees"] # rift [rift] [rift.remote_access] - name = "Acces a distance" - description = "Puisez dans le vide et accedez a un conteneur marque." - lore1 = "Perle de l'Ender + Boussole = Portoloin reliquaire" - lore2 = "Cet objet vous permet d'acceder aux conteneurs a distance" - lore3 = "Une fois fabrique, regardez l'objet pour voir son utilisation" - notcontainer = "Ce n'est pas un conteneur" - lore = ["Perle de l'Ender + Boussole = Portoloin reliquaire", "Cet objet vous permet d'acceder aux conteneurs a distance", "Une fois fabrique, regardez l'objet pour voir son utilisation"] +name = "Acces a distance" +description = "Puisez dans le vide et accedez a un conteneur marque." +lore1 = "Perle de l'Ender + Boussole = Portoloin reliquaire" +lore2 = "Cet objet vous permet d'acceder aux conteneurs a distance" +lore3 = "Une fois fabrique, regardez l'objet pour voir son utilisation" +notcontainer = "Ce n'est pas un conteneur" +lore = ["Perle de l'Ender + Boussole = Portoloin reliquaire", "Cet objet vous permet d'acceder aux conteneurs a distance", "Une fois fabrique, regardez l'objet pour voir son utilisation"] [rift.blink] - name = "Eclair de Faille" - description = "Teleportation instantanee a courte portee, en un clin d'oeil !" - lore1 = "Blocs par eclair (2x vertical)" - lore2 = "En sprintant : Appuyez deux fois sur Saut pour " - lore3 = "Eclairs" - lore = ["Blocs par eclair (2x vertical)", "En sprintant : Appuyez deux fois sur Saut pour ", "Eclairs"] +name = "Eclair de Faille" +description = "Teleportation instantanee a courte portee, en un clin d'oeil !" +lore1 = "Blocs par eclair (2x vertical)" +lore2 = "En sprintant : Appuyez deux fois sur Saut pour " +lore3 = "Eclairs" +lore = ["Blocs par eclair (2x vertical)", "En sprintant : Appuyez deux fois sur Saut pour ", "Eclairs"] [rift.chest] - name = "Coffre de l'Ender facile" - description = "Ouvrez un coffre de l'Ender en faisant un clic gauche avec en main." - lore1 = "Cliquez sur un coffre de l'Ender dans votre main pour l'ouvrir (ne le placez pas)" - lore = ["Cliquez sur un coffre de l'Ender dans votre main pour l'ouvrir (ne le placez pas)"] +name = "Coffre de l'Ender facile" +description = "Ouvrez un coffre de l'Ender en faisant un clic gauche avec en main." +lore1 = "Cliquez sur un coffre de l'Ender dans votre main pour l'ouvrir (ne le placez pas)" +lore = ["Cliquez sur un coffre de l'Ender dans votre main pour l'ouvrir (ne le placez pas)"] [rift.descent] - name = "Anti-levitation" - description = "Vous en avez assez d'etre coince en l'air ? Cette competence est faite pour vous !" - lore1 = "Accroupissez-vous simplement pour descendre, et vous tomberez a une vitesse inferieure a la normale !" - lore2 = "Temps de recharge :" - lore = ["Accroupissez-vous simplement pour descendre, et vous tomberez a une vitesse inferieure a la normale !", "Temps de recharge :"] +name = "Anti-levitation" +description = "Vous en avez assez d'etre coince en l'air ? Cette competence est faite pour vous !" +lore1 = "Accroupissez-vous simplement pour descendre, et vous tomberez a une vitesse inferieure a la normale !" +lore2 = "Temps de recharge :" +lore = ["Accroupissez-vous simplement pour descendre, et vous tomberez a une vitesse inferieure a la normale !", "Temps de recharge :"] [rift.gate] - name = "Portail de Faille" - description = "Teleportez-vous a un emplacement marque." - lore1 = "FABRICATION : Emeraude + Eclat d'amethyste + Perle de l'Ender" - lore2 = "Lisez avant d'utiliser !" - lore3 = "5s de delai, " - lore4 = "vous pouvez mourir pendant cette animation" - lore = ["FABRICATION : Emeraude + Eclat d'amethyste + Perle de l'Ender", "Lisez avant d'utiliser !", "5s de delai, ", "vous pouvez mourir pendant cette animation"] +name = "Portail de Faille" +description = "Teleportez-vous a un emplacement marque." +lore1 = "FABRICATION : Emeraude + Eclat d'amethyste + Perle de l'Ender" +lore2 = "Lisez avant d'utiliser !" +lore3 = "5s de delai, " +lore4 = "vous pouvez mourir pendant cette animation" +lore = ["FABRICATION : Emeraude + Eclat d'amethyste + Perle de l'Ender", "Lisez avant d'utiliser !", "5s de delai, ", "vous pouvez mourir pendant cette animation"] [rift.resist] - name = "Resistance de Faille" - description = "Gagnez de la resistance en utilisant des objets et capacites de l'Ender" - lore1 = "+ Passif : Fournit de la resistance quand vous utilisez des capacites de Faille ou des objets de l'Ender" - lore2 = "N'inclut PAS le coffre de l'Ender portable, seulement les objets consommables" - lore = ["+ Passif : Fournit de la resistance quand vous utilisez des capacites de Faille ou des objets de l'Ender", "N'inclut PAS le coffre de l'Ender portable, seulement les objets consommables"] +name = "Resistance de Faille" +description = "Gagnez de la resistance en utilisant des objets et capacites de l'Ender" +lore1 = "+ Passif : Fournit de la resistance quand vous utilisez des capacites de Faille ou des objets de l'Ender" +lore2 = "N'inclut PAS le coffre de l'Ender portable, seulement les objets consommables" +lore = ["+ Passif : Fournit de la resistance quand vous utilisez des capacites de Faille ou des objets de l'Ender", "N'inclut PAS le coffre de l'Ender portable, seulement les objets consommables"] [rift.visage] - name = "Visage de Faille" - description = "Empeche les Endermen de devenir agressifs si vous avez des perles de l'Ender dans votre inventaire." - lore1 = "Les Endermen ne deviendront pas agressifs si vous avez des perles de l'Ender dans votre inventaire." - lore = ["Les Endermen ne deviendront pas agressifs si vous avez des perles de l'Ender dans votre inventaire."] +name = "Visage de Faille" +description = "Empeche les Endermen de devenir agressifs si vous avez des perles de l'Ender dans votre inventaire." +lore1 = "Les Endermen ne deviendront pas agressifs si vous avez des perles de l'Ender dans votre inventaire." +lore = ["Les Endermen ne deviendront pas agressifs si vous avez des perles de l'Ender dans votre inventaire."] # seaborn [seaborn] [seaborn.oxygen] - name = "Reservoir d'oxygene organique" - description = "Retenez plus d'oxygene dans vos petits poumons !" - lore1 = "Augmentation de la capacite en oxygene" - lore = ["Augmentation de la capacite en oxygene"] +name = "Reservoir d'oxygene organique" +description = "Retenez plus d'oxygene dans vos petits poumons !" +lore1 = "Augmentation de la capacite en oxygene" +lore = ["Augmentation de la capacite en oxygene"] [seaborn.fishers_fantasy] - name = "Fantaisie du pecheur" - description = "Gagnez plus d'XP en pechant et obtenez plus de poisson !" - lore1 = "Pour chaque niveau, il y a une chance d'obtenir plus d'XP et de poisson !" - lore = ["Pour chaque niveau, il y a une chance d'obtenir plus d'XP et de poisson !"] +name = "Fantaisie du pecheur" +description = "Gagnez plus d'XP en pechant et obtenez plus de poisson !" +lore1 = "Pour chaque niveau, il y a une chance d'obtenir plus d'XP et de poisson !" +lore = ["Pour chaque niveau, il y a une chance d'obtenir plus d'XP et de poisson !"] [seaborn.haste] - name = "Mineur tortue" - description = "En minant sous l'eau, vous gagnez la hate !" - lore1 = "Hate 3 est appliquee sous l'eau pendant le minage (se cumule avec Affinite aquatique) apres que votre effet de respiration aquatique se dissipe !" - lore = ["Hate 3 est appliquee sous l'eau pendant le minage (se cumule avec Affinite aquatique) apres que votre effet de respiration aquatique se dissipe !"] +name = "Mineur tortue" +description = "En minant sous l'eau, vous gagnez la hate !" +lore1 = "Hate 3 est appliquee sous l'eau pendant le minage (se cumule avec Affinite aquatique) apres que votre effet de respiration aquatique se dissipe !" +lore = ["Hate 3 est appliquee sous l'eau pendant le minage (se cumule avec Affinite aquatique) apres que votre effet de respiration aquatique se dissipe !"] [seaborn.night_vision] - name = "Vision de tortue" - description = "Sous l'eau, vous gagnez la vision nocturne" - lore1 = "Gagnez simplement la vision nocturne sous l'eau apres que votre effet de respiration aquatique se dissipe !" - lore = ["Gagnez simplement la vision nocturne sous l'eau apres que votre effet de respiration aquatique se dissipe !"] +name = "Vision de tortue" +description = "Sous l'eau, vous gagnez la vision nocturne" +lore1 = "Gagnez simplement la vision nocturne sous l'eau apres que votre effet de respiration aquatique se dissipe !" +lore = ["Gagnez simplement la vision nocturne sous l'eau apres que votre effet de respiration aquatique se dissipe !"] [seaborn.dolphin_grace] - name = "Grace du dauphin" - description = "Nagez comme un dauphin, sans les dauphins" - lore1 = "+ Passif : gagnez " - lore2 = "x vitesse (grace du dauphin)" - lore3 = "Precision allemande de l'ingeni... attendez c'est pas ca... Non compatible avec Pas de profondeur" - lore = ["+ Passif : gagnez ", "x vitesse (grace du dauphin)", "Precision allemande de l'ingeni... attendez c'est pas ca... Non compatible avec Pas de profondeur"] +name = "Grace du dauphin" +description = "Nagez comme un dauphin, sans les dauphins" +lore1 = "+ Passif : gagnez " +lore2 = "x vitesse (grace du dauphin)" +lore3 = "Precision allemande de l'ingeni... attendez c'est pas ca... Non compatible avec Pas de profondeur" +lore = ["+ Passif : gagnez ", "x vitesse (grace du dauphin)", "Precision allemande de l'ingeni... attendez c'est pas ca... Non compatible avec Pas de profondeur"] # stealth [stealth] [stealth.ghost_armor] - name = "Armure fantome" - description = "Armure qui se construit lentement quand vous ne subissez pas de degats, dure 1 coup" - lore1 = "Armure maximale" - lore2 = "Vitesse" - lore = ["Armure maximale", "Vitesse"] +name = "Armure fantome" +description = "Armure qui se construit lentement quand vous ne subissez pas de degats, dure 1 coup" +lore1 = "Armure maximale" +lore2 = "Vitesse" +lore = ["Armure maximale", "Vitesse"] [stealth.night_vision] - name = "Vision furtive" - description = "Gagnez la vision nocturne en etant accroupi" - lore1 = "Gagnez une salve de " - lore2 = "vision nocturne" - lore3 = "en etant accroupi" - lore = ["Gagnez une salve de ", "vision nocturne", "en etant accroupi"] +name = "Vision furtive" +description = "Gagnez la vision nocturne en etant accroupi" +lore1 = "Gagnez une salve de " +lore2 = "vision nocturne" +lore3 = "en etant accroupi" +lore = ["Gagnez une salve de ", "vision nocturne", "en etant accroupi"] [stealth.snatch] - name = "Chapardage d'objet" - description = "Ramassez les objets au sol instantanement en etant accroupi !" - lore1 = "Rayon de chapardage" - lore = ["Rayon de chapardage"] +name = "Chapardage d'objet" +description = "Ramassez les objets au sol instantanement en etant accroupi !" +lore1 = "Rayon de chapardage" +lore = ["Rayon de chapardage"] [stealth.speed] - name = "Vitesse furtive" - description = "Gagnez de la vitesse en etant accroupi" - lore1 = "Vitesse en mode accroupi" - lore = ["Vitesse en mode accroupi"] +name = "Vitesse furtive" +description = "Gagnez de la vitesse en etant accroupi" +lore1 = "Vitesse en mode accroupi" +lore = ["Vitesse en mode accroupi"] [stealth.ender_veil] - name = "Voile de l'Ender" - description = "Plus besoin de citrouilles pour empecher les attaques d'Endermen" - lore1 = "Empeche les attaques d'Endermen en etant accroupi" - lore2 = "Empeche toutes les attaques d'Endermen" - lore = ["Empeche les attaques d'Endermen en etant accroupi", "Empeche toutes les attaques d'Endermen"] +name = "Voile de l'Ender" +description = "Plus besoin de citrouilles pour empecher les attaques d'Endermen" +lore1 = "Empeche les attaques d'Endermen en etant accroupi" +lore2 = "Empeche toutes les attaques d'Endermen" +lore = ["Empeche les attaques d'Endermen en etant accroupi", "Empeche toutes les attaques d'Endermen"] # sword [sword] [sword.machete] - name = "Machette" - description = "Tranchez le feuillage avec facilite !" - lore1 = "Rayon de tranche" - lore2 = "Temps de recharge de coupe" - lore3 = "Usure de l'outil" - lore = ["Rayon de tranche", "Temps de recharge de coupe", "Usure de l'outil"] +name = "Machette" +description = "Tranchez le feuillage avec facilite !" +lore1 = "Rayon de tranche" +lore2 = "Temps de recharge de coupe" +lore3 = "Usure de l'outil" +lore = ["Rayon de tranche", "Temps de recharge de coupe", "Usure de l'outil"] [sword.bloody_blade] - name = "Lame sanglante" - description = "Les coups d'epee provoquent le saignement !" - lore1 = "Frapper une entite vivante avec votre epee provoque le saignement" - lore2 = "Duree du saignement" - lore3 = "Temps de recharge du saignement" - lore = ["Frapper une entite vivante avec votre epee provoque le saignement", "Duree du saignement", "Temps de recharge du saignement"] +name = "Lame sanglante" +description = "Les coups d'epee provoquent le saignement !" +lore1 = "Frapper une entite vivante avec votre epee provoque le saignement" +lore2 = "Duree du saignement" +lore3 = "Temps de recharge du saignement" +lore = ["Frapper une entite vivante avec votre epee provoque le saignement", "Duree du saignement", "Temps de recharge du saignement"] [sword.poisoned_blade] - name = "Lame empoisonnee" - description = "Les coups d'epee provoquent l'empoisonnement !" - lore1 = "Frapper une entite vivante avec votre epee provoque l'empoisonnement" - lore2 = "Duree du poison" - lore3 = "Temps de recharge du poison" - lore = ["Frapper une entite vivante avec votre epee provoque l'empoisonnement", "Duree du poison", "Temps de recharge du poison"] +name = "Lame empoisonnee" +description = "Les coups d'epee provoquent l'empoisonnement !" +lore1 = "Frapper une entite vivante avec votre epee provoque l'empoisonnement" +lore2 = "Duree du poison" +lore3 = "Temps de recharge du poison" +lore = ["Frapper une entite vivante avec votre epee provoque l'empoisonnement", "Duree du poison", "Temps de recharge du poison"] # taming [taming] [taming.damage] - name = "Degats d'apprivoisement" - description = "Augmentez les degats infliges par vos animaux apprivoises." - lore1 = "Degats augmentes" - lore = ["Degats augmentes"] +name = "Degats d'apprivoisement" +description = "Augmentez les degats infliges par vos animaux apprivoises." +lore1 = "Degats augmentes" +lore = ["Degats augmentes"] [taming.health] - name = "Sante d'apprivoisement" - description = "Augmentez la sante de vos animaux apprivoises." - lore1 = "Sante augmentee" - lore = ["Sante augmentee"] +name = "Sante d'apprivoisement" +description = "Augmentez la sante de vos animaux apprivoises." +lore1 = "Sante augmentee" +lore = ["Sante augmentee"] [taming.regeneration] - name = "Regeneration d'apprivoisement" - description = "Augmentez la regeneration de vos animaux apprivoises." - lore1 = "PV/s" - lore = ["PV/s"] +name = "Regeneration d'apprivoisement" +description = "Augmentez la regeneration de vos animaux apprivoises." +lore1 = "PV/s" +lore = ["PV/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Epines" - description = "Renvoyez les degats a votre attaquant !" - lore1 = "Degats renvoyes quand vous etes frappe" - lore = ["Degats renvoyes quand vous etes frappe"] +name = "Epines" +description = "Renvoyez les degats a votre attaquant !" +lore1 = "Degats renvoyes quand vous etes frappe" +lore = ["Degats renvoyes quand vous etes frappe"] [tragoul.globe] - name = "Globe de douleur" - description = "Repartissez les degats que vous infligez selon le nombre d'ennemis autour de vous !" - lore1 = "Plus il y a d'ennemis autour de vous, moins vous infligez de degats a chacun" - lore2 = "Portee : " - lore3 = "Degats supplementaires a toutes les entites : " - lore = ["Plus il y a d'ennemis autour de vous, moins vous infligez de degats a chacun", "Portee : ", "Degats supplementaires a toutes les entites : "] +name = "Globe de douleur" +description = "Repartissez les degats que vous infligez selon le nombre d'ennemis autour de vous !" +lore1 = "Plus il y a d'ennemis autour de vous, moins vous infligez de degats a chacun" +lore2 = "Portee : " +lore3 = "Degats supplementaires a toutes les entites : " +lore = ["Plus il y a d'ennemis autour de vous, moins vous infligez de degats a chacun", "Portee : ", "Degats supplementaires a toutes les entites : "] [tragoul.healing] - name = "Volonte de souffrance" - description = "Regagnez de la sante en fonction des degats que vous infligez !" - lore1 = "Faire du mal n'a jamais fait autant de bien ! Soignez-vous grace aux degats infliges" - lore2 = "Il y a une fenetre de degats de 3 secondes, pour le soin et 1 seconde de temps de recharge " - lore3 = "Soin par pourcentage de degats : " - lore = ["Faire du mal n'a jamais fait autant de bien ! Soignez-vous grace aux degats infliges", "Il y a une fenetre de degats de 3 secondes, pour le soin et 1 seconde de temps de recharge ", "Soin par pourcentage de degats : "] +name = "Volonte de souffrance" +description = "Regagnez de la sante en fonction des degats que vous infligez !" +lore1 = "Faire du mal n'a jamais fait autant de bien ! Soignez-vous grace aux degats infliges" +lore2 = "Il y a une fenetre de degats de 3 secondes, pour le soin et 1 seconde de temps de recharge " +lore3 = "Soin par pourcentage de degats : " +lore = ["Faire du mal n'a jamais fait autant de bien ! Soignez-vous grace aux degats infliges", "Il y a une fenetre de degats de 3 secondes, pour le soin et 1 seconde de temps de recharge ", "Soin par pourcentage de degats : "] [tragoul.lance] - name = "Lances de cadavre" - description = "Tuer un ennemi ou qu'une capacite tue un ennemi fait apparaitre une lance qui inflige des degats a un ennemi proche !" - lore1 = "Les lances jaillissent de tout ce que vous tuez, ET si cette capacite tue un ennemi." - lore2 = "Sacrifiez une partie de votre vie pour creer les lances (cela peut vous tuer)" - lore3 = "Lances max : 1 + " - lore = ["Les lances jaillissent de tout ce que vous tuez, ET si cette capacite tue un ennemi.", "Sacrifiez une partie de votre vie pour creer les lances (cela peut vous tuer)", "Lances max : 1 + "] +name = "Lances de cadavre" +description = "Tuer un ennemi ou qu'une capacite tue un ennemi fait apparaitre une lance qui inflige des degats a un ennemi proche !" +lore1 = "Les lances jaillissent de tout ce que vous tuez, ET si cette capacite tue un ennemi." +lore2 = "Sacrifiez une partie de votre vie pour creer les lances (cela peut vous tuer)" +lore3 = "Lances max : 1 + " +lore = ["Les lances jaillissent de tout ce que vous tuez, ET si cette capacite tue un ennemi.", "Sacrifiez une partie de votre vie pour creer les lances (cela peut vous tuer)", "Lances max : 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Canon de verre" - description = "Bonus de degats a mains nues plus votre valeur d'armure est basse" - lore1 = "x degats a 0 armure" - lore2 = "Degats bonus par niveau" - lore = ["x degats a 0 armure", "Degats bonus par niveau"] +name = "Canon de verre" +description = "Bonus de degats a mains nues plus votre valeur d'armure est basse" +lore1 = "x degats a 0 armure" +lore2 = "Degats bonus par niveau" +lore = ["x degats a 0 armure", "Degats bonus par niveau"] [unarmed.power] - name = "Puissance a mains nues" - description = "Degats a mains nues ameliores" - lore1 = "Degats" - lore = ["Degats"] +name = "Puissance a mains nues" +description = "Degats a mains nues ameliores" +lore1 = "Degats" +lore = ["Degats"] [unarmed.sucker_punch] - name = "Coup en traitre" - description = "Coups de poing en sprint, mais plus mortels." - lore1 = "Degats" - lore2 = "Les degats augmentent avec votre vitesse lors de la frappe" - lore = ["Degats", "Les degats augmentent avec votre vitesse lors de la frappe"] +name = "Coup en traitre" +description = "Coups de poing en sprint, mais plus mortels." +lore1 = "Degats" +lore2 = "Les degats augmentent avec votre vitesse lors de la frappe" +lore = ["Degats", "Les degats augmentent avec votre vitesse lors de la frappe"] diff --git a/src/main/resources/he_IL.toml b/src/main/resources/he_IL.toml index e60867864..dd6f27810 100644 --- a/src/main/resources/he_IL.toml +++ b/src/main/resources/he_IL.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "חייב לזוז!" - description = "ללכת יותר מקילומטר אחד (1,000 בלוקים)" +title = "חייב לזוז!" +description = "ללכת יותר מקילומטר אחד (1,000 בלוקים)" [advancement.challenge_sprint_5k] - title = "ספרינט 5K!" - description = "ללכת יותר מ-5 קילומטרים (5,000 בלוקים)" +title = "ספרינט 5K!" +description = "ללכת יותר מ-5 קילומטרים (5,000 בלוקים)" [advancement.challenge_sprint_50k] - title = "זום של 50K!" - description = "ללכת יותר מ-50 קילומטרים (50,000 בלוקים)" +title = "זום של 50K!" +description = "ללכת יותר מ-50 קילומטרים (50,000 בלוקים)" [advancement.challenge_sprint_500k] - title = "לחצות את היקום!!" - description = "ללכת יותר מ-500 קילומטרים (500,000 בלוקים)" +title = "לחצות את היקום!!" +description = "ללכת יותר מ-500 קילומטרים (500,000 בלוקים)" [advancement.challenge_sprint_marathon] - title = "ספרינט מרתון (תרתי משמע)!" - description = "לרוץ מעל 42,195 בלוקים!" +title = "ספרינט מרתון (תרתי משמע)!" +description = "לרוץ מעל 42,195 בלוקים!" [advancement.challenge_place_1k] - title = "בונה מתחיל!" - description = "הנח 1,000 בלוקים" +title = "בונה מתחיל!" +description = "הנח 1,000 בלוקים" [advancement.challenge_place_5k] - title = "בונה בינוני!" - description = "הנח 5,000 בלוקים" +title = "בונה בינוני!" +description = "הנח 5,000 בלוקים" [advancement.challenge_place_50k] - title = "בונה מתקדם!" - description = "הנח 50,000 בלוקים" +title = "בונה מתקדם!" +description = "הנח 50,000 בלוקים" [advancement.challenge_place_500k] - title = "בונה מאסטר!" - description = "הנח 500,000 בלוקים" +title = "בונה מאסטר!" +description = "הנח 500,000 בלוקים" [advancement.challenge_place_5m] - title = "תלמיד הסימטריה!" - description = "המציאות היא מגרש המשחקים שלך! (5 מיליון בלוקים)" +title = "תלמיד הסימטריה!" +description = "המציאות היא מגרש המשחקים שלך! (5 מיליון בלוקים)" [advancement.challenge_chop_1k] - title = "חוטב עצים מתחיל!" - description = "חטוב 1,000 בלוקים" +title = "חוטב עצים מתחיל!" +description = "חטוב 1,000 בלוקים" [advancement.challenge_chop_5k] - title = "חוטב עצים בינוני!" - description = "חטוב 5,000 בלוקים" +title = "חוטב עצים בינוני!" +description = "חטוב 5,000 בלוקים" [advancement.challenge_chop_50k] - title = "חוטב עצים מתקדם!" - description = "חטוב 50,000 בלוקים" +title = "חוטב עצים מתקדם!" +description = "חטוב 50,000 בלוקים" [advancement.challenge_chop_500k] - title = "חוטב עצים מאסטר!" - description = "חטוב 500,000 בלוקים" +title = "חוטב עצים מאסטר!" +description = "חטוב 500,000 בלוקים" [advancement.challenge_chop_5m] - title = "ג'קסון הכלב" - description = "הילד הכי טוב שיש! (5 מיליון בלוקים)" +title = "ג'קסון הכלב" +description = "הילד הכי טוב שיש! (5 מיליון בלוקים)" [advancement.challenge_block_1k] - title = "בקושי חוסם!" - description = "חסום 1000 מכות" +title = "בקושי חוסם!" +description = "חסום 1000 מכות" [advancement.challenge_block_5k] - title = "חסימה זה כיף!" - description = "חסום 5000 מכות" +title = "חסימה זה כיף!" +description = "חסום 5000 מכות" [advancement.challenge_block_50k] - title = "חסימה זה החיים שלי!" - description = "חסום 50,000 מכות" +title = "חסימה זה החיים שלי!" +description = "חסום 50,000 מכות" [advancement.challenge_block_500k] - title = "חסימה היא הייעוד שלי!" - description = "חסום 500,000 מכות" +title = "חסימה היא הייעוד שלי!" +description = "חסום 500,000 מכות" [advancement.challenge_block_5m] - title = "היד הכואבת" - description = "חסום 5,000,000 מכות" +title = "היד הכואבת" +description = "חסום 5,000,000 מכות" [advancement.challenge_brew_1k] - title = "אלכימאי מתחיל!" - description = "צרוך 1000 שיקויים" +title = "אלכימאי מתחיל!" +description = "צרוך 1000 שיקויים" [advancement.challenge_brew_5k] - title = "אלכימאי בינוני!" - description = "צרוך 5000 שיקויים" +title = "אלכימאי בינוני!" +description = "צרוך 5000 שיקויים" [advancement.challenge_brew_50k] - title = "אלכימאי מתקדם!" - description = "צרוך 50,000 שיקויים" +title = "אלכימאי מתקדם!" +description = "צרוך 50,000 שיקויים" [advancement.challenge_brew_500k] - title = "אלכימאי מאסטר!" - description = "צרוך 500,000 שיקויים" +title = "אלכימאי מאסטר!" +description = "צרוך 500,000 שיקויים" [advancement.challenge_brew_5m] - title = "האלכימאי" - description = "צרוך 5,000,000 שיקויים" +title = "האלכימאי" +description = "צרוך 5,000,000 שיקויים" [advancement.challenge_brewsplash_1k] - title = "מתיז שיקויים מתחיל!" - description = "התז 1000 שיקויים" +title = "מתיז שיקויים מתחיל!" +description = "התז 1000 שיקויים" [advancement.challenge_brewsplash_5k] - title = "מתיז שיקויים בינוני!" - description = "התז 5000 שיקויים" +title = "מתיז שיקויים בינוני!" +description = "התז 5000 שיקויים" [advancement.challenge_brewsplash_50k] - title = "מתיז שיקויים מתקדם!" - description = "התז 50,000 שיקויים" +title = "מתיז שיקויים מתקדם!" +description = "התז 50,000 שיקויים" [advancement.challenge_brewsplash_500k] - title = "מתיז שיקויים מאסטר!" - description = "התז 500,000 שיקויים" +title = "מתיז שיקויים מאסטר!" +description = "התז 500,000 שיקויים" [advancement.challenge_brewsplash_5m] - title = "מייסטר ההתזה" - description = "התז 5,000,000 שיקויים" +title = "מייסטר ההתזה" +description = "התז 5,000,000 שיקויים" [advancement.challenge_craft_1k] - title = "יוצר מלאכתי!" - description = "צור 1000 פריטים" +title = "יוצר מלאכתי!" +description = "צור 1000 פריטים" [advancement.challenge_craft_5k] - title = "יוצר עקשן!" - description = "צור 5000 פריטים" +title = "יוצר עקשן!" +description = "צור 5000 פריטים" [advancement.challenge_craft_50k] - title = "יוצר מסור!" - description = "צור 50,000 פריטים" +title = "יוצר מסור!" +description = "צור 50,000 פריטים" [advancement.challenge_craft_500k] - title = "יוצר רועש!" - description = "צור 500,000 פריטים" +title = "יוצר רועש!" +description = "צור 500,000 פריטים" [advancement.challenge_craft_5m] - title = "מיסטר יוצר-פנים" - description = "צור 5,000,000 פריטים" +title = "מיסטר יוצר-פנים" +description = "צור 5,000,000 פריטים" [advancement.challenge_enchant_1k] - title = "קוסם מתחיל!" - description = "כשף 1000 פריטים" +title = "קוסם מתחיל!" +description = "כשף 1000 פריטים" [advancement.challenge_enchant_5k] - title = "קוסם בינוני!" - description = "כשף 5000 פריטים" +title = "קוסם בינוני!" +description = "כשף 5000 פריטים" [advancement.challenge_enchant_50k] - title = "קוסם מתקדם!" - description = "כשף 50,000 פריטים" +title = "קוסם מתקדם!" +description = "כשף 50,000 פריטים" [advancement.challenge_enchant_500k] - title = "קוסם מאסטר!" - description = "כשף 500,000 פריטים" +title = "קוסם מאסטר!" +description = "כשף 500,000 פריטים" [advancement.challenge_enchant_5m] - title = "הקוסם החידתי" - description = "כשף 5,000,000 פריטים" +title = "הקוסם החידתי" +description = "כשף 5,000,000 פריטים" [advancement.challenge_excavate_1k] - title = "חופר נלהב!" - description = "חפור 1000 בלוקים" +title = "חופר נלהב!" +description = "חפור 1000 בלוקים" [advancement.challenge_excavate_5k] - title = "חופר בינוני!" - description = "חפור 5000 בלוקים" +title = "חופר בינוני!" +description = "חפור 5000 בלוקים" [advancement.challenge_excavate_50k] - title = "חופר מתקדם!" - description = "חפור 50,000 בלוקים" +title = "חופר מתקדם!" +description = "חפור 50,000 בלוקים" [advancement.challenge_excavate_500k] - title = "חופר מאסטר!" - description = "חפור 500,000 בלוקים" +title = "חופר מאסטר!" +description = "חפור 500,000 בלוקים" [advancement.challenge_excavate_5m] - title = "החופר החידתי" - description = "חפור 5,000,000 בלוקים" +title = "החופר החידתי" +description = "חפור 5,000,000 בלוקים" [advancement.horrible_person] - title = "אתה אדם נורא" - description = "בלתי נתפס, באמת" +title = "אתה אדם נורא" +description = "בלתי נתפס, באמת" [advancement.challenge_turtle_egg_smasher] - title = "מרסק ביצי צבים!" - description = "שבור 100 ביצי צבים" +title = "מרסק ביצי צבים!" +description = "שבור 100 ביצי צבים" [advancement.challenge_turtle_egg_annihilator] - title = "משמיד ביצי צבים!" - description = "שבור 500 ביצי צבים" +title = "משמיד ביצי צבים!" +description = "שבור 500 ביצי צבים" [advancement.challenge_novice_hunter] - title = "צייד מתחיל!" - description = "הרוג 100 ישויות" +title = "צייד מתחיל!" +description = "הרוג 100 ישויות" [advancement.challenge_intermediate_hunter] - title = "צייד בינוני!" - description = "הרוג 500 ישויות" +title = "צייד בינוני!" +description = "הרוג 500 ישויות" [advancement.challenge_advanced_hunter] - title = "צייד מתקדם!" - description = "הרוג 5000 ישויות" +title = "צייד מתקדם!" +description = "הרוג 5000 ישויות" [advancement.challenge_creeper_conqueror] - title = "כובש קריפרים!" - description = "הרוג 50 קריפרים" +title = "כובש קריפרים!" +description = "הרוג 50 קריפרים" [advancement.challenge_creeper_annihilator] - title = "משמיד קריפרים!" - description = "הרוג 200 קריפרים" +title = "משמיד קריפרים!" +description = "הרוג 200 קריפרים" [advancement.challenge_pickaxe_1k] - title = "כורה מתחיל" - description = "שבור 1000 בלוקים" +title = "כורה מתחיל" +description = "שבור 1000 בלוקים" [advancement.challenge_pickaxe_5k] - title = "כורה מיומן" - description = "שבור 5000 בלוקים" +title = "כורה מיומן" +description = "שבור 5000 בלוקים" [advancement.challenge_pickaxe_50k] - title = "כורה מומחה" - description = "שבור 50,000 בלוקים" +title = "כורה מומחה" +description = "שבור 50,000 בלוקים" [advancement.challenge_pickaxe_500k] - title = "כורה מאסטר" - description = "שבור 500,000 בלוקים" +title = "כורה מאסטר" +description = "שבור 500,000 בלוקים" [advancement.challenge_pickaxe_5m] - title = "כורה אגדי" - description = "שבור 5,000,000 בלוקים" +title = "כורה אגדי" +description = "שבור 5,000,000 בלוקים" [advancement.challenge_eat_100] - title = "כל כך הרבה לאכול!" - description = "אכול מעל 100 פריטים!" +title = "כל כך הרבה לאכול!" +description = "אכול מעל 100 פריטים!" [advancement.challenge_eat_1000] - title = "רעב בלתי ניתן לריווי!" - description = "אכול מעל 1,000 פריטים!" +title = "רעב בלתי ניתן לריווי!" +description = "אכול מעל 1,000 פריטים!" [advancement.challenge_eat_10000] - title = "רעב נצחי!" - description = "אכול מעל 10,000 פריטים!" +title = "רעב נצחי!" +description = "אכול מעל 10,000 פריטים!" [advancement.challenge_harvest_100] - title = "קציר מלא" - description = "קצור מעל 100 יבולים!" +title = "קציר מלא" +description = "קצור מעל 100 יבולים!" [advancement.challenge_harvest_1000] - title = "קציר גדול" - description = "קצור מעל 1,000 יבולים!" +title = "קציר גדול" +description = "קצור מעל 1,000 יבולים!" [advancement.challenge_swim_1nm] - title = "צוללת אנושית!" - description = "שחה מייל ימי אחד (1,852 בלוקים)" +title = "צוללת אנושית!" +description = "שחה מייל ימי אחד (1,852 בלוקים)" [advancement.challenge_sneak_1k] - title = "כאב ברכיים" - description = "התגנב מעל קילומטר (1,000 בלוקים)" +title = "כאב ברכיים" +description = "התגנב מעל קילומטר (1,000 בלוקים)" [advancement.challenge_sneak_5k] - title = "הולך הצללים" - description = "התגנב מעל 5,000 בלוקים" +title = "הולך הצללים" +description = "התגנב מעל 5,000 בלוקים" [advancement.challenge_sneak_20k] - title = "רוח רפאים" - description = "התגנב מעל 20,000 בלוקים" +title = "רוח רפאים" +description = "התגנב מעל 20,000 בלוקים" [advancement.challenge_swim_5k] - title = "צולל מעמקים" - description = "שחה מעל 5,000 בלוקים" +title = "צולל מעמקים" +description = "שחה מעל 5,000 בלוקים" [advancement.challenge_swim_20k] - title = "בחיר פוסידון" - description = "שחה מעל 20,000 בלוקים" +title = "בחיר פוסידון" +description = "שחה מעל 20,000 בלוקים" [advancement.challenge_sword_100] - title = "דם ראשון" - description = "פגע 100 פעמים בחרב" +title = "דם ראשון" +description = "פגע 100 פעמים בחרב" [advancement.challenge_sword_1k] - title = "רוקד החרבות" - description = "פגע 1,000 פעמים בחרב" +title = "רוקד החרבות" +description = "פגע 1,000 פעמים בחרב" [advancement.challenge_sword_10k] - title = "אלף חתכים" - description = "פגע 10,000 פעמים בחרב" +title = "אלף חתכים" +description = "פגע 10,000 פעמים בחרב" [advancement.challenge_unarmed_100] - title = "קטטן בר" - description = "פגע 100 פעמים בידיים חשופות" +title = "קטטן בר" +description = "פגע 100 פעמים בידיים חשופות" [advancement.challenge_unarmed_1k] - title = "אגרופי ברזל" - description = "פגע 1,000 פעמים בידיים חשופות" +title = "אגרופי ברזל" +description = "פגע 1,000 פעמים בידיים חשופות" [advancement.challenge_unarmed_10k] - title = "אגרוף אחד" - description = "פגע 10,000 פעמים בידיים חשופות" +title = "אגרוף אחד" +description = "פגע 10,000 פעמים בידיים חשופות" [advancement.challenge_trag_1k] - title = "מחיר הדם" - description = "קבל 1,000 נזק" +title = "מחיר הדם" +description = "קבל 1,000 נזק" [advancement.challenge_trag_10k] - title = "גאות ארגמן" - description = "קבל 10,000 נזק" +title = "גאות ארגמן" +description = "קבל 10,000 נזק" [advancement.challenge_trag_100k] - title = "אווטאר הסבל" - description = "קבל 100,000 נזק" +title = "אווטאר הסבל" +description = "קבל 100,000 נזק" [advancement.challenge_ranged_100] - title = "תרגול מטרות" - description = "ירה 100 קליעים" +title = "תרגול מטרות" +description = "ירה 100 קליעים" [advancement.challenge_ranged_1k] - title = "עין הנץ" - description = "ירה 1,000 קליעים" +title = "עין הנץ" +description = "ירה 1,000 קליעים" [advancement.challenge_ranged_10k] - title = "סערת חיצים" - description = "ירה 10,000 קליעים" +title = "סערת חיצים" +description = "ירה 10,000 קליעים" [advancement.challenge_chronos_1h] - title = "טיק טוק" - description = "בלה שעה אחת מחובר" +title = "טיק טוק" +description = "בלה שעה אחת מחובר" [advancement.challenge_chronos_24h] - title = "חולות הזמן" - description = "בלה 24 שעות מחובר" +title = "חולות הזמן" +description = "בלה 24 שעות מחובר" [advancement.challenge_chronos_168h] - title = "נצחי" - description = "בלה 168 שעות (שבוע) מחובר" +title = "נצחי" +description = "בלה 168 שעות (שבוע) מחובר" [advancement.challenge_nether_50] - title = "שומר שערי הגיהנום" - description = "הרוג 50 יצורי נתר" +title = "שומר שערי הגיהנום" +description = "הרוג 50 יצורי נתר" [advancement.challenge_nether_500] - title = "שומר התהום" - description = "הרוג 500 יצורי נתר" +title = "שומר התהום" +description = "הרוג 500 יצורי נתר" [advancement.challenge_nether_5k] - title = "אדון הנתר" - description = "הרוג 5,000 יצורי נתר" +title = "אדון הנתר" +description = "הרוג 5,000 יצורי נתר" [advancement.challenge_rift_50] - title = "חריגה מרחבית" - description = "השתגר 50 פעמים" +title = "חריגה מרחבית" +description = "השתגר 50 פעמים" [advancement.challenge_rift_500] - title = "הולך הריק" - description = "השתגר 500 פעמים" +title = "הולך הריק" +description = "השתגר 500 פעמים" [advancement.challenge_rift_5k] - title = "בין העולמות" - description = "השתגר 5,000 פעמים" +title = "בין העולמות" +description = "השתגר 5,000 פעמים" [advancement.challenge_taming_10] - title = "לוחש לחיות" - description = "הרבה 10 חיות" +title = "לוחש לחיות" +description = "הרבה 10 חיות" [advancement.challenge_taming_50] - title = "מנהיג הלהקה" - description = "הרבה 50 חיות" +title = "מנהיג הלהקה" +description = "הרבה 50 חיות" [advancement.challenge_taming_500] - title = "אדון החיות" - description = "הרבה 500 חיות" +title = "אדון החיות" +description = "הרבה 500 חיות" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "שד המהירות" - description = "רוץ מעל 5 קילומטרים (5,000 בלוקים)" +title = "שד המהירות" +description = "רוץ מעל 5 קילומטרים (5,000 בלוקים)" [advancement.challenge_sprint_dist_50k] - title = "רגלי ברק" - description = "רוץ מעל 50 קילומטרים (50,000 בלוקים)" +title = "רגלי ברק" +description = "רוץ מעל 50 קילומטרים (50,000 בלוקים)" [advancement.challenge_agility_swim_1k] - title = "פוסע על המים" - description = "שחה מעל קילומטר אחד (1,000 בלוקים)" +title = "פוסע על המים" +description = "שחה מעל קילומטר אחד (1,000 בלוקים)" [advancement.challenge_agility_swim_10k] - title = "מסע ימי" - description = "שחה מעל 10 קילומטרים (10,000 בלוקים)" +title = "מסע ימי" +description = "שחה מעל 10 קילומטרים (10,000 בלוקים)" [advancement.challenge_fly_1k] - title = "רקדן השמיים" - description = "טוס מעל קילומטר אחד (1,000 בלוקים)" +title = "רקדן השמיים" +description = "טוס מעל קילומטר אחד (1,000 בלוקים)" [advancement.challenge_fly_10k] - title = "רוכב הרוח" - description = "טוס מעל 10 קילומטרים (10,000 בלוקים)" +title = "רוכב הרוח" +description = "טוס מעל 10 קילומטרים (10,000 בלוקים)" [advancement.challenge_agility_sneak_500] - title = "צעדים שקטים" - description = "התגנב מעל 500 בלוקים" +title = "צעדים שקטים" +description = "התגנב מעל 500 בלוקים" [advancement.challenge_agility_sneak_5k] - title = "צעדי רפאים" - description = "התגנב מעל 5 קילומטרים (5,000 בלוקים)" +title = "צעדי רפאים" +description = "התגנב מעל 5 קילומטרים (5,000 בלוקים)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "צוות הריסה" - description = "שבור 500 בלוקים" +title = "צוות הריסה" +description = "שבור 500 בלוקים" [advancement.challenge_demolish_5k] - title = "כדור ההריסה" - description = "שבור 5,000 בלוקים" +title = "כדור ההריסה" +description = "שבור 5,000 בלוקים" [advancement.challenge_value_placed_10k] - title = "בנאי בעל ערך" - description = "הנח בלוקים בשווי 10,000" +title = "בנאי בעל ערך" +description = "הנח בלוקים בשווי 10,000" [advancement.challenge_value_placed_100k] - title = "אדריכל ראשי" - description = "הנח בלוקים בשווי 100,000" +title = "אדריכל ראשי" +description = "הנח בלוקים בשווי 100,000" [advancement.challenge_demolish_val_5k] - title = "מומחה מיחזור" - description = "מחזר 5,000 ערך בלוקים מהריסה" +title = "מומחה מיחזור" +description = "מחזר 5,000 ערך בלוקים מהריסה" [advancement.challenge_demolish_val_50k] - title = "פירוק מוחלט" - description = "מחזר 50,000 ערך בלוקים מהריסה" +title = "פירוק מוחלט" +description = "מחזר 50,000 ערך בלוקים מהריסה" [advancement.challenge_high_build_100] - title = "בנאי גבהים" - description = "הנח 100 בלוקים מעל Y=128" +title = "בנאי גבהים" +description = "הנח 100 בלוקים מעל Y=128" [advancement.challenge_high_build_1k] - title = "אדריכל העננים" - description = "הנח 1,000 בלוקים מעל Y=128" +title = "אדריכל העננים" +description = "הנח 1,000 בלוקים מעל Y=128" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "מניף הגרזן" - description = "הנף את הגרזן 500 פעמים" +title = "מניף הגרזן" +description = "הנף את הגרזן 500 פעמים" [advancement.challenge_axe_swing_5k] - title = "ברסרקר" - description = "הנף את הגרזן 5,000 פעמים" +title = "ברסרקר" +description = "הנף את הגרזן 5,000 פעמים" [advancement.challenge_axe_damage_1k] - title = "מבתר" - description = "גרום 1,000 נזק עם גרזנים" +title = "מבתר" +description = "גרום 1,000 נזק עם גרזנים" [advancement.challenge_axe_damage_10k] - title = "גרזן התליין" - description = "גרום 10,000 נזק עם גרזנים" +title = "גרזן התליין" +description = "גרום 10,000 נזק עם גרזנים" [advancement.challenge_axe_value_5k] - title = "סוחר עצים" - description = "כרות עץ בשווי 5,000" +title = "סוחר עצים" +description = "כרות עץ בשווי 5,000" [advancement.challenge_axe_value_50k] - title = "מלך הכריתה" - description = "כרות עץ בשווי 50,000" +title = "מלך הכריתה" +description = "כרות עץ בשווי 50,000" [advancement.challenge_leaves_500] - title = "מפוח עלים" - description = "נקה 500 בלוקי עלים עם גרזן" +title = "מפוח עלים" +description = "נקה 500 בלוקי עלים עם גרזן" [advancement.challenge_leaves_5k] - title = "משיר העלים" - description = "נקה 5,000 בלוקי עלים עם גרזן" +title = "משיר העלים" +description = "נקה 5,000 בלוקי עלים עם גרזן" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "סופג נזק" - description = "חסום 1,000 נזק עם מגן" +title = "סופג נזק" +description = "חסום 1,000 נזק עם מגן" [advancement.challenge_block_dmg_10k] - title = "מגן אנושי" - description = "חסום 10,000 נזק עם מגן" +title = "מגן אנושי" +description = "חסום 10,000 נזק עם מגן" [advancement.challenge_block_proj_100] - title = "מסיט חצים" - description = "חסום 100 קליעים עם מגן" +title = "מסיט חצים" +description = "חסום 100 קליעים עם מגן" [advancement.challenge_block_proj_1k] - title = "מגן קליעים" - description = "חסום 1,000 קליעים עם מגן" +title = "מגן קליעים" +description = "חסום 1,000 קליעים עם מגן" [advancement.challenge_block_melee_500] - title = "אמן הפריירה" - description = "חסום 500 תקיפות קרב קרוב עם מגן" +title = "אמן הפריירה" +description = "חסום 500 תקיפות קרב קרוב עם מגן" [advancement.challenge_block_melee_5k] - title = "מבצר הברזל" - description = "חסום 5,000 תקיפות קרב קרוב עם מגן" +title = "מבצר הברזל" +description = "חסום 5,000 תקיפות קרב קרוב עם מגן" [advancement.challenge_block_heavy_50] - title = "טנק" - description = "חסום 50 תקיפות כבדות (מעל 5 נזק)" +title = "טנק" +description = "חסום 50 תקיפות כבדות (מעל 5 נזק)" [advancement.challenge_block_heavy_500] - title = "עצם בלתי ניתן להזזה" - description = "חסום 500 תקיפות כבדות (מעל 5 נזק)" +title = "עצם בלתי ניתן להזזה" +description = "חסום 500 תקיפות כבדות (מעל 5 נזק)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "תחנת שיקויים" - description = "הנח 10 מעמדי שיקויים" +title = "תחנת שיקויים" +description = "הנח 10 מעמדי שיקויים" [advancement.challenge_brew_stands_50] - title = "מפעל שיקויים" - description = "הנח 50 מעמדי שיקויים" +title = "מפעל שיקויים" +description = "הנח 50 מעמדי שיקויים" [advancement.challenge_brew_strong_25] - title = "שיקוי חזק" - description = "שתה 25 שיקויים משודרגים" +title = "שיקוי חזק" +description = "שתה 25 שיקויים משודרגים" [advancement.challenge_brew_strong_250] - title = "עוצמה מרבית" - description = "שתה 250 שיקויים משודרגים" +title = "עוצמה מרבית" +description = "שתה 250 שיקויים משודרגים" [advancement.challenge_brew_splash_hits_50] - title = "אזור התזה" - description = "פגע ב-50 ישויות עם שיקויי התזה" +title = "אזור התזה" +description = "פגע ב-50 ישויות עם שיקויי התזה" [advancement.challenge_brew_splash_hits_500] - title = "רופא המגיפה" - description = "פגע ב-500 ישויות עם שיקויי התזה" +title = "רופא המגיפה" +description = "פגע ב-500 ישויות עם שיקויי התזה" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "חסר מנוחה" - description = "עבור קילומטר אחד בזמן פעיל" +title = "חסר מנוחה" +description = "עבור קילומטר אחד בזמן פעיל" [advancement.challenge_active_dist_10k] - title = "סולל דרכים" - description = "עבור 10 קילומטרים בזמן פעיל" +title = "סולל דרכים" +description = "עבור 10 קילומטרים בזמן פעיל" [advancement.challenge_active_dist_100k] - title = "תנועה מתמדת" - description = "עבור 100 קילומטרים בזמן פעיל" +title = "תנועה מתמדת" +description = "עבור 100 קילומטרים בזמן פעיל" [advancement.challenge_beds_10] - title = "משכים קום" - description = "ישן במיטה 10 פעמים" +title = "משכים קום" +description = "ישן במיטה 10 פעמים" [advancement.challenge_beds_100] - title = "מדלג על הזמן" - description = "ישן במיטה 100 פעמים" +title = "מדלג על הזמן" +description = "ישן במיטה 100 פעמים" [advancement.challenge_chronos_tp_50] - title = "הסטה זמנית" - description = "השתגר 50 פעמים" +title = "הסטה זמנית" +description = "השתגר 50 פעמים" [advancement.challenge_chronos_tp_500] - title = "עיוות זמן" - description = "השתגר 500 פעמים" +title = "עיוות זמן" +description = "השתגר 500 פעמים" [advancement.challenge_chronos_deaths_10] - title = "בן תמותה" - description = "מות 10 פעמים" +title = "בן תמותה" +description = "מות 10 פעמים" [advancement.challenge_chronos_deaths_100] - title = "מתגרה במוות" - description = "מות 100 פעמים" +title = "מתגרה במוות" +description = "מות 100 פעמים" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "יצירה בעלת ערך" - description = "צור חפצים בשווי כולל של 10,000" +title = "יצירה בעלת ערך" +description = "צור חפצים בשווי כולל של 10,000" [advancement.challenge_craft_value_100k] - title = "אומן" - description = "צור חפצים בשווי כולל של 100,000" +title = "אומן" +description = "צור חפצים בשווי כולל של 100,000" [advancement.challenge_craft_tools_25] - title = "יוצר כלים" - description = "צור 25 כלים" +title = "יוצר כלים" +description = "צור 25 כלים" [advancement.challenge_craft_tools_250] - title = "נפח ראשי" - description = "צור 250 כלים" +title = "נפח ראשי" +description = "צור 250 כלים" [advancement.challenge_craft_armor_25] - title = "יוצר שריון" - description = "צור 25 חלקי שריון" +title = "יוצר שריון" +description = "צור 25 חלקי שריון" [advancement.challenge_craft_armor_250] - title = "אמן השריון" - description = "צור 250 חלקי שריון" +title = "אמן השריון" +description = "צור 250 חלקי שריון" [advancement.challenge_craft_mass_25k] - title = "ייצור המוני" - description = "צור 25,000 חפצים" +title = "ייצור המוני" +description = "צור 25,000 חפצים" [advancement.challenge_craft_mass_250k] - title = "מהפכה תעשייתית" - description = "צור 250,000 חפצים" +title = "מהפכה תעשייתית" +description = "צור 250,000 חפצים" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "אספן" - description = "גלה 50 חפצים ייחודיים" +title = "אספן" +description = "גלה 50 חפצים ייחודיים" [advancement.challenge_discover_items_250] - title = "מקטלג" - description = "גלה 250 חפצים ייחודיים" +title = "מקטלג" +description = "גלה 250 חפצים ייחודיים" [advancement.challenge_discover_blocks_50] - title = "סוקר" - description = "גלה 50 בלוקים ייחודיים" +title = "סוקר" +description = "גלה 50 בלוקים ייחודיים" [advancement.challenge_discover_blocks_250] - title = "גיאולוג" - description = "גלה 250 בלוקים ייחודיים" +title = "גיאולוג" +description = "גלה 250 בלוקים ייחודיים" [advancement.challenge_discover_mobs_25] - title = "צופה" - description = "גלה 25 מובים ייחודיים" +title = "צופה" +description = "גלה 25 מובים ייחודיים" [advancement.challenge_discover_mobs_75] - title = "חוקר טבע" - description = "גלה 75 מובים ייחודיים" +title = "חוקר טבע" +description = "גלה 75 מובים ייחודיים" [advancement.challenge_discover_biomes_10] - title = "נווד" - description = "גלה 10 ביומים ייחודיים" +title = "נווד" +description = "גלה 10 ביומים ייחודיים" [advancement.challenge_discover_biomes_40] - title = "מטייל העולם" - description = "גלה 40 ביומים ייחודיים" +title = "מטייל העולם" +description = "גלה 40 ביומים ייחודיים" [advancement.challenge_discover_foods_10] - title = "שפית" - description = "גלה 10 מאכלים ייחודיים" +title = "שפית" +description = "גלה 10 מאכלים ייחודיים" [advancement.challenge_discover_foods_30] - title = "מאסטר שף" - description = "גלה 30 מאכלים ייחודיים" +title = "מאסטר שף" +description = "גלה 30 מאכלים ייחודיים" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "אורג כוח" - description = "צבור 100 כוח כישוף" +title = "אורג כוח" +description = "צבור 100 כוח כישוף" [advancement.challenge_enchant_power_1k] - title = "אדון הקסם" - description = "צבור 1,000 כוח כישוף" +title = "אדון הקסם" +description = "צבור 1,000 כוח כישוף" [advancement.challenge_enchant_levels_1k] - title = "מבזבז רמות" - description = "הוצא 1,000 רמות ניסיון על כישוף" +title = "מבזבז רמות" +description = "הוצא 1,000 רמות ניסיון על כישוף" [advancement.challenge_enchant_levels_10k] - title = "בור ניסיון" - description = "הוצא 10,000 רמות ניסיון על כישוף" +title = "בור ניסיון" +description = "הוצא 10,000 רמות ניסיון על כישוף" [advancement.challenge_enchant_high_25] - title = "מהמר גדול" - description = "בצע 25 כישופים ברמה מקסימלית" +title = "מהמר גדול" +description = "בצע 25 כישופים ברמה מקסימלית" [advancement.challenge_enchant_high_250] - title = "מכשף אגדי" - description = "בצע 250 כישופים ברמה מקסימלית" +title = "מכשף אגדי" +description = "בצע 250 כישופים ברמה מקסימלית" [advancement.challenge_enchant_total_500] - title = "שורף רמות" - description = "הוצא 500 רמות סה\"כ על כל הכישופים" +title = "שורף רמות" +description = "הוצא 500 רמות סה\"כ על כל הכישופים" [advancement.challenge_enchant_total_5k] - title = "השקעה קסומה" - description = "הוצא 5,000 רמות סה\"כ על כל הכישופים" +title = "השקעה קסומה" +description = "הוצא 5,000 רמות סה\"כ על כל הכישופים" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "חופר" - description = "הנף את האת 500 פעמים" +title = "חופר" +description = "הנף את האת 500 פעמים" [advancement.challenge_dig_swing_5k] - title = "מחפרון" - description = "הנף את האת 5,000 פעמים" +title = "מחפרון" +description = "הנף את האת 5,000 פעמים" [advancement.challenge_dig_damage_1k] - title = "אביר האת" - description = "גרום 1,000 נזק עם את" +title = "אביר האת" +description = "גרום 1,000 נזק עם את" [advancement.challenge_dig_damage_10k] - title = "אדון האת" - description = "גרום 10,000 נזק עם את" +title = "אדון האת" +description = "גרום 10,000 נזק עם את" [advancement.challenge_dig_value_5k] - title = "סוחר אדמה" - description = "חפור בלוקים בשווי 5,000" +title = "סוחר אדמה" +description = "חפור בלוקים בשווי 5,000" [advancement.challenge_dig_value_50k] - title = "אדון האדמות" - description = "חפור בלוקים בשווי 50,000" +title = "אדון האדמות" +description = "חפור בלוקים בשווי 50,000" [advancement.challenge_dig_gravel_500] - title = "טוחן חצץ" - description = "חפור 500 בלוקי חצץ, חול או חרסית" +title = "טוחן חצץ" +description = "חפור 500 בלוקי חצץ, חול או חרסית" [advancement.challenge_dig_gravel_5k] - title = "מנפה חול" - description = "חפור 5,000 בלוקי חצץ, חול או חרסית" +title = "מנפה חול" +description = "חפור 5,000 בלוקי חצץ, חול או חרסית" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "זורע" - description = "שתול 100 גידולים" +title = "זורע" +description = "שתול 100 גידולים" [advancement.challenge_plant_1k] - title = "אגודל ירוק" - description = "שתול 1,000 גידולים" +title = "אגודל ירוק" +description = "שתול 1,000 גידולים" [advancement.challenge_plant_5k] - title = "מלך החקלאות" - description = "שתול 5,000 גידולים" +title = "מלך החקלאות" +description = "שתול 5,000 גידולים" [advancement.challenge_compost_50] - title = "ממחזר" - description = "הכנס 50 חפצים לקומפוסט" +title = "ממחזר" +description = "הכנס 50 חפצים לקומפוסט" [advancement.challenge_compost_500] - title = "מעשיר אדמה" - description = "הכנס 500 חפצים לקומפוסט" +title = "מעשיר אדמה" +description = "הכנס 500 חפצים לקומפוסט" [advancement.challenge_shear_50] - title = "גוזז" - description = "גזוז 50 ישויות" +title = "גוזז" +description = "גזוז 50 ישויות" [advancement.challenge_shear_250] - title = "אדון העדר" - description = "גזוז 250 ישויות" +title = "אדון העדר" +description = "גזוז 250 ישויות" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "קוטל" - description = "הרוג 500 יצורים" +title = "קוטל" +description = "הרוג 500 יצורים" [advancement.challenge_kills_5k] - title = "תליין" - description = "הרוג 5,000 יצורים" +title = "תליין" +description = "הרוג 5,000 יצורים" [advancement.challenge_boss_1] - title = "מאתגר בוסים" - description = "הרוג מוב בוס" +title = "מאתגר בוסים" +description = "הרוג מוב בוס" [advancement.challenge_boss_10] - title = "רוצח אגדות" - description = "הרוג 10 מובי בוס" +title = "רוצח אגדות" +description = "הרוג 10 מובי בוס" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "נקלע לכמישה" - description = "ספוג 500 נזק כמישה" +title = "נקלע לכמישה" +description = "ספוג 500 נזק כמישה" [advancement.challenge_wither_dmg_5k] - title = "שורד המגיפה" - description = "ספוג 5,000 נזק כמישה" +title = "שורד המגיפה" +description = "ספוג 5,000 נזק כמישה" [advancement.challenge_wither_skel_25] - title = "אוסף עצמות" - description = "הרוג 25 שלדי ויתר" +title = "אוסף עצמות" +description = "הרוג 25 שלדי ויתר" [advancement.challenge_wither_skel_250] - title = "מכת השלדים" - description = "הרוג 250 שלדי ויתר" +title = "מכת השלדים" +description = "הרוג 250 שלדי ויתר" [advancement.challenge_wither_boss_1] - title = "קוטל הויתר" - description = "הבס את הויתר" +title = "קוטל הויתר" +description = "הבס את הויתר" [advancement.challenge_wither_boss_10] - title = "שליט הנתר" - description = "הבס את הויתר 10 פעמים" +title = "שליט הנתר" +description = "הבס את הויתר 10 פעמים" [advancement.challenge_roses_10] - title = "גנן המוות" - description = "שבור 10 ורדי ויתר" +title = "גנן המוות" +description = "שבור 10 ורדי ויתר" [advancement.challenge_roses_100] - title = "אוסף הכמישה" - description = "שבור 100 ורדי ויתר" +title = "אוסף הכמישה" +description = "שבור 100 ורדי ויתר" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "זרוע הכורה" - description = "הנף את המכוש 500 פעמים" +title = "זרוע הכורה" +description = "הנף את המכוש 500 פעמים" [advancement.challenge_pick_swing_5k] - title = "כורה מנהרות" - description = "הנף את המכוש 5,000 פעמים" +title = "כורה מנהרות" +description = "הנף את המכוש 5,000 פעמים" [advancement.challenge_pick_damage_1k] - title = "לוחם מכוש" - description = "גרום 1,000 נזק עם מכוש" +title = "לוחם מכוש" +description = "גרום 1,000 נזק עם מכוש" [advancement.challenge_pick_damage_10k] - title = "מכוש מלחמה" - description = "גרום 10,000 נזק עם מכוש" +title = "מכוש מלחמה" +description = "גרום 10,000 נזק עם מכוש" [advancement.challenge_pick_value_5k] - title = "מוצא אבני חן" - description = "כרה בלוקים בשווי 5,000" +title = "מוצא אבני חן" +description = "כרה בלוקים בשווי 5,000" [advancement.challenge_pick_value_50k] - title = "מלך המחצבים" - description = "כרה בלוקים בשווי 50,000" +title = "מלך המחצבים" +description = "כרה בלוקים בשווי 50,000" [advancement.challenge_pick_ores_500] - title = "מחפש מחצבים" - description = "כרה 500 בלוקי עפרה" +title = "מחפש מחצבים" +description = "כרה 500 בלוקי עפרה" [advancement.challenge_pick_ores_5k] - title = "כורה ראשי" - description = "כרה 5,000 בלוקי עפרה" +title = "כורה ראשי" +description = "כרה 5,000 בלוקי עפרה" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "צלף מדויק" - description = "גרום 1,000 נזק מרחוק" +title = "צלף מדויק" +description = "גרום 1,000 נזק מרחוק" [advancement.challenge_ranged_dmg_10k] - title = "קשת קטלני" - description = "גרום 10,000 נזק מרחוק" +title = "קשת קטלני" +description = "גרום 10,000 נזק מרחוק" [advancement.challenge_ranged_dist_5k] - title = "טווח ארוך" - description = "ירה קליעים שכיסו מרחק כולל של 5,000 בלוקים" +title = "טווח ארוך" +description = "ירה קליעים שכיסו מרחק כולל של 5,000 בלוקים" [advancement.challenge_ranged_dist_50k] - title = "יורה לקילומטרים" - description = "ירה קליעים שכיסו מרחק כולל של 50,000 בלוקים" +title = "יורה לקילומטרים" +description = "ירה קליעים שכיסו מרחק כולל של 50,000 בלוקים" [advancement.challenge_ranged_kills_50] - title = "קשת" - description = "הרוג 50 מובים עם נשק טווח" +title = "קשת" +description = "הרוג 50 מובים עם נשק טווח" [advancement.challenge_ranged_kills_500] - title = "קשת מאסטר" - description = "הרוג 500 מובים עם נשק טווח" +title = "קשת מאסטר" +description = "הרוג 500 מובים עם נשק טווח" [advancement.challenge_longshot_25] - title = "צלף" - description = "פגע 25 פגיעות מרחוק (מעל 30 בלוקים)" +title = "צלף" +description = "פגע 25 פגיעות מרחוק (מעל 30 בלוקים)" [advancement.challenge_longshot_250] - title = "עין הנשר" - description = "פגע 250 פגיעות מרחוק (מעל 30 בלוקים)" +title = "עין הנשר" +description = "פגע 250 פגיעות מרחוק (מעל 30 בלוקים)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "זורק פנינים" - description = "זרוק 50 פניני אנדר" +title = "זורק פנינים" +description = "זרוק 50 פניני אנדר" [advancement.challenge_rift_pearls_500] - title = "מכור לשיגור" - description = "זרוק 500 פניני אנדר" +title = "מכור לשיגור" +description = "זרוק 500 פניני אנדר" [advancement.challenge_rift_enderman_50] - title = "צייד אנדרמנים" - description = "הרוג 50 אנדרמנים" +title = "צייד אנדרמנים" +description = "הרוג 50 אנדרמנים" [advancement.challenge_rift_enderman_500] - title = "רודף הריק" - description = "הרוג 500 אנדרמנים" +title = "רודף הריק" +description = "הרוג 500 אנדרמנים" [advancement.challenge_rift_dragon_500] - title = "לוחם הדרקון" - description = "גרום 500 נזק לדרקון האנדר" +title = "לוחם הדרקון" +description = "גרום 500 נזק לדרקון האנדר" [advancement.challenge_rift_dragon_5k] - title = "מכת הדרקון" - description = "גרום 5,000 נזק לדרקון האנדר" +title = "מכת הדרקון" +description = "גרום 5,000 נזק לדרקון האנדר" [advancement.challenge_rift_crystal_10] - title = "שובר גבישים" - description = "השמד 10 גבישי קצה" +title = "שובר גבישים" +description = "השמד 10 גבישי קצה" [advancement.challenge_rift_crystal_100] - title = "הורס הקצה" - description = "השמד 100 גבישי קצה" +title = "הורס הקצה" +description = "השמד 100 גבישי קצה" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "דייג" - description = "תפוס 25 דגים" +title = "דייג" +description = "תפוס 25 דגים" [advancement.challenge_fish_250] - title = "דייג מאסטר" - description = "תפוס 250 דגים" +title = "דייג מאסטר" +description = "תפוס 250 דגים" [advancement.challenge_drowned_25] - title = "צייד טבועים" - description = "הרוג 25 טבועים" +title = "צייד טבועים" +description = "הרוג 25 טבועים" [advancement.challenge_drowned_250] - title = "מנקה האוקיינוס" - description = "הרוג 250 טבועים" +title = "מנקה האוקיינוס" +description = "הרוג 250 טבועים" [advancement.challenge_guardian_10] - title = "קוטל שומרים" - description = "הרוג 10 שומרים" +title = "קוטל שומרים" +description = "הרוג 10 שומרים" [advancement.challenge_guardian_100] - title = "פורץ מקדשים" - description = "הרוג 100 שומרים" +title = "פורץ מקדשים" +description = "הרוג 100 שומרים" [advancement.challenge_underwater_blocks_100] - title = "כורה תת-מימי" - description = "שבור 100 בלוקים מתחת למים" +title = "כורה תת-מימי" +description = "שבור 100 בלוקים מתחת למים" [advancement.challenge_underwater_blocks_1k] - title = "מהנדס תת-מימי" - description = "שבור 1,000 בלוקים מתחת למים" +title = "מהנדס תת-מימי" +description = "שבור 1,000 בלוקים מתחת למים" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "דוקר גב" - description = "גרום 500 נזק בזחילה" +title = "דוקר גב" +description = "גרום 500 נזק בזחילה" [advancement.challenge_stealth_dmg_5k] - title = "רוצח שקט" - description = "גרום 5,000 נזק בזחילה" +title = "רוצח שקט" +description = "גרום 5,000 נזק בזחילה" [advancement.challenge_stealth_kills_10] - title = "מתנקש" - description = "הרוג 10 מובים בזחילה" +title = "מתנקש" +description = "הרוג 10 מובים בזחילה" [advancement.challenge_stealth_kills_100] - title = "קוצר הצללים" - description = "הרוג 100 מובים בזחילה" +title = "קוצר הצללים" +description = "הרוג 100 מובים בזחילה" [advancement.challenge_stealth_time_1h] - title = "סבלני" - description = "בלה שעה אחת בזחילה (3,600 שניות)" +title = "סבלני" +description = "בלה שעה אחת בזחילה (3,600 שניות)" [advancement.challenge_stealth_time_10h] - title = "אדון הצללים" - description = "בלה 10 שעות בזחילה (36,000 שניות)" +title = "אדון הצללים" +description = "בלה 10 שעות בזחילה (36,000 שניות)" [advancement.challenge_stealth_arrows_50] - title = "קשת שקט" - description = "ירה 50 חצים בזחילה" +title = "קשת שקט" +description = "ירה 50 חצים בזחילה" [advancement.challenge_stealth_arrows_500] - title = "קשת הרפאים" - description = "ירה 500 חצים בזחילה" +title = "קשת הרפאים" +description = "ירה 500 חצים בזחילה" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "חניך החרב" - description = "גרום 1,000 נזק עם חרבות" +title = "חניך החרב" +description = "גרום 1,000 נזק עם חרבות" [advancement.challenge_sword_dmg_10k] - title = "סייף" - description = "גרום 10,000 נזק עם חרבות" +title = "סייף" +description = "גרום 10,000 נזק עם חרבות" [advancement.challenge_sword_kills_50] - title = "דו-קרבן" - description = "הרוג 50 מובים עם חרבות" +title = "דו-קרבן" +description = "הרוג 50 מובים עם חרבות" [advancement.challenge_sword_kills_500] - title = "גלדיאטור" - description = "הרוג 500 מובים עם חרבות" +title = "גלדיאטור" +description = "הרוג 500 מובים עם חרבות" [advancement.challenge_sword_crit_50] - title = "מכה קריטית" - description = "נחות 50 מכות קריטיות עם חרבות" +title = "מכה קריטית" +description = "נחות 50 מכות קריטיות עם חרבות" [advancement.challenge_sword_crit_500] - title = "אמן הדיוק" - description = "נחות 500 מכות קריטיות עם חרבות" +title = "אמן הדיוק" +description = "נחות 500 מכות קריטיות עם חרבות" [advancement.challenge_sword_heavy_25] - title = "מכה כבדה" - description = "נחות 25 מכות כבדות עם חרבות (מעל 8 נזק)" +title = "מכה כבדה" +description = "נחות 25 מכות כבדות עם חרבות (מעל 8 נזק)" [advancement.challenge_sword_heavy_250] - title = "מכה הרסנית" - description = "נחות 250 מכות כבדות עם חרבות (מעל 8 נזק)" +title = "מכה הרסנית" +description = "נחות 250 מכות כבדות עם חרבות (מעל 8 נזק)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "מאלף חיות" - description = "חיות המחמד שלך גורמות 500 נזק כולל" +title = "מאלף חיות" +description = "חיות המחמד שלך גורמות 500 נזק כולל" [advancement.challenge_pet_dmg_5k] - title = "אדון המלחמה" - description = "חיות המחמד שלך גורמות 5,000 נזק כולל" +title = "אדון המלחמה" +description = "חיות המחמד שלך גורמות 5,000 נזק כולל" [advancement.challenge_tamed_10] - title = "ידיד בעלי חיים" - description = "אלף 10 חיות" +title = "ידיד בעלי חיים" +description = "אלף 10 חיות" [advancement.challenge_tamed_100] - title = "שומר גן החיות" - description = "אלף 100 חיות" +title = "שומר גן החיות" +description = "אלף 100 חיות" [advancement.challenge_pet_kills_25] - title = "טקטיקת להקה" - description = "חיות המחמד שלך הורגות 25 יצורים" +title = "טקטיקת להקה" +description = "חיות המחמד שלך הורגות 25 יצורים" [advancement.challenge_pet_kills_250] - title = "מפקד הלהקה" - description = "חיות המחמד שלך הורגות 250 יצורים" +title = "מפקד הלהקה" +description = "חיות המחמד שלך הורגות 250 יצורים" [advancement.challenge_taming_2500] - title = "מומחה רבייה" - description = "הרבה 2,500 חיות" +title = "מומחה רבייה" +description = "הרבה 2,500 חיות" [advancement.challenge_taming_25k] - title = "מאסטר גנטיקה" - description = "הרבה 25,000 חיות" +title = "מאסטר גנטיקה" +description = "הרבה 25,000 חיות" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "שק חבטות" - description = "קבל 500 מכות" +title = "שק חבטות" +description = "קבל 500 מכות" [advancement.challenge_trag_hits_5k] - title = "מכור לסבל" - description = "קבל 5,000 מכות" +title = "מכור לסבל" +description = "קבל 5,000 מכות" [advancement.challenge_trag_deaths_10] - title = "תשע נשמות" - description = "מות 10 פעמים" +title = "תשע נשמות" +description = "מות 10 פעמים" [advancement.challenge_trag_deaths_100] - title = "סיוט חוזר" - description = "מות 100 פעמים" +title = "סיוט חוזר" +description = "מות 100 פעמים" [advancement.challenge_trag_fire_500] - title = "קורבן אש" - description = "ספוג 500 נזק אש" +title = "קורבן אש" +description = "ספוג 500 נזק אש" [advancement.challenge_trag_fire_5k] - title = "עוף החול" - description = "ספוג 5,000 נזק אש" +title = "עוף החול" +description = "ספוג 5,000 נזק אש" [advancement.challenge_trag_fall_500] - title = "בדיקת כבידה" - description = "ספוג 500 נזק נפילה" +title = "בדיקת כבידה" +description = "ספוג 500 נזק נפילה" [advancement.challenge_trag_fall_5k] - title = "מהירות סופית" - description = "ספוג 5,000 נזק נפילה" +title = "מהירות סופית" +description = "ספוג 5,000 נזק נפילה" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "מתאגרף" - description = "גרום 1,000 נזק באגרופים חשופים" +title = "מתאגרף" +description = "גרום 1,000 נזק באגרופים חשופים" [advancement.challenge_unarmed_dmg_10k] - title = "אמן לחימה" - description = "גרום 10,000 נזק באגרופים חשופים" +title = "אמן לחימה" +description = "גרום 10,000 נזק באגרופים חשופים" [advancement.challenge_unarmed_kills_25] - title = "אגרוף חשוף" - description = "הרוג 25 מובים באגרופים חשופים" +title = "אגרוף חשוף" +description = "הרוג 25 מובים באגרופים חשופים" [advancement.challenge_unarmed_kills_250] - title = "אגרוף האגדה" - description = "הרוג 250 מובים באגרופים חשופים" +title = "אגרוף האגדה" +description = "הרוג 250 מובים באגרופים חשופים" [advancement.challenge_unarmed_crit_25] - title = "אגרוף קריטי" - description = "נחות 25 מכות קריטיות באגרופים חשופים" +title = "אגרוף קריטי" +description = "נחות 25 מכות קריטיות באגרופים חשופים" [advancement.challenge_unarmed_crit_250] - title = "אגרוף מדויק" - description = "נחות 250 מכות קריטיות באגרופים חשופים" +title = "אגרוף מדויק" +description = "נחות 250 מכות קריטיות באגרופים חשופים" [advancement.challenge_unarmed_heavy_25] - title = "אגרוף כוח" - description = "נחות 25 מכות כבדות באגרופים חשופים (מעל 6 נזק)" +title = "אגרוף כוח" +description = "נחות 25 מכות כבדות באגרופים חשופים (מעל 6 נזק)" [advancement.challenge_unarmed_heavy_250] - title = "מלך הנוקאאוט" - description = "נחות 250 מכות כבדות באגרופים חשופים (מעל 6 נזק)" +title = "מלך הנוקאאוט" +description = "נחות 250 מכות כבדות באגרופים חשופים (מעל 6 נזק)" # items [items] [items.bound_ender_peral] - name = "מפתח שער רליקוויה" - usage1 = "Shift + לחיצה שמאלית כדי לאגד" - usage2 = "לחיצה ימנית כדי לגשת למלאי המאוגד" +name = "מפתח שער רליקוויה" +usage1 = "Shift + לחיצה שמאלית כדי לאגד" +usage2 = "לחיצה ימנית כדי לגשת למלאי המאוגד" [items.bound_eye_of_ender] - name = "עוגן עיני" - usage1 = "לחיצה ימנית כדי לצרוך ולהשתגר למיקום המאוגד" - usage2 = "Shift + לחיצה שמאלית כדי לאגד לבלוק" +name = "עוגן עיני" +usage1 = "לחיצה ימנית כדי לצרוך ולהשתגר למיקום המאוגד" +usage2 = "Shift + לחיצה שמאלית כדי לאגד לבלוק" [items.bound_redstone_torch] - name = "שלט רדסטון" - usage1 = "לחיצה ימנית כדי ליצור פולס רדסטון של טיק אחד" - usage2 = "Shift + לחיצה שמאלית על בלוק 'מטרה' כדי לאגד" +name = "שלט רדסטון" +usage1 = "לחיצה ימנית כדי ליצור פולס רדסטון של טיק אחד" +usage2 = "Shift + לחיצה שמאלית על בלוק 'מטרה' כדי לאגד" [items.bound_snowball] - name = "מלכודת קורים!" - usage1 = "זרוק כדי ליצור מלכודת קורים זמנית במיקום" +name = "מלכודת קורים!" +usage1 = "זרוק כדי ליצור מלכודת קורים זמנית במיקום" [items.chrono_time_bottle] - name = "זמן בבקבוק" - usage1 = "אוגר זמן באופן פסיבי כשהוא במלאי שלך" - usage2 = "לחיצה ימנית על בלוקים מתוזמנים או חיות תינוק כדי לבזבז זמן אגור" - stored = "זמן אגור" +name = "זמן בבקבוק" +usage1 = "אוגר זמן באופן פסיבי כשהוא במלאי שלך" +usage2 = "לחיצה ימנית על בלוקים מתוזמנים או חיות תינוק כדי לבזבז זמן אגור" +stored = "זמן אגור" [items.chrono_time_bomb] - name = "פצצת זמן" - usage1 = "לחיצה ימנית כדי לשגר בליסטיקת כרונו שיוצרת שדה טמפורלי" +name = "פצצת זמן" +usage1 = "לחיצה ימנית כדי לשגר בליסטיקת כרונו שיוצרת שדה טמפורלי" [items.elevator_block] - name = "בלוק מעלית" - usage1 = "קפוץ כדי להשתגר למעלה" - usage2 = "התכופף כדי להשתגר למטה" - usage3 = "מינימום 2 בלוקי אוויר בין המעליות" +name = "בלוק מעלית" +usage1 = "קפוץ כדי להשתגר למעלה" +usage2 = "התכופף כדי להשתגר למטה" +usage3 = "מינימום 2 בלוקי אוויר בין המעליות" # snippets [snippets] [snippets.gui] - level = "רמה" - knowledge = "ידע" - power_used = "כוח בשימוש" - not_learned = "לא נלמד" - xp = "XP עד" - welcome = "ברוך הבא!" - welcome_back = "ברוך שובך!" - xp_bonus_for_time = "XP עבור" - max_ability_power = "כוח יכולת מרבי" - unlock_this_by_clicking = "שחרר את הנעילה על ידי לחיצה ימנית: " - back = "חזור" - unlearn_all = "שכח הכל" - unlearned_all = "נשכח הכל" +level = "רמה" +knowledge = "ידע" +power_used = "כוח בשימוש" +not_learned = "לא נלמד" +xp = "XP עד" +welcome = "ברוך הבא!" +welcome_back = "ברוך שובך!" +xp_bonus_for_time = "XP עבור" +max_ability_power = "כוח יכולת מרבי" +unlock_this_by_clicking = "שחרר את הנעילה על ידי לחיצה ימנית: " +back = "חזור" +unlearn_all = "שכח הכל" +unlearned_all = "נשכח הכל" [snippets.adapt_menu] - may_not_unlearn = "אסור לשכוח" - may_unlearn = "ניתן ללמוד/לשכוח" - knowledge_cost = "עלות ידע" - knowledge_available = "ידע זמין" - already_learned = "כבר נלמד" - unlearn_refund = "לחץ כדי לשכוח ולקבל החזר" - no_refunds = "הארדקור, החזרים מושבתים" - knowledge = "ידע" - click_learn = "לחץ כדי ללמוד" - no_knowledge = "(אין לך ידע)" - you_only_have = "יש לך רק" - how_to_level_up = "עלה ברמת מיומנויות כדי להגדיל את הכוח המרבי שלך." - not_enough_power = "אין מספיק כוח! כל רמת יכולת עולה 1 כוח." - power = "כוח" - power_drain = "ניקוז כוח" - learned = "נלמד " - unlearned = "נשכח " - activator_block = "מדף ספרים" +may_not_unlearn = "אסור לשכוח" +may_unlearn = "ניתן ללמוד/לשכוח" +knowledge_cost = "עלות ידע" +knowledge_available = "ידע זמין" +already_learned = "כבר נלמד" +unlearn_refund = "לחץ כדי לשכוח ולקבל החזר" +no_refunds = "הארדקור, החזרים מושבתים" +knowledge = "ידע" +click_learn = "לחץ כדי ללמוד" +no_knowledge = "(אין לך ידע)" +you_only_have = "יש לך רק" +how_to_level_up = "עלה ברמת מיומנויות כדי להגדיל את הכוח המרבי שלך." +not_enough_power = "אין מספיק כוח! כל רמת יכולת עולה 1 כוח." +power = "כוח" +power_drain = "ניקוז כוח" +learned = "נלמד " +unlearned = "נשכח " +activator_block = "מדף ספרים" [snippets.knowledge_orb] - contains = "מכיל" - knowledge = "ידע" - rightclick = "לחיצה ימנית" - togainknowledge = "כדי לקבל את הידע הזה" - knowledge_orb = "כדור ידע" +contains = "מכיל" +knowledge = "ידע" +rightclick = "לחיצה ימנית" +togainknowledge = "כדי לקבל את הידע הזה" +knowledge_orb = "כדור ידע" [snippets.experience_orb] - contains = "מכיל" - xp = "ניסיון" - rightclick = "לחיצה ימנית" - togainxp = "כדי לקבל את הניסיון הזה" - xporb = "כדור ניסיון" +contains = "מכיל" +xp = "ניסיון" +rightclick = "לחיצה ימנית" +togainxp = "כדי לקבל את הניסיון הזה" +xporb = "כדור ניסיון" # skill [skill] [skill.agility] - name = "זריזות" - icon = "⇉" - description = "זריזות היא היכולת לנוע במהירות ובגמישות מול מכשולים." +name = "זריזות" +icon = "⇉" +description = "זריזות היא היכולת לנוע במהירות ובגמישות מול מכשולים." [skill.architect] - name = "אדריכלות" - icon = "⬧" - description = "מבנים הם אבני הבניין של העולם. המציאות בידיים שלך, שלך לשלוט." +name = "אדריכלות" +icon = "⬧" +description = "מבנים הם אבני הבניין של העולם. המציאות בידיים שלך, שלך לשלוט." [skill.axes] - name = "גרזנים" - icon = "🪓" - description1 = "למה לחטוב עצים, כשאפשר לחטוב " - description2 = "דברים" - description3 = "במקום, אותה תוצאה סופית!" +name = "גרזנים" +icon = "🪓" +description1 = "למה לחטוב עצים, כשאפשר לחטוב " +description2 = "דברים" +description3 = "במקום, אותה תוצאה סופית!" [skill.brewing] - name = "בישול שיקויים" - icon = "❦" - description = "בועה כפולה, בועה משולשת, בועה מרובעת- אני עדיין לא מצליח להכניס את השיקוי הזה לקדרה" +name = "בישול שיקויים" +icon = "❦" +description = "בועה כפולה, בועה משולשת, בועה מרובעת- אני עדיין לא מצליח להכניס את השיקוי הזה לקדרה" [skill.blocking] - name = "חסימה" - icon = "🛡" - description = "מקלות ואבנים לא ישברו לך עצמות, אבל מגן כן." +name = "חסימה" +icon = "🛡" +description = "מקלות ואבנים לא ישברו לך עצמות, אבל מגן כן." [skill.crafting] - name = "יצירה" - icon = "⌂" - description = "בלי שנותרו עוד חלקים למקם, למה לא ליצור עוד?" +name = "יצירה" +icon = "⌂" +description = "בלי שנותרו עוד חלקים למקם, למה לא ליצור עוד?" [skill.discovery] - name = "גילוי" - icon = "⚛" - description = "ככל שהתפיסה שלך מתרחבת, מוחך נפתח לגלות את מה שלא ידעת." +name = "גילוי" +icon = "⚛" +description = "ככל שהתפיסה שלך מתרחבת, מוחך נפתח לגלות את מה שלא ידעת." [skill.enchanting] - name = "כישוף" - icon = "♰" - description = "על מה אתה מדבר? נבואות, חזיונות, שטויות אמונה טפלה?" +name = "כישוף" +icon = "♰" +description = "על מה אתה מדבר? נבואות, חזיונות, שטויות אמונה טפלה?" [skill.excavation] - name = "חפירה" - icon = "ᛳ" - description = "חפירה חפירה חור..." +name = "חפירה" +icon = "ᛳ" +description = "חפירה חפירה חור..." [skill.herbalism] - name = "צמחי מרפא" - icon = "⚘" - description = "אני לא מוצא צמחים, אבל אני מוצא כמה זרעים ו- זה... עשב?" +name = "צמחי מרפא" +icon = "⚘" +description = "אני לא מוצא צמחים, אבל אני מוצא כמה זרעים ו- זה... עשב?" [skill.hunter] - name = "צייד" - icon = "☠" - description = "ציד זה המסע ולא התוצאה." +name = "צייד" +icon = "☠" +description = "ציד זה המסע ולא התוצאה." [skill.nether] - name = "נתר" - icon = "₪" - description = "ממעמקי הנתר עצמו." +name = "נתר" +icon = "₪" +description = "ממעמקי הנתר עצמו." [skill.pickaxe] - name = "מכוש" - icon = "⛏" - description = "גמדים הם הכורים, אבל למדתי דבר או שניים בזמני. אני שוודי!" +name = "מכוש" +icon = "⛏" +description = "גמדים הם הכורים, אבל למדתי דבר או שניים בזמני. אני שוודי!" [skill.ranged] - name = "טווח" - icon = "🏹" - description = "מרחק הוא המפתח לניצחון, והמפתח להישרדות." +name = "טווח" +icon = "🏹" +description = "מרחק הוא המפתח לניצחון, והמפתח להישרדות." [skill.rift] - name = "קרע" - icon = "❍" - description = "הקרע הוא רתמה צורבת, אבל רתמת את הרתמה." +name = "קרע" +icon = "❍" +description = "הקרע הוא רתמה צורבת, אבל רתמת את הרתמה." [skill.seaborne] - name = "ימי" - icon = "🎣" - description = "עם מיומנות זו, תוכל לשלוט בפלאות המים." +name = "ימי" +icon = "🎣" +description = "עם מיומנות זו, תוכל לשלוט בפלאות המים." [skill.stealth] - name = "התגנבות" - icon = "☯" - description = "אומנות הבלתי נראה. ללכת בצללים." +name = "התגנבות" +icon = "☯" +description = "אומנות הבלתי נראה. ללכת בצללים." [skill.swords] - name = "חרבות" - icon = "⚔" - description = "בכוחו של גריי-סטון!" +name = "חרבות" +icon = "⚔" +description = "בכוחו של גריי-סטון!" [skill.taming] - name = "אילוף" - icon = "♥" - description = "התוכים והדבורים... ואתה?" +name = "אילוף" +icon = "♥" +description = "התוכים והדבורים... ואתה?" [skill.tragoul] - name = "טראגול" - icon = "🗡" - description = "הדם זורם בעורקי היקום. מכווץ בידיים שלך." +name = "טראגול" +icon = "🗡" +description = "הדם זורם בעורקי היקום. מכווץ בידיים שלך." [skill.chronos] - name = "כרונוס" - icon = "🕒" - description = "כוון את שעון היקום, חווה את הזרימה. שבור את השעון, הפוך אליו." +name = "כרונוס" +icon = "🕒" +description = "כוון את שעון היקום, חווה את הזרימה. שבור את השעון, הפוך אליו." [skill.unarmed] - name = "לא חמוש" - icon = "»" - description = "בלי נשק לא אומר בלי כוח." +name = "לא חמוש" +icon = "»" +description = "בלי נשק לא אומר בלי כוח." # agility [agility] [agility.armor_up] - name = "שריון-אפ" - description = "קבל יותר שריון ככל שאתה רץ יותר זמן!" - lore1 = "שריון מרבי" - lore2 = "זמן שריון-אפ" - lore = ["שריון מרבי", "זמן שריון-אפ"] +name = "שריון-אפ" +description = "קבל יותר שריון ככל שאתה רץ יותר זמן!" +lore1 = "שריון מרבי" +lore2 = "זמן שריון-אפ" +lore = ["שריון מרבי", "זמן שריון-אפ"] [agility.ladder_slide] - name = "החלקת סולם" - description = "טפס והחלק על סולמות הרבה יותר מהר בשני הכיוונים." - lore1 = "מכפיל מהירות סולם" - lore2 = "מהירות ירידה מהירה" - lore = ["מכפיל מהירות סולם", "מהירות ירידה מהירה"] +name = "החלקת סולם" +description = "טפס והחלק על סולמות הרבה יותר מהר בשני הכיוונים." +lore1 = "מכפיל מהירות סולם" +lore2 = "מהירות ירידה מהירה" +lore = ["מכפיל מהירות סולם", "מהירות ירידה מהירה"] [agility.super_jump] - name = "סופר קפיצה" - description = "יתרון גובה יוצא דופן." - lore1 = "גובה קפיצה מרבי" - lore2 = "התכופף + קפוץ לסופר קפיצה!" - lore = ["גובה קפיצה מרבי", "התכופף + קפוץ לסופר קפיצה!"] +name = "סופר קפיצה" +description = "יתרון גובה יוצא דופן." +lore1 = "גובה קפיצה מרבי" +lore2 = "התכופף + קפוץ לסופר קפיצה!" +lore = ["גובה קפיצה מרבי", "התכופף + קפוץ לסופר קפיצה!"] [agility.wall_jump] - name = "קפיצת קיר" - description = "החזק shift באוויר ליד קיר כדי להיתלות ולקפוץ!" - lore1 = "מספר קפיצות מרבי" - lore2 = "גובה קפיצה" - lore = ["מספר קפיצות מרבי", "גובה קפיצה"] +name = "קפיצת קיר" +description = "החזק shift באוויר ליד קיר כדי להיתלות ולקפוץ!" +lore1 = "מספר קפיצות מרבי" +lore2 = "גובה קפיצה" +lore = ["מספר קפיצות מרבי", "גובה קפיצה"] [agility.wind_up] - name = "תאוצה" - description = "הפוך מהיר יותר ככל שאתה רץ זמן רב יותר!" - lore1 = "מהירות מרבית" - lore2 = "זמן תאוצה" - lore = ["מהירות מרבית", "זמן תאוצה"] +name = "תאוצה" +description = "הפוך מהיר יותר ככל שאתה רץ זמן רב יותר!" +lore1 = "מהירות מרבית" +lore2 = "זמן תאוצה" +lore = ["מהירות מרבית", "זמן תאוצה"] # architect [architect] [architect.elevator] - name = "מעלית" - description = "זה מאפשר לך לבנות מעלית לשיגור אנכי מהיר!" - lore1 = "פותח מתכון מעלית: X=צמר, Y=פנינת אנדר" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["פותח מתכון מעלית: X=צמר, Y=פנינת אנדר", "XXX", "XYX", "XXX"] +name = "מעלית" +description = "זה מאפשר לך לבנות מעלית לשיגור אנכי מהיר!" +lore1 = "פותח מתכון מעלית: X=צמר, Y=פנינת אנדר" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["פותח מתכון מעלית: X=צמר, Y=פנינת אנדר", "XXX", "XYX", "XXX"] [architect.foundation] - name = "יסוד קסום" - description = "מאפשר לך להתכופף ולהניח יסוד זמני מתחתיך!" - lore1 = "צור בקסם: " - lore2 = "בלוקים מתחתיך!" - lore = ["צור בקסם: ", "בלוקים מתחתיך!"] +name = "יסוד קסום" +description = "מאפשר לך להתכופף ולהניח יסוד זמני מתחתיך!" +lore1 = "צור בקסם: " +lore2 = "בלוקים מתחתיך!" +lore = ["צור בקסם: ", "בלוקים מתחתיך!"] [architect.glass] - name = "זכוכית מגע-משי" - description = "מאפשר לך למנוע אובדן בלוקי זכוכית כשאתה שובר אותם ביד ריקה!" - lore1 = "הידיים שלך מקבלות מגע-משי לזכוכית" - lore = ["הידיים שלך מקבלות מגע-משי לזכוכית"] +name = "זכוכית מגע-משי" +description = "מאפשר לך למנוע אובדן בלוקי זכוכית כשאתה שובר אותם ביד ריקה!" +lore1 = "הידיים שלך מקבלות מגע-משי לזכוכית" +lore = ["הידיים שלך מקבלות מגע-משי לזכוכית"] [architect.wireless_redstone] - name = "שלט רדסטון" - description = "מאפשר לך להשתמש בלפיד רדסטון כדי להפעיל רדסטון מרחוק!" - lore1 = "מטרה + לפיד רדסטון + פנינת אנדר = שלט רדסטון אחד" - lore = ["מטרה + לפיד רדסטון + פנינת אנדר = שלט רדסטון אחד"] +name = "שלט רדסטון" +description = "מאפשר לך להשתמש בלפיד רדסטון כדי להפעיל רדסטון מרחוק!" +lore1 = "מטרה + לפיד רדסטון + פנינת אנדר = שלט רדסטון אחד" +lore = ["מטרה + לפיד רדסטון + פנינת אנדר = שלט רדסטון אחד"] [architect.placement] - name = "שרביט בנאים" - description = "מאפשר לך להניח מספר בלוקים בבת אחת! התכופף, והחזק בלוק שמתאים לבלוק שאתה מסתכל עליו והנח! שים לב, ייתכן שתצטרך לזוז מעט כדי לסמן את התיבות!" - lore1 = "אתה צריך" - lore2 = "בלוקים ביד כדי להניח את זה" - lore3 = "שרביט בנאים מחומר" - lore = ["אתה צריך", "בלוקים ביד כדי להניח את זה", "שרביט בנאים מחומר"] +name = "שרביט בנאים" +description = "מאפשר לך להניח מספר בלוקים בבת אחת! התכופף, והחזק בלוק שמתאים לבלוק שאתה מסתכל עליו והנח! שים לב, ייתכן שתצטרך לזוז מעט כדי לסמן את התיבות!" +lore1 = "אתה צריך" +lore2 = "בלוקים ביד כדי להניח את זה" +lore3 = "שרביט בנאים מחומר" +lore = ["אתה צריך", "בלוקים ביד כדי להניח את זה", "שרביט בנאים מחומר"] # axe [axe] [axe.chop] - name = "חיתוך גרזן" - description = "חטוב עצים על ידי לחיצה ימנית על בול הבסיס!" - lore1 = "בלוקים לכל חיתוך" - lore2 = "זמן קירור חיתוך" - lore3 = "בלאי כלי" - lore = ["בלוקים לכל חיתוך", "זמן קירור חיתוך", "בלאי כלי"] +name = "חיתוך גרזן" +description = "חטוב עצים על ידי לחיצה ימנית על בול הבסיס!" +lore1 = "בלוקים לכל חיתוך" +lore2 = "זמן קירור חיתוך" +lore3 = "בלאי כלי" +lore = ["בלוקים לכל חיתוך", "זמן קירור חיתוך", "בלאי כלי"] [axe.log_swap] - name = "מחליף הבולים של לוסי" - description = "שנה את סוג הבולים בשולחן יצירה!" - lore1 = "8 בולים מכל סוג + 1 שתיל = 8 בולים מסוג השתיל" - lore = ["8 בולים מכל סוג + 1 שתיל = 8 בולים מסוג השתיל"] +name = "מחליף הבולים של לוסי" +description = "שנה את סוג הבולים בשולחן יצירה!" +lore1 = "8 בולים מכל סוג + 1 שתיל = 8 בולים מסוג השתיל" +lore = ["8 בולים מכל סוג + 1 שתיל = 8 בולים מסוג השתיל"] [axe.drop_to_inventory] - name = "גרזן-ישר-למלאי" +name = "גרזן-ישר-למלאי" [axe.ground_smash] - name = "מעיכת קרקע בגרזן" - description = "קפוץ, ואז התכופף ומעך את כל האויבים הקרובים." - lore1 = "נזק" - lore2 = "רדיוס בלוקים" - lore3 = "כוח" - lore4 = "זמן קירור מעיכה" - lore = ["נזק", "רדיוס בלוקים", "כוח", "זמן קירור מעיכה"] +name = "מעיכת קרקע בגרזן" +description = "קפוץ, ואז התכופף ומעך את כל האויבים הקרובים." +lore1 = "נזק" +lore2 = "רדיוס בלוקים" +lore3 = "כוח" +lore4 = "זמן קירור מעיכה" +lore = ["נזק", "רדיוס בלוקים", "כוח", "זמן קירור מעיכה"] [axe.leaf_miner] - name = "כורה עלים" - description = "מאפשר לך לשבור עלים בכמות בבת אחת!" - lore1 = "התכופף, וכרה עלים" - lore2 = "טווח כריית עלים" - lore3 = "לא תקבל את הדרופים מהעלים (מניעת ניצול)" - lore = ["התכופף, וכרה עלים", "טווח כריית עלים", "לא תקבל את הדרופים מהעלים (מניעת ניצול)"] +name = "כורה עלים" +description = "מאפשר לך לשבור עלים בכמות בבת אחת!" +lore1 = "התכופף, וכרה עלים" +lore2 = "טווח כריית עלים" +lore3 = "לא תקבל את הדרופים מהעלים (מניעת ניצול)" +lore = ["התכופף, וכרה עלים", "טווח כריית עלים", "לא תקבל את הדרופים מהעלים (מניעת ניצול)"] [axe.wood_miner] - name = "כורה עץ" - description = "מאפשר לך לשבור עץ בכמות בבת אחת!" - lore1 = "התכופף, וכרה עץ/בולים (לא קרשים)" - lore2 = "טווח כריית עץ" - lore3 = "עובד עם ישר-למלאי" - lore = ["התכופף, וכרה עץ/בולים (לא קרשים)", "טווח כריית עץ", "עובד עם ישר-למלאי"] +name = "כורה עץ" +description = "מאפשר לך לשבור עץ בכמות בבת אחת!" +lore1 = "התכופף, וכרה עץ/בולים (לא קרשים)" +lore2 = "טווח כריית עץ" +lore3 = "עובד עם ישר-למלאי" +lore = ["התכופף, וכרה עץ/בולים (לא קרשים)", "טווח כריית עץ", "עובד עם ישר-למלאי"] # brewing [brewing] [brewing.lingering] - name = "שיקוי מתמשך" - description = "שיקויים מבושלים נמשכים זמן רב יותר!" - lore1 = "משך" - lore2 = "משך" - lore = ["משך", "משך"] +name = "שיקוי מתמשך" +description = "שיקויים מבושלים נמשכים זמן רב יותר!" +lore1 = "משך" +lore2 = "משך" +lore = ["משך", "משך"] [brewing.super_heated] - name = "בישול סופר-חם" - description = "דוכני בישול עובדים מהר יותר ככל שהם חמים יותר." - lore1 = "לכל בלוק אש נוגע" - lore2 = "לכל בלוק לבה נוגע" - lore = ["לכל בלוק אש נוגע", "לכל בלוק לבה נוגע"] +name = "בישול סופר-חם" +description = "דוכני בישול עובדים מהר יותר ככל שהם חמים יותר." +lore1 = "לכל בלוק אש נוגע" +lore2 = "לכל בלוק לבה נוגע" +lore = ["לכל בלוק אש נוגע", "לכל בלוק לבה נוגע"] [brewing.darkness] - name = "חושך בבקבוק" - description = "לא בטוח למה אתה צריך את זה, אבל הנה לך!" - lore1 = "שיקוי ראיית לילה + בטון שחור = שיקוי חושך (30 שניות)" - lore2 = "שים לב שזה מונע מהמשתמש לרוץ!" - lore = ["שיקוי ראיית לילה + בטון שחור = שיקוי חושך (30 שניות)", "שים לב שזה מונע מהמשתמש לרוץ!"] +name = "חושך בבקבוק" +description = "לא בטוח למה אתה צריך את זה, אבל הנה לך!" +lore1 = "שיקוי ראיית לילה + בטון שחור = שיקוי חושך (30 שניות)" +lore2 = "שים לב שזה מונע מהמשתמש לרוץ!" +lore = ["שיקוי ראיית לילה + בטון שחור = שיקוי חושך (30 שניות)", "שים לב שזה מונע מהמשתמש לרוץ!"] [brewing.haste] - name = "חיפזון בבקבוק" - description = "כשיעילות לא מספיקה" - lore1 = "שיקוי מהירות + רסיס אמטיסט = שיקוי חיפזון (60 שניות)" - lore2 = "שיקוי מהירות + בלוק אמטיסט = שיקוי חיפזון-2 (30 שניות)" - lore = ["שיקוי מהירות + רסיס אמטיסט = שיקוי חיפזון (60 שניות)", "שיקוי מהירות + בלוק אמטיסט = שיקוי חיפזון-2 (30 שניות)"] +name = "חיפזון בבקבוק" +description = "כשיעילות לא מספיקה" +lore1 = "שיקוי מהירות + רסיס אמטיסט = שיקוי חיפזון (60 שניות)" +lore2 = "שיקוי מהירות + בלוק אמטיסט = שיקוי חיפזון-2 (30 שניות)" +lore = ["שיקוי מהירות + רסיס אמטיסט = שיקוי חיפזון (60 שניות)", "שיקוי מהירות + בלוק אמטיסט = שיקוי חיפזון-2 (30 שניות)"] [brewing.absorption] - name = "ספיגה בבקבוק" - description = "חזק את הגוף!" - lore1 = "ריפוי מיידי + קוורץ = שיקוי ספיגה (60 שניות)" - lore2 = "ריפוי מיידי + בלוק קוורץ = שיקוי ספיגה-2 (30 שניות)" - lore = ["ריפוי מיידי + קוורץ = שיקוי ספיגה (60 שניות)", "ריפוי מיידי + בלוק קוורץ = שיקוי ספיגה-2 (30 שניות)"] +name = "ספיגה בבקבוק" +description = "חזק את הגוף!" +lore1 = "ריפוי מיידי + קוורץ = שיקוי ספיגה (60 שניות)" +lore2 = "ריפוי מיידי + בלוק קוורץ = שיקוי ספיגה-2 (30 שניות)" +lore = ["ריפוי מיידי + קוורץ = שיקוי ספיגה (60 שניות)", "ריפוי מיידי + בלוק קוורץ = שיקוי ספיגה-2 (30 שניות)"] [brewing.fatigue] - name = "עייפות בבקבוק" - description = "החלש את הגוף!" - lore1 = "שיקוי חולשה + כדור רפש = שיקוי עייפות (30 שניות)" - lore2 = "שיקוי חולשה + בלוק רפש = שיקוי עייפות-2 (15 שניות)" - lore = ["שיקוי חולשה + כדור רפש = שיקוי עייפות (30 שניות)", "שיקוי חולשה + בלוק רפש = שיקוי עייפות-2 (15 שניות)"] +name = "עייפות בבקבוק" +description = "החלש את הגוף!" +lore1 = "שיקוי חולשה + כדור רפש = שיקוי עייפות (30 שניות)" +lore2 = "שיקוי חולשה + בלוק רפש = שיקוי עייפות-2 (15 שניות)" +lore = ["שיקוי חולשה + כדור רפש = שיקוי עייפות (30 שניות)", "שיקוי חולשה + בלוק רפש = שיקוי עייפות-2 (15 שניות)"] [brewing.hunger] - name = "רעב בבקבוק" - description = "האכל את הבלתי שביע!" - lore1 = "שיקוי מוזר + בשר רקוב = שיקוי רעב (30 שניות)" - lore2 = "שיקוי חולשה + בשר רקוב = שיקוי רעב-3 (15 שניות)" - lore = ["שיקוי מוזר + בשר רקוב = שיקוי רעב (30 שניות)", "שיקוי חולשה + בשר רקוב = שיקוי רעב-3 (15 שניות)"] +name = "רעב בבקבוק" +description = "האכל את הבלתי שביע!" +lore1 = "שיקוי מוזר + בשר רקוב = שיקוי רעב (30 שניות)" +lore2 = "שיקוי חולשה + בשר רקוב = שיקוי רעב-3 (15 שניות)" +lore = ["שיקוי מוזר + בשר רקוב = שיקוי רעב (30 שניות)", "שיקוי חולשה + בשר רקוב = שיקוי רעב-3 (15 שניות)"] [brewing.nausea] - name = "בחילה בבקבוק" - description = "אתה מחליא אותי!" - lore1 = "שיקוי מוזר + פטריה חומה = שיקוי בחילה (16 שניות)" - lore2 = "שיקוי מוזר + פטריית ארגמן = שיקוי בחילה-2 (8 שניות)" - lore = ["שיקוי מוזר + פטריה חומה = שיקוי בחילה (16 שניות)", "שיקוי מוזר + פטריית ארגמן = שיקוי בחילה-2 (8 שניות)"] +name = "בחילה בבקבוק" +description = "אתה מחליא אותי!" +lore1 = "שיקוי מוזר + פטריה חומה = שיקוי בחילה (16 שניות)" +lore2 = "שיקוי מוזר + פטריית ארגמן = שיקוי בחילה-2 (8 שניות)" +lore = ["שיקוי מוזר + פטריה חומה = שיקוי בחילה (16 שניות)", "שיקוי מוזר + פטריית ארגמן = שיקוי בחילה-2 (8 שניות)"] [brewing.blindness] - name = "עיוורון בבקבוק" - description = "אתה אדם נורא..." - lore1 = "שיקוי מוזר + שק דיו = שיקוי עיוורון (30 שניות)" - lore2 = "שיקוי מוזר + שק דיו זוהר = שיקוי עיוורון-2 (15 שניות)" - lore = ["שיקוי מוזר + שק דיו = שיקוי עיוורון (30 שניות)", "שיקוי מוזר + שק דיו זוהר = שיקוי עיוורון-2 (15 שניות)"] +name = "עיוורון בבקבוק" +description = "אתה אדם נורא..." +lore1 = "שיקוי מוזר + שק דיו = שיקוי עיוורון (30 שניות)" +lore2 = "שיקוי מוזר + שק דיו זוהר = שיקוי עיוורון-2 (15 שניות)" +lore = ["שיקוי מוזר + שק דיו = שיקוי עיוורון (30 שניות)", "שיקוי מוזר + שק דיו זוהר = שיקוי עיוורון-2 (15 שניות)"] [brewing.resistance] - name = "התנגדות בבקבוק" - description = "ביצור במיטבו!" - lore1 = "שיקוי מוזר + מטיל ברזל = שיקוי התנגדות (60 שניות)" - lore2 = "שיקוי מוזר + בלוק ברזל = שיקוי התנגדות-2 (30 שניות)" - lore = ["שיקוי מוזר + מטיל ברזל = שיקוי התנגדות (60 שניות)", "שיקוי מוזר + בלוק ברזל = שיקוי התנגדות-2 (30 שניות)"] +name = "התנגדות בבקבוק" +description = "ביצור במיטבו!" +lore1 = "שיקוי מוזר + מטיל ברזל = שיקוי התנגדות (60 שניות)" +lore2 = "שיקוי מוזר + בלוק ברזל = שיקוי התנגדות-2 (30 שניות)" +lore = ["שיקוי מוזר + מטיל ברזל = שיקוי התנגדות (60 שניות)", "שיקוי מוזר + בלוק ברזל = שיקוי התנגדות-2 (30 שניות)"] [brewing.health_boost] - name = "חיים בבקבוק" - description = "כשבריאות מרבית לא מספיקה..." - lore1 = "שיקוי ריפוי מיידי + תפוח זהב = שיקוי חיזוק בריאות (120 שניות)" - lore2 = "שיקוי ריפוי מיידי + תפוח זהב קסום = שיקוי חיזוק בריאות-2 (120 שניות)" - lore = ["שיקוי ריפוי מיידי + תפוח זהב = שיקוי חיזוק בריאות (120 שניות)", "שיקוי ריפוי מיידי + תפוח זהב קסום = שיקוי חיזוק בריאות-2 (120 שניות)"] +name = "חיים בבקבוק" +description = "כשבריאות מרבית לא מספיקה..." +lore1 = "שיקוי ריפוי מיידי + תפוח זהב = שיקוי חיזוק בריאות (120 שניות)" +lore2 = "שיקוי ריפוי מיידי + תפוח זהב קסום = שיקוי חיזוק בריאות-2 (120 שניות)" +lore = ["שיקוי ריפוי מיידי + תפוח זהב = שיקוי חיזוק בריאות (120 שניות)", "שיקוי ריפוי מיידי + תפוח זהב קסום = שיקוי חיזוק בריאות-2 (120 שניות)"] [brewing.decay] - name = "ריקבון בבקבוק" - description = "מי ידע שפסולת תהיה כל כך שימושית?" - lore1 = "שיקוי חולשה + תפוח אדמה רעיל = שיקוי כמילה (16 שניות)" - lore2 = "שיקוי חולשה + שורשי ארגמן = שיקוי כמילה-2 (8 שניות)" - lore = ["שיקוי חולשה + תפוח אדמה רעיל = שיקוי כמילה (16 שניות)", "שיקוי חולשה + שורשי ארגמן = שיקוי כמילה-2 (8 שניות)"] +name = "ריקבון בבקבוק" +description = "מי ידע שפסולת תהיה כל כך שימושית?" +lore1 = "שיקוי חולשה + תפוח אדמה רעיל = שיקוי כמילה (16 שניות)" +lore2 = "שיקוי חולשה + שורשי ארגמן = שיקוי כמילה-2 (8 שניות)" +lore = ["שיקוי חולשה + תפוח אדמה רעיל = שיקוי כמילה (16 שניות)", "שיקוי חולשה + שורשי ארגמן = שיקוי כמילה-2 (8 שניות)"] [brewing.saturation] - name = "רוויה בבקבוק" - description = "אתה יודע... אני אפילו לא רעב..." - lore1 = "שיקוי התחדשות + תפוח אדמה אפוי = שיקוי רוויה" - lore2 = "שיקוי התחדשות + חבילת חציר = שיקוי רוויה-2" - lore = ["שיקוי התחדשות + תפוח אדמה אפוי = שיקוי רוויה", "שיקוי התחדשות + חבילת חציר = שיקוי רוויה-2"] +name = "רוויה בבקבוק" +description = "אתה יודע... אני אפילו לא רעב..." +lore1 = "שיקוי התחדשות + תפוח אדמה אפוי = שיקוי רוויה" +lore2 = "שיקוי התחדשות + חבילת חציר = שיקוי רוויה-2" +lore = ["שיקוי התחדשות + תפוח אדמה אפוי = שיקוי רוויה", "שיקוי התחדשות + חבילת חציר = שיקוי רוויה-2"] # blocking [blocking] [blocking.chain_armorer] - name = "שרשראות מפיסטופלס" - description = "מאפשר לך ליצור שריון שרשרת" - lore1 = "מתכון היצירה זהה לכל שריון אחר, אבל עם גושי ברזל קטנים במקום" - lore = ["מתכון היצירה זהה לכל שריון אחר, אבל עם גושי ברזל קטנים במקום"] +name = "שרשראות מפיסטופלס" +description = "מאפשר לך ליצור שריון שרשרת" +lore1 = "מתכון היצירה זהה לכל שריון אחר, אבל עם גושי ברזל קטנים במקום" +lore = ["מתכון היצירה זהה לכל שריון אחר, אבל עם גושי ברזל קטנים במקום"] [blocking.horse_armorer] - name = "שריון סוס בר-יצירה" - description = "מאפשר לך ליצור שריון סוס" - lore1 = "הקף אוכף עם החומר שבו תרצה להשתמש ליצירת השריון" - lore = ["הקף אוכף עם החומר שבו תרצה להשתמש ליצירת השריון"] +name = "שריון סוס בר-יצירה" +description = "מאפשר לך ליצור שריון סוס" +lore1 = "הקף אוכף עם החומר שבו תרצה להשתמש ליצירת השריון" +lore = ["הקף אוכף עם החומר שבו תרצה להשתמש ליצירת השריון"] [blocking.saddle_crafter] - name = "אוכף בר-יצירה" - description = "צור אוכף עם עור" - lore1 = "מתכון: 5 עורות:" - lore = ["מתכון: 5 עורות:"] +name = "אוכף בר-יצירה" +description = "צור אוכף עם עור" +lore1 = "מתכון: 5 עורות:" +lore = ["מתכון: 5 עורות:"] [blocking.multi_armor] - name = "רב-שריון" - description = "אגד אליטרות לשריון" - lore1 = "זוהי מיומנות מדהימה לנסיעות." - lore2 = "מזג ושנה שריון/אליטרה באופן דינמי תוך כדי תנועה!" - lore3 = "למיזוג, לחץ shift על פריט מעל אחר במלאי שלך." - lore4 = "לשחרור שריון, התכופף-והפל את הפריט, והוא יתפרק." - lore5 = "אם הרב-שריון שלך נהרס, תאבד את כל הפריטים שבו." - lore6 = "אני לא צריך שריון, זה מאכזב אותי..." - lore = ["זוהי מיומנות מדהימה לנסיעות.", "מזג ושנה שריון/אליטרה באופן דינמי תוך כדי תנועה!", "למיזוג, לחץ shift על פריט מעל אחר במלאי שלך.", "לשחרור שריון, התכופף-והפל את הפריט, והוא יתפרק.", "אם הרב-שריון שלך נהרס, תאבד את כל הפריטים שבו.", "אני לא צריך שריון, זה מאכזב אותי..."] +name = "רב-שריון" +description = "אגד אליטרות לשריון" +lore1 = "זוהי מיומנות מדהימה לנסיעות." +lore2 = "מזג ושנה שריון/אליטרה באופן דינמי תוך כדי תנועה!" +lore3 = "למיזוג, לחץ shift על פריט מעל אחר במלאי שלך." +lore4 = "לשחרור שריון, התכופף-והפל את הפריט, והוא יתפרק." +lore5 = "אם הרב-שריון שלך נהרס, תאבד את כל הפריטים שבו." +lore6 = "אני לא צריך שריון, זה מאכזב אותי..." +lore = ["זוהי מיומנות מדהימה לנסיעות.", "מזג ושנה שריון/אליטרה באופן דינמי תוך כדי תנועה!", "למיזוג, לחץ shift על פריט מעל אחר במלאי שלך.", "לשחרור שריון, התכופף-והפל את הפריט, והוא יתפרק.", "אם הרב-שריון שלך נהרס, תאבד את כל הפריטים שבו.", "אני לא צריך שריון, זה מאכזב אותי..."] # crafting [crafting] [crafting.deconstruction] - name = "פירוק" - description = "פרק בלוקים ופריטים לרכיבי בסיס שניתן להציל!" - lore1 = "הפל פריט כלשהו לרצפה." - lore2 = "אז, התכופף ולחץ לחיצה ימנית עם מספריים" - lore = ["הפל פריט כלשהו לרצפה.", "אז, התכופף ולחץ לחיצה ימנית עם מספריים"] +name = "פירוק" +description = "פרק בלוקים ופריטים לרכיבי בסיס שניתן להציל!" +lore1 = "הפל פריט כלשהו לרצפה." +lore2 = "אז, התכופף ולחץ לחיצה ימנית עם מספריים" +lore = ["הפל פריט כלשהו לרצפה.", "אז, התכופף ולחץ לחיצה ימנית עם מספריים"] [crafting.xp] - name = "ניסיון יצירה" - description = "קבל ניסיון פסיבי בעת יצירה" - lore1 = "קבל ניסיון בעת יצירה" - lore = ["קבל ניסיון בעת יצירה"] +name = "ניסיון יצירה" +description = "קבל ניסיון פסיבי בעת יצירה" +lore1 = "קבל ניסיון בעת יצירה" +lore = ["קבל ניסיון בעת יצירה"] [crafting.reconstruction] - name = "שחזור עפרות" - description = "צור מחדש עפרות מרכיבי הבסיס שלהם!" - lore1 = "8 מהדרופים ו-1 מארח = 1 עפרה (ללא צורה)" - lore2 = "הדרופים חייבים להיות מותכים (אם רלוונטי)" - lore3 = "לא כולל: גרוטאות, קוורץ, ואזמרגדים וכו'..." - lore4 = "מארח = עטיפה. כלומר: אבן, נתראק, צפחה עמוקה" - lore = ["8 מהדרופים ו-1 מארח = 1 עפרה (ללא צורה)", "הדרופים חייבים להיות מותכים (אם רלוונטי)", "לא כולל: גרוטאות, קוורץ, ואזמרגדים וכו'...", "מארח = עטיפה. כלומר: אבן, נתראק, צפחה עמוקה"] +name = "שחזור עפרות" +description = "צור מחדש עפרות מרכיבי הבסיס שלהם!" +lore1 = "8 מהדרופים ו-1 מארח = 1 עפרה (ללא צורה)" +lore2 = "הדרופים חייבים להיות מותכים (אם רלוונטי)" +lore3 = "לא כולל: גרוטאות, קוורץ, ואזמרגדים וכו'..." +lore4 = "מארח = עטיפה. כלומר: אבן, נתראק, צפחה עמוקה" +lore = ["8 מהדרופים ו-1 מארח = 1 עפרה (ללא צורה)", "הדרופים חייבים להיות מותכים (אם רלוונטי)", "לא כולל: גרוטאות, קוורץ, ואזמרגדים וכו'...", "מארח = עטיפה. כלומר: אבן, נתראק, צפחה עמוקה"] [crafting.leather] - name = "עור בר-יצירה" - description = "צור עור מבשר רקוב" - lore1 = "פשוט זרוק אותו (בשר רקוב) על המדורה!" - lore = ["פשוט זרוק אותו (בשר רקוב) על המדורה!"] +name = "עור בר-יצירה" +description = "צור עור מבשר רקוב" +lore1 = "פשוט זרוק אותו (בשר רקוב) על המדורה!" +lore = ["פשוט זרוק אותו (בשר רקוב) על המדורה!"] [crafting.backpacks] - name = "התרמילים של בוטילייה!" - description = "זה פשוט מביא את צרור מוג'אנג למשחק!" - lore1 = "אתה צריך להיות במצב הישרדות כדי להשתמש בזה" - lore2 = "XLX : עור, רצועה, עור" - lore3 = "XSX : עור, קופסת חבית, עור" - lore4 = "XCX : עור, ארגז, עור" - lore = ["אתה צריך להיות במצב הישרדות כדי להשתמש בזה", "XLX : עור, רצועה, עור", "XSX : עור, קופסת חבית, עור", "XCX : עור, ארגז, עור"] +name = "התרמילים של בוטילייה!" +description = "זה פשוט מביא את צרור מוג'אנג למשחק!" +lore1 = "אתה צריך להיות במצב הישרדות כדי להשתמש בזה" +lore2 = "XLX : עור, רצועה, עור" +lore3 = "XSX : עור, קופסת חבית, עור" +lore4 = "XCX : עור, ארגז, עור" +lore = ["אתה צריך להיות במצב הישרדות כדי להשתמש בזה", "XLX : עור, רצועה, עור", "XSX : עור, קופסת חבית, עור", "XCX : עור, ארגז, עור"] [crafting.stations] - name = "שולחנות ניידים!" - description = "השתמש בשולחן מכף ידך!" - lore2 = "כל פריט שתשכח בשולחן כשהוא נסגר אבוד לנצח!" - lore3 = "שולחנות תקפים: סדן, יצירה, אבן השחזה, קרטוגרפיה, חותך אבן, נול" - lore = ["כל פריט שתשכח בשולחן כשהוא נסגר אבוד לנצח!", "שולחנות תקפים: סדן, יצירה, אבן השחזה, קרטוגרפיה, חותך אבן, נול"] +name = "שולחנות ניידים!" +description = "השתמש בשולחן מכף ידך!" +lore2 = "כל פריט שתשכח בשולחן כשהוא נסגר אבוד לנצח!" +lore3 = "שולחנות תקפים: סדן, יצירה, אבן השחזה, קרטוגרפיה, חותך אבן, נול" +lore = ["כל פריט שתשכח בשולחן כשהוא נסגר אבוד לנצח!", "שולחנות תקפים: סדן, יצירה, אבן השחזה, קרטוגרפיה, חותך אבן, נול"] [crafting.skulls] - name = "גולגולות בנות-יצירה!" - description = "באמצעות חומרים תוכל ליצור גולגולות מפלצות!" - lore1 = "הקף בלוק עצם עם הדברים הבאים כדי לקבל גולגולת:" - lore2 = "זומבי: בשר רקוב" - lore3 = "שלד: עצם" - lore4 = "קריפר: אבק שריפה" - lore5 = "וויתר: לבנת נתר" - lore6 = "דרקון: נשימת דרקון" - lore = ["הקף בלוק עצם עם הדברים הבאים כדי לקבל גולגולת:", "זומבי: בשר רקוב", "שלד: עצם", "קריפר: אבק שריפה", "וויתר: לבנת נתר", "דרקון: נשימת דרקון"] +name = "גולגולות בנות-יצירה!" +description = "באמצעות חומרים תוכל ליצור גולגולות מפלצות!" +lore1 = "הקף בלוק עצם עם הדברים הבאים כדי לקבל גולגולת:" +lore2 = "זומבי: בשר רקוב" +lore3 = "שלד: עצם" +lore4 = "קריפר: אבק שריפה" +lore5 = "וויתר: לבנת נתר" +lore6 = "דרקון: נשימת דרקון" +lore = ["הקף בלוק עצם עם הדברים הבאים כדי לקבל גולגולת:", "זומבי: בשר רקוב", "שלד: עצם", "קריפר: אבק שריפה", "וויתר: לבנת נתר", "דרקון: נשימת דרקון"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "זמן בבקבוק" - description = "נשא בקבוק טמפורלי שאוגר זמן והוצא אותו כדי להאיץ בלוקים מתוזמנים, גידולים, וישויות בעלות גיל כמו חיות תינוק. מתכון (ללא צורה): שיקוי מהירות + שעון + בקבוק זכוכית." - lore1 = "שניות נטענות כל טיק" - lore2 = "האצת זמן לכל שנייה אגורה" - lore3 = "מתכון (ללא צורה): שיקוי מהירות + שעון + בקבוק זכוכית" - lore = ["שניות נטענות כל טיק", "האצת זמן לכל שנייה אגורה", "מתכון (ללא צורה): שיקוי מהירות + שעון + בקבוק זכוכית"] +name = "זמן בבקבוק" +description = "נשא בקבוק טמפורלי שאוגר זמן והוצא אותו כדי להאיץ בלוקים מתוזמנים, גידולים, וישויות בעלות גיל כמו חיות תינוק. מתכון (ללא צורה): שיקוי מהירות + שעון + בקבוק זכוכית." +lore1 = "שניות נטענות כל טיק" +lore2 = "האצת זמן לכל שנייה אגורה" +lore3 = "מתכון (ללא צורה): שיקוי מהירות + שעון + בקבוק זכוכית" +lore = ["שניות נטענות כל טיק", "האצת זמן לכל שנייה אגורה", "מתכון (ללא צורה): שיקוי מהירות + שעון + בקבוק זכוכית"] [chronos.aberrant_touch] - name = "מגע סוטה" - description = "מכות קרב מחילות האטה מצטברת על חשבון רעב, עם מגבלות PvP קפדניות, ומשרשות מטרות ב-5 ערימות." - lore1 = "מכות קרב מחילות האטה מצטברת" - lore2 = "מגבלת משך האטה ב-PvE" - lore3 = "מגבלת עוצמת האטה ב-PvP" - lore = ["מכות קרב מחילות האטה מצטברת", "מגבלת משך האטה ב-PvE", "מגבלת עוצמת האטה ב-PvP"] +name = "מגע סוטה" +description = "מכות קרב מחילות האטה מצטברת על חשבון רעב, עם מגבלות PvP קפדניות, ומשרשות מטרות ב-5 ערימות." +lore1 = "מכות קרב מחילות האטה מצטברת" +lore2 = "מגבלת משך האטה ב-PvE" +lore3 = "מגבלת עוצמת האטה ב-PvP" +lore = ["מכות קרב מחילות האטה מצטברת", "מגבלת משך האטה ב-PvE", "מגבלת עוצמת האטה ב-PvP"] [chronos.instant_recall] - name = "ריקול מיידי" - description = "לחץ שמאל או ימין עם שעון ביד כדי להריץ אחורה לתמונת מצב אחרונה עם שחזור בריאות ורעב." - lore1 = "משך הרצה לאחור" - lore2 = "זמן קירור" - lore3 = "ללא שחזור מלאי" - lore = ["משך הרצה לאחור", "זמן קירור", "ללא שחזור מלאי"] +name = "ריקול מיידי" +description = "לחץ שמאל או ימין עם שעון ביד כדי להריץ אחורה לתמונת מצב אחרונה עם שחזור בריאות ורעב." +lore1 = "משך הרצה לאחור" +lore2 = "זמן קירור" +lore3 = "ללא שחזור מלאי" +lore = ["משך הרצה לאחור", "זמן קירור", "ללא שחזור מלאי"] [chronos.time_bomb] - name = "פצצת זמן" - description = "זרוק פצצת כרונו מעוצבת שיוצרת שדה טמפורלי, מאטה ישויות, ומקפיאה קליעים." - lore1 = "רדיוס שדה טמפורלי" - lore2 = "משך שדה טמפורלי" - lore3 = "זמן קירור פצצה" - lore4 = "מתכון (ללא צורה): שעון + כדור שלג + יהלום + חול" - lore = ["רדיוס שדה טמפורלי", "משך שדה טמפורלי", "זמן קירור פצצה", "מתכון (ללא צורה): שעון + כדור שלג + יהלום + חול"] +name = "פצצת זמן" +description = "זרוק פצצת כרונו מעוצבת שיוצרת שדה טמפורלי, מאטה ישויות, ומקפיאה קליעים." +lore1 = "רדיוס שדה טמפורלי" +lore2 = "משך שדה טמפורלי" +lore3 = "זמן קירור פצצה" +lore4 = "מתכון (ללא צורה): שעון + כדור שלג + יהלום + חול" +lore = ["רדיוס שדה טמפורלי", "משך שדה טמפורלי", "זמן קירור פצצה", "מתכון (ללא צורה): שעון + כדור שלג + יהלום + חול"] # discovery [discovery] [discovery.armor] - name = "שריון עולמי" - description = "שריון פסיבי בהתאם לקשיות הבלוקים הסמוכים." - lore1 = "שריון פסיבי" - lore2 = "מבוסס על קשיות בלוקים סמוכים" - lore3 = "עוצמת שריון:" - lore = ["שריון פסיבי", "מבוסס על קשיות בלוקים סמוכים", "עוצמת שריון:"] +name = "שריון עולמי" +description = "שריון פסיבי בהתאם לקשיות הבלוקים הסמוכים." +lore1 = "שריון פסיבי" +lore2 = "מבוסס על קשיות בלוקים סמוכים" +lore3 = "עוצמת שריון:" +lore = ["שריון פסיבי", "מבוסס על קשיות בלוקים סמוכים", "עוצמת שריון:"] [discovery.unity] - name = "אחדות ניסיונית" - description = "איסוף כדורי ניסיון מוסיף XP למיומנויות אקראיות." - lore1 = "XP " - lore2 = "לכל כדור" - lore = ["XP ", "לכל כדור"] +name = "אחדות ניסיונית" +description = "איסוף כדורי ניסיון מוסיף XP למיומנויות אקראיות." +lore1 = "XP " +lore2 = "לכל כדור" +lore = ["XP ", "לכל כדור"] [discovery.resist] - name = "התנגדות ניסיונית" - description = "צרוך ניסיון כדי להקטין נזק רק כשמכה עלולה להפיל אותך מתחת ל-5 לבבות או להרוג אותך." - lore0 = "מופעל רק בבריאות קריטית (<= 5 לבבות) פעם ב-15 שניות" - lore1 = " נזק מופחת" - lore2 = "ניסיון מנוקז" - lore = ["מופעל רק בבריאות קריטית (<= 5 לבבות) פעם ב-15 שניות", " נזק מופחת", "ניסיון מנוקז"] +name = "התנגדות ניסיונית" +description = "צרוך ניסיון כדי להקטין נזק רק כשמכה עלולה להפיל אותך מתחת ל-5 לבבות או להרוג אותך." +lore0 = "מופעל רק בבריאות קריטית (<= 5 לבבות) פעם ב-15 שניות" +lore1 = " נזק מופחת" +lore2 = "ניסיון מנוקז" +lore = ["מופעל רק בבריאות קריטית (<= 5 לבבות) פעם ב-15 שניות", " נזק מופחת", "ניסיון מנוקז"] [discovery.villager] - name = "משיכת כפריים" - description = "מאפשר לך לקבל עסקאות טובות יותר עם כפריים!" - lore1 = "צורך XP לכל אינטראקציה עם כפריים" - lore2 = "סיכוי לכל אינטראקציה לצרוך XP ולשפר עסקאות" - lore3 = "ניקוז XP נדרש לכל אינטראקציה" - lore = ["צורך XP לכל אינטראקציה עם כפריים", "סיכוי לכל אינטראקציה לצרוך XP ולשפר עסקאות", "ניקוז XP נדרש לכל אינטראקציה"] +name = "משיכת כפריים" +description = "מאפשר לך לקבל עסקאות טובות יותר עם כפריים!" +lore1 = "צורך XP לכל אינטראקציה עם כפריים" +lore2 = "סיכוי לכל אינטראקציה לצרוך XP ולשפר עסקאות" +lore3 = "ניקוז XP נדרש לכל אינטראקציה" +lore = ["צורך XP לכל אינטראקציה עם כפריים", "סיכוי לכל אינטראקציה לצרוך XP ולשפר עסקאות", "ניקוז XP נדרש לכל אינטראקציה"] # enchanting [enchanting] [enchanting.lapis_return] - name = "החזר לאפיס" - description = "במחיר רמה אחת נוספת של XP, ויש סיכוי לקבל לאפיס חינם בתמורה" - lore1 = "לכל רמה, מגדיל את עלות הכישוף ב-1, אבל יכול להחזיר עד 3 לאפיס" - lore = ["לכל רמה, מגדיל את עלות הכישוף ב-1, אבל יכול להחזיר עד 3 לאפיס"] +name = "החזר לאפיס" +description = "במחיר רמה אחת נוספת של XP, ויש סיכוי לקבל לאפיס חינם בתמורה" +lore1 = "לכל רמה, מגדיל את עלות הכישוף ב-1, אבל יכול להחזיר עד 3 לאפיס" +lore = ["לכל רמה, מגדיל את עלות הכישוף ב-1, אבל יכול להחזיר עד 3 לאפיס"] [enchanting.quick_enchant] - name = "כישוף בלחיצה מהירה" - description = "כשף פריטים על ידי לחיצה על ספרי כישוף ישירות עליהם." - lore1 = "רמות משולבות מרביות" - lore2 = "לא ניתן לכשף פריט עם יותר מ-" - lore3 = "כוח" - lore = ["רמות משולבות מרביות", "לא ניתן לכשף פריט עם יותר מ-", "כוח"] +name = "כישוף בלחיצה מהירה" +description = "כשף פריטים על ידי לחיצה על ספרי כישוף ישירות עליהם." +lore1 = "רמות משולבות מרביות" +lore2 = "לא ניתן לכשף פריט עם יותר מ-" +lore3 = "כוח" +lore = ["רמות משולבות מרביות", "לא ניתן לכשף פריט עם יותר מ-", "כוח"] [enchanting.return] - name = "החזר XP" - description = "ניסיון כישוף מוחזר אליך כשאתה מכשף פריט." - lore1 = "לניסיון שהוצאת יש סיכוי להיות מוחזר כשאתה מכשף פריט" - lore2 = "ניסיון לכל כישוף" - lore = ["לניסיון שהוצאת יש סיכוי להיות מוחזר כשאתה מכשף פריט", "ניסיון לכל כישוף"] +name = "החזר XP" +description = "ניסיון כישוף מוחזר אליך כשאתה מכשף פריט." +lore1 = "לניסיון שהוצאת יש סיכוי להיות מוחזר כשאתה מכשף פריט" +lore2 = "ניסיון לכל כישוף" +lore = ["לניסיון שהוצאת יש סיכוי להיות מוחזר כשאתה מכשף פריט", "ניסיון לכל כישוף"] # excavation [excavation] [excavation.haste] - name = "חופר חפוז" - description = "זה יאיץ את תהליך החפירה, עם חיפזון!" - lore1 = "קבל חיפזון בזמן חפירה" - lore2 = "x רמות חיפזון כשאתה מתחיל לכרות כל בלוק." - lore = ["קבל חיפזון בזמן חפירה", "x רמות חיפזון כשאתה מתחיל לכרות כל בלוק."] +name = "חופר חפוז" +description = "זה יאיץ את תהליך החפירה, עם חיפזון!" +lore1 = "קבל חיפזון בזמן חפירה" +lore2 = "x רמות חיפזון כשאתה מתחיל לכרות כל בלוק." +lore = ["קבל חיפזון בזמן חפירה", "x רמות חיפזון כשאתה מתחיל לכרות כל בלוק."] [excavation.spelunker] - name = "חוקר מערות על-חושי!" - description = "ראה עפרות בעיניך, דרך האדמה!" - lore1 = "עפרה ביד השנייה, גרגרי זוהר ביד הראשית, והתכופף!" - lore2 = "טווח בלוקים: " - lore3 = "צורך גרגר זוהר בשימוש" - lore = ["עפרה ביד השנייה, גרגרי זוהר ביד הראשית, והתכופף!", "טווח בלוקים: ", "צורך גרגר זוהר בשימוש"] +name = "חוקר מערות על-חושי!" +description = "ראה עפרות בעיניך, דרך האדמה!" +lore1 = "עפרה ביד השנייה, גרגרי זוהר ביד הראשית, והתכופף!" +lore2 = "טווח בלוקים: " +lore3 = "צורך גרגר זוהר בשימוש" +lore = ["עפרה ביד השנייה, גרגרי זוהר ביד הראשית, והתכופף!", "טווח בלוקים: ", "צורך גרגר זוהר בשימוש"] [excavation.drop_to_inventory] - name = "את-ישר-למלאי" +name = "את-ישר-למלאי" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "הלדרמן המפואר המעוצב-יתר של טאקל" - lore1 = "כנראה החזק מבין רבים מאפשר לך" - lore2 = "למזג ולשנות כלים באופן דינמי, בהתאם לצרכים שלך." - lore3 = "למיזוג, לחץ shift על פריט מעל אחר במלאי שלך." - lore4 = "לשחרור כלים, התכופף-והפל את הפריט, והוא יתפרק." - lore5 = "אי אפשר לשבור כלים בלדרמן הזה אבל אי אפשר להשתמש בכלים שבורים" - lore6 = "פריטים מרביים הניתנים למיזוג." - lore7 = "אפשר להשתמש בחמישה או שישה כלים, או רק אחד!" - lore = ["כנראה החזק מבין רבים מאפשר לך", "למזג ולשנות כלים באופן דינמי, בהתאם לצרכים שלך.", "למיזוג, לחץ shift על פריט מעל אחר במלאי שלך.", "לשחרור כלים, התכופף-והפל את הפריט, והוא יתפרק.", "אי אפשר לשבור כלים בלדרמן הזה אבל אי אפשר להשתמש בכלים שבורים", "פריטים מרביים הניתנים למיזוג.", "אפשר להשתמש בחמישה או שישה כלים, או רק אחד!"] +name = "OMNI - T.O.O.L." +description = "הלדרמן המפואר המעוצב-יתר של טאקל" +lore1 = "כנראה החזק מבין רבים מאפשר לך" +lore2 = "למזג ולשנות כלים באופן דינמי, בהתאם לצרכים שלך." +lore3 = "למיזוג, לחץ shift על פריט מעל אחר במלאי שלך." +lore4 = "לשחרור כלים, התכופף-והפל את הפריט, והוא יתפרק." +lore5 = "אי אפשר לשבור כלים בלדרמן הזה אבל אי אפשר להשתמש בכלים שבורים" +lore6 = "פריטים מרביים הניתנים למיזוג." +lore7 = "אפשר להשתמש בחמישה או שישה כלים, או רק אחד!" +lore = ["כנראה החזק מבין רבים מאפשר לך", "למזג ולשנות כלים באופן דינמי, בהתאם לצרכים שלך.", "למיזוג, לחץ shift על פריט מעל אחר במלאי שלך.", "לשחרור כלים, התכופף-והפל את הפריט, והוא יתפרק.", "אי אפשר לשבור כלים בלדרמן הזה אבל אי אפשר להשתמש בכלים שבורים", "פריטים מרביים הניתנים למיזוג.", "אפשר להשתמש בחמישה או שישה כלים, או רק אחד!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "הילת צמיחה" - description = "גדל טבע סביבך בהילה" - lore1 = "רדיוס בלוקים" - lore2 = "עוצמת הילת צמיחה" - lore3 = "עלות אוכל" - lore = ["רדיוס בלוקים", "עוצמת הילת צמיחה", "עלות אוכל"] +name = "הילת צמיחה" +description = "גדל טבע סביבך בהילה" +lore1 = "רדיוס בלוקים" +lore2 = "עוצמת הילת צמיחה" +lore3 = "עלות אוכל" +lore = ["רדיוס בלוקים", "עוצמת הילת צמיחה", "עלות אוכל"] [herbalism.hippo] - name = "היפו של הצמחאי" - description = "צריכת אוכל נותנת לך יותר רוויה" - lore1 = "אוכל) נקודות רוויה נוספות בצריכה" - lore = ["אוכל) נקודות רוויה נוספות בצריכה"] +name = "היפו של הצמחאי" +description = "צריכת אוכל נותנת לך יותר רוויה" +lore1 = "אוכל) נקודות רוויה נוספות בצריכה" +lore = ["אוכל) נקודות רוויה נוספות בצריכה"] [herbalism.myconid] - name = "מיקוניד של הצמחאי" - description = "נותן לך את היכולת ליצור מיצליום" - lore1 = "כל אדמה, ופטריה חומה ואדומה ייצרו מיצליום." - lore = ["כל אדמה, ופטריה חומה ואדומה ייצרו מיצליום."] +name = "מיקוניד של הצמחאי" +description = "נותן לך את היכולת ליצור מיצליום" +lore1 = "כל אדמה, ופטריה חומה ואדומה ייצרו מיצליום." +lore = ["כל אדמה, ופטריה חומה ואדומה ייצרו מיצליום."] [herbalism.terralid] - name = "טראליד של הצמחאי" - description = "נותן לך את היכולת ליצור בלוקי דשא" - lore1 = "שלושה זרעים, מעל 3 אדמה, ייצרו 3 בלוקי דשא." - lore = ["שלושה זרעים, מעל 3 אדמה, ייצרו 3 בלוקי דשא."] +name = "טראליד של הצמחאי" +description = "נותן לך את היכולת ליצור בלוקי דשא" +lore1 = "שלושה זרעים, מעל 3 אדמה, ייצרו 3 בלוקי דשא." +lore = ["שלושה זרעים, מעל 3 אדמה, ייצרו 3 בלוקי דשא."] [herbalism.cobweb] - name = "יוצר קורים" - description = "נותן לך את היכולת ליצור קורי עכביש בשולחן יצירה" - lore1 = "תשעה חוטים ייצרו קור עכביש." - lore = ["תשעה חוטים ייצרו קור עכביש."] +name = "יוצר קורים" +description = "נותן לך את היכולת ליצור קורי עכביש בשולחן יצירה" +lore1 = "תשעה חוטים ייצרו קור עכביש." +lore = ["תשעה חוטים ייצרו קור עכביש."] [herbalism.mushroom_blocks] - name = "יוצר פטריות" - description = "נותן לך את היכולת ליצור בלוקי פטריות בשולחן יצירה" - lore1 = "ארבע פטריות ליצירת בלוק, או בלוק ליצירת גזע." - lore = ["ארבע פטריות ליצירת בלוק, או בלוק ליצירת גזע."] +name = "יוצר פטריות" +description = "נותן לך את היכולת ליצור בלוקי פטריות בשולחן יצירה" +lore1 = "ארבע פטריות ליצירת בלוק, או בלוק ליצירת גזע." +lore = ["ארבע פטריות ליצירת בלוק, או בלוק ליצירת גזע."] [herbalism.drop_to_inventory] - name = "מעדר-ישר-למלאי" +name = "מעדר-ישר-למלאי" [herbalism.hungry_shield] - name = "מגן רעב" - description = "קבל נזק לרעב שלך לפני הבריאות." - lore1 = "מופחת על ידי רעב" - lore = ["מופחת על ידי רעב"] +name = "מגן רעב" +description = "קבל נזק לרעב שלך לפני הבריאות." +lore1 = "מופחת על ידי רעב" +lore = ["מופחת על ידי רעב"] [herbalism.luck] - name = "מזל הצמחאי" - description = "כשאתה שובר דשא/פרחים, יש סיכוי לקבל פריט אקראי" - lore0 = "פרחים = אוכל, ודשא = זרעים" - lore1 = "סיכוי לקבל פריט משבירת פרחים" - lore2 = "סיכוי לקבל פריט משבירת דשא" - lore = ["פרחים = אוכל, ודשא = זרעים", "סיכוי לקבל פריט משבירת פרחים", "סיכוי לקבל פריט משבירת דשא"] +name = "מזל הצמחאי" +description = "כשאתה שובר דשא/פרחים, יש סיכוי לקבל פריט אקראי" +lore0 = "פרחים = אוכל, ודשא = זרעים" +lore1 = "סיכוי לקבל פריט משבירת פרחים" +lore2 = "סיכוי לקבל פריט משבירת דשא" +lore = ["פרחים = אוכל, ודשא = זרעים", "סיכוי לקבל פריט משבירת פרחים", "סיכוי לקבל פריט משבירת דשא"] [herbalism.replant] - name = "קציר ושתילה מחדש" - description = "לחץ ימני על יבול עם מעדר כדי לקצור ולשתול מחדש." - lore1 = "רדיוס שתילה מחדש של בלוקים" - lore = ["רדיוס שתילה מחדש של בלוקים"] +name = "קציר ושתילה מחדש" +description = "לחץ ימני על יבול עם מעדר כדי לקצור ולשתול מחדש." +lore1 = "רדיוס שתילה מחדש של בלוקים" +lore = ["רדיוס שתילה מחדש של בלוקים"] # hunter [hunter] [hunter.adrenaline] - name = "אדרנלין" - description = "גרום יותר נזק ככל שהבריאות שלך נמוכה יותר (קרב קרוב)" - lore1 = "נזק מרבי" - lore = ["נזק מרבי"] +name = "אדרנלין" +description = "גרום יותר נזק ככל שהבריאות שלך נמוכה יותר (קרב קרוב)" +lore1 = "נזק מרבי" +lore = ["נזק מרבי"] [hunter.penalty] - name = "" - description = "" - lore1 = "תקבל ערימות רעל אם נגמר לך הרעב" - lore = ["תקבל ערימות רעל אם נגמר לך הרעב"] +name = "" +description = "" +lore1 = "תקבל ערימות רעל אם נגמר לך הרעב" +lore = ["תקבל ערימות רעל אם נגמר לך הרעב"] [hunter.drop_to_inventory] - name = "פריטים-ישר-למלאי" - description = "כשאתה הורג משהו / שובר בלוק עם חרב, הדרופים משתגרים למלאי שלך" - lore1 = "בכל פעם שנופל פריט ממפלצת/בלוק שאתה שובר, הוא נכנס למלאי שלך אם אפשר." - lore = ["בכל פעם שנופל פריט ממפלצת/בלוק שאתה שובר, הוא נכנס למלאי שלך אם אפשר."] +name = "פריטים-ישר-למלאי" +description = "כשאתה הורג משהו / שובר בלוק עם חרב, הדרופים משתגרים למלאי שלך" +lore1 = "בכל פעם שנופל פריט ממפלצת/בלוק שאתה שובר, הוא נכנס למלאי שלך אם אפשר." +lore = ["בכל פעם שנופל פריט ממפלצת/בלוק שאתה שובר, הוא נכנס למלאי שלך אם אפשר."] [hunter.invisibility] - name = "צעד נעלם" - description = "כשנפגע אתה מקבל אי-נראות, על חשבון רעב" - lore1 = "קבל אי-נראות פסיבית כשנפגע" - lore2 = "x ערימות אי-נראות למשך 3 שניות בפגיעה" - lore3 = "x רעב מצטבר" - lore4 = "משך ומכפיל ערימות רעב." - lore5 = "משך אי-נראות" - lore = ["קבל אי-נראות פסיבית כשנפגע", "x ערימות אי-נראות למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "משך אי-נראות"] +name = "צעד נעלם" +description = "כשנפגע אתה מקבל אי-נראות, על חשבון רעב" +lore1 = "קבל אי-נראות פסיבית כשנפגע" +lore2 = "x ערימות אי-נראות למשך 3 שניות בפגיעה" +lore3 = "x רעב מצטבר" +lore4 = "משך ומכפיל ערימות רעב." +lore5 = "משך אי-נראות" +lore = ["קבל אי-נראות פסיבית כשנפגע", "x ערימות אי-נראות למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "משך אי-נראות"] [hunter.jump_boost] - name = "גבהים של הצייד" - description = "כשנפגע אתה מקבל חיזוק קפיצה, על חשבון רעב" - lore1 = "קבל חיזוק קפיצה פסיבי כשנפגע" - lore2 = "x ערימות חיזוק קפיצה למשך 3 שניות בפגיעה" - lore3 = "x רעב מצטבר" - lore4 = "משך ומכפיל ערימות רעב." - lore5 = "מכפיל ערימות חיזוק קפיצה, לא משך." - lore = ["קבל חיזוק קפיצה פסיבי כשנפגע", "x ערימות חיזוק קפיצה למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות חיזוק קפיצה, לא משך."] +name = "גבהים של הצייד" +description = "כשנפגע אתה מקבל חיזוק קפיצה, על חשבון רעב" +lore1 = "קבל חיזוק קפיצה פסיבי כשנפגע" +lore2 = "x ערימות חיזוק קפיצה למשך 3 שניות בפגיעה" +lore3 = "x רעב מצטבר" +lore4 = "משך ומכפיל ערימות רעב." +lore5 = "מכפיל ערימות חיזוק קפיצה, לא משך." +lore = ["קבל חיזוק קפיצה פסיבי כשנפגע", "x ערימות חיזוק קפיצה למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות חיזוק קפיצה, לא משך."] [hunter.luck] - name = "מזל הצייד" - description = "כשנפגע אתה מקבל מזל, על חשבון רעב" - lore1 = "קבל מזל פסיבי כשנפגע" - lore2 = "x ערימות מזל למשך 3 שניות בפגיעה" - lore3 = "x רעב מצטבר" - lore4 = "משך ומכפיל ערימות רעב." - lore5 = "מכפיל ערימות מזל, לא משך." - lore = ["קבל מזל פסיבי כשנפגע", "x ערימות מזל למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות מזל, לא משך."] +name = "מזל הצייד" +description = "כשנפגע אתה מקבל מזל, על חשבון רעב" +lore1 = "קבל מזל פסיבי כשנפגע" +lore2 = "x ערימות מזל למשך 3 שניות בפגיעה" +lore3 = "x רעב מצטבר" +lore4 = "משך ומכפיל ערימות רעב." +lore5 = "מכפיל ערימות מזל, לא משך." +lore = ["קבל מזל פסיבי כשנפגע", "x ערימות מזל למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות מזל, לא משך."] [hunter.regen] - name = "התחדשות הצייד" - description = "כשנפגע אתה מקבל התחדשות, על חשבון רעב" - lore1 = "קבל התחדשות פסיבית כשנפגע" - lore2 = "x ערימות התחדשות למשך 3 שניות בפגיעה" - lore3 = "x רעב מצטבר" - lore4 = "משך ומכפיל ערימות רעב." - lore5 = "מכפיל ערימות התחדשות, לא משך." - lore = ["קבל התחדשות פסיבית כשנפגע", "x ערימות התחדשות למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות התחדשות, לא משך."] +name = "התחדשות הצייד" +description = "כשנפגע אתה מקבל התחדשות, על חשבון רעב" +lore1 = "קבל התחדשות פסיבית כשנפגע" +lore2 = "x ערימות התחדשות למשך 3 שניות בפגיעה" +lore3 = "x רעב מצטבר" +lore4 = "משך ומכפיל ערימות רעב." +lore5 = "מכפיל ערימות התחדשות, לא משך." +lore = ["קבל התחדשות פסיבית כשנפגע", "x ערימות התחדשות למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות התחדשות, לא משך."] [hunter.resistance] - name = "התנגדות הצייד" - description = "כשנפגע אתה מקבל התנגדות, על חשבון רעב" - lore1 = "קבל התנגדות פסיבית כשנפגע" - lore2 = "x ערימות התנגדות למשך 3 שניות בפגיעה" - lore3 = "x רעב מצטבר" - lore4 = "משך ומכפיל ערימות רעב." - lore5 = "מכפיל ערימות התנגדות, לא משך." - lore = ["קבל התנגדות פסיבית כשנפגע", "x ערימות התנגדות למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות התנגדות, לא משך."] +name = "התנגדות הצייד" +description = "כשנפגע אתה מקבל התנגדות, על חשבון רעב" +lore1 = "קבל התנגדות פסיבית כשנפגע" +lore2 = "x ערימות התנגדות למשך 3 שניות בפגיעה" +lore3 = "x רעב מצטבר" +lore4 = "משך ומכפיל ערימות רעב." +lore5 = "מכפיל ערימות התנגדות, לא משך." +lore = ["קבל התנגדות פסיבית כשנפגע", "x ערימות התנגדות למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות התנגדות, לא משך."] [hunter.speed] - name = "מהירות הצייד" - description = "כשנפגע אתה מקבל מהירות, על חשבון רעב" - lore1 = "קבל מהירות פסיבית כשנפגע" - lore2 = "x ערימות מהירות למשך 3 שניות בפגיעה" - lore3 = "x רעב מצטבר" - lore4 = "משך ומכפיל ערימות רעב." - lore5 = "מכפיל ערימות מהירות, לא משך." - lore = ["קבל מהירות פסיבית כשנפגע", "x ערימות מהירות למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות מהירות, לא משך."] +name = "מהירות הצייד" +description = "כשנפגע אתה מקבל מהירות, על חשבון רעב" +lore1 = "קבל מהירות פסיבית כשנפגע" +lore2 = "x ערימות מהירות למשך 3 שניות בפגיעה" +lore3 = "x רעב מצטבר" +lore4 = "משך ומכפיל ערימות רעב." +lore5 = "מכפיל ערימות מהירות, לא משך." +lore = ["קבל מהירות פסיבית כשנפגע", "x ערימות מהירות למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות מהירות, לא משך."] [hunter.strength] - name = "עוצמת הצייד" - description = "כשנפגע אתה מקבל עוצמה, על חשבון רעב" - lore1 = "קבל עוצמה פסיבית כשנפגע" - lore2 = "x ערימות עוצמה למשך 3 שניות בפגיעה" - lore3 = "x רעב מצטבר" - lore4 = "משך ומכפיל ערימות רעב." - lore5 = "מכפיל ערימות עוצמה, לא משך." - lore = ["קבל עוצמה פסיבית כשנפגע", "x ערימות עוצמה למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות עוצמה, לא משך."] +name = "עוצמת הצייד" +description = "כשנפגע אתה מקבל עוצמה, על חשבון רעב" +lore1 = "קבל עוצמה פסיבית כשנפגע" +lore2 = "x ערימות עוצמה למשך 3 שניות בפגיעה" +lore3 = "x רעב מצטבר" +lore4 = "משך ומכפיל ערימות רעב." +lore5 = "מכפיל ערימות עוצמה, לא משך." +lore = ["קבל עוצמה פסיבית כשנפגע", "x ערימות עוצמה למשך 3 שניות בפגיעה", "x רעב מצטבר", "משך ומכפיל ערימות רעב.", "מכפיל ערימות עוצמה, לא משך."] # nether [nether] [nether.skull_toss] - name = "זריקת גולגולת וויתר" - description1 = "שחרר את הוויתר הפנימי שלך על ידי שימוש" - description2 = "במישהו" - description3 = "ראש." - lore1 = "שניות קירור בין זריקות גולגולת." - lore2 = "שימוש בגולגולת וויתר: זרוק " - lore3 = "גולגולת וויתר" - lore4 = "שמתפוצצת בפגיעה." - lore = ["שניות קירור בין זריקות גולגולת.", "שימוש בגולגולת וויתר: זרוק ", "גולגולת וויתר", "שמתפוצצת בפגיעה."] +name = "זריקת גולגולת וויתר" +description1 = "שחרר את הוויתר הפנימי שלך על ידי שימוש" +description2 = "במישהו" +description3 = "ראש." +lore1 = "שניות קירור בין זריקות גולגולת." +lore2 = "שימוש בגולגולת וויתר: זרוק " +lore3 = "גולגולת וויתר" +lore4 = "שמתפוצצת בפגיעה." +lore = ["שניות קירור בין זריקות גולגולת.", "שימוש בגולגולת וויתר: זרוק ", "גולגולת וויתר", "שמתפוצצת בפגיעה."] [nether.wither_resist] - name = "התנגדות לכמילה" - description = "מתנגד לכמילה דרך כוחו של נתרייט." - lore1 = "סיכוי לבטל כמילה (לכל חלק)." - lore2 = "פסיבי: לבישת שריון נתרייט מעניקה סיכוי לבטל " - lore3 = "כמילה." - lore = ["סיכוי לבטל כמילה (לכל חלק).", "פסיבי: לבישת שריון נתרייט מעניקה סיכוי לבטל ", "כמילה."] +name = "התנגדות לכמילה" +description = "מתנגד לכמילה דרך כוחו של נתרייט." +lore1 = "סיכוי לבטל כמילה (לכל חלק)." +lore2 = "פסיבי: לבישת שריון נתרייט מעניקה סיכוי לבטל " +lore3 = "כמילה." +lore = ["סיכוי לבטל כמילה (לכל חלק).", "פסיבי: לבישת שריון נתרייט מעניקה סיכוי לבטל ", "כמילה."] [nether.fire_resist] - name = "התנגדות לאש" - description = "מתנגד לאש על ידי הקשחת העור." - lore1 = "סיכוי לבטל את אפקט הבעירה!" - lore = ["סיכוי לבטל את אפקט הבעירה!"] +name = "התנגדות לאש" +description = "מתנגד לאש על ידי הקשחת העור." +lore1 = "סיכוי לבטל את אפקט הבעירה!" +lore = ["סיכוי לבטל את אפקט הבעירה!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "התכה אוטומטית" - description = "מאפשר לך להתיך עפרות וניל שנכרו" - lore1 = "עפרות הניתנות להתכה מותכות אוטומטית" - lore2 = "% סיכוי לתוספת" - lore = ["עפרות הניתנות להתכה מותכות אוטומטית", "% סיכוי לתוספת"] +name = "התכה אוטומטית" +description = "מאפשר לך להתיך עפרות וניל שנכרו" +lore1 = "עפרות הניתנות להתכה מותכות אוטומטית" +lore2 = "% סיכוי לתוספת" +lore = ["עפרות הניתנות להתכה מותכות אוטומטית", "% סיכוי לתוספת"] [pickaxe.chisel] - name = "אזמל עפרות" - description = "לחץ ימני על עפרות כדי לאזמל עוד עפרה מהן, בעלות עמידות חמורה." - lore1 = "סיכוי להפלה" - lore2 = "בלאי כלי" - lore = ["סיכוי להפלה", "בלאי כלי"] +name = "אזמל עפרות" +description = "לחץ ימני על עפרות כדי לאזמל עוד עפרה מהן, בעלות עמידות חמורה." +lore1 = "סיכוי להפלה" +lore2 = "בלאי כלי" +lore = ["סיכוי להפלה", "בלאי כלי"] [pickaxe.drop_to_inventory] - name = "מכוש-ישר-למלאי" - description = "כשאתה שובר בלוק, הפריט משתגר למלאי שלך" - lore1 = "בכל פעם שנופל פריט מבלוק שאתה שובר, הוא נכנס למלאי שלך אם אפשר." - lore = ["בכל פעם שנופל פריט מבלוק שאתה שובר, הוא נכנס למלאי שלך אם אפשר."] +name = "מכוש-ישר-למלאי" +description = "כשאתה שובר בלוק, הפריט משתגר למלאי שלך" +lore1 = "בכל פעם שנופל פריט מבלוק שאתה שובר, הוא נכנס למלאי שלך אם אפשר." +lore = ["בכל פעם שנופל פריט מבלוק שאתה שובר, הוא נכנס למלאי שלך אם אפשר."] [pickaxe.silk_spawner] - name = "מכוש משי-יוצר-מפלצות" - description = "גורם ליוצרי מפלצות ליפול כשנשברים" - lore1 = "הופך יוצרי מפלצות לשבירים עם מגע-משי." - lore2 = "הופך יוצרי מפלצות לשבירים תוך כדי התכופפות." - lore = ["הופך יוצרי מפלצות לשבירים עם מגע-משי.", "הופך יוצרי מפלצות לשבירים תוך כדי התכופפות."] +name = "מכוש משי-יוצר-מפלצות" +description = "גורם ליוצרי מפלצות ליפול כשנשברים" +lore1 = "הופך יוצרי מפלצות לשבירים עם מגע-משי." +lore2 = "הופך יוצרי מפלצות לשבירים תוך כדי התכופפות." +lore = ["הופך יוצרי מפלצות לשבירים עם מגע-משי.", "הופך יוצרי מפלצות לשבירים תוך כדי התכופפות."] [pickaxe.vein_miner] - name = "כורה ורידים" - description = "מאפשר לך לשבור בלוקים בוריד/אשכול של עפרות וניל" - lore1 = "התכופף, וכרה עפרות" - lore2 = "טווח כריית ורידים" - lore3 = "מיומנות זו לא מקבצת את כל הדרופים יחד!" - lore = ["התכופף, וכרה עפרות", "טווח כריית ורידים", "מיומנות זו לא מקבצת את כל הדרופים יחד!"] +name = "כורה ורידים" +description = "מאפשר לך לשבור בלוקים בוריד/אשכול של עפרות וניל" +lore1 = "התכופף, וכרה עפרות" +lore2 = "טווח כריית ורידים" +lore3 = "מיומנות זו לא מקבצת את כל הדרופים יחד!" +lore = ["התכופף, וכרה עפרות", "טווח כריית ורידים", "מיומנות זו לא מקבצת את כל הדרופים יחד!"] # ranged [ranged] [ranged.arrow_recovery] - name = "שחזור חצים" - description = "שחזר חצים אחרי שהרגת אויב." - lore1 = "סיכוי לשחזר חצים בפגיעה/הריגה" - lore2 = "סיכוי: " - lore = ["סיכוי לשחזר חצים בפגיעה/הריגה", "סיכוי: "] +name = "שחזור חצים" +description = "שחזר חצים אחרי שהרגת אויב." +lore1 = "סיכוי לשחזר חצים בפגיעה/הריגה" +lore2 = "סיכוי: " +lore = ["סיכוי לשחזר חצים בפגיעה/הריגה", "סיכוי: "] [ranged.web_shot] - name = "מלכודת קורים" - description = "הקף את המטרה שלך בקורי עכביש כשאתה פוגע בה!" - lore1 = "8 קורי עכביש סביב כדור שלג, וזרוק!" - lore2 = "שניות של כלוב, בערך." - lore = ["8 קורי עכביש סביב כדור שלג, וזרוק!", "שניות של כלוב, בערך."] +name = "מלכודת קורים" +description = "הקף את המטרה שלך בקורי עכביש כשאתה פוגע בה!" +lore1 = "8 קורי עכביש סביב כדור שלג, וזרוק!" +lore2 = "שניות של כלוב, בערך." +lore = ["8 קורי עכביש סביב כדור שלג, וזרוק!", "שניות של כלוב, בערך."] [ranged.force_shot] - name = "יריית כוח" - description = "ירה קליעים רחוק ומהר יותר!" - advancementname = "יריה ארוכה" - advancementlore = "נחת יריה ממרחק של מעל 30 בלוקים!" - lore1 = "מהירות קליע" - lore = ["מהירות קליע"] +name = "יריית כוח" +description = "ירה קליעים רחוק ומהר יותר!" +advancementname = "יריה ארוכה" +advancementlore = "נחת יריה ממרחק של מעל 30 בלוקים!" +lore1 = "מהירות קליע" +lore = ["מהירות קליע"] [ranged.lunge_shot] - name = "יריית זינוק" - description = "בזמן נפילה החצים שלך זורקים אותך לכיוון אקראי" - lore1 = "מהירות פרץ אקראית" - lore = ["מהירות פרץ אקראית"] +name = "יריית זינוק" +description = "בזמן נפילה החצים שלך זורקים אותך לכיוון אקראי" +lore1 = "מהירות פרץ אקראית" +lore = ["מהירות פרץ אקראית"] [ranged.arrow_piercing] - name = "חץ חודר" - description = "מוסיף חדירה לקליעים! ירה דרך דברים!" - lore1 = "מטרות חדירה" - lore = ["מטרות חדירה"] +name = "חץ חודר" +description = "מוסיף חדירה לקליעים! ירה דרך דברים!" +lore1 = "מטרות חדירה" +lore = ["מטרות חדירה"] # rift [rift] [rift.remote_access] - name = "גישה מרחוק" - description = "שלוף מהריק, והיכנס למכולה מסומנת." - lore1 = "פנינת אנדר + מצפן = מפתח שער רליקוויה" - lore2 = "פריט זה מאפשר לך לגשת למכולות מרחוק" - lore3 = "אחרי היצירה הסתכל על הפריט לראות שימוש" - notcontainer = "זה לא מכולה" - lore = ["פנינת אנדר + מצפן = מפתח שער רליקוויה", "פריט זה מאפשר לך לגשת למכולות מרחוק", "אחרי היצירה הסתכל על הפריט לראות שימוש"] +name = "גישה מרחוק" +description = "שלוף מהריק, והיכנס למכולה מסומנת." +lore1 = "פנינת אנדר + מצפן = מפתח שער רליקוויה" +lore2 = "פריט זה מאפשר לך לגשת למכולות מרחוק" +lore3 = "אחרי היצירה הסתכל על הפריט לראות שימוש" +notcontainer = "זה לא מכולה" +lore = ["פנינת אנדר + מצפן = מפתח שער רליקוויה", "פריט זה מאפשר לך לגשת למכולות מרחוק", "אחרי היצירה הסתכל על הפריט לראות שימוש"] [rift.blink] - name = "הבזק קרע" - description = "שיגור מיידי לטווח קצר, רק הבזק!" - lore1 = "בלוקים בהבזק (2x אנכי)" - lore2 = "בזמן ריצה: הקש כפול על קפיצה כדי " - lore3 = "להבזק" - lore = ["בלוקים בהבזק (2x אנכי)", "בזמן ריצה: הקש כפול על קפיצה כדי ", "להבזק"] +name = "הבזק קרע" +description = "שיגור מיידי לטווח קצר, רק הבזק!" +lore1 = "בלוקים בהבזק (2x אנכי)" +lore2 = "בזמן ריצה: הקש כפול על קפיצה כדי " +lore3 = "להבזק" +lore = ["בלוקים בהבזק (2x אנכי)", "בזמן ריצה: הקש כפול על קפיצה כדי ", "להבזק"] [rift.chest] - name = "ארגז אנדר קל" - description = "פתח ארגז אנדר על ידי לחיצה שמאלית עליו ביד." - lore1 = "לחץ על ארגז אנדר ביד כדי לפתוח (פשוט אל תניח אותו)" - lore = ["לחץ על ארגז אנדר ביד כדי לפתוח (פשוט אל תניח אותו)"] +name = "ארגז אנדר קל" +description = "פתח ארגז אנדר על ידי לחיצה שמאלית עליו ביד." +lore1 = "לחץ על ארגז אנדר ביד כדי לפתוח (פשוט אל תניח אותו)" +lore = ["לחץ על ארגז אנדר ביד כדי לפתוח (פשוט אל תניח אותו)"] [rift.descent] - name = "נגד-ריחוף" - description = "נמאס לך להיתקע באוויר? זו המיומנות בשבילך!" - lore1 = "פשוט התכופף כדי לרדת, ותיפול בקצב איטי מהרגיל!" - lore2 = "זמן קירור:" - lore = ["פשוט התכופף כדי לרדת, ותיפול בקצב איטי מהרגיל!", "זמן קירור:"] +name = "נגד-ריחוף" +description = "נמאס לך להיתקע באוויר? זו המיומנות בשבילך!" +lore1 = "פשוט התכופף כדי לרדת, ותיפול בקצב איטי מהרגיל!" +lore2 = "זמן קירור:" +lore = ["פשוט התכופף כדי לרדת, ותיפול בקצב איטי מהרגיל!", "זמן קירור:"] [rift.gate] - name = "שער קרע" - description = "השתגר למיקום מסומן." - lore1 = "יצירה: אזמרגד + רסיס אמטיסט + פנינת אנדר" - lore2 = "קרא לפני השימוש!" - lore3 = "השהיה של 5 שניות, " - lore4 = "אתה יכול למות בזמן האנימציה הזו" - lore = ["יצירה: אזמרגד + רסיס אמטיסט + פנינת אנדר", "קרא לפני השימוש!", "השהיה של 5 שניות, ", "אתה יכול למות בזמן האנימציה הזו"] +name = "שער קרע" +description = "השתגר למיקום מסומן." +lore1 = "יצירה: אזמרגד + רסיס אמטיסט + פנינת אנדר" +lore2 = "קרא לפני השימוש!" +lore3 = "השהיה של 5 שניות, " +lore4 = "אתה יכול למות בזמן האנימציה הזו" +lore = ["יצירה: אזמרגד + רסיס אמטיסט + פנינת אנדר", "קרא לפני השימוש!", "השהיה של 5 שניות, ", "אתה יכול למות בזמן האנימציה הזו"] [rift.resist] - name = "התנגדות קרע" - description = "קבל התנגדות בעת שימוש בפריטי ויכולות אנדר" - lore1 = "+ פסיבי: מעניק התנגדות כשאתה משתמש ביכולות קרע, או פריטי אנדר" - lore2 = "לא כולל ארגז אנדר נייד, רק דברים שאתה יכול לצרוך" - lore = ["+ פסיבי: מעניק התנגדות כשאתה משתמש ביכולות קרע, או פריטי אנדר", "לא כולל ארגז אנדר נייד, רק דברים שאתה יכול לצרוך"] +name = "התנגדות קרע" +description = "קבל התנגדות בעת שימוש בפריטי ויכולות אנדר" +lore1 = "+ פסיבי: מעניק התנגדות כשאתה משתמש ביכולות קרע, או פריטי אנדר" +lore2 = "לא כולל ארגז אנדר נייד, רק דברים שאתה יכול לצרוך" +lore = ["+ פסיבי: מעניק התנגדות כשאתה משתמש ביכולות קרע, או פריטי אנדר", "לא כולל ארגז אנדר נייד, רק דברים שאתה יכול לצרוך"] [rift.visage] - name = "מסווה קרע" - description = "מונע מאנדרמנים להפוך לעוינים אם יש לך פניני אנדר במלאי." - lore1 = "אנדרמנים לא יהפכו לעוינים אם יש לך פניני אנדר במלאי." - lore = ["אנדרמנים לא יהפכו לעוינים אם יש לך פניני אנדר במלאי."] +name = "מסווה קרע" +description = "מונע מאנדרמנים להפוך לעוינים אם יש לך פניני אנדר במלאי." +lore1 = "אנדרמנים לא יהפכו לעוינים אם יש לך פניני אנדר במלאי." +lore = ["אנדרמנים לא יהפכו לעוינים אם יש לך פניני אנדר במלאי."] # seaborn [seaborn] [seaborn.oxygen] - name = "מיכל חמצן אורגני" - description = "החזק יותר חמצן בריאות הזעירות שלך!" - lore1 = "הגדלת קיבולת חמצן" - lore = ["הגדלת קיבולת חמצן"] +name = "מיכל חמצן אורגני" +description = "החזק יותר חמצן בריאות הזעירות שלך!" +lore1 = "הגדלת קיבולת חמצן" +lore = ["הגדלת קיבולת חמצן"] [seaborn.fishers_fantasy] - name = "פנטזיית הדייג" - description = "הרווח יותר XP מדיג, וקבל יותר דגים!" - lore1 = "לכל רמה יש סיכוי לקבל יותר XP ודגים!" - lore = ["לכל רמה יש סיכוי לקבל יותר XP ודגים!"] +name = "פנטזיית הדייג" +description = "הרווח יותר XP מדיג, וקבל יותר דגים!" +lore1 = "לכל רמה יש סיכוי לקבל יותר XP ודגים!" +lore = ["לכל רמה יש סיכוי לקבל יותר XP ודגים!"] [seaborn.haste] - name = "כורה צבים" - description = "בזמן כרייה מתחת למים אתה מקבל חיפזון!" - lore1 = "חיפזון 3 מופעל מתחת למים בזמן כרייה (מצטבר עם זיקת מים) אחרי שאפקט נשימת המים שלך נגמר!" - lore = ["חיפזון 3 מופעל מתחת למים בזמן כרייה (מצטבר עם זיקת מים) אחרי שאפקט נשימת המים שלך נגמר!"] +name = "כורה צבים" +description = "בזמן כרייה מתחת למים אתה מקבל חיפזון!" +lore1 = "חיפזון 3 מופעל מתחת למים בזמן כרייה (מצטבר עם זיקת מים) אחרי שאפקט נשימת המים שלך נגמר!" +lore = ["חיפזון 3 מופעל מתחת למים בזמן כרייה (מצטבר עם זיקת מים) אחרי שאפקט נשימת המים שלך נגמר!"] [seaborn.night_vision] - name = "חזון הצב" - description = "בזמן שאתה מתחת למים, אתה מקבל ראיית לילה" - lore1 = "פשוט קבל ראיית לילה מתחת למים אחרי שאפקט נשימת המים שלך נגמר!" - lore = ["פשוט קבל ראיית לילה מתחת למים אחרי שאפקט נשימת המים שלך נגמר!"] +name = "חזון הצב" +description = "בזמן שאתה מתחת למים, אתה מקבל ראיית לילה" +lore1 = "פשוט קבל ראיית לילה מתחת למים אחרי שאפקט נשימת המים שלך נגמר!" +lore = ["פשוט קבל ראיית לילה מתחת למים אחרי שאפקט נשימת המים שלך נגמר!"] [seaborn.dolphin_grace] - name = "חסד הדולפין" - description = "שחה כמו דולפין, בלי הדולפינים" - lore1 = "+ פסיבי: קבל " - lore2 = "x מהירות (חסד דולפין)" - lore3 = "הנדסה גרמנית מדויק- רגע זה לא נכון... לא תואם עם צעד עמוק" - lore = ["+ פסיבי: קבל ", "x מהירות (חסד דולפין)", "הנדסה גרמנית מדויק- רגע זה לא נכון... לא תואם עם צעד עמוק"] +name = "חסד הדולפין" +description = "שחה כמו דולפין, בלי הדולפינים" +lore1 = "+ פסיבי: קבל " +lore2 = "x מהירות (חסד דולפין)" +lore3 = "הנדסה גרמנית מדויק- רגע זה לא נכון... לא תואם עם צעד עמוק" +lore = ["+ פסיבי: קבל ", "x מהירות (חסד דולפין)", "הנדסה גרמנית מדויק- רגע זה לא נכון... לא תואם עם צעד עמוק"] # stealth [stealth] [stealth.ghost_armor] - name = "שריון הרוח" - description = "שריון שנבנה לאט כשלא נפגע, מחזיק מכה אחת" - lore1 = "שריון מרבי" - lore2 = "מהירות" - lore = ["שריון מרבי", "מהירות"] +name = "שריון הרוח" +description = "שריון שנבנה לאט כשלא נפגע, מחזיק מכה אחת" +lore1 = "שריון מרבי" +lore2 = "מהירות" +lore = ["שריון מרבי", "מהירות"] [stealth.night_vision] - name = "ראיית התגנבות" - description = "קבל ראיית לילה תוך כדי התגנבות" - lore1 = "קבל פרץ של " - lore2 = "ראיית לילה" - lore3 = "תוך כדי התגנבות" - lore = ["קבל פרץ של ", "ראיית לילה", "תוך כדי התגנבות"] +name = "ראיית התגנבות" +description = "קבל ראיית לילה תוך כדי התגנבות" +lore1 = "קבל פרץ של " +lore2 = "ראיית לילה" +lore3 = "תוך כדי התגנבות" +lore = ["קבל פרץ של ", "ראיית לילה", "תוך כדי התגנבות"] [stealth.snatch] - name = "חטיפת פריטים" - description = "חטוף פריטים שנפלו מיד תוך כדי התגנבות!" - lore1 = "רדיוס חטיפה" - lore = ["רדיוס חטיפה"] +name = "חטיפת פריטים" +description = "חטוף פריטים שנפלו מיד תוך כדי התגנבות!" +lore1 = "רדיוס חטיפה" +lore = ["רדיוס חטיפה"] [stealth.speed] - name = "מהירות התגנבות" - description = "קבל מהירות תוך כדי התגנבות" - lore1 = "מהירות התגנבות" - lore = ["מהירות התגנבות"] +name = "מהירות התגנבות" +description = "קבל מהירות תוך כדי התגנבות" +lore1 = "מהירות התגנבות" +lore = ["מהירות התגנבות"] [stealth.ender_veil] - name = "צעיף אנדר" - description = "לא עוד דלעות כדי למנוע התקפות אנדרמנים" - lore1 = "מנע התקפות אנדרמנים תוך כדי התגנבות" - lore2 = "מנע את כל התקפות האנדרמנים" - lore = ["מנע התקפות אנדרמנים תוך כדי התגנבות", "מנע את כל התקפות האנדרמנים"] +name = "צעיף אנדר" +description = "לא עוד דלעות כדי למנוע התקפות אנדרמנים" +lore1 = "מנע התקפות אנדרמנים תוך כדי התגנבות" +lore2 = "מנע את כל התקפות האנדרמנים" +lore = ["מנע התקפות אנדרמנים תוך כדי התגנבות", "מנע את כל התקפות האנדרמנים"] # sword [sword] [sword.machete] - name = "מצ'טה" - description = "חתוך דרך שיחים בקלות!" - lore1 = "רדיוס חיתוך" - lore2 = "זמן קירור חיתוך" - lore3 = "בלאי כלי" - lore = ["רדיוס חיתוך", "זמן קירור חיתוך", "בלאי כלי"] +name = "מצ'טה" +description = "חתוך דרך שיחים בקלות!" +lore1 = "רדיוס חיתוך" +lore2 = "זמן קירור חיתוך" +lore3 = "בלאי כלי" +lore = ["רדיוס חיתוך", "זמן קירור חיתוך", "בלאי כלי"] [sword.bloody_blade] - name = "להב מדמם" - description = "מכות עם החרב שלך גורמות לדימום!" - lore1 = "פגיעה בישות חיה עם החרב שלך גורמת לדימום" - lore2 = "משך דימום" - lore3 = "זמן קירור דימום" - lore = ["פגיעה בישות חיה עם החרב שלך גורמת לדימום", "משך דימום", "זמן קירור דימום"] +name = "להב מדמם" +description = "מכות עם החרב שלך גורמות לדימום!" +lore1 = "פגיעה בישות חיה עם החרב שלך גורמת לדימום" +lore2 = "משך דימום" +lore3 = "זמן קירור דימום" +lore = ["פגיעה בישות חיה עם החרב שלך גורמת לדימום", "משך דימום", "זמן קירור דימום"] [sword.poisoned_blade] - name = "להב מורעל" - description = "מכות עם החרב שלך גורמות להרעלה!" - lore1 = "פגיעה בישות חיה עם החרב שלך גורמת להרעלה" - lore2 = "משך הרעלה" - lore3 = "זמן קירור הרעלה" - lore = ["פגיעה בישות חיה עם החרב שלך גורמת להרעלה", "משך הרעלה", "זמן קירור הרעלה"] +name = "להב מורעל" +description = "מכות עם החרב שלך גורמות להרעלה!" +lore1 = "פגיעה בישות חיה עם החרב שלך גורמת להרעלה" +lore2 = "משך הרעלה" +lore3 = "זמן קירור הרעלה" +lore = ["פגיעה בישות חיה עם החרב שלך גורמת להרעלה", "משך הרעלה", "זמן קירור הרעלה"] # taming [taming] [taming.damage] - name = "נזק מאולף" - description = "הגדל את הנזק שחיות מאולפות שלך גורמות." - lore1 = "נזק מוגבר" - lore = ["נזק מוגבר"] +name = "נזק מאולף" +description = "הגדל את הנזק שחיות מאולפות שלך גורמות." +lore1 = "נזק מוגבר" +lore = ["נזק מוגבר"] [taming.health] - name = "בריאות מאולף" - description = "הגדל את הבריאות של חיות מאולפות שלך." - lore1 = "בריאות מוגברת" - lore = ["בריאות מוגברת"] +name = "בריאות מאולף" +description = "הגדל את הבריאות של חיות מאולפות שלך." +lore1 = "בריאות מוגברת" +lore = ["בריאות מוגברת"] [taming.regeneration] - name = "התחדשות מאולף" - description = "הגדל את ההתחדשות של חיות מאולפות שלך." - lore1 = "HP/s" - lore = ["HP/s"] +name = "התחדשות מאולף" +description = "הגדל את ההתחדשות של חיות מאולפות שלך." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "קוצים" - description = "החזר נזק לתוקף שלך!" - lore1 = "נזק מוחזר כשנפגע" - lore = ["נזק מוחזר כשנפגע"] +name = "קוצים" +description = "החזר נזק לתוקף שלך!" +lore1 = "נזק מוחזר כשנפגע" +lore = ["נזק מוחזר כשנפגע"] [tragoul.globe] - name = "כדור הכאב" - description = "חלק את הנזק שאתה גורם בהתאם למספר האויבים סביבך!" - lore1 = "ככל שיותר אויבים סביבך, כך פחות נזק אתה גורם לכל אחד" - lore2 = "טווח: " - lore3 = "נזק נוסף לכל הישויות: " - lore = ["ככל שיותר אויבים סביבך, כך פחות נזק אתה גורם לכל אחד", "טווח: ", "נזק נוסף לכל הישויות: "] +name = "כדור הכאב" +description = "חלק את הנזק שאתה גורם בהתאם למספר האויבים סביבך!" +lore1 = "ככל שיותר אויבים סביבך, כך פחות נזק אתה גורם לכל אחד" +lore2 = "טווח: " +lore3 = "נזק נוסף לכל הישויות: " +lore = ["ככל שיותר אויבים סביבך, כך פחות נזק אתה גורם לכל אחד", "טווח: ", "נזק נוסף לכל הישויות: "] [tragoul.healing] - name = "רצון הכאב" - description = "שחזר בריאות בהתאם לנזק שאתה גורם!" - lore1 = "לפגוע בדברים מעולם לא הרגיש כל כך טוב! התרפא מנזק שגרמת" - lore2 = "יש חלון נזק של 3 שניות, לריפוי וזמן קירור של שנייה " - lore3 = "ריפוי לכל אחוז נזק: " - lore = ["לפגוע בדברים מעולם לא הרגיש כל כך טוב! התרפא מנזק שגרמת", "יש חלון נזק של 3 שניות, לריפוי וזמן קירור של שנייה ", "ריפוי לכל אחוז נזק: "] +name = "רצון הכאב" +description = "שחזר בריאות בהתאם לנזק שאתה גורם!" +lore1 = "לפגוע בדברים מעולם לא הרגיש כל כך טוב! התרפא מנזק שגרמת" +lore2 = "יש חלון נזק של 3 שניות, לריפוי וזמן קירור של שנייה " +lore3 = "ריפוי לכל אחוז נזק: " +lore = ["לפגוע בדברים מעולם לא הרגיש כל כך טוב! התרפא מנזק שגרמת", "יש חלון נזק של 3 שניות, לריפוי וזמן קירור של שנייה ", "ריפוי לכל אחוז נזק: "] [tragoul.lance] - name = "חניתות גופות" - description = "הריגת אויב או הריגה על ידי יכולת יוצרת חנית שפוגעת באויב קרוב!" - lore1 = "חניתות ישוגרו מכל דבר שאתה הורג, וגם אם יכולת זו הורגת אויב." - lore2 = "הקרב חלק מהחיים שלך כדי ליצור חניתות (זה יכול להרוג אותך)" - lore3 = "חניתות מרביות: 1 + " - lore = ["חניתות ישוגרו מכל דבר שאתה הורג, וגם אם יכולת זו הורגת אויב.", "הקרב חלק מהחיים שלך כדי ליצור חניתות (זה יכול להרוג אותך)", "חניתות מרביות: 1 + "] +name = "חניתות גופות" +description = "הריגת אויב או הריגה על ידי יכולת יוצרת חנית שפוגעת באויב קרוב!" +lore1 = "חניתות ישוגרו מכל דבר שאתה הורג, וגם אם יכולת זו הורגת אויב." +lore2 = "הקרב חלק מהחיים שלך כדי ליצור חניתות (זה יכול להרוג אותך)" +lore3 = "חניתות מרביות: 1 + " +lore = ["חניתות ישוגרו מכל דבר שאתה הורג, וגם אם יכולת זו הורגת אויב.", "הקרב חלק מהחיים שלך כדי ליצור חניתות (זה יכול להרוג אותך)", "חניתות מרביות: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "תותח זכוכית" - description = "נזק בונוס לא חמוש ככל שערך השריון שלך נמוך יותר" - lore1 = "x נזק ב-0 שריון" - lore2 = "נזק בונוס לכל רמה" - lore = ["x נזק ב-0 שריון", "נזק בונוס לכל רמה"] +name = "תותח זכוכית" +description = "נזק בונוס לא חמוש ככל שערך השריון שלך נמוך יותר" +lore1 = "x נזק ב-0 שריון" +lore2 = "נזק בונוס לכל רמה" +lore = ["x נזק ב-0 שריון", "נזק בונוס לכל רמה"] [unarmed.power] - name = "כוח לא חמוש" - description = "נזק לא חמוש משופר" - lore1 = "נזק" - lore = ["נזק"] +name = "כוח לא חמוש" +description = "נזק לא חמוש משופר" +lore1 = "נזק" +lore = ["נזק"] [unarmed.sucker_punch] - name = "אגרוף מתגנב" - description = "אגרופי ספרינט, אבל קטלניים יותר." - lore1 = "נזק" - lore2 = "הנזק גדל עם המהירות שלך בזמן האגרוף" - lore = ["נזק", "הנזק גדל עם המהירות שלך בזמן האגרוף"] +name = "אגרוף מתגנב" +description = "אגרופי ספרינט, אבל קטלניים יותר." +lore1 = "נזק" +lore2 = "הנזק גדל עם המהירות שלך בזמן האגרוף" +lore = ["נזק", "הנזק גדל עם המהירות שלך בזמן האגרוף"] diff --git a/src/main/resources/it_IT.toml b/src/main/resources/it_IT.toml index cde2ce9ed..2486792e8 100644 --- a/src/main/resources/it_IT.toml +++ b/src/main/resources/it_IT.toml @@ -2,2060 +2,2060 @@ [advancement] [advancement.challenge_move_1k] - title = "Devo muovermi!" - description = "Cammina per 1 chilometro (1.000 blocchi)" +title = "Devo muovermi!" +description = "Cammina per 1 chilometro (1.000 blocchi)" [advancement.challenge_sprint_5k] - title = "Scatta per 5 km!" - description = "Cammina per 5 chilometri (5.000 blocchi)" +title = "Scatta per 5 km!" +description = "Cammina per 5 chilometri (5.000 blocchi)" [advancement.challenge_sprint_50k] - title = "Sfreccia per 50 km!" - description = "Cammina per oltre 50 chilometri (50.000 blocchi)" +title = "Sfreccia per 50 km!" +description = "Cammina per oltre 50 chilometri (50.000 blocchi)" [advancement.challenge_sprint_500k] - title = "Attraversa l'universo!!" - description = "Cammina per oltre 500 chilometri (500.000 blocchi)" +title = "Attraversa l'universo!!" +description = "Cammina per oltre 500 chilometri (500.000 blocchi)" [advancement.challenge_sprint_marathon] - title = "Scatta una maratona (letterale)!" - description = "Scatta per oltre 42.195 blocchi!" +title = "Scatta una maratona (letterale)!" +description = "Scatta per oltre 42.195 blocchi!" [advancement.challenge_place_1k] - title = "Costruttore principiante!" - description = "Posiziona 1.000 blocchi" +title = "Costruttore principiante!" +description = "Posiziona 1.000 blocchi" [advancement.challenge_place_5k] - title = "Costruttore intermedio!" - description = "Posiziona 5.000 blocchi" +title = "Costruttore intermedio!" +description = "Posiziona 5.000 blocchi" [advancement.challenge_place_50k] - title = "Costruttore avanzato!" - description = "Posiziona 50.000 blocchi" +title = "Costruttore avanzato!" +description = "Posiziona 50.000 blocchi" [advancement.challenge_place_500k] - title = "Mastro costruttore!" - description = "Posiziona 500.000 blocchi" +title = "Mastro costruttore!" +description = "Posiziona 500.000 blocchi" [advancement.challenge_place_5m] - title = "Accolito della Simmetria!" - description = "LA REALTA' E' IL TUO PARCO GIOCHI! (5 milioni di blocchi)" +title = "Accolito della Simmetria!" +description = "LA REALTA' E' IL TUO PARCO GIOCHI! (5 milioni di blocchi)" [advancement.challenge_chop_1k] - title = "Boscaiolo principiante!" - description = "Taglia 1.000 blocchi" +title = "Boscaiolo principiante!" +description = "Taglia 1.000 blocchi" [advancement.challenge_chop_5k] - title = "Boscaiolo intermedio!" - description = "Taglia 5.000 blocchi" +title = "Boscaiolo intermedio!" +description = "Taglia 5.000 blocchi" [advancement.challenge_chop_50k] - title = "Boscaiolo avanzato!" - description = "Taglia 50.000 blocchi" +title = "Boscaiolo avanzato!" +description = "Taglia 50.000 blocchi" [advancement.challenge_chop_500k] - title = "Maestro boscaiolo!" - description = "Taglia 500.000 blocchi" +title = "Maestro boscaiolo!" +description = "Taglia 500.000 blocchi" [advancement.challenge_chop_5m] - title = "Jackson il cane" - description = "Il migliore dei bravi ragazzi! (5 milioni di blocchi)" +title = "Jackson il cane" +description = "Il migliore dei bravi ragazzi! (5 milioni di blocchi)" [advancement.challenge_block_1k] - title = "Blocco a malapena!" - description = "Blocca 1000 colpi" +title = "Blocco a malapena!" +description = "Blocca 1000 colpi" [advancement.challenge_block_5k] - title = "Bloccare e' divertente!" - description = "Blocca 5000 colpi" +title = "Bloccare e' divertente!" +description = "Blocca 5000 colpi" [advancement.challenge_block_50k] - title = "Bloccare e' la mia vita!" - description = "Blocca 50.000 colpi" +title = "Bloccare e' la mia vita!" +description = "Blocca 50.000 colpi" [advancement.challenge_block_500k] - title = "Bloccare e' il mio scopo!" - description = "Blocca 500.000 colpi" +title = "Bloccare e' il mio scopo!" +description = "Blocca 500.000 colpi" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Blocca 5.000.000 di colpi" +title = "Die Hand Die Verletzt" +description = "Blocca 5.000.000 di colpi" [advancement.challenge_brew_1k] - title = "Alchimista principiante!" - description = "Consuma 1000 pozioni" +title = "Alchimista principiante!" +description = "Consuma 1000 pozioni" [advancement.challenge_brew_5k] - title = "Alchimista intermedio!" - description = "Consuma 5000 pozioni" +title = "Alchimista intermedio!" +description = "Consuma 5000 pozioni" [advancement.challenge_brew_50k] - title = "Alchimista avanzato!" - description = "Consuma 50.000 pozioni" +title = "Alchimista avanzato!" +description = "Consuma 50.000 pozioni" [advancement.challenge_brew_500k] - title = "Maestro alchimista!" - description = "Consuma 500.000 pozioni" +title = "Maestro alchimista!" +description = "Consuma 500.000 pozioni" [advancement.challenge_brew_5m] - title = "L'Alchimista" - description = "Consuma 5.000.000 di pozioni" +title = "L'Alchimista" +description = "Consuma 5.000.000 di pozioni" [advancement.challenge_brewsplash_1k] - title = "Lanciatore di pozioni principiante!" - description = "Lancia 1000 pozioni" +title = "Lanciatore di pozioni principiante!" +description = "Lancia 1000 pozioni" [advancement.challenge_brewsplash_5k] - title = "Lanciatore di pozioni intermedio!" - description = "Lancia 5000 pozioni" +title = "Lanciatore di pozioni intermedio!" +description = "Lancia 5000 pozioni" [advancement.challenge_brewsplash_50k] - title = "Lanciatore di pozioni avanzato!" - description = "Lancia 50.000 pozioni" +title = "Lanciatore di pozioni avanzato!" +description = "Lancia 50.000 pozioni" [advancement.challenge_brewsplash_500k] - title = "Maestro lanciatore di pozioni!" - description = "Lancia 500.000 pozioni" +title = "Maestro lanciatore di pozioni!" +description = "Lancia 500.000 pozioni" [advancement.challenge_brewsplash_5m] - title = "Il Maestro degli Schizzi" - description = "Lancia 5.000.000 di pozioni" +title = "Il Maestro degli Schizzi" +description = "Lancia 5.000.000 di pozioni" [advancement.challenge_craft_1k] - title = "Artigiano furbo!" - description = "Crea 1000 oggetti" +title = "Artigiano furbo!" +description = "Crea 1000 oggetti" [advancement.challenge_craft_5k] - title = "Artigiano irascibile!" - description = "Crea 5000 oggetti" +title = "Artigiano irascibile!" +description = "Crea 5000 oggetti" [advancement.challenge_craft_50k] - title = "Artigiano servile!" - description = "Crea 50.000 oggetti" +title = "Artigiano servile!" +description = "Crea 50.000 oggetti" [advancement.challenge_craft_500k] - title = "Artigiano cacofonico!" - description = "Crea 500.000 oggetti" +title = "Artigiano cacofonico!" +description = "Crea 500.000 oggetti" [advancement.challenge_craft_5m] - title = "Il Calamitoso McCraftface" - description = "Crea 5.000.000 di oggetti" +title = "Il Calamitoso McCraftface" +description = "Crea 5.000.000 di oggetti" [advancement.challenge_enchant_1k] - title = "Incantatore principiante!" - description = "Incanta 1000 oggetti" +title = "Incantatore principiante!" +description = "Incanta 1000 oggetti" [advancement.challenge_enchant_5k] - title = "Incantatore intermedio!" - description = "Incanta 5000 oggetti" +title = "Incantatore intermedio!" +description = "Incanta 5000 oggetti" [advancement.challenge_enchant_50k] - title = "Incantatore avanzato!" - description = "Incanta 50.000 oggetti" +title = "Incantatore avanzato!" +description = "Incanta 50.000 oggetti" [advancement.challenge_enchant_500k] - title = "Maestro incantatore!" - description = "Incanta 500.000 oggetti" +title = "Maestro incantatore!" +description = "Incanta 500.000 oggetti" [advancement.challenge_enchant_5m] - title = "Incantatore enigmatico" - description = "Incanta 5.000.000 di oggetti" +title = "Incantatore enigmatico" +description = "Incanta 5.000.000 di oggetti" [advancement.challenge_excavate_1k] - title = "Scavatore entusiasta!" - description = "Scava 1000 blocchi" +title = "Scavatore entusiasta!" +description = "Scava 1000 blocchi" [advancement.challenge_excavate_5k] - title = "Scavatore intermedio!" - description = "Scava 5000 blocchi" +title = "Scavatore intermedio!" +description = "Scava 5000 blocchi" [advancement.challenge_excavate_50k] - title = "Scavatore avanzato!" - description = "Scava 50.000 blocchi" +title = "Scavatore avanzato!" +description = "Scava 50.000 blocchi" [advancement.challenge_excavate_500k] - title = "Maestro scavatore!" - description = "Scava 500.000 blocchi" +title = "Maestro scavatore!" +description = "Scava 500.000 blocchi" [advancement.challenge_excavate_5m] - title = "Scavatore enigmatico" - description = "Scava 5.000.000 di blocchi" +title = "Scavatore enigmatico" +description = "Scava 5.000.000 di blocchi" [advancement.horrible_person] - title = "Sei una persona orribile" - description = "Inconcepibile, davvero" +title = "Sei una persona orribile" +description = "Inconcepibile, davvero" [advancement.challenge_turtle_egg_smasher] - title = "Spaccauova di tartaruga!" - description = "Rompi 100 uova di tartaruga" +title = "Spaccauova di tartaruga!" +description = "Rompi 100 uova di tartaruga" [advancement.challenge_turtle_egg_annihilator] - title = "Annientatore di uova di tartaruga!" - description = "Rompi 500 uova di tartaruga" +title = "Annientatore di uova di tartaruga!" +description = "Rompi 500 uova di tartaruga" [advancement.challenge_novice_hunter] - title = "Cacciatore principiante!" - description = "Uccidi 100 entita'" +title = "Cacciatore principiante!" +description = "Uccidi 100 entita'" [advancement.challenge_intermediate_hunter] - title = "Cacciatore intermedio!" - description = "Uccidi 500 entita'" +title = "Cacciatore intermedio!" +description = "Uccidi 500 entita'" [advancement.challenge_advanced_hunter] - title = "Cacciatore avanzato!" - description = "Uccidi 5000 entita'" +title = "Cacciatore avanzato!" +description = "Uccidi 5000 entita'" [advancement.challenge_creeper_conqueror] - title = "Conquistatore di Creeper!" - description = "Uccidi 50 creeper" +title = "Conquistatore di Creeper!" +description = "Uccidi 50 creeper" [advancement.challenge_creeper_annihilator] - title = "Annientatore di Creeper!" - description = "Uccidi 200 creeper" +title = "Annientatore di Creeper!" +description = "Uccidi 200 creeper" [advancement.challenge_pickaxe_1k] - title = "Minatore principiante" - description = "Rompi 1000 blocchi" +title = "Minatore principiante" +description = "Rompi 1000 blocchi" [advancement.challenge_pickaxe_5k] - title = "Minatore abile" - description = "Rompi 5000 blocchi" +title = "Minatore abile" +description = "Rompi 5000 blocchi" [advancement.challenge_pickaxe_50k] - title = "Minatore esperto" - description = "Rompi 50.000 blocchi" +title = "Minatore esperto" +description = "Rompi 50.000 blocchi" [advancement.challenge_pickaxe_500k] - title = "Maestro minatore" - description = "Rompi 500.000 blocchi" +title = "Maestro minatore" +description = "Rompi 500.000 blocchi" [advancement.challenge_pickaxe_5m] - title = "Minatore leggendario" - description = "Rompi 5.000.000 di blocchi" +title = "Minatore leggendario" +description = "Rompi 5.000.000 di blocchi" [advancement.challenge_eat_100] - title = "Cosi' tanto da mangiare!" - description = "Mangia piu' di 100 oggetti!" +title = "Cosi' tanto da mangiare!" +description = "Mangia piu' di 100 oggetti!" [advancement.challenge_eat_1000] - title = "Fame insaziabile!" - description = "Mangia piu' di 1.000 oggetti!" +title = "Fame insaziabile!" +description = "Mangia piu' di 1.000 oggetti!" [advancement.challenge_eat_10000] - title = "FAME ETERNA!" - description = "Mangia piu' di 10.000 oggetti!" +title = "FAME ETERNA!" +description = "Mangia piu' di 10.000 oggetti!" [advancement.challenge_harvest_100] - title = "Raccolto abbondante" - description = "Raccogli piu' di 100 colture!" +title = "Raccolto abbondante" +description = "Raccogli piu' di 100 colture!" [advancement.challenge_harvest_1000] - title = "Grande raccolto" - description = "Raccogli piu' di 1.000 colture!" +title = "Grande raccolto" +description = "Raccogli piu' di 1.000 colture!" [advancement.challenge_swim_1nm] - title = "Sottomarino umano!" - description = "Nuota per 1 miglio nautico (1.852 blocchi)" +title = "Sottomarino umano!" +description = "Nuota per 1 miglio nautico (1.852 blocchi)" [advancement.challenge_sneak_1k] - title = "Dolore alle ginocchia" - description = "Accovacciati per oltre un chilometro (1.000 blocchi)" +title = "Dolore alle ginocchia" +description = "Accovacciati per oltre un chilometro (1.000 blocchi)" [advancement.challenge_sneak_5k] - title = "Camminatore d'Ombra" - description = "Accovacciati per oltre 5.000 blocchi" +title = "Camminatore d'Ombra" +description = "Accovacciati per oltre 5.000 blocchi" [advancement.challenge_sneak_20k] - title = "Fantasma" - description = "Accovacciati per oltre 20.000 blocchi" +title = "Fantasma" +description = "Accovacciati per oltre 20.000 blocchi" [advancement.challenge_swim_5k] - title = "Sommozzatore" - description = "Nuota per oltre 5.000 blocchi" +title = "Sommozzatore" +description = "Nuota per oltre 5.000 blocchi" [advancement.challenge_swim_20k] - title = "Prescelto di Poseidon" - description = "Nuota per oltre 20.000 blocchi" +title = "Prescelto di Poseidon" +description = "Nuota per oltre 20.000 blocchi" [advancement.challenge_sword_100] - title = "Primo Sangue" - description = "Colpisci 100 volte con una spada" +title = "Primo Sangue" +description = "Colpisci 100 volte con una spada" [advancement.challenge_sword_1k] - title = "Danzatore di Lame" - description = "Colpisci 1.000 volte con una spada" +title = "Danzatore di Lame" +description = "Colpisci 1.000 volte con una spada" [advancement.challenge_sword_10k] - title = "Mille Tagli" - description = "Colpisci 10.000 volte con una spada" +title = "Mille Tagli" +description = "Colpisci 10.000 volte con una spada" [advancement.challenge_unarmed_100] - title = "Attaccabrighe" - description = "Colpisci 100 volte a mani nude" +title = "Attaccabrighe" +description = "Colpisci 100 volte a mani nude" [advancement.challenge_unarmed_1k] - title = "Pugni di Ferro" - description = "Colpisci 1.000 volte a mani nude" +title = "Pugni di Ferro" +description = "Colpisci 1.000 volte a mani nude" [advancement.challenge_unarmed_10k] - title = "One Punch" - description = "Colpisci 10.000 volte a mani nude" +title = "One Punch" +description = "Colpisci 10.000 volte a mani nude" [advancement.challenge_trag_1k] - title = "Prezzo di Sangue" - description = "Subisci 1.000 danni" +title = "Prezzo di Sangue" +description = "Subisci 1.000 danni" [advancement.challenge_trag_10k] - title = "Marea Cremisi" - description = "Subisci 10.000 danni" +title = "Marea Cremisi" +description = "Subisci 10.000 danni" [advancement.challenge_trag_100k] - title = "Avatar della Sofferenza" - description = "Subisci 100.000 danni" +title = "Avatar della Sofferenza" +description = "Subisci 100.000 danni" [advancement.challenge_ranged_100] - title = "Esercizio di Tiro" - description = "Lancia 100 proiettili" +title = "Esercizio di Tiro" +description = "Lancia 100 proiettili" [advancement.challenge_ranged_1k] - title = "Occhio di Falco" - description = "Lancia 1.000 proiettili" +title = "Occhio di Falco" +description = "Lancia 1.000 proiettili" [advancement.challenge_ranged_10k] - title = "Tempesta di Frecce" - description = "Lancia 10.000 proiettili" +title = "Tempesta di Frecce" +description = "Lancia 10.000 proiettili" [advancement.challenge_chronos_1h] - title = "Tic Tac" - description = "Trascorri 1 ora online" +title = "Tic Tac" +description = "Trascorri 1 ora online" [advancement.challenge_chronos_24h] - title = "Sabbie del Tempo" - description = "Trascorri 24 ore online" +title = "Sabbie del Tempo" +description = "Trascorri 24 ore online" [advancement.challenge_chronos_168h] - title = "Senza Tempo" - description = "Trascorri 168 ore (1 settimana) online" +title = "Senza Tempo" +description = "Trascorri 168 ore (1 settimana) online" [advancement.challenge_nether_50] - title = "Guardiano dell'Inferno" - description = "Uccidi 50 creature del Nether" +title = "Guardiano dell'Inferno" +description = "Uccidi 50 creature del Nether" [advancement.challenge_nether_500] - title = "Guardiano dell'Abisso" - description = "Uccidi 500 creature del Nether" +title = "Guardiano dell'Abisso" +description = "Uccidi 500 creature del Nether" [advancement.challenge_nether_5k] - title = "Signore del Nether" - description = "Uccidi 5.000 creature del Nether" +title = "Signore del Nether" +description = "Uccidi 5.000 creature del Nether" [advancement.challenge_rift_50] - title = "Anomalia Spaziale" - description = "Teletrasportati 50 volte" +title = "Anomalia Spaziale" +description = "Teletrasportati 50 volte" [advancement.challenge_rift_500] - title = "Camminatore del Vuoto" - description = "Teletrasportati 500 volte" +title = "Camminatore del Vuoto" +description = "Teletrasportati 500 volte" [advancement.challenge_rift_5k] - title = "Tra i Mondi" - description = "Teletrasportati 5.000 volte" +title = "Tra i Mondi" +description = "Teletrasportati 5.000 volte" [advancement.challenge_taming_10] - title = "Sussurratore di Animali" - description = "Alleva 10 animali" +title = "Sussurratore di Animali" +description = "Alleva 10 animali" [advancement.challenge_taming_50] - title = "Capobranco" - description = "Alleva 50 animali" +title = "Capobranco" +description = "Alleva 50 animali" [advancement.challenge_taming_500] - title = "Domatore di Bestie" - description = "Alleva 500 animali" +title = "Domatore di Bestie" +description = "Alleva 500 animali" # Agility [advancement.challenge_sprint_dist_5k] - title = "Demone della Velocita" - description = "Corri per oltre 5 Chilometri (5.000 blocchi)" +title = "Demone della Velocita" +description = "Corri per oltre 5 Chilometri (5.000 blocchi)" [advancement.challenge_sprint_dist_50k] - title = "Gambe Fulminee" - description = "Corri per oltre 50 Chilometri (50.000 blocchi)" +title = "Gambe Fulminee" +description = "Corri per oltre 50 Chilometri (50.000 blocchi)" [advancement.challenge_agility_swim_1k] - title = "Camminatore d'Acqua" - description = "Nuota per oltre 1 Chilometro (1.000 blocchi)" +title = "Camminatore d'Acqua" +description = "Nuota per oltre 1 Chilometro (1.000 blocchi)" [advancement.challenge_agility_swim_10k] - title = "Viaggiatore Acquatico" - description = "Nuota per oltre 10 Chilometri (10.000 blocchi)" +title = "Viaggiatore Acquatico" +description = "Nuota per oltre 10 Chilometri (10.000 blocchi)" [advancement.challenge_fly_1k] - title = "Danzatore del Cielo" - description = "Vola per oltre 1 Chilometro (1.000 blocchi)" +title = "Danzatore del Cielo" +description = "Vola per oltre 1 Chilometro (1.000 blocchi)" [advancement.challenge_fly_10k] - title = "Cavaliere del Vento" - description = "Vola per oltre 10 Chilometri (10.000 blocchi)" +title = "Cavaliere del Vento" +description = "Vola per oltre 10 Chilometri (10.000 blocchi)" [advancement.challenge_agility_sneak_500] - title = "Passi Silenziosi" - description = "Muoviti furtivamente per oltre 500 blocchi" +title = "Passi Silenziosi" +description = "Muoviti furtivamente per oltre 500 blocchi" [advancement.challenge_agility_sneak_5k] - title = "Passi Fantasma" - description = "Muoviti furtivamente per oltre 5 Chilometri (5.000 blocchi)" +title = "Passi Fantasma" +description = "Muoviti furtivamente per oltre 5 Chilometri (5.000 blocchi)" # Architect [advancement.challenge_demolish_500] - title = "Squadra di Demolizione" - description = "Distruggi 500 blocchi" +title = "Squadra di Demolizione" +description = "Distruggi 500 blocchi" [advancement.challenge_demolish_5k] - title = "Palla da Demolizione" - description = "Distruggi 5.000 blocchi" +title = "Palla da Demolizione" +description = "Distruggi 5.000 blocchi" [advancement.challenge_value_placed_10k] - title = "Costruttore di Valore" - description = "Piazza blocchi del valore di 10.000" +title = "Costruttore di Valore" +description = "Piazza blocchi del valore di 10.000" [advancement.challenge_value_placed_100k] - title = "Maestro Architetto" - description = "Piazza blocchi del valore di 100.000" +title = "Maestro Architetto" +description = "Piazza blocchi del valore di 100.000" [advancement.challenge_demolish_val_5k] - title = "Esperto di Recupero" - description = "Recupera 5.000 di valore dai blocchi demoliti" +title = "Esperto di Recupero" +description = "Recupera 5.000 di valore dai blocchi demoliti" [advancement.challenge_demolish_val_50k] - title = "Decostruzione Totale" - description = "Recupera 50.000 di valore dai blocchi demoliti" +title = "Decostruzione Totale" +description = "Recupera 50.000 di valore dai blocchi demoliti" [advancement.challenge_high_build_100] - title = "Costruttore Celeste" - description = "Piazza 100 blocchi sopra Y=128" +title = "Costruttore Celeste" +description = "Piazza 100 blocchi sopra Y=128" [advancement.challenge_high_build_1k] - title = "Architetto delle Nuvole" - description = "Piazza 1.000 blocchi sopra Y=128" +title = "Architetto delle Nuvole" +description = "Piazza 1.000 blocchi sopra Y=128" # Axes [advancement.challenge_axe_swing_500] - title = "Boscaiolo" - description = "Brandisci la tua ascia 500 volte" +title = "Boscaiolo" +description = "Brandisci la tua ascia 500 volte" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Brandisci la tua ascia 5.000 volte" +title = "Berserker" +description = "Brandisci la tua ascia 5.000 volte" [advancement.challenge_axe_damage_1k] - title = "Spaccalegna" - description = "Infliggi 1.000 danni con le asce" +title = "Spaccalegna" +description = "Infliggi 1.000 danni con le asce" [advancement.challenge_axe_damage_10k] - title = "Ascia del Boia" - description = "Infliggi 10.000 danni con le asce" +title = "Ascia del Boia" +description = "Infliggi 10.000 danni con le asce" [advancement.challenge_axe_value_5k] - title = "Mercante di Legname" - description = "Raccogli legname del valore di 5.000" +title = "Mercante di Legname" +description = "Raccogli legname del valore di 5.000" [advancement.challenge_axe_value_50k] - title = "Barone del Legno" - description = "Raccogli legname del valore di 50.000" +title = "Barone del Legno" +description = "Raccogli legname del valore di 50.000" [advancement.challenge_leaves_500] - title = "Soffiatore di Foglie" - description = "Elimina 500 blocchi di foglie con un'ascia" +title = "Soffiatore di Foglie" +description = "Elimina 500 blocchi di foglie con un'ascia" [advancement.challenge_leaves_5k] - title = "Defogliatore" - description = "Elimina 5.000 blocchi di foglie con un'ascia" +title = "Defogliatore" +description = "Elimina 5.000 blocchi di foglie con un'ascia" # Blocking [advancement.challenge_block_dmg_1k] - title = "Assorbitore di Danni" - description = "Blocca 1.000 danni con uno scudo" +title = "Assorbitore di Danni" +description = "Blocca 1.000 danni con uno scudo" [advancement.challenge_block_dmg_10k] - title = "Scudo Umano" - description = "Blocca 10.000 danni con uno scudo" +title = "Scudo Umano" +description = "Blocca 10.000 danni con uno scudo" [advancement.challenge_block_proj_100] - title = "Deflettore di Frecce" - description = "Blocca 100 proiettili con uno scudo" +title = "Deflettore di Frecce" +description = "Blocca 100 proiettili con uno scudo" [advancement.challenge_block_proj_1k] - title = "Scudo Anti-Proiettili" - description = "Blocca 1.000 proiettili con uno scudo" +title = "Scudo Anti-Proiettili" +description = "Blocca 1.000 proiettili con uno scudo" [advancement.challenge_block_melee_500] - title = "Maestro della Parata" - description = "Blocca 500 attacchi ravvicinati con uno scudo" +title = "Maestro della Parata" +description = "Blocca 500 attacchi ravvicinati con uno scudo" [advancement.challenge_block_melee_5k] - title = "Fortezza di Ferro" - description = "Blocca 5.000 attacchi ravvicinati con uno scudo" +title = "Fortezza di Ferro" +description = "Blocca 5.000 attacchi ravvicinati con uno scudo" [advancement.challenge_block_heavy_50] - title = "Carro Armato" - description = "Blocca 50 attacchi pesanti (oltre 5 danni)" +title = "Carro Armato" +description = "Blocca 50 attacchi pesanti (oltre 5 danni)" [advancement.challenge_block_heavy_500] - title = "Oggetto Inamovibile" - description = "Blocca 500 attacchi pesanti (oltre 5 danni)" +title = "Oggetto Inamovibile" +description = "Blocca 500 attacchi pesanti (oltre 5 danni)" # Brewing [advancement.challenge_brew_stands_10] - title = "Preparazione dell'Alchimista" - description = "Piazza 10 alambicchi" +title = "Preparazione dell'Alchimista" +description = "Piazza 10 alambicchi" [advancement.challenge_brew_stands_50] - title = "Fabbrica di Pozioni" - description = "Piazza 50 alambicchi" +title = "Fabbrica di Pozioni" +description = "Piazza 50 alambicchi" [advancement.challenge_brew_strong_25] - title = "Miscela Potente" - description = "Consuma 25 pozioni potenziate" +title = "Miscela Potente" +description = "Consuma 25 pozioni potenziate" [advancement.challenge_brew_strong_250] - title = "Potenza Massima" - description = "Consuma 250 pozioni potenziate" +title = "Potenza Massima" +description = "Consuma 250 pozioni potenziate" [advancement.challenge_brew_splash_hits_50] - title = "Zona di Schizzo" - description = "Colpisci 50 entita con pozioni da lancio" +title = "Zona di Schizzo" +description = "Colpisci 50 entita con pozioni da lancio" [advancement.challenge_brew_splash_hits_500] - title = "Medico della Peste" - description = "Colpisci 500 entita con pozioni da lancio" +title = "Medico della Peste" +description = "Colpisci 500 entita con pozioni da lancio" # Chronos [advancement.challenge_active_dist_1k] - title = "Irrequieto" - description = "Viaggia per 1 Chilometro mentre sei attivo" +title = "Irrequieto" +description = "Viaggia per 1 Chilometro mentre sei attivo" [advancement.challenge_active_dist_10k] - title = "Esploratore" - description = "Viaggia per 10 Chilometri mentre sei attivo" +title = "Esploratore" +description = "Viaggia per 10 Chilometri mentre sei attivo" [advancement.challenge_active_dist_100k] - title = "Moto Perpetuo" - description = "Viaggia per 100 Chilometri mentre sei attivo" +title = "Moto Perpetuo" +description = "Viaggia per 100 Chilometri mentre sei attivo" [advancement.challenge_beds_10] - title = "Mattiniero" - description = "Dormi in un letto 10 volte" +title = "Mattiniero" +description = "Dormi in un letto 10 volte" [advancement.challenge_beds_100] - title = "Saltatore del Tempo" - description = "Dormi in un letto 100 volte" +title = "Saltatore del Tempo" +description = "Dormi in un letto 100 volte" [advancement.challenge_chronos_tp_50] - title = "Spostamento Temporale" - description = "Teletrasportati 50 volte" +title = "Spostamento Temporale" +description = "Teletrasportati 50 volte" [advancement.challenge_chronos_tp_500] - title = "Distorsione Temporale" - description = "Teletrasportati 500 volte" +title = "Distorsione Temporale" +description = "Teletrasportati 500 volte" [advancement.challenge_chronos_deaths_10] - title = "Mortale" - description = "Muori 10 volte" +title = "Mortale" +description = "Muori 10 volte" [advancement.challenge_chronos_deaths_100] - title = "Sfidante della Morte" - description = "Muori 100 volte" +title = "Sfidante della Morte" +description = "Muori 100 volte" # Crafting [advancement.challenge_craft_value_10k] - title = "Valore Artigianale" - description = "Crea oggetti del valore totale di 10.000" +title = "Valore Artigianale" +description = "Crea oggetti del valore totale di 10.000" [advancement.challenge_craft_value_100k] - title = "Artigiano" - description = "Crea oggetti del valore totale di 100.000" +title = "Artigiano" +description = "Crea oggetti del valore totale di 100.000" [advancement.challenge_craft_tools_25] - title = "Fabbro di Utensili" - description = "Crea 25 utensili" +title = "Fabbro di Utensili" +description = "Crea 25 utensili" [advancement.challenge_craft_tools_250] - title = "Maestro Forgiatore" - description = "Crea 250 utensili" +title = "Maestro Forgiatore" +description = "Crea 250 utensili" [advancement.challenge_craft_armor_25] - title = "Fabbro di Armature" - description = "Crea 25 pezzi di armatura" +title = "Fabbro di Armature" +description = "Crea 25 pezzi di armatura" [advancement.challenge_craft_armor_250] - title = "Maestro Armaiolo" - description = "Crea 250 pezzi di armatura" +title = "Maestro Armaiolo" +description = "Crea 250 pezzi di armatura" [advancement.challenge_craft_mass_25k] - title = "Produttore di Massa" - description = "Crea 25.000 oggetti" +title = "Produttore di Massa" +description = "Crea 25.000 oggetti" [advancement.challenge_craft_mass_250k] - title = "Rivoluzione Industriale" - description = "Crea 250.000 oggetti" +title = "Rivoluzione Industriale" +description = "Crea 250.000 oggetti" # Discovery [advancement.challenge_discover_items_50] - title = "Collezionista" - description = "Scopri 50 oggetti unici" +title = "Collezionista" +description = "Scopri 50 oggetti unici" [advancement.challenge_discover_items_250] - title = "Catalogatore" - description = "Scopri 250 oggetti unici" +title = "Catalogatore" +description = "Scopri 250 oggetti unici" [advancement.challenge_discover_blocks_50] - title = "Topografo" - description = "Scopri 50 blocchi unici" +title = "Topografo" +description = "Scopri 50 blocchi unici" [advancement.challenge_discover_blocks_250] - title = "Geologo" - description = "Scopri 250 blocchi unici" +title = "Geologo" +description = "Scopri 250 blocchi unici" [advancement.challenge_discover_mobs_25] - title = "Osservatore" - description = "Scopri 25 creature uniche" +title = "Osservatore" +description = "Scopri 25 creature uniche" [advancement.challenge_discover_mobs_75] - title = "Naturalista" - description = "Scopri 75 creature uniche" +title = "Naturalista" +description = "Scopri 75 creature uniche" [advancement.challenge_discover_biomes_10] - title = "Vagabondo" - description = "Scopri 10 biomi unici" +title = "Vagabondo" +description = "Scopri 10 biomi unici" [advancement.challenge_discover_biomes_40] - title = "Giramondo" - description = "Scopri 40 biomi unici" +title = "Giramondo" +description = "Scopri 40 biomi unici" [advancement.challenge_discover_foods_10] - title = "Buongustaio" - description = "Scopri 10 cibi unici" +title = "Buongustaio" +description = "Scopri 10 cibi unici" [advancement.challenge_discover_foods_30] - title = "Maestro Culinario" - description = "Scopri 30 cibi unici" +title = "Maestro Culinario" +description = "Scopri 30 cibi unici" # Enchanting [advancement.challenge_enchant_power_100] - title = "Tessitore di Potere" - description = "Accumula 100 di potere d'incantesimo" +title = "Tessitore di Potere" +description = "Accumula 100 di potere d'incantesimo" [advancement.challenge_enchant_power_1k] - title = "Maestro Arcano" - description = "Accumula 1.000 di potere d'incantesimo" +title = "Maestro Arcano" +description = "Accumula 1.000 di potere d'incantesimo" [advancement.challenge_enchant_levels_1k] - title = "Spendaccione di Livelli" - description = "Spendi 1.000 livelli di esperienza in incantesimi" +title = "Spendaccione di Livelli" +description = "Spendi 1.000 livelli di esperienza in incantesimi" [advancement.challenge_enchant_levels_10k] - title = "Divoratore di XP" - description = "Spendi 10.000 livelli di esperienza in incantesimi" +title = "Divoratore di XP" +description = "Spendi 10.000 livelli di esperienza in incantesimi" [advancement.challenge_enchant_high_25] - title = "Giocatore d'Azzardo" - description = "Esegui 25 incantesimi di livello massimo" +title = "Giocatore d'Azzardo" +description = "Esegui 25 incantesimi di livello massimo" [advancement.challenge_enchant_high_250] - title = "Incantatore Leggendario" - description = "Esegui 250 incantesimi di livello massimo" +title = "Incantatore Leggendario" +description = "Esegui 250 incantesimi di livello massimo" [advancement.challenge_enchant_total_500] - title = "Bruciatore di Livelli" - description = "Spendi 500 livelli totali in incantesimi" +title = "Bruciatore di Livelli" +description = "Spendi 500 livelli totali in incantesimi" [advancement.challenge_enchant_total_5k] - title = "Investimento Arcano" - description = "Spendi 5.000 livelli totali in incantesimi" +title = "Investimento Arcano" +description = "Spendi 5.000 livelli totali in incantesimi" # Excavation [advancement.challenge_dig_swing_500] - title = "Scavatore" - description = "Brandisci la tua pala 500 volte" +title = "Scavatore" +description = "Brandisci la tua pala 500 volte" [advancement.challenge_dig_swing_5k] - title = "Escavatore" - description = "Brandisci la tua pala 5.000 volte" +title = "Escavatore" +description = "Brandisci la tua pala 5.000 volte" [advancement.challenge_dig_damage_1k] - title = "Cavaliere della Pala" - description = "Infliggi 1.000 danni con una pala" +title = "Cavaliere della Pala" +description = "Infliggi 1.000 danni con una pala" [advancement.challenge_dig_damage_10k] - title = "Maestro della Pala" - description = "Infliggi 10.000 danni con una pala" +title = "Maestro della Pala" +description = "Infliggi 10.000 danni con una pala" [advancement.challenge_dig_value_5k] - title = "Mercante di Terra" - description = "Scava blocchi del valore di 5.000" +title = "Mercante di Terra" +description = "Scava blocchi del valore di 5.000" [advancement.challenge_dig_value_50k] - title = "Barone della Terra" - description = "Scava blocchi del valore di 50.000" +title = "Barone della Terra" +description = "Scava blocchi del valore di 50.000" [advancement.challenge_dig_gravel_500] - title = "Frantumatore di Ghiaia" - description = "Scava 500 blocchi di ghiaia, sabbia o argilla" +title = "Frantumatore di Ghiaia" +description = "Scava 500 blocchi di ghiaia, sabbia o argilla" [advancement.challenge_dig_gravel_5k] - title = "Setacciatore di Sabbia" - description = "Scava 5.000 blocchi di ghiaia, sabbia o argilla" +title = "Setacciatore di Sabbia" +description = "Scava 5.000 blocchi di ghiaia, sabbia o argilla" # Herbalism [advancement.challenge_plant_100] - title = "Seminatore" - description = "Pianta 100 colture" +title = "Seminatore" +description = "Pianta 100 colture" [advancement.challenge_plant_1k] - title = "Pollice Verde" - description = "Pianta 1.000 colture" +title = "Pollice Verde" +description = "Pianta 1.000 colture" [advancement.challenge_plant_5k] - title = "Barone Agricolo" - description = "Pianta 5.000 colture" +title = "Barone Agricolo" +description = "Pianta 5.000 colture" [advancement.challenge_compost_50] - title = "Riciclatore" - description = "Composta 50 oggetti" +title = "Riciclatore" +description = "Composta 50 oggetti" [advancement.challenge_compost_500] - title = "Arricchitore del Suolo" - description = "Composta 500 oggetti" +title = "Arricchitore del Suolo" +description = "Composta 500 oggetti" [advancement.challenge_shear_50] - title = "Tosatore" - description = "Tosa 50 entita" +title = "Tosatore" +description = "Tosa 50 entita" [advancement.challenge_shear_250] - title = "Maestro del Gregge" - description = "Tosa 250 entita" +title = "Maestro del Gregge" +description = "Tosa 250 entita" # Hunter [advancement.challenge_kills_500] - title = "Sterminatore" - description = "Uccidi 500 creature" +title = "Sterminatore" +description = "Uccidi 500 creature" [advancement.challenge_kills_5k] - title = "Carnefice" - description = "Uccidi 5.000 creature" +title = "Carnefice" +description = "Uccidi 5.000 creature" [advancement.challenge_boss_1] - title = "Sfidante di Boss" - description = "Sconfiggi un boss" +title = "Sfidante di Boss" +description = "Sconfiggi un boss" [advancement.challenge_boss_10] - title = "Uccisore di Leggende" - description = "Sconfiggi 10 boss" +title = "Uccisore di Leggende" +description = "Sconfiggi 10 boss" # Nether [advancement.challenge_wither_dmg_500] - title = "Avvizzito" - description = "Sopporta 500 danni da Wither" +title = "Avvizzito" +description = "Sopporta 500 danni da Wither" [advancement.challenge_wither_dmg_5k] - title = "Sopravvissuto alla Piaga" - description = "Sopporta 5.000 danni da Wither" +title = "Sopravvissuto alla Piaga" +description = "Sopporta 5.000 danni da Wither" [advancement.challenge_wither_skel_25] - title = "Collezionista di Ossa" - description = "Uccidi 25 scheletri Wither" +title = "Collezionista di Ossa" +description = "Uccidi 25 scheletri Wither" [advancement.challenge_wither_skel_250] - title = "Flagello degli Scheletri" - description = "Uccidi 250 scheletri Wither" +title = "Flagello degli Scheletri" +description = "Uccidi 250 scheletri Wither" [advancement.challenge_wither_boss_1] - title = "Uccisore del Wither" - description = "Sconfiggi il Wither" +title = "Uccisore del Wither" +description = "Sconfiggi il Wither" [advancement.challenge_wither_boss_10] - title = "Dominatore del Nether" - description = "Sconfiggi il Wither 10 volte" +title = "Dominatore del Nether" +description = "Sconfiggi il Wither 10 volte" [advancement.challenge_roses_10] - title = "Giardiniere della Morte" - description = "Distruggi 10 rose del Wither" +title = "Giardiniere della Morte" +description = "Distruggi 10 rose del Wither" [advancement.challenge_roses_100] - title = "Collezionista di Piaghe" - description = "Distruggi 100 rose del Wither" +title = "Collezionista di Piaghe" +description = "Distruggi 100 rose del Wither" # Pickaxes [advancement.challenge_pick_swing_500] - title = "Braccio del Minatore" - description = "Brandisci il tuo piccone 500 volte" +title = "Braccio del Minatore" +description = "Brandisci il tuo piccone 500 volte" [advancement.challenge_pick_swing_5k] - title = "Scavatore di Tunnel" - description = "Brandisci il tuo piccone 5.000 volte" +title = "Scavatore di Tunnel" +description = "Brandisci il tuo piccone 5.000 volte" [advancement.challenge_pick_damage_1k] - title = "Combattente col Piccone" - description = "Infliggi 1.000 danni con un piccone" +title = "Combattente col Piccone" +description = "Infliggi 1.000 danni con un piccone" [advancement.challenge_pick_damage_10k] - title = "Piccone da Guerra" - description = "Infliggi 10.000 danni con un piccone" +title = "Piccone da Guerra" +description = "Infliggi 10.000 danni con un piccone" [advancement.challenge_pick_value_5k] - title = "Cercatore di Gemme" - description = "Mina blocchi del valore di 5.000" +title = "Cercatore di Gemme" +description = "Mina blocchi del valore di 5.000" [advancement.challenge_pick_value_50k] - title = "Barone dei Minerali" - description = "Mina blocchi del valore di 50.000" +title = "Barone dei Minerali" +description = "Mina blocchi del valore di 50.000" [advancement.challenge_pick_ores_500] - title = "Cercatore" - description = "Mina 500 blocchi di minerale" +title = "Cercatore" +description = "Mina 500 blocchi di minerale" [advancement.challenge_pick_ores_5k] - title = "Maestro Minatore" - description = "Mina 5.000 blocchi di minerale" +title = "Maestro Minatore" +description = "Mina 5.000 blocchi di minerale" # Ranged [advancement.challenge_ranged_dmg_1k] - title = "Tiratore Scelto" - description = "Infliggi 1.000 danni a distanza" +title = "Tiratore Scelto" +description = "Infliggi 1.000 danni a distanza" [advancement.challenge_ranged_dmg_10k] - title = "Arciere Letale" - description = "Infliggi 10.000 danni a distanza" +title = "Arciere Letale" +description = "Infliggi 10.000 danni a distanza" [advancement.challenge_ranged_dist_5k] - title = "Lunga Gittata" - description = "Spara proiettili coprendo 5.000 blocchi di distanza totale" +title = "Lunga Gittata" +description = "Spara proiettili coprendo 5.000 blocchi di distanza totale" [advancement.challenge_ranged_dist_50k] - title = "Tiratore Kilometrico" - description = "Spara proiettili coprendo 50.000 blocchi di distanza totale" +title = "Tiratore Kilometrico" +description = "Spara proiettili coprendo 50.000 blocchi di distanza totale" [advancement.challenge_ranged_kills_50] - title = "Arciere" - description = "Uccidi 50 creature con armi a distanza" +title = "Arciere" +description = "Uccidi 50 creature con armi a distanza" [advancement.challenge_ranged_kills_500] - title = "Maestro Arciere" - description = "Uccidi 500 creature con armi a distanza" +title = "Maestro Arciere" +description = "Uccidi 500 creature con armi a distanza" [advancement.challenge_longshot_25] - title = "Cecchino" - description = "Metti a segno 25 tiri a lunga distanza (oltre 30 blocchi)" +title = "Cecchino" +description = "Metti a segno 25 tiri a lunga distanza (oltre 30 blocchi)" [advancement.challenge_longshot_250] - title = "Occhio d'Aquila" - description = "Metti a segno 250 tiri a lunga distanza (oltre 30 blocchi)" +title = "Occhio d'Aquila" +description = "Metti a segno 250 tiri a lunga distanza (oltre 30 blocchi)" # Rift [advancement.challenge_rift_pearls_50] - title = "Lanciatore di Perle" - description = "Lancia 50 perle dell'Ender" +title = "Lanciatore di Perle" +description = "Lancia 50 perle dell'Ender" [advancement.challenge_rift_pearls_500] - title = "Dipendente dal Teletrasporto" - description = "Lancia 500 perle dell'Ender" +title = "Dipendente dal Teletrasporto" +description = "Lancia 500 perle dell'Ender" [advancement.challenge_rift_enderman_50] - title = "Cacciatore di Enderman" - description = "Uccidi 50 endermen" +title = "Cacciatore di Enderman" +description = "Uccidi 50 endermen" [advancement.challenge_rift_enderman_500] - title = "Predatore del Vuoto" - description = "Uccidi 500 endermen" +title = "Predatore del Vuoto" +description = "Uccidi 500 endermen" [advancement.challenge_rift_dragon_500] - title = "Combattente di Draghi" - description = "Infliggi 500 danni al Drago dell'End" +title = "Combattente di Draghi" +description = "Infliggi 500 danni al Drago dell'End" [advancement.challenge_rift_dragon_5k] - title = "Flagello dei Draghi" - description = "Infliggi 5.000 danni al Drago dell'End" +title = "Flagello dei Draghi" +description = "Infliggi 5.000 danni al Drago dell'End" [advancement.challenge_rift_crystal_10] - title = "Spaccacristalli" - description = "Distruggi 10 cristalli dell'End" +title = "Spaccacristalli" +description = "Distruggi 10 cristalli dell'End" [advancement.challenge_rift_crystal_100] - title = "Demolitore dell'End" - description = "Distruggi 100 cristalli dell'End" +title = "Demolitore dell'End" +description = "Distruggi 100 cristalli dell'End" # Seaborne [advancement.challenge_fish_25] - title = "Pescatore" - description = "Cattura 25 pesci" +title = "Pescatore" +description = "Cattura 25 pesci" [advancement.challenge_fish_250] - title = "Maestro Pescatore" - description = "Cattura 250 pesci" +title = "Maestro Pescatore" +description = "Cattura 250 pesci" [advancement.challenge_drowned_25] - title = "Cacciatore di Annegati" - description = "Uccidi 25 annegati" +title = "Cacciatore di Annegati" +description = "Uccidi 25 annegati" [advancement.challenge_drowned_250] - title = "Pulitore dell'Oceano" - description = "Uccidi 250 annegati" +title = "Pulitore dell'Oceano" +description = "Uccidi 250 annegati" [advancement.challenge_guardian_10] - title = "Uccisore di Guardiani" - description = "Uccidi 10 guardiani" +title = "Uccisore di Guardiani" +description = "Uccidi 10 guardiani" [advancement.challenge_guardian_100] - title = "Razziatore di Templi" - description = "Uccidi 100 guardiani" +title = "Razziatore di Templi" +description = "Uccidi 100 guardiani" [advancement.challenge_underwater_blocks_100] - title = "Minatore Subacqueo" - description = "Distruggi 100 blocchi sott'acqua" +title = "Minatore Subacqueo" +description = "Distruggi 100 blocchi sott'acqua" [advancement.challenge_underwater_blocks_1k] - title = "Ingegnere Acquatico" - description = "Distruggi 1.000 blocchi sott'acqua" +title = "Ingegnere Acquatico" +description = "Distruggi 1.000 blocchi sott'acqua" # Stealth [advancement.challenge_stealth_dmg_500] - title = "Pugnalatore alle Spalle" - description = "Infliggi 500 danni mentre sei accovacciato" +title = "Pugnalatore alle Spalle" +description = "Infliggi 500 danni mentre sei accovacciato" [advancement.challenge_stealth_dmg_5k] - title = "Assassino Silenzioso" - description = "Infliggi 5.000 danni mentre sei accovacciato" +title = "Assassino Silenzioso" +description = "Infliggi 5.000 danni mentre sei accovacciato" [advancement.challenge_stealth_kills_10] - title = "Assassino" - description = "Uccidi 10 creature mentre sei accovacciato" +title = "Assassino" +description = "Uccidi 10 creature mentre sei accovacciato" [advancement.challenge_stealth_kills_100] - title = "Mietitore delle Ombre" - description = "Uccidi 100 creature mentre sei accovacciato" +title = "Mietitore delle Ombre" +description = "Uccidi 100 creature mentre sei accovacciato" [advancement.challenge_stealth_time_1h] - title = "Paziente" - description = "Trascorri 1 ora accovacciato (3.600 secondi)" +title = "Paziente" +description = "Trascorri 1 ora accovacciato (3.600 secondi)" [advancement.challenge_stealth_time_10h] - title = "Maestro delle Ombre" - description = "Trascorri 10 ore accovacciato (36.000 secondi)" +title = "Maestro delle Ombre" +description = "Trascorri 10 ore accovacciato (36.000 secondi)" [advancement.challenge_stealth_arrows_50] - title = "Arciere Silenzioso" - description = "Scagliare 50 frecce mentre sei accovacciato" +title = "Arciere Silenzioso" +description = "Scagliare 50 frecce mentre sei accovacciato" [advancement.challenge_stealth_arrows_500] - title = "Arciere Fantasma" - description = "Scagliare 500 frecce mentre sei accovacciato" +title = "Arciere Fantasma" +description = "Scagliare 500 frecce mentre sei accovacciato" # Swords [advancement.challenge_sword_dmg_1k] - title = "Apprendista della Lama" - description = "Infliggi 1.000 danni con le spade" +title = "Apprendista della Lama" +description = "Infliggi 1.000 danni con le spade" [advancement.challenge_sword_dmg_10k] - title = "Spadaccino" - description = "Infliggi 10.000 danni con le spade" +title = "Spadaccino" +description = "Infliggi 10.000 danni con le spade" [advancement.challenge_sword_kills_50] - title = "Duellante" - description = "Uccidi 50 creature con le spade" +title = "Duellante" +description = "Uccidi 50 creature con le spade" [advancement.challenge_sword_kills_500] - title = "Gladiatore" - description = "Uccidi 500 creature con le spade" +title = "Gladiatore" +description = "Uccidi 500 creature con le spade" [advancement.challenge_sword_crit_50] - title = "Colpo Critico" - description = "Metti a segno 50 colpi critici con le spade" +title = "Colpo Critico" +description = "Metti a segno 50 colpi critici con le spade" [advancement.challenge_sword_crit_500] - title = "Maestro della Precisione" - description = "Metti a segno 500 colpi critici con le spade" +title = "Maestro della Precisione" +description = "Metti a segno 500 colpi critici con le spade" [advancement.challenge_sword_heavy_25] - title = "Colpo Pesante" - description = "Metti a segno 25 colpi pesanti con le spade (oltre 8 danni)" +title = "Colpo Pesante" +description = "Metti a segno 25 colpi pesanti con le spade (oltre 8 danni)" [advancement.challenge_sword_heavy_250] - title = "Colpo Devastante" - description = "Metti a segno 250 colpi pesanti con le spade (oltre 8 danni)" +title = "Colpo Devastante" +description = "Metti a segno 250 colpi pesanti con le spade (oltre 8 danni)" # Taming [advancement.challenge_pet_dmg_500] - title = "Addestratore di Bestie" - description = "I tuoi animali infliggono 500 danni totali" +title = "Addestratore di Bestie" +description = "I tuoi animali infliggono 500 danni totali" [advancement.challenge_pet_dmg_5k] - title = "Maestro di Guerra" - description = "I tuoi animali infliggono 5.000 danni totali" +title = "Maestro di Guerra" +description = "I tuoi animali infliggono 5.000 danni totali" [advancement.challenge_tamed_10] - title = "Amico degli Animali" - description = "Addomestica 10 animali" +title = "Amico degli Animali" +description = "Addomestica 10 animali" [advancement.challenge_tamed_100] - title = "Guardiano dello Zoo" - description = "Addomestica 100 animali" +title = "Guardiano dello Zoo" +description = "Addomestica 100 animali" [advancement.challenge_pet_kills_25] - title = "Tattica del Branco" - description = "I tuoi animali uccidono 25 creature" +title = "Tattica del Branco" +description = "I tuoi animali uccidono 25 creature" [advancement.challenge_pet_kills_250] - title = "Comandante Alfa" - description = "I tuoi animali uccidono 250 creature" +title = "Comandante Alfa" +description = "I tuoi animali uccidono 250 creature" [advancement.challenge_taming_2500] - title = "Esperto di Allevamento" - description = "Alleva 2.500 animali" +title = "Esperto di Allevamento" +description = "Alleva 2.500 animali" [advancement.challenge_taming_25k] - title = "Maestro Genetista" - description = "Alleva 25.000 animali" +title = "Maestro Genetista" +description = "Alleva 25.000 animali" # TragOul [advancement.challenge_trag_hits_500] - title = "Sacco da Pugni" - description = "Ricevi 500 colpi" +title = "Sacco da Pugni" +description = "Ricevi 500 colpi" [advancement.challenge_trag_hits_5k] - title = "Ghiottone di Punizioni" - description = "Ricevi 5.000 colpi" +title = "Ghiottone di Punizioni" +description = "Ricevi 5.000 colpi" [advancement.challenge_trag_deaths_10] - title = "Nove Vite" - description = "Muori 10 volte" +title = "Nove Vite" +description = "Muori 10 volte" [advancement.challenge_trag_deaths_100] - title = "Incubo Ricorrente" - description = "Muori 100 volte" +title = "Incubo Ricorrente" +description = "Muori 100 volte" [advancement.challenge_trag_fire_500] - title = "Vittima del Fuoco" - description = "Sopporta 500 danni da fuoco" +title = "Vittima del Fuoco" +description = "Sopporta 500 danni da fuoco" [advancement.challenge_trag_fire_5k] - title = "Fenice" - description = "Sopporta 5.000 danni da fuoco" +title = "Fenice" +description = "Sopporta 5.000 danni da fuoco" [advancement.challenge_trag_fall_500] - title = "Test di Gravita" - description = "Sopporta 500 danni da caduta" +title = "Test di Gravita" +description = "Sopporta 500 danni da caduta" [advancement.challenge_trag_fall_5k] - title = "Velocita Terminale" - description = "Sopporta 5.000 danni da caduta" +title = "Velocita Terminale" +description = "Sopporta 5.000 danni da caduta" # Unarmed [advancement.challenge_unarmed_dmg_1k] - title = "Attaccabrighe" - description = "Infliggi 1.000 danni a mani nude" +title = "Attaccabrighe" +description = "Infliggi 1.000 danni a mani nude" [advancement.challenge_unarmed_dmg_10k] - title = "Artista Marziale" - description = "Infliggi 10.000 danni a mani nude" +title = "Artista Marziale" +description = "Infliggi 10.000 danni a mani nude" [advancement.challenge_unarmed_kills_25] - title = "Pugni Nudi" - description = "Uccidi 25 creature a mani nude" +title = "Pugni Nudi" +description = "Uccidi 25 creature a mani nude" [advancement.challenge_unarmed_kills_250] - title = "Pugno di Leggenda" - description = "Uccidi 250 creature a mani nude" +title = "Pugno di Leggenda" +description = "Uccidi 250 creature a mani nude" [advancement.challenge_unarmed_crit_25] - title = "Pugno Critico" - description = "Metti a segno 25 colpi critici a mani nude" +title = "Pugno Critico" +description = "Metti a segno 25 colpi critici a mani nude" [advancement.challenge_unarmed_crit_250] - title = "Pugno di Precisione" - description = "Metti a segno 250 colpi critici a mani nude" +title = "Pugno di Precisione" +description = "Metti a segno 250 colpi critici a mani nude" [advancement.challenge_unarmed_heavy_25] - title = "Pugno Potente" - description = "Metti a segno 25 colpi pesanti a mani nude (oltre 6 danni)" +title = "Pugno Potente" +description = "Metti a segno 25 colpi pesanti a mani nude (oltre 6 danni)" [advancement.challenge_unarmed_heavy_250] - title = "Re del Knockout" - description = "Metti a segno 250 colpi pesanti a mani nude (oltre 6 danni)" +title = "Re del Knockout" +description = "Metti a segno 250 colpi pesanti a mani nude (oltre 6 danni)" # items [items] [items.bound_ender_peral] - name = "Portachiave del Reliquiario" - usage1 = "Shift + Clic sinistro per legare" - usage2 = "Clic destro per accedere all'inventario legato" +name = "Portachiave del Reliquiario" +usage1 = "Shift + Clic sinistro per legare" +usage2 = "Clic destro per accedere all'inventario legato" [items.bound_eye_of_ender] - name = "Ancora Oculare" - usage1 = "Clic destro per consumare e teletrasportarsi alla posizione legata" - usage2 = "Shift + Clic sinistro per legare a un blocco" +name = "Ancora Oculare" +usage1 = "Clic destro per consumare e teletrasportarsi alla posizione legata" +usage2 = "Shift + Clic sinistro per legare a un blocco" [items.bound_redstone_torch] - name = "Telecomando di Redstone" - usage1 = "Clic destro per creare un impulso di Redstone da 1 tick" - usage2 = "Shift + Clic sinistro su un blocco 'Bersaglio' per legare" +name = "Telecomando di Redstone" +usage1 = "Clic destro per creare un impulso di Redstone da 1 tick" +usage2 = "Shift + Clic sinistro su un blocco 'Bersaglio' per legare" [items.bound_snowball] - name = "Trappola di Ragnatele!" - usage1 = "Lancia per creare una trappola di ragnatele temporanea nella posizione" +name = "Trappola di Ragnatele!" +usage1 = "Lancia per creare una trappola di ragnatele temporanea nella posizione" [items.chrono_time_bottle] - name = "Tempo in Bottiglia" - usage1 = "Accumula tempo passivamente mentre e' nel tuo inventario" - usage2 = "Clic destro su blocchi temporizzati o cuccioli per spendere il tempo accumulato" - stored = "Tempo Accumulato" +name = "Tempo in Bottiglia" +usage1 = "Accumula tempo passivamente mentre e' nel tuo inventario" +usage2 = "Clic destro su blocchi temporizzati o cuccioli per spendere il tempo accumulato" +stored = "Tempo Accumulato" [items.chrono_time_bomb] - name = "Bomba Temporale" - usage1 = "Clic destro per lanciare un proiettile crono che crea un campo temporale" +name = "Bomba Temporale" +usage1 = "Clic destro per lanciare un proiettile crono che crea un campo temporale" [items.elevator_block] - name = "Blocco Ascensore" - usage1 = "Salta per teletrasportarti in alto" - usage2 = "Accovacciati per teletrasportarti in basso" - usage3 = "Minimo 2 blocchi d'aria tra gli ascensori" +name = "Blocco Ascensore" +usage1 = "Salta per teletrasportarti in alto" +usage2 = "Accovacciati per teletrasportarti in basso" +usage3 = "Minimo 2 blocchi d'aria tra gli ascensori" # snippets [snippets] [snippets.gui] - level = "Livello" - knowledge = "conoscenza" - power_used = "Potere utilizzato" - not_learned = "Non appreso" - xp = "XP per" - welcome = "Benvenuto!" - welcome_back = "Bentornato!" - xp_bonus_for_time = "XP per" - max_ability_power = "Potere massimo delle abilita'" - unlock_this_by_clicking = "Sblocca questo facendo clic destro: " - back = "Indietro" - unlearn_all = "Disimpara tutto" - unlearned_all = "Tutto disimparato" +level = "Livello" +knowledge = "conoscenza" +power_used = "Potere utilizzato" +not_learned = "Non appreso" +xp = "XP per" +welcome = "Benvenuto!" +welcome_back = "Bentornato!" +xp_bonus_for_time = "XP per" +max_ability_power = "Potere massimo delle abilita'" +unlock_this_by_clicking = "Sblocca questo facendo clic destro: " +back = "Indietro" +unlearn_all = "Disimpara tutto" +unlearned_all = "Tutto disimparato" [snippets.adapt_menu] - may_not_unlearn = "NON PUOI DISIMPARARE" - may_unlearn = "PUOI IMPARARE/DISIMPARARE" - knowledge_cost = "Costo di conoscenza" - knowledge_available = "Conoscenza disponibile" - already_learned = "Gia' appreso" - unlearn_refund = "Clicca per disimparare e rimborsare" - no_refunds = "HARDCORE, RIMBORSI DISABILITATI" - knowledge = "conoscenza" - click_learn = "Clicca per imparare" - no_knowledge = "(Non hai alcuna conoscenza)" - you_only_have = "Hai solo" - how_to_level_up = "Aumenta il livello delle abilita' per incrementare il tuo potere massimo." - not_enough_power = "Potere insufficiente! Ogni livello di abilita' costa 1 potere." - power = "potere" - power_drain = "Consumo di potere" - learned = "Appreso " - unlearned = "Disimparato " - activator_block = "Libreria" +may_not_unlearn = "NON PUOI DISIMPARARE" +may_unlearn = "PUOI IMPARARE/DISIMPARARE" +knowledge_cost = "Costo di conoscenza" +knowledge_available = "Conoscenza disponibile" +already_learned = "Gia' appreso" +unlearn_refund = "Clicca per disimparare e rimborsare" +no_refunds = "HARDCORE, RIMBORSI DISABILITATI" +knowledge = "conoscenza" +click_learn = "Clicca per imparare" +no_knowledge = "(Non hai alcuna conoscenza)" +you_only_have = "Hai solo" +how_to_level_up = "Aumenta il livello delle abilita' per incrementare il tuo potere massimo." +not_enough_power = "Potere insufficiente! Ogni livello di abilita' costa 1 potere." +power = "potere" +power_drain = "Consumo di potere" +learned = "Appreso " +unlearned = "Disimparato " +activator_block = "Libreria" [snippets.knowledge_orb] - contains = "contiene" - knowledge = "conoscenza" - rightclick = "Clic destro" - togainknowledge = "per ottenere questa conoscenza" - knowledge_orb = "Sfera della Conoscenza" +contains = "contiene" +knowledge = "conoscenza" +rightclick = "Clic destro" +togainknowledge = "per ottenere questa conoscenza" +knowledge_orb = "Sfera della Conoscenza" [snippets.experience_orb] - contains = "contiene" - xp = "Esperienza" - rightclick = "Clic destro" - togainxp = "per ottenere questa esperienza" - xporb = "Sfera dell'Esperienza" +contains = "contiene" +xp = "Esperienza" +rightclick = "Clic destro" +togainxp = "per ottenere questa esperienza" +xporb = "Sfera dell'Esperienza" # skill [skill] [skill.agility] - name = "Agilita'" - icon = "⇉" - description = "L'agilita' e' la capacita' di muoversi rapidamente e fluidamente di fronte agli ostacoli." +name = "Agilita'" +icon = "⇉" +description = "L'agilita' e' la capacita' di muoversi rapidamente e fluidamente di fronte agli ostacoli." [skill.architect] - name = "Architetto" - icon = "⬧" - description = "Le strutture sono i mattoni del mondo. La realta' e' nelle tue mani, tua da controllare." +name = "Architetto" +icon = "⬧" +description = "Le strutture sono i mattoni del mondo. La realta' e' nelle tue mani, tua da controllare." [skill.axes] - name = "Asce" - icon = "🪓" - description1 = "Perche' abbattere alberi, quando puoi tagliare " - description2 = "cose" - description3 = "al loro posto, stesso risultato finale!" +name = "Asce" +icon = "🪓" +description1 = "Perche' abbattere alberi, quando puoi tagliare " +description2 = "cose" +description3 = "al loro posto, stesso risultato finale!" [skill.brewing] - name = "Alchimia" - icon = "❦" - description = "Doppia bolla, tripla bolla, quadrupla bolla - ancora non riesco a mettere questa pozione in un calderone" +name = "Alchimia" +icon = "❦" +description = "Doppia bolla, tripla bolla, quadrupla bolla - ancora non riesco a mettere questa pozione in un calderone" [skill.blocking] - name = "Difesa" - icon = "🛡" - description = "Bastoni e pietre non ti spezzeranno le ossa, ma uno scudo si'." +name = "Difesa" +icon = "🛡" +description = "Bastoni e pietre non ti spezzeranno le ossa, ma uno scudo si'." [skill.crafting] - name = "Artigianato" - icon = "⌂" - description = "Non essendoci piu' pezzi da piazzare, perche' non crearne un altro?" +name = "Artigianato" +icon = "⌂" +description = "Non essendoci piu' pezzi da piazzare, perche' non crearne un altro?" [skill.discovery] - name = "Scoperta" - icon = "⚛" - description = "Mentre la tua percezione si espande, la tua mente si dipana per scoprire cio' che non conoscevi." +name = "Scoperta" +icon = "⚛" +description = "Mentre la tua percezione si espande, la tua mente si dipana per scoprire cio' che non conoscevi." [skill.enchanting] - name = "Incantamento" - icon = "♰" - description = "Di che stai parlando? Profezie, visioni, chiacchiere superstiziose?" +name = "Incantamento" +icon = "♰" +description = "Di che stai parlando? Profezie, visioni, chiacchiere superstiziose?" [skill.excavation] - name = "Scavo" - icon = "ᛳ" - description = "Scava scava un buco..." +name = "Scavo" +icon = "ᛳ" +description = "Scava scava un buco..." [skill.herbalism] - name = "Erboristeria" - icon = "⚘" - description = "Non trovo nessuna pianta, ma trovo dei semi e... quella e'... erba?" +name = "Erboristeria" +icon = "⚘" +description = "Non trovo nessuna pianta, ma trovo dei semi e... quella e'... erba?" [skill.hunter] - name = "Cacciatore" - icon = "☠" - description = "La caccia riguarda il viaggio, non il risultato." +name = "Cacciatore" +icon = "☠" +description = "La caccia riguarda il viaggio, non il risultato." [skill.nether] - name = "Nether" - icon = "₪" - description = "Dalle profondita' del Nether stesso." +name = "Nether" +icon = "₪" +description = "Dalle profondita' del Nether stesso." [skill.pickaxe] - name = "Piccone" - icon = "⛏" - description = "I nani sono i minatori, ma ho imparato un paio di cose col tempo. SONO SVEDESE" +name = "Piccone" +icon = "⛏" +description = "I nani sono i minatori, ma ho imparato un paio di cose col tempo. SONO SVEDESE" [skill.ranged] - name = "A distanza" - icon = "🏹" - description = "La distanza e' la chiave della vittoria, e la chiave della sopravvivenza." +name = "A distanza" +icon = "🏹" +description = "La distanza e' la chiave della vittoria, e la chiave della sopravvivenza." [skill.rift] - name = "Varco" - icon = "❍" - description = "Il Varco e' un'imbracatura caustica, ma tu hai imbrigliato l'imbracatura." +name = "Varco" +icon = "❍" +description = "Il Varco e' un'imbracatura caustica, ma tu hai imbrigliato l'imbracatura." [skill.seaborne] - name = "Marittimo" - icon = "🎣" - description = "Con questa abilita', potrai dominare le meraviglie dell'acqua." +name = "Marittimo" +icon = "🎣" +description = "Con questa abilita', potrai dominare le meraviglie dell'acqua." [skill.stealth] - name = "Furtivita'" - icon = "☯" - description = "L'arte dell'invisibile. Cammina nelle ombre." +name = "Furtivita'" +icon = "☯" +description = "L'arte dell'invisibile. Cammina nelle ombre." [skill.swords] - name = "Spade" - icon = "⚔" - description = "Per il potere della Pietra Grigia!" +name = "Spade" +icon = "⚔" +description = "Per il potere della Pietra Grigia!" [skill.taming] - name = "Addomesticamento" - icon = "♥" - description = "I pappagalli e le api... e tu?" +name = "Addomesticamento" +icon = "♥" +description = "I pappagalli e le api... e tu?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Il sangue scorre nelle vene dell'universo. Costretto dalle tue mani." +name = "TragOul" +icon = "🗡" +description = "Il sangue scorre nelle vene dell'universo. Costretto dalle tue mani." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Carica l'orologio dell'universo, sperimenta il flusso. Rompi l'orologio, diventa l'orologio." +name = "Chronos" +icon = "🕒" +description = "Carica l'orologio dell'universo, sperimenta il flusso. Rompi l'orologio, diventa l'orologio." [skill.unarmed] - name = "Disarmato" - icon = "»" - description = "Senza un'arma non significa senza forza." +name = "Disarmato" +icon = "»" +description = "Senza un'arma non significa senza forza." # agility [agility] [agility.armor_up] - name = "Armatura Crescente" - description = "Ottieni piu' armatura quanto piu' a lungo scatti!" - lore1 = "Armatura massima" - lore2 = "Tempo di accumulo armatura" - lore = ["Armatura massima", "Tempo di accumulo armatura"] +name = "Armatura Crescente" +description = "Ottieni piu' armatura quanto piu' a lungo scatti!" +lore1 = "Armatura massima" +lore2 = "Tempo di accumulo armatura" +lore = ["Armatura massima", "Tempo di accumulo armatura"] [agility.ladder_slide] - name = "Scivolata sulla Scala" - description = "Sali e scendi le scale molto piu' velocemente in entrambe le direzioni." - lore1 = "Moltiplicatore velocita' scala" - lore2 = "Velocita' discesa rapida" - lore = ["Moltiplicatore velocita' scala", "Velocita' discesa rapida"] +name = "Scivolata sulla Scala" +description = "Sali e scendi le scale molto piu' velocemente in entrambe le direzioni." +lore1 = "Moltiplicatore velocita' scala" +lore2 = "Velocita' discesa rapida" +lore = ["Moltiplicatore velocita' scala", "Velocita' discesa rapida"] [agility.super_jump] - name = "Super Salto" - description = "Vantaggio eccezionale in altezza." - lore1 = "Altezza massima di salto" - lore2 = "Accovacciati + Salta per il Super Salto!" - lore = ["Altezza massima di salto", "Accovacciati + Salta per il Super Salto!"] +name = "Super Salto" +description = "Vantaggio eccezionale in altezza." +lore1 = "Altezza massima di salto" +lore2 = "Accovacciati + Salta per il Super Salto!" +lore = ["Altezza massima di salto", "Accovacciati + Salta per il Super Salto!"] [agility.wall_jump] - name = "Salto dal Muro" - description = "Tieni premuto Shift a mezz'aria contro un muro per aggrapparti e saltare!" - lore1 = "Salti massimi" - lore2 = "Altezza di salto" - lore = ["Salti massimi", "Altezza di salto"] +name = "Salto dal Muro" +description = "Tieni premuto Shift a mezz'aria contro un muro per aggrapparti e saltare!" +lore1 = "Salti massimi" +lore2 = "Altezza di salto" +lore = ["Salti massimi", "Altezza di salto"] [agility.wind_up] - name = "Accelerazione" - description = "Diventa piu' veloce quanto piu' a lungo scatti!" - lore1 = "Velocita' massima" - lore2 = "Tempo di accelerazione" - lore = ["Velocita' massima", "Tempo di accelerazione"] +name = "Accelerazione" +description = "Diventa piu' veloce quanto piu' a lungo scatti!" +lore1 = "Velocita' massima" +lore2 = "Tempo di accelerazione" +lore = ["Velocita' massima", "Tempo di accelerazione"] # architect [architect] [architect.elevator] - name = "Ascensore" - description = "Ti permette di costruire un ascensore per teletrasportarti verticalmente in fretta!" - lore1 = "Sblocca la ricetta dell'ascensore: X=LANA, Y=PERLA DI ENDER" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Sblocca la ricetta dell'ascensore: X=LANA, Y=PERLA DI ENDER", "XXX", "XYX", "XXX"] +name = "Ascensore" +description = "Ti permette di costruire un ascensore per teletrasportarti verticalmente in fretta!" +lore1 = "Sblocca la ricetta dell'ascensore: X=LANA, Y=PERLA DI ENDER" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Sblocca la ricetta dell'ascensore: X=LANA, Y=PERLA DI ENDER", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Fondamenta Magiche" - description = "Ti permette di accovacciarti e posizionare delle fondamenta temporanee sotto di te!" - lore1 = "Crea magicamente: " - lore2 = "Blocchi sotto di te!" - lore = ["Crea magicamente: ", "Blocchi sotto di te!"] +name = "Fondamenta Magiche" +description = "Ti permette di accovacciarti e posizionare delle fondamenta temporanee sotto di te!" +lore1 = "Crea magicamente: " +lore2 = "Blocchi sotto di te!" +lore = ["Crea magicamente: ", "Blocchi sotto di te!"] [architect.glass] - name = "Vetro Tocco di Velluto" - description = "Ti permette di evitare la perdita di blocchi di vetro quando li rompi a mani nude!" - lore1 = "Le tue mani ottengono Tocco di Velluto per il Vetro" - lore = ["Le tue mani ottengono Tocco di Velluto per il Vetro"] +name = "Vetro Tocco di Velluto" +description = "Ti permette di evitare la perdita di blocchi di vetro quando li rompi a mani nude!" +lore1 = "Le tue mani ottengono Tocco di Velluto per il Vetro" +lore = ["Le tue mani ottengono Tocco di Velluto per il Vetro"] [architect.wireless_redstone] - name = "Telecomando di Redstone" - description = "Ti permette di usare una torcia di redstone per attivare la redstone a distanza!" - lore1 = "Bersaglio + Torcia di Redstone + Perla di Ender = 1 Telecomando di Redstone" - lore = ["Bersaglio + Torcia di Redstone + Perla di Ender = 1 Telecomando di Redstone"] +name = "Telecomando di Redstone" +description = "Ti permette di usare una torcia di redstone per attivare la redstone a distanza!" +lore1 = "Bersaglio + Torcia di Redstone + Perla di Ender = 1 Telecomando di Redstone" +lore = ["Bersaglio + Torcia di Redstone + Perla di Ender = 1 Telecomando di Redstone"] [architect.placement] - name = "Bacchetta del Costruttore" - description = "Ti permette di posizionare piu' blocchi alla volta. Per attivarla accovacciati e tieni un blocco che corrisponde a quello che stai guardando e posiziona! Potrebbe essere necessario spostarsi un po' per attivare il riquadro!" - lore1 = "Ti servono" - lore2 = "blocchi in mano per posizionare questo" - lore3 = "Bacchetta del Costruttore di Materiali" - lore = ["Ti servono", "blocchi in mano per posizionare questo", "Bacchetta del Costruttore di Materiali"] +name = "Bacchetta del Costruttore" +description = "Ti permette di posizionare piu' blocchi alla volta. Per attivarla accovacciati e tieni un blocco che corrisponde a quello che stai guardando e posiziona! Potrebbe essere necessario spostarsi un po' per attivare il riquadro!" +lore1 = "Ti servono" +lore2 = "blocchi in mano per posizionare questo" +lore3 = "Bacchetta del Costruttore di Materiali" +lore = ["Ti servono", "blocchi in mano per posizionare questo", "Bacchetta del Costruttore di Materiali"] # axe [axe] [axe.chop] - name = "Taglio con l'Ascia" - description = "Abbatti gli alberi facendo clic destro sul tronco alla base!" - lore1 = "Blocchi per taglio" - lore2 = "Tempo di recupero del taglio" - lore3 = "Usura dell'attrezzo" - lore = ["Blocchi per taglio", "Tempo di recupero del taglio", "Usura dell'attrezzo"] +name = "Taglio con l'Ascia" +description = "Abbatti gli alberi facendo clic destro sul tronco alla base!" +lore1 = "Blocchi per taglio" +lore2 = "Tempo di recupero del taglio" +lore3 = "Usura dell'attrezzo" +lore = ["Blocchi per taglio", "Tempo di recupero del taglio", "Usura dell'attrezzo"] [axe.log_swap] - name = "Scambiatore di Tronchi di Lucy" - description = "Cambia il tipo di tronchi in un Banco da Lavoro!" - lore1 = "8 tronchi di qualsiasi tipo + 1 arboscello = 8 tronchi del tipo dell'arboscello" - lore = ["8 tronchi di qualsiasi tipo + 1 arboscello = 8 tronchi del tipo dell'arboscello"] +name = "Scambiatore di Tronchi di Lucy" +description = "Cambia il tipo di tronchi in un Banco da Lavoro!" +lore1 = "8 tronchi di qualsiasi tipo + 1 arboscello = 8 tronchi del tipo dell'arboscello" +lore = ["8 tronchi di qualsiasi tipo + 1 arboscello = 8 tronchi del tipo dell'arboscello"] [axe.drop_to_inventory] - name = "Ascia Raccogli-nell'Inventario" +name = "Ascia Raccogli-nell'Inventario" [axe.ground_smash] - name = "Schianto a Terra con l'Ascia" - description = "Salta, poi accovacciati e colpisci tutti i nemici vicini." - lore1 = "Danno" - lore2 = "Raggio in blocchi" - lore3 = "Forza" - lore4 = "Tempo di recupero dello schianto" - lore = ["Danno", "Raggio in blocchi", "Forza", "Tempo di recupero dello schianto"] +name = "Schianto a Terra con l'Ascia" +description = "Salta, poi accovacciati e colpisci tutti i nemici vicini." +lore1 = "Danno" +lore2 = "Raggio in blocchi" +lore3 = "Forza" +lore4 = "Tempo di recupero dello schianto" +lore = ["Danno", "Raggio in blocchi", "Forza", "Tempo di recupero dello schianto"] [axe.leaf_miner] - name = "Sfogliatore" - description = "Ti permette di rompere grandi quantita' di foglie in una volta!" - lore1 = "Accovacciati e mina le FOGLIE" - lore2 = "raggio di raccolta foglie" - lore3 = "Non otterrai i drop dalle foglie (Prevenzione Exploit)" - lore = ["Accovacciati e mina le FOGLIE", "raggio di raccolta foglie", "Non otterrai i drop dalle foglie (Prevenzione Exploit)"] +name = "Sfogliatore" +description = "Ti permette di rompere grandi quantita' di foglie in una volta!" +lore1 = "Accovacciati e mina le FOGLIE" +lore2 = "raggio di raccolta foglie" +lore3 = "Non otterrai i drop dalle foglie (Prevenzione Exploit)" +lore = ["Accovacciati e mina le FOGLIE", "raggio di raccolta foglie", "Non otterrai i drop dalle foglie (Prevenzione Exploit)"] [axe.wood_miner] - name = "Taglia-legna" - description = "Ti permette di rompere grandi quantita' di legno in una volta!" - lore1 = "Accovacciati e mina LEGNO/TRONCHI (non assi)" - lore2 = "raggio di raccolta legno" - lore3 = "Funziona con Raccogli nell'Inventario" - lore = ["Accovacciati e mina LEGNO/TRONCHI (non assi)", "raggio di raccolta legno", "Funziona con Raccogli nell'Inventario"] +name = "Taglia-legna" +description = "Ti permette di rompere grandi quantita' di legno in una volta!" +lore1 = "Accovacciati e mina LEGNO/TRONCHI (non assi)" +lore2 = "raggio di raccolta legno" +lore3 = "Funziona con Raccogli nell'Inventario" +lore = ["Accovacciati e mina LEGNO/TRONCHI (non assi)", "raggio di raccolta legno", "Funziona con Raccogli nell'Inventario"] # brewing [brewing] [brewing.lingering] - name = "Infuso Persistente" - description = "Le pozioni preparate durano piu' a lungo!" - lore1 = "Durata" - lore2 = "Durata" - lore = ["Durata", "Durata"] +name = "Infuso Persistente" +description = "Le pozioni preparate durano piu' a lungo!" +lore1 = "Durata" +lore2 = "Durata" +lore = ["Durata", "Durata"] [brewing.super_heated] - name = "Infuso Surriscaldato" - description = "I supporti per pozioni funzionano piu' velocemente quanto piu' sono caldi." - lore1 = "Per blocco di fuoco a contatto" - lore2 = "Per blocco di lava a contatto" - lore = ["Per blocco di fuoco a contatto", "Per blocco di lava a contatto"] +name = "Infuso Surriscaldato" +description = "I supporti per pozioni funzionano piu' velocemente quanto piu' sono caldi." +lore1 = "Per blocco di fuoco a contatto" +lore2 = "Per blocco di lava a contatto" +lore = ["Per blocco di fuoco a contatto", "Per blocco di lava a contatto"] [brewing.darkness] - name = "Oscurita' in Bottiglia" - description = "Non so perche' ne hai bisogno, ma eccotela!" - lore1 = "Pozione di Visione Notturna + Cemento Nero = Pozione dell'Oscurita' (30 secondi)" - lore2 = "Nota bene: questo impedisce all'utilizzatore di scattare!" - lore = ["Pozione di Visione Notturna + Cemento Nero = Pozione dell'Oscurita' (30 secondi)", "Nota bene: questo impedisce all'utilizzatore di scattare!"] +name = "Oscurita' in Bottiglia" +description = "Non so perche' ne hai bisogno, ma eccotela!" +lore1 = "Pozione di Visione Notturna + Cemento Nero = Pozione dell'Oscurita' (30 secondi)" +lore2 = "Nota bene: questo impedisce all'utilizzatore di scattare!" +lore = ["Pozione di Visione Notturna + Cemento Nero = Pozione dell'Oscurita' (30 secondi)", "Nota bene: questo impedisce all'utilizzatore di scattare!"] [brewing.haste] - name = "Fretta in Bottiglia" - description = "Quando l'Efficienza non basta" - lore1 = "Pozione di Velocita' + Frammento di Ametista = Pozione della Fretta (60 secondi)" - lore2 = "Pozione di Velocita' + Blocco di Ametista = Pozione della Fretta-2 (30 secondi)" - lore = ["Pozione di Velocita' + Frammento di Ametista = Pozione della Fretta (60 secondi)", "Pozione di Velocita' + Blocco di Ametista = Pozione della Fretta-2 (30 secondi)"] +name = "Fretta in Bottiglia" +description = "Quando l'Efficienza non basta" +lore1 = "Pozione di Velocita' + Frammento di Ametista = Pozione della Fretta (60 secondi)" +lore2 = "Pozione di Velocita' + Blocco di Ametista = Pozione della Fretta-2 (30 secondi)" +lore = ["Pozione di Velocita' + Frammento di Ametista = Pozione della Fretta (60 secondi)", "Pozione di Velocita' + Blocco di Ametista = Pozione della Fretta-2 (30 secondi)"] [brewing.absorption] - name = "Assorbimento in Bottiglia" - description = "Rafforza il corpo!" - lore1 = "Guarigione Istantanea + Quarzo = Pozione dell'Assorbimento (60 secondi)" - lore2 = "Guarigione Istantanea + Blocco di Quarzo = Pozione dell'Assorbimento-2 (30 secondi)" - lore = ["Guarigione Istantanea + Quarzo = Pozione dell'Assorbimento (60 secondi)", "Guarigione Istantanea + Blocco di Quarzo = Pozione dell'Assorbimento-2 (30 secondi)"] +name = "Assorbimento in Bottiglia" +description = "Rafforza il corpo!" +lore1 = "Guarigione Istantanea + Quarzo = Pozione dell'Assorbimento (60 secondi)" +lore2 = "Guarigione Istantanea + Blocco di Quarzo = Pozione dell'Assorbimento-2 (30 secondi)" +lore = ["Guarigione Istantanea + Quarzo = Pozione dell'Assorbimento (60 secondi)", "Guarigione Istantanea + Blocco di Quarzo = Pozione dell'Assorbimento-2 (30 secondi)"] [brewing.fatigue] - name = "Fatica in Bottiglia" - description = "Indebolisci il corpo!" - lore1 = "Pozione di Debolezza + Palla di Slime = Pozione della Fatica (30 secondi)" - lore2 = "Pozione di Debolezza + Blocco di Slime = Pozione della Fatica-2 (15 secondi)" - lore = ["Pozione di Debolezza + Palla di Slime = Pozione della Fatica (30 secondi)", "Pozione di Debolezza + Blocco di Slime = Pozione della Fatica-2 (15 secondi)"] +name = "Fatica in Bottiglia" +description = "Indebolisci il corpo!" +lore1 = "Pozione di Debolezza + Palla di Slime = Pozione della Fatica (30 secondi)" +lore2 = "Pozione di Debolezza + Blocco di Slime = Pozione della Fatica-2 (15 secondi)" +lore = ["Pozione di Debolezza + Palla di Slime = Pozione della Fatica (30 secondi)", "Pozione di Debolezza + Blocco di Slime = Pozione della Fatica-2 (15 secondi)"] [brewing.hunger] - name = "Fame in Bottiglia" - description = "Dai da mangiare all'insaziabile!" - lore1 = "Pozione Sconclusionata + Carne Marcia = Pozione della Fame (30 secondi)" - lore2 = "Pozione di Debolezza + Carne Marcia = Pozione della Fame-3 (15 secondi)" - lore = ["Pozione Sconclusionata + Carne Marcia = Pozione della Fame (30 secondi)", "Pozione di Debolezza + Carne Marcia = Pozione della Fame-3 (15 secondi)"] +name = "Fame in Bottiglia" +description = "Dai da mangiare all'insaziabile!" +lore1 = "Pozione Sconclusionata + Carne Marcia = Pozione della Fame (30 secondi)" +lore2 = "Pozione di Debolezza + Carne Marcia = Pozione della Fame-3 (15 secondi)" +lore = ["Pozione Sconclusionata + Carne Marcia = Pozione della Fame (30 secondi)", "Pozione di Debolezza + Carne Marcia = Pozione della Fame-3 (15 secondi)"] [brewing.nausea] - name = "Nausea in Bottiglia" - description = "Mi fai venire il voltastomaco!" - lore1 = "Pozione Sconclusionata + Fungo Marrone = Pozione della Nausea (16 secondi)" - lore2 = "Pozione Sconclusionata + Fungo Cremisi = Pozione della Nausea-2 (8 secondi)" - lore = ["Pozione Sconclusionata + Fungo Marrone = Pozione della Nausea (16 secondi)", "Pozione Sconclusionata + Fungo Cremisi = Pozione della Nausea-2 (8 secondi)"] +name = "Nausea in Bottiglia" +description = "Mi fai venire il voltastomaco!" +lore1 = "Pozione Sconclusionata + Fungo Marrone = Pozione della Nausea (16 secondi)" +lore2 = "Pozione Sconclusionata + Fungo Cremisi = Pozione della Nausea-2 (8 secondi)" +lore = ["Pozione Sconclusionata + Fungo Marrone = Pozione della Nausea (16 secondi)", "Pozione Sconclusionata + Fungo Cremisi = Pozione della Nausea-2 (8 secondi)"] [brewing.blindness] - name = "Cecita' in Bottiglia" - description = "Sei una persona orribile..." - lore1 = "Pozione Sconclusionata + Sacca d'Inchiostro = Pozione della Cecita' (30 secondi)" - lore2 = "Pozione Sconclusionata + Sacca d'Inchiostro Luminosa = Pozione della Cecita'-2 (15 secondi)" - lore = ["Pozione Sconclusionata + Sacca d'Inchiostro = Pozione della Cecita' (30 secondi)", "Pozione Sconclusionata + Sacca d'Inchiostro Luminosa = Pozione della Cecita'-2 (15 secondi)"] +name = "Cecita' in Bottiglia" +description = "Sei una persona orribile..." +lore1 = "Pozione Sconclusionata + Sacca d'Inchiostro = Pozione della Cecita' (30 secondi)" +lore2 = "Pozione Sconclusionata + Sacca d'Inchiostro Luminosa = Pozione della Cecita'-2 (15 secondi)" +lore = ["Pozione Sconclusionata + Sacca d'Inchiostro = Pozione della Cecita' (30 secondi)", "Pozione Sconclusionata + Sacca d'Inchiostro Luminosa = Pozione della Cecita'-2 (15 secondi)"] [brewing.resistance] - name = "Resistenza in Bottiglia" - description = "Fortificazione al suo massimo!" - lore1 = "Pozione Sconclusionata + Lingotto di Ferro = Pozione della Resistenza (60 secondi)" - lore2 = "Pozione Sconclusionata + Blocco di Ferro = Pozione della Resistenza-2 (30 secondi)" - lore = ["Pozione Sconclusionata + Lingotto di Ferro = Pozione della Resistenza (60 secondi)", "Pozione Sconclusionata + Blocco di Ferro = Pozione della Resistenza-2 (30 secondi)"] +name = "Resistenza in Bottiglia" +description = "Fortificazione al suo massimo!" +lore1 = "Pozione Sconclusionata + Lingotto di Ferro = Pozione della Resistenza (60 secondi)" +lore2 = "Pozione Sconclusionata + Blocco di Ferro = Pozione della Resistenza-2 (30 secondi)" +lore = ["Pozione Sconclusionata + Lingotto di Ferro = Pozione della Resistenza (60 secondi)", "Pozione Sconclusionata + Blocco di Ferro = Pozione della Resistenza-2 (30 secondi)"] [brewing.health_boost] - name = "Vita in Bottiglia" - description = "Quando la salute massima non basta..." - lore1 = "Pozione di Guarigione Istantanea + Mela d'Oro = Pozione di Salute Potenziata (120 secondi)" - lore2 = "Pozione di Guarigione Istantanea + Mela d'Oro Incantata = Pozione di Salute Potenziata-2 (120 secondi)" - lore = ["Pozione di Guarigione Istantanea + Mela d'Oro = Pozione di Salute Potenziata (120 secondi)", "Pozione di Guarigione Istantanea + Mela d'Oro Incantata = Pozione di Salute Potenziata-2 (120 secondi)"] +name = "Vita in Bottiglia" +description = "Quando la salute massima non basta..." +lore1 = "Pozione di Guarigione Istantanea + Mela d'Oro = Pozione di Salute Potenziata (120 secondi)" +lore2 = "Pozione di Guarigione Istantanea + Mela d'Oro Incantata = Pozione di Salute Potenziata-2 (120 secondi)" +lore = ["Pozione di Guarigione Istantanea + Mela d'Oro = Pozione di Salute Potenziata (120 secondi)", "Pozione di Guarigione Istantanea + Mela d'Oro Incantata = Pozione di Salute Potenziata-2 (120 secondi)"] [brewing.decay] - name = "Decadimento in Bottiglia" - description = "Chi l'avrebbe mai detto che il Detritus sarebbe stato cosi' utile?" - lore1 = "Pozione di Debolezza + Patata Velenosa = Pozione dell'Avvizzimento (16 secondi)" - lore2 = "Pozione di Debolezza + Radici Cremisi = Pozione dell'Avvizzimento-2 (8 secondi)" - lore = ["Pozione di Debolezza + Patata Velenosa = Pozione dell'Avvizzimento (16 secondi)", "Pozione di Debolezza + Radici Cremisi = Pozione dell'Avvizzimento-2 (8 secondi)"] +name = "Decadimento in Bottiglia" +description = "Chi l'avrebbe mai detto che il Detritus sarebbe stato cosi' utile?" +lore1 = "Pozione di Debolezza + Patata Velenosa = Pozione dell'Avvizzimento (16 secondi)" +lore2 = "Pozione di Debolezza + Radici Cremisi = Pozione dell'Avvizzimento-2 (8 secondi)" +lore = ["Pozione di Debolezza + Patata Velenosa = Pozione dell'Avvizzimento (16 secondi)", "Pozione di Debolezza + Radici Cremisi = Pozione dell'Avvizzimento-2 (8 secondi)"] [brewing.saturation] - name = "Saturazione in Bottiglia" - description = "Sai... non ho nemmeno fame..." - lore1 = "Pozione di Rigenerazione + Patata al Forno = Pozione della Saturazione" - lore2 = "Pozione di Rigenerazione + Balla di Fieno = Pozione della Saturazione-2" - lore = ["Pozione di Rigenerazione + Patata al Forno = Pozione della Saturazione", "Pozione di Rigenerazione + Balla di Fieno = Pozione della Saturazione-2"] +name = "Saturazione in Bottiglia" +description = "Sai... non ho nemmeno fame..." +lore1 = "Pozione di Rigenerazione + Patata al Forno = Pozione della Saturazione" +lore2 = "Pozione di Rigenerazione + Balla di Fieno = Pozione della Saturazione-2" +lore = ["Pozione di Rigenerazione + Patata al Forno = Pozione della Saturazione", "Pozione di Rigenerazione + Balla di Fieno = Pozione della Saturazione-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Catene di Mefistofele" - description = "Ti permette di creare armature di maglia" - lore1 = "La ricetta e' la stessa di qualsiasi altra armatura, ma con pepite di ferro" - lore = ["La ricetta e' la stessa di qualsiasi altra armatura, ma con pepite di ferro"] +name = "Catene di Mefistofele" +description = "Ti permette di creare armature di maglia" +lore1 = "La ricetta e' la stessa di qualsiasi altra armatura, ma con pepite di ferro" +lore = ["La ricetta e' la stessa di qualsiasi altra armatura, ma con pepite di ferro"] [blocking.horse_armorer] - name = "Armatura per Cavalli Artigianale" - description = "Ti permette di creare armature per cavalli" - lore1 = "Circonda una sella con il materiale che vuoi usare per forgiare l'armatura" - lore = ["Circonda una sella con il materiale che vuoi usare per forgiare l'armatura"] +name = "Armatura per Cavalli Artigianale" +description = "Ti permette di creare armature per cavalli" +lore1 = "Circonda una sella con il materiale che vuoi usare per forgiare l'armatura" +lore = ["Circonda una sella con il materiale che vuoi usare per forgiare l'armatura"] [blocking.saddle_crafter] - name = "Sella Artigianale" - description = "Crea una Sella con la Pelle" - lore1 = "Ricetta: 5 Pelle:" - lore = ["Ricetta: 5 Pelle:"] +name = "Sella Artigianale" +description = "Crea una Sella con la Pelle" +lore1 = "Ricetta: 5 Pelle:" +lore = ["Ricetta: 5 Pelle:"] [blocking.multi_armor] - name = "Multi-Armatura" - description = "Lega le Elitre all'Armatura" - lore1 = "Un'abilita' straordinaria per viaggiare." - lore2 = "Unisci e cambia dinamicamente Armatura/Elitre al volo!" - lore3 = "Per unire, Shift+Clic su un oggetto sopra un altro nel tuo inventario." - lore4 = "Per separare l'Armatura, Lascia cadere accovacciandoti e si smontera'." - lore5 = "Se la tua MultiArmatura viene distrutta, perderai tutti gli oggetti contenuti." - lore6 = "Non ho bisogno di armature, mi deludono..." - lore = ["Un'abilita' straordinaria per viaggiare.", "Unisci e cambia dinamicamente Armatura/Elitre al volo!", "Per unire, Shift+Clic su un oggetto sopra un altro nel tuo inventario.", "Per separare l'Armatura, Lascia cadere accovacciandoti e si smontera'.", "Se la tua MultiArmatura viene distrutta, perderai tutti gli oggetti contenuti.", "Non ho bisogno di armature, mi deludono..."] +name = "Multi-Armatura" +description = "Lega le Elitre all'Armatura" +lore1 = "Un'abilita' straordinaria per viaggiare." +lore2 = "Unisci e cambia dinamicamente Armatura/Elitre al volo!" +lore3 = "Per unire, Shift+Clic su un oggetto sopra un altro nel tuo inventario." +lore4 = "Per separare l'Armatura, Lascia cadere accovacciandoti e si smontera'." +lore5 = "Se la tua MultiArmatura viene distrutta, perderai tutti gli oggetti contenuti." +lore6 = "Non ho bisogno di armature, mi deludono..." +lore = ["Un'abilita' straordinaria per viaggiare.", "Unisci e cambia dinamicamente Armatura/Elitre al volo!", "Per unire, Shift+Clic su un oggetto sopra un altro nel tuo inventario.", "Per separare l'Armatura, Lascia cadere accovacciandoti e si smontera'.", "Se la tua MultiArmatura viene distrutta, perderai tutti gli oggetti contenuti.", "Non ho bisogno di armature, mi deludono..."] # crafting [crafting] [crafting.deconstruction] - name = "Decostruzione" - description = "Decostruisci blocchi e oggetti nei loro componenti base recuperabili!" - lore1 = "Lascia cadere un qualsiasi oggetto a terra." - lore2 = "Poi, accovacciati e fai Clic destro con le Cesoie" - lore = ["Lascia cadere un qualsiasi oggetto a terra.", "Poi, accovacciati e fai Clic destro con le Cesoie"] +name = "Decostruzione" +description = "Decostruisci blocchi e oggetti nei loro componenti base recuperabili!" +lore1 = "Lascia cadere un qualsiasi oggetto a terra." +lore2 = "Poi, accovacciati e fai Clic destro con le Cesoie" +lore = ["Lascia cadere un qualsiasi oggetto a terra.", "Poi, accovacciati e fai Clic destro con le Cesoie"] [crafting.xp] - name = "XP Artigianato" - description = "Guadagna XP passivi quando crei oggetti" - lore1 = "Guadagna XP quando crei oggetti" - lore = ["Guadagna XP quando crei oggetti"] +name = "XP Artigianato" +description = "Guadagna XP passivi quando crei oggetti" +lore1 = "Guadagna XP quando crei oggetti" +lore = ["Guadagna XP quando crei oggetti"] [crafting.reconstruction] - name = "Ricostruzione Minerali" - description = "Ricrea i minerali dai loro componenti base!" - lore1 = "8 dei drop e 1 ospite = 1 minerale (senza forma)" - lore2 = "I drop devono essere fusi (se applicabile)" - lore3 = "Esclusi: Scarti, Quarzo e Smeraldi ecc..." - lore4 = "Ospite = Involucro, cioe': Pietra, Netherrack, Ardesia Abissale" - lore = ["8 dei drop e 1 ospite = 1 minerale (senza forma)", "I drop devono essere fusi (se applicabile)", "Esclusi: Scarti, Quarzo e Smeraldi ecc...", "Ospite = Involucro, cioe': Pietra, Netherrack, Ardesia Abissale"] +name = "Ricostruzione Minerali" +description = "Ricrea i minerali dai loro componenti base!" +lore1 = "8 dei drop e 1 ospite = 1 minerale (senza forma)" +lore2 = "I drop devono essere fusi (se applicabile)" +lore3 = "Esclusi: Scarti, Quarzo e Smeraldi ecc..." +lore4 = "Ospite = Involucro, cioe': Pietra, Netherrack, Ardesia Abissale" +lore = ["8 dei drop e 1 ospite = 1 minerale (senza forma)", "I drop devono essere fusi (se applicabile)", "Esclusi: Scarti, Quarzo e Smeraldi ecc...", "Ospite = Involucro, cioe': Pietra, Netherrack, Ardesia Abissale"] [crafting.leather] - name = "Pelle Artigianale" - description = "Crea Pelle dalla Carne Marcia" - lore1 = "Basta gettarla (carne marcia) sul fuoco da campo!" - lore = ["Basta gettarla (carne marcia) sul fuoco da campo!"] +name = "Pelle Artigianale" +description = "Crea Pelle dalla Carne Marcia" +lore1 = "Basta gettarla (carne marcia) sul fuoco da campo!" +lore = ["Basta gettarla (carne marcia) sul fuoco da campo!"] [crafting.backpacks] - name = "Gli Zaini di un Boutilier!" - description = "Questo porta il Fagotto di Mojang nel gioco!" - lore1 = "Devi essere in Sopravvivenza per usarlo" - lore2 = "XLX : Pelle, Guinzaglio, Pelle" - lore3 = "XSX : Pelle, Barile, Pelle" - lore4 = "XCX : Pelle, Baule, Pelle" - lore = ["Devi essere in Sopravvivenza per usarlo", "XLX : Pelle, Guinzaglio, Pelle", "XSX : Pelle, Barile, Pelle", "XCX : Pelle, Baule, Pelle"] +name = "Gli Zaini di un Boutilier!" +description = "Questo porta il Fagotto di Mojang nel gioco!" +lore1 = "Devi essere in Sopravvivenza per usarlo" +lore2 = "XLX : Pelle, Guinzaglio, Pelle" +lore3 = "XSX : Pelle, Barile, Pelle" +lore4 = "XCX : Pelle, Baule, Pelle" +lore = ["Devi essere in Sopravvivenza per usarlo", "XLX : Pelle, Guinzaglio, Pelle", "XSX : Pelle, Barile, Pelle", "XCX : Pelle, Baule, Pelle"] [crafting.stations] - name = "Tavoli Portatili!" - description = "Usa un tavolo nel palmo della tua mano!" - lore2 = "TUTTI GLI OGGETTI DIMENTICATI NEL TAVOLO ALLA CHIUSURA SONO PERSI PER SEMPRE!" - lore3 = "Tavoli validi: Incudine, Banco da Lavoro, Mola, Cartografia, Tagliapietre, Telaio" - lore = ["TUTTI GLI OGGETTI DIMENTICATI NEL TAVOLO ALLA CHIUSURA SONO PERSI PER SEMPRE!", "Tavoli validi: Incudine, Banco da Lavoro, Mola, Cartografia, Tagliapietre, Telaio"] +name = "Tavoli Portatili!" +description = "Usa un tavolo nel palmo della tua mano!" +lore2 = "TUTTI GLI OGGETTI DIMENTICATI NEL TAVOLO ALLA CHIUSURA SONO PERSI PER SEMPRE!" +lore3 = "Tavoli validi: Incudine, Banco da Lavoro, Mola, Cartografia, Tagliapietre, Telaio" +lore = ["TUTTI GLI OGGETTI DIMENTICATI NEL TAVOLO ALLA CHIUSURA SONO PERSI PER SEMPRE!", "Tavoli validi: Incudine, Banco da Lavoro, Mola, Cartografia, Tagliapietre, Telaio"] [crafting.skulls] - name = "Teschi Artigianali!" - description = "Usando dei materiali puoi creare teschi di mob!" - lore1 = "Circonda un Blocco d'Ossa con i seguenti per ottenere un teschio:" - lore2 = "Zombie: Carne Marcia" - lore3 = "Scheletro: Osso" - lore4 = "Creeper: Polvere da Sparo" - lore5 = "Wither: Mattone del Nether" - lore6 = "Drago: Soffio del Drago" - lore = ["Circonda un Blocco d'Ossa con i seguenti per ottenere un teschio:", "Zombie: Carne Marcia", "Scheletro: Osso", "Creeper: Polvere da Sparo", "Wither: Mattone del Nether", "Drago: Soffio del Drago"] +name = "Teschi Artigianali!" +description = "Usando dei materiali puoi creare teschi di mob!" +lore1 = "Circonda un Blocco d'Ossa con i seguenti per ottenere un teschio:" +lore2 = "Zombie: Carne Marcia" +lore3 = "Scheletro: Osso" +lore4 = "Creeper: Polvere da Sparo" +lore5 = "Wither: Mattone del Nether" +lore6 = "Drago: Soffio del Drago" +lore = ["Circonda un Blocco d'Ossa con i seguenti per ottenere un teschio:", "Zombie: Carne Marcia", "Scheletro: Osso", "Creeper: Polvere da Sparo", "Wither: Mattone del Nether", "Drago: Soffio del Drago"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Tempo in Bottiglia" - description = "Porta con te una bottiglia temporale che accumula tempo e spendilo per accelerare blocchi temporizzati, coltivazioni e creature invecchiabili come i cuccioli. Ricetta (senza forma): Pozione di Rapidita' + Orologio + Ampolla di Vetro." - lore1 = "Secondi accumulati per tick" - lore2 = "Accelerazione temporale per secondo accumulato" - lore3 = "Ricetta (senza forma): Pozione di Rapidita' + Orologio + Ampolla di Vetro" - lore = ["Secondi accumulati per tick", "Accelerazione temporale per secondo accumulato", "Ricetta (senza forma): Pozione di Rapidita' + Orologio + Ampolla di Vetro"] +name = "Tempo in Bottiglia" +description = "Porta con te una bottiglia temporale che accumula tempo e spendilo per accelerare blocchi temporizzati, coltivazioni e creature invecchiabili come i cuccioli. Ricetta (senza forma): Pozione di Rapidita' + Orologio + Ampolla di Vetro." +lore1 = "Secondi accumulati per tick" +lore2 = "Accelerazione temporale per secondo accumulato" +lore3 = "Ricetta (senza forma): Pozione di Rapidita' + Orologio + Ampolla di Vetro" +lore = ["Secondi accumulati per tick", "Accelerazione temporale per secondo accumulato", "Ricetta (senza forma): Pozione di Rapidita' + Orologio + Ampolla di Vetro"] [chronos.aberrant_touch] - name = "Tocco Aberrante" - description = "Gli attacchi in mischia applicano lentezza cumulativa al costo della fame, con limiti PvP rigorosi, e immobilizzano i bersagli a 5 accumuli." - lore1 = "Gli attacchi in mischia applicano lentezza cumulativa" - lore2 = "Durata massima lentezza PvE" - lore3 = "Amplificatore massimo lentezza PvP" - lore = ["Gli attacchi in mischia applicano lentezza cumulativa", "Durata massima lentezza PvE", "Amplificatore massimo lentezza PvP"] +name = "Tocco Aberrante" +description = "Gli attacchi in mischia applicano lentezza cumulativa al costo della fame, con limiti PvP rigorosi, e immobilizzano i bersagli a 5 accumuli." +lore1 = "Gli attacchi in mischia applicano lentezza cumulativa" +lore2 = "Durata massima lentezza PvE" +lore3 = "Amplificatore massimo lentezza PvP" +lore = ["Gli attacchi in mischia applicano lentezza cumulativa", "Durata massima lentezza PvE", "Amplificatore massimo lentezza PvP"] [chronos.instant_recall] - name = "Richiamo Istantaneo" - description = "Fai clic sinistro o destro con un orologio in mano per riavvolgere a un'istantanea recente con salute e fame ripristinate." - lore1 = "Durata del riavvolgimento" - lore2 = "Tempo di recupero" - lore3 = "Nessun ripristino dell'inventario" - lore = ["Durata del riavvolgimento", "Tempo di recupero", "Nessun ripristino dell'inventario"] +name = "Richiamo Istantaneo" +description = "Fai clic sinistro o destro con un orologio in mano per riavvolgere a un'istantanea recente con salute e fame ripristinate." +lore1 = "Durata del riavvolgimento" +lore2 = "Tempo di recupero" +lore3 = "Nessun ripristino dell'inventario" +lore = ["Durata del riavvolgimento", "Tempo di recupero", "Nessun ripristino dell'inventario"] [chronos.time_bomb] - name = "Bomba Temporale" - description = "Lancia una bomba crono forgiata che crea un campo temporale, rallenta le entita' e congela i proiettili." - lore1 = "Raggio del campo temporale" - lore2 = "Durata del campo temporale" - lore3 = "Tempo di recupero della bomba" - lore4 = "Ricetta (senza forma): Orologio + Palla di Neve + Diamante + Sabbia" - lore = ["Raggio del campo temporale", "Durata del campo temporale", "Tempo di recupero della bomba", "Ricetta (senza forma): Orologio + Palla di Neve + Diamante + Sabbia"] +name = "Bomba Temporale" +description = "Lancia una bomba crono forgiata che crea un campo temporale, rallenta le entita' e congela i proiettili." +lore1 = "Raggio del campo temporale" +lore2 = "Durata del campo temporale" +lore3 = "Tempo di recupero della bomba" +lore4 = "Ricetta (senza forma): Orologio + Palla di Neve + Diamante + Sabbia" +lore = ["Raggio del campo temporale", "Durata del campo temporale", "Tempo di recupero della bomba", "Ricetta (senza forma): Orologio + Palla di Neve + Diamante + Sabbia"] # discovery [discovery] [discovery.armor] - name = "Armatura del Mondo" - description = "Armatura passiva basata sulla durezza dei blocchi circostanti." - lore1 = "Armatura passiva" - lore2 = "Basata sulla durezza dei blocchi vicini" - lore3 = "Forza dell'armatura:" - lore = ["Armatura passiva", "Basata sulla durezza dei blocchi vicini", "Forza dell'armatura:"] +name = "Armatura del Mondo" +description = "Armatura passiva basata sulla durezza dei blocchi circostanti." +lore1 = "Armatura passiva" +lore2 = "Basata sulla durezza dei blocchi vicini" +lore3 = "Forza dell'armatura:" +lore = ["Armatura passiva", "Basata sulla durezza dei blocchi vicini", "Forza dell'armatura:"] [discovery.unity] - name = "Unita' Sperimentale" - description = "Raccogliere sfere di esperienza aggiunge XP ad abilita' casuali." - lore1 = "XP " - lore2 = "Per sfera" - lore = ["XP ", "Per sfera"] +name = "Unita' Sperimentale" +description = "Raccogliere sfere di esperienza aggiunge XP ad abilita' casuali." +lore1 = "XP " +lore2 = "Per sfera" +lore = ["XP ", "Per sfera"] [discovery.resist] - name = "Resistenza Sperimentale" - description = "Consuma esperienza per mitigare i danni solo quando un colpo ti farebbe scendere sotto i 5 cuori o ucciderti." - lore0 = "Si attiva solo a salute critica (<= 5 cuori) una volta ogni 15 secondi" - lore1 = " Danno ridotto" - lore2 = "esperienza drenata" - lore = ["Si attiva solo a salute critica (<= 5 cuori) una volta ogni 15 secondi", " Danno ridotto", "esperienza drenata"] +name = "Resistenza Sperimentale" +description = "Consuma esperienza per mitigare i danni solo quando un colpo ti farebbe scendere sotto i 5 cuori o ucciderti." +lore0 = "Si attiva solo a salute critica (<= 5 cuori) una volta ogni 15 secondi" +lore1 = " Danno ridotto" +lore2 = "esperienza drenata" +lore = ["Si attiva solo a salute critica (<= 5 cuori) una volta ogni 15 secondi", " Danno ridotto", "esperienza drenata"] [discovery.villager] - name = "Attrazione dei Villici" - description = "Ti permette di ottenere scambi migliori con i villici!" - lore1 = "Questo consuma XP per interazione con i villici" - lore2 = "Probabilita' per interazione di consumare XP e migliorare gli scambi" - lore3 = "XP richiesti drenati per interazione" - lore = ["Questo consuma XP per interazione con i villici", "Probabilita' per interazione di consumare XP e migliorare gli scambi", "XP richiesti drenati per interazione"] +name = "Attrazione dei Villici" +description = "Ti permette di ottenere scambi migliori con i villici!" +lore1 = "Questo consuma XP per interazione con i villici" +lore2 = "Probabilita' per interazione di consumare XP e migliorare gli scambi" +lore3 = "XP richiesti drenati per interazione" +lore = ["Questo consuma XP per interazione con i villici", "Probabilita' per interazione di consumare XP e migliorare gli scambi", "XP richiesti drenati per interazione"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Ritorno del Lapislazzuli" - description = "Al costo di 1 livello in piu' di XP, ha una probabilita' di restituirti lapislazzuli gratis" - lore1 = "Per ogni livello, aumenta il costo dell'incantamento di 1, ma puo' restituire fino a 3 Lapislazzuli" - lore = ["Per ogni livello, aumenta il costo dell'incantamento di 1, ma puo' restituire fino a 3 Lapislazzuli"] +name = "Ritorno del Lapislazzuli" +description = "Al costo di 1 livello in piu' di XP, ha una probabilita' di restituirti lapislazzuli gratis" +lore1 = "Per ogni livello, aumenta il costo dell'incantamento di 1, ma puo' restituire fino a 3 Lapislazzuli" +lore = ["Per ogni livello, aumenta il costo dell'incantamento di 1, ma puo' restituire fino a 3 Lapislazzuli"] [enchanting.quick_enchant] - name = "Incantamento Rapido" - description = "Incanta oggetti cliccando i libri degli incantesimi direttamente su di essi." - lore1 = "Livelli massimi combinati" - lore2 = "Non puoi incantare un oggetto con piu' di " - lore3 = "potere" - lore = ["Livelli massimi combinati", "Non puoi incantare un oggetto con piu' di ", "potere"] +name = "Incantamento Rapido" +description = "Incanta oggetti cliccando i libri degli incantesimi direttamente su di essi." +lore1 = "Livelli massimi combinati" +lore2 = "Non puoi incantare un oggetto con piu' di " +lore3 = "potere" +lore = ["Livelli massimi combinati", "Non puoi incantare un oggetto con piu' di ", "potere"] [enchanting.return] - name = "Ritorno di XP" - description = "L'XP di incantamento ti viene restituita quando incanti un oggetto." - lore1 = "L'esperienza spesa ha una probabilita' di essere rimborsata quando incanti un oggetto" - lore2 = "Esperienza per incantamento" - lore = ["L'esperienza spesa ha una probabilita' di essere rimborsata quando incanti un oggetto", "Esperienza per incantamento"] +name = "Ritorno di XP" +description = "L'XP di incantamento ti viene restituita quando incanti un oggetto." +lore1 = "L'esperienza spesa ha una probabilita' di essere rimborsata quando incanti un oggetto" +lore2 = "Esperienza per incantamento" +lore = ["L'esperienza spesa ha una probabilita' di essere rimborsata quando incanti un oggetto", "Esperienza per incantamento"] # excavation [excavation] [excavation.haste] - name = "Scavatore Frettoloso" - description = "Accelera il processo di scavo con la FRETTA!" - lore1 = "Ottieni Fretta durante lo scavo" - lore2 = "x livelli di Fretta quando inizi a minare QUALSIASI blocco." - lore = ["Ottieni Fretta durante lo scavo", "x livelli di Fretta quando inizi a minare QUALSIASI blocco."] +name = "Scavatore Frettoloso" +description = "Accelera il processo di scavo con la FRETTA!" +lore1 = "Ottieni Fretta durante lo scavo" +lore2 = "x livelli di Fretta quando inizi a minare QUALSIASI blocco." +lore = ["Ottieni Fretta durante lo scavo", "x livelli di Fretta quando inizi a minare QUALSIASI blocco."] [excavation.spelunker] - name = "Speleologo Super-Veggente!" - description = "Vedi i minerali con i tuoi occhi, ma attraverso il terreno!" - lore1 = "Minerale nella mano secondaria, Bacche Luminose nella mano principale, e accovacciati!" - lore2 = "Raggio in blocchi: " - lore3 = "Consuma una Bacca Luminosa ad ogni uso" - lore = ["Minerale nella mano secondaria, Bacche Luminose nella mano principale, e accovacciati!", "Raggio in blocchi: ", "Consuma una Bacca Luminosa ad ogni uso"] +name = "Speleologo Super-Veggente!" +description = "Vedi i minerali con i tuoi occhi, ma attraverso il terreno!" +lore1 = "Minerale nella mano secondaria, Bacche Luminose nella mano principale, e accovacciati!" +lore2 = "Raggio in blocchi: " +lore3 = "Consuma una Bacca Luminosa ad ogni uso" +lore = ["Minerale nella mano secondaria, Bacche Luminose nella mano principale, e accovacciati!", "Raggio in blocchi: ", "Consuma una Bacca Luminosa ad ogni uso"] [excavation.drop_to_inventory] - name = "Pala Raccogli-nell'Inventario" +name = "Pala Raccogli-nell'Inventario" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "L'opulento multiuso di design eccessivo di Tackle" - lore1 = "Probabilmente il piu' potente tra i tanti, ti permette di" - lore2 = "unire e cambiare dinamicamente gli attrezzi al volo, in base alle tue esigenze." - lore3 = "Per unire, Shift+Clic su un oggetto sopra un altro nel tuo inventario." - lore4 = "Per separare gli attrezzi, lascia cadere l'oggetto accovacciandoti e si smontera'." - lore5 = "Non puoi rompere gli attrezzi in questo multiuso ma non puoi usare attrezzi rotti" - lore6 = "oggetti unificabili totali." - lore7 = "Potresti usare cinque o sei attrezzi, oppure uno solo!" - lore = ["Probabilmente il piu' potente tra i tanti, ti permette di", "unire e cambiare dinamicamente gli attrezzi al volo, in base alle tue esigenze.", "Per unire, Shift+Clic su un oggetto sopra un altro nel tuo inventario.", "Per separare gli attrezzi, lascia cadere l'oggetto accovacciandoti e si smontera'.", "Non puoi rompere gli attrezzi in questo multiuso ma non puoi usare attrezzi rotti", "oggetti unificabili totali.", "Potresti usare cinque o sei attrezzi, oppure uno solo!"] +name = "OMNI - T.O.O.L." +description = "L'opulento multiuso di design eccessivo di Tackle" +lore1 = "Probabilmente il piu' potente tra i tanti, ti permette di" +lore2 = "unire e cambiare dinamicamente gli attrezzi al volo, in base alle tue esigenze." +lore3 = "Per unire, Shift+Clic su un oggetto sopra un altro nel tuo inventario." +lore4 = "Per separare gli attrezzi, lascia cadere l'oggetto accovacciandoti e si smontera'." +lore5 = "Non puoi rompere gli attrezzi in questo multiuso ma non puoi usare attrezzi rotti" +lore6 = "oggetti unificabili totali." +lore7 = "Potresti usare cinque o sei attrezzi, oppure uno solo!" +lore = ["Probabilmente il piu' potente tra i tanti, ti permette di", "unire e cambiare dinamicamente gli attrezzi al volo, in base alle tue esigenze.", "Per unire, Shift+Clic su un oggetto sopra un altro nel tuo inventario.", "Per separare gli attrezzi, lascia cadere l'oggetto accovacciandoti e si smontera'.", "Non puoi rompere gli attrezzi in questo multiuso ma non puoi usare attrezzi rotti", "oggetti unificabili totali.", "Potresti usare cinque o sei attrezzi, oppure uno solo!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Aura di Crescita" - description = "Fai crescere la natura intorno a te in un'aura" - lore1 = "Raggio in blocchi" - lore2 = "Forza dell'Aura di Crescita" - lore3 = "Costo in cibo" - lore = ["Raggio in blocchi", "Forza dell'Aura di Crescita", "Costo in cibo"] +name = "Aura di Crescita" +description = "Fai crescere la natura intorno a te in un'aura" +lore1 = "Raggio in blocchi" +lore2 = "Forza dell'Aura di Crescita" +lore3 = "Costo in cibo" +lore = ["Raggio in blocchi", "Forza dell'Aura di Crescita", "Costo in cibo"] [herbalism.hippo] - name = "Ippopotamo dell'Erborista" - description = "Consumare cibo ti da' piu' saturazione" - lore1 = "Cibo) punti di saturazione aggiuntivi al consumo" - lore = ["Cibo) punti di saturazione aggiuntivi al consumo"] +name = "Ippopotamo dell'Erborista" +description = "Consumare cibo ti da' piu' saturazione" +lore1 = "Cibo) punti di saturazione aggiuntivi al consumo" +lore = ["Cibo) punti di saturazione aggiuntivi al consumo"] [herbalism.myconid] - name = "Miconide dell'Erborista" - description = "Ti da' la capacita' di creare il Micelio" - lore1 = "Qualsiasi Terra, e un Fungo Marrone e Rosso creeranno il Micelio." - lore = ["Qualsiasi Terra, e un Fungo Marrone e Rosso creeranno il Micelio."] +name = "Miconide dell'Erborista" +description = "Ti da' la capacita' di creare il Micelio" +lore1 = "Qualsiasi Terra, e un Fungo Marrone e Rosso creeranno il Micelio." +lore = ["Qualsiasi Terra, e un Fungo Marrone e Rosso creeranno il Micelio."] [herbalism.terralid] - name = "Terralide dell'Erborista" - description = "Ti da' la capacita' di creare Blocchi d'Erba" - lore1 = "Tre semi, sopra 3 Terra, creeranno 3 Blocchi d'Erba." - lore = ["Tre semi, sopra 3 Terra, creeranno 3 Blocchi d'Erba."] +name = "Terralide dell'Erborista" +description = "Ti da' la capacita' di creare Blocchi d'Erba" +lore1 = "Tre semi, sopra 3 Terra, creeranno 3 Blocchi d'Erba." +lore = ["Tre semi, sopra 3 Terra, creeranno 3 Blocchi d'Erba."] [herbalism.cobweb] - name = "Creatore di Ragnatele" - description = "Ti da' la capacita' di creare Ragnatele in un Banco da Lavoro" - lore1 = "Nove Fili creeranno una Ragnatela." - lore = ["Nove Fili creeranno una Ragnatela."] +name = "Creatore di Ragnatele" +description = "Ti da' la capacita' di creare Ragnatele in un Banco da Lavoro" +lore1 = "Nove Fili creeranno una Ragnatela." +lore = ["Nove Fili creeranno una Ragnatela."] [herbalism.mushroom_blocks] - name = "Creatore di Funghi" - description = "Ti da' la capacita' di creare Blocchi di Fungo in un Banco da Lavoro" - lore1 = "Quattro Funghi per fare un blocco, o un blocco per fare un gambo." - lore = ["Quattro Funghi per fare un blocco, o un blocco per fare un gambo."] +name = "Creatore di Funghi" +description = "Ti da' la capacita' di creare Blocchi di Fungo in un Banco da Lavoro" +lore1 = "Quattro Funghi per fare un blocco, o un blocco per fare un gambo." +lore = ["Quattro Funghi per fare un blocco, o un blocco per fare un gambo."] [herbalism.drop_to_inventory] - name = "Zappa Raccogli-nell'Inventario" +name = "Zappa Raccogli-nell'Inventario" [herbalism.hungry_shield] - name = "Scudo della Fame" - description = "Subisci danni alla fame prima che alla salute." - lore1 = "Resistenza tramite Fame" - lore = ["Resistenza tramite Fame"] +name = "Scudo della Fame" +description = "Subisci danni alla fame prima che alla salute." +lore1 = "Resistenza tramite Fame" +lore = ["Resistenza tramite Fame"] [herbalism.luck] - name = "Fortuna dell'Erborista" - description = "Quando rompi Erba/Fiori, hai una probabilita' di ottenere un oggetto casuale" - lore0 = "Fiori = Cibo, e Erba = Semi" - lore1 = "Probabilita' di ottenere un oggetto rompendo i Fiori" - lore2 = "Probabilita' di ottenere un oggetto rompendo l'Erba" - lore = ["Fiori = Cibo, e Erba = Semi", "Probabilita' di ottenere un oggetto rompendo i Fiori", "Probabilita' di ottenere un oggetto rompendo l'Erba"] +name = "Fortuna dell'Erborista" +description = "Quando rompi Erba/Fiori, hai una probabilita' di ottenere un oggetto casuale" +lore0 = "Fiori = Cibo, e Erba = Semi" +lore1 = "Probabilita' di ottenere un oggetto rompendo i Fiori" +lore2 = "Probabilita' di ottenere un oggetto rompendo l'Erba" +lore = ["Fiori = Cibo, e Erba = Semi", "Probabilita' di ottenere un oggetto rompendo i Fiori", "Probabilita' di ottenere un oggetto rompendo l'Erba"] [herbalism.replant] - name = "Raccogli e Ripianta" - description = "Fai clic destro su una coltura con una zappa per raccogliere e ripiantare." - lore1 = "Raggio di reimpianto in blocchi" - lore = ["Raggio di reimpianto in blocchi"] +name = "Raccogli e Ripianta" +description = "Fai clic destro su una coltura con una zappa per raccogliere e ripiantare." +lore1 = "Raggio di reimpianto in blocchi" +lore = ["Raggio di reimpianto in blocchi"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenalina" - description = "Infliggi piu' danni quanto piu' e' bassa la tua salute (Mischia)" - lore1 = "Danno massimo" - lore = ["Danno massimo"] +name = "Adrenalina" +description = "Infliggi piu' danni quanto piu' e' bassa la tua salute (Mischia)" +lore1 = "Danno massimo" +lore = ["Danno massimo"] [hunter.penalty] - name = "" - description = "" - lore1 = "Otterrai accumuli di Veleno se esaurisci la fame" - lore = ["Otterrai accumuli di Veleno se esaurisci la fame"] +name = "" +description = "" +lore1 = "Otterrai accumuli di Veleno se esaurisci la fame" +lore = ["Otterrai accumuli di Veleno se esaurisci la fame"] [hunter.drop_to_inventory] - name = "Oggetti Raccogli-nell'Inventario" - description = "Quando uccidi qualcosa / Rompi un blocco con una spada, i drop vengono teletrasportati nel tuo inventario" - lore1 = "Ogni volta che un oggetto viene lasciato da un mob/blocco che rompi, finisce nel tuo inventario se possibile." - lore = ["Ogni volta che un oggetto viene lasciato da un mob/blocco che rompi, finisce nel tuo inventario se possibile."] +name = "Oggetti Raccogli-nell'Inventario" +description = "Quando uccidi qualcosa / Rompi un blocco con una spada, i drop vengono teletrasportati nel tuo inventario" +lore1 = "Ogni volta che un oggetto viene lasciato da un mob/blocco che rompi, finisce nel tuo inventario se possibile." +lore = ["Ogni volta che un oggetto viene lasciato da un mob/blocco che rompi, finisce nel tuo inventario se possibile."] [hunter.invisibility] - name = "Passo Evanescente" - description = "Quando vieni colpito ottieni invisibilita', al costo della fame" - lore1 = "Ottieni invisibilita' passiva quando vieni colpito" - lore2 = "x accumuli di Invisibilita' per 3 secondi al colpo" - lore3 = "x Fame cumulativa" - lore4 = "Durata e moltiplicatore degli accumuli di fame." - lore5 = "Durata dell'invisibilita'" - lore = ["Ottieni invisibilita' passiva quando vieni colpito", "x accumuli di Invisibilita' per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Durata dell'invisibilita'"] +name = "Passo Evanescente" +description = "Quando vieni colpito ottieni invisibilita', al costo della fame" +lore1 = "Ottieni invisibilita' passiva quando vieni colpito" +lore2 = "x accumuli di Invisibilita' per 3 secondi al colpo" +lore3 = "x Fame cumulativa" +lore4 = "Durata e moltiplicatore degli accumuli di fame." +lore5 = "Durata dell'invisibilita'" +lore = ["Ottieni invisibilita' passiva quando vieni colpito", "x accumuli di Invisibilita' per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Durata dell'invisibilita'"] [hunter.jump_boost] - name = "Altezze del Cacciatore" - description = "Quando vieni colpito ottieni potenziamento del salto, al costo della fame" - lore1 = "Ottieni potenziamento del salto passivo quando vieni colpito" - lore2 = "x accumuli di Potenziamento Salto per 3 secondi al colpo" - lore3 = "x Fame cumulativa" - lore4 = "Durata e moltiplicatore degli accumuli di fame." - lore5 = "Moltiplicatore degli accumuli di Potenziamento Salto, non durata." - lore = ["Ottieni potenziamento del salto passivo quando vieni colpito", "x accumuli di Potenziamento Salto per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Potenziamento Salto, non durata."] +name = "Altezze del Cacciatore" +description = "Quando vieni colpito ottieni potenziamento del salto, al costo della fame" +lore1 = "Ottieni potenziamento del salto passivo quando vieni colpito" +lore2 = "x accumuli di Potenziamento Salto per 3 secondi al colpo" +lore3 = "x Fame cumulativa" +lore4 = "Durata e moltiplicatore degli accumuli di fame." +lore5 = "Moltiplicatore degli accumuli di Potenziamento Salto, non durata." +lore = ["Ottieni potenziamento del salto passivo quando vieni colpito", "x accumuli di Potenziamento Salto per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Potenziamento Salto, non durata."] [hunter.luck] - name = "Fortuna del Cacciatore" - description = "Quando vieni colpito ottieni fortuna, al costo della fame" - lore1 = "Ottieni fortuna passiva quando vieni colpito" - lore2 = "x accumuli di Fortuna per 3 secondi al colpo" - lore3 = "x Fame cumulativa" - lore4 = "Durata e moltiplicatore degli accumuli di fame." - lore5 = "Moltiplicatore degli accumuli di Fortuna, non durata." - lore = ["Ottieni fortuna passiva quando vieni colpito", "x accumuli di Fortuna per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Fortuna, non durata."] +name = "Fortuna del Cacciatore" +description = "Quando vieni colpito ottieni fortuna, al costo della fame" +lore1 = "Ottieni fortuna passiva quando vieni colpito" +lore2 = "x accumuli di Fortuna per 3 secondi al colpo" +lore3 = "x Fame cumulativa" +lore4 = "Durata e moltiplicatore degli accumuli di fame." +lore5 = "Moltiplicatore degli accumuli di Fortuna, non durata." +lore = ["Ottieni fortuna passiva quando vieni colpito", "x accumuli di Fortuna per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Fortuna, non durata."] [hunter.regen] - name = "Rigenerazione del Cacciatore" - description = "Quando vieni colpito ottieni rigenerazione, al costo della fame" - lore1 = "Ottieni rigenerazione passiva quando vieni colpito" - lore2 = "x accumuli di Rigenerazione per 3 secondi al colpo" - lore3 = "x Fame cumulativa" - lore4 = "Durata e moltiplicatore degli accumuli di fame." - lore5 = "Moltiplicatore degli accumuli di Rigenerazione, non durata." - lore = ["Ottieni rigenerazione passiva quando vieni colpito", "x accumuli di Rigenerazione per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Rigenerazione, non durata."] +name = "Rigenerazione del Cacciatore" +description = "Quando vieni colpito ottieni rigenerazione, al costo della fame" +lore1 = "Ottieni rigenerazione passiva quando vieni colpito" +lore2 = "x accumuli di Rigenerazione per 3 secondi al colpo" +lore3 = "x Fame cumulativa" +lore4 = "Durata e moltiplicatore degli accumuli di fame." +lore5 = "Moltiplicatore degli accumuli di Rigenerazione, non durata." +lore = ["Ottieni rigenerazione passiva quando vieni colpito", "x accumuli di Rigenerazione per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Rigenerazione, non durata."] [hunter.resistance] - name = "Resistenza del Cacciatore" - description = "Quando vieni colpito ottieni resistenza, al costo della fame" - lore1 = "Ottieni resistenza passiva quando vieni colpito" - lore2 = "x accumuli di Resistenza per 3 secondi al colpo" - lore3 = "x Fame cumulativa" - lore4 = "Durata e moltiplicatore degli accumuli di fame." - lore5 = "Moltiplicatore degli accumuli di Resistenza, non durata." - lore = ["Ottieni resistenza passiva quando vieni colpito", "x accumuli di Resistenza per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Resistenza, non durata."] +name = "Resistenza del Cacciatore" +description = "Quando vieni colpito ottieni resistenza, al costo della fame" +lore1 = "Ottieni resistenza passiva quando vieni colpito" +lore2 = "x accumuli di Resistenza per 3 secondi al colpo" +lore3 = "x Fame cumulativa" +lore4 = "Durata e moltiplicatore degli accumuli di fame." +lore5 = "Moltiplicatore degli accumuli di Resistenza, non durata." +lore = ["Ottieni resistenza passiva quando vieni colpito", "x accumuli di Resistenza per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Resistenza, non durata."] [hunter.speed] - name = "Velocita' del Cacciatore" - description = "Quando vieni colpito ottieni velocita', al costo della fame" - lore1 = "Ottieni velocita' passiva quando vieni colpito" - lore2 = "x accumuli di Velocita' per 3 secondi al colpo" - lore3 = "x Fame cumulativa" - lore4 = "Durata e moltiplicatore degli accumuli di fame." - lore5 = "Moltiplicatore degli accumuli di Velocita', non durata." - lore = ["Ottieni velocita' passiva quando vieni colpito", "x accumuli di Velocita' per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Velocita', non durata."] +name = "Velocita' del Cacciatore" +description = "Quando vieni colpito ottieni velocita', al costo della fame" +lore1 = "Ottieni velocita' passiva quando vieni colpito" +lore2 = "x accumuli di Velocita' per 3 secondi al colpo" +lore3 = "x Fame cumulativa" +lore4 = "Durata e moltiplicatore degli accumuli di fame." +lore5 = "Moltiplicatore degli accumuli di Velocita', non durata." +lore = ["Ottieni velocita' passiva quando vieni colpito", "x accumuli di Velocita' per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Velocita', non durata."] [hunter.strength] - name = "Forza del Cacciatore" - description = "Quando vieni colpito ottieni forza, al costo della fame" - lore1 = "Ottieni forza passiva quando vieni colpito" - lore2 = "x accumuli di Forza per 3 secondi al colpo" - lore3 = "x Fame cumulativa" - lore4 = "Durata e moltiplicatore degli accumuli di fame." - lore5 = "Moltiplicatore degli accumuli di Forza, non durata." - lore = ["Ottieni forza passiva quando vieni colpito", "x accumuli di Forza per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Forza, non durata."] +name = "Forza del Cacciatore" +description = "Quando vieni colpito ottieni forza, al costo della fame" +lore1 = "Ottieni forza passiva quando vieni colpito" +lore2 = "x accumuli di Forza per 3 secondi al colpo" +lore3 = "x Fame cumulativa" +lore4 = "Durata e moltiplicatore degli accumuli di fame." +lore5 = "Moltiplicatore degli accumuli di Forza, non durata." +lore = ["Ottieni forza passiva quando vieni colpito", "x accumuli di Forza per 3 secondi al colpo", "x Fame cumulativa", "Durata e moltiplicatore degli accumuli di fame.", "Moltiplicatore degli accumuli di Forza, non durata."] # nether [nether] [nether.skull_toss] - name = "Lancio del Teschio di Wither" - description1 = "Scatena il tuo Wither interiore usando la" - description2 = "testa" - description3 = "di qualcuno." - lore1 = "Secondi di recupero tra un lancio e l'altro." - lore2 = "Usando un Teschio di Wither: Lancia un " - lore3 = "Teschio di Wither" - lore4 = "che esplode all'impatto." - lore = ["Secondi di recupero tra un lancio e l'altro.", "Usando un Teschio di Wither: Lancia un ", "Teschio di Wither", "che esplode all'impatto."] +name = "Lancio del Teschio di Wither" +description1 = "Scatena il tuo Wither interiore usando la" +description2 = "testa" +description3 = "di qualcuno." +lore1 = "Secondi di recupero tra un lancio e l'altro." +lore2 = "Usando un Teschio di Wither: Lancia un " +lore3 = "Teschio di Wither" +lore4 = "che esplode all'impatto." +lore = ["Secondi di recupero tra un lancio e l'altro.", "Usando un Teschio di Wither: Lancia un ", "Teschio di Wither", "che esplode all'impatto."] [nether.wither_resist] - name = "Resistenza all'Avvizzimento" - description = "Resisti all'avvizzimento grazie al potere della Netherite." - lore1 = "probabilita' di annullare l'avvizzimento (per pezzo)." - lore2 = "Passiva: Indossare armatura di Netherite ha una probabilita' di annullare " - lore3 = "l'avvizzimento." - lore = ["probabilita' di annullare l'avvizzimento (per pezzo).", "Passiva: Indossare armatura di Netherite ha una probabilita' di annullare ", "l'avvizzimento."] +name = "Resistenza all'Avvizzimento" +description = "Resisti all'avvizzimento grazie al potere della Netherite." +lore1 = "probabilita' di annullare l'avvizzimento (per pezzo)." +lore2 = "Passiva: Indossare armatura di Netherite ha una probabilita' di annullare " +lore3 = "l'avvizzimento." +lore = ["probabilita' di annullare l'avvizzimento (per pezzo).", "Passiva: Indossare armatura di Netherite ha una probabilita' di annullare ", "l'avvizzimento."] [nether.fire_resist] - name = "Resistenza al Fuoco" - description = "Resisti al fuoco indurendo la tua pelle." - lore1 = "probabilita' di annullare l'effetto di bruciatura!" - lore = ["probabilita' di annullare l'effetto di bruciatura!"] +name = "Resistenza al Fuoco" +description = "Resisti al fuoco indurendo la tua pelle." +lore1 = "probabilita' di annullare l'effetto di bruciatura!" +lore = ["probabilita' di annullare l'effetto di bruciatura!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Fusione Automatica" - description = "Ti permette di fondere automaticamente i minerali Vanilla estratti" - lore1 = "I minerali che possono essere fusi vengono fusi automaticamente" - lore2 = "% di probabilita' per un extra" - lore = ["I minerali che possono essere fusi vengono fusi automaticamente", "% di probabilita' per un extra"] +name = "Fusione Automatica" +description = "Ti permette di fondere automaticamente i minerali Vanilla estratti" +lore1 = "I minerali che possono essere fusi vengono fusi automaticamente" +lore2 = "% di probabilita' per un extra" +lore = ["I minerali che possono essere fusi vengono fusi automaticamente", "% di probabilita' per un extra"] [pickaxe.chisel] - name = "Scalpello per Minerali" - description = "Fai clic destro sui minerali per scalpellarli ed estrarne di piu', a un grave costo di durabilita'." - lore1 = "Probabilita' di drop" - lore2 = "Usura dell'attrezzo" - lore = ["Probabilita' di drop", "Usura dell'attrezzo"] +name = "Scalpello per Minerali" +description = "Fai clic destro sui minerali per scalpellarli ed estrarne di piu', a un grave costo di durabilita'." +lore1 = "Probabilita' di drop" +lore2 = "Usura dell'attrezzo" +lore = ["Probabilita' di drop", "Usura dell'attrezzo"] [pickaxe.drop_to_inventory] - name = "Piccone Raccogli-nell'Inventario" - description = "Quando rompi un blocco, l'oggetto viene teletrasportato nel tuo inventario" - lore1 = "Ogni volta che un oggetto viene lasciato da un blocco che rompi, finisce nel tuo inventario se possibile." - lore = ["Ogni volta che un oggetto viene lasciato da un blocco che rompi, finisce nel tuo inventario se possibile."] +name = "Piccone Raccogli-nell'Inventario" +description = "Quando rompi un blocco, l'oggetto viene teletrasportato nel tuo inventario" +lore1 = "Ogni volta che un oggetto viene lasciato da un blocco che rompi, finisce nel tuo inventario se possibile." +lore = ["Ogni volta che un oggetto viene lasciato da un blocco che rompi, finisce nel tuo inventario se possibile."] [pickaxe.silk_spawner] - name = "Piccone Tocco di Velluto Spawner" - description = "Fa cadere gli Spawner quando vengono rotti" - lore1 = "Rende gli Spawner rompibili con Tocco di Velluto." - lore2 = "Rende gli Spawner rompibili mentre sei accovacciato." - lore = ["Rende gli Spawner rompibili con Tocco di Velluto.", "Rende gli Spawner rompibili mentre sei accovacciato."] +name = "Piccone Tocco di Velluto Spawner" +description = "Fa cadere gli Spawner quando vengono rotti" +lore1 = "Rende gli Spawner rompibili con Tocco di Velluto." +lore2 = "Rende gli Spawner rompibili mentre sei accovacciato." +lore = ["Rende gli Spawner rompibili con Tocco di Velluto.", "Rende gli Spawner rompibili mentre sei accovacciato."] [pickaxe.vein_miner] - name = "Minatore di Vene" - description = "Ti permette di rompere blocchi in una vena/gruppo di minerali Vanilla" - lore1 = "Accovacciati e mina i MINERALI" - lore2 = "raggio di estrazione a vena" - lore3 = "Questa abilita' NON raggruppa tutti i drop!" - lore = ["Accovacciati e mina i MINERALI", "raggio di estrazione a vena", "Questa abilita' NON raggruppa tutti i drop!"] +name = "Minatore di Vene" +description = "Ti permette di rompere blocchi in una vena/gruppo di minerali Vanilla" +lore1 = "Accovacciati e mina i MINERALI" +lore2 = "raggio di estrazione a vena" +lore3 = "Questa abilita' NON raggruppa tutti i drop!" +lore = ["Accovacciati e mina i MINERALI", "raggio di estrazione a vena", "Questa abilita' NON raggruppa tutti i drop!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Recupero Frecce" - description = "Recupera le frecce dopo aver ucciso un nemico." - lore1 = "Probabilita' di recuperare le frecce al colpo/uccisione" - lore2 = "Probabilita': " - lore = ["Probabilita' di recuperare le frecce al colpo/uccisione", "Probabilita': "] +name = "Recupero Frecce" +description = "Recupera le frecce dopo aver ucciso un nemico." +lore1 = "Probabilita' di recuperare le frecce al colpo/uccisione" +lore2 = "Probabilita': " +lore = ["Probabilita' di recuperare le frecce al colpo/uccisione", "Probabilita': "] [ranged.web_shot] - name = "Trappola di Ragnatele" - description = "Avvolgi ragnatele attorno al bersaglio quando lo colpisci!" - lore1 = "8 Ragnatele attorno a una Palla di Neve, e lancia!" - lore2 = "secondi di gabbia, circa." - lore = ["8 Ragnatele attorno a una Palla di Neve, e lancia!", "secondi di gabbia, circa."] +name = "Trappola di Ragnatele" +description = "Avvolgi ragnatele attorno al bersaglio quando lo colpisci!" +lore1 = "8 Ragnatele attorno a una Palla di Neve, e lancia!" +lore2 = "secondi di gabbia, circa." +lore = ["8 Ragnatele attorno a una Palla di Neve, e lancia!", "secondi di gabbia, circa."] [ranged.force_shot] - name = "Colpo di Forza" - description = "Spara proiettili piu' lontano, piu' velocemente!" - advancementname = "Tiro Lungo" - advancementlore = "Colpisci un bersaglio a oltre 30 blocchi di distanza!" - lore1 = "Velocita' del proiettile" - lore = ["Velocita' del proiettile"] +name = "Colpo di Forza" +description = "Spara proiettili piu' lontano, piu' velocemente!" +advancementname = "Tiro Lungo" +advancementlore = "Colpisci un bersaglio a oltre 30 blocchi di distanza!" +lore1 = "Velocita' del proiettile" +lore = ["Velocita' del proiettile"] [ranged.lunge_shot] - name = "Colpo d'Affondo" - description = "Mentre cadi, le tue frecce ti lanciano in una direzione casuale" - lore1 = "Velocita' di spinta casuale" - lore = ["Velocita' di spinta casuale"] +name = "Colpo d'Affondo" +description = "Mentre cadi, le tue frecce ti lanciano in una direzione casuale" +lore1 = "Velocita' di spinta casuale" +lore = ["Velocita' di spinta casuale"] [ranged.arrow_piercing] - name = "Freccia Perforante" - description = "Aggiunge Perforazione ai proiettili! Spara attraverso le cose!" - lore1 = "Bersagli perforati" - lore = ["Bersagli perforati"] +name = "Freccia Perforante" +description = "Aggiunge Perforazione ai proiettili! Spara attraverso le cose!" +lore1 = "Bersagli perforati" +lore = ["Bersagli perforati"] # rift [rift] [rift.remote_access] - name = "Accesso Remoto" - description = "Pesca dal vuoto e accedi a un contenitore contrassegnato." - lore1 = "Perla di Ender + Bussola = Portachiave del Reliquiario" - lore2 = "Questo oggetto ti permette di accedere ai contenitori a distanza" - lore3 = "Una volta creato, guarda l'oggetto per vederne l'utilizzo" - notcontainer = "Quello non e' un contenitore" - lore = ["Perla di Ender + Bussola = Portachiave del Reliquiario", "Questo oggetto ti permette di accedere ai contenitori a distanza", "Una volta creato, guarda l'oggetto per vederne l'utilizzo"] +name = "Accesso Remoto" +description = "Pesca dal vuoto e accedi a un contenitore contrassegnato." +lore1 = "Perla di Ender + Bussola = Portachiave del Reliquiario" +lore2 = "Questo oggetto ti permette di accedere ai contenitori a distanza" +lore3 = "Una volta creato, guarda l'oggetto per vederne l'utilizzo" +notcontainer = "Quello non e' un contenitore" +lore = ["Perla di Ender + Bussola = Portachiave del Reliquiario", "Questo oggetto ti permette di accedere ai contenitori a distanza", "Una volta creato, guarda l'oggetto per vederne l'utilizzo"] [rift.blink] - name = "Lampo del Varco" - description = "Teletrasporto istantaneo a corto raggio, a solo un battito di ciglia!" - lore1 = "Blocchi per lampo (2x verticale)" - lore2 = "Mentre scatti: Premi Salto due volte per " - lore3 = "Lampeggiare" - lore = ["Blocchi per lampo (2x verticale)", "Mentre scatti: Premi Salto due volte per ", "Lampeggiare"] +name = "Lampo del Varco" +description = "Teletrasporto istantaneo a corto raggio, a solo un battito di ciglia!" +lore1 = "Blocchi per lampo (2x verticale)" +lore2 = "Mentre scatti: Premi Salto due volte per " +lore3 = "Lampeggiare" +lore = ["Blocchi per lampo (2x verticale)", "Mentre scatti: Premi Salto due volte per ", "Lampeggiare"] [rift.chest] - name = "Baule dell'End Facile" - description = "Apri un Baule dell'End facendo clic sinistro tenendolo in mano." - lore1 = "Clicca un Baule dell'End nella tua mano per aprirlo (non piazzarlo)" - lore = ["Clicca un Baule dell'End nella tua mano per aprirlo (non piazzarlo)"] +name = "Baule dell'End Facile" +description = "Apri un Baule dell'End facendo clic sinistro tenendolo in mano." +lore1 = "Clicca un Baule dell'End nella tua mano per aprirlo (non piazzarlo)" +lore = ["Clicca un Baule dell'End nella tua mano per aprirlo (non piazzarlo)"] [rift.descent] - name = "Anti-Levitazione" - description = "Sei stanco di restare bloccato in aria? Questa e' l'abilita' che fa per te!" - lore1 = "Accovacciati per scendere, e cadrai a una velocita' inferiore al normale!" - lore2 = "Tempo di recupero:" - lore = ["Accovacciati per scendere, e cadrai a una velocita' inferiore al normale!", "Tempo di recupero:"] +name = "Anti-Levitazione" +description = "Sei stanco di restare bloccato in aria? Questa e' l'abilita' che fa per te!" +lore1 = "Accovacciati per scendere, e cadrai a una velocita' inferiore al normale!" +lore2 = "Tempo di recupero:" +lore = ["Accovacciati per scendere, e cadrai a una velocita' inferiore al normale!", "Tempo di recupero:"] [rift.gate] - name = "Portale del Varco" - description = "Teletrasportati a un luogo contrassegnato." - lore1 = "CREAZIONE: Smeraldo + Frammento di Ametista + Perla di Ender" - lore2 = "Leggi prima di usare!" - lore3 = "Ritardo di 5 secondi, " - lore4 = "puoi morire durante questa animazione" - lore = ["CREAZIONE: Smeraldo + Frammento di Ametista + Perla di Ender", "Leggi prima di usare!", "Ritardo di 5 secondi, ", "puoi morire durante questa animazione"] +name = "Portale del Varco" +description = "Teletrasportati a un luogo contrassegnato." +lore1 = "CREAZIONE: Smeraldo + Frammento di Ametista + Perla di Ender" +lore2 = "Leggi prima di usare!" +lore3 = "Ritardo di 5 secondi, " +lore4 = "puoi morire durante questa animazione" +lore = ["CREAZIONE: Smeraldo + Frammento di Ametista + Perla di Ender", "Leggi prima di usare!", "Ritardo di 5 secondi, ", "puoi morire durante questa animazione"] [rift.resist] - name = "Resistenza del Varco" - description = "Ottieni Resistenza quando usi oggetti e abilita' dell'Ender" - lore1 = "+ Passiva: Fornisce resistenza quando usi abilita' del Varco o oggetti dell'Ender" - lore2 = "NON include il Baule dell'End portatile, solo cose che puoi consumare" - lore = ["+ Passiva: Fornisce resistenza quando usi abilita' del Varco o oggetti dell'Ender", "NON include il Baule dell'End portatile, solo cose che puoi consumare"] +name = "Resistenza del Varco" +description = "Ottieni Resistenza quando usi oggetti e abilita' dell'Ender" +lore1 = "+ Passiva: Fornisce resistenza quando usi abilita' del Varco o oggetti dell'Ender" +lore2 = "NON include il Baule dell'End portatile, solo cose che puoi consumare" +lore = ["+ Passiva: Fornisce resistenza quando usi abilita' del Varco o oggetti dell'Ender", "NON include il Baule dell'End portatile, solo cose che puoi consumare"] [rift.visage] - name = "Sembianza del Varco" - description = "Impedisce agli Enderman di diventare aggressivi se hai Perle di Ender nel tuo inventario." - lore1 = "Gli Enderman non diventeranno aggressivi se hai Perle di Ender nel tuo inventario." - lore = ["Gli Enderman non diventeranno aggressivi se hai Perle di Ender nel tuo inventario."] +name = "Sembianza del Varco" +description = "Impedisce agli Enderman di diventare aggressivi se hai Perle di Ender nel tuo inventario." +lore1 = "Gli Enderman non diventeranno aggressivi se hai Perle di Ender nel tuo inventario." +lore = ["Gli Enderman non diventeranno aggressivi se hai Perle di Ender nel tuo inventario."] # seaborn [seaborn] [seaborn.oxygen] - name = "Bombola d'Ossigeno Organica" - description = "Trattieni piu' ossigeno nei tuoi piccoli polmoni!" - lore1 = "Aumento della capacita' di ossigeno" - lore = ["Aumento della capacita' di ossigeno"] +name = "Bombola d'Ossigeno Organica" +description = "Trattieni piu' ossigeno nei tuoi piccoli polmoni!" +lore1 = "Aumento della capacita' di ossigeno" +lore = ["Aumento della capacita' di ossigeno"] [seaborn.fishers_fantasy] - name = "Fantasia del Pescatore" - description = "Guadagna piu' XP dalla pesca e ottieni piu' pesci!" - lore1 = "Per ogni livello c'e' una probabilita' di ottenere piu' XP e Pesci!" - lore = ["Per ogni livello c'e' una probabilita' di ottenere piu' XP e Pesci!"] +name = "Fantasia del Pescatore" +description = "Guadagna piu' XP dalla pesca e ottieni piu' pesci!" +lore1 = "Per ogni livello c'e' una probabilita' di ottenere piu' XP e Pesci!" +lore = ["Per ogni livello c'e' una probabilita' di ottenere piu' XP e Pesci!"] [seaborn.haste] - name = "Minatore Tartaruga" - description = "Mentre mini sott'acqua ottieni Fretta!" - lore1 = "Fretta 3 viene applicata sott'acqua durante l'estrazione (si accumula con Affinita' Acquatica) dopo che l'effetto di respirazione acquatica svanisce!" - lore = ["Fretta 3 viene applicata sott'acqua durante l'estrazione (si accumula con Affinita' Acquatica) dopo che l'effetto di respirazione acquatica svanisce!"] +name = "Minatore Tartaruga" +description = "Mentre mini sott'acqua ottieni Fretta!" +lore1 = "Fretta 3 viene applicata sott'acqua durante l'estrazione (si accumula con Affinita' Acquatica) dopo che l'effetto di respirazione acquatica svanisce!" +lore = ["Fretta 3 viene applicata sott'acqua durante l'estrazione (si accumula con Affinita' Acquatica) dopo che l'effetto di respirazione acquatica svanisce!"] [seaborn.night_vision] - name = "Visione della Tartaruga" - description = "Mentre sei sott'acqua, ottieni Visione Notturna" - lore1 = "Ottieni semplicemente Visione Notturna mentre sei sott'acqua dopo che l'effetto di respirazione acquatica svanisce!" - lore = ["Ottieni semplicemente Visione Notturna mentre sei sott'acqua dopo che l'effetto di respirazione acquatica svanisce!"] +name = "Visione della Tartaruga" +description = "Mentre sei sott'acqua, ottieni Visione Notturna" +lore1 = "Ottieni semplicemente Visione Notturna mentre sei sott'acqua dopo che l'effetto di respirazione acquatica svanisce!" +lore = ["Ottieni semplicemente Visione Notturna mentre sei sott'acqua dopo che l'effetto di respirazione acquatica svanisce!"] [seaborn.dolphin_grace] - name = "Grazia del Delfino" - description = "Nuota come un delfino, senza i delfini" - lore1 = "+ Passiva: ottieni " - lore2 = "x velocita' (grazia del delfino)" - lore3 = "ingegneria tedesca di precisi- aspetta, non e' giusto... Non compatibile con Passo del Fondale" - lore = ["+ Passiva: ottieni ", "x velocita' (grazia del delfino)", "ingegneria tedesca di precisi- aspetta, non e' giusto... Non compatibile con Passo del Fondale"] +name = "Grazia del Delfino" +description = "Nuota come un delfino, senza i delfini" +lore1 = "+ Passiva: ottieni " +lore2 = "x velocita' (grazia del delfino)" +lore3 = "ingegneria tedesca di precisi- aspetta, non e' giusto... Non compatibile con Passo del Fondale" +lore = ["+ Passiva: ottieni ", "x velocita' (grazia del delfino)", "ingegneria tedesca di precisi- aspetta, non e' giusto... Non compatibile con Passo del Fondale"] # stealth [stealth] [stealth.ghost_armor] - name = "Armatura Fantasma" - description = "Armatura ad accumulo lento quando non subisci danni, dura per 1 colpo" - lore1 = "Armatura massima" - lore2 = "Velocita'" - lore = ["Armatura massima", "Velocita'"] +name = "Armatura Fantasma" +description = "Armatura ad accumulo lento quando non subisci danni, dura per 1 colpo" +lore1 = "Armatura massima" +lore2 = "Velocita'" +lore = ["Armatura massima", "Velocita'"] [stealth.night_vision] - name = "Visione Furtiva" - description = "Ottieni visione notturna mentre sei accovacciato" - lore1 = "Ottieni un'ondata di " - lore2 = "visione notturna" - lore3 = "mentre sei accovacciato" - lore = ["Ottieni un'ondata di ", "visione notturna", "mentre sei accovacciato"] +name = "Visione Furtiva" +description = "Ottieni visione notturna mentre sei accovacciato" +lore1 = "Ottieni un'ondata di " +lore2 = "visione notturna" +lore3 = "mentre sei accovacciato" +lore = ["Ottieni un'ondata di ", "visione notturna", "mentre sei accovacciato"] [stealth.snatch] - name = "Arraffa Oggetti" - description = "Arraffa istantaneamente gli oggetti caduti mentre sei accovacciato!" - lore1 = "Raggio di raccolta" - lore = ["Raggio di raccolta"] +name = "Arraffa Oggetti" +description = "Arraffa istantaneamente gli oggetti caduti mentre sei accovacciato!" +lore1 = "Raggio di raccolta" +lore = ["Raggio di raccolta"] [stealth.speed] - name = "Velocita' Furtiva" - description = "Ottieni velocita' mentre sei accovacciato" - lore1 = "Velocita' furtiva" - lore = ["Velocita' furtiva"] +name = "Velocita' Furtiva" +description = "Ottieni velocita' mentre sei accovacciato" +lore1 = "Velocita' furtiva" +lore = ["Velocita' furtiva"] [stealth.ender_veil] - name = "Velo dell'Ender" - description = "Niente piu' zucche per prevenire gli attacchi degli Enderman" - lore1 = "Previeni gli attacchi degli Enderman mentre sei accovacciato" - lore2 = "Previeni tutti gli attacchi degli Enderman" - lore = ["Previeni gli attacchi degli Enderman mentre sei accovacciato", "Previeni tutti gli attacchi degli Enderman"] +name = "Velo dell'Ender" +description = "Niente piu' zucche per prevenire gli attacchi degli Enderman" +lore1 = "Previeni gli attacchi degli Enderman mentre sei accovacciato" +lore2 = "Previeni tutti gli attacchi degli Enderman" +lore = ["Previeni gli attacchi degli Enderman mentre sei accovacciato", "Previeni tutti gli attacchi degli Enderman"] # sword [sword] [sword.machete] - name = "Machete" - description = "Taglia il fogliame con facilita'!" - lore1 = "Raggio del fendente" - lore2 = "Tempo di recupero del taglio" - lore3 = "Usura dell'attrezzo" - lore = ["Raggio del fendente", "Tempo di recupero del taglio", "Usura dell'attrezzo"] +name = "Machete" +description = "Taglia il fogliame con facilita'!" +lore1 = "Raggio del fendente" +lore2 = "Tempo di recupero del taglio" +lore3 = "Usura dell'attrezzo" +lore = ["Raggio del fendente", "Tempo di recupero del taglio", "Usura dell'attrezzo"] [sword.bloody_blade] - name = "Lama Insanguinata" - description = "I colpi con la tua spada causano Sanguinamento!" - lore1 = "Colpire un'entita' vivente con la tua spada causa Sanguinamento" - lore2 = "Durata del sanguinamento" - lore3 = "Tempo di recupero del sanguinamento" - lore = ["Colpire un'entita' vivente con la tua spada causa Sanguinamento", "Durata del sanguinamento", "Tempo di recupero del sanguinamento"] +name = "Lama Insanguinata" +description = "I colpi con la tua spada causano Sanguinamento!" +lore1 = "Colpire un'entita' vivente con la tua spada causa Sanguinamento" +lore2 = "Durata del sanguinamento" +lore3 = "Tempo di recupero del sanguinamento" +lore = ["Colpire un'entita' vivente con la tua spada causa Sanguinamento", "Durata del sanguinamento", "Tempo di recupero del sanguinamento"] [sword.poisoned_blade] - name = "Lama Avvelenata" - description = "I colpi con la tua spada causano Veleno!" - lore1 = "Colpire un'entita' vivente con la tua spada causa Veleno" - lore2 = "Durata del veleno" - lore3 = "Tempo di recupero del veleno" - lore = ["Colpire un'entita' vivente con la tua spada causa Veleno", "Durata del veleno", "Tempo di recupero del veleno"] +name = "Lama Avvelenata" +description = "I colpi con la tua spada causano Veleno!" +lore1 = "Colpire un'entita' vivente con la tua spada causa Veleno" +lore2 = "Durata del veleno" +lore3 = "Tempo di recupero del veleno" +lore = ["Colpire un'entita' vivente con la tua spada causa Veleno", "Durata del veleno", "Tempo di recupero del veleno"] # taming [taming] [taming.damage] - name = "Danno dell'Addomesticato" - description = "Aumenta i danni inflitti dal tuo animale addomesticato." - lore1 = "Danno aumentato" - lore = ["Danno aumentato"] +name = "Danno dell'Addomesticato" +description = "Aumenta i danni inflitti dal tuo animale addomesticato." +lore1 = "Danno aumentato" +lore = ["Danno aumentato"] [taming.health] - name = "Salute dell'Addomesticato" - description = "Aumenta la salute del tuo animale addomesticato." - lore1 = "Salute aumentata" - lore = ["Salute aumentata"] +name = "Salute dell'Addomesticato" +description = "Aumenta la salute del tuo animale addomesticato." +lore1 = "Salute aumentata" +lore = ["Salute aumentata"] [taming.regeneration] - name = "Rigenerazione dell'Addomesticato" - description = "Aumenta la rigenerazione del tuo animale addomesticato." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Rigenerazione dell'Addomesticato" +description = "Aumenta la rigenerazione del tuo animale addomesticato." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Spine" - description = "Rifletti il danno sul tuo aggressore!" - lore1 = "Danno restituito quando vieni colpito" - lore = ["Danno restituito quando vieni colpito"] +name = "Spine" +description = "Rifletti il danno sul tuo aggressore!" +lore1 = "Danno restituito quando vieni colpito" +lore = ["Danno restituito quando vieni colpito"] [tragoul.globe] - name = "Globo del Dolore" - description = "Dividi il danno che infliggi in base al numero di nemici intorno a te!" - lore1 = "Piu' nemici ti circondano, meno danni infliggi a ciascuno di loro" - lore2 = "Raggio: " - lore3 = "Danno aggiuntivo a tutte le entita': " - lore = ["Piu' nemici ti circondano, meno danni infliggi a ciascuno di loro", "Raggio: ", "Danno aggiuntivo a tutte le entita': "] +name = "Globo del Dolore" +description = "Dividi il danno che infliggi in base al numero di nemici intorno a te!" +lore1 = "Piu' nemici ti circondano, meno danni infliggi a ciascuno di loro" +lore2 = "Raggio: " +lore3 = "Danno aggiuntivo a tutte le entita': " +lore = ["Piu' nemici ti circondano, meno danni infliggi a ciascuno di loro", "Raggio: ", "Danno aggiuntivo a tutte le entita': "] [tragoul.healing] - name = "Volonta' del Dolore" - description = "Recupera salute in base al danno che infliggi!" - lore1 = "Fare del male non e' mai stato cosi' piacevole! Guarisci dal danno inflitto" - lore2 = "C'e' una finestra di danno di 3 secondi per la guarigione e un tempo di recupero di 1 secondo " - lore3 = "Guarigione per percentuale di danno: " - lore = ["Fare del male non e' mai stato cosi' piacevole! Guarisci dal danno inflitto", "C'e' una finestra di danno di 3 secondi per la guarigione e un tempo di recupero di 1 secondo ", "Guarigione per percentuale di danno: "] +name = "Volonta' del Dolore" +description = "Recupera salute in base al danno che infliggi!" +lore1 = "Fare del male non e' mai stato cosi' piacevole! Guarisci dal danno inflitto" +lore2 = "C'e' una finestra di danno di 3 secondi per la guarigione e un tempo di recupero di 1 secondo " +lore3 = "Guarigione per percentuale di danno: " +lore = ["Fare del male non e' mai stato cosi' piacevole! Guarisci dal danno inflitto", "C'e' una finestra di danno di 3 secondi per la guarigione e un tempo di recupero di 1 secondo ", "Guarigione per percentuale di danno: "] [tragoul.lance] - name = "Lance di Cadavere" - description = "Uccidere un nemico o avere un'abilita' che uccide un nemico genera una lancia che infligge danni a un nemico vicino!" - lore1 = "Le Lance cercheranno da qualsiasi cosa tu uccida, E se questa abilita' uccide un nemico." - lore2 = "Sacrifica una parte della tua vita per creare le lance (puo' ucciderti)" - lore3 = "Lance massime: 1 + " - lore = ["Le Lance cercheranno da qualsiasi cosa tu uccida, E se questa abilita' uccide un nemico.", "Sacrifica una parte della tua vita per creare le lance (puo' ucciderti)", "Lance massime: 1 + "] +name = "Lance di Cadavere" +description = "Uccidere un nemico o avere un'abilita' che uccide un nemico genera una lancia che infligge danni a un nemico vicino!" +lore1 = "Le Lance cercheranno da qualsiasi cosa tu uccida, E se questa abilita' uccide un nemico." +lore2 = "Sacrifica una parte della tua vita per creare le lance (puo' ucciderti)" +lore3 = "Lance massime: 1 + " +lore = ["Le Lance cercheranno da qualsiasi cosa tu uccida, E se questa abilita' uccide un nemico.", "Sacrifica una parte della tua vita per creare le lance (puo' ucciderti)", "Lance massime: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Cannone di Vetro" - description = "Danno disarmato bonus quanto piu' basso e' il tuo valore di armatura" - lore1 = "x Danno a 0 armatura" - lore2 = "Danno bonus per livello" - lore = ["x Danno a 0 armatura", "Danno bonus per livello"] +name = "Cannone di Vetro" +description = "Danno disarmato bonus quanto piu' basso e' il tuo valore di armatura" +lore1 = "x Danno a 0 armatura" +lore2 = "Danno bonus per livello" +lore = ["x Danno a 0 armatura", "Danno bonus per livello"] [unarmed.power] - name = "Potenza Disarmata" - description = "Danno disarmato migliorato" - lore1 = "Danno" - lore = ["Danno"] +name = "Potenza Disarmata" +description = "Danno disarmato migliorato" +lore1 = "Danno" +lore = ["Danno"] [unarmed.sucker_punch] - name = "Pugno a Tradimento" - description = "Pugni in corsa, ma piu' letali." - lore1 = "Danno" - lore2 = "Il danno aumenta con la tua velocita' mentre colpisci" - lore = ["Danno", "Il danno aumenta con la tua velocita' mentre colpisci"] +name = "Pugno a Tradimento" +description = "Pugni in corsa, ma piu' letali." +lore1 = "Danno" +lore2 = "Il danno aumenta con la tua velocita' mentre colpisci" +lore = ["Danno", "Il danno aumenta con la tua velocita' mentre colpisci"] diff --git a/src/main/resources/ja-JP.toml b/src/main/resources/ja-JP.toml index b54639185..0d456f14e 100644 --- a/src/main/resources/ja-JP.toml +++ b/src/main/resources/ja-JP.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "動かなきゃ!" - description = "1キロメートル(1,000ブロック)以上歩く" +title = "動かなきゃ!" +description = "1キロメートル(1,000ブロック)以上歩く" [advancement.challenge_sprint_5k] - title = "5Kを全力疾走!" - description = "5キロメートル(5,000ブロック)以上歩く" +title = "5Kを全力疾走!" +description = "5キロメートル(5,000ブロック)以上歩く" [advancement.challenge_sprint_50k] - title = "50Kをズーム!" - description = "50キロメートル(50,000ブロック)以上歩く" +title = "50Kをズーム!" +description = "50キロメートル(50,000ブロック)以上歩く" [advancement.challenge_sprint_500k] - title = "宇宙を横断せよ!!" - description = "500キロメートル(500,000ブロック)以上歩く" +title = "宇宙を横断せよ!!" +description = "500キロメートル(500,000ブロック)以上歩く" [advancement.challenge_sprint_marathon] - title = "(文字通りの)マラソンを全力疾走!" - description = "42,195ブロックを全力疾走!" +title = "(文字通りの)マラソンを全力疾走!" +description = "42,195ブロックを全力疾走!" [advancement.challenge_place_1k] - title = "初心者ビルダー!" - description = "1,000個のブロックを設置する" +title = "初心者ビルダー!" +description = "1,000個のブロックを設置する" [advancement.challenge_place_5k] - title = "中級ビルダー!" - description = "5,000個のブロックを設置する" +title = "中級ビルダー!" +description = "5,000個のブロックを設置する" [advancement.challenge_place_50k] - title = "上級ビルダー!" - description = "50,000個のブロックを設置する" +title = "上級ビルダー!" +description = "50,000個のブロックを設置する" [advancement.challenge_place_500k] - title = "マスタービルダー!" - description = "500,000個のブロックを設置する" +title = "マスタービルダー!" +description = "500,000個のブロックを設置する" [advancement.challenge_place_5m] - title = "シンメトリーの侍者!" - description = "現実はあなたの遊び場だ!(500万ブロック)" +title = "シンメトリーの侍者!" +description = "現実はあなたの遊び場だ!(500万ブロック)" [advancement.challenge_chop_1k] - title = "新米きこり!" - description = "1,000ブロックを伐採する" +title = "新米きこり!" +description = "1,000ブロックを伐採する" [advancement.challenge_chop_5k] - title = "中級きこり!" - description = "5,000ブロックを伐採する" +title = "中級きこり!" +description = "5,000ブロックを伐採する" [advancement.challenge_chop_50k] - title = "上級きこり!" - description = "50,000ブロックを伐採する" +title = "上級きこり!" +description = "50,000ブロックを伐採する" [advancement.challenge_chop_500k] - title = "きこりの達人!" - description = "500,000ブロックを伐採する" +title = "きこりの達人!" +description = "500,000ブロックを伐採する" [advancement.challenge_chop_5m] - title = "犬のジャクソン" - description = "最高のいい子!(500万ブロック)" +title = "犬のジャクソン" +description = "最高のいい子!(500万ブロック)" [advancement.challenge_block_1k] - title = "かろうじて防御!" - description = "1000回の攻撃をブロックする" +title = "かろうじて防御!" +description = "1000回の攻撃をブロックする" [advancement.challenge_block_5k] - title = "防御は楽しい!" - description = "5000回の攻撃をブロックする" +title = "防御は楽しい!" +description = "5000回の攻撃をブロックする" [advancement.challenge_block_50k] - title = "防御こそ我が人生!" - description = "50,000回の攻撃をブロックする" +title = "防御こそ我が人生!" +description = "50,000回の攻撃をブロックする" [advancement.challenge_block_500k] - title = "防御こそ我が使命!" - description = "500,000回の攻撃をブロックする" +title = "防御こそ我が使命!" +description = "500,000回の攻撃をブロックする" [advancement.challenge_block_5m] - title = "傷つける手" - description = "5,000,000回の攻撃をブロックする" +title = "傷つける手" +description = "5,000,000回の攻撃をブロックする" [advancement.challenge_brew_1k] - title = "新米錬金術師!" - description = "ポーションを1000個消費する" +title = "新米錬金術師!" +description = "ポーションを1000個消費する" [advancement.challenge_brew_5k] - title = "中級錬金術師!" - description = "ポーションを5000個消費する" +title = "中級錬金術師!" +description = "ポーションを5000個消費する" [advancement.challenge_brew_50k] - title = "上級錬金術師!" - description = "ポーションを50,000個消費する" +title = "上級錬金術師!" +description = "ポーションを50,000個消費する" [advancement.challenge_brew_500k] - title = "錬金術の達人!" - description = "ポーションを500,000個消費する" +title = "錬金術の達人!" +description = "ポーションを500,000個消費する" [advancement.challenge_brew_5m] - title = "錬金術師" - description = "ポーションを5,000,000個消費する" +title = "錬金術師" +description = "ポーションを5,000,000個消費する" [advancement.challenge_brewsplash_1k] - title = "初心者スプラッシュ師!" - description = "1000個のスプラッシュポーションを投げる" +title = "初心者スプラッシュ師!" +description = "1000個のスプラッシュポーションを投げる" [advancement.challenge_brewsplash_5k] - title = "中級スプラッシュ師!" - description = "5000個のスプラッシュポーションを投げる" +title = "中級スプラッシュ師!" +description = "5000個のスプラッシュポーションを投げる" [advancement.challenge_brewsplash_50k] - title = "上級スプラッシュ師!" - description = "50,000個のスプラッシュポーションを投げる" +title = "上級スプラッシュ師!" +description = "50,000個のスプラッシュポーションを投げる" [advancement.challenge_brewsplash_500k] - title = "マスタースプラッシュ師!" - description = "500,000個のスプラッシュポーションを投げる" +title = "マスタースプラッシュ師!" +description = "500,000個のスプラッシュポーションを投げる" [advancement.challenge_brewsplash_5m] - title = "スプラッシュマイスター" - description = "5,000,000個のスプラッシュポーションを投げる" +title = "スプラッシュマイスター" +description = "5,000,000個のスプラッシュポーションを投げる" [advancement.challenge_craft_1k] - title = "器用なクラフター!" - description = "1000個のアイテムを作成する" +title = "器用なクラフター!" +description = "1000個のアイテムを作成する" [advancement.challenge_craft_5k] - title = "気難しいクラフター!" - description = "5000個のアイテムを作成する" +title = "気難しいクラフター!" +description = "5000個のアイテムを作成する" [advancement.challenge_craft_50k] - title = "献身的なクラフター!" - description = "50,000個のアイテムを作成する" +title = "献身的なクラフター!" +description = "50,000個のアイテムを作成する" [advancement.challenge_craft_500k] - title = "騒々しいクラフター!" - description = "500,000個のアイテムを作成する" +title = "騒々しいクラフター!" +description = "500,000個のアイテムを作成する" [advancement.challenge_craft_5m] - title = "災厄のマックラフト" - description = "5,000,000個のアイテムを作成する" +title = "災厄のマックラフト" +description = "5,000,000個のアイテムを作成する" [advancement.challenge_enchant_1k] - title = "初心者エンチャンター!" - description = "1000個のアイテムをエンチャントする" +title = "初心者エンチャンター!" +description = "1000個のアイテムをエンチャントする" [advancement.challenge_enchant_5k] - title = "中級エンチャンター!" - description = "5000個のアイテムをエンチャントする" +title = "中級エンチャンター!" +description = "5000個のアイテムをエンチャントする" [advancement.challenge_enchant_50k] - title = "上級エンチャンター!" - description = "50,000個のアイテムをエンチャントする" +title = "上級エンチャンター!" +description = "50,000個のアイテムをエンチャントする" [advancement.challenge_enchant_500k] - title = "マスターエンチャンター!" - description = "500,000個のアイテムをエンチャントする" +title = "マスターエンチャンター!" +description = "500,000個のアイテムをエンチャントする" [advancement.challenge_enchant_5m] - title = "謎めくエンチャンター" - description = "5,000,000個のアイテムをエンチャントする" +title = "謎めくエンチャンター" +description = "5,000,000個のアイテムをエンチャントする" [advancement.challenge_excavate_1k] - title = "魅せられた発掘者!" - description = "1000ブロックを発掘する" +title = "魅せられた発掘者!" +description = "1000ブロックを発掘する" [advancement.challenge_excavate_5k] - title = "中級発掘者!" - description = "5000ブロックを発掘する" +title = "中級発掘者!" +description = "5000ブロックを発掘する" [advancement.challenge_excavate_50k] - title = "上級発掘者!" - description = "50,000ブロックを発掘する" +title = "上級発掘者!" +description = "50,000ブロックを発掘する" [advancement.challenge_excavate_500k] - title = "発掘の達人!" - description = "500,000ブロックを発掘する" +title = "発掘の達人!" +description = "500,000ブロックを発掘する" [advancement.challenge_excavate_5m] - title = "謎めく発掘者" - description = "5,000,000ブロックを発掘する" +title = "謎めく発掘者" +description = "5,000,000ブロックを発掘する" [advancement.horrible_person] - title = "あなたはひどい人だ" - description = "本当に計り知れない" +title = "あなたはひどい人だ" +description = "本当に計り知れない" [advancement.challenge_turtle_egg_smasher] - title = "カメの卵クラッシャー!" - description = "カメの卵を100個割る" +title = "カメの卵クラッシャー!" +description = "カメの卵を100個割る" [advancement.challenge_turtle_egg_annihilator] - title = "カメの卵殲滅者!" - description = "カメの卵を500個割る" +title = "カメの卵殲滅者!" +description = "カメの卵を500個割る" [advancement.challenge_novice_hunter] - title = "初心者ハンター!" - description = "100体のエンティティを倒す" +title = "初心者ハンター!" +description = "100体のエンティティを倒す" [advancement.challenge_intermediate_hunter] - title = "中級ハンター!" - description = "500体のエンティティを倒す" +title = "中級ハンター!" +description = "500体のエンティティを倒す" [advancement.challenge_advanced_hunter] - title = "上級ハンター!" - description = "5000体のエンティティを倒す" +title = "上級ハンター!" +description = "5000体のエンティティを倒す" [advancement.challenge_creeper_conqueror] - title = "クリーパー征服者!" - description = "クリーパーを50体倒す" +title = "クリーパー征服者!" +description = "クリーパーを50体倒す" [advancement.challenge_creeper_annihilator] - title = "クリーパー殲滅者!" - description = "クリーパーを200体倒す" +title = "クリーパー殲滅者!" +description = "クリーパーを200体倒す" [advancement.challenge_pickaxe_1k] - title = "初心者マイナー" - description = "1000個のブロックを壊す" +title = "初心者マイナー" +description = "1000個のブロックを壊す" [advancement.challenge_pickaxe_5k] - title = "熟練マイナー" - description = "5000個のブロックを壊す" +title = "熟練マイナー" +description = "5000個のブロックを壊す" [advancement.challenge_pickaxe_50k] - title = "エキスパートマイナー" - description = "50,000個のブロックを壊す" +title = "エキスパートマイナー" +description = "50,000個のブロックを壊す" [advancement.challenge_pickaxe_500k] - title = "マスターマイナー" - description = "500,000個のブロックを壊す" +title = "マスターマイナー" +description = "500,000個のブロックを壊す" [advancement.challenge_pickaxe_5m] - title = "伝説のマイナー" - description = "5,000,000個のブロックを壊す" +title = "伝説のマイナー" +description = "5,000,000個のブロックを壊す" [advancement.challenge_eat_100] - title = "食べるものがいっぱい!" - description = "100個以上のアイテムを食べよう!" +title = "食べるものがいっぱい!" +description = "100個以上のアイテムを食べよう!" [advancement.challenge_eat_1000] - title = "止められない空腹!" - description = "1,000個以上のアイテムを食べよう!" +title = "止められない空腹!" +description = "1,000個以上のアイテムを食べよう!" [advancement.challenge_eat_10000] - title = "永遠の飢餓!" - description = "10,000個以上のアイテムを食べよう!" +title = "永遠の飢餓!" +description = "10,000個以上のアイテムを食べよう!" [advancement.challenge_harvest_100] - title = "豊穣の収穫" - description = "100個以上の作物を収穫しよう!" +title = "豊穣の収穫" +description = "100個以上の作物を収穫しよう!" [advancement.challenge_harvest_1000] - title = "大豊穣の収穫" - description = "1,000個以上の作物を収穫しよう!" +title = "大豊穣の収穫" +description = "1,000個以上の作物を収穫しよう!" [advancement.challenge_swim_1nm] - title = "人間潜水艦!" - description = "1海里(1,852ブロック)泳ぐ" +title = "人間潜水艦!" +description = "1海里(1,852ブロック)泳ぐ" [advancement.challenge_sneak_1k] - title = "膝の痛み" - description = "1キロメートル(1,000ブロック)以上こっそり進む" +title = "膝の痛み" +description = "1キロメートル(1,000ブロック)以上こっそり進む" [advancement.challenge_sneak_5k] - title = "影の歩行者" - description = "5,000ブロック以上こっそり進む" +title = "影の歩行者" +description = "5,000ブロック以上こっそり進む" [advancement.challenge_sneak_20k] - title = "幽霊" - description = "20,000ブロック以上こっそり進む" +title = "幽霊" +description = "20,000ブロック以上こっそり進む" [advancement.challenge_swim_5k] - title = "深海ダイバー" - description = "5,000ブロック以上泳ぐ" +title = "深海ダイバー" +description = "5,000ブロック以上泳ぐ" [advancement.challenge_swim_20k] - title = "ポセイドンの選ばれし者" - description = "20,000ブロック以上泳ぐ" +title = "ポセイドンの選ばれし者" +description = "20,000ブロック以上泳ぐ" [advancement.challenge_sword_100] - title = "初めての血" - description = "剣で100回攻撃する" +title = "初めての血" +description = "剣で100回攻撃する" [advancement.challenge_sword_1k] - title = "剣舞" - description = "剣で1,000回攻撃する" +title = "剣舞" +description = "剣で1,000回攻撃する" [advancement.challenge_sword_10k] - title = "千の斬撃" - description = "剣で10,000回攻撃する" +title = "千の斬撃" +description = "剣で10,000回攻撃する" [advancement.challenge_unarmed_100] - title = "酒場の喧嘩屋" - description = "素手で100回攻撃する" +title = "酒場の喧嘩屋" +description = "素手で100回攻撃する" [advancement.challenge_unarmed_1k] - title = "鉄の拳" - description = "素手で1,000回攻撃する" +title = "鉄の拳" +description = "素手で1,000回攻撃する" [advancement.challenge_unarmed_10k] - title = "ワンパンチ" - description = "素手で10,000回攻撃する" +title = "ワンパンチ" +description = "素手で10,000回攻撃する" [advancement.challenge_trag_1k] - title = "血の代償" - description = "1,000ダメージを受ける" +title = "血の代償" +description = "1,000ダメージを受ける" [advancement.challenge_trag_10k] - title = "紅の潮流" - description = "10,000ダメージを受ける" +title = "紅の潮流" +description = "10,000ダメージを受ける" [advancement.challenge_trag_100k] - title = "苦痛の化身" - description = "100,000ダメージを受ける" +title = "苦痛の化身" +description = "100,000ダメージを受ける" [advancement.challenge_ranged_100] - title = "射撃練習" - description = "100発の飛び道具を発射する" +title = "射撃練習" +description = "100発の飛び道具を発射する" [advancement.challenge_ranged_1k] - title = "ホークアイ" - description = "1,000発の飛び道具を発射する" +title = "ホークアイ" +description = "1,000発の飛び道具を発射する" [advancement.challenge_ranged_10k] - title = "矢の嵐" - description = "10,000発の飛び道具を発射する" +title = "矢の嵐" +description = "10,000発の飛び道具を発射する" [advancement.challenge_chronos_1h] - title = "チクタク" - description = "1時間オンラインで過ごす" +title = "チクタク" +description = "1時間オンラインで過ごす" [advancement.challenge_chronos_24h] - title = "時の砂" - description = "24時間オンラインで過ごす" +title = "時の砂" +description = "24時間オンラインで過ごす" [advancement.challenge_chronos_168h] - title = "時を超えし者" - description = "168時間(1週間)オンラインで過ごす" +title = "時を超えし者" +description = "168時間(1週間)オンラインで過ごす" [advancement.challenge_nether_50] - title = "地獄の門番" - description = "ネザーの生物を50体倒す" +title = "地獄の門番" +description = "ネザーの生物を50体倒す" [advancement.challenge_nether_500] - title = "深淵の番人" - description = "ネザーの生物を500体倒す" +title = "深淵の番人" +description = "ネザーの生物を500体倒す" [advancement.challenge_nether_5k] - title = "ネザーの支配者" - description = "ネザーの生物を5,000体倒す" +title = "ネザーの支配者" +description = "ネザーの生物を5,000体倒す" [advancement.challenge_rift_50] - title = "空間の歪み" - description = "50回テレポートする" +title = "空間の歪み" +description = "50回テレポートする" [advancement.challenge_rift_500] - title = "虚空の歩行者" - description = "500回テレポートする" +title = "虚空の歩行者" +description = "500回テレポートする" [advancement.challenge_rift_5k] - title = "世界の狭間" - description = "5,000回テレポートする" +title = "世界の狭間" +description = "5,000回テレポートする" [advancement.challenge_taming_10] - title = "動物のささやき手" - description = "10匹の動物を繁殖させる" +title = "動物のささやき手" +description = "10匹の動物を繁殖させる" [advancement.challenge_taming_50] - title = "群れのリーダー" - description = "50匹の動物を繁殖させる" +title = "群れのリーダー" +description = "50匹の動物を繁殖させる" [advancement.challenge_taming_500] - title = "猛獣使い" - description = "500匹の動物を繁殖させる" +title = "猛獣使い" +description = "500匹の動物を繁殖させる" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "疾風の脚" - description = "5キロメートル(5,000ブロック)以上をダッシュする" +title = "疾風の脚" +description = "5キロメートル(5,000ブロック)以上をダッシュする" [advancement.challenge_sprint_dist_50k] - title = "稲妻の脚" - description = "50キロメートル(50,000ブロック)以上をダッシュする" +title = "稲妻の脚" +description = "50キロメートル(50,000ブロック)以上をダッシュする" [advancement.challenge_agility_swim_1k] - title = "水面走り" - description = "1キロメートル(1,000ブロック)以上を泳ぐ" +title = "水面走り" +description = "1キロメートル(1,000ブロック)以上を泳ぐ" [advancement.challenge_agility_swim_10k] - title = "水の旅人" - description = "10キロメートル(10,000ブロック)以上を泳ぐ" +title = "水の旅人" +description = "10キロメートル(10,000ブロック)以上を泳ぐ" [advancement.challenge_fly_1k] - title = "空の舞" - description = "1キロメートル(1,000ブロック)以上を飛ぶ" +title = "空の舞" +description = "1キロメートル(1,000ブロック)以上を飛ぶ" [advancement.challenge_fly_10k] - title = "風乗り" - description = "10キロメートル(10,000ブロック)以上を飛ぶ" +title = "風乗り" +description = "10キロメートル(10,000ブロック)以上を飛ぶ" [advancement.challenge_agility_sneak_500] - title = "静かな足取り" - description = "500ブロック以上をスニークする" +title = "静かな足取り" +description = "500ブロック以上をスニークする" [advancement.challenge_agility_sneak_5k] - title = "幻影の歩み" - description = "5キロメートル(5,000ブロック)以上をスニークする" +title = "幻影の歩み" +description = "5キロメートル(5,000ブロック)以上をスニークする" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "解体班" - description = "500ブロックを破壊する" +title = "解体班" +description = "500ブロックを破壊する" [advancement.challenge_demolish_5k] - title = "破壊球" - description = "5,000ブロックを破壊する" +title = "破壊球" +description = "5,000ブロックを破壊する" [advancement.challenge_value_placed_10k] - title = "価値ある建築家" - description = "10,000の価値分のブロックを設置する" +title = "価値ある建築家" +description = "10,000の価値分のブロックを設置する" [advancement.challenge_value_placed_100k] - title = "建築の達人" - description = "100,000の価値分のブロックを設置する" +title = "建築の達人" +description = "100,000の価値分のブロックを設置する" [advancement.challenge_demolish_val_5k] - title = "回収の達人" - description = "解体から5,000の価値を回収する" +title = "回収の達人" +description = "解体から5,000の価値を回収する" [advancement.challenge_demolish_val_50k] - title = "完全解体" - description = "解体から50,000の価値を回収する" +title = "完全解体" +description = "解体から50,000の価値を回収する" [advancement.challenge_high_build_100] - title = "天空の建築家" - description = "Y=128以上に100ブロックを設置する" +title = "天空の建築家" +description = "Y=128以上に100ブロックを設置する" [advancement.challenge_high_build_1k] - title = "雲上の建築士" - description = "Y=128以上に1,000ブロックを設置する" +title = "雲上の建築士" +description = "Y=128以上に1,000ブロックを設置する" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "斧振り" - description = "斧を500回振る" +title = "斧振り" +description = "斧を500回振る" [advancement.challenge_axe_swing_5k] - title = "狂戦士" - description = "斧を5,000回振る" +title = "狂戦士" +description = "斧を5,000回振る" [advancement.challenge_axe_damage_1k] - title = "断裂者" - description = "斧で1,000ダメージを与える" +title = "断裂者" +description = "斧で1,000ダメージを与える" [advancement.challenge_axe_damage_10k] - title = "処刑人の斧" - description = "斧で10,000ダメージを与える" +title = "処刑人の斧" +description = "斧で10,000ダメージを与える" [advancement.challenge_axe_value_5k] - title = "木材商人" - description = "5,000の価値分の木材を収穫する" +title = "木材商人" +description = "5,000の価値分の木材を収穫する" [advancement.challenge_axe_value_50k] - title = "木材王" - description = "50,000の価値分の木材を収穫する" +title = "木材王" +description = "50,000の価値分の木材を収穫する" [advancement.challenge_leaves_500] - title = "落葉掃き" - description = "斧で500の葉ブロックを除去する" +title = "落葉掃き" +description = "斧で500の葉ブロックを除去する" [advancement.challenge_leaves_5k] - title = "落葉粉砕機" - description = "斧で5,000の葉ブロックを除去する" +title = "落葉粉砕機" +description = "斧で5,000の葉ブロックを除去する" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "ダメージ吸収" - description = "盾で1,000ダメージをブロックする" +title = "ダメージ吸収" +description = "盾で1,000ダメージをブロックする" [advancement.challenge_block_dmg_10k] - title = "人間の盾" - description = "盾で10,000ダメージをブロックする" +title = "人間の盾" +description = "盾で10,000ダメージをブロックする" [advancement.challenge_block_proj_100] - title = "矢弾き" - description = "盾で100の飛び道具をブロックする" +title = "矢弾き" +description = "盾で100の飛び道具をブロックする" [advancement.challenge_block_proj_1k] - title = "飛び道具の盾" - description = "盾で1,000の飛び道具をブロックする" +title = "飛び道具の盾" +description = "盾で1,000の飛び道具をブロックする" [advancement.challenge_block_melee_500] - title = "受け流しの達人" - description = "盾で500の近接攻撃をブロックする" +title = "受け流しの達人" +description = "盾で500の近接攻撃をブロックする" [advancement.challenge_block_melee_5k] - title = "鉄の要塞" - description = "盾で5,000の近接攻撃をブロックする" +title = "鉄の要塞" +description = "盾で5,000の近接攻撃をブロックする" [advancement.challenge_block_heavy_50] - title = "戦車" - description = "50の強力な攻撃(5ダメージ超)をブロックする" +title = "戦車" +description = "50の強力な攻撃(5ダメージ超)をブロックする" [advancement.challenge_block_heavy_500] - title = "不動の壁" - description = "500の強力な攻撃(5ダメージ超)をブロックする" +title = "不動の壁" +description = "500の強力な攻撃(5ダメージ超)をブロックする" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "醸造の準備" - description = "醸造台を10台設置する" +title = "醸造の準備" +description = "醸造台を10台設置する" [advancement.challenge_brew_stands_50] - title = "ポーション工場" - description = "醸造台を50台設置する" +title = "ポーション工場" +description = "醸造台を50台設置する" [advancement.challenge_brew_strong_25] - title = "強力な醸造" - description = "強化されたポーションを25本飲む" +title = "強力な醸造" +description = "強化されたポーションを25本飲む" [advancement.challenge_brew_strong_250] - title = "最大効力" - description = "強化されたポーションを250本飲む" +title = "最大効力" +description = "強化されたポーションを250本飲む" [advancement.challenge_brew_splash_hits_50] - title = "飛散範囲" - description = "スプラッシュポーションで50体のエンティティに命中する" +title = "飛散範囲" +description = "スプラッシュポーションで50体のエンティティに命中する" [advancement.challenge_brew_splash_hits_500] - title = "疫病医" - description = "スプラッシュポーションで500体のエンティティに命中する" +title = "疫病医" +description = "スプラッシュポーションで500体のエンティティに命中する" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "落ち着きなし" - description = "活動中に1キロメートルを移動する" +title = "落ち着きなし" +description = "活動中に1キロメートルを移動する" [advancement.challenge_active_dist_10k] - title = "道の開拓者" - description = "活動中に10キロメートルを移動する" +title = "道の開拓者" +description = "活動中に10キロメートルを移動する" [advancement.challenge_active_dist_100k] - title = "永久運動" - description = "活動中に100キロメートルを移動する" +title = "永久運動" +description = "活動中に100キロメートルを移動する" [advancement.challenge_beds_10] - title = "早起き" - description = "ベッドで10回眠る" +title = "早起き" +description = "ベッドで10回眠る" [advancement.challenge_beds_100] - title = "時間飛ばし" - description = "ベッドで100回眠る" +title = "時間飛ばし" +description = "ベッドで100回眠る" [advancement.challenge_chronos_tp_50] - title = "時空転移" - description = "50回テレポートする" +title = "時空転移" +description = "50回テレポートする" [advancement.challenge_chronos_tp_500] - title = "時間の歪み" - description = "500回テレポートする" +title = "時間の歪み" +description = "500回テレポートする" [advancement.challenge_chronos_deaths_10] - title = "死すべき者" - description = "10回死ぬ" +title = "死すべき者" +description = "10回死ぬ" [advancement.challenge_chronos_deaths_100] - title = "死を超える者" - description = "100回死ぬ" +title = "死を超える者" +description = "100回死ぬ" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "製作の価値" - description = "合計10,000の価値のアイテムをクラフトする" +title = "製作の価値" +description = "合計10,000の価値のアイテムをクラフトする" [advancement.challenge_craft_value_100k] - title = "職人" - description = "合計100,000の価値のアイテムをクラフトする" +title = "職人" +description = "合計100,000の価値のアイテムをクラフトする" [advancement.challenge_craft_tools_25] - title = "道具鍛冶" - description = "25個の道具をクラフトする" +title = "道具鍛冶" +description = "25個の道具をクラフトする" [advancement.challenge_craft_tools_250] - title = "鍛造の達人" - description = "250個の道具をクラフトする" +title = "鍛造の達人" +description = "250個の道具をクラフトする" [advancement.challenge_craft_armor_25] - title = "防具鍛冶" - description = "25個の防具をクラフトする" +title = "防具鍛冶" +description = "25個の防具をクラフトする" [advancement.challenge_craft_armor_250] - title = "防具の達人" - description = "250個の防具をクラフトする" +title = "防具の達人" +description = "250個の防具をクラフトする" [advancement.challenge_craft_mass_25k] - title = "大量生産" - description = "25,000個のアイテムをクラフトする" +title = "大量生産" +description = "25,000個のアイテムをクラフトする" [advancement.challenge_craft_mass_250k] - title = "産業革命" - description = "250,000個のアイテムをクラフトする" +title = "産業革命" +description = "250,000個のアイテムをクラフトする" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "収集家" - description = "50種類のアイテムを発見する" +title = "収集家" +description = "50種類のアイテムを発見する" [advancement.challenge_discover_items_250] - title = "目録係" - description = "250種類のアイテムを発見する" +title = "目録係" +description = "250種類のアイテムを発見する" [advancement.challenge_discover_blocks_50] - title = "測量士" - description = "50種類のブロックを発見する" +title = "測量士" +description = "50種類のブロックを発見する" [advancement.challenge_discover_blocks_250] - title = "地質学者" - description = "250種類のブロックを発見する" +title = "地質学者" +description = "250種類のブロックを発見する" [advancement.challenge_discover_mobs_25] - title = "観察者" - description = "25種類のモブを発見する" +title = "観察者" +description = "25種類のモブを発見する" [advancement.challenge_discover_mobs_75] - title = "博物学者" - description = "75種類のモブを発見する" +title = "博物学者" +description = "75種類のモブを発見する" [advancement.challenge_discover_biomes_10] - title = "放浪者" - description = "10種類のバイオームを発見する" +title = "放浪者" +description = "10種類のバイオームを発見する" [advancement.challenge_discover_biomes_40] - title = "世界旅行者" - description = "40種類のバイオームを発見する" +title = "世界旅行者" +description = "40種類のバイオームを発見する" [advancement.challenge_discover_foods_10] - title = "美食家" - description = "10種類の食べ物を発見する" +title = "美食家" +description = "10種類の食べ物を発見する" [advancement.challenge_discover_foods_30] - title = "料理の達人" - description = "30種類の食べ物を発見する" +title = "料理の達人" +description = "30種類の食べ物を発見する" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "力の織り手" - description = "エンチャントパワーを100蓄積する" +title = "力の織り手" +description = "エンチャントパワーを100蓄積する" [advancement.challenge_enchant_power_1k] - title = "秘術の達人" - description = "エンチャントパワーを1,000蓄積する" +title = "秘術の達人" +description = "エンチャントパワーを1,000蓄積する" [advancement.challenge_enchant_levels_1k] - title = "レベル消費者" - description = "エンチャントに1,000経験レベルを使う" +title = "レベル消費者" +description = "エンチャントに1,000経験レベルを使う" [advancement.challenge_enchant_levels_10k] - title = "経験値の泉" - description = "エンチャントに10,000経験レベルを使う" +title = "経験値の泉" +description = "エンチャントに10,000経験レベルを使う" [advancement.challenge_enchant_high_25] - title = "大勝負" - description = "最大レベルのエンチャントを25回行う" +title = "大勝負" +description = "最大レベルのエンチャントを25回行う" [advancement.challenge_enchant_high_250] - title = "伝説のエンチャンター" - description = "最大レベルのエンチャントを250回行う" +title = "伝説のエンチャンター" +description = "最大レベルのエンチャントを250回行う" [advancement.challenge_enchant_total_500] - title = "レベル燃焼" - description = "全エンチャントで合計500レベルを使う" +title = "レベル燃焼" +description = "全エンチャントで合計500レベルを使う" [advancement.challenge_enchant_total_5k] - title = "秘術の投資" - description = "全エンチャントで合計5,000レベルを使う" +title = "秘術の投資" +description = "全エンチャントで合計5,000レベルを使う" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "掘り師" - description = "シャベルを500回振る" +title = "掘り師" +description = "シャベルを500回振る" [advancement.challenge_dig_swing_5k] - title = "掘削者" - description = "シャベルを5,000回振る" +title = "掘削者" +description = "シャベルを5,000回振る" [advancement.challenge_dig_damage_1k] - title = "シャベルの騎士" - description = "シャベルで1,000ダメージを与える" +title = "シャベルの騎士" +description = "シャベルで1,000ダメージを与える" [advancement.challenge_dig_damage_10k] - title = "シャベルの達人" - description = "シャベルで10,000ダメージを与える" +title = "シャベルの達人" +description = "シャベルで10,000ダメージを与える" [advancement.challenge_dig_value_5k] - title = "土の商人" - description = "5,000の価値分のブロックを掘削する" +title = "土の商人" +description = "5,000の価値分のブロックを掘削する" [advancement.challenge_dig_value_50k] - title = "大地の支配者" - description = "50,000の価値分のブロックを掘削する" +title = "大地の支配者" +description = "50,000の価値分のブロックを掘削する" [advancement.challenge_dig_gravel_500] - title = "砂利砕き" - description = "砂利、砂、粘土を500ブロック掘る" +title = "砂利砕き" +description = "砂利、砂、粘土を500ブロック掘る" [advancement.challenge_dig_gravel_5k] - title = "砂ふるい" - description = "砂利、砂、粘土を5,000ブロック掘る" +title = "砂ふるい" +description = "砂利、砂、粘土を5,000ブロック掘る" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "種まき人" - description = "100の作物を植える" +title = "種まき人" +description = "100の作物を植える" [advancement.challenge_plant_1k] - title = "緑の親指" - description = "1,000の作物を植える" +title = "緑の親指" +description = "1,000の作物を植える" [advancement.challenge_plant_5k] - title = "農業王" - description = "5,000の作物を植える" +title = "農業王" +description = "5,000の作物を植える" [advancement.challenge_compost_50] - title = "再利用者" - description = "50個のアイテムを堆肥にする" +title = "再利用者" +description = "50個のアイテムを堆肥にする" [advancement.challenge_compost_500] - title = "土壌改良" - description = "500個のアイテムを堆肥にする" +title = "土壌改良" +description = "500個のアイテムを堆肥にする" [advancement.challenge_shear_50] - title = "毛刈り師" - description = "50体のエンティティを毛刈りする" +title = "毛刈り師" +description = "50体のエンティティを毛刈りする" [advancement.challenge_shear_250] - title = "群れの長" - description = "250体のエンティティを毛刈りする" +title = "群れの長" +description = "250体のエンティティを毛刈りする" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "狩人" - description = "500体の生物を倒す" +title = "狩人" +description = "500体の生物を倒す" [advancement.challenge_kills_5k] - title = "処刑人" - description = "5,000体の生物を倒す" +title = "処刑人" +description = "5,000体の生物を倒す" [advancement.challenge_boss_1] - title = "ボス挑戦者" - description = "ボスモブを1体倒す" +title = "ボス挑戦者" +description = "ボスモブを1体倒す" [advancement.challenge_boss_10] - title = "伝説殺し" - description = "ボスモブを10体倒す" +title = "伝説殺し" +description = "ボスモブを10体倒す" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "衰弱" - description = "ウィザーダメージを500耐える" +title = "衰弱" +description = "ウィザーダメージを500耐える" [advancement.challenge_wither_dmg_5k] - title = "枯死の生存者" - description = "ウィザーダメージを5,000耐える" +title = "枯死の生存者" +description = "ウィザーダメージを5,000耐える" [advancement.challenge_wither_skel_25] - title = "骨の収集者" - description = "ウィザースケルトンを25体倒す" +title = "骨の収集者" +description = "ウィザースケルトンを25体倒す" [advancement.challenge_wither_skel_250] - title = "骸骨の天敵" - description = "ウィザースケルトンを250体倒す" +title = "骸骨の天敵" +description = "ウィザースケルトンを250体倒す" [advancement.challenge_wither_boss_1] - title = "ウィザー討伐" - description = "ウィザーを倒す" +title = "ウィザー討伐" +description = "ウィザーを倒す" [advancement.challenge_wither_boss_10] - title = "ネザーの支配者" - description = "ウィザーを10回倒す" +title = "ネザーの支配者" +description = "ウィザーを10回倒す" [advancement.challenge_roses_10] - title = "死の庭師" - description = "ウィザーローズを10本壊す" +title = "死の庭師" +description = "ウィザーローズを10本壊す" [advancement.challenge_roses_100] - title = "枯死の収集者" - description = "ウィザーローズを100本壊す" +title = "枯死の収集者" +description = "ウィザーローズを100本壊す" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "鉱夫の腕" - description = "つるはしを500回振る" +title = "鉱夫の腕" +description = "つるはしを500回振る" [advancement.challenge_pick_swing_5k] - title = "坑道掘り" - description = "つるはしを5,000回振る" +title = "坑道掘り" +description = "つるはしを5,000回振る" [advancement.challenge_pick_damage_1k] - title = "つるはし戦士" - description = "つるはしで1,000ダメージを与える" +title = "つるはし戦士" +description = "つるはしで1,000ダメージを与える" [advancement.challenge_pick_damage_10k] - title = "戦のつるはし" - description = "つるはしで10,000ダメージを与える" +title = "戦のつるはし" +description = "つるはしで10,000ダメージを与える" [advancement.challenge_pick_value_5k] - title = "宝石探し" - description = "5,000の価値分のブロックを採掘する" +title = "宝石探し" +description = "5,000の価値分のブロックを採掘する" [advancement.challenge_pick_value_50k] - title = "鉱石王" - description = "50,000の価値分のブロックを採掘する" +title = "鉱石王" +description = "50,000の価値分のブロックを採掘する" [advancement.challenge_pick_ores_500] - title = "探鉱者" - description = "500の鉱石ブロックを採掘する" +title = "探鉱者" +description = "500の鉱石ブロックを採掘する" [advancement.challenge_pick_ores_5k] - title = "採掘の達人" - description = "5,000の鉱石ブロックを採掘する" +title = "採掘の達人" +description = "5,000の鉱石ブロックを採掘する" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "射撃の名手" - description = "遠距離ダメージを1,000与える" +title = "射撃の名手" +description = "遠距離ダメージを1,000与える" [advancement.challenge_ranged_dmg_10k] - title = "致命の射手" - description = "遠距離ダメージを10,000与える" +title = "致命の射手" +description = "遠距離ダメージを10,000与える" [advancement.challenge_ranged_dist_5k] - title = "長距離射撃" - description = "飛び道具の合計飛距離5,000ブロックを達成する" +title = "長距離射撃" +description = "飛び道具の合計飛距離5,000ブロックを達成する" [advancement.challenge_ranged_dist_50k] - title = "遠射の王" - description = "飛び道具の合計飛距離50,000ブロックを達成する" +title = "遠射の王" +description = "飛び道具の合計飛距離50,000ブロックを達成する" [advancement.challenge_ranged_kills_50] - title = "弓兵" - description = "遠距離武器で50体のモブを倒す" +title = "弓兵" +description = "遠距離武器で50体のモブを倒す" [advancement.challenge_ranged_kills_500] - title = "弓の達人" - description = "遠距離武器で500体のモブを倒す" +title = "弓の達人" +description = "遠距離武器で500体のモブを倒す" [advancement.challenge_longshot_25] - title = "狙撃手" - description = "長距離射撃(30ブロック超)を25回命中させる" +title = "狙撃手" +description = "長距離射撃(30ブロック超)を25回命中させる" [advancement.challenge_longshot_250] - title = "鷹の目" - description = "長距離射撃(30ブロック超)を250回命中させる" +title = "鷹の目" +description = "長距離射撃(30ブロック超)を250回命中させる" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "真珠投げ" - description = "エンダーパールを50個投げる" +title = "真珠投げ" +description = "エンダーパールを50個投げる" [advancement.challenge_rift_pearls_500] - title = "転送中毒" - description = "エンダーパールを500個投げる" +title = "転送中毒" +description = "エンダーパールを500個投げる" [advancement.challenge_rift_enderman_50] - title = "エンダーマン狩り" - description = "エンダーマンを50体倒す" +title = "エンダーマン狩り" +description = "エンダーマンを50体倒す" [advancement.challenge_rift_enderman_500] - title = "虚空の追跡者" - description = "エンダーマンを500体倒す" +title = "虚空の追跡者" +description = "エンダーマンを500体倒す" [advancement.challenge_rift_dragon_500] - title = "竜と闘う者" - description = "エンダードラゴンに500ダメージを与える" +title = "竜と闘う者" +description = "エンダードラゴンに500ダメージを与える" [advancement.challenge_rift_dragon_5k] - title = "竜殺し" - description = "エンダードラゴンに5,000ダメージを与える" +title = "竜殺し" +description = "エンダードラゴンに5,000ダメージを与える" [advancement.challenge_rift_crystal_10] - title = "水晶破壊者" - description = "エンドクリスタルを10個破壊する" +title = "水晶破壊者" +description = "エンドクリスタルを10個破壊する" [advancement.challenge_rift_crystal_100] - title = "エンドの破壊者" - description = "エンドクリスタルを100個破壊する" +title = "エンドの破壊者" +description = "エンドクリスタルを100個破壊する" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "釣り人" - description = "25匹の魚を釣る" +title = "釣り人" +description = "25匹の魚を釣る" [advancement.challenge_fish_250] - title = "釣りの達人" - description = "250匹の魚を釣る" +title = "釣りの達人" +description = "250匹の魚を釣る" [advancement.challenge_drowned_25] - title = "溺死体狩り" - description = "ドラウンドを25体倒す" +title = "溺死体狩り" +description = "ドラウンドを25体倒す" [advancement.challenge_drowned_250] - title = "海の浄化者" - description = "ドラウンドを250体倒す" +title = "海の浄化者" +description = "ドラウンドを250体倒す" [advancement.challenge_guardian_10] - title = "ガーディアン討伐" - description = "ガーディアンを10体倒す" +title = "ガーディアン討伐" +description = "ガーディアンを10体倒す" [advancement.challenge_guardian_100] - title = "神殿襲撃者" - description = "ガーディアンを100体倒す" +title = "神殿襲撃者" +description = "ガーディアンを100体倒す" [advancement.challenge_underwater_blocks_100] - title = "水中採掘者" - description = "水中で100ブロックを破壊する" +title = "水中採掘者" +description = "水中で100ブロックを破壊する" [advancement.challenge_underwater_blocks_1k] - title = "水中技師" - description = "水中で1,000ブロックを破壊する" +title = "水中技師" +description = "水中で1,000ブロックを破壊する" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "背後からの一刺し" - description = "スニーク中に500ダメージを与える" +title = "背後からの一刺し" +description = "スニーク中に500ダメージを与える" [advancement.challenge_stealth_dmg_5k] - title = "沈黙の殺し屋" - description = "スニーク中に5,000ダメージを与える" +title = "沈黙の殺し屋" +description = "スニーク中に5,000ダメージを与える" [advancement.challenge_stealth_kills_10] - title = "暗殺者" - description = "スニーク中に10体のモブを倒す" +title = "暗殺者" +description = "スニーク中に10体のモブを倒す" [advancement.challenge_stealth_kills_100] - title = "影の死神" - description = "スニーク中に100体のモブを倒す" +title = "影の死神" +description = "スニーク中に100体のモブを倒す" [advancement.challenge_stealth_time_1h] - title = "忍耐" - description = "1時間(3,600秒)スニークする" +title = "忍耐" +description = "1時間(3,600秒)スニークする" [advancement.challenge_stealth_time_10h] - title = "影の支配者" - description = "10時間(36,000秒)スニークする" +title = "影の支配者" +description = "10時間(36,000秒)スニークする" [advancement.challenge_stealth_arrows_50] - title = "静かなる射手" - description = "スニーク中に50本の矢を放つ" +title = "静かなる射手" +description = "スニーク中に50本の矢を放つ" [advancement.challenge_stealth_arrows_500] - title = "幻影の弓手" - description = "スニーク中に500本の矢を放つ" +title = "幻影の弓手" +description = "スニーク中に500本の矢を放つ" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "剣の見習い" - description = "剣で1,000ダメージを与える" +title = "剣の見習い" +description = "剣で1,000ダメージを与える" [advancement.challenge_sword_dmg_10k] - title = "剣士" - description = "剣で10,000ダメージを与える" +title = "剣士" +description = "剣で10,000ダメージを与える" [advancement.challenge_sword_kills_50] - title = "決闘者" - description = "剣で50体のモブを倒す" +title = "決闘者" +description = "剣で50体のモブを倒す" [advancement.challenge_sword_kills_500] - title = "剣闘士" - description = "剣で500体のモブを倒す" +title = "剣闘士" +description = "剣で500体のモブを倒す" [advancement.challenge_sword_crit_50] - title = "会心の一撃" - description = "剣でクリティカルヒットを50回決める" +title = "会心の一撃" +description = "剣でクリティカルヒットを50回決める" [advancement.challenge_sword_crit_500] - title = "精密の達人" - description = "剣でクリティカルヒットを500回決める" +title = "精密の達人" +description = "剣でクリティカルヒットを500回決める" [advancement.challenge_sword_heavy_25] - title = "重い一撃" - description = "剣で強力な一撃(8ダメージ超)を25回決める" +title = "重い一撃" +description = "剣で強力な一撃(8ダメージ超)を25回決める" [advancement.challenge_sword_heavy_250] - title = "壊滅の一撃" - description = "剣で強力な一撃(8ダメージ超)を250回決める" +title = "壊滅の一撃" +description = "剣で強力な一撃(8ダメージ超)を250回決める" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "獣の調教師" - description = "ペットが合計500ダメージを与える" +title = "獣の調教師" +description = "ペットが合計500ダメージを与える" [advancement.challenge_pet_dmg_5k] - title = "戦の支配者" - description = "ペットが合計5,000ダメージを与える" +title = "戦の支配者" +description = "ペットが合計5,000ダメージを与える" [advancement.challenge_tamed_10] - title = "動物の友" - description = "10匹の動物を手懐ける" +title = "動物の友" +description = "10匹の動物を手懐ける" [advancement.challenge_tamed_100] - title = "動物園の管理人" - description = "100匹の動物を手懐ける" +title = "動物園の管理人" +description = "100匹の動物を手懐ける" [advancement.challenge_pet_kills_25] - title = "群れの戦術" - description = "ペットが25体の生物を倒す" +title = "群れの戦術" +description = "ペットが25体の生物を倒す" [advancement.challenge_pet_kills_250] - title = "群れの指揮官" - description = "ペットが250体の生物を倒す" +title = "群れの指揮官" +description = "ペットが250体の生物を倒す" [advancement.challenge_taming_2500] - title = "繁殖の専門家" - description = "2,500匹の動物を繁殖させる" +title = "繁殖の専門家" +description = "2,500匹の動物を繁殖させる" [advancement.challenge_taming_25k] - title = "遺伝学の達人" - description = "25,000匹の動物を繁殖させる" +title = "遺伝学の達人" +description = "25,000匹の動物を繁殖させる" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "打たれ役" - description = "500回攻撃を受ける" +title = "打たれ役" +description = "500回攻撃を受ける" [advancement.challenge_trag_hits_5k] - title = "痛みの虜" - description = "5,000回攻撃を受ける" +title = "痛みの虜" +description = "5,000回攻撃を受ける" [advancement.challenge_trag_deaths_10] - title = "九つの命" - description = "10回死ぬ" +title = "九つの命" +description = "10回死ぬ" [advancement.challenge_trag_deaths_100] - title = "繰り返す悪夢" - description = "100回死ぬ" +title = "繰り返す悪夢" +description = "100回死ぬ" [advancement.challenge_trag_fire_500] - title = "炎の犠牲者" - description = "炎ダメージを500耐える" +title = "炎の犠牲者" +description = "炎ダメージを500耐える" [advancement.challenge_trag_fire_5k] - title = "不死鳥" - description = "炎ダメージを5,000耐える" +title = "不死鳥" +description = "炎ダメージを5,000耐える" [advancement.challenge_trag_fall_500] - title = "重力確認" - description = "落下ダメージを500耐える" +title = "重力確認" +description = "落下ダメージを500耐える" [advancement.challenge_trag_fall_5k] - title = "終端速度" - description = "落下ダメージを5,000耐える" +title = "終端速度" +description = "落下ダメージを5,000耐える" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "喧嘩屋" - description = "素手で1,000ダメージを与える" +title = "喧嘩屋" +description = "素手で1,000ダメージを与える" [advancement.challenge_unarmed_dmg_10k] - title = "武術家" - description = "素手で10,000ダメージを与える" +title = "武術家" +description = "素手で10,000ダメージを与える" [advancement.challenge_unarmed_kills_25] - title = "素手の拳" - description = "素手で25体のモブを倒す" +title = "素手の拳" +description = "素手で25体のモブを倒す" [advancement.challenge_unarmed_kills_250] - title = "拳の伝説" - description = "素手で250体のモブを倒す" +title = "拳の伝説" +description = "素手で250体のモブを倒す" [advancement.challenge_unarmed_crit_25] - title = "会心の拳" - description = "素手でクリティカルヒットを25回決める" +title = "会心の拳" +description = "素手でクリティカルヒットを25回決める" [advancement.challenge_unarmed_crit_250] - title = "精密の拳" - description = "素手でクリティカルヒットを250回決める" +title = "精密の拳" +description = "素手でクリティカルヒットを250回決める" [advancement.challenge_unarmed_heavy_25] - title = "剛拳" - description = "素手で強力な一撃(6ダメージ超)を25回決める" +title = "剛拳" +description = "素手で強力な一撃(6ダメージ超)を25回決める" [advancement.challenge_unarmed_heavy_250] - title = "一撃必殺" - description = "素手で強力な一撃(6ダメージ超)を250回決める" +title = "一撃必殺" +description = "素手で強力な一撃(6ダメージ超)を250回決める" # items [items] [items.bound_ender_peral] - name = "聖遺物ポートキー" - usage1 = "Shift + 左クリックでバインド" - usage2 = "右クリックでバインドされたインベントリにアクセス" +name = "聖遺物ポートキー" +usage1 = "Shift + 左クリックでバインド" +usage2 = "右クリックでバインドされたインベントリにアクセス" [items.bound_eye_of_ender] - name = "眼球アンカー" - usage1 = "右クリックで消費し、バインドされた場所にテレポート" - usage2 = "Shift + 左クリックでブロックにバインド" +name = "眼球アンカー" +usage1 = "右クリックで消費し、バインドされた場所にテレポート" +usage2 = "Shift + 左クリックでブロックにバインド" [items.bound_redstone_torch] - name = "レッドストーンリモコン" - usage1 = "右クリックで1ティックのレッドストーンパルスを作成" - usage2 = "Shift + 左クリックで「ターゲット」ブロックにバインド" +name = "レッドストーンリモコン" +usage1 = "右クリックで1ティックのレッドストーンパルスを作成" +usage2 = "Shift + 左クリックで「ターゲット」ブロックにバインド" [items.bound_snowball] - name = "ウェブスネア!" - usage1 = "投げるとその場所に一時的なクモの巣の罠が作成される" +name = "ウェブスネア!" +usage1 = "投げるとその場所に一時的なクモの巣の罠が作成される" [items.chrono_time_bottle] - name = "時のボトル" - usage1 = "インベントリにある間、受動的に時間を蓄える" - usage2 = "時間経過するブロックや子どもの動物を右クリックして蓄えた時間を消費" - stored = "蓄えた時間" +name = "時のボトル" +usage1 = "インベントリにある間、受動的に時間を蓄える" +usage2 = "時間経過するブロックや子どもの動物を右クリックして蓄えた時間を消費" +stored = "蓄えた時間" [items.chrono_time_bomb] - name = "タイムボム" - usage1 = "右クリックで時空の場を生成するクロノボルトを発射" +name = "タイムボム" +usage1 = "右クリックで時空の場を生成するクロノボルトを発射" [items.elevator_block] - name = "エレベーターブロック" - usage1 = "ジャンプで上にテレポート" - usage2 = "シフトで下にテレポート" - usage3 = "エレベーター間には最低2ブロックの空間が必要" +name = "エレベーターブロック" +usage1 = "ジャンプで上にテレポート" +usage2 = "シフトで下にテレポート" +usage3 = "エレベーター間には最低2ブロックの空間が必要" # snippets [snippets] [snippets.gui] - level = "レベル" - knowledge = "知識" - power_used = "使用パワー" - not_learned = "未習得" - xp = "XPまで" - welcome = "ようこそ!" - welcome_back = "おかえりなさい!" - xp_bonus_for_time = "XP対象" - max_ability_power = "最大アビリティパワー" - unlock_this_by_clicking = "右クリックでアンロック:" - back = "戻る" - unlearn_all = "すべて忘れる" - unlearned_all = "すべて忘却した" +level = "レベル" +knowledge = "知識" +power_used = "使用パワー" +not_learned = "未習得" +xp = "XPまで" +welcome = "ようこそ!" +welcome_back = "おかえりなさい!" +xp_bonus_for_time = "XP対象" +max_ability_power = "最大アビリティパワー" +unlock_this_by_clicking = "右クリックでアンロック:" +back = "戻る" +unlearn_all = "すべて忘れる" +unlearned_all = "すべて忘却した" [snippets.adapt_menu] - may_not_unlearn = "忘却不可" - may_unlearn = "習得/忘却可能" - knowledge_cost = "知識コスト" - knowledge_available = "利用可能な知識" - already_learned = "習得済み" - unlearn_refund = "クリックして忘却し返金する" - no_refunds = "ハードコア:返金無効" - knowledge = "知識" - click_learn = "クリックして習得" - no_knowledge = "(知識がありません)" - you_only_have = "所持しているのは" - how_to_level_up = "スキルをレベルアップして最大パワーを上げましょう。" - not_enough_power = "パワーが足りません!各アビリティレベルには1パワー必要です。" - power = "パワー" - power_drain = "パワー消費" - learned = "習得した " - unlearned = "忘却した " - activator_block = "本棚" +may_not_unlearn = "忘却不可" +may_unlearn = "習得/忘却可能" +knowledge_cost = "知識コスト" +knowledge_available = "利用可能な知識" +already_learned = "習得済み" +unlearn_refund = "クリックして忘却し返金する" +no_refunds = "ハードコア:返金無効" +knowledge = "知識" +click_learn = "クリックして習得" +no_knowledge = "(知識がありません)" +you_only_have = "所持しているのは" +how_to_level_up = "スキルをレベルアップして最大パワーを上げましょう。" +not_enough_power = "パワーが足りません!各アビリティレベルには1パワー必要です。" +power = "パワー" +power_drain = "パワー消費" +learned = "習得した " +unlearned = "忘却した " +activator_block = "本棚" [snippets.knowledge_orb] - contains = "含む" - knowledge = "知識" - rightclick = "右クリック" - togainknowledge = "でこの知識を得る" - knowledge_orb = "知識のオーブ" +contains = "含む" +knowledge = "知識" +rightclick = "右クリック" +togainknowledge = "でこの知識を得る" +knowledge_orb = "知識のオーブ" [snippets.experience_orb] - contains = "含む" - xp = "経験値" - rightclick = "右クリック" - togainxp = "でこの経験値を得る" - xporb = "経験値オーブ" +contains = "含む" +xp = "経験値" +rightclick = "右クリック" +togainxp = "でこの経験値を得る" +xporb = "経験値オーブ" # skill [skill] [skill.agility] - name = "敏捷性" - icon = "⇉" - description = "敏捷性とは、障害物に直面しても素早くしなやかに動く能力である。" +name = "敏捷性" +icon = "⇉" +description = "敏捷性とは、障害物に直面しても素早くしなやかに動く能力である。" [skill.architect] - name = "建築家" - icon = "⬧" - description = "構造は世界の基盤である。現実はあなたの手の中にあり、あなたがコントロールする。" +name = "建築家" +icon = "⬧" +description = "構造は世界の基盤である。現実はあなたの手の中にあり、あなたがコントロールする。" [skill.axes] - name = "斧" - icon = "🪓" - description1 = "なぜ木を切り倒すのか、" - description2 = "モノ" - description3 = "を切った方が結果は同じだろう!" +name = "斧" +icon = "🪓" +description1 = "なぜ木を切り倒すのか、" +description2 = "モノ" +description3 = "を切った方が結果は同じだろう!" [skill.brewing] - name = "醸造" - icon = "❦" - description = "ダブルバブル、トリプルバブル、クアドラプルバブル――まだこのポーションを大釜に入れられない" +name = "醸造" +icon = "❦" +description = "ダブルバブル、トリプルバブル、クアドラプルバブル――まだこのポーションを大釜に入れられない" [skill.blocking] - name = "防御" - icon = "🛡" - description = "棒や石では骨は折れない、だが盾なら折れる。" +name = "防御" +icon = "🛡" +description = "棒や石では骨は折れない、だが盾なら折れる。" [skill.crafting] - name = "クラフト" - icon = "⌂" - description = "置くピースがもう無いなら、新しく作ればいいじゃない?" +name = "クラフト" +icon = "⌂" +description = "置くピースがもう無いなら、新しく作ればいいじゃない?" [skill.discovery] - name = "発見" - icon = "⚛" - description = "知覚が広がるにつれ、あなたの心は解き放たれ、見えなかったものが見えてくる。" +name = "発見" +icon = "⚛" +description = "知覚が広がるにつれ、あなたの心は解き放たれ、見えなかったものが見えてくる。" [skill.enchanting] - name = "エンチャント" - icon = "♰" - description = "何を言っているんだ?予言、幻視、迷信的なたわ言?" +name = "エンチャント" +icon = "♰" +description = "何を言っているんだ?予言、幻視、迷信的なたわ言?" [skill.excavation] - name = "発掘" - icon = "ᛳ" - description = "掘って掘って穴を掘る…" +name = "発掘" +icon = "ᛳ" +description = "掘って掘って穴を掘る…" [skill.herbalism] - name = "薬草学" - icon = "⚘" - description = "植物が見つからないが、種なら見つかる…これはもしかして、雑草?" +name = "薬草学" +icon = "⚘" +description = "植物が見つからないが、種なら見つかる…これはもしかして、雑草?" [skill.hunter] - name = "ハンター" - icon = "☠" - description = "狩りとは結果ではなく、その道のりにある。" +name = "ハンター" +icon = "☠" +description = "狩りとは結果ではなく、その道のりにある。" [skill.nether] - name = "ネザー" - icon = "₪" - description = "ネザーの深淵そのものより。" +name = "ネザー" +icon = "₪" +description = "ネザーの深淵そのものより。" [skill.pickaxe] - name = "つるはし" - icon = "⛏" - description = "ドワーフが鉱夫だが、俺も多少は学んだ。俺はスウェーデン人だ!" +name = "つるはし" +icon = "⛏" +description = "ドワーフが鉱夫だが、俺も多少は学んだ。俺はスウェーデン人だ!" [skill.ranged] - name = "遠距離" - icon = "🏹" - description = "距離こそが勝利の鍵であり、生存の鍵である。" +name = "遠距離" +icon = "🏹" +description = "距離こそが勝利の鍵であり、生存の鍵である。" [skill.rift] - name = "リフト" - icon = "❍" - description = "リフトは危険な力だが、あなたはその力を手懐けた。" +name = "リフト" +icon = "❍" +description = "リフトは危険な力だが、あなたはその力を手懐けた。" [skill.seaborne] - name = "海の民" - icon = "🎣" - description = "このスキルで、水の奇跡を自在に操ることができる。" +name = "海の民" +icon = "🎣" +description = "このスキルで、水の奇跡を自在に操ることができる。" [skill.stealth] - name = "隠密" - icon = "☯" - description = "見えざる者の技。影の中を歩け。" +name = "隠密" +icon = "☯" +description = "見えざる者の技。影の中を歩け。" [skill.swords] - name = "剣術" - icon = "⚔" - description = "グレイストーンの力をもって!" +name = "剣術" +icon = "⚔" +description = "グレイストーンの力をもって!" [skill.taming] - name = "飼育" - icon = "♥" - description = "オウムとミツバチ…それと、あなたは?" +name = "飼育" +icon = "♥" +description = "オウムとミツバチ…それと、あなたは?" [skill.tragoul] - name = "トラゴウル" - icon = "🗡" - description = "血は宇宙の血管を流れる。あなたの手で締め付けられながら。" +name = "トラゴウル" +icon = "🗡" +description = "血は宇宙の血管を流れる。あなたの手で締め付けられながら。" [skill.chronos] - name = "クロノス" - icon = "🕒" - description = "宇宙の時計を巻き、流れを体験せよ。時計を壊し、時計そのものになれ。" +name = "クロノス" +icon = "🕒" +description = "宇宙の時計を巻き、流れを体験せよ。時計を壊し、時計そのものになれ。" [skill.unarmed] - name = "素手" - icon = "»" - description = "武器が無くとも、力が無いわけではない。" +name = "素手" +icon = "»" +description = "武器が無くとも、力が無いわけではない。" # agility [agility] [agility.armor_up] - name = "アーマーアップ" - description = "ダッシュし続けるほど防具が強化される!" - lore1 = "最大防具値" - lore2 = "防具強化時間" - lore = ["最大防具値", "防具強化時間"] +name = "アーマーアップ" +description = "ダッシュし続けるほど防具が強化される!" +lore1 = "最大防具値" +lore2 = "防具強化時間" +lore = ["最大防具値", "防具強化時間"] [agility.ladder_slide] - name = "ハシゴスライド" - description = "ハシゴの上り下りがどちらも大幅に速くなる。" - lore1 = "ハシゴ速度倍率" - lore2 = "高速下降速度" - lore = ["ハシゴ速度倍率", "高速下降速度"] +name = "ハシゴスライド" +description = "ハシゴの上り下りがどちらも大幅に速くなる。" +lore1 = "ハシゴ速度倍率" +lore2 = "高速下降速度" +lore = ["ハシゴ速度倍率", "高速下降速度"] [agility.super_jump] - name = "スーパージャンプ" - description = "圧倒的な高さのアドバンテージ。" - lore1 = "最大ジャンプ高度" - lore2 = "スニーク + ジャンプでスーパージャンプ!" - lore = ["最大ジャンプ高度", "スニーク + ジャンプでスーパージャンプ!"] +name = "スーパージャンプ" +description = "圧倒的な高さのアドバンテージ。" +lore1 = "最大ジャンプ高度" +lore2 = "スニーク + ジャンプでスーパージャンプ!" +lore = ["最大ジャンプ高度", "スニーク + ジャンプでスーパージャンプ!"] [agility.wall_jump] - name = "壁ジャンプ" - description = "空中で壁に向かってシフトを押し続けると壁に張り付いてジャンプ!" - lore1 = "最大ジャンプ回数" - lore2 = "ジャンプの高さ" - lore = ["最大ジャンプ回数", "ジャンプの高さ"] +name = "壁ジャンプ" +description = "空中で壁に向かってシフトを押し続けると壁に張り付いてジャンプ!" +lore1 = "最大ジャンプ回数" +lore2 = "ジャンプの高さ" +lore = ["最大ジャンプ回数", "ジャンプの高さ"] [agility.wind_up] - name = "ウィンドアップ" - description = "ダッシュし続けるほど加速する!" - lore1 = "最高速度" - lore2 = "加速時間" - lore = ["最高速度", "加速時間"] +name = "ウィンドアップ" +description = "ダッシュし続けるほど加速する!" +lore1 = "最高速度" +lore2 = "加速時間" +lore = ["最高速度", "加速時間"] # architect [architect] [architect.elevator] - name = "エレベーター" - description = "垂直方向に高速テレポートするエレベーターを建設できる!" - lore1 = "エレベーターのレシピを解放:X=羊毛、Y=エンダーパール" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["エレベーターのレシピを解放:X=羊毛、Y=エンダーパール", "XXX", "XYX", "XXX"] +name = "エレベーター" +description = "垂直方向に高速テレポートするエレベーターを建設できる!" +lore1 = "エレベーターのレシピを解放:X=羊毛、Y=エンダーパール" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["エレベーターのレシピを解放:X=羊毛、Y=エンダーパール", "XXX", "XYX", "XXX"] [architect.foundation] - name = "魔法の足場" - description = "スニークして自分の足元に仮の足場を生成できる!" - lore1 = "魔法で生成:" - lore2 = "足元にブロック!" - lore = ["魔法で生成:", "足元にブロック!"] +name = "魔法の足場" +description = "スニークして自分の足元に仮の足場を生成できる!" +lore1 = "魔法で生成:" +lore2 = "足元にブロック!" +lore = ["魔法で生成:", "足元にブロック!"] [architect.glass] - name = "シルクタッチガラス" - description = "素手でガラスブロックを壊してもガラスが失われなくなる!" - lore1 = "素手にガラスへのシルクタッチ効果を付与" - lore = ["素手にガラスへのシルクタッチ効果を付与"] +name = "シルクタッチガラス" +description = "素手でガラスブロックを壊してもガラスが失われなくなる!" +lore1 = "素手にガラスへのシルクタッチ効果を付与" +lore = ["素手にガラスへのシルクタッチ効果を付与"] [architect.wireless_redstone] - name = "レッドストーンリモコン" - description = "レッドストーンのたいまつを使って遠隔でレッドストーンを切り替えられる!" - lore1 = "ターゲット + レッドストーンのたいまつ + エンダーパール = レッドストーンリモコン1個" - lore = ["ターゲット + レッドストーンのたいまつ + エンダーパール = レッドストーンリモコン1個"] +name = "レッドストーンリモコン" +description = "レッドストーンのたいまつを使って遠隔でレッドストーンを切り替えられる!" +lore1 = "ターゲット + レッドストーンのたいまつ + エンダーパール = レッドストーンリモコン1個" +lore = ["ターゲット + レッドストーンのたいまつ + エンダーパール = レッドストーンリモコン1個"] [architect.placement] - name = "ビルダーワンド" - description = "複数のブロックを一度に設置可能!スニークして、見ているブロックと同じブロックを手に持って設置しよう。範囲をトリガーするには少し動く必要がある場合もある!" - lore1 = "必要数" - lore2 = "ブロックを手に持って設置する" - lore3 = "素材ビルダーワンド" - lore = ["必要数", "ブロックを手に持って設置する", "素材ビルダーワンド"] +name = "ビルダーワンド" +description = "複数のブロックを一度に設置可能!スニークして、見ているブロックと同じブロックを手に持って設置しよう。範囲をトリガーするには少し動く必要がある場合もある!" +lore1 = "必要数" +lore2 = "ブロックを手に持って設置する" +lore3 = "素材ビルダーワンド" +lore = ["必要数", "ブロックを手に持って設置する", "素材ビルダーワンド"] # axe [axe] [axe.chop] - name = "斧で伐採" - description = "木の根元を右クリックして一気に伐採!" - lore1 = "1回の伐採ブロック数" - lore2 = "伐採クールダウン" - lore3 = "ツール消耗" - lore = ["1回の伐採ブロック数", "伐採クールダウン", "ツール消耗"] +name = "斧で伐採" +description = "木の根元を右クリックして一気に伐採!" +lore1 = "1回の伐採ブロック数" +lore2 = "伐採クールダウン" +lore3 = "ツール消耗" +lore = ["1回の伐採ブロック数", "伐採クールダウン", "ツール消耗"] [axe.log_swap] - name = "ルーシーの丸太交換" - description = "作業台で丸太の種類を変えよう!" - lore1 = "任意の丸太8本 + 苗木1本 = 苗木の種類の丸太8本" - lore = ["任意の丸太8本 + 苗木1本 = 苗木の種類の丸太8本"] +name = "ルーシーの丸太交換" +description = "作業台で丸太の種類を変えよう!" +lore1 = "任意の丸太8本 + 苗木1本 = 苗木の種類の丸太8本" +lore = ["任意の丸太8本 + 苗木1本 = 苗木の種類の丸太8本"] [axe.drop_to_inventory] - name = "斧ドロップ→インベントリ" +name = "斧ドロップ→インベントリ" [axe.ground_smash] - name = "斧グラウンドスマッシュ" - description = "ジャンプしてからスニークで周囲の敵を叩き潰す。" - lore1 = "ダメージ" - lore2 = "ブロック半径" - lore3 = "吹き飛ばし力" - lore4 = "スマッシュクールダウン" - lore = ["ダメージ", "ブロック半径", "吹き飛ばし力", "スマッシュクールダウン"] +name = "斧グラウンドスマッシュ" +description = "ジャンプしてからスニークで周囲の敵を叩き潰す。" +lore1 = "ダメージ" +lore2 = "ブロック半径" +lore3 = "吹き飛ばし力" +lore4 = "スマッシュクールダウン" +lore = ["ダメージ", "ブロック半径", "吹き飛ばし力", "スマッシュクールダウン"] [axe.leaf_miner] - name = "リーフマイナー" - description = "大量の葉を一度に破壊できる!" - lore1 = "スニークして葉を採掘" - lore2 = "リーフマイニング範囲" - lore3 = "葉からのドロップは取得不可(悪用防止)" - lore = ["スニークして葉を採掘", "リーフマイニング範囲", "葉からのドロップは取得不可(悪用防止)"] +name = "リーフマイナー" +description = "大量の葉を一度に破壊できる!" +lore1 = "スニークして葉を採掘" +lore2 = "リーフマイニング範囲" +lore3 = "葉からのドロップは取得不可(悪用防止)" +lore = ["スニークして葉を採掘", "リーフマイニング範囲", "葉からのドロップは取得不可(悪用防止)"] [axe.wood_miner] - name = "ウッドマイナー" - description = "大量の木材を一度に破壊できる!" - lore1 = "スニークして木材/原木を採掘(板材は対象外)" - lore2 = "ウッドマイニング範囲" - lore3 = "ドロップ→インベントリ対応" - lore = ["スニークして木材/原木を採掘(板材は対象外)", "ウッドマイニング範囲", "ドロップ→インベントリ対応"] +name = "ウッドマイナー" +description = "大量の木材を一度に破壊できる!" +lore1 = "スニークして木材/原木を採掘(板材は対象外)" +lore2 = "ウッドマイニング範囲" +lore3 = "ドロップ→インベントリ対応" +lore = ["スニークして木材/原木を採掘(板材は対象外)", "ウッドマイニング範囲", "ドロップ→インベントリ対応"] # brewing [brewing] [brewing.lingering] - name = "残留醸造" - description = "醸造したポーションの効果が長持ちする!" - lore1 = "持続時間" - lore2 = "持続時間" - lore = ["持続時間", "持続時間"] +name = "残留醸造" +description = "醸造したポーションの効果が長持ちする!" +lore1 = "持続時間" +lore2 = "持続時間" +lore = ["持続時間", "持続時間"] [brewing.super_heated] - name = "超加熱醸造" - description = "醸造台は熱いほど速く動作する。" - lore1 = "接触する火ブロック1つにつき" - lore2 = "接触する溶岩ブロック1つにつき" - lore = ["接触する火ブロック1つにつき", "接触する溶岩ブロック1つにつき"] +name = "超加熱醸造" +description = "醸造台は熱いほど速く動作する。" +lore1 = "接触する火ブロック1つにつき" +lore2 = "接触する溶岩ブロック1つにつき" +lore = ["接触する火ブロック1つにつき", "接触する溶岩ブロック1つにつき"] [brewing.darkness] - name = "瓶詰めの暗闇" - description = "なぜこれが必要かは分からないけど、はいどうぞ!" - lore1 = "暗視ポーション + 黒コンクリート = 暗闇のポーション(30秒)" - lore2 = "注意:このポーションを使うとダッシュできなくなる!" - lore = ["暗視ポーション + 黒コンクリート = 暗闇のポーション(30秒)", "注意:このポーションを使うとダッシュできなくなる!"] +name = "瓶詰めの暗闇" +description = "なぜこれが必要かは分からないけど、はいどうぞ!" +lore1 = "暗視ポーション + 黒コンクリート = 暗闇のポーション(30秒)" +lore2 = "注意:このポーションを使うとダッシュできなくなる!" +lore = ["暗視ポーション + 黒コンクリート = 暗闇のポーション(30秒)", "注意:このポーションを使うとダッシュできなくなる!"] [brewing.haste] - name = "瓶詰めの採掘速度上昇" - description = "効率強化では足りない時に" - lore1 = "俊敏のポーション + アメジストの欠片 = 採掘速度上昇のポーション(60秒)" - lore2 = "俊敏のポーション + アメジストブロック = 採掘速度上昇IIのポーション(30秒)" - lore = ["俊敏のポーション + アメジストの欠片 = 採掘速度上昇のポーション(60秒)", "俊敏のポーション + アメジストブロック = 採掘速度上昇IIのポーション(30秒)"] +name = "瓶詰めの採掘速度上昇" +description = "効率強化では足りない時に" +lore1 = "俊敏のポーション + アメジストの欠片 = 採掘速度上昇のポーション(60秒)" +lore2 = "俊敏のポーション + アメジストブロック = 採掘速度上昇IIのポーション(30秒)" +lore = ["俊敏のポーション + アメジストの欠片 = 採掘速度上昇のポーション(60秒)", "俊敏のポーション + アメジストブロック = 採掘速度上昇IIのポーション(30秒)"] [brewing.absorption] - name = "瓶詰めの衝撃吸収" - description = "体を硬くしろ!" - lore1 = "即時回復 + クォーツ = 衝撃吸収のポーション(60秒)" - lore2 = "即時回復 + クォーツブロック = 衝撃吸収IIのポーション(30秒)" - lore = ["即時回復 + クォーツ = 衝撃吸収のポーション(60秒)", "即時回復 + クォーツブロック = 衝撃吸収IIのポーション(30秒)"] +name = "瓶詰めの衝撃吸収" +description = "体を硬くしろ!" +lore1 = "即時回復 + クォーツ = 衝撃吸収のポーション(60秒)" +lore2 = "即時回復 + クォーツブロック = 衝撃吸収IIのポーション(30秒)" +lore = ["即時回復 + クォーツ = 衝撃吸収のポーション(60秒)", "即時回復 + クォーツブロック = 衝撃吸収IIのポーション(30秒)"] [brewing.fatigue] - name = "瓶詰めの採掘疲労" - description = "体を弱体化させろ!" - lore1 = "弱体化のポーション + スライムボール = 採掘疲労のポーション(30秒)" - lore2 = "弱体化のポーション + スライムブロック = 採掘疲労IIのポーション(15秒)" - lore = ["弱体化のポーション + スライムボール = 採掘疲労のポーション(30秒)", "弱体化のポーション + スライムブロック = 採掘疲労IIのポーション(15秒)"] +name = "瓶詰めの採掘疲労" +description = "体を弱体化させろ!" +lore1 = "弱体化のポーション + スライムボール = 採掘疲労のポーション(30秒)" +lore2 = "弱体化のポーション + スライムブロック = 採掘疲労IIのポーション(15秒)" +lore = ["弱体化のポーション + スライムボール = 採掘疲労のポーション(30秒)", "弱体化のポーション + スライムブロック = 採掘疲労IIのポーション(15秒)"] [brewing.hunger] - name = "瓶詰めの空腹" - description = "飽くなき食欲に!" - lore1 = "奇妙なポーション + 腐った肉 = 空腹のポーション(30秒)" - lore2 = "弱体化のポーション + 腐った肉 = 空腹IIIのポーション(15秒)" - lore = ["奇妙なポーション + 腐った肉 = 空腹のポーション(30秒)", "弱体化のポーション + 腐った肉 = 空腹IIIのポーション(15秒)"] +name = "瓶詰めの空腹" +description = "飽くなき食欲に!" +lore1 = "奇妙なポーション + 腐った肉 = 空腹のポーション(30秒)" +lore2 = "弱体化のポーション + 腐った肉 = 空腹IIIのポーション(15秒)" +lore = ["奇妙なポーション + 腐った肉 = 空腹のポーション(30秒)", "弱体化のポーション + 腐った肉 = 空腹IIIのポーション(15秒)"] [brewing.nausea] - name = "瓶詰めの吐き気" - description = "気分が悪くなるぞ!" - lore1 = "奇妙なポーション + 茶色キノコ = 吐き気のポーション(16秒)" - lore2 = "奇妙なポーション + 真紅のキノコ = 吐き気IIのポーション(8秒)" - lore = ["奇妙なポーション + 茶色キノコ = 吐き気のポーション(16秒)", "奇妙なポーション + 真紅のキノコ = 吐き気IIのポーション(8秒)"] +name = "瓶詰めの吐き気" +description = "気分が悪くなるぞ!" +lore1 = "奇妙なポーション + 茶色キノコ = 吐き気のポーション(16秒)" +lore2 = "奇妙なポーション + 真紅のキノコ = 吐き気IIのポーション(8秒)" +lore = ["奇妙なポーション + 茶色キノコ = 吐き気のポーション(16秒)", "奇妙なポーション + 真紅のキノコ = 吐き気IIのポーション(8秒)"] [brewing.blindness] - name = "瓶詰めの盲目" - description = "あなたはひどい人だ…" - lore1 = "奇妙なポーション + イカスミ = 盲目のポーション(30秒)" - lore2 = "奇妙なポーション + 輝くイカスミ = 盲目IIのポーション(15秒)" - lore = ["奇妙なポーション + イカスミ = 盲目のポーション(30秒)", "奇妙なポーション + 輝くイカスミ = 盲目IIのポーション(15秒)"] +name = "瓶詰めの盲目" +description = "あなたはひどい人だ…" +lore1 = "奇妙なポーション + イカスミ = 盲目のポーション(30秒)" +lore2 = "奇妙なポーション + 輝くイカスミ = 盲目IIのポーション(15秒)" +lore = ["奇妙なポーション + イカスミ = 盲目のポーション(30秒)", "奇妙なポーション + 輝くイカスミ = 盲目IIのポーション(15秒)"] [brewing.resistance] - name = "瓶詰めの耐性" - description = "究極の防御!" - lore1 = "奇妙なポーション + 鉄インゴット = 耐性のポーション(60秒)" - lore2 = "奇妙なポーション + 鉄ブロック = 耐性IIのポーション(30秒)" - lore = ["奇妙なポーション + 鉄インゴット = 耐性のポーション(60秒)", "奇妙なポーション + 鉄ブロック = 耐性IIのポーション(30秒)"] +name = "瓶詰めの耐性" +description = "究極の防御!" +lore1 = "奇妙なポーション + 鉄インゴット = 耐性のポーション(60秒)" +lore2 = "奇妙なポーション + 鉄ブロック = 耐性IIのポーション(30秒)" +lore = ["奇妙なポーション + 鉄インゴット = 耐性のポーション(60秒)", "奇妙なポーション + 鉄ブロック = 耐性IIのポーション(30秒)"] [brewing.health_boost] - name = "瓶詰めの生命力" - description = "最大体力では足りない時に…" - lore1 = "即時回復ポーション + 金のリンゴ = 体力増強のポーション(120秒)" - lore2 = "即時回復ポーション + エンチャント金のリンゴ = 体力増強IIのポーション(120秒)" - lore = ["即時回復ポーション + 金のリンゴ = 体力増強のポーション(120秒)", "即時回復ポーション + エンチャント金のリンゴ = 体力増強IIのポーション(120秒)"] +name = "瓶詰めの生命力" +description = "最大体力では足りない時に…" +lore1 = "即時回復ポーション + 金のリンゴ = 体力増強のポーション(120秒)" +lore2 = "即時回復ポーション + エンチャント金のリンゴ = 体力増強IIのポーション(120秒)" +lore = ["即時回復ポーション + 金のリンゴ = 体力増強のポーション(120秒)", "即時回復ポーション + エンチャント金のリンゴ = 体力増強IIのポーション(120秒)"] [brewing.decay] - name = "瓶詰めの衰退" - description = "腐敗物がこんなに役立つとは!" - lore1 = "弱体化のポーション + 毒のジャガイモ = ウィザーのポーション(16秒)" - lore2 = "弱体化のポーション + 真紅の根 = ウィザーIIのポーション(8秒)" - lore = ["弱体化のポーション + 毒のジャガイモ = ウィザーのポーション(16秒)", "弱体化のポーション + 真紅の根 = ウィザーIIのポーション(8秒)"] +name = "瓶詰めの衰退" +description = "腐敗物がこんなに役立つとは!" +lore1 = "弱体化のポーション + 毒のジャガイモ = ウィザーのポーション(16秒)" +lore2 = "弱体化のポーション + 真紅の根 = ウィザーIIのポーション(8秒)" +lore = ["弱体化のポーション + 毒のジャガイモ = ウィザーのポーション(16秒)", "弱体化のポーション + 真紅の根 = ウィザーIIのポーション(8秒)"] [brewing.saturation] - name = "瓶詰めの満腹" - description = "なあ…もうお腹いっぱいなんだけど…" - lore1 = "再生のポーション + ベイクドポテト = 満腹のポーション" - lore2 = "再生のポーション + 干し草の俵 = 満腹IIのポーション" - lore = ["再生のポーション + ベイクドポテト = 満腹のポーション", "再生のポーション + 干し草の俵 = 満腹IIのポーション"] +name = "瓶詰めの満腹" +description = "なあ…もうお腹いっぱいなんだけど…" +lore1 = "再生のポーション + ベイクドポテト = 満腹のポーション" +lore2 = "再生のポーション + 干し草の俵 = 満腹IIのポーション" +lore = ["再生のポーション + ベイクドポテト = 満腹のポーション", "再生のポーション + 干し草の俵 = 満腹IIのポーション"] # blocking [blocking] [blocking.chain_armorer] - name = "メフィストフェレスの鎖" - description = "チェーン装備をクラフトできるようになる" - lore1 = "クラフトレシピは通常の防具と同じだが、鉄塊を使用する" - lore = ["クラフトレシピは通常の防具と同じだが、鉄塊を使用する"] +name = "メフィストフェレスの鎖" +description = "チェーン装備をクラフトできるようになる" +lore1 = "クラフトレシピは通常の防具と同じだが、鉄塊を使用する" +lore = ["クラフトレシピは通常の防具と同じだが、鉄塊を使用する"] [blocking.horse_armorer] - name = "クラフト可能な馬鎧" - description = "馬の鎧をクラフトできるようになる" - lore1 = "サドルを使用したい素材で囲んでクラフト" - lore = ["サドルを使用したい素材で囲んでクラフト"] +name = "クラフト可能な馬鎧" +description = "馬の鎧をクラフトできるようになる" +lore1 = "サドルを使用したい素材で囲んでクラフト" +lore = ["サドルを使用したい素材で囲んでクラフト"] [blocking.saddle_crafter] - name = "クラフト可能なサドル" - description = "革でサドルをクラフトする" - lore1 = "レシピ:革5個:" - lore = ["レシピ:革5個:"] +name = "クラフト可能なサドル" +description = "革でサドルをクラフトする" +lore1 = "レシピ:革5個:" +lore = ["レシピ:革5個:"] [blocking.multi_armor] - name = "マルチアーマー" - description = "エリトラを防具に装着する" - lore1 = "移動に最適な素晴らしいスキル。" - lore2 = "防具とエリトラを動的に合体・変更可能!" - lore3 = "合体するには、インベントリ内でアイテムをシフトクリック。" - lore4 = "防具を分離するには、スニークしながらドロップで分解。" - lore5 = "マルチアーマーが破壊されると、中のアイテムはすべて失われる。" - lore6 = "防具なんて要らない、がっかりだ…" - lore = ["移動に最適な素晴らしいスキル。", "防具とエリトラを動的に合体・変更可能!", "合体するには、インベントリ内でアイテムをシフトクリック。", "防具を分離するには、スニークしながらドロップで分解。", "マルチアーマーが破壊されると、中のアイテムはすべて失われる。", "防具なんて要らない、がっかりだ…"] +name = "マルチアーマー" +description = "エリトラを防具に装着する" +lore1 = "移動に最適な素晴らしいスキル。" +lore2 = "防具とエリトラを動的に合体・変更可能!" +lore3 = "合体するには、インベントリ内でアイテムをシフトクリック。" +lore4 = "防具を分離するには、スニークしながらドロップで分解。" +lore5 = "マルチアーマーが破壊されると、中のアイテムはすべて失われる。" +lore6 = "防具なんて要らない、がっかりだ…" +lore = ["移動に最適な素晴らしいスキル。", "防具とエリトラを動的に合体・変更可能!", "合体するには、インベントリ内でアイテムをシフトクリック。", "防具を分離するには、スニークしながらドロップで分解。", "マルチアーマーが破壊されると、中のアイテムはすべて失われる。", "防具なんて要らない、がっかりだ…"] # crafting [crafting] [crafting.deconstruction] - name = "分解" - description = "ブロックやアイテムを再利用可能な基本素材に分解!" - lore1 = "任意のアイテムを地面にドロップ。" - lore2 = "その後、スニークしながらハサミで右クリック" - lore = ["任意のアイテムを地面にドロップ。", "その後、スニークしながらハサミで右クリック"] +name = "分解" +description = "ブロックやアイテムを再利用可能な基本素材に分解!" +lore1 = "任意のアイテムを地面にドロップ。" +lore2 = "その後、スニークしながらハサミで右クリック" +lore = ["任意のアイテムを地面にドロップ。", "その後、スニークしながらハサミで右クリック"] [crafting.xp] - name = "クラフトXP" - description = "クラフト時にパッシブXPを獲得" - lore1 = "クラフト時にXPを獲得" - lore = ["クラフト時にXPを獲得"] +name = "クラフトXP" +description = "クラフト時にパッシブXPを獲得" +lore1 = "クラフト時にXPを獲得" +lore = ["クラフト時にXPを獲得"] [crafting.reconstruction] - name = "鉱石再構築" - description = "基本素材から鉱石を再クラフト!" - lore1 = "ドロップ8個 + ホスト1個 = 鉱石1個(不定形)" - lore2 = "ドロップは製錬済みである必要あり(該当する場合)" - lore3 = "スクラップ、ネザークォーツ、エメラルドなどは対象外" - lore4 = "ホスト = 外殻。例:石、ネザーラック、深層岩" - lore = ["ドロップ8個 + ホスト1個 = 鉱石1個(不定形)", "ドロップは製錬済みである必要あり(該当する場合)", "スクラップ、ネザークォーツ、エメラルドなどは対象外", "ホスト = 外殻。例:石、ネザーラック、深層岩"] +name = "鉱石再構築" +description = "基本素材から鉱石を再クラフト!" +lore1 = "ドロップ8個 + ホスト1個 = 鉱石1個(不定形)" +lore2 = "ドロップは製錬済みである必要あり(該当する場合)" +lore3 = "スクラップ、ネザークォーツ、エメラルドなどは対象外" +lore4 = "ホスト = 外殻。例:石、ネザーラック、深層岩" +lore = ["ドロップ8個 + ホスト1個 = 鉱石1個(不定形)", "ドロップは製錬済みである必要あり(該当する場合)", "スクラップ、ネザークォーツ、エメラルドなどは対象外", "ホスト = 外殻。例:石、ネザーラック、深層岩"] [crafting.leather] - name = "クラフト可能な革" - description = "腐った肉から革をクラフト" - lore1 = "腐った肉を焚き火に乗せるだけ!" - lore = ["腐った肉を焚き火に乗せるだけ!"] +name = "クラフト可能な革" +description = "腐った肉から革をクラフト" +lore1 = "腐った肉を焚き火に乗せるだけ!" +lore = ["腐った肉を焚き火に乗せるだけ!"] [crafting.backpacks] - name = "ブティリエのバックパック!" - description = "Mojangのバンドルをゲームに導入!" - lore1 = "サバイバルモードでのみ使用可能" - lore2 = "XLX:革、リード、革" - lore3 = "XSX:革、樽、革" - lore4 = "XCX:革、チェスト、革" - lore = ["サバイバルモードでのみ使用可能", "XLX:革、リード、革", "XSX:革、樽、革", "XCX:革、チェスト、革"] +name = "ブティリエのバックパック!" +description = "Mojangのバンドルをゲームに導入!" +lore1 = "サバイバルモードでのみ使用可能" +lore2 = "XLX:革、リード、革" +lore3 = "XSX:革、樽、革" +lore4 = "XCX:革、チェスト、革" +lore = ["サバイバルモードでのみ使用可能", "XLX:革、リード、革", "XSX:革、樽、革", "XCX:革、チェスト、革"] [crafting.stations] - name = "ポータブルテーブル!" - description = "手のひらでテーブルを使おう!" - lore2 = "テーブルを閉じた時に残したアイテムは永遠に失われます!" - lore3 = "対応テーブル:金床、作業台、砥石、製図台、石切り台、織機" - lore = ["テーブルを閉じた時に残したアイテムは永遠に失われます!", "対応テーブル:金床、作業台、砥石、製図台、石切り台、織機"] +name = "ポータブルテーブル!" +description = "手のひらでテーブルを使おう!" +lore2 = "テーブルを閉じた時に残したアイテムは永遠に失われます!" +lore3 = "対応テーブル:金床、作業台、砥石、製図台、石切り台、織機" +lore = ["テーブルを閉じた時に残したアイテムは永遠に失われます!", "対応テーブル:金床、作業台、砥石、製図台、石切り台、織機"] [crafting.skulls] - name = "クラフト可能な頭蓋骨!" - description = "素材を使ってモブの頭蓋骨をクラフト!" - lore1 = "骨ブロックを以下の素材で囲んで頭蓋骨を作成:" - lore2 = "ゾンビ:腐った肉" - lore3 = "スケルトン:骨" - lore4 = "クリーパー:火薬" - lore5 = "ウィザー:ネザーレンガ" - lore6 = "ドラゴン:ドラゴンブレス" - lore = ["骨ブロックを以下の素材で囲んで頭蓋骨を作成:", "ゾンビ:腐った肉", "スケルトン:骨", "クリーパー:火薬", "ウィザー:ネザーレンガ", "ドラゴン:ドラゴンブレス"] +name = "クラフト可能な頭蓋骨!" +description = "素材を使ってモブの頭蓋骨をクラフト!" +lore1 = "骨ブロックを以下の素材で囲んで頭蓋骨を作成:" +lore2 = "ゾンビ:腐った肉" +lore3 = "スケルトン:骨" +lore4 = "クリーパー:火薬" +lore5 = "ウィザー:ネザーレンガ" +lore6 = "ドラゴン:ドラゴンブレス" +lore = ["骨ブロックを以下の素材で囲んで頭蓋骨を作成:", "ゾンビ:腐った肉", "スケルトン:骨", "クリーパー:火薬", "ウィザー:ネザーレンガ", "ドラゴン:ドラゴンブレス"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "時のボトル" - description = "時間を蓄える時空のボトルを持ち歩き、蓄えた時間を消費して時間経過ブロック、成長可能ブロック、子どもの動物などのエイジャブルエンティティを加速させる。レシピ(不定形):俊敏のポーション + 時計 + ガラス瓶。" - lore1 = "ティックごとの蓄積秒数" - lore2 = "蓄えた1秒あたりの時間加速" - lore3 = "レシピ(不定形):俊敏のポーション + 時計 + ガラス瓶" - lore = ["ティックごとの蓄積秒数", "蓄えた1秒あたりの時間加速", "レシピ(不定形):俊敏のポーション + 時計 + ガラス瓶"] +name = "時のボトル" +description = "時間を蓄える時空のボトルを持ち歩き、蓄えた時間を消費して時間経過ブロック、成長可能ブロック、子どもの動物などのエイジャブルエンティティを加速させる。レシピ(不定形):俊敏のポーション + 時計 + ガラス瓶。" +lore1 = "ティックごとの蓄積秒数" +lore2 = "蓄えた1秒あたりの時間加速" +lore3 = "レシピ(不定形):俊敏のポーション + 時計 + ガラス瓶" +lore = ["ティックごとの蓄積秒数", "蓄えた1秒あたりの時間加速", "レシピ(不定形):俊敏のポーション + 時計 + ガラス瓶"] [chronos.aberrant_touch] - name = "異常な触れ" - description = "近接攻撃が空腹を消費して鈍化をスタックさせ、厳格なPvP制限付きで5スタック時にターゲットを拘束する。" - lore1 = "近接攻撃で鈍化をスタック" - lore2 = "PvE鈍化持続時間上限" - lore3 = "PvP鈍化増幅上限" - lore = ["近接攻撃で鈍化をスタック", "PvE鈍化持続時間上限", "PvP鈍化増幅上限"] +name = "異常な触れ" +description = "近接攻撃が空腹を消費して鈍化をスタックさせ、厳格なPvP制限付きで5スタック時にターゲットを拘束する。" +lore1 = "近接攻撃で鈍化をスタック" +lore2 = "PvE鈍化持続時間上限" +lore3 = "PvP鈍化増幅上限" +lore = ["近接攻撃で鈍化をスタック", "PvE鈍化持続時間上限", "PvP鈍化増幅上限"] [chronos.instant_recall] - name = "インスタントリコール" - description = "時計を手に持って左または右クリックすると、体力と空腹が回復された状態で最近のスナップショットに巻き戻す。" - lore1 = "巻き戻し時間" - lore2 = "クールダウン" - lore3 = "インベントリは巻き戻されない" - lore = ["巻き戻し時間", "クールダウン", "インベントリは巻き戻されない"] +name = "インスタントリコール" +description = "時計を手に持って左または右クリックすると、体力と空腹が回復された状態で最近のスナップショットに巻き戻す。" +lore1 = "巻き戻し時間" +lore2 = "クールダウン" +lore3 = "インベントリは巻き戻されない" +lore = ["巻き戻し時間", "クールダウン", "インベントリは巻き戻されない"] [chronos.time_bomb] - name = "タイムボム" - description = "クラフトしたクロノボムを投げて時空の場を生成し、エンティティを減速させ、飛び道具を凍結させる。" - lore1 = "時空フィールド半径" - lore2 = "時空フィールド持続時間" - lore3 = "ボムクールダウン" - lore4 = "レシピ(不定形):時計 + 雪玉 + ダイヤモンド + 砂" - lore = ["時空フィールド半径", "時空フィールド持続時間", "ボムクールダウン", "レシピ(不定形):時計 + 雪玉 + ダイヤモンド + 砂"] +name = "タイムボム" +description = "クラフトしたクロノボムを投げて時空の場を生成し、エンティティを減速させ、飛び道具を凍結させる。" +lore1 = "時空フィールド半径" +lore2 = "時空フィールド持続時間" +lore3 = "ボムクールダウン" +lore4 = "レシピ(不定形):時計 + 雪玉 + ダイヤモンド + 砂" +lore = ["時空フィールド半径", "時空フィールド持続時間", "ボムクールダウン", "レシピ(不定形):時計 + 雪玉 + ダイヤモンド + 砂"] # discovery [discovery] [discovery.armor] - name = "ワールドアーマー" - description = "周囲のブロック硬度に応じたパッシブ防具。" - lore1 = "パッシブ防具" - lore2 = "周囲のブロック硬度に基づく" - lore3 = "防具強度:" - lore = ["パッシブ防具", "周囲のブロック硬度に基づく", "防具強度:"] +name = "ワールドアーマー" +description = "周囲のブロック硬度に応じたパッシブ防具。" +lore1 = "パッシブ防具" +lore2 = "周囲のブロック硬度に基づく" +lore3 = "防具強度:" +lore = ["パッシブ防具", "周囲のブロック硬度に基づく", "防具強度:"] [discovery.unity] - name = "実験的ユニティ" - description = "経験値オーブを回収するとランダムなスキルにXPが追加される。" - lore1 = "XP " - lore2 = "オーブごと" - lore = ["XP ", "オーブごと"] +name = "実験的ユニティ" +description = "経験値オーブを回収するとランダムなスキルにXPが追加される。" +lore1 = "XP " +lore2 = "オーブごと" +lore = ["XP ", "オーブごと"] [discovery.resist] - name = "実験的レジスタンス" - description = "体力が5ハート以下に落ちるか致命的なダメージを受けた時のみ、経験値を消費してダメージを軽減する。" - lore0 = "クリティカルヘルス時(5ハート以下)にのみ15秒に1回発動" - lore1 = " 軽減ダメージ" - lore2 = "消費経験値" - lore = ["クリティカルヘルス時(5ハート以下)にのみ15秒に1回発動", " 軽減ダメージ", "消費経験値"] +name = "実験的レジスタンス" +description = "体力が5ハート以下に落ちるか致命的なダメージを受けた時のみ、経験値を消費してダメージを軽減する。" +lore0 = "クリティカルヘルス時(5ハート以下)にのみ15秒に1回発動" +lore1 = " 軽減ダメージ" +lore2 = "消費経験値" +lore = ["クリティカルヘルス時(5ハート以下)にのみ15秒に1回発動", " 軽減ダメージ", "消費経験値"] [discovery.villager] - name = "村人の魅了" - description = "村人とのより良い取引が可能になる!" - lore1 = "村人との取引ごとにXPを消費" - lore2 = "取引ごとにXPを消費して取引を強化するチャンス" - lore3 = "取引ごとの必要XP消費量" - lore = ["村人との取引ごとにXPを消費", "取引ごとにXPを消費して取引を強化するチャンス", "取引ごとの必要XP消費量"] +name = "村人の魅了" +description = "村人とのより良い取引が可能になる!" +lore1 = "村人との取引ごとにXPを消費" +lore2 = "取引ごとにXPを消費して取引を強化するチャンス" +lore3 = "取引ごとの必要XP消費量" +lore = ["村人との取引ごとにXPを消費", "取引ごとにXPを消費して取引を強化するチャンス", "取引ごとの必要XP消費量"] # enchanting [enchanting] [enchanting.lapis_return] - name = "ラピスリターン" - description = "1レベル分追加のXPを消費する代わりに、ラピスラズリが無料で返ってくるチャンスがある" - lore1 = "レベルごとにエンチャント費用が1増加するが、最大3個のラピスを返却する可能性がある" - lore = ["レベルごとにエンチャント費用が1増加するが、最大3個のラピスを返却する可能性がある"] +name = "ラピスリターン" +description = "1レベル分追加のXPを消費する代わりに、ラピスラズリが無料で返ってくるチャンスがある" +lore1 = "レベルごとにエンチャント費用が1増加するが、最大3個のラピスを返却する可能性がある" +lore = ["レベルごとにエンチャント費用が1増加するが、最大3個のラピスを返却する可能性がある"] [enchanting.quick_enchant] - name = "クイッククリックエンチャント" - description = "エンチャントの本をアイテムに直接クリックしてエンチャント。" - lore1 = "最大合成レベル" - lore2 = "アイテムにエンチャントできる最大値は" - lore3 = "パワー" - lore = ["最大合成レベル", "アイテムにエンチャントできる最大値は", "パワー"] +name = "クイッククリックエンチャント" +description = "エンチャントの本をアイテムに直接クリックしてエンチャント。" +lore1 = "最大合成レベル" +lore2 = "アイテムにエンチャントできる最大値は" +lore3 = "パワー" +lore = ["最大合成レベル", "アイテムにエンチャントできる最大値は", "パワー"] [enchanting.return] - name = "XPリターン" - description = "エンチャント時に使用したXPが返還される。" - lore1 = "エンチャント時に消費した経験値が返還されるチャンスがある" - lore2 = "エンチャントごとの経験値" - lore = ["エンチャント時に消費した経験値が返還されるチャンスがある", "エンチャントごとの経験値"] +name = "XPリターン" +description = "エンチャント時に使用したXPが返還される。" +lore1 = "エンチャント時に消費した経験値が返還されるチャンスがある" +lore2 = "エンチャントごとの経験値" +lore = ["エンチャント時に消費した経験値が返還されるチャンスがある", "エンチャントごとの経験値"] # excavation [excavation] [excavation.haste] - name = "急速発掘者" - description = "採掘速度上昇で発掘が加速する!" - lore1 = "発掘中に採掘速度上昇を獲得" - lore2 = "任意のブロック採掘開始時にxレベルの採掘速度上昇" - lore = ["発掘中に採掘速度上昇を獲得", "任意のブロック採掘開始時にxレベルの採掘速度上昇"] +name = "急速発掘者" +description = "採掘速度上昇で発掘が加速する!" +lore1 = "発掘中に採掘速度上昇を獲得" +lore2 = "任意のブロック採掘開始時にxレベルの採掘速度上昇" +lore = ["発掘中に採掘速度上昇を獲得", "任意のブロック採掘開始時にxレベルの採掘速度上昇"] [excavation.spelunker] - name = "超視力スペランカー!" - description = "地面越しに鉱石が見える!" - lore1 = "オフハンドに鉱石、メインハンドにグロウベリーを持ってスニーク!" - lore2 = "ブロック範囲:" - lore3 = "使用時にグロウベリーを消費" - lore = ["オフハンドに鉱石、メインハンドにグロウベリーを持ってスニーク!", "ブロック範囲:", "使用時にグロウベリーを消費"] +name = "超視力スペランカー!" +description = "地面越しに鉱石が見える!" +lore1 = "オフハンドに鉱石、メインハンドにグロウベリーを持ってスニーク!" +lore2 = "ブロック範囲:" +lore3 = "使用時にグロウベリーを消費" +lore = ["オフハンドに鉱石、メインハンドにグロウベリーを持ってスニーク!", "ブロック範囲:", "使用時にグロウベリーを消費"] [excavation.drop_to_inventory] - name = "シャベルドロップ→インベントリ" +name = "シャベルドロップ→インベントリ" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "タックルの過剰設計な豪華レザーマン" - lore1 = "おそらく最も強力なツール、" - lore2 = "必要に応じてツールを動的に合体・変更できる。" - lore3 = "合体するには、インベントリ内でアイテムをシフトクリック。" - lore4 = "ツールを分離するには、スニークしながらドロップで分解。" - lore5 = "このレザーマンのツールは壊れないが、壊れたツールは使用不可" - lore6 = "合体可能なアイテム総数。" - lore7 = "5つや6つのツールを使うか、たった1つで済ませるか!" - lore = ["おそらく最も強力なツール、", "必要に応じてツールを動的に合体・変更できる。", "合体するには、インベントリ内でアイテムをシフトクリック。", "ツールを分離するには、スニークしながらドロップで分解。", "このレザーマンのツールは壊れないが、壊れたツールは使用不可", "合体可能なアイテム総数。", "5つや6つのツールを使うか、たった1つで済ませるか!"] +name = "OMNI - T.O.O.L." +description = "タックルの過剰設計な豪華レザーマン" +lore1 = "おそらく最も強力なツール、" +lore2 = "必要に応じてツールを動的に合体・変更できる。" +lore3 = "合体するには、インベントリ内でアイテムをシフトクリック。" +lore4 = "ツールを分離するには、スニークしながらドロップで分解。" +lore5 = "このレザーマンのツールは壊れないが、壊れたツールは使用不可" +lore6 = "合体可能なアイテム総数。" +lore7 = "5つや6つのツールを使うか、たった1つで済ませるか!" +lore = ["おそらく最も強力なツール、", "必要に応じてツールを動的に合体・変更できる。", "合体するには、インベントリ内でアイテムをシフトクリック。", "ツールを分離するには、スニークしながらドロップで分解。", "このレザーマンのツールは壊れないが、壊れたツールは使用不可", "合体可能なアイテム総数。", "5つや6つのツールを使うか、たった1つで済ませるか!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "成長のオーラ" - description = "周囲の自然をオーラで成長させる" - lore1 = "ブロック半径" - lore2 = "成長オーラの強さ" - lore3 = "食料コスト" - lore = ["ブロック半径", "成長オーラの強さ", "食料コスト"] +name = "成長のオーラ" +description = "周囲の自然をオーラで成長させる" +lore1 = "ブロック半径" +lore2 = "成長オーラの強さ" +lore3 = "食料コスト" +lore = ["ブロック半径", "成長オーラの強さ", "食料コスト"] [herbalism.hippo] - name = "薬草師のカバ" - description = "食べ物を消費すると満腹度が追加回復する" - lore1 = "食べ物消費時に追加の満腹度ポイント" - lore = ["食べ物消費時に追加の満腹度ポイント"] +name = "薬草師のカバ" +description = "食べ物を消費すると満腹度が追加回復する" +lore1 = "食べ物消費時に追加の満腹度ポイント" +lore = ["食べ物消費時に追加の満腹度ポイント"] [herbalism.myconid] - name = "薬草師のマイコニッド" - description = "菌糸ブロックをクラフトする能力を得る" - lore1 = "任意の土と茶色&赤キノコで菌糸をクラフトできる。" - lore = ["任意の土と茶色&赤キノコで菌糸をクラフトできる。"] +name = "薬草師のマイコニッド" +description = "菌糸ブロックをクラフトする能力を得る" +lore1 = "任意の土と茶色&赤キノコで菌糸をクラフトできる。" +lore = ["任意の土と茶色&赤キノコで菌糸をクラフトできる。"] [herbalism.terralid] - name = "薬草師のテラリッド" - description = "草ブロックをクラフトする能力を得る" - lore1 = "3つの種を3つの土の上に配置すると、草ブロック3個をクラフトできる。" - lore = ["3つの種を3つの土の上に配置すると、草ブロック3個をクラフトできる。"] +name = "薬草師のテラリッド" +description = "草ブロックをクラフトする能力を得る" +lore1 = "3つの種を3つの土の上に配置すると、草ブロック3個をクラフトできる。" +lore = ["3つの種を3つの土の上に配置すると、草ブロック3個をクラフトできる。"] [herbalism.cobweb] - name = "クモの巣クリエイター" - description = "作業台でクモの巣をクラフトする能力を得る" - lore1 = "糸9個でクモの巣をクラフトできる。" - lore = ["糸9個でクモの巣をクラフトできる。"] +name = "クモの巣クリエイター" +description = "作業台でクモの巣をクラフトする能力を得る" +lore1 = "糸9個でクモの巣をクラフトできる。" +lore = ["糸9個でクモの巣をクラフトできる。"] [herbalism.mushroom_blocks] - name = "キノコメイカー" - description = "作業台でキノコブロックをクラフトする能力を得る" - lore1 = "キノコ4個でブロックを作成、またはブロックから茎を作成。" - lore = ["キノコ4個でブロックを作成、またはブロックから茎を作成。"] +name = "キノコメイカー" +description = "作業台でキノコブロックをクラフトする能力を得る" +lore1 = "キノコ4個でブロックを作成、またはブロックから茎を作成。" +lore = ["キノコ4個でブロックを作成、またはブロックから茎を作成。"] [herbalism.drop_to_inventory] - name = "クワドロップ→インベントリ" +name = "クワドロップ→インベントリ" [herbalism.hungry_shield] - name = "ハングリーシールド" - description = "体力の前に空腹でダメージを受ける。" - lore1 = "空腹による軽減" - lore = ["空腹による軽減"] +name = "ハングリーシールド" +description = "体力の前に空腹でダメージを受ける。" +lore1 = "空腹による軽減" +lore = ["空腹による軽減"] [herbalism.luck] - name = "薬草師の幸運" - description = "草や花を壊すとランダムなアイテムを得るチャンスがある" - lore0 = "花=食べ物、草=種" - lore1 = "花を壊した時のアイテム獲得チャンス" - lore2 = "草を壊した時のアイテム獲得チャンス" - lore = ["花=食べ物、草=種", "花を壊した時のアイテム獲得チャンス", "草を壊した時のアイテム獲得チャンス"] +name = "薬草師の幸運" +description = "草や花を壊すとランダムなアイテムを得るチャンスがある" +lore0 = "花=食べ物、草=種" +lore1 = "花を壊した時のアイテム獲得チャンス" +lore2 = "草を壊した時のアイテム獲得チャンス" +lore = ["花=食べ物、草=種", "花を壊した時のアイテム獲得チャンス", "草を壊した時のアイテム獲得チャンス"] [herbalism.replant] - name = "収穫&再植え" - description = "クワで作物を右クリックすると収穫して再植えする。" - lore1 = "ブロック再植え半径" - lore = ["ブロック再植え半径"] +name = "収穫&再植え" +description = "クワで作物を右クリックすると収穫して再植えする。" +lore1 = "ブロック再植え半径" +lore = ["ブロック再植え半径"] # hunter [hunter] [hunter.adrenaline] - name = "アドレナリン" - description = "体力が低いほど近接ダメージが増加する" - lore1 = "最大ダメージ" - lore = ["最大ダメージ"] +name = "アドレナリン" +description = "体力が低いほど近接ダメージが増加する" +lore1 = "最大ダメージ" +lore = ["最大ダメージ"] [hunter.penalty] - name = "" - description = "" - lore1 = "空腹が尽きると毒スタックが付与される" - lore = ["空腹が尽きると毒スタックが付与される"] +name = "" +description = "" +lore1 = "空腹が尽きると毒スタックが付与される" +lore = ["空腹が尽きると毒スタックが付与される"] [hunter.drop_to_inventory] - name = "アイテムドロップ→インベントリ" - description = "何かを倒したり剣でブロックを壊すとドロップがインベントリに転送される" - lore1 = "モブやブロックからのドロップアイテムが可能な限りインベントリに入る。" - lore = ["モブやブロックからのドロップアイテムが可能な限りインベントリに入る。"] +name = "アイテムドロップ→インベントリ" +description = "何かを倒したり剣でブロックを壊すとドロップがインベントリに転送される" +lore1 = "モブやブロックからのドロップアイテムが可能な限りインベントリに入る。" +lore = ["モブやブロックからのドロップアイテムが可能な限りインベントリに入る。"] [hunter.invisibility] - name = "消失の一歩" - description = "攻撃を受けると空腹を代償に透明化を得る" - lore1 = "攻撃を受けるとパッシブ透明化を獲得" - lore2 = "攻撃を受けてx回3秒間透明化がスタック" - lore3 = "x空腹がスタック" - lore4 = "空腹スタックの持続時間と倍率。" - lore5 = "透明化の持続時間" - lore = ["攻撃を受けるとパッシブ透明化を獲得", "攻撃を受けてx回3秒間透明化がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "透明化の持続時間"] +name = "消失の一歩" +description = "攻撃を受けると空腹を代償に透明化を得る" +lore1 = "攻撃を受けるとパッシブ透明化を獲得" +lore2 = "攻撃を受けてx回3秒間透明化がスタック" +lore3 = "x空腹がスタック" +lore4 = "空腹スタックの持続時間と倍率。" +lore5 = "透明化の持続時間" +lore = ["攻撃を受けるとパッシブ透明化を獲得", "攻撃を受けてx回3秒間透明化がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "透明化の持続時間"] [hunter.jump_boost] - name = "ハンターの高み" - description = "攻撃を受けると空腹を代償にジャンプ力上昇を得る" - lore1 = "攻撃を受けるとパッシブジャンプ力上昇を獲得" - lore2 = "攻撃を受けてx回3秒間ジャンプ力上昇がスタック" - lore3 = "x空腹がスタック" - lore4 = "空腹スタックの持続時間と倍率。" - lore5 = "ジャンプ力上昇の倍率(持続時間ではない)。" - lore = ["攻撃を受けるとパッシブジャンプ力上昇を獲得", "攻撃を受けてx回3秒間ジャンプ力上昇がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "ジャンプ力上昇の倍率(持続時間ではない)。"] +name = "ハンターの高み" +description = "攻撃を受けると空腹を代償にジャンプ力上昇を得る" +lore1 = "攻撃を受けるとパッシブジャンプ力上昇を獲得" +lore2 = "攻撃を受けてx回3秒間ジャンプ力上昇がスタック" +lore3 = "x空腹がスタック" +lore4 = "空腹スタックの持続時間と倍率。" +lore5 = "ジャンプ力上昇の倍率(持続時間ではない)。" +lore = ["攻撃を受けるとパッシブジャンプ力上昇を獲得", "攻撃を受けてx回3秒間ジャンプ力上昇がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "ジャンプ力上昇の倍率(持続時間ではない)。"] [hunter.luck] - name = "ハンターの幸運" - description = "攻撃を受けると空腹を代償に幸運を得る" - lore1 = "攻撃を受けるとパッシブ幸運を獲得" - lore2 = "攻撃を受けてx回3秒間幸運がスタック" - lore3 = "x空腹がスタック" - lore4 = "空腹スタックの持続時間と倍率。" - lore5 = "幸運の倍率(持続時間ではない)。" - lore = ["攻撃を受けるとパッシブ幸運を獲得", "攻撃を受けてx回3秒間幸運がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "幸運の倍率(持続時間ではない)。"] +name = "ハンターの幸運" +description = "攻撃を受けると空腹を代償に幸運を得る" +lore1 = "攻撃を受けるとパッシブ幸運を獲得" +lore2 = "攻撃を受けてx回3秒間幸運がスタック" +lore3 = "x空腹がスタック" +lore4 = "空腹スタックの持続時間と倍率。" +lore5 = "幸運の倍率(持続時間ではない)。" +lore = ["攻撃を受けるとパッシブ幸運を獲得", "攻撃を受けてx回3秒間幸運がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "幸運の倍率(持続時間ではない)。"] [hunter.regen] - name = "ハンターの再生" - description = "攻撃を受けると空腹を代償に再生を得る" - lore1 = "攻撃を受けるとパッシブ再生を獲得" - lore2 = "攻撃を受けてx回3秒間再生がスタック" - lore3 = "x空腹がスタック" - lore4 = "空腹スタックの持続時間と倍率。" - lore5 = "再生の倍率(持続時間ではない)。" - lore = ["攻撃を受けるとパッシブ再生を獲得", "攻撃を受けてx回3秒間再生がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "再生の倍率(持続時間ではない)。"] +name = "ハンターの再生" +description = "攻撃を受けると空腹を代償に再生を得る" +lore1 = "攻撃を受けるとパッシブ再生を獲得" +lore2 = "攻撃を受けてx回3秒間再生がスタック" +lore3 = "x空腹がスタック" +lore4 = "空腹スタックの持続時間と倍率。" +lore5 = "再生の倍率(持続時間ではない)。" +lore = ["攻撃を受けるとパッシブ再生を獲得", "攻撃を受けてx回3秒間再生がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "再生の倍率(持続時間ではない)。"] [hunter.resistance] - name = "ハンターの耐性" - description = "攻撃を受けると空腹を代償に耐性を得る" - lore1 = "攻撃を受けるとパッシブ耐性を獲得" - lore2 = "攻撃を受けてx回3秒間耐性がスタック" - lore3 = "x空腹がスタック" - lore4 = "空腹スタックの持続時間と倍率。" - lore5 = "耐性の倍率(持続時間ではない)。" - lore = ["攻撃を受けるとパッシブ耐性を獲得", "攻撃を受けてx回3秒間耐性がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "耐性の倍率(持続時間ではない)。"] +name = "ハンターの耐性" +description = "攻撃を受けると空腹を代償に耐性を得る" +lore1 = "攻撃を受けるとパッシブ耐性を獲得" +lore2 = "攻撃を受けてx回3秒間耐性がスタック" +lore3 = "x空腹がスタック" +lore4 = "空腹スタックの持続時間と倍率。" +lore5 = "耐性の倍率(持続時間ではない)。" +lore = ["攻撃を受けるとパッシブ耐性を獲得", "攻撃を受けてx回3秒間耐性がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "耐性の倍率(持続時間ではない)。"] [hunter.speed] - name = "ハンターの俊足" - description = "攻撃を受けると空腹を代償にスピードを得る" - lore1 = "攻撃を受けるとパッシブスピードを獲得" - lore2 = "攻撃を受けてx回3秒間スピードがスタック" - lore3 = "x空腹がスタック" - lore4 = "空腹スタックの持続時間と倍率。" - lore5 = "スピードの倍率(持続時間ではない)。" - lore = ["攻撃を受けるとパッシブスピードを獲得", "攻撃を受けてx回3秒間スピードがスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "スピードの倍率(持続時間ではない)。"] +name = "ハンターの俊足" +description = "攻撃を受けると空腹を代償にスピードを得る" +lore1 = "攻撃を受けるとパッシブスピードを獲得" +lore2 = "攻撃を受けてx回3秒間スピードがスタック" +lore3 = "x空腹がスタック" +lore4 = "空腹スタックの持続時間と倍率。" +lore5 = "スピードの倍率(持続時間ではない)。" +lore = ["攻撃を受けるとパッシブスピードを獲得", "攻撃を受けてx回3秒間スピードがスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "スピードの倍率(持続時間ではない)。"] [hunter.strength] - name = "ハンターの剛力" - description = "攻撃を受けると空腹を代償に攻撃力を得る" - lore1 = "攻撃を受けるとパッシブ攻撃力を獲得" - lore2 = "攻撃を受けてx回3秒間攻撃力がスタック" - lore3 = "x空腹がスタック" - lore4 = "空腹スタックの持続時間と倍率。" - lore5 = "攻撃力の倍率(持続時間ではない)。" - lore = ["攻撃を受けるとパッシブ攻撃力を獲得", "攻撃を受けてx回3秒間攻撃力がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "攻撃力の倍率(持続時間ではない)。"] +name = "ハンターの剛力" +description = "攻撃を受けると空腹を代償に攻撃力を得る" +lore1 = "攻撃を受けるとパッシブ攻撃力を獲得" +lore2 = "攻撃を受けてx回3秒間攻撃力がスタック" +lore3 = "x空腹がスタック" +lore4 = "空腹スタックの持続時間と倍率。" +lore5 = "攻撃力の倍率(持続時間ではない)。" +lore = ["攻撃を受けるとパッシブ攻撃力を獲得", "攻撃を受けてx回3秒間攻撃力がスタック", "x空腹がスタック", "空腹スタックの持続時間と倍率。", "攻撃力の倍率(持続時間ではない)。"] # nether [nether] [nether.skull_toss] - name = "ウィザースカル投擲" - description1 = "内なるウィザーを解き放て、" - description2 = "誰かの" - description3 = "頭を使って。" - lore1 = "スカル投擲間のクールダウン秒数。" - lore2 = "ウィザースカル使用:投げる " - lore3 = "ウィザースカル" - lore4 = "着弾時に爆発。" - lore = ["スカル投擲間のクールダウン秒数。", "ウィザースカル使用:投げる ", "ウィザースカル", "着弾時に爆発。"] +name = "ウィザースカル投擲" +description1 = "内なるウィザーを解き放て、" +description2 = "誰かの" +description3 = "頭を使って。" +lore1 = "スカル投擲間のクールダウン秒数。" +lore2 = "ウィザースカル使用:投げる " +lore3 = "ウィザースカル" +lore4 = "着弾時に爆発。" +lore = ["スカル投擲間のクールダウン秒数。", "ウィザースカル使用:投げる ", "ウィザースカル", "着弾時に爆発。"] [nether.wither_resist] - name = "ウィザー耐性" - description = "ネザライトの力でウィザー効果に抵抗する。" - lore1 = "ウィザー効果を無効化する確率(部位ごと)。" - lore2 = "パッシブ:ネザライト防具装備時にウィザー効果を" - lore3 = "無効化する場合がある。" - lore = ["ウィザー効果を無効化する確率(部位ごと)。", "パッシブ:ネザライト防具装備時にウィザー効果を", "無効化する場合がある。"] +name = "ウィザー耐性" +description = "ネザライトの力でウィザー効果に抵抗する。" +lore1 = "ウィザー効果を無効化する確率(部位ごと)。" +lore2 = "パッシブ:ネザライト防具装備時にウィザー効果を" +lore3 = "無効化する場合がある。" +lore = ["ウィザー効果を無効化する確率(部位ごと)。", "パッシブ:ネザライト防具装備時にウィザー効果を", "無効化する場合がある。"] [nether.fire_resist] - name = "火炎耐性" - description = "肌を硬化させて火に抵抗する。" - lore1 = "炎上効果を無効化する確率!" - lore = ["炎上効果を無効化する確率!"] +name = "火炎耐性" +description = "肌を硬化させて火に抵抗する。" +lore1 = "炎上効果を無効化する確率!" +lore = ["炎上効果を無効化する確率!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "自動精錬" - description = "採掘したバニラ鉱石を自動で精錬する" - lore1 = "精錬可能な鉱石は自動で精錬される" - lore2 = "%の確率で追加ドロップ" - lore = ["精錬可能な鉱石は自動で精錬される", "%の確率で追加ドロップ"] +name = "自動精錬" +description = "採掘したバニラ鉱石を自動で精錬する" +lore1 = "精錬可能な鉱石は自動で精錬される" +lore2 = "%の確率で追加ドロップ" +lore = ["精錬可能な鉱石は自動で精錬される", "%の確率で追加ドロップ"] [pickaxe.chisel] - name = "鉱石チゼル" - description = "鉱石を右クリックして追加の鉱石を掘り出す。耐久値を大きく消耗する。" - lore1 = "ドロップ確率" - lore2 = "ツール消耗" - lore = ["ドロップ確率", "ツール消耗"] +name = "鉱石チゼル" +description = "鉱石を右クリックして追加の鉱石を掘り出す。耐久値を大きく消耗する。" +lore1 = "ドロップ確率" +lore2 = "ツール消耗" +lore = ["ドロップ確率", "ツール消耗"] [pickaxe.drop_to_inventory] - name = "つるはしドロップ→インベントリ" - description = "ブロックを壊すとアイテムがインベントリに直接入る" - lore1 = "ブロックからのドロップアイテムが可能な限りインベントリに入る。" - lore = ["ブロックからのドロップアイテムが可能な限りインベントリに入る。"] +name = "つるはしドロップ→インベントリ" +description = "ブロックを壊すとアイテムがインベントリに直接入る" +lore1 = "ブロックからのドロップアイテムが可能な限りインベントリに入る。" +lore = ["ブロックからのドロップアイテムが可能な限りインベントリに入る。"] [pickaxe.silk_spawner] - name = "つるはしシルクスポナー" - description = "スポナーが破壊時にドロップするようになる" - lore1 = "シルクタッチでスポナーを破壊可能にする。" - lore2 = "スニーク中にスポナーを破壊可能にする。" - lore = ["シルクタッチでスポナーを破壊可能にする。", "スニーク中にスポナーを破壊可能にする。"] +name = "つるはしシルクスポナー" +description = "スポナーが破壊時にドロップするようになる" +lore1 = "シルクタッチでスポナーを破壊可能にする。" +lore2 = "スニーク中にスポナーを破壊可能にする。" +lore = ["シルクタッチでスポナーを破壊可能にする。", "スニーク中にスポナーを破壊可能にする。"] [pickaxe.vein_miner] - name = "ヴェインマイナー" - description = "バニラ鉱石の鉱脈/クラスターを一度に破壊できる" - lore1 = "スニークして鉱石を採掘" - lore2 = "ヴェインマイニング範囲" - lore3 = "このスキルはドロップをまとめません!" - lore = ["スニークして鉱石を採掘", "ヴェインマイニング範囲", "このスキルはドロップをまとめません!"] +name = "ヴェインマイナー" +description = "バニラ鉱石の鉱脈/クラスターを一度に破壊できる" +lore1 = "スニークして鉱石を採掘" +lore2 = "ヴェインマイニング範囲" +lore3 = "このスキルはドロップをまとめません!" +lore = ["スニークして鉱石を採掘", "ヴェインマイニング範囲", "このスキルはドロップをまとめません!"] # ranged [ranged] [ranged.arrow_recovery] - name = "矢の回収" - description = "敵を倒した後に矢を回収する。" - lore1 = "ヒット/キル時の矢回収確率" - lore2 = "確率:" - lore = ["ヒット/キル時の矢回収確率", "確率:"] +name = "矢の回収" +description = "敵を倒した後に矢を回収する。" +lore1 = "ヒット/キル時の矢回収確率" +lore2 = "確率:" +lore = ["ヒット/キル時の矢回収確率", "確率:"] [ranged.web_shot] - name = "ウェブスネア" - description = "ターゲットに命中時、周囲にクモの巣を展開する!" - lore1 = "雪玉を8個のクモの巣で囲んで投げろ!" - lore2 = "約数秒間の檻効果。" - lore = ["雪玉を8個のクモの巣で囲んで投げろ!", "約数秒間の檻効果。"] +name = "ウェブスネア" +description = "ターゲットに命中時、周囲にクモの巣を展開する!" +lore1 = "雪玉を8個のクモの巣で囲んで投げろ!" +lore2 = "約数秒間の檻効果。" +lore = ["雪玉を8個のクモの巣で囲んで投げろ!", "約数秒間の檻効果。"] [ranged.force_shot] - name = "フォースショット" - description = "射出物をより速く、より遠くへ飛ばす!" - advancementname = "ロングショット" - advancementlore = "30ブロック以上離れた場所から命中させろ!" - lore1 = "射出速度" - lore = ["射出速度"] +name = "フォースショット" +description = "射出物をより速く、より遠くへ飛ばす!" +advancementname = "ロングショット" +advancementlore = "30ブロック以上離れた場所から命中させろ!" +lore1 = "射出速度" +lore = ["射出速度"] [ranged.lunge_shot] - name = "ランジショット" - description = "落下中に矢がランダムな方向に吹き飛ばす" - lore1 = "ランダムバースト速度" - lore = ["ランダムバースト速度"] +name = "ランジショット" +description = "落下中に矢がランダムな方向に吹き飛ばす" +lore1 = "ランダムバースト速度" +lore = ["ランダムバースト速度"] [ranged.arrow_piercing] - name = "矢の貫通" - description = "射出物に貫通を付与!対象を貫通して射撃!" - lore1 = "貫通対象数" - lore = ["貫通対象数"] +name = "矢の貫通" +description = "射出物に貫通を付与!対象を貫通して射撃!" +lore1 = "貫通対象数" +lore = ["貫通対象数"] # rift [rift] [rift.remote_access] - name = "リモートアクセス" - description = "虚空から引き寄せ、マークしたコンテナにアクセスする。" - lore1 = "エンダーパール + コンパス = 聖遺物ポートキー" - lore2 = "このアイテムでコンテナに遠隔アクセスできる" - lore3 = "クラフト後、アイテムの説明で使い方を確認" - notcontainer = "それはコンテナではありません" - lore = ["エンダーパール + コンパス = 聖遺物ポートキー", "このアイテムでコンテナに遠隔アクセスできる", "クラフト後、アイテムの説明で使い方を確認"] +name = "リモートアクセス" +description = "虚空から引き寄せ、マークしたコンテナにアクセスする。" +lore1 = "エンダーパール + コンパス = 聖遺物ポートキー" +lore2 = "このアイテムでコンテナに遠隔アクセスできる" +lore3 = "クラフト後、アイテムの説明で使い方を確認" +notcontainer = "それはコンテナではありません" +lore = ["エンダーパール + コンパス = 聖遺物ポートキー", "このアイテムでコンテナに遠隔アクセスできる", "クラフト後、アイテムの説明で使い方を確認"] [rift.blink] - name = "リフトブリンク" - description = "短距離瞬間移動、瞬きひとつで!" - lore1 = "ブリンク時のブロック数(垂直2倍)" - lore2 = "ダッシュ中:ジャンプを2回押して" - lore3 = "ブリンク" - lore = ["ブリンク時のブロック数(垂直2倍)", "ダッシュ中:ジャンプを2回押して", "ブリンク"] +name = "リフトブリンク" +description = "短距離瞬間移動、瞬きひとつで!" +lore1 = "ブリンク時のブロック数(垂直2倍)" +lore2 = "ダッシュ中:ジャンプを2回押して" +lore3 = "ブリンク" +lore = ["ブリンク時のブロック数(垂直2倍)", "ダッシュ中:ジャンプを2回押して", "ブリンク"] [rift.chest] - name = "簡易エンダーチェスト" - description = "手に持ったエンダーチェストを左クリックで開く。" - lore1 = "手に持ったエンダーチェストをクリックで開く(設置しないこと)" - lore = ["手に持ったエンダーチェストをクリックで開く(設置しないこと)"] +name = "簡易エンダーチェスト" +description = "手に持ったエンダーチェストを左クリックで開く。" +lore1 = "手に持ったエンダーチェストをクリックで開く(設置しないこと)" +lore = ["手に持ったエンダーチェストをクリックで開く(設置しないこと)"] [rift.descent] - name = "浮遊無効化" - description = "空中に浮かされるのが嫌?このスキルで解決!" - lore1 = "スニークで降下すると、通常より緩やかに落下する!" - lore2 = "クールダウン:" - lore = ["スニークで降下すると、通常より緩やかに落下する!", "クールダウン:"] +name = "浮遊無効化" +description = "空中に浮かされるのが嫌?このスキルで解決!" +lore1 = "スニークで降下すると、通常より緩やかに落下する!" +lore2 = "クールダウン:" +lore = ["スニークで降下すると、通常より緩やかに落下する!", "クールダウン:"] [rift.gate] - name = "リフトゲート" - description = "マークした場所にテレポートする。" - lore1 = "クラフト:エメラルド + アメジストの欠片 + エンダーパール" - lore2 = "使用前に必読!" - lore3 = "5秒の遅延、" - lore4 = "この演出中に死亡する可能性あり" - lore = ["クラフト:エメラルド + アメジストの欠片 + エンダーパール", "使用前に必読!", "5秒の遅延、", "この演出中に死亡する可能性あり"] +name = "リフトゲート" +description = "マークした場所にテレポートする。" +lore1 = "クラフト:エメラルド + アメジストの欠片 + エンダーパール" +lore2 = "使用前に必読!" +lore3 = "5秒の遅延、" +lore4 = "この演出中に死亡する可能性あり" +lore = ["クラフト:エメラルド + アメジストの欠片 + エンダーパール", "使用前に必読!", "5秒の遅延、", "この演出中に死亡する可能性あり"] [rift.resist] - name = "リフト耐性" - description = "エンダーアイテムやアビリティ使用時に耐性を獲得" - lore1 = "+ パッシブ:リフトアビリティやエンダーアイテム使用時に耐性を付与" - lore2 = "ポータブルエンダーチェストは対象外、消費可能なアイテムのみ" - lore = ["+ パッシブ:リフトアビリティやエンダーアイテム使用時に耐性を付与", "ポータブルエンダーチェストは対象外、消費可能なアイテムのみ"] +name = "リフト耐性" +description = "エンダーアイテムやアビリティ使用時に耐性を獲得" +lore1 = "+ パッシブ:リフトアビリティやエンダーアイテム使用時に耐性を付与" +lore2 = "ポータブルエンダーチェストは対象外、消費可能なアイテムのみ" +lore = ["+ パッシブ:リフトアビリティやエンダーアイテム使用時に耐性を付与", "ポータブルエンダーチェストは対象外、消費可能なアイテムのみ"] [rift.visage] - name = "リフトヴィサージュ" - description = "インベントリにエンダーパールがあるとエンダーマンが敵対しなくなる。" - lore1 = "エンダーパールを所持しているとエンダーマンが敵対しなくなる。" - lore = ["エンダーパールを所持しているとエンダーマンが敵対しなくなる。"] +name = "リフトヴィサージュ" +description = "インベントリにエンダーパールがあるとエンダーマンが敵対しなくなる。" +lore1 = "エンダーパールを所持しているとエンダーマンが敵対しなくなる。" +lore = ["エンダーパールを所持しているとエンダーマンが敵対しなくなる。"] # seaborn [seaborn] [seaborn.oxygen] - name = "有機酸素タンク" - description = "小さな肺でもっと多くの酸素を蓄えろ!" - lore1 = "酸素容量増加" - lore = ["酸素容量増加"] +name = "有機酸素タンク" +description = "小さな肺でもっと多くの酸素を蓄えろ!" +lore1 = "酸素容量増加" +lore = ["酸素容量増加"] [seaborn.fishers_fantasy] - name = "釣り人の幻想" - description = "釣りでより多くのXPと魚を獲得!" - lore1 = "レベルごとにXPと魚の追加獲得チャンスが増加!" - lore = ["レベルごとにXPと魚の追加獲得チャンスが増加!"] +name = "釣り人の幻想" +description = "釣りでより多くのXPと魚を獲得!" +lore1 = "レベルごとにXPと魚の追加獲得チャンスが増加!" +lore = ["レベルごとにXPと魚の追加獲得チャンスが増加!"] [seaborn.haste] - name = "タートルマイナー" - description = "水中で採掘すると採掘速度上昇を得る!" - lore1 = "水中呼吸の効果が切れた後、水中で採掘すると採掘速度上昇IIIが適用される(水中採掘と重複可能)!" - lore = ["水中呼吸の効果が切れた後、水中で採掘すると採掘速度上昇IIIが適用される(水中採掘と重複可能)!"] +name = "タートルマイナー" +description = "水中で採掘すると採掘速度上昇を得る!" +lore1 = "水中呼吸の効果が切れた後、水中で採掘すると採掘速度上昇IIIが適用される(水中採掘と重複可能)!" +lore = ["水中呼吸の効果が切れた後、水中で採掘すると採掘速度上昇IIIが適用される(水中採掘と重複可能)!"] [seaborn.night_vision] - name = "タートルビジョン" - description = "水中で暗視を得る" - lore1 = "水中呼吸の効果が切れた後、水中で暗視を獲得!" - lore = ["水中呼吸の効果が切れた後、水中で暗視を獲得!"] +name = "タートルビジョン" +description = "水中で暗視を得る" +lore1 = "水中呼吸の効果が切れた後、水中で暗視を獲得!" +lore = ["水中呼吸の効果が切れた後、水中で暗視を獲得!"] [seaborn.dolphin_grace] - name = "イルカの加護" - description = "イルカなしでイルカのように泳ぐ" - lore1 = "+ パッシブ:" - lore2 = "xスピード(イルカの加護)を獲得" - lore3 = "精密なドイツのエンジニアリ…いや違った…水中歩行とは併用不可" - lore = ["+ パッシブ:", "xスピード(イルカの加護)を獲得", "精密なドイツのエンジニアリ…いや違った…水中歩行とは併用不可"] +name = "イルカの加護" +description = "イルカなしでイルカのように泳ぐ" +lore1 = "+ パッシブ:" +lore2 = "xスピード(イルカの加護)を獲得" +lore3 = "精密なドイツのエンジニアリ…いや違った…水中歩行とは併用不可" +lore = ["+ パッシブ:", "xスピード(イルカの加護)を獲得", "精密なドイツのエンジニアリ…いや違った…水中歩行とは併用不可"] # stealth [stealth] [stealth.ghost_armor] - name = "ゴーストアーマー" - description = "ダメージを受けていない間徐々に防具が蓄積し、1回の攻撃で消滅する" - lore1 = "最大防具値" - lore2 = "蓄積速度" - lore = ["最大防具値", "蓄積速度"] +name = "ゴーストアーマー" +description = "ダメージを受けていない間徐々に防具が蓄積し、1回の攻撃で消滅する" +lore1 = "最大防具値" +lore2 = "蓄積速度" +lore = ["最大防具値", "蓄積速度"] [stealth.night_vision] - name = "ステルスビジョン" - description = "スニーク中に暗視を獲得する" - lore1 = "一瞬で" - lore2 = "暗視" - lore3 = "をスニーク中に獲得" - lore = ["一瞬で", "暗視", "をスニーク中に獲得"] +name = "ステルスビジョン" +description = "スニーク中に暗視を獲得する" +lore1 = "一瞬で" +lore2 = "暗視" +lore3 = "をスニーク中に獲得" +lore = ["一瞬で", "暗視", "をスニーク中に獲得"] [stealth.snatch] - name = "アイテムスナッチ" - description = "スニーク中にドロップアイテムを瞬時に回収!" - lore1 = "スナッチ半径" - lore = ["スナッチ半径"] +name = "アイテムスナッチ" +description = "スニーク中にドロップアイテムを瞬時に回収!" +lore1 = "スナッチ半径" +lore = ["スナッチ半径"] [stealth.speed] - name = "スニーク速度" - description = "スニーク中にスピードを得る" - lore1 = "スニーク速度" - lore = ["スニーク速度"] +name = "スニーク速度" +description = "スニーク中にスピードを得る" +lore1 = "スニーク速度" +lore = ["スニーク速度"] [stealth.ender_veil] - name = "エンダーヴェール" - description = "カボチャ無しでエンダーマンの敵対を防ぐ" - lore1 = "スニーク中にエンダーマンの攻撃を防ぐ" - lore2 = "エンダーマンの攻撃をすべて防ぐ" - lore = ["スニーク中にエンダーマンの攻撃を防ぐ", "エンダーマンの攻撃をすべて防ぐ"] +name = "エンダーヴェール" +description = "カボチャ無しでエンダーマンの敵対を防ぐ" +lore1 = "スニーク中にエンダーマンの攻撃を防ぐ" +lore2 = "エンダーマンの攻撃をすべて防ぐ" +lore = ["スニーク中にエンダーマンの攻撃を防ぐ", "エンダーマンの攻撃をすべて防ぐ"] # sword [sword] [sword.machete] - name = "マチェーテ" - description = "草木を容易く切り払え!" - lore1 = "斬撃半径" - lore2 = "斬撃クールダウン" - lore3 = "ツール消耗" - lore = ["斬撃半径", "斬撃クールダウン", "ツール消耗"] +name = "マチェーテ" +description = "草木を容易く切り払え!" +lore1 = "斬撃半径" +lore2 = "斬撃クールダウン" +lore3 = "ツール消耗" +lore = ["斬撃半径", "斬撃クールダウン", "ツール消耗"] [sword.bloody_blade] - name = "血塗れの刃" - description = "剣での攻撃が出血を引き起こす!" - lore1 = "剣で生物を攻撃すると出血を引き起こす" - lore2 = "出血持続時間" - lore3 = "出血クールダウン" - lore = ["剣で生物を攻撃すると出血を引き起こす", "出血持続時間", "出血クールダウン"] +name = "血塗れの刃" +description = "剣での攻撃が出血を引き起こす!" +lore1 = "剣で生物を攻撃すると出血を引き起こす" +lore2 = "出血持続時間" +lore3 = "出血クールダウン" +lore = ["剣で生物を攻撃すると出血を引き起こす", "出血持続時間", "出血クールダウン"] [sword.poisoned_blade] - name = "毒の刃" - description = "剣での攻撃が毒を引き起こす!" - lore1 = "剣で生物を攻撃すると毒を引き起こす" - lore2 = "毒の持続時間" - lore3 = "毒クールダウン" - lore = ["剣で生物を攻撃すると毒を引き起こす", "毒の持続時間", "毒クールダウン"] +name = "毒の刃" +description = "剣での攻撃が毒を引き起こす!" +lore1 = "剣で生物を攻撃すると毒を引き起こす" +lore2 = "毒の持続時間" +lore3 = "毒クールダウン" +lore = ["剣で生物を攻撃すると毒を引き起こす", "毒の持続時間", "毒クールダウン"] # taming [taming] [taming.damage] - name = "飼育ダメージ" - description = "飼いならした動物の攻撃力を増加させる。" - lore1 = "ダメージ増加" - lore = ["ダメージ増加"] +name = "飼育ダメージ" +description = "飼いならした動物の攻撃力を増加させる。" +lore1 = "ダメージ増加" +lore = ["ダメージ増加"] [taming.health] - name = "飼育体力" - description = "飼いならした動物の体力を増加させる。" - lore1 = "体力増加" - lore = ["体力増加"] +name = "飼育体力" +description = "飼いならした動物の体力を増加させる。" +lore1 = "体力増加" +lore = ["体力増加"] [taming.regeneration] - name = "飼育再生" - description = "飼いならした動物の再生力を増加させる。" - lore1 = "HP/秒" - lore = ["HP/秒"] +name = "飼育再生" +description = "飼いならした動物の再生力を増加させる。" +lore1 = "HP/秒" +lore = ["HP/秒"] # tragoul [tragoul] [tragoul.thorns] - name = "棘" - description = "受けたダメージを攻撃者に反射する!" - lore1 = "被弾時の反射ダメージ" - lore = ["被弾時の反射ダメージ"] +name = "棘" +description = "受けたダメージを攻撃者に反射する!" +lore1 = "被弾時の反射ダメージ" +lore = ["被弾時の反射ダメージ"] [tragoul.globe] - name = "苦痛の球" - description = "周囲の敵の数に応じて与えるダメージを分散する!" - lore1 = "周囲に敵が多いほど、それぞれに与えるダメージは減少する" - lore2 = "範囲:" - lore3 = "全エンティティへの追加ダメージ:" - lore = ["周囲に敵が多いほど、それぞれに与えるダメージは減少する", "範囲:", "全エンティティへの追加ダメージ:"] +name = "苦痛の球" +description = "周囲の敵の数に応じて与えるダメージを分散する!" +lore1 = "周囲に敵が多いほど、それぞれに与えるダメージは減少する" +lore2 = "範囲:" +lore3 = "全エンティティへの追加ダメージ:" +lore = ["周囲に敵が多いほど、それぞれに与えるダメージは減少する", "範囲:", "全エンティティへの追加ダメージ:"] [tragoul.healing] - name = "苦痛の意志" - description = "与えたダメージに応じて体力が回復する!" - lore1 = "傷つけることがこんなに気持ちいいとは!与ダメージから回復" - lore2 = "3秒間のダメージ判定期間があり、回復と1秒間のクールダウンがある" - lore3 = "ダメージあたりの回復率:" - lore = ["傷つけることがこんなに気持ちいいとは!与ダメージから回復", "3秒間のダメージ判定期間があり、回復と1秒間のクールダウンがある", "ダメージあたりの回復率:"] +name = "苦痛の意志" +description = "与えたダメージに応じて体力が回復する!" +lore1 = "傷つけることがこんなに気持ちいいとは!与ダメージから回復" +lore2 = "3秒間のダメージ判定期間があり、回復と1秒間のクールダウンがある" +lore3 = "ダメージあたりの回復率:" +lore = ["傷つけることがこんなに気持ちいいとは!与ダメージから回復", "3秒間のダメージ判定期間があり、回復と1秒間のクールダウンがある", "ダメージあたりの回復率:"] [tragoul.lance] - name = "屍槍" - description = "敵を倒すかアビリティで敵を倒すと、近くの敵にダメージを与える槍が出現する!" - lore1 = "槍は倒した全ての対象から飛び出し、このアビリティで敵を倒した場合も発動する。" - lore2 = "槍を生成するために自分の体力を犠牲にする(これで死ぬこともある)" - lore3 = "最大槍数:1 + " - lore = ["槍は倒した全ての対象から飛び出し、このアビリティで敵を倒した場合も発動する。", "槍を生成するために自分の体力を犠牲にする(これで死ぬこともある)", "最大槍数:1 + "] +name = "屍槍" +description = "敵を倒すかアビリティで敵を倒すと、近くの敵にダメージを与える槍が出現する!" +lore1 = "槍は倒した全ての対象から飛び出し、このアビリティで敵を倒した場合も発動する。" +lore2 = "槍を生成するために自分の体力を犠牲にする(これで死ぬこともある)" +lore3 = "最大槍数:1 + " +lore = ["槍は倒した全ての対象から飛び出し、このアビリティで敵を倒した場合も発動する。", "槍を生成するために自分の体力を犠牲にする(これで死ぬこともある)", "最大槍数:1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "ガラスの大砲" - description = "防具値が低いほど素手ダメージにボーナスが付く" - lore1 = "x 防具値0時のダメージ" - lore2 = "レベルごとのボーナスダメージ" - lore = ["x 防具値0時のダメージ", "レベルごとのボーナスダメージ"] +name = "ガラスの大砲" +description = "防具値が低いほど素手ダメージにボーナスが付く" +lore1 = "x 防具値0時のダメージ" +lore2 = "レベルごとのボーナスダメージ" +lore = ["x 防具値0時のダメージ", "レベルごとのボーナスダメージ"] [unarmed.power] - name = "素手の力" - description = "素手ダメージが向上する" - lore1 = "ダメージ" - lore = ["ダメージ"] +name = "素手の力" +description = "素手ダメージが向上する" +lore1 = "ダメージ" +lore = ["ダメージ"] [unarmed.sucker_punch] - name = "不意打ち" - description = "ダッシュパンチがさらに致命的になる。" - lore1 = "ダメージ" - lore2 = "パンチ中の移動速度に応じてダメージが増加する" - lore = ["ダメージ", "パンチ中の移動速度に応じてダメージが増加する"] +name = "不意打ち" +description = "ダッシュパンチがさらに致命的になる。" +lore1 = "ダメージ" +lore2 = "パンチ中の移動速度に応じてダメージが増加する" +lore = ["ダメージ", "パンチ中の移動速度に応じてダメージが増加する"] diff --git a/src/main/resources/ko_KR.toml b/src/main/resources/ko_KR.toml index fc73f70f6..ad0d30c10 100644 --- a/src/main/resources/ko_KR.toml +++ b/src/main/resources/ko_KR.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "움직입시다!" - description = "1KM 이상 이동하기 (1,000 블럭)" +title = "움직입시다!" +description = "1KM 이상 이동하기 (1,000 블럭)" [advancement.challenge_sprint_5k] - title = "5KM 뜀박질!" - description = "5KM 이상 이동하기 (5,000 블럭)" +title = "5KM 뜀박질!" +description = "5KM 이상 이동하기 (5,000 블럭)" [advancement.challenge_sprint_50k] - title = "50KM 전력질주!" - description = "50KM 이상 이동하기 (50,000 블럭)" +title = "50KM 전력질주!" +description = "50KM 이상 이동하기 (50,000 블럭)" [advancement.challenge_sprint_500k] - title = "우주를 횡단하라!!" - description = "500KM 이상 이동하기 (500,000 블럭)" +title = "우주를 횡단하라!!" +description = "500KM 이상 이동하기 (500,000 블럭)" [advancement.challenge_sprint_marathon] - title = "(진짜) 마라톤 뛸 시간!" - description = "42,195 블럭을 달리세요!" +title = "(진짜) 마라톤 뛸 시간!" +description = "42,195 블럭을 달리세요!" [advancement.challenge_place_1k] - title = "풋내기 건축가!" - description = "블럭 1,000개를 배치하세요" +title = "풋내기 건축가!" +description = "블럭 1,000개를 배치하세요" [advancement.challenge_place_5k] - title = "중급 건축가!" - description = "블럭 5,000개를 배치하세요" +title = "중급 건축가!" +description = "블럭 5,000개를 배치하세요" [advancement.challenge_place_50k] - title = "숙련된 건축가!" - description = "블럭 50,000개를 배치하세요" +title = "숙련된 건축가!" +description = "블럭 50,000개를 배치하세요" [advancement.challenge_place_500k] - title = "마스터 건축가!" - description = "블럭 500,000개를 배치하세요" +title = "마스터 건축가!" +description = "블럭 500,000개를 배치하세요" [advancement.challenge_place_5m] - title = "대칭의 수호자!" - description = "현실이 당신의 놀이터입니다! (5백만 블럭)" +title = "대칭의 수호자!" +description = "현실이 당신의 놀이터입니다! (5백만 블럭)" [advancement.challenge_chop_1k] - title = "풋내기 나무꾼!" - description = "나무 블럭 1,000개를 베세요" +title = "풋내기 나무꾼!" +description = "나무 블럭 1,000개를 베세요" [advancement.challenge_chop_5k] - title = "중급 나무꾼!" - description = "나무 블럭 5,000개를 베세요" +title = "중급 나무꾼!" +description = "나무 블럭 5,000개를 베세요" [advancement.challenge_chop_50k] - title = "숙련된 나무꾼!" - description = "나무 블럭 50,000개를 베세요" +title = "숙련된 나무꾼!" +description = "나무 블럭 50,000개를 베세요" [advancement.challenge_chop_500k] - title = "마스터 나무꾼!" - description = "나무 블럭 500,000개를 베세요" +title = "마스터 나무꾼!" +description = "나무 블럭 500,000개를 베세요" [advancement.challenge_chop_5m] - title = "잭슨이라는 개" - description = "세상에서 제일 착한 강아지! (5백만 블럭)" +title = "잭슨이라는 개" +description = "세상에서 제일 착한 강아지! (5백만 블럭)" [advancement.challenge_block_1k] - title = "겨우 막는 중!" - description = "공격 1,000번을 막으세요" +title = "겨우 막는 중!" +description = "공격 1,000번을 막으세요" [advancement.challenge_block_5k] - title = "막는 게 재밌어!" - description = "공격 5,000번을 막으세요" +title = "막는 게 재밌어!" +description = "공격 5,000번을 막으세요" [advancement.challenge_block_50k] - title = "막기가 내 인생이야!" - description = "공격 50,000번을 막으세요" +title = "막기가 내 인생이야!" +description = "공격 50,000번을 막으세요" [advancement.challenge_block_500k] - title = "막기가 내 존재 이유야!" - description = "공격 500,000번을 막으세요" +title = "막기가 내 존재 이유야!" +description = "공격 500,000번을 막으세요" [advancement.challenge_block_5m] - title = "아픈 손" - description = "공격 5,000,000번을 막으세요" +title = "아픈 손" +description = "공격 5,000,000번을 막으세요" [advancement.challenge_brew_1k] - title = "풋내기 연금술사!" - description = "물약 1,000개를 사용하세요" +title = "풋내기 연금술사!" +description = "물약 1,000개를 사용하세요" [advancement.challenge_brew_5k] - title = "중급 연금술사!" - description = "물약 5,000개를 사용하세요" +title = "중급 연금술사!" +description = "물약 5,000개를 사용하세요" [advancement.challenge_brew_50k] - title = "숙련된 연금술사!" - description = "물약 50,000개를 사용하세요" +title = "숙련된 연금술사!" +description = "물약 50,000개를 사용하세요" [advancement.challenge_brew_500k] - title = "마스터 연금술사!" - description = "물약 500,000개를 사용하세요" +title = "마스터 연금술사!" +description = "물약 500,000개를 사용하세요" [advancement.challenge_brew_5m] - title = "위대한 연금술사" - description = "물약 5,000,000개를 사용하세요" +title = "위대한 연금술사" +description = "물약 5,000,000개를 사용하세요" [advancement.challenge_brewsplash_1k] - title = "풋내기 투척사!" - description = "물약 1,000개를 던지세요" +title = "풋내기 투척사!" +description = "물약 1,000개를 던지세요" [advancement.challenge_brewsplash_5k] - title = "중급 투척사!" - description = "물약 5,000개를 던지세요" +title = "중급 투척사!" +description = "물약 5,000개를 던지세요" [advancement.challenge_brewsplash_50k] - title = "숙련된 투척사!" - description = "물약 50,000개를 던지세요" +title = "숙련된 투척사!" +description = "물약 50,000개를 던지세요" [advancement.challenge_brewsplash_500k] - title = "마스터 투척사!" - description = "물약 500,000개를 던지세요" +title = "마스터 투척사!" +description = "물약 500,000개를 던지세요" [advancement.challenge_brewsplash_5m] - title = "투척의 달인" - description = "물약 5,000,000개를 던지세요" +title = "투척의 달인" +description = "물약 5,000,000개를 던지세요" [advancement.challenge_craft_1k] - title = "손재주 좋은 제작자!" - description = "아이템 1,000개를 제작하세요" +title = "손재주 좋은 제작자!" +description = "아이템 1,000개를 제작하세요" [advancement.challenge_craft_5k] - title = "심술궂은 제작자!" - description = "아이템 5,000개를 제작하세요" +title = "심술궂은 제작자!" +description = "아이템 5,000개를 제작하세요" [advancement.challenge_craft_50k] - title = "헌신적인 제작자!" - description = "아이템 50,000개를 제작하세요" +title = "헌신적인 제작자!" +description = "아이템 50,000개를 제작하세요" [advancement.challenge_craft_500k] - title = "요란한 제작자!" - description = "아이템 500,000개를 제작하세요" +title = "요란한 제작자!" +description = "아이템 500,000개를 제작하세요" [advancement.challenge_craft_5m] - title = "재앙의 제작왕" - description = "아이템 5,000,000개를 제작하세요" +title = "재앙의 제작왕" +description = "아이템 5,000,000개를 제작하세요" [advancement.challenge_enchant_1k] - title = "풋내기 마법부여사!" - description = "아이템 1,000개를 마법부여하세요" +title = "풋내기 마법부여사!" +description = "아이템 1,000개를 마법부여하세요" [advancement.challenge_enchant_5k] - title = "중급 마법부여사!" - description = "아이템 5,000개를 마법부여하세요" +title = "중급 마법부여사!" +description = "아이템 5,000개를 마법부여하세요" [advancement.challenge_enchant_50k] - title = "숙련된 마법부여사!" - description = "아이템 50,000개를 마법부여하세요" +title = "숙련된 마법부여사!" +description = "아이템 50,000개를 마법부여하세요" [advancement.challenge_enchant_500k] - title = "마스터 마법부여사!" - description = "아이템 500,000개를 마법부여하세요" +title = "마스터 마법부여사!" +description = "아이템 500,000개를 마법부여하세요" [advancement.challenge_enchant_5m] - title = "신비로운 마법부여사" - description = "아이템 5,000,000개를 마법부여하세요" +title = "신비로운 마법부여사" +description = "아이템 5,000,000개를 마법부여하세요" [advancement.challenge_excavate_1k] - title = "의욕 넘치는 발굴가!" - description = "블럭 1,000개를 파내세요" +title = "의욕 넘치는 발굴가!" +description = "블럭 1,000개를 파내세요" [advancement.challenge_excavate_5k] - title = "중급 발굴가!" - description = "블럭 5,000개를 파내세요" +title = "중급 발굴가!" +description = "블럭 5,000개를 파내세요" [advancement.challenge_excavate_50k] - title = "숙련된 발굴가!" - description = "블럭 50,000개를 파내세요" +title = "숙련된 발굴가!" +description = "블럭 50,000개를 파내세요" [advancement.challenge_excavate_500k] - title = "마스터 발굴가!" - description = "블럭 500,000개를 파내세요" +title = "마스터 발굴가!" +description = "블럭 500,000개를 파내세요" [advancement.challenge_excavate_5m] - title = "신비로운 발굴가" - description = "블럭 5,000,000개를 파내세요" +title = "신비로운 발굴가" +description = "블럭 5,000,000개를 파내세요" [advancement.horrible_person] - title = "당신은 끔찍한 사람입니다" - description = "정말 이해할 수 없군요" +title = "당신은 끔찍한 사람입니다" +description = "정말 이해할 수 없군요" [advancement.challenge_turtle_egg_smasher] - title = "거북이 알 분쇄기!" - description = "거북이 알 100개 깨기" +title = "거북이 알 분쇄기!" +description = "거북이 알 100개 깨기" [advancement.challenge_turtle_egg_annihilator] - title = "거북이 알 섬멸자!" - description = "거북이 알 500개 깨기" +title = "거북이 알 섬멸자!" +description = "거북이 알 500개 깨기" [advancement.challenge_novice_hunter] - title = "초보 사냥꾼!" - description = "엔티티 100마리 처치" +title = "초보 사냥꾼!" +description = "엔티티 100마리 처치" [advancement.challenge_intermediate_hunter] - title = "중급 사냥꾼!" - description = "엔티티 500마리 처치" +title = "중급 사냥꾼!" +description = "엔티티 500마리 처치" [advancement.challenge_advanced_hunter] - title = "고급 사냥꾼!" - description = "엔티티 5,000마리 처치" +title = "고급 사냥꾼!" +description = "엔티티 5,000마리 처치" [advancement.challenge_creeper_conqueror] - title = "크리퍼 정복자!" - description = "크리퍼 50마리 처치" +title = "크리퍼 정복자!" +description = "크리퍼 50마리 처치" [advancement.challenge_creeper_annihilator] - title = "크리퍼 섬멸자!" - description = "크리퍼 200마리 처치" +title = "크리퍼 섬멸자!" +description = "크리퍼 200마리 처치" [advancement.challenge_pickaxe_1k] - title = "초보 광부" - description = "블록 1,000개 깨기" +title = "초보 광부" +description = "블록 1,000개 깨기" [advancement.challenge_pickaxe_5k] - title = "숙련된 광부" - description = "블록 5,000개 깨기" +title = "숙련된 광부" +description = "블록 5,000개 깨기" [advancement.challenge_pickaxe_50k] - title = "전문 광부" - description = "블록 50,000개 깨기" +title = "전문 광부" +description = "블록 50,000개 깨기" [advancement.challenge_pickaxe_500k] - title = "마스터 광부" - description = "블록 500,000개 깨기" +title = "마스터 광부" +description = "블록 500,000개 깨기" [advancement.challenge_pickaxe_5m] - title = "전설의 광부" - description = "블록 5,000,000개 깨기" +title = "전설의 광부" +description = "블록 5,000,000개 깨기" [advancement.challenge_eat_100] - title = "먹을 게 너무 많아!" - description = "아이템 100개 이상 먹기!" +title = "먹을 게 너무 많아!" +description = "아이템 100개 이상 먹기!" [advancement.challenge_eat_1000] - title = "채워지지 않는 허기!" - description = "아이템 1,000개 이상 먹기!" +title = "채워지지 않는 허기!" +description = "아이템 1,000개 이상 먹기!" [advancement.challenge_eat_10000] - title = "영원한 굶주림!" - description = "아이템 10,000개 이상 먹기!" +title = "영원한 굶주림!" +description = "아이템 10,000개 이상 먹기!" [advancement.challenge_harvest_100] - title = "풍성한 수확" - description = "작물 100개 이상 수확하기!" +title = "풍성한 수확" +description = "작물 100개 이상 수확하기!" [advancement.challenge_harvest_1000] - title = "대풍년" - description = "작물 1,000개 이상 수확하기!" +title = "대풍년" +description = "작물 1,000개 이상 수확하기!" [advancement.challenge_swim_1nm] - title = "인간 잠수함!" - description = "1해리 수영하기 (1,852 블록)" +title = "인간 잠수함!" +description = "1해리 수영하기 (1,852 블록)" [advancement.challenge_sneak_1k] - title = "무릎 통증" - description = "1킬로미터 이상 숨어 이동하기 (1,000 블록)" +title = "무릎 통증" +description = "1킬로미터 이상 숨어 이동하기 (1,000 블록)" [advancement.challenge_sneak_5k] - title = "그림자 보행자" - description = "5,000블록 이상 웅크려 이동하기" +title = "그림자 보행자" +description = "5,000블록 이상 웅크려 이동하기" [advancement.challenge_sneak_20k] - title = "유령" - description = "20,000블록 이상 웅크려 이동하기" +title = "유령" +description = "20,000블록 이상 웅크려 이동하기" [advancement.challenge_swim_5k] - title = "심해 잠수부" - description = "5,000블록 이상 수영하기" +title = "심해 잠수부" +description = "5,000블록 이상 수영하기" [advancement.challenge_swim_20k] - title = "포세이돈의 선택" - description = "20,000블록 이상 수영하기" +title = "포세이돈의 선택" +description = "20,000블록 이상 수영하기" [advancement.challenge_sword_100] - title = "첫 번째 피" - description = "검으로 100번 공격하기" +title = "첫 번째 피" +description = "검으로 100번 공격하기" [advancement.challenge_sword_1k] - title = "검무" - description = "검으로 1,000번 공격하기" +title = "검무" +description = "검으로 1,000번 공격하기" [advancement.challenge_sword_10k] - title = "천 개의 베기" - description = "검으로 10,000번 공격하기" +title = "천 개의 베기" +description = "검으로 10,000번 공격하기" [advancement.challenge_unarmed_100] - title = "술집 싸움꾼" - description = "맨손으로 100번 공격하기" +title = "술집 싸움꾼" +description = "맨손으로 100번 공격하기" [advancement.challenge_unarmed_1k] - title = "강철 주먹" - description = "맨손으로 1,000번 공격하기" +title = "강철 주먹" +description = "맨손으로 1,000번 공격하기" [advancement.challenge_unarmed_10k] - title = "원펀치" - description = "맨손으로 10,000번 공격하기" +title = "원펀치" +description = "맨손으로 10,000번 공격하기" [advancement.challenge_trag_1k] - title = "피의 대가" - description = "1,000 데미지 받기" +title = "피의 대가" +description = "1,000 데미지 받기" [advancement.challenge_trag_10k] - title = "핏빛 파도" - description = "10,000 데미지 받기" +title = "핏빛 파도" +description = "10,000 데미지 받기" [advancement.challenge_trag_100k] - title = "고통의 화신" - description = "100,000 데미지 받기" +title = "고통의 화신" +description = "100,000 데미지 받기" [advancement.challenge_ranged_100] - title = "사격 연습" - description = "투사체 100발 발사하기" +title = "사격 연습" +description = "투사체 100발 발사하기" [advancement.challenge_ranged_1k] - title = "호크아이" - description = "투사체 1,000발 발사하기" +title = "호크아이" +description = "투사체 1,000발 발사하기" [advancement.challenge_ranged_10k] - title = "화살의 폭풍" - description = "투사체 10,000발 발사하기" +title = "화살의 폭풍" +description = "투사체 10,000발 발사하기" [advancement.challenge_chronos_1h] - title = "째깍째깍" - description = "1시간 동안 온라인 상태 유지하기" +title = "째깍째깍" +description = "1시간 동안 온라인 상태 유지하기" [advancement.challenge_chronos_24h] - title = "시간의 모래" - description = "24시간 동안 온라인 상태 유지하기" +title = "시간의 모래" +description = "24시간 동안 온라인 상태 유지하기" [advancement.challenge_chronos_168h] - title = "영원한 자" - description = "168시간(1주일) 동안 온라인 상태 유지하기" +title = "영원한 자" +description = "168시간(1주일) 동안 온라인 상태 유지하기" [advancement.challenge_nether_50] - title = "지옥의 문지기" - description = "네더 생물 50마리 처치하기" +title = "지옥의 문지기" +description = "네더 생물 50마리 처치하기" [advancement.challenge_nether_500] - title = "심연의 수호자" - description = "네더 생물 500마리 처치하기" +title = "심연의 수호자" +description = "네더 생물 500마리 처치하기" [advancement.challenge_nether_5k] - title = "네더의 군주" - description = "네더 생물 5,000마리 처치하기" +title = "네더의 군주" +description = "네더 생물 5,000마리 처치하기" [advancement.challenge_rift_50] - title = "공간 이상" - description = "50번 텔레포트하기" +title = "공간 이상" +description = "50번 텔레포트하기" [advancement.challenge_rift_500] - title = "공허의 보행자" - description = "500번 텔레포트하기" +title = "공허의 보행자" +description = "500번 텔레포트하기" [advancement.challenge_rift_5k] - title = "세계의 틈새" - description = "5,000번 텔레포트하기" +title = "세계의 틈새" +description = "5,000번 텔레포트하기" [advancement.challenge_taming_10] - title = "동물의 속삭임" - description = "동물 10마리 번식시키기" +title = "동물의 속삭임" +description = "동물 10마리 번식시키기" [advancement.challenge_taming_50] - title = "무리의 리더" - description = "동물 50마리 번식시키기" +title = "무리의 리더" +description = "동물 50마리 번식시키기" [advancement.challenge_taming_500] - title = "야수의 지배자" - description = "동물 500마리 번식시키기" +title = "야수의 지배자" +description = "동물 500마리 번식시키기" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "질주의 악마" - description = "5킬로미터(5,000블록) 이상 질주하기" +title = "질주의 악마" +description = "5킬로미터(5,000블록) 이상 질주하기" [advancement.challenge_sprint_dist_50k] - title = "번개 다리" - description = "50킬로미터(50,000블록) 이상 질주하기" +title = "번개 다리" +description = "50킬로미터(50,000블록) 이상 질주하기" [advancement.challenge_agility_swim_1k] - title = "소금쟁이" - description = "1킬로미터(1,000블록) 이상 수영하기" +title = "소금쟁이" +description = "1킬로미터(1,000블록) 이상 수영하기" [advancement.challenge_agility_swim_10k] - title = "수중 항해사" - description = "10킬로미터(10,000블록) 이상 수영하기" +title = "수중 항해사" +description = "10킬로미터(10,000블록) 이상 수영하기" [advancement.challenge_fly_1k] - title = "하늘의 춤" - description = "1킬로미터(1,000블록) 이상 비행하기" +title = "하늘의 춤" +description = "1킬로미터(1,000블록) 이상 비행하기" [advancement.challenge_fly_10k] - title = "바람 타는 자" - description = "10킬로미터(10,000블록) 이상 비행하기" +title = "바람 타는 자" +description = "10킬로미터(10,000블록) 이상 비행하기" [advancement.challenge_agility_sneak_500] - title = "조용한 발걸음" - description = "500블록 이상 웅크리기" +title = "조용한 발걸음" +description = "500블록 이상 웅크리기" [advancement.challenge_agility_sneak_5k] - title = "유령 걸음" - description = "5킬로미터(5,000블록) 이상 웅크리기" +title = "유령 걸음" +description = "5킬로미터(5,000블록) 이상 웅크리기" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "철거 부대" - description = "500블록 파괴하기" +title = "철거 부대" +description = "500블록 파괴하기" [advancement.challenge_demolish_5k] - title = "파괴의 공" - description = "5,000블록 파괴하기" +title = "파괴의 공" +description = "5,000블록 파괴하기" [advancement.challenge_value_placed_10k] - title = "가치 있는 건축가" - description = "10,000 가치의 블록 설치하기" +title = "가치 있는 건축가" +description = "10,000 가치의 블록 설치하기" [advancement.challenge_value_placed_100k] - title = "건축의 대가" - description = "100,000 가치의 블록 설치하기" +title = "건축의 대가" +description = "100,000 가치의 블록 설치하기" [advancement.challenge_demolish_val_5k] - title = "회수 전문가" - description = "철거에서 5,000 가치를 회수하기" +title = "회수 전문가" +description = "철거에서 5,000 가치를 회수하기" [advancement.challenge_demolish_val_50k] - title = "완전 해체" - description = "철거에서 50,000 가치를 회수하기" +title = "완전 해체" +description = "철거에서 50,000 가치를 회수하기" [advancement.challenge_high_build_100] - title = "하늘 건축가" - description = "Y=128 위에 100블록 설치하기" +title = "하늘 건축가" +description = "Y=128 위에 100블록 설치하기" [advancement.challenge_high_build_1k] - title = "구름 위의 건축사" - description = "Y=128 위에 1,000블록 설치하기" +title = "구름 위의 건축사" +description = "Y=128 위에 1,000블록 설치하기" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "도끼 휘두르기" - description = "도끼를 500번 휘두르기" +title = "도끼 휘두르기" +description = "도끼를 500번 휘두르기" [advancement.challenge_axe_swing_5k] - title = "광전사" - description = "도끼를 5,000번 휘두르기" +title = "광전사" +description = "도끼를 5,000번 휘두르기" [advancement.challenge_axe_damage_1k] - title = "절단자" - description = "도끼로 1,000 데미지 입히기" +title = "절단자" +description = "도끼로 1,000 데미지 입히기" [advancement.challenge_axe_damage_10k] - title = "처형인의 도끼" - description = "도끼로 10,000 데미지 입히기" +title = "처형인의 도끼" +description = "도끼로 10,000 데미지 입히기" [advancement.challenge_axe_value_5k] - title = "목재 상인" - description = "5,000 가치의 목재를 수확하기" +title = "목재 상인" +description = "5,000 가치의 목재를 수확하기" [advancement.challenge_axe_value_50k] - title = "벌목 재벌" - description = "50,000 가치의 목재를 수확하기" +title = "벌목 재벌" +description = "50,000 가치의 목재를 수확하기" [advancement.challenge_leaves_500] - title = "낙엽 청소기" - description = "도끼로 잎 블록 500개 제거하기" +title = "낙엽 청소기" +description = "도끼로 잎 블록 500개 제거하기" [advancement.challenge_leaves_5k] - title = "낙엽 분쇄기" - description = "도끼로 잎 블록 5,000개 제거하기" +title = "낙엽 분쇄기" +description = "도끼로 잎 블록 5,000개 제거하기" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "데미지 흡수" - description = "방패로 1,000 데미지 막기" +title = "데미지 흡수" +description = "방패로 1,000 데미지 막기" [advancement.challenge_block_dmg_10k] - title = "인간 방패" - description = "방패로 10,000 데미지 막기" +title = "인간 방패" +description = "방패로 10,000 데미지 막기" [advancement.challenge_block_proj_100] - title = "화살 튕기기" - description = "방패로 투사체 100개 막기" +title = "화살 튕기기" +description = "방패로 투사체 100개 막기" [advancement.challenge_block_proj_1k] - title = "투사체 방패" - description = "방패로 투사체 1,000개 막기" +title = "투사체 방패" +description = "방패로 투사체 1,000개 막기" [advancement.challenge_block_melee_500] - title = "패리의 달인" - description = "방패로 근접 공격 500회 막기" +title = "패리의 달인" +description = "방패로 근접 공격 500회 막기" [advancement.challenge_block_melee_5k] - title = "철의 요새" - description = "방패로 근접 공격 5,000회 막기" +title = "철의 요새" +description = "방패로 근접 공격 5,000회 막기" [advancement.challenge_block_heavy_50] - title = "탱커" - description = "강력한 공격(5 데미지 초과) 50회 막기" +title = "탱커" +description = "강력한 공격(5 데미지 초과) 50회 막기" [advancement.challenge_block_heavy_500] - title = "움직이지 않는 벽" - description = "강력한 공격(5 데미지 초과) 500회 막기" +title = "움직이지 않는 벽" +description = "강력한 공격(5 데미지 초과) 500회 막기" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "양조 준비" - description = "양조기 10개 설치하기" +title = "양조 준비" +description = "양조기 10개 설치하기" [advancement.challenge_brew_stands_50] - title = "포션 공장" - description = "양조기 50개 설치하기" +title = "포션 공장" +description = "양조기 50개 설치하기" [advancement.challenge_brew_strong_25] - title = "강력한 양조" - description = "강화된 포션 25개 마시기" +title = "강력한 양조" +description = "강화된 포션 25개 마시기" [advancement.challenge_brew_strong_250] - title = "최대 효능" - description = "강화된 포션 250개 마시기" +title = "최대 효능" +description = "강화된 포션 250개 마시기" [advancement.challenge_brew_splash_hits_50] - title = "투척 범위" - description = "투척 포션으로 엔티티 50마리 맞추기" +title = "투척 범위" +description = "투척 포션으로 엔티티 50마리 맞추기" [advancement.challenge_brew_splash_hits_500] - title = "역병 의사" - description = "투척 포션으로 엔티티 500마리 맞추기" +title = "역병 의사" +description = "투척 포션으로 엔티티 500마리 맞추기" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "쉬지 않는 자" - description = "활동 중 1킬로미터 이동하기" +title = "쉬지 않는 자" +description = "활동 중 1킬로미터 이동하기" [advancement.challenge_active_dist_10k] - title = "길 개척자" - description = "활동 중 10킬로미터 이동하기" +title = "길 개척자" +description = "활동 중 10킬로미터 이동하기" [advancement.challenge_active_dist_100k] - title = "영원한 움직임" - description = "활동 중 100킬로미터 이동하기" +title = "영원한 움직임" +description = "활동 중 100킬로미터 이동하기" [advancement.challenge_beds_10] - title = "이른 기상" - description = "침대에서 10번 자기" +title = "이른 기상" +description = "침대에서 10번 자기" [advancement.challenge_beds_100] - title = "시간 건너뛰기" - description = "침대에서 100번 자기" +title = "시간 건너뛰기" +description = "침대에서 100번 자기" [advancement.challenge_chronos_tp_50] - title = "시공간 이동" - description = "50번 텔레포트하기" +title = "시공간 이동" +description = "50번 텔레포트하기" [advancement.challenge_chronos_tp_500] - title = "시간의 왜곡" - description = "500번 텔레포트하기" +title = "시간의 왜곡" +description = "500번 텔레포트하기" [advancement.challenge_chronos_deaths_10] - title = "필멸자" - description = "10번 죽기" +title = "필멸자" +description = "10번 죽기" [advancement.challenge_chronos_deaths_100] - title = "죽음을 이기는 자" - description = "100번 죽기" +title = "죽음을 이기는 자" +description = "100번 죽기" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "제작의 가치" - description = "총 10,000 가치의 아이템 제작하기" +title = "제작의 가치" +description = "총 10,000 가치의 아이템 제작하기" [advancement.challenge_craft_value_100k] - title = "장인" - description = "총 100,000 가치의 아이템 제작하기" +title = "장인" +description = "총 100,000 가치의 아이템 제작하기" [advancement.challenge_craft_tools_25] - title = "도구 대장장이" - description = "도구 25개 제작하기" +title = "도구 대장장이" +description = "도구 25개 제작하기" [advancement.challenge_craft_tools_250] - title = "단조의 대가" - description = "도구 250개 제작하기" +title = "단조의 대가" +description = "도구 250개 제작하기" [advancement.challenge_craft_armor_25] - title = "갑옷 대장장이" - description = "갑옷 25개 제작하기" +title = "갑옷 대장장이" +description = "갑옷 25개 제작하기" [advancement.challenge_craft_armor_250] - title = "갑옷의 대가" - description = "갑옷 250개 제작하기" +title = "갑옷의 대가" +description = "갑옷 250개 제작하기" [advancement.challenge_craft_mass_25k] - title = "대량 생산" - description = "아이템 25,000개 제작하기" +title = "대량 생산" +description = "아이템 25,000개 제작하기" [advancement.challenge_craft_mass_250k] - title = "산업 혁명" - description = "아이템 250,000개 제작하기" +title = "산업 혁명" +description = "아이템 250,000개 제작하기" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "수집가" - description = "고유 아이템 50종 발견하기" +title = "수집가" +description = "고유 아이템 50종 발견하기" [advancement.challenge_discover_items_250] - title = "목록 작성자" - description = "고유 아이템 250종 발견하기" +title = "목록 작성자" +description = "고유 아이템 250종 발견하기" [advancement.challenge_discover_blocks_50] - title = "측량사" - description = "고유 블록 50종 발견하기" +title = "측량사" +description = "고유 블록 50종 발견하기" [advancement.challenge_discover_blocks_250] - title = "지질학자" - description = "고유 블록 250종 발견하기" +title = "지질학자" +description = "고유 블록 250종 발견하기" [advancement.challenge_discover_mobs_25] - title = "관찰자" - description = "고유 몹 25종 발견하기" +title = "관찰자" +description = "고유 몹 25종 발견하기" [advancement.challenge_discover_mobs_75] - title = "박물학자" - description = "고유 몹 75종 발견하기" +title = "박물학자" +description = "고유 몹 75종 발견하기" [advancement.challenge_discover_biomes_10] - title = "방랑자" - description = "고유 바이옴 10종 발견하기" +title = "방랑자" +description = "고유 바이옴 10종 발견하기" [advancement.challenge_discover_biomes_40] - title = "세계 여행자" - description = "고유 바이옴 40종 발견하기" +title = "세계 여행자" +description = "고유 바이옴 40종 발견하기" [advancement.challenge_discover_foods_10] - title = "미식가" - description = "고유 음식 10종 발견하기" +title = "미식가" +description = "고유 음식 10종 발견하기" [advancement.challenge_discover_foods_30] - title = "요리의 달인" - description = "고유 음식 30종 발견하기" +title = "요리의 달인" +description = "고유 음식 30종 발견하기" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "힘의 직공" - description = "인챈트 파워 100 축적하기" +title = "힘의 직공" +description = "인챈트 파워 100 축적하기" [advancement.challenge_enchant_power_1k] - title = "비술의 대가" - description = "인챈트 파워 1,000 축적하기" +title = "비술의 대가" +description = "인챈트 파워 1,000 축적하기" [advancement.challenge_enchant_levels_1k] - title = "레벨 소모자" - description = "인챈트에 경험 레벨 1,000 사용하기" +title = "레벨 소모자" +description = "인챈트에 경험 레벨 1,000 사용하기" [advancement.challenge_enchant_levels_10k] - title = "경험치의 무덤" - description = "인챈트에 경험 레벨 10,000 사용하기" +title = "경험치의 무덤" +description = "인챈트에 경험 레벨 10,000 사용하기" [advancement.challenge_enchant_high_25] - title = "도박사" - description = "최대 레벨 인챈트 25회 수행하기" +title = "도박사" +description = "최대 레벨 인챈트 25회 수행하기" [advancement.challenge_enchant_high_250] - title = "전설의 인챈터" - description = "최대 레벨 인챈트 250회 수행하기" +title = "전설의 인챈터" +description = "최대 레벨 인챈트 250회 수행하기" [advancement.challenge_enchant_total_500] - title = "레벨 연소" - description = "모든 인챈트에 총 500레벨 사용하기" +title = "레벨 연소" +description = "모든 인챈트에 총 500레벨 사용하기" [advancement.challenge_enchant_total_5k] - title = "비술의 투자" - description = "모든 인챈트에 총 5,000레벨 사용하기" +title = "비술의 투자" +description = "모든 인챈트에 총 5,000레벨 사용하기" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "삽질꾼" - description = "삽을 500번 휘두르기" +title = "삽질꾼" +description = "삽을 500번 휘두르기" [advancement.challenge_dig_swing_5k] - title = "굴착기" - description = "삽을 5,000번 휘두르기" +title = "굴착기" +description = "삽을 5,000번 휘두르기" [advancement.challenge_dig_damage_1k] - title = "삽의 기사" - description = "삽으로 1,000 데미지 입히기" +title = "삽의 기사" +description = "삽으로 1,000 데미지 입히기" [advancement.challenge_dig_damage_10k] - title = "삽의 대가" - description = "삽으로 10,000 데미지 입히기" +title = "삽의 대가" +description = "삽으로 10,000 데미지 입히기" [advancement.challenge_dig_value_5k] - title = "흙 상인" - description = "5,000 가치의 블록 굴착하기" +title = "흙 상인" +description = "5,000 가치의 블록 굴착하기" [advancement.challenge_dig_value_50k] - title = "대지의 지배자" - description = "50,000 가치의 블록 굴착하기" +title = "대지의 지배자" +description = "50,000 가치의 블록 굴착하기" [advancement.challenge_dig_gravel_500] - title = "자갈 분쇄기" - description = "자갈, 모래, 점토 블록 500개 파기" +title = "자갈 분쇄기" +description = "자갈, 모래, 점토 블록 500개 파기" [advancement.challenge_dig_gravel_5k] - title = "모래 체질" - description = "자갈, 모래, 점토 블록 5,000개 파기" +title = "모래 체질" +description = "자갈, 모래, 점토 블록 5,000개 파기" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "씨 뿌리는 자" - description = "작물 100개 심기" +title = "씨 뿌리는 자" +description = "작물 100개 심기" [advancement.challenge_plant_1k] - title = "녹색 손가락" - description = "작물 1,000개 심기" +title = "녹색 손가락" +description = "작물 1,000개 심기" [advancement.challenge_plant_5k] - title = "농업 재벌" - description = "작물 5,000개 심기" +title = "농업 재벌" +description = "작물 5,000개 심기" [advancement.challenge_compost_50] - title = "재활용가" - description = "아이템 50개 퇴비로 만들기" +title = "재활용가" +description = "아이템 50개 퇴비로 만들기" [advancement.challenge_compost_500] - title = "토양 개량가" - description = "아이템 500개 퇴비로 만들기" +title = "토양 개량가" +description = "아이템 500개 퇴비로 만들기" [advancement.challenge_shear_50] - title = "양털 깎는 자" - description = "엔티티 50마리 양털 깎기" +title = "양털 깎는 자" +description = "엔티티 50마리 양털 깎기" [advancement.challenge_shear_250] - title = "무리의 주인" - description = "엔티티 250마리 양털 깎기" +title = "무리의 주인" +description = "엔티티 250마리 양털 깎기" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "사냥꾼" - description = "생물 500마리 처치하기" +title = "사냥꾼" +description = "생물 500마리 처치하기" [advancement.challenge_kills_5k] - title = "처형인" - description = "생물 5,000마리 처치하기" +title = "처형인" +description = "생물 5,000마리 처치하기" [advancement.challenge_boss_1] - title = "보스 도전자" - description = "보스 몹 1마리 처치하기" +title = "보스 도전자" +description = "보스 몹 1마리 처치하기" [advancement.challenge_boss_10] - title = "전설 사냥꾼" - description = "보스 몹 10마리 처치하기" +title = "전설 사냥꾼" +description = "보스 몹 10마리 처치하기" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "시듦" - description = "위더 데미지 500 견디기" +title = "시듦" +description = "위더 데미지 500 견디기" [advancement.challenge_wither_dmg_5k] - title = "역병의 생존자" - description = "위더 데미지 5,000 견디기" +title = "역병의 생존자" +description = "위더 데미지 5,000 견디기" [advancement.challenge_wither_skel_25] - title = "뼈 수집가" - description = "위더 스켈레톤 25마리 처치하기" +title = "뼈 수집가" +description = "위더 스켈레톤 25마리 처치하기" [advancement.challenge_wither_skel_250] - title = "해골의 천적" - description = "위더 스켈레톤 250마리 처치하기" +title = "해골의 천적" +description = "위더 스켈레톤 250마리 처치하기" [advancement.challenge_wither_boss_1] - title = "위더 처치자" - description = "위더 처치하기" +title = "위더 처치자" +description = "위더 처치하기" [advancement.challenge_wither_boss_10] - title = "네더의 지배자" - description = "위더 10번 처치하기" +title = "네더의 지배자" +description = "위더 10번 처치하기" [advancement.challenge_roses_10] - title = "죽음의 정원사" - description = "위더 장미 10송이 부수기" +title = "죽음의 정원사" +description = "위더 장미 10송이 부수기" [advancement.challenge_roses_100] - title = "역병 수집가" - description = "위더 장미 100송이 부수기" +title = "역병 수집가" +description = "위더 장미 100송이 부수기" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "광부의 팔" - description = "곡괭이를 500번 휘두르기" +title = "광부의 팔" +description = "곡괭이를 500번 휘두르기" [advancement.challenge_pick_swing_5k] - title = "터널 굴착자" - description = "곡괭이를 5,000번 휘두르기" +title = "터널 굴착자" +description = "곡괭이를 5,000번 휘두르기" [advancement.challenge_pick_damage_1k] - title = "곡괭이 전사" - description = "곡괭이로 1,000 데미지 입히기" +title = "곡괭이 전사" +description = "곡괭이로 1,000 데미지 입히기" [advancement.challenge_pick_damage_10k] - title = "전쟁의 곡괭이" - description = "곡괭이로 10,000 데미지 입히기" +title = "전쟁의 곡괭이" +description = "곡괭이로 10,000 데미지 입히기" [advancement.challenge_pick_value_5k] - title = "보석 탐색자" - description = "5,000 가치의 블록 채굴하기" +title = "보석 탐색자" +description = "5,000 가치의 블록 채굴하기" [advancement.challenge_pick_value_50k] - title = "광석 재벌" - description = "50,000 가치의 블록 채굴하기" +title = "광석 재벌" +description = "50,000 가치의 블록 채굴하기" [advancement.challenge_pick_ores_500] - title = "탐광자" - description = "광석 블록 500개 채굴하기" +title = "탐광자" +description = "광석 블록 500개 채굴하기" [advancement.challenge_pick_ores_5k] - title = "채굴의 대가" - description = "광석 블록 5,000개 채굴하기" +title = "채굴의 대가" +description = "광석 블록 5,000개 채굴하기" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "명사수" - description = "원거리 데미지 1,000 입히기" +title = "명사수" +description = "원거리 데미지 1,000 입히기" [advancement.challenge_ranged_dmg_10k] - title = "치명적 궁수" - description = "원거리 데미지 10,000 입히기" +title = "치명적 궁수" +description = "원거리 데미지 10,000 입히기" [advancement.challenge_ranged_dist_5k] - title = "장거리 사격" - description = "투사체 총 비행 거리 5,000블록 달성하기" +title = "장거리 사격" +description = "투사체 총 비행 거리 5,000블록 달성하기" [advancement.challenge_ranged_dist_50k] - title = "초장거리 사수" - description = "투사체 총 비행 거리 50,000블록 달성하기" +title = "초장거리 사수" +description = "투사체 총 비행 거리 50,000블록 달성하기" [advancement.challenge_ranged_kills_50] - title = "궁수" - description = "원거리 무기로 몹 50마리 처치하기" +title = "궁수" +description = "원거리 무기로 몹 50마리 처치하기" [advancement.challenge_ranged_kills_500] - title = "궁술의 대가" - description = "원거리 무기로 몹 500마리 처치하기" +title = "궁술의 대가" +description = "원거리 무기로 몹 500마리 처치하기" [advancement.challenge_longshot_25] - title = "저격수" - description = "장거리 사격(30블록 초과) 25회 명중하기" +title = "저격수" +description = "장거리 사격(30블록 초과) 25회 명중하기" [advancement.challenge_longshot_250] - title = "매의 눈" - description = "장거리 사격(30블록 초과) 250회 명중하기" +title = "매의 눈" +description = "장거리 사격(30블록 초과) 250회 명중하기" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "진주 던지기" - description = "엔더 진주 50개 던지기" +title = "진주 던지기" +description = "엔더 진주 50개 던지기" [advancement.challenge_rift_pearls_500] - title = "텔레포트 중독" - description = "엔더 진주 500개 던지기" +title = "텔레포트 중독" +description = "엔더 진주 500개 던지기" [advancement.challenge_rift_enderman_50] - title = "엔더맨 사냥꾼" - description = "엔더맨 50마리 처치하기" +title = "엔더맨 사냥꾼" +description = "엔더맨 50마리 처치하기" [advancement.challenge_rift_enderman_500] - title = "공허의 추적자" - description = "엔더맨 500마리 처치하기" +title = "공허의 추적자" +description = "엔더맨 500마리 처치하기" [advancement.challenge_rift_dragon_500] - title = "용과 싸우는 자" - description = "엔더 드래곤에게 500 데미지 입히기" +title = "용과 싸우는 자" +description = "엔더 드래곤에게 500 데미지 입히기" [advancement.challenge_rift_dragon_5k] - title = "용살자" - description = "엔더 드래곤에게 5,000 데미지 입히기" +title = "용살자" +description = "엔더 드래곤에게 5,000 데미지 입히기" [advancement.challenge_rift_crystal_10] - title = "수정 파괴자" - description = "엔드 수정 10개 파괴하기" +title = "수정 파괴자" +description = "엔드 수정 10개 파괴하기" [advancement.challenge_rift_crystal_100] - title = "엔드 파괴자" - description = "엔드 수정 100개 파괴하기" +title = "엔드 파괴자" +description = "엔드 수정 100개 파괴하기" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "낚시꾼" - description = "물고기 25마리 잡기" +title = "낚시꾼" +description = "물고기 25마리 잡기" [advancement.challenge_fish_250] - title = "낚시의 달인" - description = "물고기 250마리 잡기" +title = "낚시의 달인" +description = "물고기 250마리 잡기" [advancement.challenge_drowned_25] - title = "드라운드 사냥꾼" - description = "드라운드 25마리 처치하기" +title = "드라운드 사냥꾼" +description = "드라운드 25마리 처치하기" [advancement.challenge_drowned_250] - title = "바다 정화자" - description = "드라운드 250마리 처치하기" +title = "바다 정화자" +description = "드라운드 250마리 처치하기" [advancement.challenge_guardian_10] - title = "가디언 처치자" - description = "가디언 10마리 처치하기" +title = "가디언 처치자" +description = "가디언 10마리 처치하기" [advancement.challenge_guardian_100] - title = "신전 침략자" - description = "가디언 100마리 처치하기" +title = "신전 침략자" +description = "가디언 100마리 처치하기" [advancement.challenge_underwater_blocks_100] - title = "수중 채굴자" - description = "수중에서 블록 100개 파괴하기" +title = "수중 채굴자" +description = "수중에서 블록 100개 파괴하기" [advancement.challenge_underwater_blocks_1k] - title = "수중 기술자" - description = "수중에서 블록 1,000개 파괴하기" +title = "수중 기술자" +description = "수중에서 블록 1,000개 파괴하기" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "등 뒤의 칼" - description = "웅크리기 중 500 데미지 입히기" +title = "등 뒤의 칼" +description = "웅크리기 중 500 데미지 입히기" [advancement.challenge_stealth_dmg_5k] - title = "침묵의 살인자" - description = "웅크리기 중 5,000 데미지 입히기" +title = "침묵의 살인자" +description = "웅크리기 중 5,000 데미지 입히기" [advancement.challenge_stealth_kills_10] - title = "암살자" - description = "웅크리기 중 몹 10마리 처치하기" +title = "암살자" +description = "웅크리기 중 몹 10마리 처치하기" [advancement.challenge_stealth_kills_100] - title = "그림자 사신" - description = "웅크리기 중 몹 100마리 처치하기" +title = "그림자 사신" +description = "웅크리기 중 몹 100마리 처치하기" [advancement.challenge_stealth_time_1h] - title = "인내" - description = "1시간(3,600초) 웅크리기" +title = "인내" +description = "1시간(3,600초) 웅크리기" [advancement.challenge_stealth_time_10h] - title = "그림자의 지배자" - description = "10시간(36,000초) 웅크리기" +title = "그림자의 지배자" +description = "10시간(36,000초) 웅크리기" [advancement.challenge_stealth_arrows_50] - title = "조용한 궁수" - description = "웅크리기 중 화살 50발 발사하기" +title = "조용한 궁수" +description = "웅크리기 중 화살 50발 발사하기" [advancement.challenge_stealth_arrows_500] - title = "유령 궁수" - description = "웅크리기 중 화살 500발 발사하기" +title = "유령 궁수" +description = "웅크리기 중 화살 500발 발사하기" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "검의 견습생" - description = "검으로 1,000 데미지 입히기" +title = "검의 견습생" +description = "검으로 1,000 데미지 입히기" [advancement.challenge_sword_dmg_10k] - title = "검사" - description = "검으로 10,000 데미지 입히기" +title = "검사" +description = "검으로 10,000 데미지 입히기" [advancement.challenge_sword_kills_50] - title = "결투사" - description = "검으로 몹 50마리 처치하기" +title = "결투사" +description = "검으로 몹 50마리 처치하기" [advancement.challenge_sword_kills_500] - title = "검투사" - description = "검으로 몹 500마리 처치하기" +title = "검투사" +description = "검으로 몹 500마리 처치하기" [advancement.challenge_sword_crit_50] - title = "치명타" - description = "검으로 치명타 50회 달성하기" +title = "치명타" +description = "검으로 치명타 50회 달성하기" [advancement.challenge_sword_crit_500] - title = "정밀의 달인" - description = "검으로 치명타 500회 달성하기" +title = "정밀의 달인" +description = "검으로 치명타 500회 달성하기" [advancement.challenge_sword_heavy_25] - title = "강타" - description = "검으로 강력한 일격(8 데미지 초과) 25회 달성하기" +title = "강타" +description = "검으로 강력한 일격(8 데미지 초과) 25회 달성하기" [advancement.challenge_sword_heavy_250] - title = "파멸의 일격" - description = "검으로 강력한 일격(8 데미지 초과) 250회 달성하기" +title = "파멸의 일격" +description = "검으로 강력한 일격(8 데미지 초과) 250회 달성하기" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "야수 조련사" - description = "펫이 총 500 데미지 입히기" +title = "야수 조련사" +description = "펫이 총 500 데미지 입히기" [advancement.challenge_pet_dmg_5k] - title = "전쟁의 주인" - description = "펫이 총 5,000 데미지 입히기" +title = "전쟁의 주인" +description = "펫이 총 5,000 데미지 입히기" [advancement.challenge_tamed_10] - title = "동물의 친구" - description = "동물 10마리 길들이기" +title = "동물의 친구" +description = "동물 10마리 길들이기" [advancement.challenge_tamed_100] - title = "동물원 관리인" - description = "동물 100마리 길들이기" +title = "동물원 관리인" +description = "동물 100마리 길들이기" [advancement.challenge_pet_kills_25] - title = "무리의 전술" - description = "펫이 생물 25마리 처치하기" +title = "무리의 전술" +description = "펫이 생물 25마리 처치하기" [advancement.challenge_pet_kills_250] - title = "우두머리 지휘관" - description = "펫이 생물 250마리 처치하기" +title = "우두머리 지휘관" +description = "펫이 생물 250마리 처치하기" [advancement.challenge_taming_2500] - title = "번식 전문가" - description = "동물 2,500마리 번식시키기" +title = "번식 전문가" +description = "동물 2,500마리 번식시키기" [advancement.challenge_taming_25k] - title = "유전학의 대가" - description = "동물 25,000마리 번식시키기" +title = "유전학의 대가" +description = "동물 25,000마리 번식시키기" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "샌드백" - description = "500번 맞기" +title = "샌드백" +description = "500번 맞기" [advancement.challenge_trag_hits_5k] - title = "고통의 미식가" - description = "5,000번 맞기" +title = "고통의 미식가" +description = "5,000번 맞기" [advancement.challenge_trag_deaths_10] - title = "아홉 개의 생명" - description = "10번 죽기" +title = "아홉 개의 생명" +description = "10번 죽기" [advancement.challenge_trag_deaths_100] - title = "반복되는 악몽" - description = "100번 죽기" +title = "반복되는 악몽" +description = "100번 죽기" [advancement.challenge_trag_fire_500] - title = "화상 피해자" - description = "화염 데미지 500 견디기" +title = "화상 피해자" +description = "화염 데미지 500 견디기" [advancement.challenge_trag_fire_5k] - title = "불사조" - description = "화염 데미지 5,000 견디기" +title = "불사조" +description = "화염 데미지 5,000 견디기" [advancement.challenge_trag_fall_500] - title = "중력 확인" - description = "낙하 데미지 500 견디기" +title = "중력 확인" +description = "낙하 데미지 500 견디기" [advancement.challenge_trag_fall_5k] - title = "종단 속도" - description = "낙하 데미지 5,000 견디기" +title = "종단 속도" +description = "낙하 데미지 5,000 견디기" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "싸움꾼" - description = "맨주먹으로 1,000 데미지 입히기" +title = "싸움꾼" +description = "맨주먹으로 1,000 데미지 입히기" [advancement.challenge_unarmed_dmg_10k] - title = "무술가" - description = "맨주먹으로 10,000 데미지 입히기" +title = "무술가" +description = "맨주먹으로 10,000 데미지 입히기" [advancement.challenge_unarmed_kills_25] - title = "맨주먹 싸움" - description = "맨주먹으로 몹 25마리 처치하기" +title = "맨주먹 싸움" +description = "맨주먹으로 몹 25마리 처치하기" [advancement.challenge_unarmed_kills_250] - title = "주먹의 전설" - description = "맨주먹으로 몹 250마리 처치하기" +title = "주먹의 전설" +description = "맨주먹으로 몹 250마리 처치하기" [advancement.challenge_unarmed_crit_25] - title = "치명적 주먹" - description = "맨주먹으로 치명타 25회 달성하기" +title = "치명적 주먹" +description = "맨주먹으로 치명타 25회 달성하기" [advancement.challenge_unarmed_crit_250] - title = "정밀한 주먹" - description = "맨주먹으로 치명타 250회 달성하기" +title = "정밀한 주먹" +description = "맨주먹으로 치명타 250회 달성하기" [advancement.challenge_unarmed_heavy_25] - title = "강력한 주먹" - description = "맨주먹으로 강력한 일격(6 데미지 초과) 25회 달성하기" +title = "강력한 주먹" +description = "맨주먹으로 강력한 일격(6 데미지 초과) 25회 달성하기" [advancement.challenge_unarmed_heavy_250] - title = "일격필살" - description = "맨주먹으로 강력한 일격(6 데미지 초과) 250회 달성하기" +title = "일격필살" +description = "맨주먹으로 강력한 일격(6 데미지 초과) 250회 달성하기" # items [items] [items.bound_ender_peral] - name = "유물 포트키" - usage1 = "Shift + 좌클릭으로 연결" - usage2 = "우클릭으로 연결된 인벤토리에 접근" +name = "유물 포트키" +usage1 = "Shift + 좌클릭으로 연결" +usage2 = "우클릭으로 연결된 인벤토리에 접근" [items.bound_eye_of_ender] - name = "안구 닻" - usage1 = "우클릭하여 소모하고 연결된 위치로 순간이동" - usage2 = "Shift + 좌클릭으로 블록에 연결" +name = "안구 닻" +usage1 = "우클릭하여 소모하고 연결된 위치로 순간이동" +usage2 = "Shift + 좌클릭으로 블록에 연결" [items.bound_redstone_torch] - name = "레드스톤 리모컨" - usage1 = "우클릭으로 1틱 레드스톤 신호 발생" - usage2 = "'대상' 블록에 Shift + 좌클릭으로 연결" +name = "레드스톤 리모컨" +usage1 = "우클릭으로 1틱 레드스톤 신호 발생" +usage2 = "'대상' 블록에 Shift + 좌클릭으로 연결" [items.bound_snowball] - name = "거미줄 올가미!" - usage1 = "던져서 해당 위치에 임시 거미줄 함정을 만듭니다" +name = "거미줄 올가미!" +usage1 = "던져서 해당 위치에 임시 거미줄 함정을 만듭니다" [items.chrono_time_bottle] - name = "시간을 담은 병" - usage1 = "인벤토리에 있는 동안 수동으로 시간을 저장합니다" - usage2 = "우클릭으로 시간이 걸리는 블록이나 새끼 동물에게 저장된 시간을 사용합니다" - stored = "저장된 시간" +name = "시간을 담은 병" +usage1 = "인벤토리에 있는 동안 수동으로 시간을 저장합니다" +usage2 = "우클릭으로 시간이 걸리는 블록이나 새끼 동물에게 저장된 시간을 사용합니다" +stored = "저장된 시간" [items.chrono_time_bomb] - name = "시간 폭탄" - usage1 = "우클릭하여 시간의 장을 생성하는 시간 화살을 발사합니다" +name = "시간 폭탄" +usage1 = "우클릭하여 시간의 장을 생성하는 시간 화살을 발사합니다" [items.elevator_block] - name = "엘리베이터 블록" - usage1 = "점프하여 위로 순간이동" - usage2 = "Shift로 아래로 순간이동" - usage3 = "엘리베이터 사이에 최소 2칸의 공기 블록이 필요합니다" +name = "엘리베이터 블록" +usage1 = "점프하여 위로 순간이동" +usage2 = "Shift로 아래로 순간이동" +usage3 = "엘리베이터 사이에 최소 2칸의 공기 블록이 필요합니다" # snippets [snippets] [snippets.gui] - level = "레벨" - knowledge = "지식" - power_used = "사용 중인 능력치" - not_learned = "미습득" - xp = "XP까지" - welcome = "환영합니다!" - welcome_back = "다시 오신 것을 환영합니다!" - xp_bonus_for_time = "XP 보너스" - max_ability_power = "최대 능력 파워" - unlock_this_by_clicking = "우클릭하여 잠금 해제: " - back = "뒤로" - unlearn_all = "모두 잊기" - unlearned_all = "모두 잊었습니다" +level = "레벨" +knowledge = "지식" +power_used = "사용 중인 능력치" +not_learned = "미습득" +xp = "XP까지" +welcome = "환영합니다!" +welcome_back = "다시 오신 것을 환영합니다!" +xp_bonus_for_time = "XP 보너스" +max_ability_power = "최대 능력 파워" +unlock_this_by_clicking = "우클릭하여 잠금 해제: " +back = "뒤로" +unlearn_all = "모두 잊기" +unlearned_all = "모두 잊었습니다" [snippets.adapt_menu] - may_not_unlearn = "잊을 수 없습니다" - may_unlearn = "배우기/잊기 가능" - knowledge_cost = "지식 비용" - knowledge_available = "사용 가능한 지식" - already_learned = "이미 습득함" - unlearn_refund = "클릭하여 잊기 및 환불" - no_refunds = "하드코어, 환불 불가" - knowledge = "지식" - click_learn = "클릭하여 배우기" - no_knowledge = "(지식이 없습니다)" - you_only_have = "현재 보유량:" - how_to_level_up = "스킬 레벨을 올려 최대 파워를 높이세요." - not_enough_power = "파워가 부족합니다! 각 능력 레벨은 파워 1을 소모합니다." - power = "파워" - power_drain = "파워 소모" - learned = "습득 완료 " - unlearned = "잊음 " - activator_block = "책장" +may_not_unlearn = "잊을 수 없습니다" +may_unlearn = "배우기/잊기 가능" +knowledge_cost = "지식 비용" +knowledge_available = "사용 가능한 지식" +already_learned = "이미 습득함" +unlearn_refund = "클릭하여 잊기 및 환불" +no_refunds = "하드코어, 환불 불가" +knowledge = "지식" +click_learn = "클릭하여 배우기" +no_knowledge = "(지식이 없습니다)" +you_only_have = "현재 보유량:" +how_to_level_up = "스킬 레벨을 올려 최대 파워를 높이세요." +not_enough_power = "파워가 부족합니다! 각 능력 레벨은 파워 1을 소모합니다." +power = "파워" +power_drain = "파워 소모" +learned = "습득 완료 " +unlearned = "잊음 " +activator_block = "책장" [snippets.knowledge_orb] - contains = "포함" - knowledge = "지식" - rightclick = "우클릭" - togainknowledge = "하여 이 지식을 획득" - knowledge_orb = "지식의 구슬" +contains = "포함" +knowledge = "지식" +rightclick = "우클릭" +togainknowledge = "하여 이 지식을 획득" +knowledge_orb = "지식의 구슬" [snippets.experience_orb] - contains = "포함" - xp = "경험치" - rightclick = "우클릭" - togainxp = "하여 이 경험치를 획득" - xporb = "경험치 구슬" +contains = "포함" +xp = "경험치" +rightclick = "우클릭" +togainxp = "하여 이 경험치를 획득" +xporb = "경험치 구슬" # skill [skill] [skill.agility] - name = "민첩" - icon = "⇉" - description = "민첩은 장애물 앞에서 빠르고 유연하게 움직이는 능력입니다." +name = "민첩" +icon = "⇉" +description = "민첩은 장애물 앞에서 빠르고 유연하게 움직이는 능력입니다." [skill.architect] - name = "건축" - icon = "⬧" - description = "구조물은 세계의 기초입니다. 현실은 당신의 손에 달려 있으며, 당신이 지배합니다." +name = "건축" +icon = "⬧" +description = "구조물은 세계의 기초입니다. 현실은 당신의 손에 달려 있으며, 당신이 지배합니다." [skill.axes] - name = "도끼" - icon = "🪓" - description1 = "왜 나무를 베겠어, " - description2 = "것들을" - description3 = " 벨 수 있는데, 결과는 똑같잖아!" +name = "도끼" +icon = "🪓" +description1 = "왜 나무를 베겠어, " +description2 = "것들을" +description3 = " 벨 수 있는데, 결과는 똑같잖아!" [skill.brewing] - name = "양조" - icon = "❦" - description = "두 배 거품, 세 배 거품, 네 배 거품- 아직도 이 물약을 가마솥에 넣을 수 없다니" +name = "양조" +icon = "❦" +description = "두 배 거품, 세 배 거품, 네 배 거품- 아직도 이 물약을 가마솥에 넣을 수 없다니" [skill.blocking] - name = "방어" - icon = "🛡" - description = "돌멩이와 막대기로는 뼈가 부러지지 않지만, 방패는 가능하죠." +name = "방어" +icon = "🛡" +description = "돌멩이와 막대기로는 뼈가 부러지지 않지만, 방패는 가능하죠." [skill.crafting] - name = "제작" - icon = "⌂" - description = "놓을 조각이 더 이상 없다면, 하나 더 만들면 되지 않겠어?" +name = "제작" +icon = "⌂" +description = "놓을 조각이 더 이상 없다면, 하나 더 만들면 되지 않겠어?" [skill.discovery] - name = "탐험" - icon = "⚛" - description = "인식이 확장될수록, 마음이 풀려 알지 못했던 것들을 발견하게 됩니다." +name = "탐험" +icon = "⚛" +description = "인식이 확장될수록, 마음이 풀려 알지 못했던 것들을 발견하게 됩니다." [skill.enchanting] - name = "마법부여" - icon = "♰" - description = "무슨 소리를 하는 거야? 예언, 환상, 미신적인 헛소리?" +name = "마법부여" +icon = "♰" +description = "무슨 소리를 하는 거야? 예언, 환상, 미신적인 헛소리?" [skill.excavation] - name = "발굴" - icon = "ᛳ" - description = "파고 파고 또 파고..." +name = "발굴" +icon = "ᛳ" +description = "파고 파고 또 파고..." [skill.herbalism] - name = "약초학" - icon = "⚘" - description = "식물은 못 찾겠는데 씨앗은 좀 찾았고- 저건... 잡초인가?" +name = "약초학" +icon = "⚘" +description = "식물은 못 찾겠는데 씨앗은 좀 찾았고- 저건... 잡초인가?" [skill.hunter] - name = "사냥" - icon = "☠" - description = "사냥은 결과가 아닌 여정에 관한 것입니다." +name = "사냥" +icon = "☠" +description = "사냥은 결과가 아닌 여정에 관한 것입니다." [skill.nether] - name = "네더" - icon = "₪" - description = "네더 그 자체의 심연에서 비롯된 힘." +name = "네더" +icon = "₪" +description = "네더 그 자체의 심연에서 비롯된 힘." [skill.pickaxe] - name = "곡괭이" - icon = "⛏" - description = "드워프가 광부이긴 하지만, 나도 나름 경험을 쌓았다고. 나는 스웨덴인이야!" +name = "곡괭이" +icon = "⛏" +description = "드워프가 광부이긴 하지만, 나도 나름 경험을 쌓았다고. 나는 스웨덴인이야!" [skill.ranged] - name = "원거리" - icon = "🏹" - description = "거리가 승리의 열쇠이자, 생존의 열쇠입니다." +name = "원거리" +icon = "🏹" +description = "거리가 승리의 열쇠이자, 생존의 열쇠입니다." [skill.rift] - name = "균열" - icon = "❍" - description = "균열은 부식성 속박이지만, 당신은 그 속박을 다스렸습니다." +name = "균열" +icon = "❍" +description = "균열은 부식성 속박이지만, 당신은 그 속박을 다스렸습니다." [skill.seaborne] - name = "해양" - icon = "🎣" - description = "이 스킬로 물의 경이로움을 다스릴 수 있습니다." +name = "해양" +icon = "🎣" +description = "이 스킬로 물의 경이로움을 다스릴 수 있습니다." [skill.stealth] - name = "은신" - icon = "☯" - description = "보이지 않는 자의 예술. 그림자 속을 걸으라." +name = "은신" +icon = "☯" +description = "보이지 않는 자의 예술. 그림자 속을 걸으라." [skill.swords] - name = "검술" - icon = "⚔" - description = "그레이스톤의 힘으로!" +name = "검술" +icon = "⚔" +description = "그레이스톤의 힘으로!" [skill.taming] - name = "조련" - icon = "♥" - description = "앵무새와 벌들... 그리고 당신은?" +name = "조련" +icon = "♥" +description = "앵무새와 벌들... 그리고 당신은?" [skill.tragoul] - name = "트라골" - icon = "🗡" - description = "피는 우주의 혈관을 타고 흐릅니다. 당신의 손에 의해 조여지며." +name = "트라골" +icon = "🗡" +description = "피는 우주의 혈관을 타고 흐릅니다. 당신의 손에 의해 조여지며." [skill.chronos] - name = "크로노스" - icon = "🕒" - description = "우주의 시계를 감고, 그 흐름을 경험하세요. 시계를 부수고, 그 흐름이 되어 보세요." +name = "크로노스" +icon = "🕒" +description = "우주의 시계를 감고, 그 흐름을 경험하세요. 시계를 부수고, 그 흐름이 되어 보세요." [skill.unarmed] - name = "비무장" - icon = "»" - description = "무기가 없다고 힘이 없는 것은 아닙니다." +name = "비무장" +icon = "»" +description = "무기가 없다고 힘이 없는 것은 아닙니다." # agility [agility] [agility.armor_up] - name = "방어구 강화" - description = "오래 달릴수록 더 많은 방어력을 얻습니다!" - lore1 = "최대 방어력" - lore2 = "방어구 강화 시간" - lore = ["최대 방어력", "방어구 강화 시간"] +name = "방어구 강화" +description = "오래 달릴수록 더 많은 방어력을 얻습니다!" +lore1 = "최대 방어력" +lore2 = "방어구 강화 시간" +lore = ["최대 방어력", "방어구 강화 시간"] [agility.ladder_slide] - name = "사다리 미끄러짐" - description = "사다리를 양 방향으로 훨씬 빠르게 오르내립니다." - lore1 = "사다리 속도 배수" - lore2 = "빠른 하강 속도" - lore = ["사다리 속도 배수", "빠른 하강 속도"] +name = "사다리 미끄러짐" +description = "사다리를 양 방향으로 훨씬 빠르게 오르내립니다." +lore1 = "사다리 속도 배수" +lore2 = "빠른 하강 속도" +lore = ["사다리 속도 배수", "빠른 하강 속도"] [agility.super_jump] - name = "슈퍼 점프" - description = "탁월한 높이의 이점." - lore1 = "최대 점프 높이" - lore2 = "웅크리기 + 점프로 슈퍼 점프!" - lore = ["최대 점프 높이", "웅크리기 + 점프로 슈퍼 점프!"] +name = "슈퍼 점프" +description = "탁월한 높이의 이점." +lore1 = "최대 점프 높이" +lore2 = "웅크리기 + 점프로 슈퍼 점프!" +lore = ["최대 점프 높이", "웅크리기 + 점프로 슈퍼 점프!"] [agility.wall_jump] - name = "벽 점프" - description = "공중에서 벽에 붙어 Shift를 눌러 벽에 매달리고 점프하세요!" - lore1 = "최대 점프 횟수" - lore2 = "점프 높이" - lore = ["최대 점프 횟수", "점프 높이"] +name = "벽 점프" +description = "공중에서 벽에 붙어 Shift를 눌러 벽에 매달리고 점프하세요!" +lore1 = "최대 점프 횟수" +lore2 = "점프 높이" +lore = ["최대 점프 횟수", "점프 높이"] [agility.wind_up] - name = "가속" - description = "오래 달릴수록 더 빨라집니다!" - lore1 = "최대 속도" - lore2 = "가속 시간" - lore = ["최대 속도", "가속 시간"] +name = "가속" +description = "오래 달릴수록 더 빨라집니다!" +lore1 = "최대 속도" +lore2 = "가속 시간" +lore = ["최대 속도", "가속 시간"] # architect [architect] [architect.elevator] - name = "엘리베이터" - description = "수직으로 빠르게 순간이동하는 엘리베이터를 건설할 수 있습니다!" - lore1 = "엘리베이터 레시피 해금: X=양털, Y=엔더 진주" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["엘리베이터 레시피 해금: X=양털, Y=엔더 진주", "XXX", "XYX", "XXX"] +name = "엘리베이터" +description = "수직으로 빠르게 순간이동하는 엘리베이터를 건설할 수 있습니다!" +lore1 = "엘리베이터 레시피 해금: X=양털, Y=엔더 진주" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["엘리베이터 레시피 해금: X=양털, Y=엔더 진주", "XXX", "XYX", "XXX"] [architect.foundation] - name = "마법 발판" - description = "웅크려서 발 밑에 임시 발판을 생성합니다!" - lore1 = "마법으로 생성: " - lore2 = "발 밑의 블록!" - lore = ["마법으로 생성: ", "발 밑의 블록!"] +name = "마법 발판" +description = "웅크려서 발 밑에 임시 발판을 생성합니다!" +lore1 = "마법으로 생성: " +lore2 = "발 밑의 블록!" +lore = ["마법으로 생성: ", "발 밑의 블록!"] [architect.glass] - name = "유리 섬세한 손길" - description = "맨손으로 유리 블록을 깨도 잃지 않게 됩니다!" - lore1 = "유리에 대해 섬세한 손길 효과를 얻습니다" - lore = ["유리에 대해 섬세한 손길 효과를 얻습니다"] +name = "유리 섬세한 손길" +description = "맨손으로 유리 블록을 깨도 잃지 않게 됩니다!" +lore1 = "유리에 대해 섬세한 손길 효과를 얻습니다" +lore = ["유리에 대해 섬세한 손길 효과를 얻습니다"] [architect.wireless_redstone] - name = "레드스톤 리모컨" - description = "레드스톤 횃불을 사용하여 원격으로 레드스톤을 전환할 수 있습니다!" - lore1 = "대상 + 레드스톤 횃불 + 엔더 진주 = 레드스톤 리모컨 1개" - lore = ["대상 + 레드스톤 횃불 + 엔더 진주 = 레드스톤 리모컨 1개"] +name = "레드스톤 리모컨" +description = "레드스톤 횃불을 사용하여 원격으로 레드스톤을 전환할 수 있습니다!" +lore1 = "대상 + 레드스톤 횃불 + 엔더 진주 = 레드스톤 리모컨 1개" +lore = ["대상 + 레드스톤 횃불 + 엔더 진주 = 레드스톤 리모컨 1개"] [architect.placement] - name = "건축가의 지팡이" - description = "한 번에 여러 블록을 설치할 수 있습니다! 웅크리고, 바라보는 블록과 같은 블록을 들고 설치하세요! 범위 지정을 위해 약간 움직여야 할 수 있습니다!" - lore1 = "이것을 설치하려면" - lore2 = "블록이 손에 필요합니다" - lore3 = "재료 건축가의 지팡이" - lore = ["이것을 설치하려면", "블록이 손에 필요합니다", "재료 건축가의 지팡이"] +name = "건축가의 지팡이" +description = "한 번에 여러 블록을 설치할 수 있습니다! 웅크리고, 바라보는 블록과 같은 블록을 들고 설치하세요! 범위 지정을 위해 약간 움직여야 할 수 있습니다!" +lore1 = "이것을 설치하려면" +lore2 = "블록이 손에 필요합니다" +lore3 = "재료 건축가의 지팡이" +lore = ["이것을 설치하려면", "블록이 손에 필요합니다", "재료 건축가의 지팡이"] # axe [axe] [axe.chop] - name = "도끼 벌목" - description = "나무 밑동을 우클릭하여 나무를 베세요!" - lore1 = "벌목당 블록 수" - lore2 = "벌목 쿨타임" - lore3 = "도구 마모" - lore = ["벌목당 블록 수", "벌목 쿨타임", "도구 마모"] +name = "도끼 벌목" +description = "나무 밑동을 우클릭하여 나무를 베세요!" +lore1 = "벌목당 블록 수" +lore2 = "벌목 쿨타임" +lore3 = "도구 마모" +lore = ["벌목당 블록 수", "벌목 쿨타임", "도구 마모"] [axe.log_swap] - name = "루시의 원목 교환기" - description = "제작대에서 원목의 종류를 변경하세요!" - lore1 = "아무 종류의 원목 8개 + 묘목 1개 = 묘목 종류의 원목 8개" - lore = ["아무 종류의 원목 8개 + 묘목 1개 = 묘목 종류의 원목 8개"] +name = "루시의 원목 교환기" +description = "제작대에서 원목의 종류를 변경하세요!" +lore1 = "아무 종류의 원목 8개 + 묘목 1개 = 묘목 종류의 원목 8개" +lore = ["아무 종류의 원목 8개 + 묘목 1개 = 묘목 종류의 원목 8개"] [axe.drop_to_inventory] - name = "도끼 인벤토리 드롭" +name = "도끼 인벤토리 드롭" [axe.ground_smash] - name = "도끼 지면 강타" - description = "점프한 후, 웅크려서 주변의 모든 적을 강타합니다." - lore1 = "피해량" - lore2 = "블록 반경" - lore3 = "힘" - lore4 = "강타 쿨타임" - lore = ["피해량", "블록 반경", "힘", "강타 쿨타임"] +name = "도끼 지면 강타" +description = "점프한 후, 웅크려서 주변의 모든 적을 강타합니다." +lore1 = "피해량" +lore2 = "블록 반경" +lore3 = "힘" +lore4 = "강타 쿨타임" +lore = ["피해량", "블록 반경", "힘", "강타 쿨타임"] [axe.leaf_miner] - name = "나뭇잎 채굴자" - description = "나뭇잎을 한꺼번에 부술 수 있습니다!" - lore1 = "웅크리고 나뭇잎을 캐세요" - lore2 = "나뭇잎 채굴 범위" - lore3 = "나뭇잎에서 아이템이 드롭되지 않습니다 (악용 방지)" - lore = ["웅크리고 나뭇잎을 캐세요", "나뭇잎 채굴 범위", "나뭇잎에서 아이템이 드롭되지 않습니다 (악용 방지)"] +name = "나뭇잎 채굴자" +description = "나뭇잎을 한꺼번에 부술 수 있습니다!" +lore1 = "웅크리고 나뭇잎을 캐세요" +lore2 = "나뭇잎 채굴 범위" +lore3 = "나뭇잎에서 아이템이 드롭되지 않습니다 (악용 방지)" +lore = ["웅크리고 나뭇잎을 캐세요", "나뭇잎 채굴 범위", "나뭇잎에서 아이템이 드롭되지 않습니다 (악용 방지)"] [axe.wood_miner] - name = "목재 채굴자" - description = "원목을 한꺼번에 부술 수 있습니다!" - lore1 = "웅크리고 원목/나무를 캐세요 (판자 제외)" - lore2 = "목재 채굴 범위" - lore3 = "인벤토리 드롭과 함께 작동" - lore = ["웅크리고 원목/나무를 캐세요 (판자 제외)", "목재 채굴 범위", "인벤토리 드롭과 함께 작동"] +name = "목재 채굴자" +description = "원목을 한꺼번에 부술 수 있습니다!" +lore1 = "웅크리고 원목/나무를 캐세요 (판자 제외)" +lore2 = "목재 채굴 범위" +lore3 = "인벤토리 드롭과 함께 작동" +lore = ["웅크리고 원목/나무를 캐세요 (판자 제외)", "목재 채굴 범위", "인벤토리 드롭과 함께 작동"] # brewing [brewing] [brewing.lingering] - name = "잔류 양조" - description = "양조한 물약의 효과가 더 오래 지속됩니다!" - lore1 = "지속시간" - lore2 = "지속시간" - lore = ["지속시간", "지속시간"] +name = "잔류 양조" +description = "양조한 물약의 효과가 더 오래 지속됩니다!" +lore1 = "지속시간" +lore2 = "지속시간" +lore = ["지속시간", "지속시간"] [brewing.super_heated] - name = "초고열 양조" - description = "양조기가 뜨거울수록 더 빠르게 작동합니다." - lore1 = "인접한 불 블록당" - lore2 = "인접한 용암 블록당" - lore = ["인접한 불 블록당", "인접한 용암 블록당"] +name = "초고열 양조" +description = "양조기가 뜨거울수록 더 빠르게 작동합니다." +lore1 = "인접한 불 블록당" +lore2 = "인접한 용암 블록당" +lore = ["인접한 불 블록당", "인접한 용암 블록당"] [brewing.darkness] - name = "병에 담긴 어둠" - description = "왜 이게 필요한지는 모르겠지만, 여기 있습니다!" - lore1 = "야간 투시 물약 + 검은색 콘크리트 = 어둠의 물약 (30초)" - lore2 = "이 물약은 대상이 질주하는 것을 방지합니다!" - lore = ["야간 투시 물약 + 검은색 콘크리트 = 어둠의 물약 (30초)", "이 물약은 대상이 질주하는 것을 방지합니다!"] +name = "병에 담긴 어둠" +description = "왜 이게 필요한지는 모르겠지만, 여기 있습니다!" +lore1 = "야간 투시 물약 + 검은색 콘크리트 = 어둠의 물약 (30초)" +lore2 = "이 물약은 대상이 질주하는 것을 방지합니다!" +lore = ["야간 투시 물약 + 검은색 콘크리트 = 어둠의 물약 (30초)", "이 물약은 대상이 질주하는 것을 방지합니다!"] [brewing.haste] - name = "병에 담긴 성급함" - description = "효율 마법이 충분하지 않을 때" - lore1 = "신속 물약 + 자수정 조각 = 성급함의 물약 (60초)" - lore2 = "신속 물약 + 자수정 블록 = 성급함의 물약-2 (30초)" - lore = ["신속 물약 + 자수정 조각 = 성급함의 물약 (60초)", "신속 물약 + 자수정 블록 = 성급함의 물약-2 (30초)"] +name = "병에 담긴 성급함" +description = "효율 마법이 충분하지 않을 때" +lore1 = "신속 물약 + 자수정 조각 = 성급함의 물약 (60초)" +lore2 = "신속 물약 + 자수정 블록 = 성급함의 물약-2 (30초)" +lore = ["신속 물약 + 자수정 조각 = 성급함의 물약 (60초)", "신속 물약 + 자수정 블록 = 성급함의 물약-2 (30초)"] [brewing.absorption] - name = "병에 담긴 흡수" - description = "몸을 강화하라!" - lore1 = "즉시 치유 + 석영 = 흡수의 물약 (60초)" - lore2 = "즉시 치유 + 석영 블록 = 흡수의 물약-2 (30초)" - lore = ["즉시 치유 + 석영 = 흡수의 물약 (60초)", "즉시 치유 + 석영 블록 = 흡수의 물약-2 (30초)"] +name = "병에 담긴 흡수" +description = "몸을 강화하라!" +lore1 = "즉시 치유 + 석영 = 흡수의 물약 (60초)" +lore2 = "즉시 치유 + 석영 블록 = 흡수의 물약-2 (30초)" +lore = ["즉시 치유 + 석영 = 흡수의 물약 (60초)", "즉시 치유 + 석영 블록 = 흡수의 물약-2 (30초)"] [brewing.fatigue] - name = "병에 담긴 피로" - description = "몸을 약화시켜라!" - lore1 = "나약함 물약 + 슬라임볼 = 피로의 물약 (30초)" - lore2 = "나약함 물약 + 슬라임 블록 = 피로의 물약-2 (15초)" - lore = ["나약함 물약 + 슬라임볼 = 피로의 물약 (30초)", "나약함 물약 + 슬라임 블록 = 피로의 물약-2 (15초)"] +name = "병에 담긴 피로" +description = "몸을 약화시켜라!" +lore1 = "나약함 물약 + 슬라임볼 = 피로의 물약 (30초)" +lore2 = "나약함 물약 + 슬라임 블록 = 피로의 물약-2 (15초)" +lore = ["나약함 물약 + 슬라임볼 = 피로의 물약 (30초)", "나약함 물약 + 슬라임 블록 = 피로의 물약-2 (15초)"] [brewing.hunger] - name = "병에 담긴 굶주림" - description = "끝없는 식욕을 채워라!" - lore1 = "어색한 물약 + 썩은 살점 = 굶주림의 물약 (30초)" - lore2 = "나약함 물약 + 썩은 살점 = 굶주림의 물약-3 (15초)" - lore = ["어색한 물약 + 썩은 살점 = 굶주림의 물약 (30초)", "나약함 물약 + 썩은 살점 = 굶주림의 물약-3 (15초)"] +name = "병에 담긴 굶주림" +description = "끝없는 식욕을 채워라!" +lore1 = "어색한 물약 + 썩은 살점 = 굶주림의 물약 (30초)" +lore2 = "나약함 물약 + 썩은 살점 = 굶주림의 물약-3 (15초)" +lore = ["어색한 물약 + 썩은 살점 = 굶주림의 물약 (30초)", "나약함 물약 + 썩은 살점 = 굶주림의 물약-3 (15초)"] [brewing.nausea] - name = "병에 담긴 메스꺼움" - description = "당신 때문에 속이 메스껍군!" - lore1 = "어색한 물약 + 갈색 버섯 = 메스꺼움의 물약 (16초)" - lore2 = "어색한 물약 + 진홍빛 균 = 메스꺼움의 물약-2 (8초)" - lore = ["어색한 물약 + 갈색 버섯 = 메스꺼움의 물약 (16초)", "어색한 물약 + 진홍빛 균 = 메스꺼움의 물약-2 (8초)"] +name = "병에 담긴 메스꺼움" +description = "당신 때문에 속이 메스껍군!" +lore1 = "어색한 물약 + 갈색 버섯 = 메스꺼움의 물약 (16초)" +lore2 = "어색한 물약 + 진홍빛 균 = 메스꺼움의 물약-2 (8초)" +lore = ["어색한 물약 + 갈색 버섯 = 메스꺼움의 물약 (16초)", "어색한 물약 + 진홍빛 균 = 메스꺼움의 물약-2 (8초)"] [brewing.blindness] - name = "병에 담긴 실명" - description = "당신은 정말 끔찍한 사람이군요..." - lore1 = "어색한 물약 + 먹물 주머니 = 실명의 물약 (30초)" - lore2 = "어색한 물약 + 발광 먹물 주머니 = 실명의 물약-2 (15초)" - lore = ["어색한 물약 + 먹물 주머니 = 실명의 물약 (30초)", "어색한 물약 + 발광 먹물 주머니 = 실명의 물약-2 (15초)"] +name = "병에 담긴 실명" +description = "당신은 정말 끔찍한 사람이군요..." +lore1 = "어색한 물약 + 먹물 주머니 = 실명의 물약 (30초)" +lore2 = "어색한 물약 + 발광 먹물 주머니 = 실명의 물약-2 (15초)" +lore = ["어색한 물약 + 먹물 주머니 = 실명의 물약 (30초)", "어색한 물약 + 발광 먹물 주머니 = 실명의 물약-2 (15초)"] [brewing.resistance] - name = "병에 담긴 저항" - description = "최상의 요새화!" - lore1 = "어색한 물약 + 철 주괴 = 저항의 물약 (60초)" - lore2 = "어색한 물약 + 철 블록 = 저항의 물약-2 (30초)" - lore = ["어색한 물약 + 철 주괴 = 저항의 물약 (60초)", "어색한 물약 + 철 블록 = 저항의 물약-2 (30초)"] +name = "병에 담긴 저항" +description = "최상의 요새화!" +lore1 = "어색한 물약 + 철 주괴 = 저항의 물약 (60초)" +lore2 = "어색한 물약 + 철 블록 = 저항의 물약-2 (30초)" +lore = ["어색한 물약 + 철 주괴 = 저항의 물약 (60초)", "어색한 물약 + 철 블록 = 저항의 물약-2 (30초)"] [brewing.health_boost] - name = "병에 담긴 생명" - description = "최대 체력이 부족할 때..." - lore1 = "즉시 치유 물약 + 황금 사과 = 체력 증진 물약 (120초)" - lore2 = "즉시 치유 물약 + 마법이 부여된 황금 사과 = 체력 증진 물약-2 (120초)" - lore = ["즉시 치유 물약 + 황금 사과 = 체력 증진 물약 (120초)", "즉시 치유 물약 + 마법이 부여된 황금 사과 = 체력 증진 물약-2 (120초)"] +name = "병에 담긴 생명" +description = "최대 체력이 부족할 때..." +lore1 = "즉시 치유 물약 + 황금 사과 = 체력 증진 물약 (120초)" +lore2 = "즉시 치유 물약 + 마법이 부여된 황금 사과 = 체력 증진 물약-2 (120초)" +lore = ["즉시 치유 물약 + 황금 사과 = 체력 증진 물약 (120초)", "즉시 치유 물약 + 마법이 부여된 황금 사과 = 체력 증진 물약-2 (120초)"] [brewing.decay] - name = "병에 담긴 부패" - description = "찌꺼기가 이렇게 유용할 줄 누가 알았겠어?" - lore1 = "나약함 물약 + 독이 든 감자 = 시듦의 물약 (16초)" - lore2 = "나약함 물약 + 진홍빛 뿌리 = 시듦의 물약-2 (8초)" - lore = ["나약함 물약 + 독이 든 감자 = 시듦의 물약 (16초)", "나약함 물약 + 진홍빛 뿌리 = 시듦의 물약-2 (8초)"] +name = "병에 담긴 부패" +description = "찌꺼기가 이렇게 유용할 줄 누가 알았겠어?" +lore1 = "나약함 물약 + 독이 든 감자 = 시듦의 물약 (16초)" +lore2 = "나약함 물약 + 진홍빛 뿌리 = 시듦의 물약-2 (8초)" +lore = ["나약함 물약 + 독이 든 감자 = 시듦의 물약 (16초)", "나약함 물약 + 진홍빛 뿌리 = 시듦의 물약-2 (8초)"] [brewing.saturation] - name = "병에 담긴 포만감" - description = "있잖아... 나 배도 안 고프네..." - lore1 = "재생 물약 + 구운 감자 = 포만감의 물약" - lore2 = "재생 물약 + 건초 더미 = 포만감의 물약-2" - lore = ["재생 물약 + 구운 감자 = 포만감의 물약", "재생 물약 + 건초 더미 = 포만감의 물약-2"] +name = "병에 담긴 포만감" +description = "있잖아... 나 배도 안 고프네..." +lore1 = "재생 물약 + 구운 감자 = 포만감의 물약" +lore2 = "재생 물약 + 건초 더미 = 포만감의 물약-2" +lore = ["재생 물약 + 구운 감자 = 포만감의 물약", "재생 물약 + 건초 더미 = 포만감의 물약-2"] # blocking [blocking] [blocking.chain_armorer] - name = "메피스토펠레스의 사슬" - description = "사슬 갑옷을 제작할 수 있습니다" - lore1 = "제작법은 다른 갑옷과 동일하지만, 철 조각을 대신 사용합니다" - lore = ["제작법은 다른 갑옷과 동일하지만, 철 조각을 대신 사용합니다"] +name = "메피스토펠레스의 사슬" +description = "사슬 갑옷을 제작할 수 있습니다" +lore1 = "제작법은 다른 갑옷과 동일하지만, 철 조각을 대신 사용합니다" +lore = ["제작법은 다른 갑옷과 동일하지만, 철 조각을 대신 사용합니다"] [blocking.horse_armorer] - name = "제작 가능한 말 갑옷" - description = "말 갑옷을 제작할 수 있습니다" - lore1 = "안장을 원하는 재료로 둘러싸서 갑옷을 제작하세요" - lore = ["안장을 원하는 재료로 둘러싸서 갑옷을 제작하세요"] +name = "제작 가능한 말 갑옷" +description = "말 갑옷을 제작할 수 있습니다" +lore1 = "안장을 원하는 재료로 둘러싸서 갑옷을 제작하세요" +lore = ["안장을 원하는 재료로 둘러싸서 갑옷을 제작하세요"] [blocking.saddle_crafter] - name = "제작 가능한 안장" - description = "가죽으로 안장을 제작합니다" - lore1 = "레시피: 가죽 5개:" - lore = ["레시피: 가죽 5개:"] +name = "제작 가능한 안장" +description = "가죽으로 안장을 제작합니다" +lore1 = "레시피: 가죽 5개:" +lore = ["레시피: 가죽 5개:"] [blocking.multi_armor] - name = "다중 방어구" - description = "겉날개를 갑옷에 결합합니다" - lore1 = "이동에 매우 유용한 스킬입니다." - lore2 = "갑옷/겉날개를 즉석에서 합치고 변경하세요!" - lore3 = "합치려면 인벤토리에서 아이템을 다른 아이템 위에 Shift 클릭하세요." - lore4 = "방어구를 분리하려면, 해당 아이템을 웅크린 채 버리면 분해됩니다." - lore5 = "합친 방어구가 파괴되면, 그 안의 모든 아이템을 잃게 됩니다." - lore6 = "갑옷은 필요 없어, 실망스럽네..." - lore = ["이동에 매우 유용한 스킬입니다.", "갑옷/겉날개를 즉석에서 합치고 변경하세요!", "합치려면 인벤토리에서 아이템을 다른 아이템 위에 Shift 클릭하세요.", "방어구를 분리하려면, 해당 아이템을 웅크린 채 버리면 분해됩니다.", "합친 방어구가 파괴되면, 그 안의 모든 아이템을 잃게 됩니다.", "갑옷은 필요 없어, 실망스럽네..."] +name = "다중 방어구" +description = "겉날개를 갑옷에 결합합니다" +lore1 = "이동에 매우 유용한 스킬입니다." +lore2 = "갑옷/겉날개를 즉석에서 합치고 변경하세요!" +lore3 = "합치려면 인벤토리에서 아이템을 다른 아이템 위에 Shift 클릭하세요." +lore4 = "방어구를 분리하려면, 해당 아이템을 웅크린 채 버리면 분해됩니다." +lore5 = "합친 방어구가 파괴되면, 그 안의 모든 아이템을 잃게 됩니다." +lore6 = "갑옷은 필요 없어, 실망스럽네..." +lore = ["이동에 매우 유용한 스킬입니다.", "갑옷/겉날개를 즉석에서 합치고 변경하세요!", "합치려면 인벤토리에서 아이템을 다른 아이템 위에 Shift 클릭하세요.", "방어구를 분리하려면, 해당 아이템을 웅크린 채 버리면 분해됩니다.", "합친 방어구가 파괴되면, 그 안의 모든 아이템을 잃게 됩니다.", "갑옷은 필요 없어, 실망스럽네..."] # crafting [crafting] [crafting.deconstruction] - name = "해체" - description = "블록과 아이템을 기본 재료로 분해하세요!" - lore1 = "아이템을 땅에 떨어뜨리세요." - lore2 = "그런 다음, 웅크리고 가위로 우클릭하세요" - lore = ["아이템을 땅에 떨어뜨리세요.", "그런 다음, 웅크리고 가위로 우클릭하세요"] +name = "해체" +description = "블록과 아이템을 기본 재료로 분해하세요!" +lore1 = "아이템을 땅에 떨어뜨리세요." +lore2 = "그런 다음, 웅크리고 가위로 우클릭하세요" +lore = ["아이템을 땅에 떨어뜨리세요.", "그런 다음, 웅크리고 가위로 우클릭하세요"] [crafting.xp] - name = "제작 경험치" - description = "제작 시 패시브 XP를 획득합니다" - lore1 = "제작 시 XP 획득" - lore = ["제작 시 XP 획득"] +name = "제작 경험치" +description = "제작 시 패시브 XP를 획득합니다" +lore1 = "제작 시 XP 획득" +lore = ["제작 시 XP 획득"] [crafting.reconstruction] - name = "광석 복원" - description = "기본 재료로 광석을 다시 만드세요!" - lore1 = "드롭 아이템 8개 + 모체 1개 = 광석 1개 (비정형)" - lore2 = "드롭 아이템은 제련되어야 합니다 (해당되는 경우)" - lore3 = "제외: 파편, 석영, 에메랄드 등..." - lore4 = "모체 = 감싸는 블록. 예: 돌, 네더랙, 심층암" - lore = ["드롭 아이템 8개 + 모체 1개 = 광석 1개 (비정형)", "드롭 아이템은 제련되어야 합니다 (해당되는 경우)", "제외: 파편, 석영, 에메랄드 등...", "모체 = 감싸는 블록. 예: 돌, 네더랙, 심층암"] +name = "광석 복원" +description = "기본 재료로 광석을 다시 만드세요!" +lore1 = "드롭 아이템 8개 + 모체 1개 = 광석 1개 (비정형)" +lore2 = "드롭 아이템은 제련되어야 합니다 (해당되는 경우)" +lore3 = "제외: 파편, 석영, 에메랄드 등..." +lore4 = "모체 = 감싸는 블록. 예: 돌, 네더랙, 심층암" +lore = ["드롭 아이템 8개 + 모체 1개 = 광석 1개 (비정형)", "드롭 아이템은 제련되어야 합니다 (해당되는 경우)", "제외: 파편, 석영, 에메랄드 등...", "모체 = 감싸는 블록. 예: 돌, 네더랙, 심층암"] [crafting.leather] - name = "제작 가능한 가죽" - description = "썩은 살점으로 가죽을 만듭니다" - lore1 = "모닥불 위에 (썩은 살점을) 올려놓기만 하면 됩니다!" - lore = ["모닥불 위에 (썩은 살점을) 올려놓기만 하면 됩니다!"] +name = "제작 가능한 가죽" +description = "썩은 살점으로 가죽을 만듭니다" +lore1 = "모닥불 위에 (썩은 살점을) 올려놓기만 하면 됩니다!" +lore = ["모닥불 위에 (썩은 살점을) 올려놓기만 하면 됩니다!"] [crafting.backpacks] - name = "부틸리에의 배낭!" - description = "모장의 묶음을 게임에 추가합니다!" - lore1 = "사용하려면 서바이벌 모드여야 합니다" - lore2 = "XLX : 가죽, 끈, 가죽" - lore3 = "XSX : 가죽, 통, 가죽" - lore4 = "XCX : 가죽, 상자, 가죽" - lore = ["사용하려면 서바이벌 모드여야 합니다", "XLX : 가죽, 끈, 가죽", "XSX : 가죽, 통, 가죽", "XCX : 가죽, 상자, 가죽"] +name = "부틸리에의 배낭!" +description = "모장의 묶음을 게임에 추가합니다!" +lore1 = "사용하려면 서바이벌 모드여야 합니다" +lore2 = "XLX : 가죽, 끈, 가죽" +lore3 = "XSX : 가죽, 통, 가죽" +lore4 = "XCX : 가죽, 상자, 가죽" +lore = ["사용하려면 서바이벌 모드여야 합니다", "XLX : 가죽, 끈, 가죽", "XSX : 가죽, 통, 가죽", "XCX : 가죽, 상자, 가죽"] [crafting.stations] - name = "휴대용 작업대!" - description = "손바닥 위에서 작업대를 사용하세요!" - lore2 = "작업대를 닫을 때 남겨둔 아이템은 영원히 사라집니다!" - lore3 = "사용 가능한 작업대: 모루, 제작대, 숫돌, 지도 제작대, 석재 절단기, 베틀" - lore = ["작업대를 닫을 때 남겨둔 아이템은 영원히 사라집니다!", "사용 가능한 작업대: 모루, 제작대, 숫돌, 지도 제작대, 석재 절단기, 베틀"] +name = "휴대용 작업대!" +description = "손바닥 위에서 작업대를 사용하세요!" +lore2 = "작업대를 닫을 때 남겨둔 아이템은 영원히 사라집니다!" +lore3 = "사용 가능한 작업대: 모루, 제작대, 숫돌, 지도 제작대, 석재 절단기, 베틀" +lore = ["작업대를 닫을 때 남겨둔 아이템은 영원히 사라집니다!", "사용 가능한 작업대: 모루, 제작대, 숫돌, 지도 제작대, 석재 절단기, 베틀"] [crafting.skulls] - name = "제작 가능한 머리!" - description = "재료를 사용하여 몹 머리를 제작할 수 있습니다!" - lore1 = "뼈 블록을 다음 재료로 둘러싸면 머리를 얻습니다:" - lore2 = "좀비: 썩은 살점" - lore3 = "스켈레톤: 뼈" - lore4 = "크리퍼: 화약" - lore5 = "위더: 네더 벽돌" - lore6 = "드래곤: 드래곤의 숨결" - lore = ["뼈 블록을 다음 재료로 둘러싸면 머리를 얻습니다:", "좀비: 썩은 살점", "스켈레톤: 뼈", "크리퍼: 화약", "위더: 네더 벽돌", "드래곤: 드래곤의 숨결"] +name = "제작 가능한 머리!" +description = "재료를 사용하여 몹 머리를 제작할 수 있습니다!" +lore1 = "뼈 블록을 다음 재료로 둘러싸면 머리를 얻습니다:" +lore2 = "좀비: 썩은 살점" +lore3 = "스켈레톤: 뼈" +lore4 = "크리퍼: 화약" +lore5 = "위더: 네더 벽돌" +lore6 = "드래곤: 드래곤의 숨결" +lore = ["뼈 블록을 다음 재료로 둘러싸면 머리를 얻습니다:", "좀비: 썩은 살점", "스켈레톤: 뼈", "크리퍼: 화약", "위더: 네더 벽돌", "드래곤: 드래곤의 숨결"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "시간을 담은 병" - description = "시간을 저장하는 시간의 병을 들고 다니며, 저장된 시간을 사용하여 시간이 걸리는 블록, 작물, 새끼 동물과 같은 성장 가능한 엔티티를 가속시킵니다. 레시피 (비정형): 신속의 물약 + 시계 + 유리병." - lore1 = "틱마다 충전되는 저장 초" - lore2 = "저장된 초당 시간 가속" - lore3 = "레시피 (비정형): 신속의 물약 + 시계 + 유리병" - lore = ["틱마다 충전되는 저장 초", "저장된 초당 시간 가속", "레시피 (비정형): 신속의 물약 + 시계 + 유리병"] +name = "시간을 담은 병" +description = "시간을 저장하는 시간의 병을 들고 다니며, 저장된 시간을 사용하여 시간이 걸리는 블록, 작물, 새끼 동물과 같은 성장 가능한 엔티티를 가속시킵니다. 레시피 (비정형): 신속의 물약 + 시계 + 유리병." +lore1 = "틱마다 충전되는 저장 초" +lore2 = "저장된 초당 시간 가속" +lore3 = "레시피 (비정형): 신속의 물약 + 시계 + 유리병" +lore = ["틱마다 충전되는 저장 초", "저장된 초당 시간 가속", "레시피 (비정형): 신속의 물약 + 시계 + 유리병"] [chronos.aberrant_touch] - name = "이질적 손길" - description = "근접 공격이 배고픔을 대가로 중첩되는 구속을 적용하며, 엄격한 PvP 제한이 있고, 5스택 시 대상을 속박합니다." - lore1 = "근접 공격이 중첩되는 구속을 적용" - lore2 = "PvE 구속 지속시간 상한" - lore3 = "PvP 구속 증폭 상한" - lore = ["근접 공격이 중첩되는 구속을 적용", "PvE 구속 지속시간 상한", "PvP 구속 증폭 상한"] +name = "이질적 손길" +description = "근접 공격이 배고픔을 대가로 중첩되는 구속을 적용하며, 엄격한 PvP 제한이 있고, 5스택 시 대상을 속박합니다." +lore1 = "근접 공격이 중첩되는 구속을 적용" +lore2 = "PvE 구속 지속시간 상한" +lore3 = "PvP 구속 증폭 상한" +lore = ["근접 공격이 중첩되는 구속을 적용", "PvE 구속 지속시간 상한", "PvP 구속 증폭 상한"] [chronos.instant_recall] - name = "즉시 회귀" - description = "시계를 손에 들고 좌클릭 또는 우클릭하여 최근 스냅샷으로 되돌아가며, 체력과 배고픔이 복구됩니다." - lore1 = "되감기 시간" - lore2 = "쿨타임" - lore3 = "인벤토리 롤백 없음" - lore = ["되감기 시간", "쿨타임", "인벤토리 롤백 없음"] +name = "즉시 회귀" +description = "시계를 손에 들고 좌클릭 또는 우클릭하여 최근 스냅샷으로 되돌아가며, 체력과 배고픔이 복구됩니다." +lore1 = "되감기 시간" +lore2 = "쿨타임" +lore3 = "인벤토리 롤백 없음" +lore = ["되감기 시간", "쿨타임", "인벤토리 롤백 없음"] [chronos.time_bomb] - name = "시간 폭탄" - description = "제작된 시간 폭탄을 던져 시간의 장을 생성하고, 엔티티를 느리게 하며, 투사체를 정지시킵니다." - lore1 = "시간 장의 반경" - lore2 = "시간 장의 지속시간" - lore3 = "폭탄 쿨타임" - lore4 = "레시피 (비정형): 시계 + 눈덩이 + 다이아몬드 + 모래" - lore = ["시간 장의 반경", "시간 장의 지속시간", "폭탄 쿨타임", "레시피 (비정형): 시계 + 눈덩이 + 다이아몬드 + 모래"] +name = "시간 폭탄" +description = "제작된 시간 폭탄을 던져 시간의 장을 생성하고, 엔티티를 느리게 하며, 투사체를 정지시킵니다." +lore1 = "시간 장의 반경" +lore2 = "시간 장의 지속시간" +lore3 = "폭탄 쿨타임" +lore4 = "레시피 (비정형): 시계 + 눈덩이 + 다이아몬드 + 모래" +lore = ["시간 장의 반경", "시간 장의 지속시간", "폭탄 쿨타임", "레시피 (비정형): 시계 + 눈덩이 + 다이아몬드 + 모래"] # discovery [discovery] [discovery.armor] - name = "세계의 갑옷" - description = "주변 블록 경도에 따른 패시브 방어력." - lore1 = "패시브 방어력" - lore2 = "주변 블록 경도 기준" - lore3 = "방어력 강도:" - lore = ["패시브 방어력", "주변 블록 경도 기준", "방어력 강도:"] +name = "세계의 갑옷" +description = "주변 블록 경도에 따른 패시브 방어력." +lore1 = "패시브 방어력" +lore2 = "주변 블록 경도 기준" +lore3 = "방어력 강도:" +lore = ["패시브 방어력", "주변 블록 경도 기준", "방어력 강도:"] [discovery.unity] - name = "실험적 통합" - description = "경험치 구슬을 모으면 랜덤 스킬에 XP가 추가됩니다." - lore1 = "XP " - lore2 = "구슬당" - lore = ["XP ", "구슬당"] +name = "실험적 통합" +description = "경험치 구슬을 모으면 랜덤 스킬에 XP가 추가됩니다." +lore1 = "XP " +lore2 = "구슬당" +lore = ["XP ", "구슬당"] [discovery.resist] - name = "실험적 저항" - description = "피격 시 체력이 5칸 이하로 떨어지거나 죽을 위기일 때만 경험치를 소모하여 피해를 완화합니다." - lore0 = "위험 체력(<= 5칸)에서만 15초마다 한 번 발동" - lore1 = " 피해 감소" - lore2 = "경험치 소모" - lore = ["위험 체력(<= 5칸)에서만 15초마다 한 번 발동", " 피해 감소", "경험치 소모"] +name = "실험적 저항" +description = "피격 시 체력이 5칸 이하로 떨어지거나 죽을 위기일 때만 경험치를 소모하여 피해를 완화합니다." +lore0 = "위험 체력(<= 5칸)에서만 15초마다 한 번 발동" +lore1 = " 피해 감소" +lore2 = "경험치 소모" +lore = ["위험 체력(<= 5칸)에서만 15초마다 한 번 발동", " 피해 감소", "경험치 소모"] [discovery.villager] - name = "주민 매력" - description = "주민과 더 좋은 거래를 할 수 있습니다!" - lore1 = "주민과 상호작용할 때마다 XP를 소모합니다" - lore2 = "상호작용당 XP를 소비하고 거래를 강화할 확률" - lore3 = "상호작용당 필요한 XP 소모량" - lore = ["주민과 상호작용할 때마다 XP를 소모합니다", "상호작용당 XP를 소비하고 거래를 강화할 확률", "상호작용당 필요한 XP 소모량"] +name = "주민 매력" +description = "주민과 더 좋은 거래를 할 수 있습니다!" +lore1 = "주민과 상호작용할 때마다 XP를 소모합니다" +lore2 = "상호작용당 XP를 소비하고 거래를 강화할 확률" +lore3 = "상호작용당 필요한 XP 소모량" +lore = ["주민과 상호작용할 때마다 XP를 소모합니다", "상호작용당 XP를 소비하고 거래를 강화할 확률", "상호작용당 필요한 XP 소모량"] # enchanting [enchanting] [enchanting.lapis_return] - name = "청금석 반환" - description = "XP 1레벨 추가 비용 대신, 무료 청금석을 돌려받을 확률이 있습니다" - lore1 = "레벨당 마법부여 비용이 1 증가하지만, 최대 3개의 청금석을 돌려받을 수 있습니다" - lore = ["레벨당 마법부여 비용이 1 증가하지만, 최대 3개의 청금석을 돌려받을 수 있습니다"] +name = "청금석 반환" +description = "XP 1레벨 추가 비용 대신, 무료 청금석을 돌려받을 확률이 있습니다" +lore1 = "레벨당 마법부여 비용이 1 증가하지만, 최대 3개의 청금석을 돌려받을 수 있습니다" +lore = ["레벨당 마법부여 비용이 1 증가하지만, 최대 3개의 청금석을 돌려받을 수 있습니다"] [enchanting.quick_enchant] - name = "퀵클릭 마법부여" - description = "마법부여 책을 아이템에 직접 클릭하여 마법을 부여합니다." - lore1 = "최대 합산 레벨" - lore2 = "아이템에 다음을 초과하여 마법부여할 수 없습니다 " - lore3 = "파워" - lore = ["최대 합산 레벨", "아이템에 다음을 초과하여 마법부여할 수 없습니다 ", "파워"] +name = "퀵클릭 마법부여" +description = "마법부여 책을 아이템에 직접 클릭하여 마법을 부여합니다." +lore1 = "최대 합산 레벨" +lore2 = "아이템에 다음을 초과하여 마법부여할 수 없습니다 " +lore3 = "파워" +lore = ["최대 합산 레벨", "아이템에 다음을 초과하여 마법부여할 수 없습니다 ", "파워"] [enchanting.return] - name = "XP 반환" - description = "아이템에 마법부여 시 사용한 XP가 돌아옵니다." - lore1 = "아이템을 마법부여할 때 소비한 경험치가 환불될 확률이 있습니다" - lore2 = "마법부여당 경험치" - lore = ["아이템을 마법부여할 때 소비한 경험치가 환불될 확률이 있습니다", "마법부여당 경험치"] +name = "XP 반환" +description = "아이템에 마법부여 시 사용한 XP가 돌아옵니다." +lore1 = "아이템을 마법부여할 때 소비한 경험치가 환불될 확률이 있습니다" +lore2 = "마법부여당 경험치" +lore = ["아이템을 마법부여할 때 소비한 경험치가 환불될 확률이 있습니다", "마법부여당 경험치"] # excavation [excavation] [excavation.haste] - name = "성급한 발굴가" - description = "성급함 효과로 발굴 속도가 빨라집니다!" - lore1 = "발굴 중 성급함 획득" - lore2 = "x 블록을 캐기 시작하면 성급함 레벨 획득." - lore = ["발굴 중 성급함 획득", "x 블록을 캐기 시작하면 성급함 레벨 획득."] +name = "성급한 발굴가" +description = "성급함 효과로 발굴 속도가 빨라집니다!" +lore1 = "발굴 중 성급함 획득" +lore2 = "x 블록을 캐기 시작하면 성급함 레벨 획득." +lore = ["발굴 중 성급함 획득", "x 블록을 캐기 시작하면 성급함 레벨 획득."] [excavation.spelunker] - name = "초감각 탐험가!" - description = "땅 너머로 광석을 볼 수 있습니다!" - lore1 = "보조 손에 광석, 주 손에 발광 열매를 들고 웅크리세요!" - lore2 = "블록 범위: " - lore3 = "사용 시 발광 열매 소모" - lore = ["보조 손에 광석, 주 손에 발광 열매를 들고 웅크리세요!", "블록 범위: ", "사용 시 발광 열매 소모"] +name = "초감각 탐험가!" +description = "땅 너머로 광석을 볼 수 있습니다!" +lore1 = "보조 손에 광석, 주 손에 발광 열매를 들고 웅크리세요!" +lore2 = "블록 범위: " +lore3 = "사용 시 발광 열매 소모" +lore = ["보조 손에 광석, 주 손에 발광 열매를 들고 웅크리세요!", "블록 범위: ", "사용 시 발광 열매 소모"] [excavation.drop_to_inventory] - name = "삽 인벤토리 드롭" +name = "삽 인벤토리 드롭" [excavation.omni_tool] - name = "OMNI - 도구" - description = "태클의 과하게 설계된 화려한 멀티툴" - lore1 = "가장 강력한 기능 중 하나로, 다음을 할 수 있습니다" - lore2 = "필요에 따라 도구를 즉석에서 합치고 변경할 수 있습니다." - lore3 = "합치려면 인벤토리에서 아이템을 다른 아이템 위에 Shift 클릭하세요." - lore4 = "도구를 분리하려면, 해당 도구를 웅크린 채 버리면 분해됩니다." - lore5 = "이 멀티툴에서 도구를 파괴할 수 없지만 부서진 도구는 사용할 수 없습니다" - lore6 = "합칠 수 있는 총 아이템 수." - lore7 = "도구를 5~6개 쓸 수도 있고, 하나만 쓸 수도 있습니다!" - lore = ["가장 강력한 기능 중 하나로, 다음을 할 수 있습니다", "필요에 따라 도구를 즉석에서 합치고 변경할 수 있습니다.", "합치려면 인벤토리에서 아이템을 다른 아이템 위에 Shift 클릭하세요.", "도구를 분리하려면, 해당 도구를 웅크린 채 버리면 분해됩니다.", "이 멀티툴에서 도구를 파괴할 수 없지만 부서진 도구는 사용할 수 없습니다", "합칠 수 있는 총 아이템 수.", "도구를 5~6개 쓸 수도 있고, 하나만 쓸 수도 있습니다!"] +name = "OMNI - 도구" +description = "태클의 과하게 설계된 화려한 멀티툴" +lore1 = "가장 강력한 기능 중 하나로, 다음을 할 수 있습니다" +lore2 = "필요에 따라 도구를 즉석에서 합치고 변경할 수 있습니다." +lore3 = "합치려면 인벤토리에서 아이템을 다른 아이템 위에 Shift 클릭하세요." +lore4 = "도구를 분리하려면, 해당 도구를 웅크린 채 버리면 분해됩니다." +lore5 = "이 멀티툴에서 도구를 파괴할 수 없지만 부서진 도구는 사용할 수 없습니다" +lore6 = "합칠 수 있는 총 아이템 수." +lore7 = "도구를 5~6개 쓸 수도 있고, 하나만 쓸 수도 있습니다!" +lore = ["가장 강력한 기능 중 하나로, 다음을 할 수 있습니다", "필요에 따라 도구를 즉석에서 합치고 변경할 수 있습니다.", "합치려면 인벤토리에서 아이템을 다른 아이템 위에 Shift 클릭하세요.", "도구를 분리하려면, 해당 도구를 웅크린 채 버리면 분해됩니다.", "이 멀티툴에서 도구를 파괴할 수 없지만 부서진 도구는 사용할 수 없습니다", "합칠 수 있는 총 아이템 수.", "도구를 5~6개 쓸 수도 있고, 하나만 쓸 수도 있습니다!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "성장의 오라" - description = "주변의 자연을 오라로 성장시킵니다" - lore1 = "블록 반경" - lore2 = "성장 오라 강도" - lore3 = "배고픔 비용" - lore = ["블록 반경", "성장 오라 강도", "배고픔 비용"] +name = "성장의 오라" +description = "주변의 자연을 오라로 성장시킵니다" +lore1 = "블록 반경" +lore2 = "성장 오라 강도" +lore3 = "배고픔 비용" +lore = ["블록 반경", "성장 오라 강도", "배고픔 비용"] [herbalism.hippo] - name = "약초학자의 하마" - description = "음식을 먹으면 더 많은 포만감을 얻습니다" - lore1 = "음식) 섭취 시 추가 포만감 포인트" - lore = ["음식) 섭취 시 추가 포만감 포인트"] +name = "약초학자의 하마" +description = "음식을 먹으면 더 많은 포만감을 얻습니다" +lore1 = "음식) 섭취 시 추가 포만감 포인트" +lore = ["음식) 섭취 시 추가 포만감 포인트"] [herbalism.myconid] - name = "약초학자의 균류" - description = "균사체를 제작할 수 있는 능력을 부여합니다" - lore1 = "아무 흙 + 갈색 버섯 + 빨간 버섯으로 균사체를 제작합니다." - lore = ["아무 흙 + 갈색 버섯 + 빨간 버섯으로 균사체를 제작합니다."] +name = "약초학자의 균류" +description = "균사체를 제작할 수 있는 능력을 부여합니다" +lore1 = "아무 흙 + 갈색 버섯 + 빨간 버섯으로 균사체를 제작합니다." +lore = ["아무 흙 + 갈색 버섯 + 빨간 버섯으로 균사체를 제작합니다."] [herbalism.terralid] - name = "약초학자의 대지" - description = "잔디 블록을 제작할 수 있는 능력을 부여합니다" - lore1 = "씨앗 3개를 흙 3개 위에 놓으면 잔디 블록 3개를 제작합니다." - lore = ["씨앗 3개를 흙 3개 위에 놓으면 잔디 블록 3개를 제작합니다."] +name = "약초학자의 대지" +description = "잔디 블록을 제작할 수 있는 능력을 부여합니다" +lore1 = "씨앗 3개를 흙 3개 위에 놓으면 잔디 블록 3개를 제작합니다." +lore = ["씨앗 3개를 흙 3개 위에 놓으면 잔디 블록 3개를 제작합니다."] [herbalism.cobweb] - name = "거미줄 제작자" - description = "제작대에서 거미줄을 만들 수 있는 능력을 부여합니다" - lore1 = "실 9개로 거미줄을 만듭니다." - lore = ["실 9개로 거미줄을 만듭니다."] +name = "거미줄 제작자" +description = "제작대에서 거미줄을 만들 수 있는 능력을 부여합니다" +lore1 = "실 9개로 거미줄을 만듭니다." +lore = ["실 9개로 거미줄을 만듭니다."] [herbalism.mushroom_blocks] - name = "버섯 블록 제작자" - description = "제작대에서 버섯 블록을 만들 수 있는 능력을 부여합니다" - lore1 = "버섯 4개로 블록을, 블록으로 줄기를 만듭니다." - lore = ["버섯 4개로 블록을, 블록으로 줄기를 만듭니다."] +name = "버섯 블록 제작자" +description = "제작대에서 버섯 블록을 만들 수 있는 능력을 부여합니다" +lore1 = "버섯 4개로 블록을, 블록으로 줄기를 만듭니다." +lore = ["버섯 4개로 블록을, 블록으로 줄기를 만듭니다."] [herbalism.drop_to_inventory] - name = "괭이 인벤토리 드롭" +name = "괭이 인벤토리 드롭" [herbalism.hungry_shield] - name = "굶주린 방패" - description = "체력 대신 배고픔으로 피해를 받습니다." - lore1 = "배고픔으로 저항" - lore = ["배고픔으로 저항"] +name = "굶주린 방패" +description = "체력 대신 배고픔으로 피해를 받습니다." +lore1 = "배고픔으로 저항" +lore = ["배고픔으로 저항"] [herbalism.luck] - name = "약초학자의 행운" - description = "풀/꽃을 부수면 일정 확률로 랜덤 아이템을 얻습니다" - lore0 = "꽃 = 음식, 풀 = 씨앗" - lore1 = "꽃을 부수면 아이템을 얻을 확률" - lore2 = "풀을 부수면 아이템을 얻을 확률" - lore = ["꽃 = 음식, 풀 = 씨앗", "꽃을 부수면 아이템을 얻을 확률", "풀을 부수면 아이템을 얻을 확률"] +name = "약초학자의 행운" +description = "풀/꽃을 부수면 일정 확률로 랜덤 아이템을 얻습니다" +lore0 = "꽃 = 음식, 풀 = 씨앗" +lore1 = "꽃을 부수면 아이템을 얻을 확률" +lore2 = "풀을 부수면 아이템을 얻을 확률" +lore = ["꽃 = 음식, 풀 = 씨앗", "꽃을 부수면 아이템을 얻을 확률", "풀을 부수면 아이템을 얻을 확률"] [herbalism.replant] - name = "수확 및 재파종" - description = "괭이로 작물을 우클릭하여 수확하고 다시 심습니다." - lore1 = "블록 재파종 반경" - lore = ["블록 재파종 반경"] +name = "수확 및 재파종" +description = "괭이로 작물을 우클릭하여 수확하고 다시 심습니다." +lore1 = "블록 재파종 반경" +lore = ["블록 재파종 반경"] # hunter [hunter] [hunter.adrenaline] - name = "아드레날린" - description = "체력이 낮을수록 더 많은 피해를 줍니다 (근접)" - lore1 = "최대 피해량" - lore = ["최대 피해량"] +name = "아드레날린" +description = "체력이 낮을수록 더 많은 피해를 줍니다 (근접)" +lore1 = "최대 피해량" +lore = ["최대 피해량"] [hunter.penalty] - name = "" - description = "" - lore1 = "배고픔이 바닥나면 독 스택을 얻습니다" - lore = ["배고픔이 바닥나면 독 스택을 얻습니다"] +name = "" +description = "" +lore1 = "배고픔이 바닥나면 독 스택을 얻습니다" +lore = ["배고픔이 바닥나면 독 스택을 얻습니다"] [hunter.drop_to_inventory] - name = "아이템 인벤토리 드롭" - description = "무언가를 처치하거나 / 검으로 블록을 부수면 드롭 아이템이 인벤토리로 들어갑니다" - lore1 = "몹/블록에서 아이템이 드롭될 때마다 가능하면 인벤토리로 직접 들어갑니다." - lore = ["몹/블록에서 아이템이 드롭될 때마다 가능하면 인벤토리로 직접 들어갑니다."] +name = "아이템 인벤토리 드롭" +description = "무언가를 처치하거나 / 검으로 블록을 부수면 드롭 아이템이 인벤토리로 들어갑니다" +lore1 = "몹/블록에서 아이템이 드롭될 때마다 가능하면 인벤토리로 직접 들어갑니다." +lore = ["몹/블록에서 아이템이 드롭될 때마다 가능하면 인벤토리로 직접 들어갑니다."] [hunter.invisibility] - name = "소멸의 발걸음" - description = "피격 시 배고픔을 대가로 투명화를 얻습니다" - lore1 = "피격 시 패시브 투명화 획득" - lore2 = "x 피격 시 3초간 투명화 중첩" - lore3 = "x 중첩 배고픔" - lore4 = "배고픔 중첩의 지속시간과 배수." - lore5 = "투명화 지속시간" - lore = ["피격 시 패시브 투명화 획득", "x 피격 시 3초간 투명화 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "투명화 지속시간"] +name = "소멸의 발걸음" +description = "피격 시 배고픔을 대가로 투명화를 얻습니다" +lore1 = "피격 시 패시브 투명화 획득" +lore2 = "x 피격 시 3초간 투명화 중첩" +lore3 = "x 중첩 배고픔" +lore4 = "배고픔 중첩의 지속시간과 배수." +lore5 = "투명화 지속시간" +lore = ["피격 시 패시브 투명화 획득", "x 피격 시 3초간 투명화 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "투명화 지속시간"] [hunter.jump_boost] - name = "사냥꾼의 높이" - description = "피격 시 배고픔을 대가로 점프 강화를 얻습니다" - lore1 = "피격 시 패시브 점프 강화 획득" - lore2 = "x 피격 시 3초간 점프 강화 중첩" - lore3 = "x 중첩 배고픔" - lore4 = "배고픔 중첩의 지속시간과 배수." - lore5 = "점프 강화 중첩 배수 (지속시간 아님)." - lore = ["피격 시 패시브 점프 강화 획득", "x 피격 시 3초간 점프 강화 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "점프 강화 중첩 배수 (지속시간 아님)."] +name = "사냥꾼의 높이" +description = "피격 시 배고픔을 대가로 점프 강화를 얻습니다" +lore1 = "피격 시 패시브 점프 강화 획득" +lore2 = "x 피격 시 3초간 점프 강화 중첩" +lore3 = "x 중첩 배고픔" +lore4 = "배고픔 중첩의 지속시간과 배수." +lore5 = "점프 강화 중첩 배수 (지속시간 아님)." +lore = ["피격 시 패시브 점프 강화 획득", "x 피격 시 3초간 점프 강화 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "점프 강화 중첩 배수 (지속시간 아님)."] [hunter.luck] - name = "사냥꾼의 행운" - description = "피격 시 배고픔을 대가로 행운을 얻습니다" - lore1 = "피격 시 패시브 행운 획득" - lore2 = "x 피격 시 3초간 행운 중첩" - lore3 = "x 중첩 배고픔" - lore4 = "배고픔 중첩의 지속시간과 배수." - lore5 = "행운 중첩 배수 (지속시간 아님)." - lore = ["피격 시 패시브 행운 획득", "x 피격 시 3초간 행운 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "행운 중첩 배수 (지속시간 아님)."] +name = "사냥꾼의 행운" +description = "피격 시 배고픔을 대가로 행운을 얻습니다" +lore1 = "피격 시 패시브 행운 획득" +lore2 = "x 피격 시 3초간 행운 중첩" +lore3 = "x 중첩 배고픔" +lore4 = "배고픔 중첩의 지속시간과 배수." +lore5 = "행운 중첩 배수 (지속시간 아님)." +lore = ["피격 시 패시브 행운 획득", "x 피격 시 3초간 행운 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "행운 중첩 배수 (지속시간 아님)."] [hunter.regen] - name = "사냥꾼의 재생" - description = "피격 시 배고픔을 대가로 재생력을 얻습니다" - lore1 = "피격 시 패시브 재생력 획득" - lore2 = "x 피격 시 3초간 재생력 중첩" - lore3 = "x 중첩 배고픔" - lore4 = "배고픔 중첩의 지속시간과 배수." - lore5 = "재생력 중첩 배수 (지속시간 아님)." - lore = ["피격 시 패시브 재생력 획득", "x 피격 시 3초간 재생력 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "재생력 중첩 배수 (지속시간 아님)."] +name = "사냥꾼의 재생" +description = "피격 시 배고픔을 대가로 재생력을 얻습니다" +lore1 = "피격 시 패시브 재생력 획득" +lore2 = "x 피격 시 3초간 재생력 중첩" +lore3 = "x 중첩 배고픔" +lore4 = "배고픔 중첩의 지속시간과 배수." +lore5 = "재생력 중첩 배수 (지속시간 아님)." +lore = ["피격 시 패시브 재생력 획득", "x 피격 시 3초간 재생력 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "재생력 중첩 배수 (지속시간 아님)."] [hunter.resistance] - name = "사냥꾼의 저항" - description = "피격 시 배고픔을 대가로 저항력을 얻습니다" - lore1 = "피격 시 패시브 저항력 획득" - lore2 = "x 피격 시 3초간 저항력 중첩" - lore3 = "x 중첩 배고픔" - lore4 = "배고픔 중첩의 지속시간과 배수." - lore5 = "저항력 중첩 배수 (지속시간 아님)." - lore = ["피격 시 패시브 저항력 획득", "x 피격 시 3초간 저항력 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "저항력 중첩 배수 (지속시간 아님)."] +name = "사냥꾼의 저항" +description = "피격 시 배고픔을 대가로 저항력을 얻습니다" +lore1 = "피격 시 패시브 저항력 획득" +lore2 = "x 피격 시 3초간 저항력 중첩" +lore3 = "x 중첩 배고픔" +lore4 = "배고픔 중첩의 지속시간과 배수." +lore5 = "저항력 중첩 배수 (지속시간 아님)." +lore = ["피격 시 패시브 저항력 획득", "x 피격 시 3초간 저항력 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "저항력 중첩 배수 (지속시간 아님)."] [hunter.speed] - name = "사냥꾼의 속도" - description = "피격 시 배고픔을 대가로 속도를 얻습니다" - lore1 = "피격 시 패시브 속도 획득" - lore2 = "x 피격 시 3초간 속도 중첩" - lore3 = "x 중첩 배고픔" - lore4 = "배고픔 중첩의 지속시간과 배수." - lore5 = "속도 중첩 배수 (지속시간 아님)." - lore = ["피격 시 패시브 속도 획득", "x 피격 시 3초간 속도 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "속도 중첩 배수 (지속시간 아님)."] +name = "사냥꾼의 속도" +description = "피격 시 배고픔을 대가로 속도를 얻습니다" +lore1 = "피격 시 패시브 속도 획득" +lore2 = "x 피격 시 3초간 속도 중첩" +lore3 = "x 중첩 배고픔" +lore4 = "배고픔 중첩의 지속시간과 배수." +lore5 = "속도 중첩 배수 (지속시간 아님)." +lore = ["피격 시 패시브 속도 획득", "x 피격 시 3초간 속도 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "속도 중첩 배수 (지속시간 아님)."] [hunter.strength] - name = "사냥꾼의 힘" - description = "피격 시 배고픔을 대가로 힘을 얻습니다" - lore1 = "피격 시 패시브 힘 획득" - lore2 = "x 피격 시 3초간 힘 중첩" - lore3 = "x 중첩 배고픔" - lore4 = "배고픔 중첩의 지속시간과 배수." - lore5 = "힘 중첩 배수 (지속시간 아님)." - lore = ["피격 시 패시브 힘 획득", "x 피격 시 3초간 힘 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "힘 중첩 배수 (지속시간 아님)."] +name = "사냥꾼의 힘" +description = "피격 시 배고픔을 대가로 힘을 얻습니다" +lore1 = "피격 시 패시브 힘 획득" +lore2 = "x 피격 시 3초간 힘 중첩" +lore3 = "x 중첩 배고픔" +lore4 = "배고픔 중첩의 지속시간과 배수." +lore5 = "힘 중첩 배수 (지속시간 아님)." +lore = ["피격 시 패시브 힘 획득", "x 피격 시 3초간 힘 중첩", "x 중첩 배고픔", "배고픔 중첩의 지속시간과 배수.", "힘 중첩 배수 (지속시간 아님)."] # nether [nether] [nether.skull_toss] - name = "위더 해골 투척" - description1 = "내면의 위더를 해방하세요," - description2 = "누군가의" - description3 = "머리를 사용하여." - lore1 = "해골 투척 사이의 쿨타임 초." - lore2 = "위더 해골 사용: 던지기 " - lore3 = "위더 해골" - lore4 = "충돌 시 폭발." - lore = ["해골 투척 사이의 쿨타임 초.", "위더 해골 사용: 던지기 ", "위더 해골", "충돌 시 폭발."] +name = "위더 해골 투척" +description1 = "내면의 위더를 해방하세요," +description2 = "누군가의" +description3 = "머리를 사용하여." +lore1 = "해골 투척 사이의 쿨타임 초." +lore2 = "위더 해골 사용: 던지기 " +lore3 = "위더 해골" +lore4 = "충돌 시 폭발." +lore = ["해골 투척 사이의 쿨타임 초.", "위더 해골 사용: 던지기 ", "위더 해골", "충돌 시 폭발."] [nether.wither_resist] - name = "위더 저항" - description = "네더라이트의 힘으로 시듦을 저항합니다." - lore1 = "시듦을 무효화할 확률 (부위당)." - lore2 = "패시브: 네더라이트 갑옷 착용 시 일정 확률로 무효화 " - lore3 = "시듦 효과." - lore = ["시듦을 무효화할 확률 (부위당).", "패시브: 네더라이트 갑옷 착용 시 일정 확률로 무효화 ", "시듦 효과."] +name = "위더 저항" +description = "네더라이트의 힘으로 시듦을 저항합니다." +lore1 = "시듦을 무효화할 확률 (부위당)." +lore2 = "패시브: 네더라이트 갑옷 착용 시 일정 확률로 무효화 " +lore3 = "시듦 효과." +lore = ["시듦을 무효화할 확률 (부위당).", "패시브: 네더라이트 갑옷 착용 시 일정 확률로 무효화 ", "시듦 효과."] [nether.fire_resist] - name = "화염 저항" - description = "피부를 단단하게 하여 불에 저항합니다." - lore1 = "불타는 효과를 무효화할 확률!" - lore = ["불타는 효과를 무효화할 확률!"] +name = "화염 저항" +description = "피부를 단단하게 하여 불에 저항합니다." +lore1 = "불타는 효과를 무효화할 확률!" +lore = ["불타는 효과를 무효화할 확률!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "자동 제련" - description = "채굴한 바닐라 광석을 자동으로 제련합니다" - lore1 = "제련 가능한 광석이 자동으로 제련됩니다" - lore2 = "% 확률로 추가 드롭" - lore = ["제련 가능한 광석이 자동으로 제련됩니다", "% 확률로 추가 드롭"] +name = "자동 제련" +description = "채굴한 바닐라 광석을 자동으로 제련합니다" +lore1 = "제련 가능한 광석이 자동으로 제련됩니다" +lore2 = "% 확률로 추가 드롭" +lore = ["제련 가능한 광석이 자동으로 제련됩니다", "% 확률로 추가 드롭"] [pickaxe.chisel] - name = "광석 끌" - description = "광석을 우클릭하여 더 많은 광석을 캐낼 수 있지만, 내구도 소모가 심합니다." - lore1 = "드롭 확률" - lore2 = "도구 마모" - lore = ["드롭 확률", "도구 마모"] +name = "광석 끌" +description = "광석을 우클릭하여 더 많은 광석을 캐낼 수 있지만, 내구도 소모가 심합니다." +lore1 = "드롭 확률" +lore2 = "도구 마모" +lore = ["드롭 확률", "도구 마모"] [pickaxe.drop_to_inventory] - name = "곡괭이 인벤토리 드롭" - description = "블록을 깨면 아이템이 인벤토리로 바로 들어갑니다" - lore1 = "블록에서 아이템이 드롭될 때마다 가능하면 인벤토리로 직접 들어갑니다." - lore = ["블록에서 아이템이 드롭될 때마다 가능하면 인벤토리로 직접 들어갑니다."] +name = "곡괭이 인벤토리 드롭" +description = "블록을 깨면 아이템이 인벤토리로 바로 들어갑니다" +lore1 = "블록에서 아이템이 드롭될 때마다 가능하면 인벤토리로 직접 들어갑니다." +lore = ["블록에서 아이템이 드롭될 때마다 가능하면 인벤토리로 직접 들어갑니다."] [pickaxe.silk_spawner] - name = "곡괭이 섬세한 스포너" - description = "스포너를 파괴할 때 드롭되게 합니다" - lore1 = "섬세한 손길로 스포너를 부술 수 있게 합니다." - lore2 = "웅크린 채로 스포너를 부술 수 있게 합니다." - lore = ["섬세한 손길로 스포너를 부술 수 있게 합니다.", "웅크린 채로 스포너를 부술 수 있게 합니다."] +name = "곡괭이 섬세한 스포너" +description = "스포너를 파괴할 때 드롭되게 합니다" +lore1 = "섬세한 손길로 스포너를 부술 수 있게 합니다." +lore2 = "웅크린 채로 스포너를 부술 수 있게 합니다." +lore = ["섬세한 손길로 스포너를 부술 수 있게 합니다.", "웅크린 채로 스포너를 부술 수 있게 합니다."] [pickaxe.vein_miner] - name = "광맥 채굴" - description = "바닐라 광석의 맥/덩어리를 한꺼번에 캘 수 있습니다" - lore1 = "웅크리고 광석을 캐세요" - lore2 = "광맥 채굴 범위" - lore3 = "이 스킬은 모든 드롭을 하나로 합치지 않습니다!" - lore = ["웅크리고 광석을 캐세요", "광맥 채굴 범위", "이 스킬은 모든 드롭을 하나로 합치지 않습니다!"] +name = "광맥 채굴" +description = "바닐라 광석의 맥/덩어리를 한꺼번에 캘 수 있습니다" +lore1 = "웅크리고 광석을 캐세요" +lore2 = "광맥 채굴 범위" +lore3 = "이 스킬은 모든 드롭을 하나로 합치지 않습니다!" +lore = ["웅크리고 광석을 캐세요", "광맥 채굴 범위", "이 스킬은 모든 드롭을 하나로 합치지 않습니다!"] # ranged [ranged] [ranged.arrow_recovery] - name = "화살 회수" - description = "적을 죽인 후 화살을 회수합니다." - lore1 = "적중/처치 시 화살 회수 확률" - lore2 = "확률: " - lore = ["적중/처치 시 화살 회수 확률", "확률: "] +name = "화살 회수" +description = "적을 죽인 후 화살을 회수합니다." +lore1 = "적중/처치 시 화살 회수 확률" +lore2 = "확률: " +lore = ["적중/처치 시 화살 회수 확률", "확률: "] [ranged.web_shot] - name = "거미줄 올가미" - description = "대상을 맞추면 주위에 거미줄이 생깁니다!" - lore1 = "눈덩이 주변에 거미줄 8개, 그리고 던지세요!" - lore2 = "대략적인 가두기 시간(초)." - lore = ["눈덩이 주변에 거미줄 8개, 그리고 던지세요!", "대략적인 가두기 시간(초)."] +name = "거미줄 올가미" +description = "대상을 맞추면 주위에 거미줄이 생깁니다!" +lore1 = "눈덩이 주변에 거미줄 8개, 그리고 던지세요!" +lore2 = "대략적인 가두기 시간(초)." +lore = ["눈덩이 주변에 거미줄 8개, 그리고 던지세요!", "대략적인 가두기 시간(초)."] [ranged.force_shot] - name = "강력 사격" - description = "더 멀리, 더 빠르게 투사체를 쏩니다!" - advancementname = "장거리 사격" - advancementlore = "30블록 이상 떨어진 곳에서 적중시키세요!" - lore1 = "투사체 속도" - lore = ["투사체 속도"] +name = "강력 사격" +description = "더 멀리, 더 빠르게 투사체를 쏩니다!" +advancementname = "장거리 사격" +advancementlore = "30블록 이상 떨어진 곳에서 적중시키세요!" +lore1 = "투사체 속도" +lore = ["투사체 속도"] [ranged.lunge_shot] - name = "돌진 사격" - description = "낙하 중 화살을 쏘면 랜덤 방향으로 튕겨납니다" - lore1 = "랜덤 돌진 속도" - lore = ["랜덤 돌진 속도"] +name = "돌진 사격" +description = "낙하 중 화살을 쏘면 랜덤 방향으로 튕겨납니다" +lore1 = "랜덤 돌진 속도" +lore = ["랜덤 돌진 속도"] [ranged.arrow_piercing] - name = "화살 관통" - description = "투사체에 관통을 추가합니다! 적을 꿰뚫으세요!" - lore1 = "관통 대상 수" - lore = ["관통 대상 수"] +name = "화살 관통" +description = "투사체에 관통을 추가합니다! 적을 꿰뚫으세요!" +lore1 = "관통 대상 수" +lore = ["관통 대상 수"] # rift [rift] [rift.remote_access] - name = "원격 접근" - description = "공허에서 끌어와 표시된 보관함에 접근합니다." - lore1 = "엔더 진주 + 나침반 = 유물 포트키" - lore2 = "이 아이템으로 원격으로 보관함에 접근할 수 있습니다" - lore3 = "제작 후 아이템을 보고 사용법을 확인하세요" - notcontainer = "그것은 보관함이 아닙니다" - lore = ["엔더 진주 + 나침반 = 유물 포트키", "이 아이템으로 원격으로 보관함에 접근할 수 있습니다", "제작 후 아이템을 보고 사용법을 확인하세요"] +name = "원격 접근" +description = "공허에서 끌어와 표시된 보관함에 접근합니다." +lore1 = "엔더 진주 + 나침반 = 유물 포트키" +lore2 = "이 아이템으로 원격으로 보관함에 접근할 수 있습니다" +lore3 = "제작 후 아이템을 보고 사용법을 확인하세요" +notcontainer = "그것은 보관함이 아닙니다" +lore = ["엔더 진주 + 나침반 = 유물 포트키", "이 아이템으로 원격으로 보관함에 접근할 수 있습니다", "제작 후 아이템을 보고 사용법을 확인하세요"] [rift.blink] - name = "균열 점멸" - description = "근거리 즉시 순간이동, 눈 깜짝할 사이!" - lore1 = "점멸 블록 수 (수직 2배)" - lore2 = "질주 중: 점프를 두 번 탭하여 " - lore3 = "점멸" - lore = ["점멸 블록 수 (수직 2배)", "질주 중: 점프를 두 번 탭하여 ", "점멸"] +name = "균열 점멸" +description = "근거리 즉시 순간이동, 눈 깜짝할 사이!" +lore1 = "점멸 블록 수 (수직 2배)" +lore2 = "질주 중: 점프를 두 번 탭하여 " +lore3 = "점멸" +lore = ["점멸 블록 수 (수직 2배)", "질주 중: 점프를 두 번 탭하여 ", "점멸"] [rift.chest] - name = "간편 엔더 상자" - description = "손에 든 엔더 상자를 좌클릭하여 엽니다." - lore1 = "손에 든 엔더 상자를 클릭하여 열기 (설치하지 마세요)" - lore = ["손에 든 엔더 상자를 클릭하여 열기 (설치하지 마세요)"] +name = "간편 엔더 상자" +description = "손에 든 엔더 상자를 좌클릭하여 엽니다." +lore1 = "손에 든 엔더 상자를 클릭하여 열기 (설치하지 마세요)" +lore = ["손에 든 엔더 상자를 클릭하여 열기 (설치하지 마세요)"] [rift.descent] - name = "공중부양 방지" - description = "공중에 떠 있는 게 지겹나요? 이 스킬이 바로 당신을 위한 것입니다!" - lore1 = "웅크리면 하강하며, 일반보다 느린 속도로 떨어집니다!" - lore2 = "쿨타임:" - lore = ["웅크리면 하강하며, 일반보다 느린 속도로 떨어집니다!", "쿨타임:"] +name = "공중부양 방지" +description = "공중에 떠 있는 게 지겹나요? 이 스킬이 바로 당신을 위한 것입니다!" +lore1 = "웅크리면 하강하며, 일반보다 느린 속도로 떨어집니다!" +lore2 = "쿨타임:" +lore = ["웅크리면 하강하며, 일반보다 느린 속도로 떨어집니다!", "쿨타임:"] [rift.gate] - name = "균열 게이트" - description = "표시된 위치로 순간이동합니다." - lore1 = "제작: 에메랄드 + 자수정 조각 + 엔더 진주" - lore2 = "사용 전에 읽어보세요!" - lore3 = "5초 지연, " - lore4 = "이 애니메이션 중에 죽을 수 있습니다" - lore = ["제작: 에메랄드 + 자수정 조각 + 엔더 진주", "사용 전에 읽어보세요!", "5초 지연, ", "이 애니메이션 중에 죽을 수 있습니다"] +name = "균열 게이트" +description = "표시된 위치로 순간이동합니다." +lore1 = "제작: 에메랄드 + 자수정 조각 + 엔더 진주" +lore2 = "사용 전에 읽어보세요!" +lore3 = "5초 지연, " +lore4 = "이 애니메이션 중에 죽을 수 있습니다" +lore = ["제작: 에메랄드 + 자수정 조각 + 엔더 진주", "사용 전에 읽어보세요!", "5초 지연, ", "이 애니메이션 중에 죽을 수 있습니다"] [rift.resist] - name = "균열 저항" - description = "엔더 아이템 및 능력 사용 시 저항력을 얻습니다" - lore1 = "+ 패시브: 균열 능력이나 엔더 아이템 사용 시 저항력 제공" - lore2 = "휴대용 엔더 상자 제외, 소모 가능한 것만 해당" - lore = ["+ 패시브: 균열 능력이나 엔더 아이템 사용 시 저항력 제공", "휴대용 엔더 상자 제외, 소모 가능한 것만 해당"] +name = "균열 저항" +description = "엔더 아이템 및 능력 사용 시 저항력을 얻습니다" +lore1 = "+ 패시브: 균열 능력이나 엔더 아이템 사용 시 저항력 제공" +lore2 = "휴대용 엔더 상자 제외, 소모 가능한 것만 해당" +lore = ["+ 패시브: 균열 능력이나 엔더 아이템 사용 시 저항력 제공", "휴대용 엔더 상자 제외, 소모 가능한 것만 해당"] [rift.visage] - name = "균열 형상" - description = "인벤토리에 엔더 진주가 있으면 엔더맨이 적대적으로 변하지 않습니다." - lore1 = "인벤토리에 엔더 진주가 있으면 엔더맨이 적대적으로 변하지 않습니다." - lore = ["인벤토리에 엔더 진주가 있으면 엔더맨이 적대적으로 변하지 않습니다."] +name = "균열 형상" +description = "인벤토리에 엔더 진주가 있으면 엔더맨이 적대적으로 변하지 않습니다." +lore1 = "인벤토리에 엔더 진주가 있으면 엔더맨이 적대적으로 변하지 않습니다." +lore = ["인벤토리에 엔더 진주가 있으면 엔더맨이 적대적으로 변하지 않습니다."] # seaborn [seaborn] [seaborn.oxygen] - name = "유기 산소 탱크" - description = "작은 폐에 더 많은 산소를 담으세요!" - lore1 = "산소 용량 증가" - lore = ["산소 용량 증가"] +name = "유기 산소 탱크" +description = "작은 폐에 더 많은 산소를 담으세요!" +lore1 = "산소 용량 증가" +lore = ["산소 용량 증가"] [seaborn.fishers_fantasy] - name = "낚시꾼의 환상" - description = "낚시에서 더 많은 XP와 물고기를 얻으세요!" - lore1 = "레벨마다 더 많은 XP와 물고기를 얻을 확률이 있습니다!" - lore = ["레벨마다 더 많은 XP와 물고기를 얻을 확률이 있습니다!"] +name = "낚시꾼의 환상" +description = "낚시에서 더 많은 XP와 물고기를 얻으세요!" +lore1 = "레벨마다 더 많은 XP와 물고기를 얻을 확률이 있습니다!" +lore = ["레벨마다 더 많은 XP와 물고기를 얻을 확률이 있습니다!"] [seaborn.haste] - name = "거북이 광부" - description = "수중에서 채굴 시 성급함을 얻습니다!" - lore1 = "수중 호흡 효과가 사라진 후 수중에서 채굴 시 성급함 3이 적용됩니다 (친수성과 중첩)!" - lore = ["수중 호흡 효과가 사라진 후 수중에서 채굴 시 성급함 3이 적용됩니다 (친수성과 중첩)!"] +name = "거북이 광부" +description = "수중에서 채굴 시 성급함을 얻습니다!" +lore1 = "수중 호흡 효과가 사라진 후 수중에서 채굴 시 성급함 3이 적용됩니다 (친수성과 중첩)!" +lore = ["수중 호흡 효과가 사라진 후 수중에서 채굴 시 성급함 3이 적용됩니다 (친수성과 중첩)!"] [seaborn.night_vision] - name = "거북이의 시야" - description = "수중에서 야간 투시를 얻습니다" - lore1 = "수중 호흡 효과가 사라진 후 간단히 수중에서 야간 투시를 얻습니다!" - lore = ["수중 호흡 효과가 사라진 후 간단히 수중에서 야간 투시를 얻습니다!"] +name = "거북이의 시야" +description = "수중에서 야간 투시를 얻습니다" +lore1 = "수중 호흡 효과가 사라진 후 간단히 수중에서 야간 투시를 얻습니다!" +lore = ["수중 호흡 효과가 사라진 후 간단히 수중에서 야간 투시를 얻습니다!"] [seaborn.dolphin_grace] - name = "돌고래의 은총" - description = "돌고래 없이 돌고래처럼 수영합니다" - lore1 = "+ 패시브: 획득 " - lore2 = "x 속도 (돌고래의 은총)" - lore3 = "정밀한 독일 공학이- 잠깐 그건 아니고... 물갈퀴와 호환되지 않습니다" - lore = ["+ 패시브: 획득 ", "x 속도 (돌고래의 은총)", "정밀한 독일 공학이- 잠깐 그건 아니고... 물갈퀴와 호환되지 않습니다"] +name = "돌고래의 은총" +description = "돌고래 없이 돌고래처럼 수영합니다" +lore1 = "+ 패시브: 획득 " +lore2 = "x 속도 (돌고래의 은총)" +lore3 = "정밀한 독일 공학이- 잠깐 그건 아니고... 물갈퀴와 호환되지 않습니다" +lore = ["+ 패시브: 획득 ", "x 속도 (돌고래의 은총)", "정밀한 독일 공학이- 잠깐 그건 아니고... 물갈퀴와 호환되지 않습니다"] # stealth [stealth] [stealth.ghost_armor] - name = "유령의 갑옷" - description = "피해를 받지 않을 때 천천히 방어력이 쌓이며, 1회 피격 시 소멸" - lore1 = "최대 방어력" - lore2 = "속도" - lore = ["최대 방어력", "속도"] +name = "유령의 갑옷" +description = "피해를 받지 않을 때 천천히 방어력이 쌓이며, 1회 피격 시 소멸" +lore1 = "최대 방어력" +lore2 = "속도" +lore = ["최대 방어력", "속도"] [stealth.night_vision] - name = "은신 투시" - description = "웅크리는 동안 야간 투시를 얻습니다" - lore1 = "한 차례의 " - lore2 = "야간 투시" - lore3 = "를 웅크리는 동안 얻습니다" - lore = ["한 차례의 ", "야간 투시", "를 웅크리는 동안 얻습니다"] +name = "은신 투시" +description = "웅크리는 동안 야간 투시를 얻습니다" +lore1 = "한 차례의 " +lore2 = "야간 투시" +lore3 = "를 웅크리는 동안 얻습니다" +lore = ["한 차례의 ", "야간 투시", "를 웅크리는 동안 얻습니다"] [stealth.snatch] - name = "아이템 낚아채기" - description = "웅크리는 동안 드롭된 아이템을 즉시 낚아챕니다!" - lore1 = "낚아채기 반경" - lore = ["낚아채기 반경"] +name = "아이템 낚아채기" +description = "웅크리는 동안 드롭된 아이템을 즉시 낚아챕니다!" +lore1 = "낚아채기 반경" +lore = ["낚아채기 반경"] [stealth.speed] - name = "은신 속도" - description = "웅크리는 동안 속도를 얻습니다" - lore1 = "은신 속도" - lore = ["은신 속도"] +name = "은신 속도" +description = "웅크리는 동안 속도를 얻습니다" +lore1 = "은신 속도" +lore = ["은신 속도"] [stealth.ender_veil] - name = "엔더 장막" - description = "엔더맨 공격을 방지하기 위해 호박이 더 이상 필요 없습니다" - lore1 = "웅크리는 동안 엔더맨 공격을 방지" - lore2 = "모든 엔더맨 공격을 방지" - lore = ["웅크리는 동안 엔더맨 공격을 방지", "모든 엔더맨 공격을 방지"] +name = "엔더 장막" +description = "엔더맨 공격을 방지하기 위해 호박이 더 이상 필요 없습니다" +lore1 = "웅크리는 동안 엔더맨 공격을 방지" +lore2 = "모든 엔더맨 공격을 방지" +lore = ["웅크리는 동안 엔더맨 공격을 방지", "모든 엔더맨 공격을 방지"] # sword [sword] [sword.machete] - name = "마체테" - description = "풀잎을 쉽게 베어냅니다!" - lore1 = "베기 반경" - lore2 = "베기 쿨타임" - lore3 = "도구 마모" - lore = ["베기 반경", "베기 쿨타임", "도구 마모"] +name = "마체테" +description = "풀잎을 쉽게 베어냅니다!" +lore1 = "베기 반경" +lore2 = "베기 쿨타임" +lore3 = "도구 마모" +lore = ["베기 반경", "베기 쿨타임", "도구 마모"] [sword.bloody_blade] - name = "피묻은 칼날" - description = "검으로 공격하면 출혈을 일으킵니다!" - lore1 = "검으로 생물을 공격하면 출혈이 발생합니다" - lore2 = "출혈 지속시간" - lore3 = "출혈 쿨타임" - lore = ["검으로 생물을 공격하면 출혈이 발생합니다", "출혈 지속시간", "출혈 쿨타임"] +name = "피묻은 칼날" +description = "검으로 공격하면 출혈을 일으킵니다!" +lore1 = "검으로 생물을 공격하면 출혈이 발생합니다" +lore2 = "출혈 지속시간" +lore3 = "출혈 쿨타임" +lore = ["검으로 생물을 공격하면 출혈이 발생합니다", "출혈 지속시간", "출혈 쿨타임"] [sword.poisoned_blade] - name = "독검" - description = "검으로 공격하면 독을 일으킵니다!" - lore1 = "검으로 생물을 공격하면 독이 발생합니다" - lore2 = "독 지속시간" - lore3 = "독 쿨타임" - lore = ["검으로 생물을 공격하면 독이 발생합니다", "독 지속시간", "독 쿨타임"] +name = "독검" +description = "검으로 공격하면 독을 일으킵니다!" +lore1 = "검으로 생물을 공격하면 독이 발생합니다" +lore2 = "독 지속시간" +lore3 = "독 쿨타임" +lore = ["검으로 생물을 공격하면 독이 발생합니다", "독 지속시간", "독 쿨타임"] # taming [taming] [taming.damage] - name = "길들인 동물 피해량" - description = "길들인 동물이 주는 피해를 증가시킵니다." - lore1 = "피해량 증가" - lore = ["피해량 증가"] +name = "길들인 동물 피해량" +description = "길들인 동물이 주는 피해를 증가시킵니다." +lore1 = "피해량 증가" +lore = ["피해량 증가"] [taming.health] - name = "길들인 동물 체력" - description = "길들인 동물의 체력을 증가시킵니다." - lore1 = "체력 증가" - lore = ["체력 증가"] +name = "길들인 동물 체력" +description = "길들인 동물의 체력을 증가시킵니다." +lore1 = "체력 증가" +lore = ["체력 증가"] [taming.regeneration] - name = "길들인 동물 재생" - description = "길들인 동물의 재생력을 증가시킵니다." - lore1 = "HP/초" - lore = ["HP/초"] +name = "길들인 동물 재생" +description = "길들인 동물의 재생력을 증가시킵니다." +lore1 = "HP/초" +lore = ["HP/초"] # tragoul [tragoul] [tragoul.thorns] - name = "가시" - description = "공격자에게 피해를 반사합니다!" - lore1 = "피격 시 반사되는 피해량" - lore = ["피격 시 반사되는 피해량"] +name = "가시" +description = "공격자에게 피해를 반사합니다!" +lore1 = "피격 시 반사되는 피해량" +lore = ["피격 시 반사되는 피해량"] [tragoul.globe] - name = "고통의 구체" - description = "주변 적의 수에 따라 피해를 나눕니다!" - lore1 = "주변에 적이 많을수록 각 적에게 주는 피해가 줄어듭니다" - lore2 = "범위: " - lore3 = "모든 엔티티에 추가 피해: " - lore = ["주변에 적이 많을수록 각 적에게 주는 피해가 줄어듭니다", "범위: ", "모든 엔티티에 추가 피해: "] +name = "고통의 구체" +description = "주변 적의 수에 따라 피해를 나눕니다!" +lore1 = "주변에 적이 많을수록 각 적에게 주는 피해가 줄어듭니다" +lore2 = "범위: " +lore3 = "모든 엔티티에 추가 피해: " +lore = ["주변에 적이 많을수록 각 적에게 주는 피해가 줄어듭니다", "범위: ", "모든 엔티티에 추가 피해: "] [tragoul.healing] - name = "고통의 의지" - description = "가한 피해에 비례하여 체력을 회복합니다!" - lore1 = "상처 입히는 것이 이렇게 기분 좋을 줄이야! 가한 피해로 회복" - lore2 = "3초의 피해 창과 1초의 쿨타임이 있습니다 " - lore3 = "피해 퍼센트당 회복량: " - lore = ["상처 입히는 것이 이렇게 기분 좋을 줄이야! 가한 피해로 회복", "3초의 피해 창과 1초의 쿨타임이 있습니다 ", "피해 퍼센트당 회복량: "] +name = "고통의 의지" +description = "가한 피해에 비례하여 체력을 회복합니다!" +lore1 = "상처 입히는 것이 이렇게 기분 좋을 줄이야! 가한 피해로 회복" +lore2 = "3초의 피해 창과 1초의 쿨타임이 있습니다 " +lore3 = "피해 퍼센트당 회복량: " +lore = ["상처 입히는 것이 이렇게 기분 좋을 줄이야! 가한 피해로 회복", "3초의 피해 창과 1초의 쿨타임이 있습니다 ", "피해 퍼센트당 회복량: "] [tragoul.lance] - name = "시체 창" - description = "적을 죽이거나 능력으로 적을 처치하면, 근처 적에게 피해를 주는 창이 생성됩니다!" - lore1 = "창은 당신이 처치한 모든 대상에서 발사되며, 이 능력이 적을 죽일 경우에도 발동합니다." - lore2 = "창을 만들기 위해 생명력의 일부를 희생합니다 (이것이 당신을 죽일 수 있습니다)" - lore3 = "최대 창: 1 + " - lore = ["창은 당신이 처치한 모든 대상에서 발사되며, 이 능력이 적을 죽일 경우에도 발동합니다.", "창을 만들기 위해 생명력의 일부를 희생합니다 (이것이 당신을 죽일 수 있습니다)", "최대 창: 1 + "] +name = "시체 창" +description = "적을 죽이거나 능력으로 적을 처치하면, 근처 적에게 피해를 주는 창이 생성됩니다!" +lore1 = "창은 당신이 처치한 모든 대상에서 발사되며, 이 능력이 적을 죽일 경우에도 발동합니다." +lore2 = "창을 만들기 위해 생명력의 일부를 희생합니다 (이것이 당신을 죽일 수 있습니다)" +lore3 = "최대 창: 1 + " +lore = ["창은 당신이 처치한 모든 대상에서 발사되며, 이 능력이 적을 죽일 경우에도 발동합니다.", "창을 만들기 위해 생명력의 일부를 희생합니다 (이것이 당신을 죽일 수 있습니다)", "최대 창: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "유리 대포" - description = "방어력이 낮을수록 비무장 추가 피해" - lore1 = "x 방어력 0일 때 피해량" - lore2 = "레벨당 추가 피해" - lore = ["x 방어력 0일 때 피해량", "레벨당 추가 피해"] +name = "유리 대포" +description = "방어력이 낮을수록 비무장 추가 피해" +lore1 = "x 방어력 0일 때 피해량" +lore2 = "레벨당 추가 피해" +lore = ["x 방어력 0일 때 피해량", "레벨당 추가 피해"] [unarmed.power] - name = "비무장 파워" - description = "향상된 비무장 피해" - lore1 = "피해량" - lore = ["피해량"] +name = "비무장 파워" +description = "향상된 비무장 피해" +lore1 = "피해량" +lore = ["피해량"] [unarmed.sucker_punch] - name = "기습 펀치" - description = "질주 펀치, 하지만 더 치명적으로." - lore1 = "피해량" - lore2 = "펀치를 날리며 달리는 속도에 따라 피해가 증가합니다" - lore = ["피해량", "펀치를 날리며 달리는 속도에 따라 피해가 증가합니다"] +name = "기습 펀치" +description = "질주 펀치, 하지만 더 치명적으로." +lore1 = "피해량" +lore2 = "펀치를 날리며 달리는 속도에 따라 피해가 증가합니다" +lore = ["피해량", "펀치를 날리며 달리는 속도에 따라 피해가 증가합니다"] diff --git a/src/main/resources/lt_LT.toml b/src/main/resources/lt_LT.toml index f28cc24a8..3705b53cb 100644 --- a/src/main/resources/lt_LT.toml +++ b/src/main/resources/lt_LT.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "Laikas pajudeti!" - description = "Nueikite daugiau nei 1 kilometra (1,000 bloku)" +title = "Laikas pajudeti!" +description = "Nueikite daugiau nei 1 kilometra (1,000 bloku)" [advancement.challenge_sprint_5k] - title = "Sprintas 5K!" - description = "Nueikite daugiau nei 5 kilometrus (5,000 bloku)" +title = "Sprintas 5K!" +description = "Nueikite daugiau nei 5 kilometrus (5,000 bloku)" [advancement.challenge_sprint_50k] - title = "Staigus 50K!" - description = "Nueikite daugiau nei 50 kilometru (50,000 bloku)" +title = "Staigus 50K!" +description = "Nueikite daugiau nei 50 kilometru (50,000 bloku)" [advancement.challenge_sprint_500k] - title = "Keliaukite per Visata!!" - description = "Nueikite daugiau nei 500 kilometru (500,000 bloku)" +title = "Keliaukite per Visata!!" +description = "Nueikite daugiau nei 500 kilometru (500,000 bloku)" [advancement.challenge_sprint_marathon] - title = "Sprintas ir maratonas viename!" - description = "Sprintas virs 42,195 bloku!" +title = "Sprintas ir maratonas viename!" +description = "Sprintas virs 42,195 bloku!" [advancement.challenge_place_1k] - title = "Naujas Statybininkas!" - description = "Pastatyk 1,000 Bloku" +title = "Naujas Statybininkas!" +description = "Pastatyk 1,000 Bloku" [advancement.challenge_place_5k] - title = "Tarpinis Statybininkas!" - description = "Pastatyk 5,000 Bloku" +title = "Tarpinis Statybininkas!" +description = "Pastatyk 5,000 Bloku" [advancement.challenge_place_50k] - title = "Pazangus Statybininkas!" - description = "Pastatyk 50,000 Bloku" +title = "Pazangus Statybininkas!" +description = "Pastatyk 50,000 Bloku" [advancement.challenge_place_500k] - title = "Profesionalus Statybininkas!" - description = "Pastatyk 500,000 Bloku" +title = "Profesionalus Statybininkas!" +description = "Pastatyk 500,000 Bloku" [advancement.challenge_place_5m] - title = "Simetrijos Akolitas!" - description = "REALYBE YRA JUSU ZAIDIMU AIKSTELE! (5 Milijonai bloku)" +title = "Simetrijos Akolitas!" +description = "REALYBE YRA JUSU ZAIDIMU AIKSTELE! (5 Milijonai bloku)" [advancement.challenge_chop_1k] - title = "Naujas Medkirtys!" - description = "Nukirsk 1,000 Bloku" +title = "Naujas Medkirtys!" +description = "Nukirsk 1,000 Bloku" [advancement.challenge_chop_5k] - title = "Tarpinis Medkirtys!" - description = "Nukirsk 5,000 Bloku" +title = "Tarpinis Medkirtys!" +description = "Nukirsk 5,000 Bloku" [advancement.challenge_chop_50k] - title = "Pazenges Medkirtys!" - description = "Nukirsk 50,000 Bloku" +title = "Pazenges Medkirtys!" +description = "Nukirsk 50,000 Bloku" [advancement.challenge_chop_500k] - title = "Profesionalus Medkirtys!" - description = "Nukirskite 500,000 Bloku" +title = "Profesionalus Medkirtys!" +description = "Nukirskite 500,000 Bloku" [advancement.challenge_chop_5m] - title = "Tikras Gateristas" - description = "Kieciausias Darbininkas! (5 Milijonai Bloku)" +title = "Tikras Gateristas" +description = "Kieciausias Darbininkas! (5 Milijonai Bloku)" [advancement.challenge_block_1k] - title = "Vos Pabegi!" - description = "Uzblokuok 1000 Smugiu" +title = "Vos Pabegi!" +description = "Uzblokuok 1000 Smugiu" [advancement.challenge_block_5k] - title = "Blokuoti yra Smagu!" - description = "Uzblokuok 5000 Smugiu" +title = "Blokuoti yra Smagu!" +description = "Uzblokuok 5000 Smugiu" [advancement.challenge_block_50k] - title = "Blokavimas yra mano gyvenimas!" - description = "Uzblokuok 50,000 Smugiu" +title = "Blokavimas yra mano gyvenimas!" +description = "Uzblokuok 50,000 Smugiu" [advancement.challenge_block_500k] - title = "Blokavimas yra mano tikslas!" - description = "Uzblokuok 500,000 Smugiu" +title = "Blokavimas yra mano tikslas!" +description = "Uzblokuok 500,000 Smugiu" [advancement.challenge_block_5m] - title = "Ranka, kuria skauda" - description = "Uzblokuok 5,000,000 Smugiu" +title = "Ranka, kuria skauda" +description = "Uzblokuok 5,000,000 Smugiu" [advancement.challenge_brew_1k] - title = "Naujas Alchemikas!" - description = "Suvartok 1000 Eliksyru" +title = "Naujas Alchemikas!" +description = "Suvartok 1000 Eliksyru" [advancement.challenge_brew_5k] - title = "Tarpinis Alchemikas!" - description = "Suvartok 5000 Eliksyru" +title = "Tarpinis Alchemikas!" +description = "Suvartok 5000 Eliksyru" [advancement.challenge_brew_50k] - title = "Pazenges Alchemikas!" - description = "Suvartok 50,000 Eliksyru" +title = "Pazenges Alchemikas!" +description = "Suvartok 50,000 Eliksyru" [advancement.challenge_brew_500k] - title = "Profesionalus Alchemikas!" - description = "Suvartok 500,000 Eliksyru" +title = "Profesionalus Alchemikas!" +description = "Suvartok 500,000 Eliksyru" [advancement.challenge_brew_5m] - title = "Alchemikas" - description = "Suvartok 5,000,000 Eliksyru" +title = "Alchemikas" +description = "Suvartok 5,000,000 Eliksyru" [advancement.challenge_brewsplash_1k] - title = "Naujas Eliksyru metytojas!" - description = "Sudauzyk 1000 Eliksyru" +title = "Naujas Eliksyru metytojas!" +description = "Sudauzyk 1000 Eliksyru" [advancement.challenge_brewsplash_5k] - title = "Tarpinis Eliksyru metytojas!" - description = "Sudauzyk 5000 Eliksyru" +title = "Tarpinis Eliksyru metytojas!" +description = "Sudauzyk 5000 Eliksyru" [advancement.challenge_brewsplash_50k] - title = "Pazenges Eliksyru metytojas!" - description = "Sudauzyk 50,000 Eliksyru" +title = "Pazenges Eliksyru metytojas!" +description = "Sudauzyk 50,000 Eliksyru" [advancement.challenge_brewsplash_500k] - title = "Profesionalus Eliksyru metytojas!" - description = "Sudauzyk 500,000 Eliksyru" +title = "Profesionalus Eliksyru metytojas!" +description = "Sudauzyk 500,000 Eliksyru" [advancement.challenge_brewsplash_5m] - title = "Metymo Profesionalas" - description = "Sudauzyk 5,000,000 Eliksyru" +title = "Metymo Profesionalas" +description = "Sudauzyk 5,000,000 Eliksyru" [advancement.challenge_craft_1k] - title = "Gamintojėlis!" - description = "Pagamink 1000 Daiktu" +title = "Gamintojėlis!" +description = "Pagamink 1000 Daiktu" [advancement.challenge_craft_5k] - title = "Ispudingas Meistras!" - description = "Pagamink 5000 Daiktu" +title = "Ispudingas Meistras!" +description = "Pagamink 5000 Daiktu" [advancement.challenge_craft_50k] - title = "Paslaugus Meistras!" - description = "Pagamink 50,000 Daiktu" +title = "Paslaugus Meistras!" +description = "Pagamink 50,000 Daiktu" [advancement.challenge_craft_500k] - title = "Kakofoninis Gamintojas!" - description = "Pagamink 500,000 Daiktu" +title = "Kakofoninis Gamintojas!" +description = "Pagamink 500,000 Daiktu" [advancement.challenge_craft_5m] - title = "Greitarankis" - description = "Pagamink 5,000,000 Daiktu" +title = "Greitarankis" +description = "Pagamink 5,000,000 Daiktu" [advancement.challenge_enchant_1k] - title = "Naujasis Burtininkas!" - description = "Uzburkite 1000 Daiktu" +title = "Naujasis Burtininkas!" +description = "Uzburkite 1000 Daiktu" [advancement.challenge_enchant_5k] - title = "Tarpinis Burtininkas!" - description = "Uzburkite 5000 Daiktu" +title = "Tarpinis Burtininkas!" +description = "Uzburkite 5000 Daiktu" [advancement.challenge_enchant_50k] - title = "Pazenges Burtininkas!" - description = "Uzburkite 50,000 Daiktu" +title = "Pazenges Burtininkas!" +description = "Uzburkite 50,000 Daiktu" [advancement.challenge_enchant_500k] - title = "Profesionalus Burtininkas!" - description = "Uzburkite 500,000 Daiktu" +title = "Profesionalus Burtininkas!" +description = "Uzburkite 500,000 Daiktu" [advancement.challenge_enchant_5m] - title = "Mislingasis Keretojas" - description = "Uzburkite 5,000,000 Daiktu" +title = "Mislingasis Keretojas" +description = "Uzburkite 5,000,000 Daiktu" [advancement.challenge_excavate_1k] - title = "Suviliotas Ekskavatoristas!" - description = "Iskaskite 1000 Bloku" +title = "Suviliotas Ekskavatoristas!" +description = "Iskaskite 1000 Bloku" [advancement.challenge_excavate_5k] - title = "Tarpinis Ekskavatoristas!" - description = "Iskaskite 5000 Bloku" +title = "Tarpinis Ekskavatoristas!" +description = "Iskaskite 5000 Bloku" [advancement.challenge_excavate_50k] - title = "Pazangus Ekskavatoristas!" - description = "Iskaskite 50,000 Bloku" +title = "Pazangus Ekskavatoristas!" +description = "Iskaskite 50,000 Bloku" [advancement.challenge_excavate_500k] - title = "Profesionalus Ekskavatoristas!" - description = "Iskaskite 500,000 Bloku" +title = "Profesionalus Ekskavatoristas!" +description = "Iskaskite 500,000 Bloku" [advancement.challenge_excavate_5m] - title = "Mislingas Ekskavatoristas" - description = "Iskaskite 5,000,000 Bloku" +title = "Mislingas Ekskavatoristas" +description = "Iskaskite 5,000,000 Bloku" [advancement.horrible_person] - title = "Jus Esate Siaubingas Zmogus" - description = "Nesuvokiama, tikrai" +title = "Jus Esate Siaubingas Zmogus" +description = "Nesuvokiama, tikrai" [advancement.challenge_turtle_egg_smasher] - title = "Vezlio kiausiniu dauzыtojas!" - description = "Sudauzykite 100 vezlio kiausiniu" +title = "Vezlio kiausiniu dauzыtojas!" +description = "Sudauzykite 100 vezlio kiausiniu" [advancement.challenge_turtle_egg_annihilator] - title = "Vezlio kiausiniu naikintojas!" - description = "Sudauzykite 500 vezliu kiausiniu" +title = "Vezlio kiausiniu naikintojas!" +description = "Sudauzykite 500 vezliu kiausiniu" [advancement.challenge_novice_hunter] - title = "Naujoku medziotojas!" - description = "Nuzudykite 100 subjektu" +title = "Naujoku medziotojas!" +description = "Nuzudykite 100 subjektu" [advancement.challenge_intermediate_hunter] - title = "Vidutinio lygio medziotojas!" - description = "Nuzudykite 500 subjektu" +title = "Vidutinio lygio medziotojas!" +description = "Nuzudykite 500 subjektu" [advancement.challenge_advanced_hunter] - title = "Pazangus medziotojas!" - description = "Nuzudykite 5000 subjektu" +title = "Pazangus medziotojas!" +description = "Nuzudykite 5000 subjektu" [advancement.challenge_creeper_conqueror] - title = "Kryperio Uzkariautojas!" - description = "Nuzudykite 50 kryperių" +title = "Kryperio Uzkariautojas!" +description = "Nuzudykite 50 kryperių" [advancement.challenge_creeper_annihilator] - title = "Kryperių Naikintojas!" - description = "Nuzudykite 200 kryperių" +title = "Kryperių Naikintojas!" +description = "Nuzudykite 200 kryperių" [advancement.challenge_pickaxe_1k] - title = "Pradedantysis Kalnakasys" - description = "Sugriaukite 1000 Bloku" +title = "Pradedantysis Kalnakasys" +description = "Sugriaukite 1000 Bloku" [advancement.challenge_pickaxe_5k] - title = "Iguděs Kalnakasys" - description = "Sugriaukite 5000 Bloku" +title = "Iguděs Kalnakasys" +description = "Sugriaukite 5000 Bloku" [advancement.challenge_pickaxe_50k] - title = "Ekspertas Kalnakasys" - description = "Sugriaukite 50,000 Bloku" +title = "Ekspertas Kalnakasys" +description = "Sugriaukite 50,000 Bloku" [advancement.challenge_pickaxe_500k] - title = "Meistras Kalnakasys" - description = "Sugriaukite 500,000 Bloku" +title = "Meistras Kalnakasys" +description = "Sugriaukite 500,000 Bloku" [advancement.challenge_pickaxe_5m] - title = "Legendinis Kalnakasys" - description = "Sugriaukite 5,000,000 Bloku" +title = "Legendinis Kalnakasys" +description = "Sugriaukite 5,000,000 Bloku" [advancement.challenge_eat_100] - title = "Tiek daug valgyti!" - description = "Suvalgykite daugiau nei 100 Maisto!" +title = "Tiek daug valgyti!" +description = "Suvalgykite daugiau nei 100 Maisto!" [advancement.challenge_eat_1000] - title = "Nenumaldomas Alkis!" - description = "Suvalgykite daugiau nei 1,000 Maisto!" +title = "Nenumaldomas Alkis!" +description = "Suvalgykite daugiau nei 1,000 Maisto!" [advancement.challenge_eat_10000] - title = "AMZINAS ALKIS!" - description = "Suvalgykite daugiau nei 10,000 Maisto!" +title = "AMZINAS ALKIS!" +description = "Suvalgykite daugiau nei 10,000 Maisto!" [advancement.challenge_harvest_100] - title = "Pilnas Derlius" - description = "Nuimkite daugiau nei 100 paseliu!" +title = "Pilnas Derlius" +description = "Nuimkite daugiau nei 100 paseliu!" [advancement.challenge_harvest_1000] - title = "Didysis Derlius" - description = "Nuimkite daugiau nei 1,000 paseliu!" +title = "Didysis Derlius" +description = "Nuimkite daugiau nei 1,000 paseliu!" [advancement.challenge_swim_1nm] - title = "Zmogus Povandeninis Laivas!" - description = "Plaukite 1 jurmyle (1,852 blokai)" +title = "Zmogus Povandeninis Laivas!" +description = "Plaukite 1 jurmyle (1,852 blokai)" [advancement.challenge_sneak_1k] - title = "Kelio Skausmas" - description = "Nusliauzti daugiau nei kilometra (1,000 bloku)" +title = "Kelio Skausmas" +description = "Nusliauzti daugiau nei kilometra (1,000 bloku)" [advancement.challenge_sneak_5k] - title = "Seseliu Klajunas" - description = "Nuslinkti daugiau nei 5 000 bloku" +title = "Seseliu Klajunas" +description = "Nuslinkti daugiau nei 5 000 bloku" [advancement.challenge_sneak_20k] - title = "Vaiduoklis" - description = "Nuslinkti daugiau nei 20 000 bloku" +title = "Vaiduoklis" +description = "Nuslinkti daugiau nei 20 000 bloku" [advancement.challenge_swim_5k] - title = "Gilus Naras" - description = "Nuplaukti daugiau nei 5 000 bloku" +title = "Gilus Naras" +description = "Nuplaukti daugiau nei 5 000 bloku" [advancement.challenge_swim_20k] - title = "Poseidono Isrinktasis" - description = "Nuplaukti daugiau nei 20 000 bloku" +title = "Poseidono Isrinktasis" +description = "Nuplaukti daugiau nei 20 000 bloku" [advancement.challenge_sword_100] - title = "Pirmas Kraujas" - description = "Smogti 100 kartu kardu" +title = "Pirmas Kraujas" +description = "Smogti 100 kartu kardu" [advancement.challenge_sword_1k] - title = "Asmenio Sokejas" - description = "Smogti 1 000 kartu kardu" +title = "Asmenio Sokejas" +description = "Smogti 1 000 kartu kardu" [advancement.challenge_sword_10k] - title = "Tukstantis Ipjovimu" - description = "Smogti 10 000 kartu kardu" +title = "Tukstantis Ipjovimu" +description = "Smogti 10 000 kartu kardu" [advancement.challenge_unarmed_100] - title = "Baro Mustainis" - description = "Smogti 100 kartu be ginklo" +title = "Baro Mustainis" +description = "Smogti 100 kartu be ginklo" [advancement.challenge_unarmed_1k] - title = "Geleziniai Kumsciai" - description = "Smogti 1 000 kartu be ginklo" +title = "Geleziniai Kumsciai" +description = "Smogti 1 000 kartu be ginklo" [advancement.challenge_unarmed_10k] - title = "Vienas Smugis" - description = "Smogti 10 000 kartu be ginklo" +title = "Vienas Smugis" +description = "Smogti 10 000 kartu be ginklo" [advancement.challenge_trag_1k] - title = "Kraujo Kaina" - description = "Gauti 1 000 zalos" +title = "Kraujo Kaina" +description = "Gauti 1 000 zalos" [advancement.challenge_trag_10k] - title = "Raudonas Potvynis" - description = "Gauti 10 000 zalos" +title = "Raudonas Potvynis" +description = "Gauti 10 000 zalos" [advancement.challenge_trag_100k] - title = "Kancios Avataras" - description = "Gauti 100 000 zalos" +title = "Kancios Avataras" +description = "Gauti 100 000 zalos" [advancement.challenge_ranged_100] - title = "Taikinio Pratybos" - description = "Isouti 100 sviediniai" +title = "Taikinio Pratybos" +description = "Isouti 100 sviediniai" [advancement.challenge_ranged_1k] - title = "Vanago Akis" - description = "Isouti 1 000 sviediniai" +title = "Vanago Akis" +description = "Isouti 1 000 sviediniai" [advancement.challenge_ranged_10k] - title = "Strelu Audra" - description = "Isouti 10 000 sviediniai" +title = "Strelu Audra" +description = "Isouti 10 000 sviediniai" [advancement.challenge_chronos_1h] - title = "Tik Tak" - description = "Praleisti 1 valanda prisijungus" +title = "Tik Tak" +description = "Praleisti 1 valanda prisijungus" [advancement.challenge_chronos_24h] - title = "Laiko Smeliai" - description = "Praleisti 24 valandas prisijungus" +title = "Laiko Smeliai" +description = "Praleisti 24 valandas prisijungus" [advancement.challenge_chronos_168h] - title = "Belaikiksnis" - description = "Praleisti 168 valandas (1 savaite) prisijungus" +title = "Belaikiksnis" +description = "Praleisti 168 valandas (1 savaite) prisijungus" [advancement.challenge_nether_50] - title = "Pragaro Vartininkas" - description = "Nudobti 50 nethero butyniu" +title = "Pragaro Vartininkas" +description = "Nudobti 50 nethero butyniu" [advancement.challenge_nether_500] - title = "Bedugnio Sargas" - description = "Nudobti 500 nethero butyniu" +title = "Bedugnio Sargas" +description = "Nudobti 500 nethero butyniu" [advancement.challenge_nether_5k] - title = "Nethero Valdovas" - description = "Nudobti 5 000 nethero butyniu" +title = "Nethero Valdovas" +description = "Nudobti 5 000 nethero butyniu" [advancement.challenge_rift_50] - title = "Erdves Anomalija" - description = "Teleportuotis 50 kartu" +title = "Erdves Anomalija" +description = "Teleportuotis 50 kartu" [advancement.challenge_rift_500] - title = "Tustumos Klajunas" - description = "Teleportuotis 500 kartu" +title = "Tustumos Klajunas" +description = "Teleportuotis 500 kartu" [advancement.challenge_rift_5k] - title = "Tarp Pasauliu" - description = "Teleportuotis 5 000 kartu" +title = "Tarp Pasauliu" +description = "Teleportuotis 5 000 kartu" [advancement.challenge_taming_10] - title = "Gyvunu Sunebejas" - description = "Dauginti 10 gyvunu" +title = "Gyvunu Sunebejas" +description = "Dauginti 10 gyvunu" [advancement.challenge_taming_50] - title = "Bures Lyderis" - description = "Dauginti 50 gyvunu" +title = "Bures Lyderis" +description = "Dauginti 50 gyvunu" [advancement.challenge_taming_500] - title = "Zveriu Valdovas" - description = "Dauginti 500 gyvunu" +title = "Zveriu Valdovas" +description = "Dauginti 500 gyvunu" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Greicio Demonas" - description = "Nubek daugiau nei 5 Kilometrus (5,000 bloku)" +title = "Greicio Demonas" +description = "Nubek daugiau nei 5 Kilometrus (5,000 bloku)" [advancement.challenge_sprint_dist_50k] - title = "Zaibisku Kojos" - description = "Nubek daugiau nei 50 Kilometru (50,000 bloku)" +title = "Zaibisku Kojos" +description = "Nubek daugiau nei 50 Kilometru (50,000 bloku)" [advancement.challenge_agility_swim_1k] - title = "Vandens Begiklis" - description = "Nuplaukite daugiau nei 1 Kilometra (1,000 bloku)" +title = "Vandens Begiklis" +description = "Nuplaukite daugiau nei 1 Kilometra (1,000 bloku)" [advancement.challenge_agility_swim_10k] - title = "Vandeninis Keliautojas" - description = "Nuplaukite daugiau nei 10 Kilometru (10,000 bloku)" +title = "Vandeninis Keliautojas" +description = "Nuplaukite daugiau nei 10 Kilometru (10,000 bloku)" [advancement.challenge_fly_1k] - title = "Dangaus Sokejas" - description = "Nuskriskite daugiau nei 1 Kilometra (1,000 bloku)" +title = "Dangaus Sokejas" +description = "Nuskriskite daugiau nei 1 Kilometra (1,000 bloku)" [advancement.challenge_fly_10k] - title = "Vejo Raitelis" - description = "Nuskriskite daugiau nei 10 Kilometru (10,000 bloku)" +title = "Vejo Raitelis" +description = "Nuskriskite daugiau nei 10 Kilometru (10,000 bloku)" [advancement.challenge_agility_sneak_500] - title = "Tylius Zingsniai" - description = "Prislinkinite daugiau nei 500 bloku" +title = "Tylius Zingsniai" +description = "Prislinkinite daugiau nei 500 bloku" [advancement.challenge_agility_sneak_5k] - title = "Seseliu Zingsniai" - description = "Prislinkinite daugiau nei 5 Kilometrus (5,000 bloku)" +title = "Seseliu Zingsniai" +description = "Prislinkinite daugiau nei 5 Kilometrus (5,000 bloku)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Griovimo Komanda" - description = "Sunaikinkite 500 bloku" +title = "Griovimo Komanda" +description = "Sunaikinkite 500 bloku" [advancement.challenge_demolish_5k] - title = "Griovimo Rutulys" - description = "Sunaikinkite 5,000 bloku" +title = "Griovimo Rutulys" +description = "Sunaikinkite 5,000 bloku" [advancement.challenge_value_placed_10k] - title = "Vertingas Statytojas" - description = "Pastatykite bloku uz 10,000 vertes" +title = "Vertingas Statytojas" +description = "Pastatykite bloku uz 10,000 vertes" [advancement.challenge_value_placed_100k] - title = "Architekturos Meistras" - description = "Pastatykite bloku uz 100,000 vertes" +title = "Architekturos Meistras" +description = "Pastatykite bloku uz 100,000 vertes" [advancement.challenge_demolish_val_5k] - title = "Utilizavimo Ekspertas" - description = "Isgelbékit 5,000 bloku vertes is griovimo" +title = "Utilizavimo Ekspertas" +description = "Isgelbékit 5,000 bloku vertes is griovimo" [advancement.challenge_demolish_val_50k] - title = "Visiskas Isardymas" - description = "Isgelbékit 50,000 bloku vertes is griovimo" +title = "Visiskas Isardymas" +description = "Isgelbékit 50,000 bloku vertes is griovimo" [advancement.challenge_high_build_100] - title = "Dangaus Statytojas" - description = "Pastatykite 100 bloku virs Y=128" +title = "Dangaus Statytojas" +description = "Pastatykite 100 bloku virs Y=128" [advancement.challenge_high_build_1k] - title = "Debesu Architektas" - description = "Pastatykite 1,000 bloku virs Y=128" +title = "Debesu Architektas" +description = "Pastatykite 1,000 bloku virs Y=128" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Kirviasukys" - description = "Mostelekite kirviu 500 kartu" +title = "Kirviasukys" +description = "Mostelekite kirviu 500 kartu" [advancement.challenge_axe_swing_5k] - title = "Berserkas" - description = "Mostelekite kirviu 5,000 kartu" +title = "Berserkas" +description = "Mostelekite kirviu 5,000 kartu" [advancement.challenge_axe_damage_1k] - title = "Kapotojas" - description = "Padarykite 1,000 zalos kirviu" +title = "Kapotojas" +description = "Padarykite 1,000 zalos kirviu" [advancement.challenge_axe_damage_10k] - title = "Budelio Kirvis" - description = "Padarykite 10,000 zalos kirviu" +title = "Budelio Kirvis" +description = "Padarykite 10,000 zalos kirviu" [advancement.challenge_axe_value_5k] - title = "Medienos Prekeivis" - description = "Surinkite medienos uz 5,000 vertes" +title = "Medienos Prekeivis" +description = "Surinkite medienos uz 5,000 vertes" [advancement.challenge_axe_value_50k] - title = "Medienos Baronas" - description = "Surinkite medienos uz 50,000 vertes" +title = "Medienos Baronas" +description = "Surinkite medienos uz 50,000 vertes" [advancement.challenge_leaves_500] - title = "Lapu Pustejas" - description = "Isvalkite 500 lapu bloku kirviu" +title = "Lapu Pustejas" +description = "Isvalkite 500 lapu bloku kirviu" [advancement.challenge_leaves_5k] - title = "Nulapotojas" - description = "Isvalkite 5,000 lapu bloku kirviu" +title = "Nulapotojas" +description = "Isvalkite 5,000 lapu bloku kirviu" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Zalos Sugerejas" - description = "Uzblokuokite 1,000 zalos skydu" +title = "Zalos Sugerejas" +description = "Uzblokuokite 1,000 zalos skydu" [advancement.challenge_block_dmg_10k] - title = "Gyvas Skydas" - description = "Uzblokuokite 10,000 zalos skydu" +title = "Gyvas Skydas" +description = "Uzblokuokite 10,000 zalos skydu" [advancement.challenge_block_proj_100] - title = "Strelu Atmustojas" - description = "Uzblokuokite 100 sviediniu skydu" +title = "Strelu Atmustojas" +description = "Uzblokuokite 100 sviediniu skydu" [advancement.challenge_block_proj_1k] - title = "Sviediniu Skydas" - description = "Uzblokuokite 1,000 sviediniu skydu" +title = "Sviediniu Skydas" +description = "Uzblokuokite 1,000 sviediniu skydu" [advancement.challenge_block_melee_500] - title = "Atremimu Meistras" - description = "Uzblokuokite 500 artimos kovos ataku skydu" +title = "Atremimu Meistras" +description = "Uzblokuokite 500 artimos kovos ataku skydu" [advancement.challenge_block_melee_5k] - title = "Gelezine Tvirtove" - description = "Uzblokuokite 5,000 artimos kovos ataku skydu" +title = "Gelezine Tvirtove" +description = "Uzblokuokite 5,000 artimos kovos ataku skydu" [advancement.challenge_block_heavy_50] - title = "Tankas" - description = "Uzblokuokite 50 sunkiu ataku (virs 5 zalos)" +title = "Tankas" +description = "Uzblokuokite 50 sunkiu ataku (virs 5 zalos)" [advancement.challenge_block_heavy_500] - title = "Nepajudinamas Objektas" - description = "Uzblokuokite 500 sunkiu ataku (virs 5 zalos)" +title = "Nepajudinamas Objektas" +description = "Uzblokuokite 500 sunkiu ataku (virs 5 zalos)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "Virejo Dirbtuve" - description = "Pastatykite 10 virimo stovu" +title = "Virejo Dirbtuve" +description = "Pastatykite 10 virimo stovu" [advancement.challenge_brew_stands_50] - title = "Eliksyru Fabrikas" - description = "Pastatykite 50 virimo stovu" +title = "Eliksyru Fabrikas" +description = "Pastatykite 50 virimo stovu" [advancement.challenge_brew_strong_25] - title = "Stiprus Gérimas" - description = "Isgerkite 25 pagerintu eliksyru" +title = "Stiprus Gérimas" +description = "Isgerkite 25 pagerintu eliksyru" [advancement.challenge_brew_strong_250] - title = "Maksimalus Stiprumas" - description = "Isgerkite 250 pagerintu eliksyru" +title = "Maksimalus Stiprumas" +description = "Isgerkite 250 pagerintu eliksyru" [advancement.challenge_brew_splash_hits_50] - title = "Purskimo Zona" - description = "Pataikykite i 50 butyniu purskiamaisiais eliksyrais" +title = "Purskimo Zona" +description = "Pataikykite i 50 butyniu purskiamaisiais eliksyrais" [advancement.challenge_brew_splash_hits_500] - title = "Maro Daktaras" - description = "Pataikykite i 500 butyniu purskiamaisiais eliksyrais" +title = "Maro Daktaras" +description = "Pataikykite i 500 butyniu purskiamaisiais eliksyrais" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Neramus" - description = "Nukeliaukite 1 Kilometra budami aktyvus" +title = "Neramus" +description = "Nukeliaukite 1 Kilometra budami aktyvus" [advancement.challenge_active_dist_10k] - title = "Keliu Zinovas" - description = "Nukeliaukite 10 Kilometru budami aktyvus" +title = "Keliu Zinovas" +description = "Nukeliaukite 10 Kilometru budami aktyvus" [advancement.challenge_active_dist_100k] - title = "Amzinas Judéjimas" - description = "Nukeliaukite 100 Kilometru budami aktyvus" +title = "Amzinas Judéjimas" +description = "Nukeliaukite 100 Kilometru budami aktyvus" [advancement.challenge_beds_10] - title = "Ankstyvasis Paukstis" - description = "Miegokite lovoje 10 kartu" +title = "Ankstyvasis Paukstis" +description = "Miegokite lovoje 10 kartu" [advancement.challenge_beds_100] - title = "Laiko Praleidinétojas" - description = "Miegokite lovoje 100 kartu" +title = "Laiko Praleidinétojas" +description = "Miegokite lovoje 100 kartu" [advancement.challenge_chronos_tp_50] - title = "Laiko Poslinkis" - description = "Teleportuokités 50 kartu" +title = "Laiko Poslinkis" +description = "Teleportuokités 50 kartu" [advancement.challenge_chronos_tp_500] - title = "Laiko Iskreipimas" - description = "Teleportuokités 500 kartu" +title = "Laiko Iskreipimas" +description = "Teleportuokités 500 kartu" [advancement.challenge_chronos_deaths_10] - title = "Mirtingasis" - description = "Mirkite 10 kartu" +title = "Mirtingasis" +description = "Mirkite 10 kartu" [advancement.challenge_chronos_deaths_100] - title = "Mirties Isskukejas" - description = "Mirkite 100 kartu" +title = "Mirties Isskukejas" +description = "Mirkite 100 kartu" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Amatininko Verte" - description = "Pagaminkite daiktu uz 10,000 bendros vertes" +title = "Amatininko Verte" +description = "Pagaminkite daiktu uz 10,000 bendros vertes" [advancement.challenge_craft_value_100k] - title = "Meistras Amatininkas" - description = "Pagaminkite daiktu uz 100,000 bendros vertes" +title = "Meistras Amatininkas" +description = "Pagaminkite daiktu uz 100,000 bendros vertes" [advancement.challenge_craft_tools_25] - title = "Irankiu Kalvis" - description = "Pagaminkite 25 irankes" +title = "Irankiu Kalvis" +description = "Pagaminkite 25 irankes" [advancement.challenge_craft_tools_250] - title = "Kalves Meistras" - description = "Pagaminkite 250 irankes" +title = "Kalves Meistras" +description = "Pagaminkite 250 irankes" [advancement.challenge_craft_armor_25] - title = "Sarviu Kalvis" - description = "Pagaminkite 25 sarvu dalis" +title = "Sarviu Kalvis" +description = "Pagaminkite 25 sarvu dalis" [advancement.challenge_craft_armor_250] - title = "Sarviu Meistras" - description = "Pagaminkite 250 sarvu dalis" +title = "Sarviu Meistras" +description = "Pagaminkite 250 sarvu dalis" [advancement.challenge_craft_mass_25k] - title = "Masine Gamyba" - description = "Pagaminkite 25,000 daiktu" +title = "Masine Gamyba" +description = "Pagaminkite 25,000 daiktu" [advancement.challenge_craft_mass_250k] - title = "Pramonine Revoliucija" - description = "Pagaminkite 250,000 daiktu" +title = "Pramonine Revoliucija" +description = "Pagaminkite 250,000 daiktu" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Rinkejas" - description = "Atraskite 50 unikalu daiktu" +title = "Rinkejas" +description = "Atraskite 50 unikalu daiktu" [advancement.challenge_discover_items_250] - title = "Kataloguotojas" - description = "Atraskite 250 unikalu daiktu" +title = "Kataloguotojas" +description = "Atraskite 250 unikalu daiktu" [advancement.challenge_discover_blocks_50] - title = "Tyrinetoja" - description = "Atraskite 50 unikalu bloku" +title = "Tyrinetoja" +description = "Atraskite 50 unikalu bloku" [advancement.challenge_discover_blocks_250] - title = "Geologas" - description = "Atraskite 250 unikalu bloku" +title = "Geologas" +description = "Atraskite 250 unikalu bloku" [advancement.challenge_discover_mobs_25] - title = "Stebetojas" - description = "Atraskite 25 unikalias butybes" +title = "Stebetojas" +description = "Atraskite 25 unikalias butybes" [advancement.challenge_discover_mobs_75] - title = "Gamtininkas" - description = "Atraskite 75 unikalias butybes" +title = "Gamtininkas" +description = "Atraskite 75 unikalias butybes" [advancement.challenge_discover_biomes_10] - title = "Klajoklys" - description = "Atraskite 10 unikalu biomu" +title = "Klajoklys" +description = "Atraskite 10 unikalu biomu" [advancement.challenge_discover_biomes_40] - title = "Pasaulio Keliautojas" - description = "Atraskite 40 unikalu biomu" +title = "Pasaulio Keliautojas" +description = "Atraskite 40 unikalu biomu" [advancement.challenge_discover_foods_10] - title = "Gurmantas" - description = "Atraskite 10 unikaliu maisto produktu" +title = "Gurmantas" +description = "Atraskite 10 unikaliu maisto produktu" [advancement.challenge_discover_foods_30] - title = "Kulinarijos Meistras" - description = "Atraskite 30 unikaliu maisto produktu" +title = "Kulinarijos Meistras" +description = "Atraskite 30 unikaliu maisto produktu" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Galios Audéjas" - description = "Sukaupkite 100 uzkerejimo galios" +title = "Galios Audéjas" +description = "Sukaupkite 100 uzkerejimo galios" [advancement.challenge_enchant_power_1k] - title = "Magijos Meistras" - description = "Sukaupkite 1,000 uzkerejimo galios" +title = "Magijos Meistras" +description = "Sukaupkite 1,000 uzkerejimo galios" [advancement.challenge_enchant_levels_1k] - title = "Lygiu Leidejas" - description = "Isleiskite 1,000 patirties lygiu uzkerejimams" +title = "Lygiu Leidejas" +description = "Isleiskite 1,000 patirties lygiu uzkerejimams" [advancement.challenge_enchant_levels_10k] - title = "XP Ryklys" - description = "Isleiskite 10,000 patirties lygiu uzkerejimams" +title = "XP Ryklys" +description = "Isleiskite 10,000 patirties lygiu uzkerejimams" [advancement.challenge_enchant_high_25] - title = "Aukstu Statymu Zaidejas" - description = "Atlikite 25 maksimalaus lygio uzkerejimus" +title = "Aukstu Statymu Zaidejas" +description = "Atlikite 25 maksimalaus lygio uzkerejimus" [advancement.challenge_enchant_high_250] - title = "Legendinis Uzkeretoja" - description = "Atlikite 250 maksimalaus lygio uzkerejimus" +title = "Legendinis Uzkeretoja" +description = "Atlikite 250 maksimalaus lygio uzkerejimus" [advancement.challenge_enchant_total_500] - title = "Lygiu Degintoja" - description = "Isleiskite 500 viso lygiu visiems uzkerejimams" +title = "Lygiu Degintoja" +description = "Isleiskite 500 viso lygiu visiems uzkerejimams" [advancement.challenge_enchant_total_5k] - title = "Magine Investicija" - description = "Isleiskite 5,000 viso lygiu visiems uzkerejimams" +title = "Magine Investicija" +description = "Isleiskite 5,000 viso lygiu visiems uzkerejimams" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Kasejas" - description = "Mostelekite kastuvu 500 kartu" +title = "Kasejas" +description = "Mostelekite kastuvu 500 kartu" [advancement.challenge_dig_swing_5k] - title = "Ekskavatorius" - description = "Mostelekite kastuvu 5,000 kartu" +title = "Ekskavatorius" +description = "Mostelekite kastuvu 5,000 kartu" [advancement.challenge_dig_damage_1k] - title = "Kastuvo Riteris" - description = "Padarykite 1,000 zalos kastuvu" +title = "Kastuvo Riteris" +description = "Padarykite 1,000 zalos kastuvu" [advancement.challenge_dig_damage_10k] - title = "Kastuvo Meistras" - description = "Padarykite 10,000 zalos kastuvu" +title = "Kastuvo Meistras" +description = "Padarykite 10,000 zalos kastuvu" [advancement.challenge_dig_value_5k] - title = "Purvo Prekeivis" - description = "Iskaskite bloku uz 5,000 vertes" +title = "Purvo Prekeivis" +description = "Iskaskite bloku uz 5,000 vertes" [advancement.challenge_dig_value_50k] - title = "Zemes Baronas" - description = "Iskaskite bloku uz 50,000 vertes" +title = "Zemes Baronas" +description = "Iskaskite bloku uz 50,000 vertes" [advancement.challenge_dig_gravel_500] - title = "Zvirgzdo Malimas" - description = "Iskaskite 500 zvirgzdo, smelo ar molio bloku" +title = "Zvirgzdo Malimas" +description = "Iskaskite 500 zvirgzdo, smelo ar molio bloku" [advancement.challenge_dig_gravel_5k] - title = "Smelo Sijotuvas" - description = "Iskaskite 5,000 zvirgzdo, smelo ar molio bloku" +title = "Smelo Sijotuvas" +description = "Iskaskite 5,000 zvirgzdo, smelo ar molio bloku" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Séjéjas" - description = "Pasodinkite 100 paselu" +title = "Séjéjas" +description = "Pasodinkite 100 paselu" [advancement.challenge_plant_1k] - title = "Zaliasis Pirstas" - description = "Pasodinkite 1,000 paselu" +title = "Zaliasis Pirstas" +description = "Pasodinkite 1,000 paselu" [advancement.challenge_plant_5k] - title = "Zemes Ukio Baronas" - description = "Pasodinkite 5,000 paselu" +title = "Zemes Ukio Baronas" +description = "Pasodinkite 5,000 paselu" [advancement.challenge_compost_50] - title = "Perdirbéjas" - description = "Kompostuokite 50 daiktu" +title = "Perdirbéjas" +description = "Kompostuokite 50 daiktu" [advancement.challenge_compost_500] - title = "Dirvos Turtintojas" - description = "Kompostuokite 500 daiktu" +title = "Dirvos Turtintojas" +description = "Kompostuokite 500 daiktu" [advancement.challenge_shear_50] - title = "Kirpéjas" - description = "Apkirpkite 50 butybes" +title = "Kirpéjas" +description = "Apkirpkite 50 butybes" [advancement.challenge_shear_250] - title = "Bandos Valdovas" - description = "Apkirpkite 250 butybes" +title = "Bandos Valdovas" +description = "Apkirpkite 250 butybes" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Zabikas" - description = "Uzmuskit 500 padaru" +title = "Zabikas" +description = "Uzmuskit 500 padaru" [advancement.challenge_kills_5k] - title = "Egzekutorius" - description = "Uzmuskit 5,000 padaru" +title = "Egzekutorius" +description = "Uzmuskit 5,000 padaru" [advancement.challenge_boss_1] - title = "Boso Issaukejas" - description = "Uzmuskit bosa" +title = "Boso Issaukejas" +description = "Uzmuskit bosa" [advancement.challenge_boss_10] - title = "Legendu Zabikas" - description = "Uzmuskit 10 bosu" +title = "Legendu Zabikas" +description = "Uzmuskit 10 bosu" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Nuvytes" - description = "Isgyvenkit 500 nuvytimo zalos" +title = "Nuvytes" +description = "Isgyvenkit 500 nuvytimo zalos" [advancement.challenge_wither_dmg_5k] - title = "Raupus Isgyvenes" - description = "Isgyvenkit 5,000 nuvytimo zalos" +title = "Raupus Isgyvenes" +description = "Isgyvenkit 5,000 nuvytimo zalos" [advancement.challenge_wither_skel_25] - title = "Kaulu Rinkejas" - description = "Uzmuskit 25 viterinius skeletus" +title = "Kaulu Rinkejas" +description = "Uzmuskit 25 viterinius skeletus" [advancement.challenge_wither_skel_250] - title = "Skeletu Riksté" - description = "Uzmuskit 250 viteriniu skeletu" +title = "Skeletu Riksté" +description = "Uzmuskit 250 viteriniu skeletu" [advancement.challenge_wither_boss_1] - title = "Viterio Zabikas" - description = "Nugalekit Viteri" +title = "Viterio Zabikas" +description = "Nugalekit Viteri" [advancement.challenge_wither_boss_10] - title = "Nethero Valdovas" - description = "Nugalekit Viteri 10 kartu" +title = "Nethero Valdovas" +description = "Nugalekit Viteri 10 kartu" [advancement.challenge_roses_10] - title = "Mirties Sodininkas" - description = "Sunaikinkite 10 viterio roziu" +title = "Mirties Sodininkas" +description = "Sunaikinkite 10 viterio roziu" [advancement.challenge_roses_100] - title = "Raupus Rinkejas" - description = "Sunaikinkite 100 viterio roziu" +title = "Raupus Rinkejas" +description = "Sunaikinkite 100 viterio roziu" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Kalnakasio Ranka" - description = "Mostelekite kastuvu 500 kartu" +title = "Kalnakasio Ranka" +description = "Mostelekite kastuvu 500 kartu" [advancement.challenge_pick_swing_5k] - title = "Tuneliu Kasejas" - description = "Mostelekite kastuvu 5,000 kartu" +title = "Tuneliu Kasejas" +description = "Mostelekite kastuvu 5,000 kartu" [advancement.challenge_pick_damage_1k] - title = "Kastuvo Kovotojas" - description = "Padarykite 1,000 zalos kastuvu" +title = "Kastuvo Kovotojas" +description = "Padarykite 1,000 zalos kastuvu" [advancement.challenge_pick_damage_10k] - title = "Kovinis Kastuvas" - description = "Padarykite 10,000 zalos kastuvu" +title = "Kovinis Kastuvas" +description = "Padarykite 10,000 zalos kastuvu" [advancement.challenge_pick_value_5k] - title = "Brangakmeniu Ieškotojas" - description = "Iskaskite bloku uz 5,000 vertes" +title = "Brangakmeniu Ieškotojas" +description = "Iskaskite bloku uz 5,000 vertes" [advancement.challenge_pick_value_50k] - title = "Rudos Baronas" - description = "Iskaskite bloku uz 50,000 vertes" +title = "Rudos Baronas" +description = "Iskaskite bloku uz 50,000 vertes" [advancement.challenge_pick_ores_500] - title = "Zvalgas" - description = "Iskaskite 500 rudos bloku" +title = "Zvalgas" +description = "Iskaskite 500 rudos bloku" [advancement.challenge_pick_ores_5k] - title = "Kasyklu Meistras" - description = "Iskaskite 5,000 rudos bloku" +title = "Kasyklu Meistras" +description = "Iskaskite 5,000 rudos bloku" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Taiklus Saulys" - description = "Padarykite 1,000 nuotolines zalos" +title = "Taiklus Saulys" +description = "Padarykite 1,000 nuotolines zalos" [advancement.challenge_ranged_dmg_10k] - title = "Mirtinas Lankininkas" - description = "Padarykite 10,000 nuotolines zalos" +title = "Mirtinas Lankininkas" +description = "Padarykite 10,000 nuotolines zalos" [advancement.challenge_ranged_dist_5k] - title = "Tolimas Nuotolis" - description = "Paleiskite sviedinius 5,000 bloku bendru atstumu" +title = "Tolimas Nuotolis" +description = "Paleiskite sviedinius 5,000 bloku bendru atstumu" [advancement.challenge_ranged_dist_50k] - title = "Mylios Saulys" - description = "Paleiskite sviedinius 50,000 bloku bendru atstumu" +title = "Mylios Saulys" +description = "Paleiskite sviedinius 50,000 bloku bendru atstumu" [advancement.challenge_ranged_kills_50] - title = "Lankininkas" - description = "Uzmuskit 50 butybes nuotoliniais ginklais" +title = "Lankininkas" +description = "Uzmuskit 50 butybes nuotoliniais ginklais" [advancement.challenge_ranged_kills_500] - title = "Lanku Meistras" - description = "Uzmuskit 500 butybes nuotoliniais ginklais" +title = "Lanku Meistras" +description = "Uzmuskit 500 butybes nuotoliniais ginklais" [advancement.challenge_longshot_25] - title = "Snaiperis" - description = "Pataikykite 25 tolimo nuotolio suvisus (per 30 bloku)" +title = "Snaiperis" +description = "Pataikykite 25 tolimo nuotolio suvisus (per 30 bloku)" [advancement.challenge_longshot_250] - title = "Erelinis Akis" - description = "Pataikykite 250 tolimo nuotolio suvisus (per 30 bloku)" +title = "Erelinis Akis" +description = "Pataikykite 250 tolimo nuotolio suvisus (per 30 bloku)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "Perliu Metykas" - description = "Meskite 50 enderio perlu" +title = "Perliu Metykas" +description = "Meskite 50 enderio perlu" [advancement.challenge_rift_pearls_500] - title = "Teleportacijos Manijakas" - description = "Meskite 500 enderio perlu" +title = "Teleportacijos Manijakas" +description = "Meskite 500 enderio perlu" [advancement.challenge_rift_enderman_50] - title = "Endermenu Medžiotojas" - description = "Uzmuskit 50 endermenu" +title = "Endermenu Medžiotojas" +description = "Uzmuskit 50 endermenu" [advancement.challenge_rift_enderman_500] - title = "Tustumos Seklys" - description = "Uzmuskit 500 endermenu" +title = "Tustumos Seklys" +description = "Uzmuskit 500 endermenu" [advancement.challenge_rift_dragon_500] - title = "Drakonu Kovotojas" - description = "Padarykite 500 zalos Enderio Drakonui" +title = "Drakonu Kovotojas" +description = "Padarykite 500 zalos Enderio Drakonui" [advancement.challenge_rift_dragon_5k] - title = "Drakono Riksté" - description = "Padarykite 5,000 zalos Enderio Drakonui" +title = "Drakono Riksté" +description = "Padarykite 5,000 zalos Enderio Drakonui" [advancement.challenge_rift_crystal_10] - title = "Kristalu Lauzytoja" - description = "Sunaikinkite 10 enderio kristalu" +title = "Kristalu Lauzytoja" +description = "Sunaikinkite 10 enderio kristalu" [advancement.challenge_rift_crystal_100] - title = "Endo Griovéjas" - description = "Sunaikinkite 100 enderio kristalu" +title = "Endo Griovéjas" +description = "Sunaikinkite 100 enderio kristalu" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Zvejys" - description = "Pagaukite 25 zuvis" +title = "Zvejys" +description = "Pagaukite 25 zuvis" [advancement.challenge_fish_250] - title = "Zvejybos Meistras" - description = "Pagaukite 250 zuviu" +title = "Zvejybos Meistras" +description = "Pagaukite 250 zuviu" [advancement.challenge_drowned_25] - title = "Skandintu Mediotojas" - description = "Uzmuskit 25 skandintus" +title = "Skandintu Mediotojas" +description = "Uzmuskit 25 skandintus" [advancement.challenge_drowned_250] - title = "Vandenyno Valytojas" - description = "Uzmuskit 250 skandintu" +title = "Vandenyno Valytojas" +description = "Uzmuskit 250 skandintu" [advancement.challenge_guardian_10] - title = "Sargybinu Zabikas" - description = "Uzmuskit 10 sargybiniu" +title = "Sargybinu Zabikas" +description = "Uzmuskit 10 sargybiniu" [advancement.challenge_guardian_100] - title = "Sventyklos Plesikas" - description = "Uzmuskit 100 sargybiniu" +title = "Sventyklos Plesikas" +description = "Uzmuskit 100 sargybiniu" [advancement.challenge_underwater_blocks_100] - title = "Povandeninis Kasyklis" - description = "Sunaikinkite 100 bloku po vandeniu" +title = "Povandeninis Kasyklis" +description = "Sunaikinkite 100 bloku po vandeniu" [advancement.challenge_underwater_blocks_1k] - title = "Vandens Inzinierius" - description = "Sunaikinkite 1,000 bloku po vandeniu" +title = "Vandens Inzinierius" +description = "Sunaikinkite 1,000 bloku po vandeniu" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Nugaros Durtuvas" - description = "Padarykite 500 zalos slinkinantis" +title = "Nugaros Durtuvas" +description = "Padarykite 500 zalos slinkinantis" [advancement.challenge_stealth_dmg_5k] - title = "Tylus Zabikas" - description = "Padarykite 5,000 zalos slinkinantis" +title = "Tylus Zabikas" +description = "Padarykite 5,000 zalos slinkinantis" [advancement.challenge_stealth_kills_10] - title = "Zudikis" - description = "Uzmuskit 10 butybes slinkinantis" +title = "Zudikis" +description = "Uzmuskit 10 butybes slinkinantis" [advancement.challenge_stealth_kills_100] - title = "Seseliu Pjovéjas" - description = "Uzmuskit 100 butybes slinkinantis" +title = "Seseliu Pjovéjas" +description = "Uzmuskit 100 butybes slinkinantis" [advancement.challenge_stealth_time_1h] - title = "Kantriai" - description = "Praleiskite 1 valanda slinkinant (3,600 sekundziu)" +title = "Kantriai" +description = "Praleiskite 1 valanda slinkinant (3,600 sekundziu)" [advancement.challenge_stealth_time_10h] - title = "Seseliu Meistras" - description = "Praleiskite 10 valandu slinkinant (36,000 sekundziu)" +title = "Seseliu Meistras" +description = "Praleiskite 10 valandu slinkinant (36,000 sekundziu)" [advancement.challenge_stealth_arrows_50] - title = "Tylus Lankininkas" - description = "Paleiskite 50 strelu slinkinantis" +title = "Tylus Lankininkas" +description = "Paleiskite 50 strelu slinkinantis" [advancement.challenge_stealth_arrows_500] - title = "Vaidulaklio Lankininkas" - description = "Paleiskite 500 strelu slinkinantis" +title = "Vaidulaklio Lankininkas" +description = "Paleiskite 500 strelu slinkinantis" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Kalavijo Mokinys" - description = "Padarykite 1,000 zalos kalaviju" +title = "Kalavijo Mokinys" +description = "Padarykite 1,000 zalos kalaviju" [advancement.challenge_sword_dmg_10k] - title = "Kalavijininkas" - description = "Padarykite 10,000 zalos kalaviju" +title = "Kalavijininkas" +description = "Padarykite 10,000 zalos kalaviju" [advancement.challenge_sword_kills_50] - title = "Dvikovejas" - description = "Uzmuskit 50 butybes kalaviju" +title = "Dvikovejas" +description = "Uzmuskit 50 butybes kalaviju" [advancement.challenge_sword_kills_500] - title = "Gladiatorius" - description = "Uzmuskit 500 butybes kalaviju" +title = "Gladiatorius" +description = "Uzmuskit 500 butybes kalaviju" [advancement.challenge_sword_crit_50] - title = "Kritinis Smugis" - description = "Pataikykite 50 kritiniu smugiu kalaviju" +title = "Kritinis Smugis" +description = "Pataikykite 50 kritiniu smugiu kalaviju" [advancement.challenge_sword_crit_500] - title = "Tikslumo Meistras" - description = "Pataikykite 500 kritiniu smugiu kalaviju" +title = "Tikslumo Meistras" +description = "Pataikykite 500 kritiniu smugiu kalaviju" [advancement.challenge_sword_heavy_25] - title = "Sunkus Smugis" - description = "Pataikykite 25 sunkiu smugiu kalaviju (virs 8 zalos)" +title = "Sunkus Smugis" +description = "Pataikykite 25 sunkiu smugiu kalaviju (virs 8 zalos)" [advancement.challenge_sword_heavy_250] - title = "Niokojantis Smugis" - description = "Pataikykite 250 sunkiu smugiu kalaviju (virs 8 zalos)" +title = "Niokojantis Smugis" +description = "Pataikykite 250 sunkiu smugiu kalaviju (virs 8 zalos)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Zveriu Dresuotojas" - description = "Jusu augintiniu bendrai padaryta 500 zalos" +title = "Zveriu Dresuotojas" +description = "Jusu augintiniu bendrai padaryta 500 zalos" [advancement.challenge_pet_dmg_5k] - title = "Karo Vadovas" - description = "Jusu augintiniu bendrai padaryta 5,000 zalos" +title = "Karo Vadovas" +description = "Jusu augintiniu bendrai padaryta 5,000 zalos" [advancement.challenge_tamed_10] - title = "Gyvunu Draugas" - description = "Prisijaukinkite 10 gyvunu" +title = "Gyvunu Draugas" +description = "Prisijaukinkite 10 gyvunu" [advancement.challenge_tamed_100] - title = "Zoologijos Sodo Priziuretojas" - description = "Prisijaukinkite 100 gyvunu" +title = "Zoologijos Sodo Priziuretojas" +description = "Prisijaukinkite 100 gyvunu" [advancement.challenge_pet_kills_25] - title = "Bandos Taktika" - description = "Jusu augintiniai uzmusé 25 padarus" +title = "Bandos Taktika" +description = "Jusu augintiniai uzmusé 25 padarus" [advancement.challenge_pet_kills_250] - title = "Alfos Vadas" - description = "Jusu augintiniai uzmusé 250 padaru" +title = "Alfos Vadas" +description = "Jusu augintiniai uzmusé 250 padaru" [advancement.challenge_taming_2500] - title = "Veisimo Ekspertas" - description = "Paveiskite 2,500 gyvunu" +title = "Veisimo Ekspertas" +description = "Paveiskite 2,500 gyvunu" [advancement.challenge_taming_25k] - title = "Genetikos Meistras" - description = "Paveiskite 25,000 gyvunu" +title = "Genetikos Meistras" +description = "Paveiskite 25,000 gyvunu" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Musimo Maisas" - description = "Gaukite 500 smugiu" +title = "Musimo Maisas" +description = "Gaukite 500 smugiu" [advancement.challenge_trag_hits_5k] - title = "Kanciniu Megejas" - description = "Gaukite 5,000 smugiu" +title = "Kanciniu Megejas" +description = "Gaukite 5,000 smugiu" [advancement.challenge_trag_deaths_10] - title = "Devyni Gyvenimai" - description = "Mirkite 10 kartu" +title = "Devyni Gyvenimai" +description = "Mirkite 10 kartu" [advancement.challenge_trag_deaths_100] - title = "Pasikartojantis Kosmeras" - description = "Mirkite 100 kartu" +title = "Pasikartojantis Kosmeras" +description = "Mirkite 100 kartu" [advancement.challenge_trag_fire_500] - title = "Ugnis Auka" - description = "Isgyvenkit 500 ugnies zalos" +title = "Ugnis Auka" +description = "Isgyvenkit 500 ugnies zalos" [advancement.challenge_trag_fire_5k] - title = "Feniksas" - description = "Isgyvenkit 5,000 ugnies zalos" +title = "Feniksas" +description = "Isgyvenkit 5,000 ugnies zalos" [advancement.challenge_trag_fall_500] - title = "Gravitacijos Patikrinimas" - description = "Isgyvenkit 500 kritimo zalos" +title = "Gravitacijos Patikrinimas" +description = "Isgyvenkit 500 kritimo zalos" [advancement.challenge_trag_fall_5k] - title = "Galutinis Greitis" - description = "Isgyvenkit 5,000 kritimo zalos" +title = "Galutinis Greitis" +description = "Isgyvenkit 5,000 kritimo zalos" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Mustynes" - description = "Padarykite 1,000 zalos plikemis rankomis" +title = "Mustynes" +description = "Padarykite 1,000 zalos plikemis rankomis" [advancement.challenge_unarmed_dmg_10k] - title = "Koviniu Menu Meistras" - description = "Padarykite 10,000 zalos plikemis rankomis" +title = "Koviniu Menu Meistras" +description = "Padarykite 10,000 zalos plikemis rankomis" [advancement.challenge_unarmed_kills_25] - title = "Plikos Kumstys" - description = "Uzmuskit 25 butybes plikemis rankomis" +title = "Plikos Kumstys" +description = "Uzmuskit 25 butybes plikemis rankomis" [advancement.challenge_unarmed_kills_250] - title = "Legendos Kumstis" - description = "Uzmuskit 250 butybes plikemis rankomis" +title = "Legendos Kumstis" +description = "Uzmuskit 250 butybes plikemis rankomis" [advancement.challenge_unarmed_crit_25] - title = "Kritinis Smugis Kumstimi" - description = "Pataikykite 25 kritiniu smugiu plikemis rankomis" +title = "Kritinis Smugis Kumstimi" +description = "Pataikykite 25 kritiniu smugiu plikemis rankomis" [advancement.challenge_unarmed_crit_250] - title = "Tiksli Kumstis" - description = "Pataikykite 250 kritiniu smugiu plikemis rankomis" +title = "Tiksli Kumstis" +description = "Pataikykite 250 kritiniu smugiu plikemis rankomis" [advancement.challenge_unarmed_heavy_25] - title = "Galingas Smugis" - description = "Pataikykite 25 sunkiu smugiu plikemis rankomis (virs 6 zalos)" +title = "Galingas Smugis" +description = "Pataikykite 25 sunkiu smugiu plikemis rankomis (virs 6 zalos)" [advancement.challenge_unarmed_heavy_250] - title = "Nokautu Karalius" - description = "Pataikykite 250 sunkiu smugiu plikemis rankomis (virs 6 zalos)" +title = "Nokautu Karalius" +description = "Pataikykite 250 sunkiu smugiu plikemis rankomis (virs 6 zalos)" # items [items] [items.bound_ender_peral] - name = "Relikvijoriaus Prievado Raktas" - usage1 = "Shift + Kairys paspaudimas, kad susieti" - usage2 = "Desinys paspaudimas, kad pasiektumete susieta inventoriu" +name = "Relikvijoriaus Prievado Raktas" +usage1 = "Shift + Kairys paspaudimas, kad susieti" +usage2 = "Desinys paspaudimas, kad pasiektumete susieta inventoriu" [items.bound_eye_of_ender] - name = "Akiu Inkaras" - usage1 = "Desinys Paspaudimas, kad vartoti ir teleportuotis i susieta vieta" - usage2 = "Shift + Kairys paspaudimas, kad susieti prie bloko" +name = "Akiu Inkaras" +usage1 = "Desinys Paspaudimas, kad vartoti ir teleportuotis i susieta vieta" +usage2 = "Shift + Kairys paspaudimas, kad susieti prie bloko" [items.bound_redstone_torch] - name = "Raudonakmenio Valdymo Pultas" - usage1 = "Desinys Paspaudimas, kad sukurti 1-tick raudonakmenio impulsa" - usage2 = "Shift + Kairys paspaudimas ant tikslaus bloko, kad butu galima susieti" +name = "Raudonakmenio Valdymo Pultas" +usage1 = "Desinys Paspaudimas, kad sukurti 1-tick raudonakmenio impulsa" +usage2 = "Shift + Kairys paspaudimas ant tikslaus bloko, kad butu galima susieti" [items.bound_snowball] - name = "Voratinkliu Spastai!" - usage1 = "Meskite, kad vietoje sukurtumete laikinus voratinkliu spastus" +name = "Voratinkliu Spastai!" +usage1 = "Meskite, kad vietoje sukurtumete laikinus voratinkliu spastus" [items.chrono_time_bottle] - name = "Laikas Butelyje" - usage1 = "Pasyviai kaupia laika buredamas jusu inventoriuje" - usage2 = "Desinys paspaudimas ant laiko bloku ar mazyliu gyvunu, kad isnaudotumete sukapta laika" - stored = "Sukauptas Laikas" +name = "Laikas Butelyje" +usage1 = "Pasyviai kaupia laika buredamas jusu inventoriuje" +usage2 = "Desinys paspaudimas ant laiko bloku ar mazyliu gyvunu, kad isnaudotumete sukapta laika" +stored = "Sukauptas Laikas" [items.chrono_time_bomb] - name = "Laiko Bomba" - usage1 = "Desinys paspaudimas, kad paleistumete chrono sviedini, kuris sukuria laikini lauka" +name = "Laiko Bomba" +usage1 = "Desinys paspaudimas, kad paleistumete chrono sviedini, kuris sukuria laikini lauka" [items.elevator_block] - name = "Lifto Blokas" - usage1 = "Persokti, kad teleportuotis aukstyn" - usage2 = "Shift, kad teleportuotumetes zemyn" - usage3 = "Maziausiai 2 oro blokai tarp liftu" +name = "Lifto Blokas" +usage1 = "Persokti, kad teleportuotis aukstyn" +usage2 = "Shift, kad teleportuotumetes zemyn" +usage3 = "Maziausiai 2 oro blokai tarp liftu" # snippets [snippets] [snippets.gui] - level = "Lygis" - knowledge = "zinios" - power_used = "Galios" - not_learned = "Neismokta" - xp = "XP iki" - welcome = "Sveiki!" - welcome_back = "Sveiki sugriže!" - xp_bonus_for_time = "XP uz" - max_ability_power = "Didziausia Gebejimu Galia" - unlock_this_by_clicking = "Atrakinkite tai paspausdami desiniu peles mygtuku: " - back = "Grizti" - unlearn_all = "Pamirsti viska" - unlearned_all = "Viskas pamirsta" +level = "Lygis" +knowledge = "zinios" +power_used = "Galios" +not_learned = "Neismokta" +xp = "XP iki" +welcome = "Sveiki!" +welcome_back = "Sveiki sugriže!" +xp_bonus_for_time = "XP uz" +max_ability_power = "Didziausia Gebejimu Galia" +unlock_this_by_clicking = "Atrakinkite tai paspausdami desiniu peles mygtuku: " +back = "Grizti" +unlearn_all = "Pamirsti viska" +unlearned_all = "Viskas pamirsta" [snippets.adapt_menu] - may_not_unlearn = "JUS NEGALESITE PAMIRSTI" - may_unlearn = "GALITE ISMOKTI/PAMIRSTI" - knowledge_cost = "Ziniu kaina" - knowledge_available = "Zinios Prieinama" - already_learned = "Jau ismokta" - unlearn_refund = "Spausk, kad pamirstum & Atlygintu" - no_refunds = "HARDCORE, GRAZINIMAS ISJUNGTAS" - knowledge = "ziniu" - click_learn = "Spausk, kad ismoktum" - no_knowledge = "(Jus neturite pakankamai ziniu)" - you_only_have = "Tu turi tik" - how_to_level_up = "Pasikelkite igudzius, kad padidintumete maksimalia galia." - not_enough_power = "Nepakanka galios! Kiekvienas Gebejimu lygis kainuoja 1 galia." - power = "galia" - power_drain = "Galios Eikvavimas" - learned = "Ismokta " - unlearned = "Pamirsta " - activator_block = "Knygu Lentyna" +may_not_unlearn = "JUS NEGALESITE PAMIRSTI" +may_unlearn = "GALITE ISMOKTI/PAMIRSTI" +knowledge_cost = "Ziniu kaina" +knowledge_available = "Zinios Prieinama" +already_learned = "Jau ismokta" +unlearn_refund = "Spausk, kad pamirstum & Atlygintu" +no_refunds = "HARDCORE, GRAZINIMAS ISJUNGTAS" +knowledge = "ziniu" +click_learn = "Spausk, kad ismoktum" +no_knowledge = "(Jus neturite pakankamai ziniu)" +you_only_have = "Tu turi tik" +how_to_level_up = "Pasikelkite igudzius, kad padidintumete maksimalia galia." +not_enough_power = "Nepakanka galios! Kiekvienas Gebejimu lygis kainuoja 1 galia." +power = "galia" +power_drain = "Galios Eikvavimas" +learned = "Ismokta " +unlearned = "Pamirsta " +activator_block = "Knygu Lentyna" [snippets.knowledge_orb] - contains = "yra" - knowledge = "ziniu" - rightclick = "Desinys-paspaudimas" - togainknowledge = ", kad igyti siu ziniu" - knowledge_orb = "Ziniu Rutulys" +contains = "yra" +knowledge = "ziniu" +rightclick = "Desinys-paspaudimas" +togainknowledge = ", kad igyti siu ziniu" +knowledge_orb = "Ziniu Rutulys" [snippets.experience_orb] - contains = "yra" - xp = "Patirtis" - rightclick = "Desinys-paspaudimas" - togainxp = "igyti sios patirties" - xporb = "Patirties Rutulys" +contains = "yra" +xp = "Patirtis" +rightclick = "Desinys-paspaudimas" +togainxp = "igyti sios patirties" +xporb = "Patirties Rutulys" # skill [skill] [skill.agility] - name = "Judrumas" - icon = "⇉" - description = "Judrumas - tai gebejimas greitai ir sklandziai judeti susiduris su kliutimis." +name = "Judrumas" +icon = "⇉" +description = "Judrumas - tai gebejimas greitai ir sklandziai judeti susiduris su kliutimis." [skill.architect] - name = "Architektas" - icon = "⬧" - description = "Strukturos yra pasaulio statybiniai blokai. Realybe yra jusu rankose, jus turite tai valdyti." +name = "Architektas" +icon = "⬧" +description = "Strukturos yra pasaulio statybiniai blokai. Realybe yra jusu rankose, jus turite tai valdyti." [skill.axes] - name = "Medkirtyste" - icon = "🪓" - description1 = "Kam kirsti medzius, kai gali kirsti " - description2 = "dalykus" - description3 = "vietoj to vistiek tas pats dalykas!" +name = "Medkirtyste" +icon = "🪓" +description1 = "Kam kirsti medzius, kai gali kirsti " +description2 = "dalykus" +description3 = "vietoj to vistiek tas pats dalykas!" [skill.brewing] - name = "Vyrimas" - icon = "❦" - description = "Dvigubas burbulas, Trigubas burbulas, Keturgubas burbulas - as vis tiek dar negaliu ipilti sio gerimo i katila" +name = "Vyrimas" +icon = "❦" +description = "Dvigubas burbulas, Trigubas burbulas, Keturgubas burbulas - as vis tiek dar negaliu ipilti sio gerimo i katila" [skill.blocking] - name = "Blokavimas" - icon = "🛡" - description = "Lazdos ir akmenys tau kaulu nesulauzys, bet skydas sulauzys." +name = "Blokavimas" +icon = "🛡" +description = "Lazdos ir akmenys tau kaulu nesulauzys, bet skydas sulauzys." [skill.crafting] - name = "Gamyba" - icon = "⌂" - description = "Jei nebeliko resursu, kuriuos butu galima ideti, kodel gi nepadarius kitu?" +name = "Gamyba" +icon = "⌂" +description = "Jei nebeliko resursu, kuriuos butu galima ideti, kodel gi nepadarius kitu?" [skill.discovery] - name = "Aptikimas" - icon = "⚛" - description = "Kai jusu suvokimas pleciasi, jusu protas issiskleidzia ir atranda tai, ko nepadarete." +name = "Aptikimas" +icon = "⚛" +description = "Kai jusu suvokimas pleciasi, jusu protas issiskleidzia ir atranda tai, ko nepadarete." [skill.enchanting] - name = "Kerejimas" - icon = "♰" - description = "Ka tu darai? Pranasystes, regejimai, prietaringi slamstai?" +name = "Kerejimas" +icon = "♰" +description = "Ka tu darai? Pranasystes, regejimai, prietaringi slamstai?" [skill.excavation] - name = "Kasinejimas" - icon = "ᛳ" - description = "Kasam Kasam Duobe..." +name = "Kasinejimas" +icon = "ᛳ" +description = "Kasam Kasam Duobe..." [skill.herbalism] - name = "Zolininkуste" - icon = "⚘" - description = "As nerandu augalu, bet galiu rasti keleta seklu ir... ar tai... Piktzole?" +name = "Zolininkуste" +icon = "⚘" +description = "As nerandu augalu, bet galiu rasti keleta seklu ir... ar tai... Piktzole?" [skill.hunter] - name = "Medziotojas" - icon = "☠" - description = "Medzioklе - tai kelione, o ne rezultatas." +name = "Medziotojas" +icon = "☠" +description = "Medzioklе - tai kelione, o ne rezultatas." [skill.nether] - name = "Pragaras" - icon = "₪" - description = "Is paties pragaro gelmiu." +name = "Pragaras" +icon = "₪" +description = "Is paties pragaro gelmiu." [skill.pickaxe] - name = "Kirtiklis" - icon = "⛏" - description = "Nykstukай yra kalnakasiai, bet as ismokau daiktu ar du per savo laika. AS SVEDAS" +name = "Kirtiklis" +icon = "⛏" +description = "Nykstukай yra kalnakasiai, bet as ismokau daiktu ar du per savo laika. AS SVEDAS" [skill.ranged] - name = "Nuotolis" - icon = "🏹" - description = "Atstumas yra raktas i pergale ir raktas i islikima." +name = "Nuotolis" +icon = "🏹" +description = "Atstumas yra raktas i pergale ir raktas i islikima." [skill.rift] - name = "Plyšys" - icon = "❍" - description = "Plyšys yra edanti jungtis, bet jus sutramdete ta jungti." +name = "Plyšys" +icon = "❍" +description = "Plyšys yra edanti jungtis, bet jus sutramdete ta jungti." [skill.seaborne] - name = "Jurininkas" - icon = "🎣" - description = "Turedami si igudi galesite patirti vandens stebuklus." +name = "Jurininkas" +icon = "🎣" +description = "Turedami si igudi galesite patirti vandens stebuklus." [skill.stealth] - name = "Slaptumas" - icon = "☯" - description = "Nematomo menas. Vaiksciokite seselyje." +name = "Slaptumas" +icon = "☯" +description = "Nematomo menas. Vaiksciokite seselyje." [skill.swords] - name = "Kardai" - icon = "⚔" - description = "Pilko Akmens galia!" +name = "Kardai" +icon = "⚔" +description = "Pilko Akmens galia!" [skill.taming] - name = "Prisijaukinimas" - icon = "♥" - description = "Papugos ir bites... ir tu?" +name = "Prisijaukinimas" +icon = "♥" +description = "Papugos ir bites... ir tu?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Kraujas teka visatos gyslomis. Suspaustas tavo rankomis." +name = "TragOul" +icon = "🗡" +description = "Kraujas teka visatos gyslomis. Suspaustas tavo rankomis." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Sukite visatos laikrodi, patirkite tekme. Sudauzyk laikrodi, tapk juo." +name = "Chronos" +icon = "🕒" +description = "Sukite visatos laikrodi, patirkite tekme. Sudauzyk laikrodi, tapk juo." [skill.unarmed] - name = "Beginklis" - icon = "»" - description = "Be ginklo nebunate be jegos." +name = "Beginklis" +icon = "»" +description = "Be ginklo nebunate be jegos." # agility [agility] [agility.armor_up] - name = "Sarvai Aukstyn" - description = "Kuo ilgiau begsite, tuo daugiau sarvu!" - lore1 = "Max Sarvai" - lore2 = "Sarvai Aukstyn laikas" - lore = ["Max Sarvai", "Sarvai Aukstyn laikas"] +name = "Sarvai Aukstyn" +description = "Kuo ilgiau begsite, tuo daugiau sarvu!" +lore1 = "Max Sarvai" +lore2 = "Sarvai Aukstyn laikas" +lore = ["Max Sarvai", "Sarvai Aukstyn laikas"] [agility.ladder_slide] - name = "Slydimas Kopečiomis" - description = "Lipkite ir slyskit kopečiomis daug greičiau abiem kryptimis." - lore1 = "Kopečiu greicio daugiklis" - lore2 = "Greito nusileidimo greitis" - lore = ["Kopečiu greicio daugiklis", "Greito nusileidimo greitis"] +name = "Slydimas Kopečiomis" +description = "Lipkite ir slyskit kopečiomis daug greičiau abiem kryptimis." +lore1 = "Kopečiu greicio daugiklis" +lore2 = "Greito nusileidimo greitis" +lore = ["Kopečiu greicio daugiklis", "Greito nusileidimo greitis"] [agility.super_jump] - name = "Super Suolis" - description = "Isskirtinis auksčio pranasumas." - lore1 = "Maksimalus suolio aukstis" - lore2 = "Selinti + Pasokti, kad panaudoti super suoli!" - lore = ["Maksimalus suolio aukstis", "Selinti + Pasokti, kad panaudoti super suoli!"] +name = "Super Suolis" +description = "Isskirtinis auksčio pranasumas." +lore1 = "Maksimalus suolio aukstis" +lore2 = "Selinti + Pasokti, kad panaudoti super suoli!" +lore = ["Maksimalus suolio aukstis", "Selinti + Pasokti, kad panaudoti super suoli!"] [agility.wall_jump] - name = "Sienos Suolis" - description = "Laikykite shift, kol ore nuo sienos iki sienos ir sokinekite!" - lore1 = "Max Suoliai" - lore2 = "Suolio aukstis" - lore = ["Max Suoliai", "Suolio aukstis"] +name = "Sienos Suolis" +description = "Laikykite shift, kol ore nuo sienos iki sienos ir sokinekite!" +lore1 = "Max Suoliai" +lore2 = "Suolio aukstis" +lore = ["Max Suoliai", "Suolio aukstis"] [agility.wind_up] - name = "Greitis" - description = "Kuo ilgiau begsit, beksite greiciau!" - lore1 = "Max Greitis" - lore2 = "Uzbaigimo laikas" - lore = ["Max Greitis", "Uzbaigimo laikas"] +name = "Greitis" +description = "Kuo ilgiau begsit, beksite greiciau!" +lore1 = "Max Greitis" +lore2 = "Uzbaigimo laikas" +lore = ["Max Greitis", "Uzbaigimo laikas"] # architect [architect] [architect.elevator] - name = "Liftas" - description = "Tai leidzia jums pastatyti lifta, kuris greitai leis teleportuotis vertikaliai!" - lore1 = "Atrakina lifto recepta: X=VILNA, Y=ENDERIO PERLAS" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Atrakina lifto recepta: X=VILNA, Y=ENDERIO PERLAS", "XXX", "XYX", "XXX"] +name = "Liftas" +description = "Tai leidzia jums pastatyti lifta, kuris greitai leis teleportuotis vertikaliai!" +lore1 = "Atrakina lifto recepta: X=VILNA, Y=ENDERIO PERLAS" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Atrakina lifto recepta: X=VILNA, Y=ENDERIO PERLAS", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Magiskas Fondas" - description = "Tai leidzia selinti ir pasideti po savimi laikina pagrinda!" - lore1 = "Magiskai sukurti: " - lore2 = "Blokai po tavimi!" - lore = ["Magiskai sukurti: ", "Blokai po tavimi!"] +name = "Magiskas Fondas" +description = "Tai leidzia selinti ir pasideti po savimi laikina pagrinda!" +lore1 = "Magiskai sukurti: " +lore2 = "Blokai po tavimi!" +lore = ["Magiskai sukurti: ", "Blokai po tavimi!"] [architect.glass] - name = "Silko Jutiklinis Stiklas" - description = "Tai leidzia is esmes isvengti stiklo blokeliu praradimo, kai juos sulauzуsite tuscia ranka!" - lore1 = "Jusu rankos igauna silko prisilietima stiklui" - lore = ["Jusu rankos igauna silko prisilietima stiklui"] +name = "Silko Jutiklinis Stiklas" +description = "Tai leidzia is esmes isvengti stiklo blokeliu praradimo, kai juos sulauzуsite tuscia ranka!" +lore1 = "Jusu rankos igauna silko prisilietima stiklui" +lore = ["Jusu rankos igauna silko prisilietima stiklui"] [architect.wireless_redstone] - name = "Raudonakmenio Valdymo Pultas" - description = "Tai leidzia naudoti raudonakmenio degla, kad perjungtumete Raudonakmeni nuotoliniu budu!" - lore1 = "Tikslas + Raudonakmenio Deglas + Enderio Perlas = 1 Raudonakmenio Nuotolinio Valdymo Pultas" - lore = ["Tikslas + Raudonakmenio Deglas + Enderio Perlas = 1 Raudonakmenio Nuotolinio Valdymo Pultas"] +name = "Raudonakmenio Valdymo Pultas" +description = "Tai leidzia naudoti raudonakmenio degla, kad perjungtumete Raudonakmeni nuotoliniu budu!" +lore1 = "Tikslas + Raudonakmenio Deglas + Enderio Perlas = 1 Raudonakmenio Nuotolinio Valdymo Pultas" +lore = ["Tikslas + Raudonakmenio Deglas + Enderio Perlas = 1 Raudonakmenio Nuotolinio Valdymo Pultas"] [architect.placement] - name = "Statybininku Lazdele" - description = "Leidzia deti kelis blokus vienu metu, kad suaktyvintumete spauskite shift, ir dekite toki pati bloka i kuri ziurite! Atminkite, kad gali tekti siek tiek pajudeti, kad suaktyvintumete langeliu apribojima!" - lore1 = "Tau reikia" - lore2 = "bloku, kad statyti" - lore3 = "Medziagu statybininku lazdele" - lore = ["Tau reikia", "bloku, kad statyti", "Medziagu statybininku lazdele"] +name = "Statybininku Lazdele" +description = "Leidzia deti kelis blokus vienu metu, kad suaktyvintumete spauskite shift, ir dekite toki pati bloka i kuri ziurite! Atminkite, kad gali tekti siek tiek pajudeti, kad suaktyvintumete langeliu apribojima!" +lore1 = "Tau reikia" +lore2 = "bloku, kad statyti" +lore3 = "Medziagu statybininku lazdele" +lore = ["Tau reikia", "bloku, kad statyti", "Medziagu statybininku lazdele"] # axe [axe] [axe.chop] - name = "Kapotojas" - description = "Nukirskite medzius desiniu peles mygtuku spausdami ant pagrindinio rasto!" - lore1 = "Blokai per nukirtima" - lore2 = "Kirtimo laiko tarpas" - lore3 = "Irankio susidevejimas" - lore = ["Blokai per nukirtima", "Kirtimo laiko tarpas", "Irankio susidevejimas"] +name = "Kapotojas" +description = "Nukirskite medzius desiniu peles mygtuku spausdami ant pagrindinio rasto!" +lore1 = "Blokai per nukirtima" +lore2 = "Kirtimo laiko tarpas" +lore3 = "Irankio susidevejimas" +lore = ["Blokai per nukirtima", "Kirtimo laiko tarpas", "Irankio susidevejimas"] [axe.log_swap] - name = "Liucijos Rastu Keitiklis" - description = "Pakeiskite rastu tipa darbastalyje!" - lore1 = "8 betkokio tipo rastai + 1 sodinukas = 8 sodinuko tipo rastas" - lore = ["8 betkokio tipo rastai + 1 sodinukas = 8 sodinuko tipo rastas"] +name = "Liucijos Rastu Keitiklis" +description = "Pakeiskite rastu tipa darbastalyje!" +lore1 = "8 betkokio tipo rastai + 1 sodinukas = 8 sodinuko tipo rastas" +lore = ["8 betkokio tipo rastai + 1 sodinukas = 8 sodinuko tipo rastas"] [axe.drop_to_inventory] - name = "Kirvio Trauka" +name = "Kirvio Trauka" [axe.ground_smash] - name = "Zemes Drebejimas" - description = "Pasokite, tada pritupkite ir sudauzykite visus netoliese esancius priesus." - lore1 = "Zala" - lore2 = "Bloku spindulys" - lore3 = "Jega" - lore4 = "Drebejimo laiko tarpas" - lore = ["Zala", "Bloku spindulys", "Jega", "Drebejimo laiko tarpas"] +name = "Zemes Drebejimas" +description = "Pasokite, tada pritupkite ir sudauzykite visus netoliese esancius priesus." +lore1 = "Zala" +lore2 = "Bloku spindulys" +lore3 = "Jega" +lore4 = "Drebejimo laiko tarpas" +lore = ["Zala", "Bloku spindulys", "Jega", "Drebejimo laiko tarpas"] [axe.leaf_miner] - name = "Lapu Kirtejas" - description = "Leidzia vienu metu sudraskyti masinius lapus!" - lore1 = "Selink, ir kirsk LAPUS" - lore2 = "lapu kirtimo spindulys" - lore3 = "Negausite dropu nuo lapu (Exploit prevencija)" - lore = ["Selink, ir kirsk LAPUS", "lapu kirtimo spindulys", "Negausite dropu nuo lapu (Exploit prevencija)"] +name = "Lapu Kirtejas" +description = "Leidzia vienu metu sudraskyti masinius lapus!" +lore1 = "Selink, ir kirsk LAPUS" +lore2 = "lapu kirtimo spindulys" +lore3 = "Negausite dropu nuo lapu (Exploit prevencija)" +lore = ["Selink, ir kirsk LAPUS", "lapu kirtimo spindulys", "Negausite dropu nuo lapu (Exploit prevencija)"] [axe.wood_miner] - name = "Medienos Kirtejas" - description = "Leidzia is karto nukirsti masini kieki medienos!" - lore1 = "Selink, ir kirsk mediena ( Ne Lentas )" - lore2 = "Medienos kirtimo spindulys" - lore3 = "Veikia kartu su Kirvio Trauka" - lore = ["Selink, ir kirsk mediena ( Ne Lentas )", "Medienos kirtimo spindulys", "Veikia kartu su Kirvio Trauka"] +name = "Medienos Kirtejas" +description = "Leidzia is karto nukirsti masini kieki medienos!" +lore1 = "Selink, ir kirsk mediena ( Ne Lentas )" +lore2 = "Medienos kirtimo spindulys" +lore3 = "Veikia kartu su Kirvio Trauka" +lore = ["Selink, ir kirsk mediena ( Ne Lentas )", "Medienos kirtimo spindulys", "Veikia kartu su Kirvio Trauka"] # brewing [brewing] [brewing.lingering] - name = "Uzsiteses Vyrimas" - description = "Paruosti eliksyrai issilaiko ilgiau!" - lore1 = "Trukme" - lore2 = "Trukme" - lore = ["Trukme", "Trukme"] +name = "Uzsiteses Vyrimas" +description = "Paruosti eliksyrai issilaiko ilgiau!" +lore1 = "Trukme" +lore2 = "Trukme" +lore = ["Trukme", "Trukme"] [brewing.super_heated] - name = "Super Ikaites Stovas" - description = "Eliksyru stovai dirba greiciau, kai yra kuo karstesni." - lore1 = "Uz Priliesta Ugnies Bloka" - lore2 = "Uz Priliesta Lavos Bloka" - lore = ["Uz Priliesta Ugnies Bloka", "Uz Priliesta Lavos Bloka"] +name = "Super Ikaites Stovas" +description = "Eliksyru stovai dirba greiciau, kai yra kuo karstesni." +lore1 = "Uz Priliesta Ugnies Bloka" +lore2 = "Uz Priliesta Lavos Bloka" +lore = ["Uz Priliesta Ugnies Bloka", "Uz Priliesta Lavos Bloka"] [brewing.darkness] - name = "Buteliu Tamsa" - description = "Nezinome, kodel jums to reikia, bet stai!" - lore1 = "Nakcies Matymo Eliksyras + Juodas Betonas = Tamsos Eliksyras (30 sekundziu)" - lore2 = "Reiketu pazymeti, kad tai neleidzia zaideju beti greitai!" - lore = ["Nakcies Matymo Eliksyras + Juodas Betonas = Tamsos Eliksyras (30 sekundziu)", "Reiketu pazymeti, kad tai neleidzia zaideju beti greitai!"] +name = "Buteliu Tamsa" +description = "Nezinome, kodel jums to reikia, bet stai!" +lore1 = "Nakcies Matymo Eliksyras + Juodas Betonas = Tamsos Eliksyras (30 sekundziu)" +lore2 = "Reiketu pazymeti, kad tai neleidzia zaideju beti greitai!" +lore = ["Nakcies Matymo Eliksyras + Juodas Betonas = Tamsos Eliksyras (30 sekundziu)", "Reiketu pazymeti, kad tai neleidzia zaideju beti greitai!"] [brewing.haste] - name = "Greitas Kasimas" - description = "Kai efektyvumo nepakanka" - lore1 = "Greicio eliksyras + ametisto suke = greito kasimo eliksyras (60 sekundziu)" - lore2 = "Greicio eliksyras + ametisto blokas = greito kasimo eliksyras-2 (30 sekundziu)" - lore = ["Greicio eliksyras + ametisto suke = greito kasimo eliksyras (60 sekundziu)", "Greicio eliksyras + ametisto blokas = greito kasimo eliksyras-2 (30 sekundziu)"] +name = "Greitas Kasimas" +description = "Kai efektyvumo nepakanka" +lore1 = "Greicio eliksyras + ametisto suke = greito kasimo eliksyras (60 sekundziu)" +lore2 = "Greicio eliksyras + ametisto blokas = greito kasimo eliksyras-2 (30 sekundziu)" +lore = ["Greicio eliksyras + ametisto suke = greito kasimo eliksyras (60 sekundziu)", "Greicio eliksyras + ametisto blokas = greito kasimo eliksyras-2 (30 sekundziu)"] [brewing.absorption] - name = "Absorbcija" - description = "Grudinti kuna!" - lore1 = "Momentinis gydymas + kvarcas = absorbcijos eliksyras (60 sekundziu)" - lore2 = "Momentinis gydymas + kvarco blokas = absorbcijos eliksyras-2 (30 sekundziu)" - lore = ["Momentinis gydymas + kvarcas = absorbcijos eliksyras (60 sekundziu)", "Momentinis gydymas + kvarco blokas = absorbcijos eliksyras-2 (30 sekundziu)"] +name = "Absorbcija" +description = "Grudinti kuna!" +lore1 = "Momentinis gydymas + kvarcas = absorbcijos eliksyras (60 sekundziu)" +lore2 = "Momentinis gydymas + kvarco blokas = absorbcijos eliksyras-2 (30 sekundziu)" +lore = ["Momentinis gydymas + kvarcas = absorbcijos eliksyras (60 sekundziu)", "Momentinis gydymas + kvarco blokas = absorbcijos eliksyras-2 (30 sekundziu)"] [brewing.fatigue] - name = "Nuovargis" - description = "Susilpninkite kuna!" - lore1 = "Silpnumo Eliksyras + Gleiviu Kamuolys = Nuovargio Eliksyras (30 sekundziu)" - lore2 = "Silpnumo Eliksyras + Gleiviu Blokas = Nuovargio Eliksyras-2 (15 sekundziu)" - lore = ["Silpnumo Eliksyras + Gleiviu Kamuolys = Nuovargio Eliksyras (30 sekundziu)", "Silpnumo Eliksyras + Gleiviu Blokas = Nuovargio Eliksyras-2 (15 sekundziu)"] +name = "Nuovargis" +description = "Susilpninkite kuna!" +lore1 = "Silpnumo Eliksyras + Gleiviu Kamuolys = Nuovargio Eliksyras (30 sekundziu)" +lore2 = "Silpnumo Eliksyras + Gleiviu Blokas = Nuovargio Eliksyras-2 (15 sekundziu)" +lore = ["Silpnumo Eliksyras + Gleiviu Kamuolys = Nuovargio Eliksyras (30 sekundziu)", "Silpnumo Eliksyras + Gleiviu Blokas = Nuovargio Eliksyras-2 (15 sekundziu)"] [brewing.hunger] - name = "Badas" - description = "Maitinkite nepasotinamaji!" - lore1 = "Keistas Eliksyras + Supuvusi Mesa = Bado Eliksyras (30 sekundziu)" - lore2 = "Silpnumo Eliksyras + Supuvusi Mesa = Bado Eliksyras-3 (15 sekundziu)" - lore = ["Keistas Eliksyras + Supuvusi Mesa = Bado Eliksyras (30 sekundziu)", "Silpnumo Eliksyras + Supuvusi Mesa = Bado Eliksyras-3 (15 sekundziu)"] +name = "Badas" +description = "Maitinkite nepasotinamaji!" +lore1 = "Keistas Eliksyras + Supuvusi Mesa = Bado Eliksyras (30 sekundziu)" +lore2 = "Silpnumo Eliksyras + Supuvusi Mesa = Bado Eliksyras-3 (15 sekundziu)" +lore = ["Keistas Eliksyras + Supuvusi Mesa = Bado Eliksyras (30 sekundziu)", "Silpnumo Eliksyras + Supuvusi Mesa = Bado Eliksyras-3 (15 sekundziu)"] [brewing.nausea] - name = "Pykinimas" - description = "Man nuo taves bloga!" - lore1 = "Keistas Eliksyras + Rudas Grybas = Pykinimo Eliksyras (16 sekundziu)" - lore2 = "Keistas Eliksyras + Raudonasis Grybas (Pragaro) = Pykinimo Eliksyras-2 (8 sekundziu)" - lore = ["Keistas Eliksyras + Rudas Grybas = Pykinimo Eliksyras (16 sekundziu)", "Keistas Eliksyras + Raudonasis Grybas (Pragaro) = Pykinimo Eliksyras-2 (8 sekundziu)"] +name = "Pykinimas" +description = "Man nuo taves bloga!" +lore1 = "Keistas Eliksyras + Rudas Grybas = Pykinimo Eliksyras (16 sekundziu)" +lore2 = "Keistas Eliksyras + Raudonasis Grybas (Pragaro) = Pykinimo Eliksyras-2 (8 sekundziu)" +lore = ["Keistas Eliksyras + Rudas Grybas = Pykinimo Eliksyras (16 sekundziu)", "Keistas Eliksyras + Raudonasis Grybas (Pragaro) = Pykinimo Eliksyras-2 (8 sekundziu)"] [brewing.blindness] - name = "Aklumas" - description = "Tu esi baisus zmogus..." - lore1 = "Keistas Eliksyras + Rasalo Maisas = Aklumo Eliksyras (30 sekundziu)" - lore2 = "Keistas Eliksyras + Svytintis Rasalo Maisas = Aklumo Eliksyras-2 (15 sekundziu)" - lore = ["Keistas Eliksyras + Rasalo Maisas = Aklumo Eliksyras (30 sekundziu)", "Keistas Eliksyras + Svytintis Rasalo Maisas = Aklumo Eliksyras-2 (15 sekundziu)"] +name = "Aklumas" +description = "Tu esi baisus zmogus..." +lore1 = "Keistas Eliksyras + Rasalo Maisas = Aklumo Eliksyras (30 sekundziu)" +lore2 = "Keistas Eliksyras + Svytintis Rasalo Maisas = Aklumo Eliksyras-2 (15 sekundziu)" +lore = ["Keistas Eliksyras + Rasalo Maisas = Aklumo Eliksyras (30 sekundziu)", "Keistas Eliksyras + Svytintis Rasalo Maisas = Aklumo Eliksyras-2 (15 sekundziu)"] [brewing.resistance] - name = "Atsparumas" - description = "Itvirtinimai geriausiu budu!" - lore1 = "Keistas Eliksyras + Gelezies Liejinys = Atsparumo Eliksyras (60 sekundziu)" - lore2 = "Keistas Eliksyras + Gelezies Blokas = Atsparumo Eliksyras-2 (30 sekundziu)" - lore = ["Keistas Eliksyras + Gelezies Liejinys = Atsparumo Eliksyras (60 sekundziu)", "Keistas Eliksyras + Gelezies Blokas = Atsparumo Eliksyras-2 (30 sekundziu)"] +name = "Atsparumas" +description = "Itvirtinimai geriausiu budu!" +lore1 = "Keistas Eliksyras + Gelezies Liejinys = Atsparumo Eliksyras (60 sekundziu)" +lore2 = "Keistas Eliksyras + Gelezies Blokas = Atsparumo Eliksyras-2 (30 sekundziu)" +lore = ["Keistas Eliksyras + Gelezies Liejinys = Atsparumo Eliksyras (60 sekundziu)", "Keistas Eliksyras + Gelezies Blokas = Atsparumo Eliksyras-2 (30 sekundziu)"] [brewing.health_boost] - name = "Gyvybe" - description = "Kai maksimaliu gyvybiu nepakanka..." - lore1 = "Momentinis Gydymas Eliksyras + Auksinis Obuolys = Sveikatos Stiprinimo Eliksyras (120 sekundziu)" - lore2 = "Momentinis Gydymas Eliksyras + Uzburtas Auksinis Obuolys = Sveikatos Stiprinimo Eliksyras-2 (120 sekundziu)" - lore = ["Momentinis Gydymas Eliksyras + Auksinis Obuolys = Sveikatos Stiprinimo Eliksyras (120 sekundziu)", "Momentinis Gydymas Eliksyras + Uzburtas Auksinis Obuolys = Sveikatos Stiprinimo Eliksyras-2 (120 sekundziu)"] +name = "Gyvybe" +description = "Kai maksimaliu gyvybiu nepakanka..." +lore1 = "Momentinis Gydymas Eliksyras + Auksinis Obuolys = Sveikatos Stiprinimo Eliksyras (120 sekundziu)" +lore2 = "Momentinis Gydymas Eliksyras + Uzburtas Auksinis Obuolys = Sveikatos Stiprinimo Eliksyras-2 (120 sekundziu)" +lore = ["Momentinis Gydymas Eliksyras + Auksinis Obuolys = Sveikatos Stiprinimo Eliksyras (120 sekundziu)", "Momentinis Gydymas Eliksyras + Uzburtas Auksinis Obuolys = Sveikatos Stiprinimo Eliksyras-2 (120 sekundziu)"] [brewing.decay] - name = "Nykimas" - description = "Kas zinojo, kad Detritus bus toks naudingas?" - lore1 = "Silpnumo Eliksyras + Nuodinga Bulve = Nykimo Eliksyras (16 sekundziu)" - lore2 = "Silpnumo Eliksyras + Raudonio Saknys = Nykimo Eliksyras-2 (8 sekundes)" - lore = ["Silpnumo Eliksyras + Nuodinga Bulve = Nykimo Eliksyras (16 sekundziu)", "Silpnumo Eliksyras + Raudonio Saknys = Nykimo Eliksyras-2 (8 sekundes)"] +name = "Nykimas" +description = "Kas zinojo, kad Detritus bus toks naudingas?" +lore1 = "Silpnumo Eliksyras + Nuodinga Bulve = Nykimo Eliksyras (16 sekundziu)" +lore2 = "Silpnumo Eliksyras + Raudonio Saknys = Nykimo Eliksyras-2 (8 sekundes)" +lore = ["Silpnumo Eliksyras + Nuodinga Bulve = Nykimo Eliksyras (16 sekundziu)", "Silpnumo Eliksyras + Raudonio Saknys = Nykimo Eliksyras-2 (8 sekundes)"] [brewing.saturation] - name = "Sotulis" - description = "Zinai... as net ne alkanas..." - lore1 = "Regeneracijos Eliksyras + Kepta Bulve = Sotulio Eliksyras" - lore2 = "Regeneracijos Eliksyras + Sieno Rysulys = Sotulio Eliksyras-2" - lore = ["Regeneracijos Eliksyras + Kepta Bulve = Sotulio Eliksyras", "Regeneracijos Eliksyras + Sieno Rysulys = Sotulio Eliksyras-2"] +name = "Sotulis" +description = "Zinai... as net ne alkanas..." +lore1 = "Regeneracijos Eliksyras + Kepta Bulve = Sotulio Eliksyras" +lore2 = "Regeneracijos Eliksyras + Sieno Rysulys = Sotulio Eliksyras-2" +lore = ["Regeneracijos Eliksyras + Kepta Bulve = Sotulio Eliksyras", "Regeneracijos Eliksyras + Sieno Rysulys = Sotulio Eliksyras-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Mefistofelio Grandines" - description = "Leidzia jums gaminti grandininius sarvus." - lore1 = "Gaminimo receptas yra toks pat kaip ir bet kuris kitas, taciau vietoj jo naudojami gelezies grynuoliai" - lore = ["Gaminimo receptas yra toks pat kaip ir bet kuris kitas, taciau vietoj jo naudojami gelezies grynuoliai"] +name = "Mefistofelio Grandines" +description = "Leidzia jums gaminti grandininius sarvus." +lore1 = "Gaminimo receptas yra toks pat kaip ir bet kuris kitas, taciau vietoj jo naudojami gelezies grynuoliai" +lore = ["Gaminimo receptas yra toks pat kaip ir bet kuris kitas, taciau vietoj jo naudojami gelezies grynuoliai"] [blocking.horse_armorer] - name = "Gaminami Arkliu Sarvai" - description = "Leidzia pasigaminti arkliu sarvus" - lore1 = "Apsupkite balna medziaga, kuria norite naudoti sarvams gaminti" - lore = ["Apsupkite balna medziaga, kuria norite naudoti sarvams gaminti"] +name = "Gaminami Arkliu Sarvai" +description = "Leidzia pasigaminti arkliu sarvus" +lore1 = "Apsupkite balna medziaga, kuria norite naudoti sarvams gaminti" +lore = ["Apsupkite balna medziaga, kuria norite naudoti sarvams gaminti"] [blocking.saddle_crafter] - name = "Gaminamas Balnas" - description = "Pagaminkite balna is odos" - lore1 = "Receptas: 5 Odos:" - lore = ["Receptas: 5 Odos:"] +name = "Gaminamas Balnas" +description = "Pagaminkite balna is odos" +lore1 = "Receptas: 5 Odos:" +lore = ["Receptas: 5 Odos:"] [blocking.multi_armor] - name = "Multi-Sarvai" - description = "Prirskite Elytra prie sarvu" - lore1 = "Tai nuostabus igudis keliaujant." - lore2 = "Dinamiskai sujunkite ir keiskite Sarvus/Sparnus skraidant!" - lore3 = "Jei norite sujungti, su shift spauskite daikta virs kito inventoriuje esancio daikto." - lore4 = "Noredami atristi Sarva, Selinkite-Meskite daikta ir jis bus isardytas." - lore5 = "Jei jusu Multi-Sarvai bus sunaikinti, prarasite visus jame esancius daiktus." - lore6 = "Man nereikia sarvu, tai mane nuvilia..." - lore = ["Tai nuostabus igudis keliaujant.", "Dinamiskai sujunkite ir keiskite Sarvus/Sparnus skraidant!", "Jei norite sujungti, su shift spauskite daikta virs kito inventoriuje esancio daikto.", "Noredami atristi Sarva, Selinkite-Meskite daikta ir jis bus isardytas.", "Jei jusu Multi-Sarvai bus sunaikinti, prarasite visus jame esancius daiktus.", "Man nereikia sarvu, tai mane nuvilia..."] +name = "Multi-Sarvai" +description = "Prirskite Elytra prie sarvu" +lore1 = "Tai nuostabus igudis keliaujant." +lore2 = "Dinamiskai sujunkite ir keiskite Sarvus/Sparnus skraidant!" +lore3 = "Jei norite sujungti, su shift spauskite daikta virs kito inventoriuje esancio daikto." +lore4 = "Noredami atristi Sarva, Selinkite-Meskite daikta ir jis bus isardytas." +lore5 = "Jei jusu Multi-Sarvai bus sunaikinti, prarasite visus jame esancius daiktus." +lore6 = "Man nereikia sarvu, tai mane nuvilia..." +lore = ["Tai nuostabus igudis keliaujant.", "Dinamiskai sujunkite ir keiskite Sarvus/Sparnus skraidant!", "Jei norite sujungti, su shift spauskite daikta virs kito inventoriuje esancio daikto.", "Noredami atristi Sarva, Selinkite-Meskite daikta ir jis bus isardytas.", "Jei jusu Multi-Sarvai bus sunaikinti, prarasite visus jame esancius daiktus.", "Man nereikia sarvu, tai mane nuvilia..."] # crafting [crafting] [crafting.deconstruction] - name = "Dekonstrukcija" - description = "Isardykite blokus ir daiktus i isgelbejamus pagrindinius komponentus!" - lore1 = "Numeskite bet koki daikta ant zemes." - lore2 = "Tada Selinkite ir Desiniu peles mygtuku spauskite su Zirklemis" - lore = ["Numeskite bet koki daikta ant zemes.", "Tada Selinkite ir Desiniu peles mygtuku spauskite su Zirklemis"] +name = "Dekonstrukcija" +description = "Isardykite blokus ir daiktus i isgelbejamus pagrindinius komponentus!" +lore1 = "Numeskite bet koki daikta ant zemes." +lore2 = "Tada Selinkite ir Desiniu peles mygtuku spauskite su Zirklemis" +lore = ["Numeskite bet koki daikta ant zemes.", "Tada Selinkite ir Desiniu peles mygtuku spauskite su Zirklemis"] [crafting.xp] - name = "Gamybos XP" - description = "Gamindami gaukite pasyvu XP" - lore1 = "Gaukite XP gamindami" - lore = ["Gaukite XP gamindami"] +name = "Gamybos XP" +description = "Gamindami gaukite pasyvu XP" +lore1 = "Gaukite XP gamindami" +lore = ["Gaukite XP gamindami"] [crafting.reconstruction] - name = "Rudu Rekonstrukcija" - description = "Perdirbkite rudas is ju baziniu komponentu!" - lore1 = "8 dropai ir 1 priimantis = 1 ruda (beforme)" - lore2 = "Dropai turi buti islydyti (jei taikoma)" - lore3 = "Neiskaitant: Neiskaitant atraizu, kvarco, smaragdu ir kt." - lore4 = "Seimininkas = Apgaubas, t. y.: Akmuo, Neterio Akmuo, Gilakemnis." - lore = ["8 dropai ir 1 priimantis = 1 ruda (beforme)", "Dropai turi buti islydyti (jei taikoma)", "Neiskaitant: Neiskaitant atraizu, kvarco, smaragdu ir kt.", "Seimininkas = Apgaubas, t. y.: Akmuo, Neterio Akmuo, Gilakemnis."] +name = "Rudu Rekonstrukcija" +description = "Perdirbkite rudas is ju baziniu komponentu!" +lore1 = "8 dropai ir 1 priimantis = 1 ruda (beforme)" +lore2 = "Dropai turi buti islydyti (jei taikoma)" +lore3 = "Neiskaitant: Neiskaitant atraizu, kvarco, smaragdu ir kt." +lore4 = "Seimininkas = Apgaubas, t. y.: Akmuo, Neterio Akmuo, Gilakemnis." +lore = ["8 dropai ir 1 priimantis = 1 ruda (beforme)", "Dropai turi buti islydyti (jei taikoma)", "Neiskaitant: Neiskaitant atraizu, kvarco, smaragdu ir kt.", "Seimininkas = Apgaubas, t. y.: Akmuo, Neterio Akmuo, Gilakemnis."] [crafting.leather] - name = "Gaminama Oda" - description = "Oda gaminama is supuvusios mesos" - lore1 = "Tiesiog ismeskite supuvusia mesa ant lauzo!" - lore = ["Tiesiog ismeskite supuvusia mesa ant lauzo!"] +name = "Gaminama Oda" +description = "Oda gaminama is supuvusios mesos" +lore1 = "Tiesiog ismeskite supuvusia mesa ant lauzo!" +lore = ["Tiesiog ismeskite supuvusia mesa ant lauzo!"] [crafting.backpacks] - name = "Butiljerio Kuprinės!" - description = "Tai tiesiog inesa i zaidima Mojang rysuli!" - lore1 = "Noredami tai naudoti, turite buti islikimo rezime." - lore2 = "XLX : Oda, Pavadelis, Oda" - lore3 = "XSX : Oda, Statines Deze, Oda" - lore4 = "XCX : Oda, Skrynia, Oda" - lore = ["Noredami tai naudoti, turite buti islikimo rezime.", "XLX : Oda, Pavadelis, Oda", "XSX : Oda, Statines Deze, Oda", "XCX : Oda, Skrynia, Oda"] +name = "Butiljerio Kuprinės!" +description = "Tai tiesiog inesa i zaidima Mojang rysuli!" +lore1 = "Noredami tai naudoti, turite buti islikimo rezime." +lore2 = "XLX : Oda, Pavadelis, Oda" +lore3 = "XSX : Oda, Statines Deze, Oda" +lore4 = "XCX : Oda, Skrynia, Oda" +lore = ["Noredami tai naudoti, turite buti islikimo rezime.", "XLX : Oda, Pavadelis, Oda", "XSX : Oda, Statines Deze, Oda", "XCX : Oda, Skrynia, Oda"] [crafting.stations] - name = "Nesiojami Stalai!" - description = "Naudokite stala savo rankoje!" - lore2 = "VISI DAIKTAI, KURIUOS PALIKSITE ANT STALO KAI UZDARYSITE, BUS PRARASTI VISAM!" - lore3 = "Galiojantys stalai: Priekalas, Darbastalis, Tekelas, Kartografijos stalas, Akmens pjaustykle, Stakles" - lore = ["VISI DAIKTAI, KURIUOS PALIKSITE ANT STALO KAI UZDARYSITE, BUS PRARASTI VISAM!", "Galiojantys stalai: Priekalas, Darbastalis, Tekelas, Kartografijos stalas, Akmens pjaustykle, Stakles"] +name = "Nesiojami Stalai!" +description = "Naudokite stala savo rankoje!" +lore2 = "VISI DAIKTAI, KURIUOS PALIKSITE ANT STALO KAI UZDARYSITE, BUS PRARASTI VISAM!" +lore3 = "Galiojantys stalai: Priekalas, Darbastalis, Tekelas, Kartografijos stalas, Akmens pjaustykle, Stakles" +lore = ["VISI DAIKTAI, KURIUOS PALIKSITE ANT STALO KAI UZDARYSITE, BUS PRARASTI VISAM!", "Galiojantys stalai: Priekalas, Darbastalis, Tekelas, Kartografijos stalas, Akmens pjaustykle, Stakles"] [crafting.skulls] - name = "Gaminamos Galvos!" - description = "Naudodami medziagas galite pagaminti monstru galvas!" - lore1 = "Apsupkite kaulu bloka siais daiktais, kad gautumete galva:" - lore2 = "Zombio: Supuvusi Mesa" - lore3 = "Skeletono: Kaulas" - lore4 = "Pasaluno: Parakas" - lore5 = "Viterio: Pragaro Plytos" - lore6 = "Drakono: Drakono Dvelksmas" - lore = ["Apsupkite kaulu bloka siais daiktais, kad gautumete galva:", "Zombio: Supuvusi Mesa", "Skeletono: Kaulas", "Pasaluno: Parakas", "Viterio: Pragaro Plytos", "Drakono: Drakono Dvelksmas"] +name = "Gaminamos Galvos!" +description = "Naudodami medziagas galite pagaminti monstru galvas!" +lore1 = "Apsupkite kaulu bloka siais daiktais, kad gautumete galva:" +lore2 = "Zombio: Supuvusi Mesa" +lore3 = "Skeletono: Kaulas" +lore4 = "Pasaluno: Parakas" +lore5 = "Viterio: Pragaro Plytos" +lore6 = "Drakono: Drakono Dvelksmas" +lore = ["Apsupkite kaulu bloka siais daiktais, kad gautumete galva:", "Zombio: Supuvusi Mesa", "Skeletono: Kaulas", "Pasaluno: Parakas", "Viterio: Pragaro Plytos", "Drakono: Drakono Dvelksmas"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Laikas Butelyje" - description = "Nesiokit temporalu buteli, kuris kaupia laika ir leiskite ji, kad pagreitintumete laikinius blokus, auginamus augalus ir senestancias butybes, tokias kaip mazyliai gyvunai. Receptas (Beforme): Greitumo Eliksyras + Laikrodis + Stiklinis Butelis." - lore1 = "Sukauptos sekundes kraunamos kiekviena tiksenima" - lore2 = "Laiko pagreitejimas uz kiekviena sukapta sekunde" - lore3 = "Receptas (Beforme): Greitumo Eliksyras + Laikrodis + Stiklinis Butelis" - lore = ["Sukauptos sekundes kraunamos kiekviena tiksenima", "Laiko pagreitejimas uz kiekviena sukapta sekunde", "Receptas (Beforme): Greitumo Eliksyras + Laikrodis + Stiklinis Butelis"] +name = "Laikas Butelyje" +description = "Nesiokit temporalu buteli, kuris kaupia laika ir leiskite ji, kad pagreitintumete laikinius blokus, auginamus augalus ir senestancias butybes, tokias kaip mazyliai gyvunai. Receptas (Beforme): Greitumo Eliksyras + Laikrodis + Stiklinis Butelis." +lore1 = "Sukauptos sekundes kraunamos kiekviena tiksenima" +lore2 = "Laiko pagreitejimas uz kiekviena sukapta sekunde" +lore3 = "Receptas (Beforme): Greitumo Eliksyras + Laikrodis + Stiklinis Butelis" +lore = ["Sukauptos sekundes kraunamos kiekviena tiksenima", "Laiko pagreitejimas uz kiekviena sukapta sekunde", "Receptas (Beforme): Greitumo Eliksyras + Laikrodis + Stiklinis Butelis"] [chronos.aberrant_touch] - name = "Iškrypęs Prisilietimas" - description = "Artimos kovos smogiaj suteikia besikaupianti suletejima alkio saskaita, su griežtais PvP apribojimais, ir isaldo taikinius prie 5 kaupimo." - lore1 = "Artimos kovos smugiai suteikia besikaupianti suletejima" - lore2 = "PvE suletejimo trukmes riba" - lore3 = "PvP suletejimo stiprumo riba" - lore = ["Artimos kovos smugiai suteikia besikaupianti suletejima", "PvE suletejimo trukmes riba", "PvP suletejimo stiprumo riba"] +name = "Iškrypęs Prisilietimas" +description = "Artimos kovos smogiaj suteikia besikaupianti suletejima alkio saskaita, su griežtais PvP apribojimais, ir isaldo taikinius prie 5 kaupimo." +lore1 = "Artimos kovos smugiai suteikia besikaupianti suletejima" +lore2 = "PvE suletejimo trukmes riba" +lore3 = "PvP suletejimo stiprumo riba" +lore = ["Artimos kovos smugiai suteikia besikaupianti suletejima", "PvE suletejimo trukmes riba", "PvP suletejimo stiprumo riba"] [chronos.instant_recall] - name = "Momentinis Atšaukimas" - description = "Kairiuoju arba desiniu peles mygtuku spauskite su laikrodziu rankoje, kad persuktu laika atgal i naujausiu momentini vaizda su atkurta sveikata ir alkiu." - lore1 = "Persukimo trukme" - lore2 = "Atvesimas" - lore3 = "Inventorius nesigrazina" - lore = ["Persukimo trukme", "Atvesimas", "Inventorius nesigrazina"] +name = "Momentinis Atšaukimas" +description = "Kairiuoju arba desiniu peles mygtuku spauskite su laikrodziu rankoje, kad persuktu laika atgal i naujausiu momentini vaizda su atkurta sveikata ir alkiu." +lore1 = "Persukimo trukme" +lore2 = "Atvesimas" +lore3 = "Inventorius nesigrazina" +lore = ["Persukimo trukme", "Atvesimas", "Inventorius nesigrazina"] [chronos.time_bomb] - name = "Laiko Bomba" - description = "Meskite sukurta chrono bomba, kuri sukuria temporalu lauka, suletina butybes ir sustingdo sviedinys." - lore1 = "Temporalinio lauko spindulys" - lore2 = "Temporalinio lauko trukme" - lore3 = "Bombos atvesimas" - lore4 = "Receptas (Beforme): Laikrodis + Sniegio gniuzte + Deimantas + Smėlis" - lore = ["Temporalinio lauko spindulys", "Temporalinio lauko trukme", "Bombos atvesimas", "Receptas (Beforme): Laikrodis + Sniegio gniuzte + Deimantas + Smėlis"] +name = "Laiko Bomba" +description = "Meskite sukurta chrono bomba, kuri sukuria temporalu lauka, suletina butybes ir sustingdo sviedinys." +lore1 = "Temporalinio lauko spindulys" +lore2 = "Temporalinio lauko trukme" +lore3 = "Bombos atvesimas" +lore4 = "Receptas (Beforme): Laikrodis + Sniegio gniuzte + Deimantas + Smėlis" +lore = ["Temporalinio lauko spindulys", "Temporalinio lauko trukme", "Bombos atvesimas", "Receptas (Beforme): Laikrodis + Sniegio gniuzte + Deimantas + Smėlis"] # discovery [discovery] [discovery.armor] - name = "Pasaulio Sarvai" - description = "Pasyvus sarvai, priklausomai nuo salia esancio bloko kietumo." - lore1 = "Pasyvus Sarvai" - lore2 = "Remiantis salia esancio bloko kietumu" - lore3 = "Sarvu Stiprumas:" - lore = ["Pasyvus Sarvai", "Remiantis salia esancio bloko kietumu", "Sarvu Stiprumas:"] +name = "Pasaulio Sarvai" +description = "Pasyvus sarvai, priklausomai nuo salia esancio bloko kietumo." +lore1 = "Pasyvus Sarvai" +lore2 = "Remiantis salia esancio bloko kietumu" +lore3 = "Sarvu Stiprumas:" +lore = ["Pasyvus Sarvai", "Remiantis salia esancio bloko kietumu", "Sarvu Stiprumas:"] [discovery.unity] - name = "Eksperimentine Vienybe" - description = "Patirties kamuoliu rinkimas prideda XP prie atsitiktiniu igudzių." - lore1 = "XP " - lore2 = "Per Kamuoli" - lore = ["XP ", "Per Kamuoli"] +name = "Eksperimentine Vienybe" +description = "Patirties kamuoliu rinkimas prideda XP prie atsitiktiniu igudzių." +lore1 = "XP " +lore2 = "Per Kamuoli" +lore = ["XP ", "Per Kamuoli"] [discovery.resist] - name = "Eksperimentinis Atsparumas" - description = "Sunaudokite patirtі, kad sumazintumete zala tik tada, kai smugis numustu jus zemiau 5 sirdziu arba nuzudytu." - lore0 = "Suveikia tik esant kritinei sveikatai (<= 5 sirdzių) karta per 15 sekundziu" - lore1 = " Sumazinta zala" - lore2 = "patirtis issekusi" - lore = ["Suveikia tik esant kritinei sveikatai (<= 5 sirdzių) karta per 15 sekundziu", " Sumazinta zala", "patirtis issekusi"] +name = "Eksperimentinis Atsparumas" +description = "Sunaudokite patirtі, kad sumazintumete zala tik tada, kai smugis numustu jus zemiau 5 sirdziu arba nuzudytu." +lore0 = "Suveikia tik esant kritinei sveikatai (<= 5 sirdzių) karta per 15 sekundziu" +lore1 = " Sumazinta zala" +lore2 = "patirtis issekusi" +lore = ["Suveikia tik esant kritinei sveikatai (<= 5 sirdzių) karta per 15 sekundziu", " Sumazinta zala", "patirtis issekusi"] [discovery.villager] - name = "Kaimieciu Atrakcija" - description = "Leidzia sudaryti geresnius sandorius su kaimo gyventojais!" - lore1 = "Tai sunaudoja XP vienam bendravimui su kaimieciais" - lore2 = "Tikimybe per saveika sunaudoti XP ir pagerinti sandorius" - lore3 = "reikalingas XP sunaudojimas vienai saveikai" - lore = ["Tai sunaudoja XP vienam bendravimui su kaimieciais", "Tikimybe per saveika sunaudoti XP ir pagerinti sandorius", "reikalingas XP sunaudojimas vienai saveikai"] +name = "Kaimieciu Atrakcija" +description = "Leidzia sudaryti geresnius sandorius su kaimo gyventojais!" +lore1 = "Tai sunaudoja XP vienam bendravimui su kaimieciais" +lore2 = "Tikimybe per saveika sunaudoti XP ir pagerinti sandorius" +lore3 = "reikalingas XP sunaudojimas vienai saveikai" +lore = ["Tai sunaudoja XP vienam bendravimui su kaimieciais", "Tikimybe per saveika sunaudoti XP ir pagerinti sandorius", "reikalingas XP sunaudojimas vienai saveikai"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Lapis Sugrizimas" - description = "Kaina dar 1 XP lygis, o mainais turite sansa gauti nemokamu lapis" - lore1 = "Kiekvienam lygiui tai padidina kerejimo kaina - 1, bet gali grazinti daugiau nei 3 lapis" - lore = ["Kiekvienam lygiui tai padidina kerejimo kaina - 1, bet gali grazinti daugiau nei 3 lapis"] +name = "Lapis Sugrizimas" +description = "Kaina dar 1 XP lygis, o mainais turite sansa gauti nemokamu lapis" +lore1 = "Kiekvienam lygiui tai padidina kerejimo kaina - 1, bet gali grazinti daugiau nei 3 lapis" +lore = ["Kiekvienam lygiui tai padidina kerejimo kaina - 1, bet gali grazinti daugiau nei 3 lapis"] [enchanting.quick_enchant] - name = "Staigus Burtas" - description = "Uzburkite daiktus dedami uzburtas knygas tiesiai ant ju." - lore1 = "Maksimalus Kombinuotas Lygis" - lore2 = "Negalima uzburti daikto, kuriame yra daugiau nei " - lore3 = "galia" - lore = ["Maksimalus Kombinuotas Lygis", "Negalima uzburti daikto, kuriame yra daugiau nei ", "galia"] +name = "Staigus Burtas" +description = "Uzburkite daiktus dedami uzburtas knygas tiesiai ant ju." +lore1 = "Maksimalus Kombinuotas Lygis" +lore2 = "Negalima uzburti daikto, kuriame yra daugiau nei " +lore3 = "galia" +lore = ["Maksimalus Kombinuotas Lygis", "Negalima uzburti daikto, kuriame yra daugiau nei ", "galia"] [enchanting.return] - name = "XP Grazinimas" - description = "Buriant jums XP grazinamas, kai uzburiate daikta." - lore1 = "Isleista patirtis turi galimybe buti grazinta, kai uzburiate daikta" - lore2 = "Patirtis per Burta" - lore = ["Isleista patirtis turi galimybe buti grazinta, kai uzburiate daikta", "Patirtis per Burta"] +name = "XP Grazinimas" +description = "Buriant jums XP grazinamas, kai uzburiate daikta." +lore1 = "Isleista patirtis turi galimybe buti grazinta, kai uzburiate daikta" +lore2 = "Patirtis per Burta" +lore = ["Isleista patirtis turi galimybe buti grazinta, kai uzburiate daikta", "Patirtis per Burta"] # excavation [excavation] [excavation.haste] - name = "Skubus Ekskavatoristas" - description = "Tai paspartins kasimo procesa, su GREITU KASIMU!" - lore1 = "Gaukite greita kasima kasant" - lore2 = "x Greito kasimo lygiai, kai pradedate kasti bet koki bloka." - lore = ["Gaukite greita kasima kasant", "x Greito kasimo lygiai, kai pradedate kasti bet koki bloka."] +name = "Skubus Ekskavatoristas" +description = "Tai paspartins kasimo procesa, su GREITU KASIMU!" +lore1 = "Gaukite greita kasima kasant" +lore2 = "x Greito kasimo lygiai, kai pradedate kasti bet koki bloka." +lore = ["Gaukite greita kasima kasant", "x Greito kasimo lygiai, kai pradedate kasti bet koki bloka."] [excavation.spelunker] - name = "Super-Ziurovas Spelunkeris!" - description = "Ziurekite i rudas pro zeme!" - lore1 = "Ruda laikykite rankose, o Svytincias uogas laikykite pagrindineje rankoje ir Sneak'inkite!" - lore2 = "Bloku diapazonas: " - lore3 = "Naudojant sunaudoja svytincias uogas" - lore = ["Ruda laikykite rankose, o Svytincias uogas laikykite pagrindineje rankoje ir Sneak'inkite!", "Bloku diapazonas: ", "Naudojant sunaudoja svytincias uogas"] +name = "Super-Ziurovas Spelunkeris!" +description = "Ziurekite i rudas pro zeme!" +lore1 = "Ruda laikykite rankose, o Svytincias uogas laikykite pagrindineje rankoje ir Sneak'inkite!" +lore2 = "Bloku diapazonas: " +lore3 = "Naudojant sunaudoja svytincias uogas" +lore = ["Ruda laikykite rankose, o Svytincias uogas laikykite pagrindineje rankoje ir Sneak'inkite!", "Bloku diapazonas: ", "Naudojant sunaudoja svytincias uogas"] [excavation.drop_to_inventory] - name = "Kastuvo Trauka" +name = "Kastuvo Trauka" [excavation.omni_tool] - name = "OMNI - I.R.A.N.K.I.S" - description = "Tackle perdetais prabangus Leatherman" - lore1 = "Tikriausiai galingiausias is daugelio leidzia jums" - lore2 = "keisti irankius skrydzio metu, atsizvelgiant i jusu poreikius." - lore3 = "Jei norite sujungti, su shift spauskite daikta virs kito inventoriuje esancio daikto." - lore4 = "Noredami atristi irankius, Selinkite-Meskite daikta ir bus isardytas." - lore5 = "Jus negalite sulauzyti irankiu siame odu zmoguje, bet ir negalite naudoti sulauzytu irankiu" - lore6 = "visi galimi sujungti daiktai." - lore7 = "Galite naudoti penkis ar sesius irankius arba tik viena!" - lore = ["Tikriausiai galingiausias is daugelio leidzia jums", "keisti irankius skrydzio metu, atsizvelgiant i jusu poreikius.", "Jei norite sujungti, su shift spauskite daikta virs kito inventoriuje esancio daikto.", "Noredami atristi irankius, Selinkite-Meskite daikta ir bus isardytas.", "Jus negalite sulauzyti irankiu siame odu zmoguje, bet ir negalite naudoti sulauzytu irankiu", "visi galimi sujungti daiktai.", "Galite naudoti penkis ar sesius irankius arba tik viena!"] +name = "OMNI - I.R.A.N.K.I.S" +description = "Tackle perdetais prabangus Leatherman" +lore1 = "Tikriausiai galingiausias is daugelio leidzia jums" +lore2 = "keisti irankius skrydzio metu, atsizvelgiant i jusu poreikius." +lore3 = "Jei norite sujungti, su shift spauskite daikta virs kito inventoriuje esancio daikto." +lore4 = "Noredami atristi irankius, Selinkite-Meskite daikta ir bus isardytas." +lore5 = "Jus negalite sulauzyti irankiu siame odu zmoguje, bet ir negalite naudoti sulauzytu irankiu" +lore6 = "visi galimi sujungti daiktai." +lore7 = "Galite naudoti penkis ar sesius irankius arba tik viena!" +lore = ["Tikriausiai galingiausias is daugelio leidzia jums", "keisti irankius skrydzio metu, atsizvelgiant i jusu poreikius.", "Jei norite sujungti, su shift spauskite daikta virs kito inventoriuje esancio daikto.", "Noredami atristi irankius, Selinkite-Meskite daikta ir bus isardytas.", "Jus negalite sulauzyti irankiu siame odu zmoguje, bet ir negalite naudoti sulauzytu irankiu", "visi galimi sujungti daiktai.", "Galite naudoti penkis ar sesius irankius arba tik viena!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Augimo Aura" - description = "Auginkite gamta aplink save auroje" - lore1 = "Bloko Spindulys" - lore2 = "Augimo Auros Stiprumas" - lore3 = "Maisto Kaina" - lore = ["Bloko Spindulys", "Augimo Auros Stiprumas", "Maisto Kaina"] +name = "Augimo Aura" +description = "Auginkite gamta aplink save auroje" +lore1 = "Bloko Spindulys" +lore2 = "Augimo Auros Stiprumas" +lore3 = "Maisto Kaina" +lore = ["Bloko Spindulys", "Augimo Auros Stiprumas", "Maisto Kaina"] [herbalism.hippo] - name = "Zolininko Begemotas" - description = "Maisto vartojimas suteikia daugiau sotumo" - lore1 = "Maistas) papildomi vartojimo prisotinimo taskai" - lore = ["Maistas) papildomi vartojimo prisotinimo taskai"] +name = "Zolininko Begemotas" +description = "Maisto vartojimas suteikia daugiau sotumo" +lore1 = "Maistas) papildomi vartojimo prisotinimo taskai" +lore = ["Maistas) papildomi vartojimo prisotinimo taskai"] [herbalism.myconid] - name = "Zolininku Mikonidas" - description = "Suteikia jums galimybe gaminti grybiena" - lore1 = "Bet koks zemes blokas ir rudas & raudonas grybas sukurs grybiena." - lore = ["Bet koks zemes blokas ir rudas & raudonas grybas sukurs grybiena."] +name = "Zolininku Mikonidas" +description = "Suteikia jums galimybe gaminti grybiena" +lore1 = "Bet koks zemes blokas ir rudas & raudonas grybas sukurs grybiena." +lore = ["Bet koks zemes blokas ir rudas & raudonas grybas sukurs grybiena."] [herbalism.terralid] - name = "Zolininko Zole" - description = "Suteikia jums galimybe gaminti zoles blokus" - lore1 = "Trys seklos, daugiau nei 3 zemes blokai ir sukurs 3 zoles blokus." - lore = ["Trys seklos, daugiau nei 3 zemes blokai ir sukurs 3 zoles blokus."] +name = "Zolininko Zole" +description = "Suteikia jums galimybe gaminti zoles blokus" +lore1 = "Trys seklos, daugiau nei 3 zemes blokai ir sukurs 3 zoles blokus." +lore = ["Trys seklos, daugiau nei 3 zemes blokai ir sukurs 3 zoles blokus."] [herbalism.cobweb] - name = "Voratinkliu Kurejas" - description = "Suteikia galimybe kurti voratinklius darbastalyje" - lore1 = "Devyni siulai pagamins voratinkli." - lore = ["Devyni siulai pagamins voratinkli."] +name = "Voratinkliu Kurejas" +description = "Suteikia galimybe kurti voratinklius darbastalyje" +lore1 = "Devyni siulai pagamins voratinkli." +lore = ["Devyni siulai pagamins voratinkli."] [herbalism.mushroom_blocks] - name = "Grybu Gamintojas" - description = "Suteikia jums galimybe gaminti grybu blokus darbastalyje" - lore1 = "Keturi grybai, kad padarytumete bloka, arba blokas, kad padarytumete stieba." - lore = ["Keturi grybai, kad padarytumete bloka, arba blokas, kad padarytumete stieba."] +name = "Grybu Gamintojas" +description = "Suteikia jums galimybe gaminti grybu blokus darbastalyje" +lore1 = "Keturi grybai, kad padarytumete bloka, arba blokas, kad padarytumete stieba." +lore = ["Keturi grybai, kad padarytumete bloka, arba blokas, kad padarytumete stieba."] [herbalism.drop_to_inventory] - name = "Kauptuko Trauka" +name = "Kauptuko Trauka" [herbalism.hungry_shield] - name = "Alkanas Skydas" - description = "Gaukite zala savo alkiui pries savo gyvybes." - lore1 = "Apsaugota nuo Alkio" - lore = ["Apsaugota nuo Alkio"] +name = "Alkanas Skydas" +description = "Gaukite zala savo alkiui pries savo gyvybes." +lore1 = "Apsaugota nuo Alkio" +lore = ["Apsaugota nuo Alkio"] [herbalism.luck] - name = "Zolininko Sekme" - description = "Kai sulauzysite zole/geles, turite galimybe gauti atsitiktini daikta" - lore0 = "Geles = Maistas, ir Zole = Seklos" - lore1 = "Galimybe gauti daikta griaunant geles" - lore2 = "Galimybe gauti daikta sugriovus zole" - lore = ["Geles = Maistas, ir Zole = Seklos", "Galimybe gauti daikta griaunant geles", "Galimybe gauti daikta sugriovus zole"] +name = "Zolininko Sekme" +description = "Kai sulauzysite zole/geles, turite galimybe gauti atsitiktini daikta" +lore0 = "Geles = Maistas, ir Zole = Seklos" +lore1 = "Galimybe gauti daikta griaunant geles" +lore2 = "Galimybe gauti daikta sugriovus zole" +lore = ["Geles = Maistas, ir Zole = Seklos", "Galimybe gauti daikta griaunant geles", "Galimybe gauti daikta sugriovus zole"] [herbalism.replant] - name = "Derliaus Nuemimas ir Persodinimas" - description = "Desiniu peles mygtuku spauskite ant paseliu su kapliu, kad nuimtumete derliu ir persodintumete." - lore1 = "Bloku Persodinimo Spindulys" - lore = ["Bloku Persodinimo Spindulys"] +name = "Derliaus Nuemimas ir Persodinimas" +description = "Desiniu peles mygtuku spauskite ant paseliu su kapliu, kad nuimtumete derliu ir persodintumete." +lore1 = "Bloku Persodinimo Spindulys" +lore = ["Bloku Persodinimo Spindulys"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenalinas" - description = "Kuo maziau gyvybiu, tuo daugiau zalos padarysite (Artimoje kovoje)" - lore1 = "Maksimali zala" - lore = ["Maksimali zala"] +name = "Adrenalinas" +description = "Kuo maziau gyvybiu, tuo daugiau zalos padarysite (Artimoje kovoje)" +lore1 = "Maksimali zala" +lore = ["Maksimali zala"] [hunter.penalty] - name = "" - description = "" - lore1 = "Jus gausite vis daugiau nuodu, jeigu tapsite alkanas" - lore = ["Jus gausite vis daugiau nuodu, jeigu tapsite alkanas"] +name = "" +description = "" +lore1 = "Jus gausite vis daugiau nuodu, jeigu tapsite alkanas" +lore = ["Jus gausite vis daugiau nuodu, jeigu tapsite alkanas"] [hunter.drop_to_inventory] - name = "Daiktu Trauka" - description = "Kai ka nors nuzudote/sulauzote bloka kardu, daiktai atsiras jusu inventoriuje" - lore1 = "Kiekviena karta, kai daiktas iskrenta is monstro/bloko, kuri sugriaunate, patenka i jusu inventoriu, jei imanoma." - lore = ["Kiekviena karta, kai daiktas iskrenta is monstro/bloko, kuri sugriaunate, patenka i jusu inventoriu, jei imanoma."] +name = "Daiktu Trauka" +description = "Kai ka nors nuzudote/sulauzote bloka kardu, daiktai atsiras jusu inventoriuje" +lore1 = "Kiekviena karta, kai daiktas iskrenta is monstro/bloko, kuri sugriaunate, patenka i jusu inventoriu, jei imanoma." +lore = ["Kiekviena karta, kai daiktas iskrenta is monstro/bloko, kuri sugriaunate, patenka i jusu inventoriu, jei imanoma."] [hunter.invisibility] - name = "Nykstantis Zingsnis" - description = "Kai esi sumustas, tu igyji nematomuma, tai isnaudos daug alkio" - lore1 = "Smugio metu igykite pasyvu nematomuma" - lore2 = "x Nematomumas kaupiasi 3 sekundes po smugio" - lore3 = "x Kaupiamas alkis" - lore4 = "Badas kaups trukme ir daugikli." - lore5 = "Nematomumo trukme" - lore = ["Smugio metu igykite pasyvu nematomuma", "x Nematomumas kaupiasi 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Nematomumo trukme"] +name = "Nykstantis Zingsnis" +description = "Kai esi sumustas, tu igyji nematomuma, tai isnaudos daug alkio" +lore1 = "Smugio metu igykite pasyvu nematomuma" +lore2 = "x Nematomumas kaupiasi 3 sekundes po smugio" +lore3 = "x Kaupiamas alkis" +lore4 = "Badas kaups trukme ir daugikli." +lore5 = "Nematomumo trukme" +lore = ["Smugio metu igykite pasyvu nematomuma", "x Nematomumas kaupiasi 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Nematomumo trukme"] [hunter.jump_boost] - name = "Medziodojo Auksumos" - description = "Kai tave istiko ispuolis, tu igyji suolio impulsa, tai kainuos alki" - lore1 = "Igykite pasyvu suolio padidinima smugio metu" - lore2 = "x Suoliavimas kaupiasi 3 sekundes po smugio" - lore3 = "x Kaupiamas alkis" - lore4 = "Badas kaups trukme ir daugikli." - lore5 = "Suoliavimo kaups daugikli, o ne trukme" - lore = ["Igykite pasyvu suolio padidinima smugio metu", "x Suoliavimas kaupiasi 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Suoliavimo kaups daugikli, o ne trukme"] +name = "Medziodojo Auksumos" +description = "Kai tave istiko ispuolis, tu igyji suolio impulsa, tai kainuos alki" +lore1 = "Igykite pasyvu suolio padidinima smugio metu" +lore2 = "x Suoliavimas kaupiasi 3 sekundes po smugio" +lore3 = "x Kaupiamas alkis" +lore4 = "Badas kaups trukme ir daugikli." +lore5 = "Suoliavimo kaups daugikli, o ne trukme" +lore = ["Igykite pasyvu suolio padidinima smugio metu", "x Suoliavimas kaupiasi 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Suoliavimo kaups daugikli, o ne trukme"] [hunter.luck] - name = "Medziodojo Sekme" - description = "Kai tave uzklumpa, tu laimi sekme, tai kainuos alki" - lore1 = "Pasiekite pasyvia sekme, kai nukentesete" - lore2 = "x Sekme kaupiasi 3 sekundes po smugio" - lore3 = "x Kaupiamas alkis" - lore4 = "Badas kaups trukme ir daugikli." - lore5 = "Sekme kaups daugikli, o ne trukme." - lore = ["Pasiekite pasyvia sekme, kai nukentesete", "x Sekme kaupiasi 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Sekme kaups daugikli, o ne trukme."] +name = "Medziodojo Sekme" +description = "Kai tave uzklumpa, tu laimi sekme, tai kainuos alki" +lore1 = "Pasiekite pasyvia sekme, kai nukentesete" +lore2 = "x Sekme kaupiasi 3 sekundes po smugio" +lore3 = "x Kaupiamas alkis" +lore4 = "Badas kaups trukme ir daugikli." +lore5 = "Sekme kaups daugikli, o ne trukme." +lore = ["Pasiekite pasyvia sekme, kai nukentesete", "x Sekme kaupiasi 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Sekme kaups daugikli, o ne trukme."] [hunter.regen] - name = "Medziodojo Regeneracija" - description = "Kai esi sumustas, tu atsiregenruosi, tai kainuos alki" - lore1 = "Igykite pasyvia regeneracija, kai itrenkia" - lore2 = "x Regeneracija kaupsis 3 sekundes po smugio" - lore3 = "x Kaupiamas alkis" - lore4 = "Badas kaups trukme ir daugikli." - lore5 = "Regeneracija kaups daugikli, ne trukme." - lore = ["Igykite pasyvia regeneracija, kai itrenkia", "x Regeneracija kaupsis 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Regeneracija kaups daugikli, ne trukme."] +name = "Medziodojo Regeneracija" +description = "Kai esi sumustas, tu atsiregenruosi, tai kainuos alki" +lore1 = "Igykite pasyvia regeneracija, kai itrenkia" +lore2 = "x Regeneracija kaupsis 3 sekundes po smugio" +lore3 = "x Kaupiamas alkis" +lore4 = "Badas kaups trukme ir daugikli." +lore5 = "Regeneracija kaups daugikli, ne trukme." +lore = ["Igykite pasyvia regeneracija, kai itrenkia", "x Regeneracija kaupsis 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Regeneracija kaups daugikli, ne trukme."] [hunter.resistance] - name = "Medziodojo Atsparumas" - description = "Kai tave uzklumpa, tu igyji atsparuma, tai kainuos alki" - lore1 = "Igykite pasyvu pasipriesinima, kai itrenkia" - lore2 = "x Atsparumas kaupsis 3 sekundes po smugio" - lore3 = "x Kaupiamas alkis" - lore4 = "Badas kaups trukme ir daugikli." - lore5 = "Atsparumas kaups daugikli, o ne trukme." - lore = ["Igykite pasyvu pasipriesinima, kai itrenkia", "x Atsparumas kaupsis 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Atsparumas kaups daugikli, o ne trukme."] +name = "Medziodojo Atsparumas" +description = "Kai tave uzklumpa, tu igyji atsparuma, tai kainuos alki" +lore1 = "Igykite pasyvu pasipriesinima, kai itrenkia" +lore2 = "x Atsparumas kaupsis 3 sekundes po smugio" +lore3 = "x Kaupiamas alkis" +lore4 = "Badas kaups trukme ir daugikli." +lore5 = "Atsparumas kaups daugikli, o ne trukme." +lore = ["Igykite pasyvu pasipriesinima, kai itrenkia", "x Atsparumas kaupsis 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Atsparumas kaups daugikli, o ne trukme."] [hunter.speed] - name = "Medziodojo Greitis" - description = "Kai tave uzklumpa, tu igyji greiti, tai kainuos alki" - lore1 = "Smugio metu padidinkite pasyvu greiti" - lore2 = "x Greitis kaupiasi 3 sekundes po smugio" - lore3 = "x Kaupiamas alkis" - lore4 = "Badas kaups trukme ir daugikli." - lore5 = "Greicio kaups daugikli, o ne trukme." - lore = ["Smugio metu padidinkite pasyvu greiti", "x Greitis kaupiasi 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Greicio kaups daugikli, o ne trukme."] +name = "Medziodojo Greitis" +description = "Kai tave uzklumpa, tu igyji greiti, tai kainuos alki" +lore1 = "Smugio metu padidinkite pasyvu greiti" +lore2 = "x Greitis kaupiasi 3 sekundes po smugio" +lore3 = "x Kaupiamas alkis" +lore4 = "Badas kaups trukme ir daugikli." +lore5 = "Greicio kaups daugikli, o ne trukme." +lore = ["Smugio metu padidinkite pasyvu greiti", "x Greitis kaupiasi 3 sekundes po smugio", "x Kaupiamas alkis", "Badas kaups trukme ir daugikli.", "Greicio kaups daugikli, o ne trukme."] [hunter.strength] - name = "Medziodojo Galia" - description = "Kai tave musa, tu igyji jegu, tai kainuos alki" - lore1 = "Smugio metu igykite pasyvios jegos" - lore2 = "x Jega kaupiasi 3 sekundes po smugio" - lore3 = "x Susikraunantis Alkis" - lore4 = "Bado didejimo trukme ir daugiklis." - lore5 = "Jegos kruvos daugiklis, ne trukme." - lore = ["Smugio metu igykite pasyvios jegos", "x Jega kaupiasi 3 sekundes po smugio", "x Susikraunantis Alkis", "Bado didejimo trukme ir daugiklis.", "Jegos kruvos daugiklis, ne trukme."] +name = "Medziodojo Galia" +description = "Kai tave musa, tu igyji jegu, tai kainuos alki" +lore1 = "Smugio metu igykite pasyvios jegos" +lore2 = "x Jega kaupiasi 3 sekundes po smugio" +lore3 = "x Susikraunantis Alkis" +lore4 = "Bado didejimo trukme ir daugiklis." +lore5 = "Jegos kruvos daugiklis, ne trukme." +lore = ["Smugio metu igykite pasyvios jegos", "x Jega kaupiasi 3 sekundes po smugio", "x Susikraunantis Alkis", "Bado didejimo trukme ir daugiklis.", "Jegos kruvos daugiklis, ne trukme."] # nether [nether] [nether.skull_toss] - name = "Nudziuvusios Kaukoles Metimas" - description1 = "Islaisvinkite savo vidini vytuma naudodami" - description2 = "kazkieno" - description3 = "galva." - lore1 = "sekundes tarp kaukoles metimu." - lore2 = "Viterio naudojimas: ismeskite " - lore3 = "Viterio Galva" - lore4 = "sprogsta nuo smugio." - lore = ["sekundes tarp kaukoles metimu.", "Viterio naudojimas: ismeskite ", "Viterio Galva", "sprogsta nuo smugio."] +name = "Nudziuvusios Kaukoles Metimas" +description1 = "Islaisvinkite savo vidini vytuma naudodami" +description2 = "kazkieno" +description3 = "galva." +lore1 = "sekundes tarp kaukoles metimu." +lore2 = "Viterio naudojimas: ismeskite " +lore3 = "Viterio Galva" +lore4 = "sprogsta nuo smugio." +lore = ["sekundes tarp kaukoles metimu.", "Viterio naudojimas: ismeskite ", "Viterio Galva", "sprogsta nuo smugio."] [nether.wither_resist] - name = "Atsparumas Vytimui" - description = "Priesinasi vytimui del neterito galios." - lore1 = "galimybe paneigti vytima (vienam luitui)." - lore2 = "Pasyvus: Neterito sarvu devejimas turi galimybe panaikinti " - lore3 = "vytima." - lore = ["galimybe paneigti vytima (vienam luitui).", "Pasyvus: Neterito sarvu devejimas turi galimybe panaikinti ", "vytima."] +name = "Atsparumas Vytimui" +description = "Priesinasi vytimui del neterito galios." +lore1 = "galimybe paneigti vytima (vienam luitui)." +lore2 = "Pasyvus: Neterito sarvu devejimas turi galimybe panaikinti " +lore3 = "vytima." +lore = ["galimybe paneigti vytima (vienam luitui).", "Pasyvus: Neterito sarvu devejimas turi galimybe panaikinti ", "vytima."] [nether.fire_resist] - name = "Atsparumas Ugniai" - description = "Atsparus ugniai, nes sukietina oda." - lore1 = "galimybe paneigti deginimo efekta!" - lore = ["galimybe paneigti deginimo efekta!"] +name = "Atsparumas Ugniai" +description = "Atsparus ugniai, nes sukietina oda." +lore1 = "galimybe paneigti deginimo efekta!" +lore = ["galimybe paneigti deginimo efekta!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Automatinis Lydymas" - description = "Leidzia lydyti iskastas rudas." - lore1 = "Rudos, kurias galima lydyti, islydomos automatiskai" - lore2 = "% galimybe gauti papildoma" - lore = ["Rudos, kurias galima lydyti, islydomos automatiskai", "% galimybe gauti papildoma"] +name = "Automatinis Lydymas" +description = "Leidzia lydyti iskastas rudas." +lore1 = "Rudos, kurias galima lydyti, islydomos automatiskai" +lore2 = "% galimybe gauti papildoma" +lore = ["Rudos, kurias galima lydyti, islydomos automatiskai", "% galimybe gauti papildoma"] [pickaxe.chisel] - name = "Rudos Kaltas" - description = "Desiniu peles mygtuku spauskite ant Rudos, kad is ju isgautumete daugiau rudos uz didele ilgaamziskumo kaina." - lore1 = "Galimybe numesti" - lore2 = "Irankiu susidevejimas" - lore = ["Galimybe numesti", "Irankiu susidevejimas"] +name = "Rudos Kaltas" +description = "Desiniu peles mygtuku spauskite ant Rudos, kad is ju isgautumete daugiau rudos uz didele ilgaamziskumo kaina." +lore1 = "Galimybe numesti" +lore2 = "Irankiu susidevejimas" +lore = ["Galimybe numesti", "Irankiu susidevejimas"] [pickaxe.drop_to_inventory] - name = "Kirtiklio Trauka" - description = "Kai nugrausite bloka, daiktas atsiras jusu inventoriuje" - lore1 = "Vietoj to, kad daiktas iskrenta is bloko, daiktas pateks i jusu inventoriu, jei imanoma." - lore = ["Vietoj to, kad daiktas iskrenta is bloko, daiktas pateks i jusu inventoriu, jei imanoma."] +name = "Kirtiklio Trauka" +description = "Kai nugrausite bloka, daiktas atsiras jusu inventoriuje" +lore1 = "Vietoj to, kad daiktas iskrenta is bloko, daiktas pateks i jusu inventoriu, jei imanoma." +lore = ["Vietoj to, kad daiktas iskrenta is bloko, daiktas pateks i jusu inventoriu, jei imanoma."] [pickaxe.silk_spawner] - name = "Kirtiklis Silko-Narvas" - description = "Priverszia Narvus nukristi, kai buna sugriauti" - lore1 = "Padaro narvus sugriaunamais silko prisilietimu." - lore2 = "Padaro narvus sugriaunamais selinant." - lore = ["Padaro narvus sugriaunamais silko prisilietimu.", "Padaro narvus sugriaunamais selinant."] +name = "Kirtiklis Silko-Narvas" +description = "Priverszia Narvus nukristi, kai buna sugriauti" +lore1 = "Padaro narvus sugriaunamais silko prisilietimu." +lore2 = "Padaro narvus sugriaunamais selinant." +lore = ["Padaro narvus sugriaunamais silko prisilietimu.", "Padaro narvus sugriaunamais selinant."] [pickaxe.vein_miner] - name = "Veinkasejas" - description = "Leidzia sugriauti rudu blokus gysloje/klasteryje" - lore1 = "Selink, ir kask RUDAS" - lore2 = "vein kasybos diapazonas" - lore3 = "Sis igudis negrupuoja visu dropu kartu!" - lore = ["Selink, ir kask RUDAS", "vein kasybos diapazonas", "Sis igudis negrupuoja visu dropu kartu!"] +name = "Veinkasejas" +description = "Leidzia sugriauti rudu blokus gysloje/klasteryje" +lore1 = "Selink, ir kask RUDAS" +lore2 = "vein kasybos diapazonas" +lore3 = "Sis igudis negrupuoja visu dropu kartu!" +lore = ["Selink, ir kask RUDAS", "vein kasybos diapazonas", "Sis igudis negrupuoja visu dropu kartu!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Streles Susigrazinimas" - description = "Atgaukite streles po to, kai nuzudete priesa." - lore1 = "Galimybe susigrazinti streles ant smugio/nuzudymo" - lore2 = "Sansas: " - lore = ["Galimybe susigrazinti streles ant smugio/nuzudymo", "Sansas: "] +name = "Streles Susigrazinimas" +description = "Atgaukite streles po to, kai nuzudete priesa." +lore1 = "Galimybe susigrazinti streles ant smugio/nuzudymo" +lore2 = "Sansas: " +lore = ["Galimybe susigrazinti streles ant smugio/nuzudymo", "Sansas: "] [ranged.web_shot] - name = "Voratinkliu Spastai" - description = "Apjuociate voratinkliais aplink savo taikini, kai i juos pataikote!" - lore1 = "8 Voratinkliai aplink Sniego gniuzte ir galite mesti!" - lore2 = "narvo sekundziu, apytiksliai." - lore = ["8 Voratinkliai aplink Sniego gniuzte ir galite mesti!", "narvo sekundziu, apytiksliai."] +name = "Voratinkliu Spastai" +description = "Apjuociate voratinkliais aplink savo taikini, kai i juos pataikote!" +lore1 = "8 Voratinkliai aplink Sniego gniuzte ir galite mesti!" +lore2 = "narvo sekundziu, apytiksliai." +lore = ["8 Voratinkliai aplink Sniego gniuzte ir galite mesti!", "narvo sekundziu, apytiksliai."] [ranged.force_shot] - name = "Stiprus Suvis" - description = "Saudykite sviedinys toliau, greiciau!" - advancementname = "Tolimas Suvis" - advancementlore = "Nusaukite is daugiau nei 30 bloku!" - lore1 = "Sviedinio Greitis" - lore = ["Sviedinio Greitis"] +name = "Stiprus Suvis" +description = "Saudykite sviedinys toliau, greiciau!" +advancementname = "Tolimas Suvis" +advancementlore = "Nusaukite is daugiau nei 30 bloku!" +lore1 = "Sviedinio Greitis" +lore = ["Sviedinio Greitis"] [ranged.lunge_shot] - name = "Itūpstas" - description = "Krintant jusu streles sviedzia jus atsitiktine kryptimi" - lore1 = "Atsitiktinis Sprogimo Greitis" - lore = ["Atsitiktinis Sprogimo Greitis"] +name = "Itūpstas" +description = "Krintant jusu streles sviedzia jus atsitiktine kryptimi" +lore1 = "Atsitiktinis Sprogimo Greitis" +lore = ["Atsitiktinis Sprogimo Greitis"] [ranged.arrow_piercing] - name = "Pradurincios Streles" - description = "Prideda pradurima prie sviedinių! Saudykite kiaurai per dalykus!" - lore1 = "Taikiniu Pradurimas" - lore = ["Taikiniu Pradurimas"] +name = "Pradurincios Streles" +description = "Prideda pradurima prie sviedinių! Saudykite kiaurai per dalykus!" +lore1 = "Taikiniu Pradurimas" +lore = ["Taikiniu Pradurimas"] # rift [rift] [rift.remote_access] - name = "Nuotolinis Prisijungimas" - description = "Istraukite is tustumes ir supilkite i pazymeta talpykla." - lore1 = "Enderio Perlas + Kompasas = Relikvijoriaus Prievado Raktas" - lore2 = "Sis daiktas leidzia nuotoliniu budu pasiekti konteinerius" - lore3 = "Pagamine daikta paziurekite i ji, kad pamatytumete vartosena" - notcontainer = "Tai ne konteineris" - lore = ["Enderio Perlas + Kompasas = Relikvijoriaus Prievado Raktas", "Sis daiktas leidzia nuotoliniu budu pasiekti konteinerius", "Pagamine daikta paziurekite i ji, kad pamatytumete vartosena"] +name = "Nuotolinis Prisijungimas" +description = "Istraukite is tustumes ir supilkite i pazymeta talpykla." +lore1 = "Enderio Perlas + Kompasas = Relikvijoriaus Prievado Raktas" +lore2 = "Sis daiktas leidzia nuotoliniu budu pasiekti konteinerius" +lore3 = "Pagamine daikta paziurekite i ji, kad pamatytumete vartosena" +notcontainer = "Tai ne konteineris" +lore = ["Enderio Perlas + Kompasas = Relikvijoriaus Prievado Raktas", "Sis daiktas leidzia nuotoliniu budu pasiekti konteinerius", "Pagamine daikta paziurekite i ji, kad pamatytumete vartosena"] [rift.blink] - name = "Plysio Teleportacija" - description = "Momentine teleportacija trumpu nuotoliu, vos akimirksniu!" - lore1 = "Blokai teleportacijai (2x Vertikaliai)" - lore2 = "Sprinto metu: Dukart pasokite, kad " - lore3 = "Teleportuotumetės" - lore = ["Blokai teleportacijai (2x Vertikaliai)", "Sprinto metu: Dukart pasokite, kad ", "Teleportuotumetės"] +name = "Plysio Teleportacija" +description = "Momentine teleportacija trumpu nuotoliu, vos akimirksniu!" +lore1 = "Blokai teleportacijai (2x Vertikaliai)" +lore2 = "Sprinto metu: Dukart pasokite, kad " +lore3 = "Teleportuotumetės" +lore = ["Blokai teleportacijai (2x Vertikaliai)", "Sprinto metu: Dukart pasokite, kad ", "Teleportuotumetės"] [rift.chest] - name = "Lengva Enderio Skrynia" - description = "Atidarykite enderio skrynia kairiuoju peles mygtuku, laikydami ja rankoje." - lore1 = "Spauskite ant Enderio Skrynios rankoje, kad atidarytumete (deti nereikia)" - lore = ["Spauskite ant Enderio Skrynios rankoje, kad atidarytumete (deti nereikia)"] +name = "Lengva Enderio Skrynia" +description = "Atidarykite enderio skrynia kairiuoju peles mygtuku, laikydami ja rankoje." +lore1 = "Spauskite ant Enderio Skrynios rankoje, kad atidarytumete (deti nereikia)" +lore = ["Spauskite ant Enderio Skrynios rankoje, kad atidarytumete (deti nereikia)"] [rift.descent] - name = "Anti-Levitacija" - description = "Ar pavargote buti istrige ore? Sis igudis kaip tik jums!" - lore1 = "Tiesiog sneak'inkite, kad nusileistumete, ir krisite mazesniu nei iprasta greiciau!" - lore2 = "Atvesimas:" - lore = ["Tiesiog sneak'inkite, kad nusileistumete, ir krisite mazesniu nei iprasta greiciau!", "Atvesimas:"] +name = "Anti-Levitacija" +description = "Ar pavargote buti istrige ore? Sis igudis kaip tik jums!" +lore1 = "Tiesiog sneak'inkite, kad nusileistumete, ir krisite mazesniu nei iprasta greiciau!" +lore2 = "Atvesimas:" +lore = ["Tiesiog sneak'inkite, kad nusileistumete, ir krisite mazesniu nei iprasta greiciau!", "Atvesimas:"] [rift.gate] - name = "Plysio Vartai" - description = "Teleportuokites i pazymeta vieta." - lore1 = "GAMYBA: Smaragdas + Ametisto suke + Enderio Perlas" - lore2 = "Perskaitykite pries naudodami!" - lore3 = "5s uzdelsimas, " - lore4 = "galite mirti, kol esate sioje animacijoje" - lore = ["GAMYBA: Smaragdas + Ametisto suke + Enderio Perlas", "Perskaitykite pries naudodami!", "5s uzdelsimas, ", "galite mirti, kol esate sioje animacijoje"] +name = "Plysio Vartai" +description = "Teleportuokites i pazymeta vieta." +lore1 = "GAMYBA: Smaragdas + Ametisto suke + Enderio Perlas" +lore2 = "Perskaitykite pries naudodami!" +lore3 = "5s uzdelsimas, " +lore4 = "galite mirti, kol esate sioje animacijoje" +lore = ["GAMYBA: Smaragdas + Ametisto suke + Enderio Perlas", "Perskaitykite pries naudodami!", "5s uzdelsimas, ", "galite mirti, kol esate sioje animacijoje"] [rift.resist] - name = "Plysio Atsparumas" - description = "Igykite apsauga naudodami Enderio daiktus & galimybes" - lore1 = "+ Pasyvus: Suteikia apsauga, kai naudojate Plysio sugebejimus arba Enderio daiktus." - lore2 = "Neieina nesiojamoji Enderio Skrynia, ieina tik tai, ka galite vartoti" - lore = ["+ Pasyvus: Suteikia apsauga, kai naudojate Plysio sugebejimus arba Enderio daiktus.", "Neieina nesiojamoji Enderio Skrynia, ieina tik tai, ka galite vartoti"] +name = "Plysio Atsparumas" +description = "Igykite apsauga naudodami Enderio daiktus & galimybes" +lore1 = "+ Pasyvus: Suteikia apsauga, kai naudojate Plysio sugebejimus arba Enderio daiktus." +lore2 = "Neieina nesiojamoji Enderio Skrynia, ieina tik tai, ka galite vartoti" +lore = ["+ Pasyvus: Suteikia apsauga, kai naudojate Plysio sugebejimus arba Enderio daiktus.", "Neieina nesiojamoji Enderio Skrynia, ieina tik tai, ka galite vartoti"] [rift.visage] - name = "Plysio Vizazas" - description = "Neleidzia Endermenams tapti agresyviems, jei jusu inventoriuje yra Ender perlu." - lore1 = "Endermenai netaps agresyvus, jei jusu inventoriuje yra Ender perlu." - lore = ["Endermenai netaps agresyvus, jei jusu inventoriuje yra Ender perlu."] +name = "Plysio Vizazas" +description = "Neleidzia Endermenams tapti agresyviems, jei jusu inventoriuje yra Ender perlu." +lore1 = "Endermenai netaps agresyvus, jei jusu inventoriuje yra Ender perlu." +lore = ["Endermenai netaps agresyvus, jei jusu inventoriuje yra Ender perlu."] # seaborn [seaborn] [seaborn.oxygen] - name = "Organinis Deguonies Bakas" - description = "Sulaikykite daugiau deguonies savo mazuose plauciuose!" - lore1 = "Deguonies Talpos Padidejimas" - lore = ["Deguonies Talpos Padidejimas"] +name = "Organinis Deguonies Bakas" +description = "Sulaikykite daugiau deguonies savo mazuose plauciuose!" +lore1 = "Deguonies Talpos Padidejimas" +lore = ["Deguonies Talpos Padidejimas"] [seaborn.fishers_fantasy] - name = "Zvejolio Fantazija" - description = "Uzsidirbkite daugiau XP is zvejybos ir gaukite daugiau zuvies!" - lore1 = "Kiekviename lygyje yra galimybe gauti daugiau XP ir zuvies!" - lore = ["Kiekviename lygyje yra galimybe gauti daugiau XP ir zuvies!"] +name = "Zvejolio Fantazija" +description = "Uzsidirbkite daugiau XP is zvejybos ir gaukite daugiau zuvies!" +lore1 = "Kiekviename lygyje yra galimybe gauti daugiau XP ir zuvies!" +lore = ["Kiekviename lygyje yra galimybe gauti daugiau XP ir zuvies!"] [seaborn.haste] - name = "Vezlys Sachtininkas" - description = "Kasdami po vandeniu jus gaunate greita kasima!" - lore1 = "Greitas Kasimas 3 igijamas po vandeniu kasant (prisideda kartu ir Naro burtas), kai isnyksta vandens kvepavimo efektas!" - lore = ["Greitas Kasimas 3 igijamas po vandeniu kasant (prisideda kartu ir Naro burtas), kai isnyksta vandens kvepavimo efektas!"] +name = "Vezlys Sachtininkas" +description = "Kasdami po vandeniu jus gaunate greita kasima!" +lore1 = "Greitas Kasimas 3 igijamas po vandeniu kasant (prisideda kartu ir Naro burtas), kai isnyksta vandens kvepavimo efektas!" +lore = ["Greitas Kasimas 3 igijamas po vandeniu kasant (prisideda kartu ir Naro burtas), kai isnyksta vandens kvepavimo efektas!"] [seaborn.night_vision] - name = "Vezlio Regejimas" - description = "Budami po vandeniu, igyjate naktini matyma" - lore1 = "Tiesiog gaukite naktini matyma budami po vandeniu, kai isnyksta vandens kvepavimo efektas!" - lore = ["Tiesiog gaukite naktini matyma budami po vandeniu, kai isnyksta vandens kvepavimo efektas!"] +name = "Vezlio Regejimas" +description = "Budami po vandeniu, igyjate naktini matyma" +lore1 = "Tiesiog gaukite naktini matyma budami po vandeniu, kai isnyksta vandens kvepavimo efektas!" +lore = ["Tiesiog gaukite naktini matyma budami po vandeniu, kai isnyksta vandens kvepavimo efektas!"] [seaborn.dolphin_grace] - name = "Delfinu Malone" - description = "Plauk kaip delfinas, be delfinu" - lore1 = "+ Pasyvus: gauk " - lore2 = "x greiti (delfinu malone)" - lore3 = "tiksli vokieciu inzinerija - palaukite, tai negerai... Nesuderinama su Gilumu plaukimu" - lore = ["+ Pasyvus: gauk ", "x greiti (delfinu malone)", "tiksli vokieciu inzinerija - palaukite, tai negerai... Nesuderinama su Gilumu plaukimu"] +name = "Delfinu Malone" +description = "Plauk kaip delfinas, be delfinu" +lore1 = "+ Pasyvus: gauk " +lore2 = "x greiti (delfinu malone)" +lore3 = "tiksli vokieciu inzinerija - palaukite, tai negerai... Nesuderinama su Gilumu plaukimu" +lore = ["+ Pasyvus: gauk ", "x greiti (delfinu malone)", "tiksli vokieciu inzinerija - palaukite, tai negerai... Nesuderinama su Gilumu plaukimu"] # stealth [stealth] [stealth.ghost_armor] - name = "Vaiduoklio Sarvai" - description = "Letai generuojami sarvai, kai negaunama zalos, veikia tik 1 smugiui" - lore1 = "Max Sarvai" - lore2 = "Greitis" - lore = ["Max Sarvai", "Greitis"] +name = "Vaiduoklio Sarvai" +description = "Letai generuojami sarvai, kai negaunama zalos, veikia tik 1 smugiui" +lore1 = "Max Sarvai" +lore2 = "Greitis" +lore = ["Max Sarvai", "Greitis"] [stealth.night_vision] - name = "Slaptas Regejimas" - description = "Igykite naktini matyma selinant" - lore1 = "Gauk " - lore2 = "naktini matyma " - lore3 = "selinant" - lore = ["Gauk ", "naktini matyma ", "selinant"] +name = "Slaptas Regejimas" +description = "Igykite naktini matyma selinant" +lore1 = "Gauk " +lore2 = "naktini matyma " +lore3 = "selinant" +lore = ["Gauk ", "naktini matyma ", "selinant"] [stealth.snatch] - name = "Daiktu Paciupimas" - description = "Paciupkite numestus daiktus akimirksniu selinant!" - lore1 = "Paciupimo Spindulys" - lore = ["Paciupimo Spindulys"] +name = "Daiktu Paciupimas" +description = "Paciupkite numestus daiktus akimirksniu selinant!" +lore1 = "Paciupimo Spindulys" +lore = ["Paciupimo Spindulys"] [stealth.speed] - name = "Selinimo Greitis" - description = "Igykite greiti selinant" - lore1 = "Selinimo Greitis" - lore = ["Selinimo Greitis"] +name = "Selinimo Greitis" +description = "Igykite greiti selinant" +lore1 = "Selinimo Greitis" +lore = ["Selinimo Greitis"] [stealth.ender_veil] - name = "Enderveilis" - description = "Daugiau jokiu moliugu, kad butu isvengta Endermano ataku" - lore1 = "Isvengti endermano ataku selinant" - lore2 = "Isvengti visu endermano ataku" - lore = ["Isvengti endermano ataku selinant", "Isvengti visu endermano ataku"] +name = "Enderveilis" +description = "Daugiau jokiu moliugu, kad butu isvengta Endermano ataku" +lore1 = "Isvengti endermano ataku selinant" +lore2 = "Isvengti visu endermano ataku" +lore = ["Isvengti endermano ataku selinant", "Isvengti visu endermano ataku"] # sword [sword] [sword.machete] - name = "Macete" - description = "Lengvai nupjaukite lapija!" - lore1 = "Smugio Spindulys" - lore2 = "Pjovimo veikimo laiko tarpas" - lore3 = "Irankiu Susidevejimas" - lore = ["Smugio Spindulys", "Pjovimo veikimo laiko tarpas", "Irankiu Susidevejimas"] +name = "Macete" +description = "Lengvai nupjaukite lapija!" +lore1 = "Smugio Spindulys" +lore2 = "Pjovimo veikimo laiko tarpas" +lore3 = "Irankiu Susidevejimas" +lore = ["Smugio Spindulys", "Pjovimo veikimo laiko tarpas", "Irankiu Susidevejimas"] [sword.bloody_blade] - name = "Kruvini Asmenys" - description = "Smugiuojant kardu, sukelia kraujavima!" - lore1 = "Smugis i Gyva butybe savo kardu sukelia kraujavima" - lore2 = "Kraujavimo trukme" - lore3 = "Kraujavimo veikimo laiko tarpas" - lore = ["Smugis i Gyva butybe savo kardu sukelia kraujavima", "Kraujavimo trukme", "Kraujavimo veikimo laiko tarpas"] +name = "Kruvini Asmenys" +description = "Smugiuojant kardu, sukelia kraujavima!" +lore1 = "Smugis i Gyva butybe savo kardu sukelia kraujavima" +lore2 = "Kraujavimo trukme" +lore3 = "Kraujavimo veikimo laiko tarpas" +lore = ["Smugis i Gyva butybe savo kardu sukelia kraujavima", "Kraujavimo trukme", "Kraujavimo veikimo laiko tarpas"] [sword.poisoned_blade] - name = "Apnuodyti Asmenys" - description = "Smugiuojant kardu, sukelia nuodus!" - lore1 = "Smogdamas gyvai butybei savo kardu butybe apnuodijama" - lore2 = "Nuodu trukme" - lore3 = "Nuodu veikimo laiko tarpas" - lore = ["Smogdamas gyvai butybei savo kardu butybe apnuodijama", "Nuodu trukme", "Nuodu veikimo laiko tarpas"] +name = "Apnuodyti Asmenys" +description = "Smugiuojant kardu, sukelia nuodus!" +lore1 = "Smogdamas gyvai butybei savo kardu butybe apnuodijama" +lore2 = "Nuodu trukme" +lore3 = "Nuodu veikimo laiko tarpas" +lore = ["Smogdamas gyvai butybei savo kardu butybe apnuodijama", "Nuodu trukme", "Nuodu veikimo laiko tarpas"] # taming [taming] [taming.damage] - name = "Prisijaukinimo Zala" - description = "Padidinkite savo prijaukintam gyvunui daroma zala." - lore1 = "Padidejusi Zala" - lore = ["Padidejusi Zala"] +name = "Prisijaukinimo Zala" +description = "Padidinkite savo prijaukintam gyvunui daroma zala." +lore1 = "Padidejusi Zala" +lore = ["Padidejusi Zala"] [taming.health] - name = "Prisijaukinimo Sveikata" - description = "Pagerinkite savo prijaukintu gyvunu sveikata." - lore1 = "Padidejes gyvybiu skaicius" - lore = ["Padidejes gyvybiu skaicius"] +name = "Prisijaukinimo Sveikata" +description = "Pagerinkite savo prijaukintu gyvunu sveikata." +lore1 = "Padidejes gyvybiu skaicius" +lore = ["Padidejes gyvybiu skaicius"] [taming.regeneration] - name = "Prisijaukinimo Regeneracija" - description = "Padidinkite savo prijaukinto gyvuno regeneracija." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Prisijaukinimo Regeneracija" +description = "Padidinkite savo prijaukinto gyvuno regeneracija." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Spygliai" - description = "Atmuskite zala savo uzpuolikui!" - lore1 = "Atkersija atgal, kai uzpuola" - lore = ["Atkersija atgal, kai uzpuola"] +name = "Spygliai" +description = "Atmuskite zala savo uzpuolikui!" +lore1 = "Atkersija atgal, kai uzpuola" +lore = ["Atkersija atgal, kai uzpuola"] [tragoul.globe] - name = "Skausmo Rutulys" - description = "Padalykite padaryta zala pagal aplink jus esanciu priesu skaiciu!" - lore1 = "Kuo daugiau priesu aplink jus, tuo maziau zalos darote kiekvienam is ju." - lore2 = "Diapazonas: " - lore3 = "Prideta zala visiems subjektams: " - lore = ["Kuo daugiau priesu aplink jus, tuo maziau zalos darote kiekvienam is ju.", "Diapazonas: ", "Prideta zala visiems subjektams: "] +name = "Skausmo Rutulys" +description = "Padalykite padaryta zala pagal aplink jus esanciu priesu skaiciu!" +lore1 = "Kuo daugiau priesu aplink jus, tuo maziau zalos darote kiekvienam is ju." +lore2 = "Diapazonas: " +lore3 = "Prideta zala visiems subjektams: " +lore = ["Kuo daugiau priesu aplink jus, tuo maziau zalos darote kiekvienam is ju.", "Diapazonas: ", "Prideta zala visiems subjektams: "] [tragoul.healing] - name = "Skausmo Valia" - description = "Atkurkite sveikata pagal padaryta zala!" - lore1 = "Zaloti dalykus dar niekada nebuvo taip gerai! Gydymas nuo padarytos zalos" - lore2 = "Yra 3 sekundziu zalos langas, gydymo ir 1 sekundes atvesimas. " - lore3 = "Gydymas pagal zalos procenta: " - lore = ["Zaloti dalykus dar niekada nebuvo taip gerai! Gydymas nuo padarytos zalos", "Yra 3 sekundziu zalos langas, gydymo ir 1 sekundes atvesimas. ", "Gydymas pagal zalos procenta: "] +name = "Skausmo Valia" +description = "Atkurkite sveikata pagal padaryta zala!" +lore1 = "Zaloti dalykus dar niekada nebuvo taip gerai! Gydymas nuo padarytos zalos" +lore2 = "Yra 3 sekundziu zalos langas, gydymo ir 1 sekundes atvesimas. " +lore3 = "Gydymas pagal zalos procenta: " +lore = ["Zaloti dalykus dar niekada nebuvo taip gerai! Gydymas nuo padarytos zalos", "Yra 3 sekundziu zalos langas, gydymo ir 1 sekundes atvesimas. ", "Gydymas pagal zalos procenta: "] [tragoul.lance] - name = "Lavonu Ietys" - description = "Nuzudzius priesa arba atlikus gebejima nuzudyti priesa, atsiranda ietis, daranti zala netoliese esanciam priesui!" - lore1 = "Is bet ko, ka nuzudysite, ir jei sis gebejimas nuzudys priesa, bus ieskoma ieties." - lore2 = "Paaukokite dali savo gyvybes, kad sukurtumete ietis (tai gali jus nuzudyti)." - lore3 = "Max Ietys: 1 + " - lore = ["Is bet ko, ka nuzudysite, ir jei sis gebejimas nuzudys priesa, bus ieskoma ieties.", "Paaukokite dali savo gyvybes, kad sukurtumete ietis (tai gali jus nuzudyti).", "Max Ietys: 1 + "] +name = "Lavonu Ietys" +description = "Nuzudzius priesa arba atlikus gebejima nuzudyti priesa, atsiranda ietis, daranti zala netoliese esanciam priesui!" +lore1 = "Is bet ko, ka nuzudysite, ir jei sis gebejimas nuzudys priesa, bus ieskoma ieties." +lore2 = "Paaukokite dali savo gyvybes, kad sukurtumete ietis (tai gali jus nuzudyti)." +lore3 = "Max Ietys: 1 + " +lore = ["Is bet ko, ka nuzudysite, ir jei sis gebejimas nuzudys priesa, bus ieskoma ieties.", "Paaukokite dali savo gyvybes, kad sukurtumete ietis (tai gali jus nuzudyti).", "Max Ietys: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Stiklo Patranka" - description = "Bonusas uz beginkle zala, tuo mazesne jusu sarvu verte" - lore1 = "x Zala prie 0 sarvu" - lore2 = "Pagal lygi bonus zala" - lore = ["x Zala prie 0 sarvu", "Pagal lygi bonus zala"] +name = "Stiklo Patranka" +description = "Bonusas uz beginkle zala, tuo mazesne jusu sarvu verte" +lore1 = "x Zala prie 0 sarvu" +lore2 = "Pagal lygi bonus zala" +lore = ["x Zala prie 0 sarvu", "Pagal lygi bonus zala"] [unarmed.power] - name = "Beginkle Galia" - description = "Patobulinta beginkle zala." - lore1 = "Zala" - lore = ["Zala"] +name = "Beginkle Galia" +description = "Patobulinta beginkle zala." +lore1 = "Zala" +lore = ["Zala"] [unarmed.sucker_punch] - name = "Klastingas Smugis" - description = "Sprinto smugiai, bet mirtingesni." - lore1 = "Zala" - lore2 = "Zala dideja didejant greiciui smugiuojant" - lore = ["Zala", "Zala dideja didejant greiciui smugiuojant"] +name = "Klastingas Smugis" +description = "Sprinto smugiai, bet mirtingesni." +lore1 = "Zala" +lore2 = "Zala dideja didejant greiciui smugiuojant" +lore = ["Zala", "Zala dideja didejant greiciui smugiuojant"] diff --git a/src/main/resources/nl_NL.toml b/src/main/resources/nl_NL.toml index fa8fe8af0..8321f42ef 100644 --- a/src/main/resources/nl_NL.toml +++ b/src/main/resources/nl_NL.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "Moet bewegen!" - description = "Loop meer dan 1 kilometer (1.000 blokken)" +title = "Moet bewegen!" +description = "Loop meer dan 1 kilometer (1.000 blokken)" [advancement.challenge_sprint_5k] - title = "Sprint een 5K!" - description = "Loop meer dan 5 kilometer (5.000 blokken)" +title = "Sprint een 5K!" +description = "Loop meer dan 5 kilometer (5.000 blokken)" [advancement.challenge_sprint_50k] - title = "Zoom een 50K!" - description = "Loop meer dan 50 kilometer (50.000 blokken)" +title = "Zoom een 50K!" +description = "Loop meer dan 50 kilometer (50.000 blokken)" [advancement.challenge_sprint_500k] - title = "Doorkruis het heelal!!" - description = "Loop meer dan 500 kilometer (500.000 blokken)" +title = "Doorkruis het heelal!!" +description = "Loop meer dan 500 kilometer (500.000 blokken)" [advancement.challenge_sprint_marathon] - title = "Sprint een (letterlijke) Marathon!" - description = "Sprint meer dan 42.195 blokken!" +title = "Sprint een (letterlijke) Marathon!" +description = "Sprint meer dan 42.195 blokken!" [advancement.challenge_place_1k] - title = "Beginnende Bouwer!" - description = "Plaats 1.000 blokken" +title = "Beginnende Bouwer!" +description = "Plaats 1.000 blokken" [advancement.challenge_place_5k] - title = "Gemiddelde Bouwer!" - description = "Plaats 5.000 blokken" +title = "Gemiddelde Bouwer!" +description = "Plaats 5.000 blokken" [advancement.challenge_place_50k] - title = "Gevorderde Bouwer!" - description = "Plaats 50.000 blokken" +title = "Gevorderde Bouwer!" +description = "Plaats 50.000 blokken" [advancement.challenge_place_500k] - title = "Meester Bouwer!" - description = "Plaats 500.000 blokken" +title = "Meester Bouwer!" +description = "Plaats 500.000 blokken" [advancement.challenge_place_5m] - title = "Acoliet van de Symmetrie!" - description = "DE WERKELIJKHEID IS JOUW SPEELTUIN! (5 miljoen blokken)" +title = "Acoliet van de Symmetrie!" +description = "DE WERKELIJKHEID IS JOUW SPEELTUIN! (5 miljoen blokken)" [advancement.challenge_chop_1k] - title = "Beginnende Houthakker!" - description = "Hak 1.000 blokken" +title = "Beginnende Houthakker!" +description = "Hak 1.000 blokken" [advancement.challenge_chop_5k] - title = "Gemiddelde Houthakker!" - description = "Hak 5.000 blokken" +title = "Gemiddelde Houthakker!" +description = "Hak 5.000 blokken" [advancement.challenge_chop_50k] - title = "Gevorderde Houthakker!" - description = "Hak 50.000 blokken" +title = "Gevorderde Houthakker!" +description = "Hak 50.000 blokken" [advancement.challenge_chop_500k] - title = "Meester Houthakker!" - description = "Hak 500.000 blokken" +title = "Meester Houthakker!" +description = "Hak 500.000 blokken" [advancement.challenge_chop_5m] - title = "Jackson de Hond" - description = "De allerbeste brave jongen! (5 miljoen blokken)" +title = "Jackson de Hond" +description = "De allerbeste brave jongen! (5 miljoen blokken)" [advancement.challenge_block_1k] - title = "Amper Geblokkeerd!" - description = "Blokkeer 1000 treffers" +title = "Amper Geblokkeerd!" +description = "Blokkeer 1000 treffers" [advancement.challenge_block_5k] - title = "Blokkeren is Leuk!" - description = "Blokkeer 5000 treffers" +title = "Blokkeren is Leuk!" +description = "Blokkeer 5000 treffers" [advancement.challenge_block_50k] - title = "Blokkeren is mijn Leven!" - description = "Blokkeer 50.000 treffers" +title = "Blokkeren is mijn Leven!" +description = "Blokkeer 50.000 treffers" [advancement.challenge_block_500k] - title = "Blokkeren is mijn Doel!" - description = "Blokkeer 500.000 treffers" +title = "Blokkeren is mijn Doel!" +description = "Blokkeer 500.000 treffers" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Blokkeer 5.000.000 treffers" +title = "Die Hand Die Verletzt" +description = "Blokkeer 5.000.000 treffers" [advancement.challenge_brew_1k] - title = "Beginnende Alchemist!" - description = "Consumeer 1000 drankjes" +title = "Beginnende Alchemist!" +description = "Consumeer 1000 drankjes" [advancement.challenge_brew_5k] - title = "Gemiddelde Alchemist!" - description = "Consumeer 5000 drankjes" +title = "Gemiddelde Alchemist!" +description = "Consumeer 5000 drankjes" [advancement.challenge_brew_50k] - title = "Gevorderde Alchemist!" - description = "Consumeer 50.000 drankjes" +title = "Gevorderde Alchemist!" +description = "Consumeer 50.000 drankjes" [advancement.challenge_brew_500k] - title = "Meester Alchemist!" - description = "Consumeer 500.000 drankjes" +title = "Meester Alchemist!" +description = "Consumeer 500.000 drankjes" [advancement.challenge_brew_5m] - title = "De Alchemist" - description = "Consumeer 5.000.000 drankjes" +title = "De Alchemist" +description = "Consumeer 5.000.000 drankjes" [advancement.challenge_brewsplash_1k] - title = "Beginnende Drankje-Gooier!" - description = "Gooi 1000 drankjes" +title = "Beginnende Drankje-Gooier!" +description = "Gooi 1000 drankjes" [advancement.challenge_brewsplash_5k] - title = "Gemiddelde Drankje-Gooier!" - description = "Gooi 5000 drankjes" +title = "Gemiddelde Drankje-Gooier!" +description = "Gooi 5000 drankjes" [advancement.challenge_brewsplash_50k] - title = "Gevorderde Drankje-Gooier!" - description = "Gooi 50.000 drankjes" +title = "Gevorderde Drankje-Gooier!" +description = "Gooi 50.000 drankjes" [advancement.challenge_brewsplash_500k] - title = "Meester Drankje-Gooier!" - description = "Gooi 500.000 drankjes" +title = "Meester Drankje-Gooier!" +description = "Gooi 500.000 drankjes" [advancement.challenge_brewsplash_5m] - title = "De Gooi-Meester" - description = "Gooi 5.000.000 drankjes" +title = "De Gooi-Meester" +description = "Gooi 5.000.000 drankjes" [advancement.challenge_craft_1k] - title = "Handige Knutselaar!" - description = "Maak 1000 voorwerpen" +title = "Handige Knutselaar!" +description = "Maak 1000 voorwerpen" [advancement.challenge_craft_5k] - title = "Chagrijnige Knutselaar!" - description = "Maak 5000 voorwerpen" +title = "Chagrijnige Knutselaar!" +description = "Maak 5000 voorwerpen" [advancement.challenge_craft_50k] - title = "Onderdanige Knutselaar!" - description = "Maak 50.000 voorwerpen" +title = "Onderdanige Knutselaar!" +description = "Maak 50.000 voorwerpen" [advancement.challenge_craft_500k] - title = "Kakofone Knutselaar!" - description = "Maak 500.000 voorwerpen" +title = "Kakofone Knutselaar!" +description = "Maak 500.000 voorwerpen" [advancement.challenge_craft_5m] - title = "Rampzalige McCraftface" - description = "Maak 5.000.000 voorwerpen" +title = "Rampzalige McCraftface" +description = "Maak 5.000.000 voorwerpen" [advancement.challenge_enchant_1k] - title = "Beginnende Betoveraar!" - description = "Betover 1000 voorwerpen" +title = "Beginnende Betoveraar!" +description = "Betover 1000 voorwerpen" [advancement.challenge_enchant_5k] - title = "Gemiddelde Betoveraar!" - description = "Betover 5000 voorwerpen" +title = "Gemiddelde Betoveraar!" +description = "Betover 5000 voorwerpen" [advancement.challenge_enchant_50k] - title = "Gevorderde Betoveraar!" - description = "Betover 50.000 voorwerpen" +title = "Gevorderde Betoveraar!" +description = "Betover 50.000 voorwerpen" [advancement.challenge_enchant_500k] - title = "Meester Betoveraar!" - description = "Betover 500.000 voorwerpen" +title = "Meester Betoveraar!" +description = "Betover 500.000 voorwerpen" [advancement.challenge_enchant_5m] - title = "Raadselachtige Betoveraar" - description = "Betover 5.000.000 voorwerpen" +title = "Raadselachtige Betoveraar" +description = "Betover 5.000.000 voorwerpen" [advancement.challenge_excavate_1k] - title = "Enthousiaste Graver!" - description = "Graaf 1000 blokken op" +title = "Enthousiaste Graver!" +description = "Graaf 1000 blokken op" [advancement.challenge_excavate_5k] - title = "Gemiddelde Graver!" - description = "Graaf 5000 blokken op" +title = "Gemiddelde Graver!" +description = "Graaf 5000 blokken op" [advancement.challenge_excavate_50k] - title = "Gevorderde Graver!" - description = "Graaf 50.000 blokken op" +title = "Gevorderde Graver!" +description = "Graaf 50.000 blokken op" [advancement.challenge_excavate_500k] - title = "Meester Graver!" - description = "Graaf 500.000 blokken op" +title = "Meester Graver!" +description = "Graaf 500.000 blokken op" [advancement.challenge_excavate_5m] - title = "Raadselachtige Graver" - description = "Graaf 5.000.000 blokken op" +title = "Raadselachtige Graver" +description = "Graaf 5.000.000 blokken op" [advancement.horrible_person] - title = "Je bent een verschrikkelijk persoon" - description = "Ondoorgrondelijk, echt waar" +title = "Je bent een verschrikkelijk persoon" +description = "Ondoorgrondelijk, echt waar" [advancement.challenge_turtle_egg_smasher] - title = "Schildpadei-Verbrijzelaar!" - description = "Breek 100 schildpadeieren" +title = "Schildpadei-Verbrijzelaar!" +description = "Breek 100 schildpadeieren" [advancement.challenge_turtle_egg_annihilator] - title = "Schildpadei-Vernietiger!" - description = "Breek 500 schildpadeieren" +title = "Schildpadei-Vernietiger!" +description = "Breek 500 schildpadeieren" [advancement.challenge_novice_hunter] - title = "Beginnende Jager!" - description = "Dood 100 entiteiten" +title = "Beginnende Jager!" +description = "Dood 100 entiteiten" [advancement.challenge_intermediate_hunter] - title = "Gemiddelde Jager!" - description = "Dood 500 entiteiten" +title = "Gemiddelde Jager!" +description = "Dood 500 entiteiten" [advancement.challenge_advanced_hunter] - title = "Gevorderde Jager!" - description = "Dood 5000 entiteiten" +title = "Gevorderde Jager!" +description = "Dood 5000 entiteiten" [advancement.challenge_creeper_conqueror] - title = "Creeper-Veroveraar!" - description = "Dood 50 creepers" +title = "Creeper-Veroveraar!" +description = "Dood 50 creepers" [advancement.challenge_creeper_annihilator] - title = "Creeper-Vernietiger!" - description = "Dood 200 creepers" +title = "Creeper-Vernietiger!" +description = "Dood 200 creepers" [advancement.challenge_pickaxe_1k] - title = "Beginnende Mijnwerker" - description = "Breek 1000 blokken" +title = "Beginnende Mijnwerker" +description = "Breek 1000 blokken" [advancement.challenge_pickaxe_5k] - title = "Bekwame Mijnwerker" - description = "Breek 5000 blokken" +title = "Bekwame Mijnwerker" +description = "Breek 5000 blokken" [advancement.challenge_pickaxe_50k] - title = "Deskundige Mijnwerker" - description = "Breek 50.000 blokken" +title = "Deskundige Mijnwerker" +description = "Breek 50.000 blokken" [advancement.challenge_pickaxe_500k] - title = "Meester Mijnwerker" - description = "Breek 500.000 blokken" +title = "Meester Mijnwerker" +description = "Breek 500.000 blokken" [advancement.challenge_pickaxe_5m] - title = "Legendarische Mijnwerker" - description = "Breek 5.000.000 blokken" +title = "Legendarische Mijnwerker" +description = "Breek 5.000.000 blokken" [advancement.challenge_eat_100] - title = "Zoveel te eten!" - description = "Eet meer dan 100 items!" +title = "Zoveel te eten!" +description = "Eet meer dan 100 items!" [advancement.challenge_eat_1000] - title = "Onlesbare Honger!" - description = "Eet meer dan 1.000 items!" +title = "Onlesbare Honger!" +description = "Eet meer dan 1.000 items!" [advancement.challenge_eat_10000] - title = "EEUWIGDURENDE HONGER!" - description = "Eet meer dan 10.000 items!" +title = "EEUWIGDURENDE HONGER!" +description = "Eet meer dan 10.000 items!" [advancement.challenge_harvest_100] - title = "Volledige Oogst" - description = "Oogst meer dan 100 gewassen!" +title = "Volledige Oogst" +description = "Oogst meer dan 100 gewassen!" [advancement.challenge_harvest_1000] - title = "Grote Oogst" - description = "Oogst meer dan 1.000 gewassen!" +title = "Grote Oogst" +description = "Oogst meer dan 1.000 gewassen!" [advancement.challenge_swim_1nm] - title = "Menselijke Onderzeeboot!" - description = "Zwem 1 zeemijl (1.852 blokken)" +title = "Menselijke Onderzeeboot!" +description = "Zwem 1 zeemijl (1.852 blokken)" [advancement.challenge_sneak_1k] - title = "Kniepijn" - description = "Sluip meer dan een kilometer (1.000 blokken)" +title = "Kniepijn" +description = "Sluip meer dan een kilometer (1.000 blokken)" [advancement.challenge_sneak_5k] - title = "Schaduwloper" - description = "Sluip meer dan 5.000 blokken" +title = "Schaduwloper" +description = "Sluip meer dan 5.000 blokken" [advancement.challenge_sneak_20k] - title = "Geest" - description = "Sluip meer dan 20.000 blokken" +title = "Geest" +description = "Sluip meer dan 20.000 blokken" [advancement.challenge_swim_5k] - title = "Diepzeeduiker" - description = "Zwem meer dan 5.000 blokken" +title = "Diepzeeduiker" +description = "Zwem meer dan 5.000 blokken" [advancement.challenge_swim_20k] - title = "Poseidons Uitverkorene" - description = "Zwem meer dan 20.000 blokken" +title = "Poseidons Uitverkorene" +description = "Zwem meer dan 20.000 blokken" [advancement.challenge_sword_100] - title = "Eerste Bloed" - description = "Land 100 slagen met een zwaard" +title = "Eerste Bloed" +description = "Land 100 slagen met een zwaard" [advancement.challenge_sword_1k] - title = "Zwaardendanser" - description = "Land 1.000 slagen met een zwaard" +title = "Zwaardendanser" +description = "Land 1.000 slagen met een zwaard" [advancement.challenge_sword_10k] - title = "Duizend Sneden" - description = "Land 10.000 slagen met een zwaard" +title = "Duizend Sneden" +description = "Land 10.000 slagen met een zwaard" [advancement.challenge_unarmed_100] - title = "Kroegvechter" - description = "Land 100 ongewapende slagen" +title = "Kroegvechter" +description = "Land 100 ongewapende slagen" [advancement.challenge_unarmed_1k] - title = "IJzeren Vuisten" - description = "Land 1.000 ongewapende slagen" +title = "IJzeren Vuisten" +description = "Land 1.000 ongewapende slagen" [advancement.challenge_unarmed_10k] - title = "Een Klap" - description = "Land 10.000 ongewapende slagen" +title = "Een Klap" +description = "Land 10.000 ongewapende slagen" [advancement.challenge_trag_1k] - title = "Bloedprijs" - description = "Ontvang 1.000 schade" +title = "Bloedprijs" +description = "Ontvang 1.000 schade" [advancement.challenge_trag_10k] - title = "Rode Vloed" - description = "Ontvang 10.000 schade" +title = "Rode Vloed" +description = "Ontvang 10.000 schade" [advancement.challenge_trag_100k] - title = "Avatar van Lijden" - description = "Ontvang 100.000 schade" +title = "Avatar van Lijden" +description = "Ontvang 100.000 schade" [advancement.challenge_ranged_100] - title = "Schietoefening" - description = "Vuur 100 projectielen af" +title = "Schietoefening" +description = "Vuur 100 projectielen af" [advancement.challenge_ranged_1k] - title = "Havikoog" - description = "Vuur 1.000 projectielen af" +title = "Havikoog" +description = "Vuur 1.000 projectielen af" [advancement.challenge_ranged_10k] - title = "Pijlenstorm" - description = "Vuur 10.000 projectielen af" +title = "Pijlenstorm" +description = "Vuur 10.000 projectielen af" [advancement.challenge_chronos_1h] - title = "Tik Tak" - description = "Breng 1 uur online door" +title = "Tik Tak" +description = "Breng 1 uur online door" [advancement.challenge_chronos_24h] - title = "Zand der Tijd" - description = "Breng 24 uur online door" +title = "Zand der Tijd" +description = "Breng 24 uur online door" [advancement.challenge_chronos_168h] - title = "Tijdloos" - description = "Breng 168 uur (1 week) online door" +title = "Tijdloos" +description = "Breng 168 uur (1 week) online door" [advancement.challenge_nether_50] - title = "Poortwachter van de Hel" - description = "Versla 50 netherwezens" +title = "Poortwachter van de Hel" +description = "Versla 50 netherwezens" [advancement.challenge_nether_500] - title = "Wachter van de Afgrond" - description = "Versla 500 netherwezens" +title = "Wachter van de Afgrond" +description = "Versla 500 netherwezens" [advancement.challenge_nether_5k] - title = "Heer van de Nether" - description = "Versla 5.000 netherwezens" +title = "Heer van de Nether" +description = "Versla 5.000 netherwezens" [advancement.challenge_rift_50] - title = "Ruimtelijke Afwijking" - description = "Teleporteer 50 keer" +title = "Ruimtelijke Afwijking" +description = "Teleporteer 50 keer" [advancement.challenge_rift_500] - title = "Leegtewandelaar" - description = "Teleporteer 500 keer" +title = "Leegtewandelaar" +description = "Teleporteer 500 keer" [advancement.challenge_rift_5k] - title = "Tussen Werelden" - description = "Teleporteer 5.000 keer" +title = "Tussen Werelden" +description = "Teleporteer 5.000 keer" [advancement.challenge_taming_10] - title = "Dierenfluisteraar" - description = "Fok 10 dieren" +title = "Dierenfluisteraar" +description = "Fok 10 dieren" [advancement.challenge_taming_50] - title = "Roedelleider" - description = "Fok 50 dieren" +title = "Roedelleider" +description = "Fok 50 dieren" [advancement.challenge_taming_500] - title = "Beestenmeester" - description = "Fok 500 dieren" +title = "Beestenmeester" +description = "Fok 500 dieren" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Snelheidsdemon" - description = "Sprint meer dan 5 Kilometer (5,000 blokken)" +title = "Snelheidsdemon" +description = "Sprint meer dan 5 Kilometer (5,000 blokken)" [advancement.challenge_sprint_dist_50k] - title = "Bliksembenen" - description = "Sprint meer dan 50 Kilometer (50,000 blokken)" +title = "Bliksembenen" +description = "Sprint meer dan 50 Kilometer (50,000 blokken)" [advancement.challenge_agility_swim_1k] - title = "Waterloper" - description = "Zwem meer dan 1 Kilometer (1,000 blokken)" +title = "Waterloper" +description = "Zwem meer dan 1 Kilometer (1,000 blokken)" [advancement.challenge_agility_swim_10k] - title = "Aquatische Reiziger" - description = "Zwem meer dan 10 Kilometer (10,000 blokken)" +title = "Aquatische Reiziger" +description = "Zwem meer dan 10 Kilometer (10,000 blokken)" [advancement.challenge_fly_1k] - title = "Hemeldanser" - description = "Vlieg meer dan 1 Kilometer (1,000 blokken)" +title = "Hemeldanser" +description = "Vlieg meer dan 1 Kilometer (1,000 blokken)" [advancement.challenge_fly_10k] - title = "Windrijder" - description = "Vlieg meer dan 10 Kilometer (10,000 blokken)" +title = "Windrijder" +description = "Vlieg meer dan 10 Kilometer (10,000 blokken)" [advancement.challenge_agility_sneak_500] - title = "Stille Stappen" - description = "Sluip meer dan 500 blokken" +title = "Stille Stappen" +description = "Sluip meer dan 500 blokken" [advancement.challenge_agility_sneak_5k] - title = "Spookstappen" - description = "Sluip meer dan 5 Kilometer (5,000 blokken)" +title = "Spookstappen" +description = "Sluip meer dan 5 Kilometer (5,000 blokken)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Sloopploeg" - description = "Vernietig 500 blokken" +title = "Sloopploeg" +description = "Vernietig 500 blokken" [advancement.challenge_demolish_5k] - title = "Sloopkogel" - description = "Vernietig 5,000 blokken" +title = "Sloopkogel" +description = "Vernietig 5,000 blokken" [advancement.challenge_value_placed_10k] - title = "Waardevolle Bouwer" - description = "Plaats blokken ter waarde van 10,000" +title = "Waardevolle Bouwer" +description = "Plaats blokken ter waarde van 10,000" [advancement.challenge_value_placed_100k] - title = "Meester Architect" - description = "Plaats blokken ter waarde van 100,000" +title = "Meester Architect" +description = "Plaats blokken ter waarde van 100,000" [advancement.challenge_demolish_val_5k] - title = "Bergingsexpert" - description = "Berg 5,000 blokwaarde uit sloop" +title = "Bergingsexpert" +description = "Berg 5,000 blokwaarde uit sloop" [advancement.challenge_demolish_val_50k] - title = "Totale Afbraak" - description = "Berg 50,000 blokwaarde uit sloop" +title = "Totale Afbraak" +description = "Berg 50,000 blokwaarde uit sloop" [advancement.challenge_high_build_100] - title = "Hemelbouwer" - description = "Plaats 100 blokken boven Y=128" +title = "Hemelbouwer" +description = "Plaats 100 blokken boven Y=128" [advancement.challenge_high_build_1k] - title = "Wolkenarchitect" - description = "Plaats 1,000 blokken boven Y=128" +title = "Wolkenarchitect" +description = "Plaats 1,000 blokken boven Y=128" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Bijlzwaaier" - description = "Zwaai je bijl 500 keer" +title = "Bijlzwaaier" +description = "Zwaai je bijl 500 keer" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Zwaai je bijl 5,000 keer" +title = "Berserker" +description = "Zwaai je bijl 5,000 keer" [advancement.challenge_axe_damage_1k] - title = "Kliever" - description = "Deel 1,000 schade uit met bijlen" +title = "Kliever" +description = "Deel 1,000 schade uit met bijlen" [advancement.challenge_axe_damage_10k] - title = "Beulsbijl" - description = "Deel 10,000 schade uit met bijlen" +title = "Beulsbijl" +description = "Deel 10,000 schade uit met bijlen" [advancement.challenge_axe_value_5k] - title = "Houthandelaar" - description = "Oogst 5,000 waarde aan hout" +title = "Houthandelaar" +description = "Oogst 5,000 waarde aan hout" [advancement.challenge_axe_value_50k] - title = "Houtbaron" - description = "Oogst 50,000 waarde aan hout" +title = "Houtbaron" +description = "Oogst 50,000 waarde aan hout" [advancement.challenge_leaves_500] - title = "Bladblazer" - description = "Verwijder 500 bladblokken met een bijl" +title = "Bladblazer" +description = "Verwijder 500 bladblokken met een bijl" [advancement.challenge_leaves_5k] - title = "Ontbladeraar" - description = "Verwijder 5,000 bladblokken met een bijl" +title = "Ontbladeraar" +description = "Verwijder 5,000 bladblokken met een bijl" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Schadedemper" - description = "Blokkeer 1,000 schade met een schild" +title = "Schadedemper" +description = "Blokkeer 1,000 schade met een schild" [advancement.challenge_block_dmg_10k] - title = "Menselijk Schild" - description = "Blokkeer 10,000 schade met een schild" +title = "Menselijk Schild" +description = "Blokkeer 10,000 schade met een schild" [advancement.challenge_block_proj_100] - title = "Pijlafweerder" - description = "Blokkeer 100 projectielen met een schild" +title = "Pijlafweerder" +description = "Blokkeer 100 projectielen met een schild" [advancement.challenge_block_proj_1k] - title = "Projectielschild" - description = "Blokkeer 1,000 projectielen met een schild" +title = "Projectielschild" +description = "Blokkeer 1,000 projectielen met een schild" [advancement.challenge_block_melee_500] - title = "Pareermeester" - description = "Blokkeer 500 melee-aanvallen met een schild" +title = "Pareermeester" +description = "Blokkeer 500 melee-aanvallen met een schild" [advancement.challenge_block_melee_5k] - title = "IJzeren Vesting" - description = "Blokkeer 5,000 melee-aanvallen met een schild" +title = "IJzeren Vesting" +description = "Blokkeer 5,000 melee-aanvallen met een schild" [advancement.challenge_block_heavy_50] - title = "Tank" - description = "Blokkeer 50 zware aanvallen (meer dan 5 schade)" +title = "Tank" +description = "Blokkeer 50 zware aanvallen (meer dan 5 schade)" [advancement.challenge_block_heavy_500] - title = "Onbeweegbaar Object" - description = "Blokkeer 500 zware aanvallen (meer dan 5 schade)" +title = "Onbeweegbaar Object" +description = "Blokkeer 500 zware aanvallen (meer dan 5 schade)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "Brouwerij Opzet" - description = "Plaats 10 brouwerstandaarden" +title = "Brouwerij Opzet" +description = "Plaats 10 brouwerstandaarden" [advancement.challenge_brew_stands_50] - title = "Drankjesfabriek" - description = "Plaats 50 brouwerstandaarden" +title = "Drankjesfabriek" +description = "Plaats 50 brouwerstandaarden" [advancement.challenge_brew_strong_25] - title = "Sterk Brouwsel" - description = "Verbruik 25 verbeterde drankjes" +title = "Sterk Brouwsel" +description = "Verbruik 25 verbeterde drankjes" [advancement.challenge_brew_strong_250] - title = "Maximale Kracht" - description = "Verbruik 250 verbeterde drankjes" +title = "Maximale Kracht" +description = "Verbruik 250 verbeterde drankjes" [advancement.challenge_brew_splash_hits_50] - title = "Spatzone" - description = "Raak 50 entiteiten met spatdrankjes" +title = "Spatzone" +description = "Raak 50 entiteiten met spatdrankjes" [advancement.challenge_brew_splash_hits_500] - title = "Pestdokter" - description = "Raak 500 entiteiten met spatdrankjes" +title = "Pestdokter" +description = "Raak 500 entiteiten met spatdrankjes" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Rusteloze Ziel" - description = "Reis 1 Kilometer terwijl je actief bent" +title = "Rusteloze Ziel" +description = "Reis 1 Kilometer terwijl je actief bent" [advancement.challenge_active_dist_10k] - title = "Padvinder" - description = "Reis 10 Kilometer terwijl je actief bent" +title = "Padvinder" +description = "Reis 10 Kilometer terwijl je actief bent" [advancement.challenge_active_dist_100k] - title = "Eeuwige Beweging" - description = "Reis 100 Kilometer terwijl je actief bent" +title = "Eeuwige Beweging" +description = "Reis 100 Kilometer terwijl je actief bent" [advancement.challenge_beds_10] - title = "Vroege Vogel" - description = "Slaap 10 keer in een bed" +title = "Vroege Vogel" +description = "Slaap 10 keer in een bed" [advancement.challenge_beds_100] - title = "Tijdspringer" - description = "Slaap 100 keer in een bed" +title = "Tijdspringer" +description = "Slaap 100 keer in een bed" [advancement.challenge_chronos_tp_50] - title = "Tijdverschuiving" - description = "Teleporteer 50 keer" +title = "Tijdverschuiving" +description = "Teleporteer 50 keer" [advancement.challenge_chronos_tp_500] - title = "Tijdkronkel" - description = "Teleporteer 500 keer" +title = "Tijdkronkel" +description = "Teleporteer 500 keer" [advancement.challenge_chronos_deaths_10] - title = "Sterfelijk" - description = "Sterf 10 keer" +title = "Sterfelijk" +description = "Sterf 10 keer" [advancement.challenge_chronos_deaths_100] - title = "Doodstarter" - description = "Sterf 100 keer" +title = "Doodstarter" +description = "Sterf 100 keer" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Ambachtelijke Waarde" - description = "Maak items ter waarde van 10,000 totaal" +title = "Ambachtelijke Waarde" +description = "Maak items ter waarde van 10,000 totaal" [advancement.challenge_craft_value_100k] - title = "Ambachtsman" - description = "Maak items ter waarde van 100,000 totaal" +title = "Ambachtsman" +description = "Maak items ter waarde van 100,000 totaal" [advancement.challenge_craft_tools_25] - title = "Gereedschapmaker" - description = "Maak 25 gereedschappen" +title = "Gereedschapmaker" +description = "Maak 25 gereedschappen" [advancement.challenge_craft_tools_250] - title = "Meestersmid" - description = "Maak 250 gereedschappen" +title = "Meestersmid" +description = "Maak 250 gereedschappen" [advancement.challenge_craft_armor_25] - title = "Wapensmid" - description = "Maak 25 stukken wapenrusting" +title = "Wapensmid" +description = "Maak 25 stukken wapenrusting" [advancement.challenge_craft_armor_250] - title = "Meester Wapensmid" - description = "Maak 250 stukken wapenrusting" +title = "Meester Wapensmid" +description = "Maak 250 stukken wapenrusting" [advancement.challenge_craft_mass_25k] - title = "Massaproductie" - description = "Maak 25,000 items" +title = "Massaproductie" +description = "Maak 25,000 items" [advancement.challenge_craft_mass_250k] - title = "Industriele Revolutie" - description = "Maak 250,000 items" +title = "Industriele Revolutie" +description = "Maak 250,000 items" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Verzamelaar" - description = "Ontdek 50 unieke items" +title = "Verzamelaar" +description = "Ontdek 50 unieke items" [advancement.challenge_discover_items_250] - title = "Catalogiseerder" - description = "Ontdek 250 unieke items" +title = "Catalogiseerder" +description = "Ontdek 250 unieke items" [advancement.challenge_discover_blocks_50] - title = "Landmeter" - description = "Ontdek 50 unieke blokken" +title = "Landmeter" +description = "Ontdek 50 unieke blokken" [advancement.challenge_discover_blocks_250] - title = "Geoloog" - description = "Ontdek 250 unieke blokken" +title = "Geoloog" +description = "Ontdek 250 unieke blokken" [advancement.challenge_discover_mobs_25] - title = "Waarnemer" - description = "Ontdek 25 unieke mobs" +title = "Waarnemer" +description = "Ontdek 25 unieke mobs" [advancement.challenge_discover_mobs_75] - title = "Naturalist" - description = "Ontdek 75 unieke mobs" +title = "Naturalist" +description = "Ontdek 75 unieke mobs" [advancement.challenge_discover_biomes_10] - title = "Zwerver" - description = "Ontdek 10 unieke biomen" +title = "Zwerver" +description = "Ontdek 10 unieke biomen" [advancement.challenge_discover_biomes_40] - title = "Wereldreiziger" - description = "Ontdek 40 unieke biomen" +title = "Wereldreiziger" +description = "Ontdek 40 unieke biomen" [advancement.challenge_discover_foods_10] - title = "Fijnproever" - description = "Ontdek 10 unieke voedselsoorten" +title = "Fijnproever" +description = "Ontdek 10 unieke voedselsoorten" [advancement.challenge_discover_foods_30] - title = "Culinair Meester" - description = "Ontdek 30 unieke voedselsoorten" +title = "Culinair Meester" +description = "Ontdek 30 unieke voedselsoorten" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Krachtwever" - description = "Verzamel 100 betoveringskracht" +title = "Krachtwever" +description = "Verzamel 100 betoveringskracht" [advancement.challenge_enchant_power_1k] - title = "Arcane Meester" - description = "Verzamel 1,000 betoveringskracht" +title = "Arcane Meester" +description = "Verzamel 1,000 betoveringskracht" [advancement.challenge_enchant_levels_1k] - title = "Levelverbruiker" - description = "Besteed 1,000 ervaringsniveaus aan betoveringen" +title = "Levelverbruiker" +description = "Besteed 1,000 ervaringsniveaus aan betoveringen" [advancement.challenge_enchant_levels_10k] - title = "Ervaringsput" - description = "Besteed 10,000 ervaringsniveaus aan betoveringen" +title = "Ervaringsput" +description = "Besteed 10,000 ervaringsniveaus aan betoveringen" [advancement.challenge_enchant_high_25] - title = "Hoge Inzet" - description = "Voer 25 betoveringen op maximaal niveau uit" +title = "Hoge Inzet" +description = "Voer 25 betoveringen op maximaal niveau uit" [advancement.challenge_enchant_high_250] - title = "Legendarische Betoveraar" - description = "Voer 250 betoveringen op maximaal niveau uit" +title = "Legendarische Betoveraar" +description = "Voer 250 betoveringen op maximaal niveau uit" [advancement.challenge_enchant_total_500] - title = "Levelverbrander" - description = "Besteed 500 totale niveaus aan alle betoveringen" +title = "Levelverbrander" +description = "Besteed 500 totale niveaus aan alle betoveringen" [advancement.challenge_enchant_total_5k] - title = "Arcane Investering" - description = "Besteed 5,000 totale niveaus aan alle betoveringen" +title = "Arcane Investering" +description = "Besteed 5,000 totale niveaus aan alle betoveringen" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Graver" - description = "Zwaai je schop 500 keer" +title = "Graver" +description = "Zwaai je schop 500 keer" [advancement.challenge_dig_swing_5k] - title = "Graafmachine" - description = "Zwaai je schop 5,000 keer" +title = "Graafmachine" +description = "Zwaai je schop 5,000 keer" [advancement.challenge_dig_damage_1k] - title = "Schopridder" - description = "Deel 1,000 schade uit met een schop" +title = "Schopridder" +description = "Deel 1,000 schade uit met een schop" [advancement.challenge_dig_damage_10k] - title = "Schopmeester" - description = "Deel 10,000 schade uit met een schop" +title = "Schopmeester" +description = "Deel 10,000 schade uit met een schop" [advancement.challenge_dig_value_5k] - title = "Aardhandelaar" - description = "Graaf 5,000 waarde aan blokken op" +title = "Aardhandelaar" +description = "Graaf 5,000 waarde aan blokken op" [advancement.challenge_dig_value_50k] - title = "Aardbaron" - description = "Graaf 50,000 waarde aan blokken op" +title = "Aardbaron" +description = "Graaf 50,000 waarde aan blokken op" [advancement.challenge_dig_gravel_500] - title = "Grindmaler" - description = "Graaf 500 grind-, zand- of kleiblekken" +title = "Grindmaler" +description = "Graaf 500 grind-, zand- of kleiblekken" [advancement.challenge_dig_gravel_5k] - title = "Zandzifter" - description = "Graaf 5,000 grind-, zand- of kleiblekken" +title = "Zandzifter" +description = "Graaf 5,000 grind-, zand- of kleiblekken" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Zaadzaaier" - description = "Plant 100 gewassen" +title = "Zaadzaaier" +description = "Plant 100 gewassen" [advancement.challenge_plant_1k] - title = "Groene Vingers" - description = "Plant 1,000 gewassen" +title = "Groene Vingers" +description = "Plant 1,000 gewassen" [advancement.challenge_plant_5k] - title = "Landbouwbaron" - description = "Plant 5,000 gewassen" +title = "Landbouwbaron" +description = "Plant 5,000 gewassen" [advancement.challenge_compost_50] - title = "Recycler" - description = "Composteer 50 items" +title = "Recycler" +description = "Composteer 50 items" [advancement.challenge_compost_500] - title = "Bodemverrijker" - description = "Composteer 500 items" +title = "Bodemverrijker" +description = "Composteer 500 items" [advancement.challenge_shear_50] - title = "Schaapscheerder" - description = "Scheer 50 entiteiten" +title = "Schaapscheerder" +description = "Scheer 50 entiteiten" [advancement.challenge_shear_250] - title = "Kudde Meester" - description = "Scheer 250 entiteiten" +title = "Kudde Meester" +description = "Scheer 250 entiteiten" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Jager" - description = "Versla 500 wezens" +title = "Jager" +description = "Versla 500 wezens" [advancement.challenge_kills_5k] - title = "Beul" - description = "Versla 5,000 wezens" +title = "Beul" +description = "Versla 5,000 wezens" [advancement.challenge_boss_1] - title = "Baasuitdager" - description = "Versla een baasmob" +title = "Baasuitdager" +description = "Versla een baasmob" [advancement.challenge_boss_10] - title = "Legendedoder" - description = "Versla 10 baasmobs" +title = "Legendedoder" +description = "Versla 10 baasmobs" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Verwelkt" - description = "Doorsta 500 witherschade" +title = "Verwelkt" +description = "Doorsta 500 witherschade" [advancement.challenge_wither_dmg_5k] - title = "Plaagoverlevende" - description = "Doorsta 5,000 witherschade" +title = "Plaagoverlevende" +description = "Doorsta 5,000 witherschade" [advancement.challenge_wither_skel_25] - title = "Bottenverzamelaar" - description = "Versla 25 witherskeletten" +title = "Bottenverzamelaar" +description = "Versla 25 witherskeletten" [advancement.challenge_wither_skel_250] - title = "Skeletvloek" - description = "Versla 250 witherskeletten" +title = "Skeletvloek" +description = "Versla 250 witherskeletten" [advancement.challenge_wither_boss_1] - title = "Withervernietiger" - description = "Versla de Wither" +title = "Withervernietiger" +description = "Versla de Wither" [advancement.challenge_wither_boss_10] - title = "Netherheerser" - description = "Versla de Wither 10 keer" +title = "Netherheerser" +description = "Versla de Wither 10 keer" [advancement.challenge_roses_10] - title = "Dodentuinier" - description = "Breek 10 witherrosen" +title = "Dodentuinier" +description = "Breek 10 witherrosen" [advancement.challenge_roses_100] - title = "Plaagverzamelaar" - description = "Breek 100 witherrosen" +title = "Plaagverzamelaar" +description = "Breek 100 witherrosen" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Mijnwerkersarm" - description = "Zwaai je houweel 500 keer" +title = "Mijnwerkersarm" +description = "Zwaai je houweel 500 keer" [advancement.challenge_pick_swing_5k] - title = "Tunnelgraver" - description = "Zwaai je houweel 5,000 keer" +title = "Tunnelgraver" +description = "Zwaai je houweel 5,000 keer" [advancement.challenge_pick_damage_1k] - title = "Houweelvechter" - description = "Deel 1,000 schade uit met een houweel" +title = "Houweelvechter" +description = "Deel 1,000 schade uit met een houweel" [advancement.challenge_pick_damage_10k] - title = "Oorlogshouweel" - description = "Deel 10,000 schade uit met een houweel" +title = "Oorlogshouweel" +description = "Deel 10,000 schade uit met een houweel" [advancement.challenge_pick_value_5k] - title = "Edelsteenvinder" - description = "Mijn 5,000 waarde aan blokken" +title = "Edelsteenvinder" +description = "Mijn 5,000 waarde aan blokken" [advancement.challenge_pick_value_50k] - title = "Ertsbaron" - description = "Mijn 50,000 waarde aan blokken" +title = "Ertsbaron" +description = "Mijn 50,000 waarde aan blokken" [advancement.challenge_pick_ores_500] - title = "Goudzoeker" - description = "Mijn 500 ertsblokken" +title = "Goudzoeker" +description = "Mijn 500 ertsblokken" [advancement.challenge_pick_ores_5k] - title = "Meestermijnwerker" - description = "Mijn 5,000 ertsblokken" +title = "Meestermijnwerker" +description = "Mijn 5,000 ertsblokken" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Scherpschutter" - description = "Deel 1,000 afstandsschade uit" +title = "Scherpschutter" +description = "Deel 1,000 afstandsschade uit" [advancement.challenge_ranged_dmg_10k] - title = "Dodelijke Boogschutter" - description = "Deel 10,000 afstandsschade uit" +title = "Dodelijke Boogschutter" +description = "Deel 10,000 afstandsschade uit" [advancement.challenge_ranged_dist_5k] - title = "Lange Afstand" - description = "Schiet projectielen over een totale afstand van 5,000 blokken" +title = "Lange Afstand" +description = "Schiet projectielen over een totale afstand van 5,000 blokken" [advancement.challenge_ranged_dist_50k] - title = "Kilometerschutter" - description = "Schiet projectielen over een totale afstand van 50,000 blokken" +title = "Kilometerschutter" +description = "Schiet projectielen over een totale afstand van 50,000 blokken" [advancement.challenge_ranged_kills_50] - title = "Boogschutter" - description = "Dood 50 mobs met afstandswapens" +title = "Boogschutter" +description = "Dood 50 mobs met afstandswapens" [advancement.challenge_ranged_kills_500] - title = "Meesterboogschutter" - description = "Dood 500 mobs met afstandswapens" +title = "Meesterboogschutter" +description = "Dood 500 mobs met afstandswapens" [advancement.challenge_longshot_25] - title = "Sluipschutter" - description = "Land 25 langeafstandsschoten (meer dan 30 blokken)" +title = "Sluipschutter" +description = "Land 25 langeafstandsschoten (meer dan 30 blokken)" [advancement.challenge_longshot_250] - title = "Arendsoog" - description = "Land 250 langeafstandsschoten (meer dan 30 blokken)" +title = "Arendsoog" +description = "Land 250 langeafstandsschoten (meer dan 30 blokken)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "Parelgooier" - description = "Gooi 50 enderparels" +title = "Parelgooier" +description = "Gooi 50 enderparels" [advancement.challenge_rift_pearls_500] - title = "Teleportverslaafde" - description = "Gooi 500 enderparels" +title = "Teleportverslaafde" +description = "Gooi 500 enderparels" [advancement.challenge_rift_enderman_50] - title = "Endermanjager" - description = "Versla 50 endermannen" +title = "Endermanjager" +description = "Versla 50 endermannen" [advancement.challenge_rift_enderman_500] - title = "Leegtesluiper" - description = "Versla 500 endermannen" +title = "Leegtesluiper" +description = "Versla 500 endermannen" [advancement.challenge_rift_dragon_500] - title = "Drakenvechter" - description = "Deel 500 schade toe aan de Ender Draak" +title = "Drakenvechter" +description = "Deel 500 schade toe aan de Ender Draak" [advancement.challenge_rift_dragon_5k] - title = "Drakenvloek" - description = "Deel 5,000 schade toe aan de Ender Draak" +title = "Drakenvloek" +description = "Deel 5,000 schade toe aan de Ender Draak" [advancement.challenge_rift_crystal_10] - title = "Kristalbreker" - description = "Vernietig 10 endkristallen" +title = "Kristalbreker" +description = "Vernietig 10 endkristallen" [advancement.challenge_rift_crystal_100] - title = "Endvernietiger" - description = "Vernietig 100 endkristallen" +title = "Endvernietiger" +description = "Vernietig 100 endkristallen" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Hengelaar" - description = "Vang 25 vissen" +title = "Hengelaar" +description = "Vang 25 vissen" [advancement.challenge_fish_250] - title = "Meestervisser" - description = "Vang 250 vissen" +title = "Meestervisser" +description = "Vang 250 vissen" [advancement.challenge_drowned_25] - title = "Verdronkenjager" - description = "Versla 25 verdronkenen" +title = "Verdronkenjager" +description = "Versla 25 verdronkenen" [advancement.challenge_drowned_250] - title = "Oceaanreiniger" - description = "Versla 250 verdronkenen" +title = "Oceaanreiniger" +description = "Versla 250 verdronkenen" [advancement.challenge_guardian_10] - title = "Bewakerdoder" - description = "Versla 10 bewakers" +title = "Bewakerdoder" +description = "Versla 10 bewakers" [advancement.challenge_guardian_100] - title = "Tempelplunderaar" - description = "Versla 100 bewakers" +title = "Tempelplunderaar" +description = "Versla 100 bewakers" [advancement.challenge_underwater_blocks_100] - title = "Onderwatermijnwerker" - description = "Breek 100 blokken onder water" +title = "Onderwatermijnwerker" +description = "Breek 100 blokken onder water" [advancement.challenge_underwater_blocks_1k] - title = "Aquatisch Ingenieur" - description = "Breek 1,000 blokken onder water" +title = "Aquatisch Ingenieur" +description = "Breek 1,000 blokken onder water" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Rugsteker" - description = "Deel 500 schade uit terwijl je sluipt" +title = "Rugsteker" +description = "Deel 500 schade uit terwijl je sluipt" [advancement.challenge_stealth_dmg_5k] - title = "Stille Moordenaar" - description = "Deel 5,000 schade uit terwijl je sluipt" +title = "Stille Moordenaar" +description = "Deel 5,000 schade uit terwijl je sluipt" [advancement.challenge_stealth_kills_10] - title = "Sluipmoordenaar" - description = "Dood 10 mobs terwijl je sluipt" +title = "Sluipmoordenaar" +description = "Dood 10 mobs terwijl je sluipt" [advancement.challenge_stealth_kills_100] - title = "Schaduwmaaier" - description = "Dood 100 mobs terwijl je sluipt" +title = "Schaduwmaaier" +description = "Dood 100 mobs terwijl je sluipt" [advancement.challenge_stealth_time_1h] - title = "Geduldig" - description = "Breng 1 uur sluipend door (3,600 seconden)" +title = "Geduldig" +description = "Breng 1 uur sluipend door (3,600 seconden)" [advancement.challenge_stealth_time_10h] - title = "Meester der Schaduwen" - description = "Breng 10 uur sluipend door (36,000 seconden)" +title = "Meester der Schaduwen" +description = "Breng 10 uur sluipend door (36,000 seconden)" [advancement.challenge_stealth_arrows_50] - title = "Stille Boogschutter" - description = "Schiet 50 pijlen terwijl je sluipt" +title = "Stille Boogschutter" +description = "Schiet 50 pijlen terwijl je sluipt" [advancement.challenge_stealth_arrows_500] - title = "Spookboogschutter" - description = "Schiet 500 pijlen terwijl je sluipt" +title = "Spookboogschutter" +description = "Schiet 500 pijlen terwijl je sluipt" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Zwaardleerling" - description = "Deel 1,000 schade uit met zwaarden" +title = "Zwaardleerling" +description = "Deel 1,000 schade uit met zwaarden" [advancement.challenge_sword_dmg_10k] - title = "Zwaardvechter" - description = "Deel 10,000 schade uit met zwaarden" +title = "Zwaardvechter" +description = "Deel 10,000 schade uit met zwaarden" [advancement.challenge_sword_kills_50] - title = "Duelleur" - description = "Dood 50 mobs met zwaarden" +title = "Duelleur" +description = "Dood 50 mobs met zwaarden" [advancement.challenge_sword_kills_500] - title = "Gladiator" - description = "Dood 500 mobs met zwaarden" +title = "Gladiator" +description = "Dood 500 mobs met zwaarden" [advancement.challenge_sword_crit_50] - title = "Kritieke Klap" - description = "Land 50 kritieke treffers met zwaarden" +title = "Kritieke Klap" +description = "Land 50 kritieke treffers met zwaarden" [advancement.challenge_sword_crit_500] - title = "Precisiemeester" - description = "Land 500 kritieke treffers met zwaarden" +title = "Precisiemeester" +description = "Land 500 kritieke treffers met zwaarden" [advancement.challenge_sword_heavy_25] - title = "Zware Klap" - description = "Land 25 zware treffers met zwaarden (meer dan 8 schade)" +title = "Zware Klap" +description = "Land 25 zware treffers met zwaarden (meer dan 8 schade)" [advancement.challenge_sword_heavy_250] - title = "Verwoestende Slag" - description = "Land 250 zware treffers met zwaarden (meer dan 8 schade)" +title = "Verwoestende Slag" +description = "Land 250 zware treffers met zwaarden (meer dan 8 schade)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Beestentrainer" - description = "Je huisdieren delen 500 totale schade uit" +title = "Beestentrainer" +description = "Je huisdieren delen 500 totale schade uit" [advancement.challenge_pet_dmg_5k] - title = "Oorlogsmeester" - description = "Je huisdieren delen 5,000 totale schade uit" +title = "Oorlogsmeester" +description = "Je huisdieren delen 5,000 totale schade uit" [advancement.challenge_tamed_10] - title = "Dierenvriend" - description = "Tem 10 dieren" +title = "Dierenvriend" +description = "Tem 10 dieren" [advancement.challenge_tamed_100] - title = "Dierentuinverzorger" - description = "Tem 100 dieren" +title = "Dierentuinverzorger" +description = "Tem 100 dieren" [advancement.challenge_pet_kills_25] - title = "Roedeltactiek" - description = "Je huisdieren verslaan 25 wezens" +title = "Roedeltactiek" +description = "Je huisdieren verslaan 25 wezens" [advancement.challenge_pet_kills_250] - title = "Alfacommandant" - description = "Je huisdieren verslaan 250 wezens" +title = "Alfacommandant" +description = "Je huisdieren verslaan 250 wezens" [advancement.challenge_taming_2500] - title = "Fokexpert" - description = "Fok 2,500 dieren" +title = "Fokexpert" +description = "Fok 2,500 dieren" [advancement.challenge_taming_25k] - title = "Geneticameester" - description = "Fok 25,000 dieren" +title = "Geneticameester" +description = "Fok 25,000 dieren" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Bokszak" - description = "Ontvang 500 treffers" +title = "Bokszak" +description = "Ontvang 500 treffers" [advancement.challenge_trag_hits_5k] - title = "Gulzig voor Straf" - description = "Ontvang 5,000 treffers" +title = "Gulzig voor Straf" +description = "Ontvang 5,000 treffers" [advancement.challenge_trag_deaths_10] - title = "Negen Levens" - description = "Sterf 10 keer" +title = "Negen Levens" +description = "Sterf 10 keer" [advancement.challenge_trag_deaths_100] - title = "Terugkerende Nachtmerrie" - description = "Sterf 100 keer" +title = "Terugkerende Nachtmerrie" +description = "Sterf 100 keer" [advancement.challenge_trag_fire_500] - title = "Brandslachtoffer" - description = "Doorsta 500 vuurschade" +title = "Brandslachtoffer" +description = "Doorsta 500 vuurschade" [advancement.challenge_trag_fire_5k] - title = "Feniks" - description = "Doorsta 5,000 vuurschade" +title = "Feniks" +description = "Doorsta 5,000 vuurschade" [advancement.challenge_trag_fall_500] - title = "Zwaartekrachttest" - description = "Doorsta 500 valschade" +title = "Zwaartekrachttest" +description = "Doorsta 500 valschade" [advancement.challenge_trag_fall_5k] - title = "Eindsnelheid" - description = "Doorsta 5,000 valschade" +title = "Eindsnelheid" +description = "Doorsta 5,000 valschade" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Vechtersbaas" - description = "Deel 1,000 schade uit met blote vuisten" +title = "Vechtersbaas" +description = "Deel 1,000 schade uit met blote vuisten" [advancement.challenge_unarmed_dmg_10k] - title = "Vechtsportmeester" - description = "Deel 10,000 schade uit met blote vuisten" +title = "Vechtsportmeester" +description = "Deel 10,000 schade uit met blote vuisten" [advancement.challenge_unarmed_kills_25] - title = "Blote Knokkel" - description = "Dood 25 mobs met blote vuisten" +title = "Blote Knokkel" +description = "Dood 25 mobs met blote vuisten" [advancement.challenge_unarmed_kills_250] - title = "Vuist der Legende" - description = "Dood 250 mobs met blote vuisten" +title = "Vuist der Legende" +description = "Dood 250 mobs met blote vuisten" [advancement.challenge_unarmed_crit_25] - title = "Kritieke Stomp" - description = "Land 25 kritieke treffers met blote vuisten" +title = "Kritieke Stomp" +description = "Land 25 kritieke treffers met blote vuisten" [advancement.challenge_unarmed_crit_250] - title = "Precisiepunch" - description = "Land 250 kritieke treffers met blote vuisten" +title = "Precisiepunch" +description = "Land 250 kritieke treffers met blote vuisten" [advancement.challenge_unarmed_heavy_25] - title = "Krachtstomp" - description = "Land 25 zware treffers met blote vuisten (meer dan 6 schade)" +title = "Krachtstomp" +description = "Land 25 zware treffers met blote vuisten (meer dan 6 schade)" [advancement.challenge_unarmed_heavy_250] - title = "Knockoutkoning" - description = "Land 250 zware treffers met blote vuisten (meer dan 6 schade)" +title = "Knockoutkoning" +description = "Land 250 zware treffers met blote vuisten (meer dan 6 schade)" # items [items] [items.bound_ender_peral] - name = "Reliekschrijn Portkey" - usage1 = "Shift + Linkerklik om te binden" - usage2 = "Rechtsklik om de gebonden inventaris te openen" +name = "Reliekschrijn Portkey" +usage1 = "Shift + Linkerklik om te binden" +usage2 = "Rechtsklik om de gebonden inventaris te openen" [items.bound_eye_of_ender] - name = "Oculair Anker" - usage1 = "Rechtsklik om te consumeren en naar de gebonden locatie te teleporteren" - usage2 = "Shift + Linkerklik om aan een blok te binden" +name = "Oculair Anker" +usage1 = "Rechtsklik om te consumeren en naar de gebonden locatie te teleporteren" +usage2 = "Shift + Linkerklik om aan een blok te binden" [items.bound_redstone_torch] - name = "Redstone-Afstandsbediening" - usage1 = "Rechtsklik om een 1-Tick Redstone-puls te maken" - usage2 = "Shift + Linkerklik op een 'Doel'-blok om te binden" +name = "Redstone-Afstandsbediening" +usage1 = "Rechtsklik om een 1-Tick Redstone-puls te maken" +usage2 = "Shift + Linkerklik op een 'Doel'-blok om te binden" [items.bound_snowball] - name = "Webstrik!" - usage1 = "Gooi om een tijdelijke webval op de locatie te maken" +name = "Webstrik!" +usage1 = "Gooi om een tijdelijke webval op de locatie te maken" [items.chrono_time_bottle] - name = "Tijd in een Fles" - usage1 = "Slaat passief tijd op terwijl het in je inventaris zit" - usage2 = "Rechtsklik op getimede blokken of babydieren om opgeslagen tijd te besteden" - stored = "Opgeslagen Tijd" +name = "Tijd in een Fles" +usage1 = "Slaat passief tijd op terwijl het in je inventaris zit" +usage2 = "Rechtsklik op getimede blokken of babydieren om opgeslagen tijd te besteden" +stored = "Opgeslagen Tijd" [items.chrono_time_bomb] - name = "Tijdbom" - usage1 = "Rechtsklik om een chronobolt te lanceren die een temporeel veld creëert" +name = "Tijdbom" +usage1 = "Rechtsklik om een chronobolt te lanceren die een temporeel veld creëert" [items.elevator_block] - name = "Liftblok" - usage1 = "Spring om omhoog te teleporteren" - usage2 = "Shift om omlaag te teleporteren" - usage3 = "Minimaal 2 luchtblokken tussen de liften" +name = "Liftblok" +usage1 = "Spring om omhoog te teleporteren" +usage2 = "Shift om omlaag te teleporteren" +usage3 = "Minimaal 2 luchtblokken tussen de liften" # snippets [snippets] [snippets.gui] - level = "Niveau" - knowledge = "kennis" - power_used = "Kracht Gebruikt" - not_learned = "Niet Geleerd" - xp = "XP tot" - welcome = "Welkom!" - welcome_back = "Welkom terug!" - xp_bonus_for_time = "XP voor" - max_ability_power = "Maximale Vaardigheidskracht" - unlock_this_by_clicking = "Ontgrendel dit door Rechts te Klikken: " - back = "Terug" - unlearn_all = "Alles afleren" - unlearned_all = "Alles afgeleerd" +level = "Niveau" +knowledge = "kennis" +power_used = "Kracht Gebruikt" +not_learned = "Niet Geleerd" +xp = "XP tot" +welcome = "Welkom!" +welcome_back = "Welkom terug!" +xp_bonus_for_time = "XP voor" +max_ability_power = "Maximale Vaardigheidskracht" +unlock_this_by_clicking = "Ontgrendel dit door Rechts te Klikken: " +back = "Terug" +unlearn_all = "Alles afleren" +unlearned_all = "Alles afgeleerd" [snippets.adapt_menu] - may_not_unlearn = "JE MAG NIET AFLEREN" - may_unlearn = "JE MAG LEREN/AFLEREN" - knowledge_cost = "Kenniskosten" - knowledge_available = "Kennis Beschikbaar" - already_learned = "Al Geleerd" - unlearn_refund = "Klik om Af te Leren en Terug te Krijgen" - no_refunds = "HARDCORE, TERUGBETALINGEN UITGESCHAKELD" - knowledge = "kennis" - click_learn = "Klik om te Leren" - no_knowledge = "(Je hebt geen kennis)" - you_only_have = "Je hebt slechts" - how_to_level_up = "Level vaardigheden omhoog om je maximale kracht te verhogen." - not_enough_power = "Niet genoeg kracht! Elk vaardigheidsniveau kost 1 kracht." - power = "kracht" - power_drain = "Krachtverbruik" - learned = "Geleerd " - unlearned = "Afgeleerd " - activator_block = "Boekenplank" +may_not_unlearn = "JE MAG NIET AFLEREN" +may_unlearn = "JE MAG LEREN/AFLEREN" +knowledge_cost = "Kenniskosten" +knowledge_available = "Kennis Beschikbaar" +already_learned = "Al Geleerd" +unlearn_refund = "Klik om Af te Leren en Terug te Krijgen" +no_refunds = "HARDCORE, TERUGBETALINGEN UITGESCHAKELD" +knowledge = "kennis" +click_learn = "Klik om te Leren" +no_knowledge = "(Je hebt geen kennis)" +you_only_have = "Je hebt slechts" +how_to_level_up = "Level vaardigheden omhoog om je maximale kracht te verhogen." +not_enough_power = "Niet genoeg kracht! Elk vaardigheidsniveau kost 1 kracht." +power = "kracht" +power_drain = "Krachtverbruik" +learned = "Geleerd " +unlearned = "Afgeleerd " +activator_block = "Boekenplank" [snippets.knowledge_orb] - contains = "bevat" - knowledge = "kennis" - rightclick = "Rechtsklik" - togainknowledge = "om deze kennis te verkrijgen" - knowledge_orb = "Kennisbol" +contains = "bevat" +knowledge = "kennis" +rightclick = "Rechtsklik" +togainknowledge = "om deze kennis te verkrijgen" +knowledge_orb = "Kennisbol" [snippets.experience_orb] - contains = "bevat" - xp = "Ervaring" - rightclick = "Rechtsklik" - togainxp = "om deze ervaring te verkrijgen" - xporb = "Ervaringsbol" +contains = "bevat" +xp = "Ervaring" +rightclick = "Rechtsklik" +togainxp = "om deze ervaring te verkrijgen" +xporb = "Ervaringsbol" # skill [skill] [skill.agility] - name = "Behendigheid" - icon = "⇉" - description = "Behendigheid is het vermogen om snel en soepel te bewegen ondanks obstakels." +name = "Behendigheid" +icon = "⇉" +description = "Behendigheid is het vermogen om snel en soepel te bewegen ondanks obstakels." [skill.architect] - name = "Architect" - icon = "⬧" - description = "Structuren zijn de bouwstenen van de wereld. De werkelijkheid ligt in jouw handen, aan jou om te beheersen." +name = "Architect" +icon = "⬧" +description = "Structuren zijn de bouwstenen van de wereld. De werkelijkheid ligt in jouw handen, aan jou om te beheersen." [skill.axes] - name = "Bijlen" - icon = "🪓" - description1 = "Waarom bomen hakken, als je ook " - description2 = "dingen" - description3 = "kunt hakken, zelfde resultaat!" +name = "Bijlen" +icon = "🪓" +description1 = "Waarom bomen hakken, als je ook " +description2 = "dingen" +description3 = "kunt hakken, zelfde resultaat!" [skill.brewing] - name = "Brouwen" - icon = "❦" - description = "Dubbel Bubbel, Driedubbel Bubbel, Vierdubbel Bubbel - Ik kan dit drankje nog steeds niet in een ketel doen" +name = "Brouwen" +icon = "❦" +description = "Dubbel Bubbel, Driedubbel Bubbel, Vierdubbel Bubbel - Ik kan dit drankje nog steeds niet in een ketel doen" [skill.blocking] - name = "Blokkeren" - icon = "🛡" - description = "Stokken en stenen zullen je botten niet breken, maar een schild wel." +name = "Blokkeren" +icon = "🛡" +description = "Stokken en stenen zullen je botten niet breken, maar een schild wel." [skill.crafting] - name = "Knutselen" - icon = "⌂" - description = "Als er geen stukken meer zijn om te plaatsen, waarom niet gewoon een nieuwe maken?" +name = "Knutselen" +icon = "⌂" +description = "Als er geen stukken meer zijn om te plaatsen, waarom niet gewoon een nieuwe maken?" [skill.discovery] - name = "Ontdekking" - icon = "⚛" - description = "Terwijl je waarneming zich uitbreidt, ontvouwt je geest zich om te ontdekken wat je niet wist." +name = "Ontdekking" +icon = "⚛" +description = "Terwijl je waarneming zich uitbreidt, ontvouwt je geest zich om te ontdekken wat je niet wist." [skill.enchanting] - name = "Betoveren" - icon = "♰" - description = "Waar heb je het over? Profetieen, visioenen, bijgelovig gebrabbel?" +name = "Betoveren" +icon = "♰" +description = "Waar heb je het over? Profetieen, visioenen, bijgelovig gebrabbel?" [skill.excavation] - name = "Graven" - icon = "ᛳ" - description = "Graaf graaf een gat..." +name = "Graven" +icon = "ᛳ" +description = "Graaf graaf een gat..." [skill.herbalism] - name = "Kruidkunde" - icon = "⚘" - description = "Ik kan geen planten vinden, maar wel wat zaadjes en- is dat... wiet?" +name = "Kruidkunde" +icon = "⚘" +description = "Ik kan geen planten vinden, maar wel wat zaadjes en- is dat... wiet?" [skill.hunter] - name = "Jager" - icon = "☠" - description = "Jagen gaat om de reis, niet het resultaat." +name = "Jager" +icon = "☠" +description = "Jagen gaat om de reis, niet het resultaat." [skill.nether] - name = "Nether" - icon = "₪" - description = "Uit de diepten van de Nether zelf." +name = "Nether" +icon = "₪" +description = "Uit de diepten van de Nether zelf." [skill.pickaxe] - name = "Pikhouweel" - icon = "⛏" - description = "Dwergen zijn de mijnwerkers, maar ik heb ook het een en ander geleerd in mijn tijd. IK BEN ZWEEDS" +name = "Pikhouweel" +icon = "⛏" +description = "Dwergen zijn de mijnwerkers, maar ik heb ook het een en ander geleerd in mijn tijd. IK BEN ZWEEDS" [skill.ranged] - name = "Afstandswapens" - icon = "🏹" - description = "Afstand is de sleutel tot overwinning, en de sleutel tot overleven." +name = "Afstandswapens" +icon = "🏹" +description = "Afstand is de sleutel tot overwinning, en de sleutel tot overleven." [skill.rift] - name = "Rift" - icon = "❍" - description = "De Rift is een bijtend harnas, maar jij hebt het harnas getemd." +name = "Rift" +icon = "❍" +description = "De Rift is een bijtend harnas, maar jij hebt het harnas getemd." [skill.seaborne] - name = "Zeevaarder" - icon = "🎣" - description = "Met deze vaardigheid kun je de wonderen van het water beheersen." +name = "Zeevaarder" +icon = "🎣" +description = "Met deze vaardigheid kun je de wonderen van het water beheersen." [skill.stealth] - name = "Stealth" - icon = "☯" - description = "De kunst van het ongeziene. Wandel in de schaduwen." +name = "Stealth" +icon = "☯" +description = "De kunst van het ongeziene. Wandel in de schaduwen." [skill.swords] - name = "Zwaarden" - icon = "⚔" - description = "Bij de kracht van GreyStone!" +name = "Zwaarden" +icon = "⚔" +description = "Bij de kracht van GreyStone!" [skill.taming] - name = "Temmen" - icon = "♥" - description = "De papegaaien en de bijen... en jij?" +name = "Temmen" +icon = "♥" +description = "De papegaaien en de bijen... en jij?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Bloed stroomt door de aderen van het universum. Samengeknepen door jouw handen." +name = "TragOul" +icon = "🗡" +description = "Bloed stroomt door de aderen van het universum. Samengeknepen door jouw handen." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Wind de klok van het universum op, ervaar de stroom. Breek de klok, word hem." +name = "Chronos" +icon = "🕒" +description = "Wind de klok van het universum op, ervaar de stroom. Breek de klok, word hem." [skill.unarmed] - name = "Ongewapend" - icon = "»" - description = "Zonder wapen is niet zonder kracht." +name = "Ongewapend" +icon = "»" +description = "Zonder wapen is niet zonder kracht." # agility [agility] [agility.armor_up] - name = "Pantser-Up" - description = "Krijg meer pantser hoe langer je sprint!" - lore1 = "Maximaal Pantser" - lore2 = "Pantser-Up Tijd" - lore = ["Maximaal Pantser", "Pantser-Up Tijd"] +name = "Pantser-Up" +description = "Krijg meer pantser hoe langer je sprint!" +lore1 = "Maximaal Pantser" +lore2 = "Pantser-Up Tijd" +lore = ["Maximaal Pantser", "Pantser-Up Tijd"] [agility.ladder_slide] - name = "Ladderglijden" - description = "Klim en glijd veel sneller over ladders in beide richtingen." - lore1 = "Laddersnelheid vermenigvuldiger" - lore2 = "Snelle afdalingssnelheid" - lore = ["Laddersnelheid vermenigvuldiger", "Snelle afdalingssnelheid"] +name = "Ladderglijden" +description = "Klim en glijd veel sneller over ladders in beide richtingen." +lore1 = "Laddersnelheid vermenigvuldiger" +lore2 = "Snelle afdalingssnelheid" +lore = ["Laddersnelheid vermenigvuldiger", "Snelle afdalingssnelheid"] [agility.super_jump] - name = "Super Sprong" - description = "Uitzonderlijk hoogtevoordeel." - lore1 = "Maximale Spronghoogte" - lore2 = "Sluip + Spring voor Super Sprong!" - lore = ["Maximale Spronghoogte", "Sluip + Spring voor Super Sprong!"] +name = "Super Sprong" +description = "Uitzonderlijk hoogtevoordeel." +lore1 = "Maximale Spronghoogte" +lore2 = "Sluip + Spring voor Super Sprong!" +lore = ["Maximale Spronghoogte", "Sluip + Spring voor Super Sprong!"] [agility.wall_jump] - name = "Muursprong" - description = "Houd shift ingedrukt in de lucht tegen een muur om je vast te klampen en te springen!" - lore1 = "Max Sprongen" - lore2 = "Spronghoogte" - lore = ["Max Sprongen", "Spronghoogte"] +name = "Muursprong" +description = "Houd shift ingedrukt in de lucht tegen een muur om je vast te klampen en te springen!" +lore1 = "Max Sprongen" +lore2 = "Spronghoogte" +lore = ["Max Sprongen", "Spronghoogte"] [agility.wind_up] - name = "Opwinden" - description = "Word sneller hoe langer je sprint!" - lore1 = "Max Snelheid" - lore2 = "Opwindtijd" - lore = ["Max Snelheid", "Opwindtijd"] +name = "Opwinden" +description = "Word sneller hoe langer je sprint!" +lore1 = "Max Snelheid" +lore2 = "Opwindtijd" +lore = ["Max Snelheid", "Opwindtijd"] # architect [architect] [architect.elevator] - name = "Lift" - description = "Hiermee kun je een lift bouwen om snel verticaal te teleporteren!" - lore1 = "Ontgrendelt liftrecept: X=WOL, Y=ENDERPAREL" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Ontgrendelt liftrecept: X=WOL, Y=ENDERPAREL", "XXX", "XYX", "XXX"] +name = "Lift" +description = "Hiermee kun je een lift bouwen om snel verticaal te teleporteren!" +lore1 = "Ontgrendelt liftrecept: X=WOL, Y=ENDERPAREL" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Ontgrendelt liftrecept: X=WOL, Y=ENDERPAREL", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Magisch Fundament" - description = "Hiermee kun je sluipen en een tijdelijk fundament onder je plaatsen!" - lore1 = "Creeer magisch: " - lore2 = "Blokken onder je!" - lore = ["Creeer magisch: ", "Blokken onder je!"] +name = "Magisch Fundament" +description = "Hiermee kun je sluipen en een tijdelijk fundament onder je plaatsen!" +lore1 = "Creeer magisch: " +lore2 = "Blokken onder je!" +lore = ["Creeer magisch: ", "Blokken onder je!"] [architect.glass] - name = "Silk-Touch Glas" - description = "Hiermee voorkom je het verlies van glasblokken wanneer je ze met een lege hand breekt!" - lore1 = "Je handen krijgen silk touch voor Glas" - lore = ["Je handen krijgen silk touch voor Glas"] +name = "Silk-Touch Glas" +description = "Hiermee voorkom je het verlies van glasblokken wanneer je ze met een lege hand breekt!" +lore1 = "Je handen krijgen silk touch voor Glas" +lore = ["Je handen krijgen silk touch voor Glas"] [architect.wireless_redstone] - name = "Redstone-Afstandsbediening" - description = "Hiermee kun je een redstonefakkel gebruiken om redstone op afstand te schakelen!" - lore1 = "Doelwit + Redstonefakkel + Enderparel = 1 Redstone-Afstandsbediening" - lore = ["Doelwit + Redstonefakkel + Enderparel = 1 Redstone-Afstandsbediening"] +name = "Redstone-Afstandsbediening" +description = "Hiermee kun je een redstonefakkel gebruiken om redstone op afstand te schakelen!" +lore1 = "Doelwit + Redstonefakkel + Enderparel = 1 Redstone-Afstandsbediening" +lore = ["Doelwit + Redstonefakkel + Enderparel = 1 Redstone-Afstandsbediening"] [architect.placement] - name = "Bouwerstaf" - description = "Hiermee kun je meerdere blokken tegelijk plaatsen. Sluip en houd een blok vast dat overeenkomt met het blok waar je naar kijkt en plaats! Mogelijk moet je even bewegen om de selectie te activeren!" - lore1 = "Je hebt" - lore2 = "blokken in je hand nodig om dit te plaatsen" - lore3 = "Een Materialen Bouwerstaf" - lore = ["Je hebt", "blokken in je hand nodig om dit te plaatsen", "Een Materialen Bouwerstaf"] +name = "Bouwerstaf" +description = "Hiermee kun je meerdere blokken tegelijk plaatsen. Sluip en houd een blok vast dat overeenkomt met het blok waar je naar kijkt en plaats! Mogelijk moet je even bewegen om de selectie te activeren!" +lore1 = "Je hebt" +lore2 = "blokken in je hand nodig om dit te plaatsen" +lore3 = "Een Materialen Bouwerstaf" +lore = ["Je hebt", "blokken in je hand nodig om dit te plaatsen", "Een Materialen Bouwerstaf"] # axe [axe] [axe.chop] - name = "Bijlhak" - description = "Hak bomen om door met rechts op het onderste stamblok te klikken!" - lore1 = "Blokken Per Hak" - lore2 = "Hak-Cooldown" - lore3 = "Gereedschapsslijtage" - lore = ["Blokken Per Hak", "Hak-Cooldown", "Gereedschapsslijtage"] +name = "Bijlhak" +description = "Hak bomen om door met rechts op het onderste stamblok te klikken!" +lore1 = "Blokken Per Hak" +lore2 = "Hak-Cooldown" +lore3 = "Gereedschapsslijtage" +lore = ["Blokken Per Hak", "Hak-Cooldown", "Gereedschapsslijtage"] [axe.log_swap] - name = "Lucy's Houtwisselaar" - description = "Verander het type houtblokken in een Werktafel!" - lore1 = "8 stammen van elk type + 1 boompje = 8 stammen van het type van het boompje" - lore = ["8 stammen van elk type + 1 boompje = 8 stammen van het type van het boompje"] +name = "Lucy's Houtwisselaar" +description = "Verander het type houtblokken in een Werktafel!" +lore1 = "8 stammen van elk type + 1 boompje = 8 stammen van het type van het boompje" +lore = ["8 stammen van elk type + 1 boompje = 8 stammen van het type van het boompje"] [axe.drop_to_inventory] - name = "Bijl Drop-naar-Inventaris" +name = "Bijl Drop-naar-Inventaris" [axe.ground_smash] - name = "Bijl Grondinslag" - description = "Spring, sluip vervolgens en sla alle vijanden in de buurt." - lore1 = "Schade" - lore2 = "Blokken Radius" - lore3 = "Kracht" - lore4 = "Inslag-Cooldown" - lore = ["Schade", "Blokken Radius", "Kracht", "Inslag-Cooldown"] +name = "Bijl Grondinslag" +description = "Spring, sluip vervolgens en sla alle vijanden in de buurt." +lore1 = "Schade" +lore2 = "Blokken Radius" +lore3 = "Kracht" +lore4 = "Inslag-Cooldown" +lore = ["Schade", "Blokken Radius", "Kracht", "Inslag-Cooldown"] [axe.leaf_miner] - name = "Bladmijnwerker" - description = "Hiermee kun je grote hoeveelheden bladeren tegelijk breken!" - lore1 = "Sluip en mijn BLADEREN" - lore2 = "bereik van bladmijnbouw" - lore3 = "Je krijgt geen drops van de bladeren (Exploit Preventie)" - lore = ["Sluip en mijn BLADEREN", "bereik van bladmijnbouw", "Je krijgt geen drops van de bladeren (Exploit Preventie)"] +name = "Bladmijnwerker" +description = "Hiermee kun je grote hoeveelheden bladeren tegelijk breken!" +lore1 = "Sluip en mijn BLADEREN" +lore2 = "bereik van bladmijnbouw" +lore3 = "Je krijgt geen drops van de bladeren (Exploit Preventie)" +lore = ["Sluip en mijn BLADEREN", "bereik van bladmijnbouw", "Je krijgt geen drops van de bladeren (Exploit Preventie)"] [axe.wood_miner] - name = "Houtmijnwerker" - description = "Hiermee kun je grote hoeveelheden hout tegelijk breken!" - lore1 = "Sluip en mijn HOUT/STAMMEN (geen planken)" - lore2 = "bereik van houtmijnbouw" - lore3 = "Werkt met Drop-naar-Inventaris" - lore = ["Sluip en mijn HOUT/STAMMEN (geen planken)", "bereik van houtmijnbouw", "Werkt met Drop-naar-Inventaris"] +name = "Houtmijnwerker" +description = "Hiermee kun je grote hoeveelheden hout tegelijk breken!" +lore1 = "Sluip en mijn HOUT/STAMMEN (geen planken)" +lore2 = "bereik van houtmijnbouw" +lore3 = "Werkt met Drop-naar-Inventaris" +lore = ["Sluip en mijn HOUT/STAMMEN (geen planken)", "bereik van houtmijnbouw", "Werkt met Drop-naar-Inventaris"] # brewing [brewing] [brewing.lingering] - name = "Aanhoudend Brouwsel" - description = "Gebrouwen drankjes duren langer!" - lore1 = "Duur" - lore2 = "Duur" - lore = ["Duur", "Duur"] +name = "Aanhoudend Brouwsel" +description = "Gebrouwen drankjes duren langer!" +lore1 = "Duur" +lore2 = "Duur" +lore = ["Duur", "Duur"] [brewing.super_heated] - name = "Superverhit Brouwsel" - description = "Brouwstandaarden werken sneller hoe heter ze zijn." - lore1 = "Per aanrakend vuurblok" - lore2 = "Per aanrakend lavablok" - lore = ["Per aanrakend vuurblok", "Per aanrakend lavablok"] +name = "Superverhit Brouwsel" +description = "Brouwstandaarden werken sneller hoe heter ze zijn." +lore1 = "Per aanrakend vuurblok" +lore2 = "Per aanrakend lavablok" +lore = ["Per aanrakend vuurblok", "Per aanrakend lavablok"] [brewing.darkness] - name = "Gebottelde Duisternis" - description = "Niet zeker waarom je dit nodig hebt, maar alsjeblieft!" - lore1 = "Nachtzichtdrankje + Zwart Beton = Duisternisdrankje (30 seconden)" - lore2 = "Let op: dit voorkomt dat de gebruiker kan sprinten!" - lore = ["Nachtzichtdrankje + Zwart Beton = Duisternisdrankje (30 seconden)", "Let op: dit voorkomt dat de gebruiker kan sprinten!"] +name = "Gebottelde Duisternis" +description = "Niet zeker waarom je dit nodig hebt, maar alsjeblieft!" +lore1 = "Nachtzichtdrankje + Zwart Beton = Duisternisdrankje (30 seconden)" +lore2 = "Let op: dit voorkomt dat de gebruiker kan sprinten!" +lore = ["Nachtzichtdrankje + Zwart Beton = Duisternisdrankje (30 seconden)", "Let op: dit voorkomt dat de gebruiker kan sprinten!"] [brewing.haste] - name = "Gebottelde Haast" - description = "Wanneer efficientie niet genoeg is" - lore1 = "Snelheidsdrankje + Amethist Scherf = Haastdrankje (60 seconden)" - lore2 = "Snelheidsdrankje + Amethist Blok = Haastdrankje-2 (30 seconden)" - lore = ["Snelheidsdrankje + Amethist Scherf = Haastdrankje (60 seconden)", "Snelheidsdrankje + Amethist Blok = Haastdrankje-2 (30 seconden)"] +name = "Gebottelde Haast" +description = "Wanneer efficientie niet genoeg is" +lore1 = "Snelheidsdrankje + Amethist Scherf = Haastdrankje (60 seconden)" +lore2 = "Snelheidsdrankje + Amethist Blok = Haastdrankje-2 (30 seconden)" +lore = ["Snelheidsdrankje + Amethist Scherf = Haastdrankje (60 seconden)", "Snelheidsdrankje + Amethist Blok = Haastdrankje-2 (30 seconden)"] [brewing.absorption] - name = "Gebottelde Absorptie" - description = "Verhard het lichaam!" - lore1 = "Directe Genezing + Kwarts = Absorptiedrankje (60 seconden)" - lore2 = "Directe Genezing + Kwartsblok = Absorptiedrankje-2 (30 seconden)" - lore = ["Directe Genezing + Kwarts = Absorptiedrankje (60 seconden)", "Directe Genezing + Kwartsblok = Absorptiedrankje-2 (30 seconden)"] +name = "Gebottelde Absorptie" +description = "Verhard het lichaam!" +lore1 = "Directe Genezing + Kwarts = Absorptiedrankje (60 seconden)" +lore2 = "Directe Genezing + Kwartsblok = Absorptiedrankje-2 (30 seconden)" +lore = ["Directe Genezing + Kwarts = Absorptiedrankje (60 seconden)", "Directe Genezing + Kwartsblok = Absorptiedrankje-2 (30 seconden)"] [brewing.fatigue] - name = "Gebottelde Vermoeidheid" - description = "Verzwak het lichaam!" - lore1 = "Zwaktedrankje + Slijmbal = Vermoeidheidsdrankje (30 seconden)" - lore2 = "Zwaktedrankje + Slijmblok = Vermoeidheidsdrankje-2 (15 seconden)" - lore = ["Zwaktedrankje + Slijmbal = Vermoeidheidsdrankje (30 seconden)", "Zwaktedrankje + Slijmblok = Vermoeidheidsdrankje-2 (15 seconden)"] +name = "Gebottelde Vermoeidheid" +description = "Verzwak het lichaam!" +lore1 = "Zwaktedrankje + Slijmbal = Vermoeidheidsdrankje (30 seconden)" +lore2 = "Zwaktedrankje + Slijmblok = Vermoeidheidsdrankje-2 (15 seconden)" +lore = ["Zwaktedrankje + Slijmbal = Vermoeidheidsdrankje (30 seconden)", "Zwaktedrankje + Slijmblok = Vermoeidheidsdrankje-2 (15 seconden)"] [brewing.hunger] - name = "Gebottelde Honger" - description = "Voed de onverzadigbare!" - lore1 = "Onhandig Drankje + Rottend Vlees = Hongerdrankje (30 seconden)" - lore2 = "Zwaktedrankje + Rottend Vlees = Hongerdrankje-3 (15 seconden)" - lore = ["Onhandig Drankje + Rottend Vlees = Hongerdrankje (30 seconden)", "Zwaktedrankje + Rottend Vlees = Hongerdrankje-3 (15 seconden)"] +name = "Gebottelde Honger" +description = "Voed de onverzadigbare!" +lore1 = "Onhandig Drankje + Rottend Vlees = Hongerdrankje (30 seconden)" +lore2 = "Zwaktedrankje + Rottend Vlees = Hongerdrankje-3 (15 seconden)" +lore = ["Onhandig Drankje + Rottend Vlees = Hongerdrankje (30 seconden)", "Zwaktedrankje + Rottend Vlees = Hongerdrankje-3 (15 seconden)"] [brewing.nausea] - name = "Gebottelde Misselijkheid" - description = "Je maakt me misselijk!" - lore1 = "Onhandig Drankje + Bruine Paddenstoel = Misselijkheidsdrankje (16 seconden)" - lore2 = "Onhandig Drankje + Karmozijn Schimmel = Misselijkheidsdrankje-2 (8 seconden)" - lore = ["Onhandig Drankje + Bruine Paddenstoel = Misselijkheidsdrankje (16 seconden)", "Onhandig Drankje + Karmozijn Schimmel = Misselijkheidsdrankje-2 (8 seconden)"] +name = "Gebottelde Misselijkheid" +description = "Je maakt me misselijk!" +lore1 = "Onhandig Drankje + Bruine Paddenstoel = Misselijkheidsdrankje (16 seconden)" +lore2 = "Onhandig Drankje + Karmozijn Schimmel = Misselijkheidsdrankje-2 (8 seconden)" +lore = ["Onhandig Drankje + Bruine Paddenstoel = Misselijkheidsdrankje (16 seconden)", "Onhandig Drankje + Karmozijn Schimmel = Misselijkheidsdrankje-2 (8 seconden)"] [brewing.blindness] - name = "Gebottelde Blindheid" - description = "Je bent een verschrikkelijk persoon..." - lore1 = "Onhandig Drankje + Inktzak = Blindheidsdrankje (30 seconden)" - lore2 = "Onhandig Drankje + Lichtgevende Inktzak = Blindheidsdrankje-2 (15 seconden)" - lore = ["Onhandig Drankje + Inktzak = Blindheidsdrankje (30 seconden)", "Onhandig Drankje + Lichtgevende Inktzak = Blindheidsdrankje-2 (15 seconden)"] +name = "Gebottelde Blindheid" +description = "Je bent een verschrikkelijk persoon..." +lore1 = "Onhandig Drankje + Inktzak = Blindheidsdrankje (30 seconden)" +lore2 = "Onhandig Drankje + Lichtgevende Inktzak = Blindheidsdrankje-2 (15 seconden)" +lore = ["Onhandig Drankje + Inktzak = Blindheidsdrankje (30 seconden)", "Onhandig Drankje + Lichtgevende Inktzak = Blindheidsdrankje-2 (15 seconden)"] [brewing.resistance] - name = "Gebottelde Weerstand" - description = "Fortificatie op zijn best!" - lore1 = "Onhandig Drankje + IJzerstaaf = Weerstandsdrankje (60 seconden)" - lore2 = "Onhandig Drankje + IJzerblok = Weerstandsdrankje-2 (30 seconden)" - lore = ["Onhandig Drankje + IJzerstaaf = Weerstandsdrankje (60 seconden)", "Onhandig Drankje + IJzerblok = Weerstandsdrankje-2 (30 seconden)"] +name = "Gebottelde Weerstand" +description = "Fortificatie op zijn best!" +lore1 = "Onhandig Drankje + IJzerstaaf = Weerstandsdrankje (60 seconden)" +lore2 = "Onhandig Drankje + IJzerblok = Weerstandsdrankje-2 (30 seconden)" +lore = ["Onhandig Drankje + IJzerstaaf = Weerstandsdrankje (60 seconden)", "Onhandig Drankje + IJzerblok = Weerstandsdrankje-2 (30 seconden)"] [brewing.health_boost] - name = "Gebotteld Leven" - description = "Wanneer maximale gezondheid niet genoeg is..." - lore1 = "Directe-Genezingsdrankje + Gouden Appel = Gezondheidsboostdrankje (120 seconden)" - lore2 = "Directe-Genezingsdrankje + Betoverde Gouden Appel = Gezondheidsboostdrankje-2 (120 seconden)" - lore = ["Directe-Genezingsdrankje + Gouden Appel = Gezondheidsboostdrankje (120 seconden)", "Directe-Genezingsdrankje + Betoverde Gouden Appel = Gezondheidsboostdrankje-2 (120 seconden)"] +name = "Gebotteld Leven" +description = "Wanneer maximale gezondheid niet genoeg is..." +lore1 = "Directe-Genezingsdrankje + Gouden Appel = Gezondheidsboostdrankje (120 seconden)" +lore2 = "Directe-Genezingsdrankje + Betoverde Gouden Appel = Gezondheidsboostdrankje-2 (120 seconden)" +lore = ["Directe-Genezingsdrankje + Gouden Appel = Gezondheidsboostdrankje (120 seconden)", "Directe-Genezingsdrankje + Betoverde Gouden Appel = Gezondheidsboostdrankje-2 (120 seconden)"] [brewing.decay] - name = "Gebotteld Verval" - description = "Wie wist dat afval zo nuttig zou zijn?" - lore1 = "Zwaktedrankje + Giftige Aardappel = Witherdrankje (16 seconden)" - lore2 = "Zwaktedrankje + Karmozijn Wortels = Witherdrankje-2 (8 seconden)" - lore = ["Zwaktedrankje + Giftige Aardappel = Witherdrankje (16 seconden)", "Zwaktedrankje + Karmozijn Wortels = Witherdrankje-2 (8 seconden)"] +name = "Gebotteld Verval" +description = "Wie wist dat afval zo nuttig zou zijn?" +lore1 = "Zwaktedrankje + Giftige Aardappel = Witherdrankje (16 seconden)" +lore2 = "Zwaktedrankje + Karmozijn Wortels = Witherdrankje-2 (8 seconden)" +lore = ["Zwaktedrankje + Giftige Aardappel = Witherdrankje (16 seconden)", "Zwaktedrankje + Karmozijn Wortels = Witherdrankje-2 (8 seconden)"] [brewing.saturation] - name = "Gebottelde Verzadiging" - description = "Weet je... ik heb niet eens honger..." - lore1 = "Regeneratiedrankje + Gebakken Aardappel = Verzadigingsdrankje" - lore2 = "Regeneratiedrankje + Hooibaal = Verzadigingsdrankje-2" - lore = ["Regeneratiedrankje + Gebakken Aardappel = Verzadigingsdrankje", "Regeneratiedrankje + Hooibaal = Verzadigingsdrankje-2"] +name = "Gebottelde Verzadiging" +description = "Weet je... ik heb niet eens honger..." +lore1 = "Regeneratiedrankje + Gebakken Aardappel = Verzadigingsdrankje" +lore2 = "Regeneratiedrankje + Hooibaal = Verzadigingsdrankje-2" +lore = ["Regeneratiedrankje + Gebakken Aardappel = Verzadigingsdrankje", "Regeneratiedrankje + Hooibaal = Verzadigingsdrankje-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Ketens van Mephistopheles" - description = "Hiermee kun je malienpantser maken" - lore1 = "Het receptis hetzelfde als elk ander, maar met ijzerklompjes in plaats van staafjes" - lore = ["Het receptis hetzelfde als elk ander, maar met ijzerklompjes in plaats van staafjes"] +name = "Ketens van Mephistopheles" +description = "Hiermee kun je malienpantser maken" +lore1 = "Het receptis hetzelfde als elk ander, maar met ijzerklompjes in plaats van staafjes" +lore = ["Het receptis hetzelfde als elk ander, maar met ijzerklompjes in plaats van staafjes"] [blocking.horse_armorer] - name = "Maakbaar Paardenharnas" - description = "Hiermee kun je paardenharnas maken" - lore1 = "Omring een zadel met het materiaal dat je wilt gebruiken om het harnas te maken" - lore = ["Omring een zadel met het materiaal dat je wilt gebruiken om het harnas te maken"] +name = "Maakbaar Paardenharnas" +description = "Hiermee kun je paardenharnas maken" +lore1 = "Omring een zadel met het materiaal dat je wilt gebruiken om het harnas te maken" +lore = ["Omring een zadel met het materiaal dat je wilt gebruiken om het harnas te maken"] [blocking.saddle_crafter] - name = "Maakbaar Zadel" - description = "Maak een zadel met leer" - lore1 = "Recept: 5 Leer:" - lore = ["Recept: 5 Leer:"] +name = "Maakbaar Zadel" +description = "Maak een zadel met leer" +lore1 = "Recept: 5 Leer:" +lore = ["Recept: 5 Leer:"] [blocking.multi_armor] - name = "Multi-Pantser" - description = "Bind Elytra's aan pantser" - lore1 = "Dit is een geweldige vaardigheid om te reizen." - lore2 = "Dynamisch samenvoegen en wisselen van Pantser/Elytra terwijl je vliegt!" - lore3 = "Om samen te voegen, shift-klik een item op een ander in je inventaris." - lore4 = "Om pantser los te koppelen, sluip en drop het item, en het wordt gedemonteerd." - lore5 = "Als je Multi-Pantser wordt vernietigd, verlies je alle items erin." - lore6 = "Ik heb geen pantser nodig, het stelt me teleur..." - lore = ["Dit is een geweldige vaardigheid om te reizen.", "Dynamisch samenvoegen en wisselen van Pantser/Elytra terwijl je vliegt!", "Om samen te voegen, shift-klik een item op een ander in je inventaris.", "Om pantser los te koppelen, sluip en drop het item, en het wordt gedemonteerd.", "Als je Multi-Pantser wordt vernietigd, verlies je alle items erin.", "Ik heb geen pantser nodig, het stelt me teleur..."] +name = "Multi-Pantser" +description = "Bind Elytra's aan pantser" +lore1 = "Dit is een geweldige vaardigheid om te reizen." +lore2 = "Dynamisch samenvoegen en wisselen van Pantser/Elytra terwijl je vliegt!" +lore3 = "Om samen te voegen, shift-klik een item op een ander in je inventaris." +lore4 = "Om pantser los te koppelen, sluip en drop het item, en het wordt gedemonteerd." +lore5 = "Als je Multi-Pantser wordt vernietigd, verlies je alle items erin." +lore6 = "Ik heb geen pantser nodig, het stelt me teleur..." +lore = ["Dit is een geweldige vaardigheid om te reizen.", "Dynamisch samenvoegen en wisselen van Pantser/Elytra terwijl je vliegt!", "Om samen te voegen, shift-klik een item op een ander in je inventaris.", "Om pantser los te koppelen, sluip en drop het item, en het wordt gedemonteerd.", "Als je Multi-Pantser wordt vernietigd, verlies je alle items erin.", "Ik heb geen pantser nodig, het stelt me teleur..."] # crafting [crafting] [crafting.deconstruction] - name = "Deconstructie" - description = "Ontmantel blokken & items tot bruikbare basiscomponenten!" - lore1 = "Laat een voorwerp op de grond vallen." - lore2 = "Sluip vervolgens en rechtsklik met een schaar" - lore = ["Laat een voorwerp op de grond vallen.", "Sluip vervolgens en rechtsklik met een schaar"] +name = "Deconstructie" +description = "Ontmantel blokken & items tot bruikbare basiscomponenten!" +lore1 = "Laat een voorwerp op de grond vallen." +lore2 = "Sluip vervolgens en rechtsklik met een schaar" +lore = ["Laat een voorwerp op de grond vallen.", "Sluip vervolgens en rechtsklik met een schaar"] [crafting.xp] - name = "Knutsel-XP" - description = "Verkrijg passieve XP bij het knutselen" - lore1 = "Verdien XP bij het knutselen" - lore = ["Verdien XP bij het knutselen"] +name = "Knutsel-XP" +description = "Verkrijg passieve XP bij het knutselen" +lore1 = "Verdien XP bij het knutselen" +lore = ["Verdien XP bij het knutselen"] [crafting.reconstruction] - name = "Erts Reconstructie" - description = "Maak ertsen opnieuw van hun basiscomponenten!" - lore1 = "8 van de drops en 1 gastheer = 1 erts (vormloos)" - lore2 = "Drops moeten gesmolten zijn (indien van toepassing)" - lore3 = "Exclusief: Schroot, Kwarts, en Smaragden etc..." - lore4 = "Gastheer = Omhulsel. bijv: Steen, Netherrack, Deepslate" - lore = ["8 van de drops en 1 gastheer = 1 erts (vormloos)", "Drops moeten gesmolten zijn (indien van toepassing)", "Exclusief: Schroot, Kwarts, en Smaragden etc...", "Gastheer = Omhulsel. bijv: Steen, Netherrack, Deepslate"] +name = "Erts Reconstructie" +description = "Maak ertsen opnieuw van hun basiscomponenten!" +lore1 = "8 van de drops en 1 gastheer = 1 erts (vormloos)" +lore2 = "Drops moeten gesmolten zijn (indien van toepassing)" +lore3 = "Exclusief: Schroot, Kwarts, en Smaragden etc..." +lore4 = "Gastheer = Omhulsel. bijv: Steen, Netherrack, Deepslate" +lore = ["8 van de drops en 1 gastheer = 1 erts (vormloos)", "Drops moeten gesmolten zijn (indien van toepassing)", "Exclusief: Schroot, Kwarts, en Smaragden etc...", "Gastheer = Omhulsel. bijv: Steen, Netherrack, Deepslate"] [crafting.leather] - name = "Maakbaar Leer" - description = "Maak leer van rottend vlees" - lore1 = "Gooi het (rottend vlees) gewoon op het kampvuur!" - lore = ["Gooi het (rottend vlees) gewoon op het kampvuur!"] +name = "Maakbaar Leer" +description = "Maak leer van rottend vlees" +lore1 = "Gooi het (rottend vlees) gewoon op het kampvuur!" +lore = ["Gooi het (rottend vlees) gewoon op het kampvuur!"] [crafting.backpacks] - name = "De Rugzakken van een Boutilier!" - description = "Dit brengt gewoon de Mojang Bundel in het spel!" - lore1 = "Je moet in Survival zijn om dit te gebruiken" - lore2 = "XLX : Leer, Leidtouw, Leer" - lore3 = "XSX : Leer, Vat, Leer" - lore4 = "XCX : Leer, Kist, Leer" - lore = ["Je moet in Survival zijn om dit te gebruiken", "XLX : Leer, Leidtouw, Leer", "XSX : Leer, Vat, Leer", "XCX : Leer, Kist, Leer"] +name = "De Rugzakken van een Boutilier!" +description = "Dit brengt gewoon de Mojang Bundel in het spel!" +lore1 = "Je moet in Survival zijn om dit te gebruiken" +lore2 = "XLX : Leer, Leidtouw, Leer" +lore3 = "XSX : Leer, Vat, Leer" +lore4 = "XCX : Leer, Kist, Leer" +lore = ["Je moet in Survival zijn om dit te gebruiken", "XLX : Leer, Leidtouw, Leer", "XSX : Leer, Vat, Leer", "XCX : Leer, Kist, Leer"] [crafting.stations] - name = "Draagbare Tafels!" - description = "Gebruik een werkstation in de palm van je hand!" - lore2 = "ALLE ITEMS DIE JE IN DE TAFEL VERGEET BIJ HET SLUITEN ZIJN VOOR ALTIJD VERLOREN!" - lore3 = "Geldige tafels: Aambeeld, Werktafel, Slijpsteen, Cartografietafel, Steenhouwer, Weefgetouw" - lore = ["ALLE ITEMS DIE JE IN DE TAFEL VERGEET BIJ HET SLUITEN ZIJN VOOR ALTIJD VERLOREN!", "Geldige tafels: Aambeeld, Werktafel, Slijpsteen, Cartografietafel, Steenhouwer, Weefgetouw"] +name = "Draagbare Tafels!" +description = "Gebruik een werkstation in de palm van je hand!" +lore2 = "ALLE ITEMS DIE JE IN DE TAFEL VERGEET BIJ HET SLUITEN ZIJN VOOR ALTIJD VERLOREN!" +lore3 = "Geldige tafels: Aambeeld, Werktafel, Slijpsteen, Cartografietafel, Steenhouwer, Weefgetouw" +lore = ["ALLE ITEMS DIE JE IN DE TAFEL VERGEET BIJ HET SLUITEN ZIJN VOOR ALTIJD VERLOREN!", "Geldige tafels: Aambeeld, Werktafel, Slijpsteen, Cartografietafel, Steenhouwer, Weefgetouw"] [crafting.skulls] - name = "Maakbare Schedels!" - description = "Met materialen kun je mobschedels maken!" - lore1 = "Omring een botblok met het volgende om een schedel te krijgen:" - lore2 = "Zombie: Rottend Vlees" - lore3 = "Skelet: Bot" - lore4 = "Creeper: Buskruit" - lore5 = "Wither: Netherbaksteen" - lore6 = "Draak: Drakenadem" - lore = ["Omring een botblok met het volgende om een schedel te krijgen:", "Zombie: Rottend Vlees", "Skelet: Bot", "Creeper: Buskruit", "Wither: Netherbaksteen", "Draak: Drakenadem"] +name = "Maakbare Schedels!" +description = "Met materialen kun je mobschedels maken!" +lore1 = "Omring een botblok met het volgende om een schedel te krijgen:" +lore2 = "Zombie: Rottend Vlees" +lore3 = "Skelet: Bot" +lore4 = "Creeper: Buskruit" +lore5 = "Wither: Netherbaksteen" +lore6 = "Draak: Drakenadem" +lore = ["Omring een botblok met het volgende om een schedel te krijgen:", "Zombie: Rottend Vlees", "Skelet: Bot", "Creeper: Buskruit", "Wither: Netherbaksteen", "Draak: Drakenadem"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Tijd in een Fles" - description = "Draag een temporele fles die tijd opslaat en besteed het om getimede blokken, groeibare gewassen en verouderbare entiteiten zoals babydieren te versnellen. Recept (Vormloos): Snelheidsdrankje + Klok + Glazen Fles." - lore1 = "Opgeslagen seconden opgeladen per tick" - lore2 = "Tijdversnelling per opgeslagen seconde" - lore3 = "Recept (Vormloos): Snelheidsdrankje + Klok + Glazen Fles" - lore = ["Opgeslagen seconden opgeladen per tick", "Tijdversnelling per opgeslagen seconde", "Recept (Vormloos): Snelheidsdrankje + Klok + Glazen Fles"] +name = "Tijd in een Fles" +description = "Draag een temporele fles die tijd opslaat en besteed het om getimede blokken, groeibare gewassen en verouderbare entiteiten zoals babydieren te versnellen. Recept (Vormloos): Snelheidsdrankje + Klok + Glazen Fles." +lore1 = "Opgeslagen seconden opgeladen per tick" +lore2 = "Tijdversnelling per opgeslagen seconde" +lore3 = "Recept (Vormloos): Snelheidsdrankje + Klok + Glazen Fles" +lore = ["Opgeslagen seconden opgeladen per tick", "Tijdversnelling per opgeslagen seconde", "Recept (Vormloos): Snelheidsdrankje + Klok + Glazen Fles"] [chronos.aberrant_touch] - name = "Afwijkende Aanraking" - description = "Melee-aanvallen geven stapelende traagheid ten koste van honger, met strikte PvP-limieten, en verankeren doelwitten bij 5 stapels." - lore1 = "Melee-aanvallen geven stapelende traagheid" - lore2 = "PvE traagheidsduur limiet" - lore3 = "PvP traagheidsversterker limiet" - lore = ["Melee-aanvallen geven stapelende traagheid", "PvE traagheidsduur limiet", "PvP traagheidsversterker limiet"] +name = "Afwijkende Aanraking" +description = "Melee-aanvallen geven stapelende traagheid ten koste van honger, met strikte PvP-limieten, en verankeren doelwitten bij 5 stapels." +lore1 = "Melee-aanvallen geven stapelende traagheid" +lore2 = "PvE traagheidsduur limiet" +lore3 = "PvP traagheidsversterker limiet" +lore = ["Melee-aanvallen geven stapelende traagheid", "PvE traagheidsduur limiet", "PvP traagheidsversterker limiet"] [chronos.instant_recall] - name = "Directe Terugroeping" - description = "Links- of rechtsklik met een klok in je hand om terug te spoelen naar een recent snapshot met herstelde gezondheid en honger." - lore1 = "Terugspoelduur" - lore2 = "Cooldown" - lore3 = "Geen inventaris-terugdraaiing" - lore = ["Terugspoelduur", "Cooldown", "Geen inventaris-terugdraaiing"] +name = "Directe Terugroeping" +description = "Links- of rechtsklik met een klok in je hand om terug te spoelen naar een recent snapshot met herstelde gezondheid en honger." +lore1 = "Terugspoelduur" +lore2 = "Cooldown" +lore3 = "Geen inventaris-terugdraaiing" +lore = ["Terugspoelduur", "Cooldown", "Geen inventaris-terugdraaiing"] [chronos.time_bomb] - name = "Tijdbom" - description = "Gooi een gemaakte chronobom die een temporeel veld creëert, entiteiten vertraagt en projectielen bevriest." - lore1 = "Temporeel veld radius" - lore2 = "Temporeel veld duur" - lore3 = "Bom-cooldown" - lore4 = "Recept (Vormloos): Klok + Sneeuwbal + Diamant + Zand" - lore = ["Temporeel veld radius", "Temporeel veld duur", "Bom-cooldown", "Recept (Vormloos): Klok + Sneeuwbal + Diamant + Zand"] +name = "Tijdbom" +description = "Gooi een gemaakte chronobom die een temporeel veld creëert, entiteiten vertraagt en projectielen bevriest." +lore1 = "Temporeel veld radius" +lore2 = "Temporeel veld duur" +lore3 = "Bom-cooldown" +lore4 = "Recept (Vormloos): Klok + Sneeuwbal + Diamant + Zand" +lore = ["Temporeel veld radius", "Temporeel veld duur", "Bom-cooldown", "Recept (Vormloos): Klok + Sneeuwbal + Diamant + Zand"] # discovery [discovery] [discovery.armor] - name = "Wereldpantser" - description = "Passief pantser afhankelijk van de hardheid van nabije blokken." - lore1 = "Passief Pantser" - lore2 = "Gebaseerd op nabije blokhardheid" - lore3 = "Pantsersterkte:" - lore = ["Passief Pantser", "Gebaseerd op nabije blokhardheid", "Pantsersterkte:"] +name = "Wereldpantser" +description = "Passief pantser afhankelijk van de hardheid van nabije blokken." +lore1 = "Passief Pantser" +lore2 = "Gebaseerd op nabije blokhardheid" +lore3 = "Pantsersterkte:" +lore = ["Passief Pantser", "Gebaseerd op nabije blokhardheid", "Pantsersterkte:"] [discovery.unity] - name = "Experimentele Eenheid" - description = "Het verzamelen van ervaringsbollen voegt XP toe aan willekeurige vaardigheden." - lore1 = "XP " - lore2 = "Per Bol" - lore = ["XP ", "Per Bol"] +name = "Experimentele Eenheid" +description = "Het verzamelen van ervaringsbollen voegt XP toe aan willekeurige vaardigheden." +lore1 = "XP " +lore2 = "Per Bol" +lore = ["XP ", "Per Bol"] [discovery.resist] - name = "Experimentele Weerstand" - description = "Verbruik ervaring om schade te verminderen alleen wanneer een treffer je onder 5 harten zou brengen of je zou doden." - lore0 = "Activeert alleen bij kritieke gezondheid (<= 5 harten) eens per 15 seconden" - lore1 = " Verminderde Schade" - lore2 = "ervaring verbruikt" - lore = ["Activeert alleen bij kritieke gezondheid (<= 5 harten) eens per 15 seconden", " Verminderde Schade", "ervaring verbruikt"] +name = "Experimentele Weerstand" +description = "Verbruik ervaring om schade te verminderen alleen wanneer een treffer je onder 5 harten zou brengen of je zou doden." +lore0 = "Activeert alleen bij kritieke gezondheid (<= 5 harten) eens per 15 seconden" +lore1 = " Verminderde Schade" +lore2 = "ervaring verbruikt" +lore = ["Activeert alleen bij kritieke gezondheid (<= 5 harten) eens per 15 seconden", " Verminderde Schade", "ervaring verbruikt"] [discovery.villager] - name = "Dorpelings Aantrekking" - description = "Hiermee kun je betere aanbiedingen bij dorpelingen krijgen!" - lore1 = "Dit verbruikt XP per interactie met dorpelingen" - lore2 = "Kans per interactie om XP te verbruiken en aanbiedingen te verbeteren" - lore3 = "vereiste XP-verbruik per interactie" - lore = ["Dit verbruikt XP per interactie met dorpelingen", "Kans per interactie om XP te verbruiken en aanbiedingen te verbeteren", "vereiste XP-verbruik per interactie"] +name = "Dorpelings Aantrekking" +description = "Hiermee kun je betere aanbiedingen bij dorpelingen krijgen!" +lore1 = "Dit verbruikt XP per interactie met dorpelingen" +lore2 = "Kans per interactie om XP te verbruiken en aanbiedingen te verbeteren" +lore3 = "vereiste XP-verbruik per interactie" +lore = ["Dit verbruikt XP per interactie met dorpelingen", "Kans per interactie om XP te verbruiken en aanbiedingen te verbeteren", "vereiste XP-verbruik per interactie"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Lapis Teruggave" - description = "Ten koste van 1 extra level XP, met een kans om gratis lapis terug te krijgen" - lore1 = "Voor elk niveau verhoogt het de kosten van betoveren met 1, maar kan tot 3 Lapis teruggeven" - lore = ["Voor elk niveau verhoogt het de kosten van betoveren met 1, maar kan tot 3 Lapis teruggeven"] +name = "Lapis Teruggave" +description = "Ten koste van 1 extra level XP, met een kans om gratis lapis terug te krijgen" +lore1 = "Voor elk niveau verhoogt het de kosten van betoveren met 1, maar kan tot 3 Lapis teruggeven" +lore = ["Voor elk niveau verhoogt het de kosten van betoveren met 1, maar kan tot 3 Lapis teruggeven"] [enchanting.quick_enchant] - name = "Snelklik Betoveren" - description = "Betover items door met betoveringsboeken direct erop te klikken." - lore1 = "Max Gecombineerde Niveaus" - lore2 = "Kan een item niet betoveren met meer dan " - lore3 = "kracht" - lore = ["Max Gecombineerde Niveaus", "Kan een item niet betoveren met meer dan ", "kracht"] +name = "Snelklik Betoveren" +description = "Betover items door met betoveringsboeken direct erop te klikken." +lore1 = "Max Gecombineerde Niveaus" +lore2 = "Kan een item niet betoveren met meer dan " +lore3 = "kracht" +lore = ["Max Gecombineerde Niveaus", "Kan een item niet betoveren met meer dan ", "kracht"] [enchanting.return] - name = "XP Teruggave" - description = "Betover-XP wordt aan je teruggegeven wanneer je een item betoverT." - lore1 = "Uitgegeven ervaring heeft een kans om terugbetaald te worden wanneer je een item betoverT" - lore2 = "Ervaring per Betovering" - lore = ["Uitgegeven ervaring heeft een kans om terugbetaald te worden wanneer je een item betoverT", "Ervaring per Betovering"] +name = "XP Teruggave" +description = "Betover-XP wordt aan je teruggegeven wanneer je een item betoverT." +lore1 = "Uitgegeven ervaring heeft een kans om terugbetaald te worden wanneer je een item betoverT" +lore2 = "Ervaring per Betovering" +lore = ["Uitgegeven ervaring heeft een kans om terugbetaald te worden wanneer je een item betoverT", "Ervaring per Betovering"] # excavation [excavation] [excavation.haste] - name = "Haastige Graver" - description = "Dit versnelt het graafproces, met HAAST!" - lore1 = "Verkrijg Haast tijdens het graven" - lore2 = "x niveaus haast wanneer je ELK blok begint te mijnen." - lore = ["Verkrijg Haast tijdens het graven", "x niveaus haast wanneer je ELK blok begint te mijnen."] +name = "Haastige Graver" +description = "Dit versnelt het graafproces, met HAAST!" +lore1 = "Verkrijg Haast tijdens het graven" +lore2 = "x niveaus haast wanneer je ELK blok begint te mijnen." +lore = ["Verkrijg Haast tijdens het graven", "x niveaus haast wanneer je ELK blok begint te mijnen."] [excavation.spelunker] - name = "Superziende Spelunker!" - description = "Zie ertsen met je ogen, dwars door de grond!" - lore1 = "Erts in je nevenhand, Glowberries in je hoofdhand, en Sluip!" - lore2 = "Blok Bereik: " - lore3 = "Verbruikt Glowberry bij gebruik" - lore = ["Erts in je nevenhand, Glowberries in je hoofdhand, en Sluip!", "Blok Bereik: ", "Verbruikt Glowberry bij gebruik"] +name = "Superziende Spelunker!" +description = "Zie ertsen met je ogen, dwars door de grond!" +lore1 = "Erts in je nevenhand, Glowberries in je hoofdhand, en Sluip!" +lore2 = "Blok Bereik: " +lore3 = "Verbruikt Glowberry bij gebruik" +lore = ["Erts in je nevenhand, Glowberries in je hoofdhand, en Sluip!", "Blok Bereik: ", "Verbruikt Glowberry bij gebruik"] [excavation.drop_to_inventory] - name = "Schop Drop-naar-Inventaris" +name = "Schop Drop-naar-Inventaris" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "Tackle's overontworpen weelderige zakmes" - lore1 = "Waarschijnlijk de krachtigste van velen, hiermee kun je" - lore2 = "dynamisch gereedschap samenvoegen en wisselen, op basis van je behoeften." - lore3 = "Om samen te voegen, shift-klik een item op een ander in je inventaris." - lore4 = "Om gereedschap los te koppelen, sluip en drop het item, en het wordt gedemonteerd." - lore5 = "Je kunt gereedschap in dit zakmes niet breken, maar kapot gereedschap kun je niet gebruiken" - lore6 = "totaal samen te voegen items." - lore7 = "Je kunt vijf of zes gereedschappen gebruiken, of gewoon een!" - lore = ["Waarschijnlijk de krachtigste van velen, hiermee kun je", "dynamisch gereedschap samenvoegen en wisselen, op basis van je behoeften.", "Om samen te voegen, shift-klik een item op een ander in je inventaris.", "Om gereedschap los te koppelen, sluip en drop het item, en het wordt gedemonteerd.", "Je kunt gereedschap in dit zakmes niet breken, maar kapot gereedschap kun je niet gebruiken", "totaal samen te voegen items.", "Je kunt vijf of zes gereedschappen gebruiken, of gewoon een!"] +name = "OMNI - T.O.O.L." +description = "Tackle's overontworpen weelderige zakmes" +lore1 = "Waarschijnlijk de krachtigste van velen, hiermee kun je" +lore2 = "dynamisch gereedschap samenvoegen en wisselen, op basis van je behoeften." +lore3 = "Om samen te voegen, shift-klik een item op een ander in je inventaris." +lore4 = "Om gereedschap los te koppelen, sluip en drop het item, en het wordt gedemonteerd." +lore5 = "Je kunt gereedschap in dit zakmes niet breken, maar kapot gereedschap kun je niet gebruiken" +lore6 = "totaal samen te voegen items." +lore7 = "Je kunt vijf of zes gereedschappen gebruiken, of gewoon een!" +lore = ["Waarschijnlijk de krachtigste van velen, hiermee kun je", "dynamisch gereedschap samenvoegen en wisselen, op basis van je behoeften.", "Om samen te voegen, shift-klik een item op een ander in je inventaris.", "Om gereedschap los te koppelen, sluip en drop het item, en het wordt gedemonteerd.", "Je kunt gereedschap in dit zakmes niet breken, maar kapot gereedschap kun je niet gebruiken", "totaal samen te voegen items.", "Je kunt vijf of zes gereedschappen gebruiken, of gewoon een!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Groeiaura" - description = "Laat de natuur om je heen groeien in een aura" - lore1 = "Blokken Radius" - lore2 = "Groeiaura Sterkte" - lore3 = "Voedselkosten" - lore = ["Blokken Radius", "Groeiaura Sterkte", "Voedselkosten"] +name = "Groeiaura" +description = "Laat de natuur om je heen groeien in een aura" +lore1 = "Blokken Radius" +lore2 = "Groeiaura Sterkte" +lore3 = "Voedselkosten" +lore = ["Blokken Radius", "Groeiaura Sterkte", "Voedselkosten"] [herbalism.hippo] - name = "Kruidkundige's Nijlpaard" - description = "Voedsel consumeren geeft je meer verzadiging" - lore1 = "Voedsel) extra verzadigingspunten bij consumptie" - lore = ["Voedsel) extra verzadigingspunten bij consumptie"] +name = "Kruidkundige's Nijlpaard" +description = "Voedsel consumeren geeft je meer verzadiging" +lore1 = "Voedsel) extra verzadigingspunten bij consumptie" +lore = ["Voedsel) extra verzadigingspunten bij consumptie"] [herbalism.myconid] - name = "Kruidkundige's Myconide" - description = "Geeft je de mogelijkheid om Mycelium te maken" - lore1 = "Elke aarde en een bruine & rode paddenstoel maken Mycelium." - lore = ["Elke aarde en een bruine & rode paddenstoel maken Mycelium."] +name = "Kruidkundige's Myconide" +description = "Geeft je de mogelijkheid om Mycelium te maken" +lore1 = "Elke aarde en een bruine & rode paddenstoel maken Mycelium." +lore = ["Elke aarde en een bruine & rode paddenstoel maken Mycelium."] [herbalism.terralid] - name = "Kruidkundige's Terralide" - description = "Geeft je de mogelijkheid om Grasblokken te maken" - lore1 = "Drie Zaden boven 3 Aarde maken 3 Grasblokken." - lore = ["Drie Zaden boven 3 Aarde maken 3 Grasblokken."] +name = "Kruidkundige's Terralide" +description = "Geeft je de mogelijkheid om Grasblokken te maken" +lore1 = "Drie Zaden boven 3 Aarde maken 3 Grasblokken." +lore = ["Drie Zaden boven 3 Aarde maken 3 Grasblokken."] [herbalism.cobweb] - name = "Webmaker" - description = "Geeft je de mogelijkheid om spinnenwebben te maken in een Werktafel" - lore1 = "Negen touw maakt een spinnenweb." - lore = ["Negen touw maakt een spinnenweb."] +name = "Webmaker" +description = "Geeft je de mogelijkheid om spinnenwebben te maken in een Werktafel" +lore1 = "Negen touw maakt een spinnenweb." +lore = ["Negen touw maakt een spinnenweb."] [herbalism.mushroom_blocks] - name = "Paddenstoelenmaker" - description = "Geeft je de mogelijkheid om paddenstoelenblokken te maken in een Werktafel" - lore1 = "Vier paddenstoelen om een blok te maken, of een blok om een stengel te maken." - lore = ["Vier paddenstoelen om een blok te maken, of een blok om een stengel te maken."] +name = "Paddenstoelenmaker" +description = "Geeft je de mogelijkheid om paddenstoelenblokken te maken in een Werktafel" +lore1 = "Vier paddenstoelen om een blok te maken, of een blok om een stengel te maken." +lore = ["Vier paddenstoelen om een blok te maken, of een blok om een stengel te maken."] [herbalism.drop_to_inventory] - name = "Schoffel Drop-naar-Inventaris" +name = "Schoffel Drop-naar-Inventaris" [herbalism.hungry_shield] - name = "Hongerig Schild" - description = "Neem schade op je honger voordat je gezondheid wordt aangetast." - lore1 = "Weerstaan door Honger" - lore = ["Weerstaan door Honger"] +name = "Hongerig Schild" +description = "Neem schade op je honger voordat je gezondheid wordt aangetast." +lore1 = "Weerstaan door Honger" +lore = ["Weerstaan door Honger"] [herbalism.luck] - name = "Kruidkundige's Geluk" - description = "Wanneer je Gras/Bloemen breekt, heb je kans om een willekeurig item te krijgen" - lore0 = "Bloemen = Voedsel, en Gras = Zaden" - lore1 = "Kans om een item te krijgen door Bloemen te breken" - lore2 = "Kans om een item te krijgen door Gras te breken" - lore = ["Bloemen = Voedsel, en Gras = Zaden", "Kans om een item te krijgen door Bloemen te breken", "Kans om een item te krijgen door Gras te breken"] +name = "Kruidkundige's Geluk" +description = "Wanneer je Gras/Bloemen breekt, heb je kans om een willekeurig item te krijgen" +lore0 = "Bloemen = Voedsel, en Gras = Zaden" +lore1 = "Kans om een item te krijgen door Bloemen te breken" +lore2 = "Kans om een item te krijgen door Gras te breken" +lore = ["Bloemen = Voedsel, en Gras = Zaden", "Kans om een item te krijgen door Bloemen te breken", "Kans om een item te krijgen door Gras te breken"] [herbalism.replant] - name = "Oogsten & Herplanten" - description = "Rechtsklik op een gewas met een schoffel om te oogsten en herplanten." - lore1 = "Blokken Herplant Radius" - lore = ["Blokken Herplant Radius"] +name = "Oogsten & Herplanten" +description = "Rechtsklik op een gewas met een schoffel om te oogsten en herplanten." +lore1 = "Blokken Herplant Radius" +lore = ["Blokken Herplant Radius"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenaline" - description = "Doe meer schade hoe lager je gezondheid is (melee)" - lore1 = "Max Schade" - lore = ["Max Schade"] +name = "Adrenaline" +description = "Doe meer schade hoe lager je gezondheid is (melee)" +lore1 = "Max Schade" +lore = ["Max Schade"] [hunter.penalty] - name = "" - description = "" - lore1 = "Je krijgt gifstapels als je honger opraakt" - lore = ["Je krijgt gifstapels als je honger opraakt"] +name = "" +description = "" +lore1 = "Je krijgt gifstapels als je honger opraakt" +lore = ["Je krijgt gifstapels als je honger opraakt"] [hunter.drop_to_inventory] - name = "Items Drop-naar-Inventaris" - description = "Wanneer je iets doodt / een blok breekt met een zwaard worden de drops naar je inventaris geteleporteerd" - lore1 = "Telkens wanneer een item door een mob/blok wordt gedropt dat je breekt, gaat het naar je inventaris als dat past." - lore = ["Telkens wanneer een item door een mob/blok wordt gedropt dat je breekt, gaat het naar je inventaris als dat past."] +name = "Items Drop-naar-Inventaris" +description = "Wanneer je iets doodt / een blok breekt met een zwaard worden de drops naar je inventaris geteleporteerd" +lore1 = "Telkens wanneer een item door een mob/blok wordt gedropt dat je breekt, gaat het naar je inventaris als dat past." +lore = ["Telkens wanneer een item door een mob/blok wordt gedropt dat je breekt, gaat het naar je inventaris als dat past."] [hunter.invisibility] - name = "Verdwijnende Stap" - description = "Wanneer je geraakt wordt krijg je onzichtbaarheid, ten koste van honger" - lore1 = "Verkrijg passieve onzichtbaarheid wanneer je geraakt wordt" - lore2 = "x Onzichtbaarheidsstapels voor 3 seconden bij treffer" - lore3 = "x Stapelende honger" - lore4 = "Honger stapelduur en vermenigvuldiger." - lore5 = "Onzichtbaarheidsduur" - lore = ["Verkrijg passieve onzichtbaarheid wanneer je geraakt wordt", "x Onzichtbaarheidsstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Onzichtbaarheidsduur"] +name = "Verdwijnende Stap" +description = "Wanneer je geraakt wordt krijg je onzichtbaarheid, ten koste van honger" +lore1 = "Verkrijg passieve onzichtbaarheid wanneer je geraakt wordt" +lore2 = "x Onzichtbaarheidsstapels voor 3 seconden bij treffer" +lore3 = "x Stapelende honger" +lore4 = "Honger stapelduur en vermenigvuldiger." +lore5 = "Onzichtbaarheidsduur" +lore = ["Verkrijg passieve onzichtbaarheid wanneer je geraakt wordt", "x Onzichtbaarheidsstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Onzichtbaarheidsduur"] [hunter.jump_boost] - name = "Jagershoogten" - description = "Wanneer je geraakt wordt krijg je springboost, ten koste van honger" - lore1 = "Verkrijg passieve springboost wanneer je geraakt wordt" - lore2 = "x Springbooststapels voor 3 seconden bij treffer" - lore3 = "x Stapelende honger" - lore4 = "Honger stapelduur en vermenigvuldiger." - lore5 = "Springboost stapelvermenigvuldiger, niet duur." - lore = ["Verkrijg passieve springboost wanneer je geraakt wordt", "x Springbooststapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Springboost stapelvermenigvuldiger, niet duur."] +name = "Jagershoogten" +description = "Wanneer je geraakt wordt krijg je springboost, ten koste van honger" +lore1 = "Verkrijg passieve springboost wanneer je geraakt wordt" +lore2 = "x Springbooststapels voor 3 seconden bij treffer" +lore3 = "x Stapelende honger" +lore4 = "Honger stapelduur en vermenigvuldiger." +lore5 = "Springboost stapelvermenigvuldiger, niet duur." +lore = ["Verkrijg passieve springboost wanneer je geraakt wordt", "x Springbooststapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Springboost stapelvermenigvuldiger, niet duur."] [hunter.luck] - name = "Jagersgeluk" - description = "Wanneer je geraakt wordt krijg je geluk, ten koste van honger" - lore1 = "Verkrijg passief geluk wanneer je geraakt wordt" - lore2 = "x Gelukstapels voor 3 seconden bij treffer" - lore3 = "x Stapelende honger" - lore4 = "Honger stapelduur en vermenigvuldiger." - lore5 = "Geluk stapelvermenigvuldiger, niet duur." - lore = ["Verkrijg passief geluk wanneer je geraakt wordt", "x Gelukstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Geluk stapelvermenigvuldiger, niet duur."] +name = "Jagersgeluk" +description = "Wanneer je geraakt wordt krijg je geluk, ten koste van honger" +lore1 = "Verkrijg passief geluk wanneer je geraakt wordt" +lore2 = "x Gelukstapels voor 3 seconden bij treffer" +lore3 = "x Stapelende honger" +lore4 = "Honger stapelduur en vermenigvuldiger." +lore5 = "Geluk stapelvermenigvuldiger, niet duur." +lore = ["Verkrijg passief geluk wanneer je geraakt wordt", "x Gelukstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Geluk stapelvermenigvuldiger, niet duur."] [hunter.regen] - name = "Jagersregeneratie" - description = "Wanneer je geraakt wordt krijg je regeneratie, ten koste van honger" - lore1 = "Verkrijg passieve regeneratie wanneer je geraakt wordt" - lore2 = "x Regeneratiestapels voor 3 seconden bij treffer" - lore3 = "x Stapelende honger" - lore4 = "Honger stapelduur en vermenigvuldiger." - lore5 = "Regeneratie stapelvermenigvuldiger, niet duur." - lore = ["Verkrijg passieve regeneratie wanneer je geraakt wordt", "x Regeneratiestapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Regeneratie stapelvermenigvuldiger, niet duur."] +name = "Jagersregeneratie" +description = "Wanneer je geraakt wordt krijg je regeneratie, ten koste van honger" +lore1 = "Verkrijg passieve regeneratie wanneer je geraakt wordt" +lore2 = "x Regeneratiestapels voor 3 seconden bij treffer" +lore3 = "x Stapelende honger" +lore4 = "Honger stapelduur en vermenigvuldiger." +lore5 = "Regeneratie stapelvermenigvuldiger, niet duur." +lore = ["Verkrijg passieve regeneratie wanneer je geraakt wordt", "x Regeneratiestapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Regeneratie stapelvermenigvuldiger, niet duur."] [hunter.resistance] - name = "Jagersweerstand" - description = "Wanneer je geraakt wordt krijg je weerstand, ten koste van honger" - lore1 = "Verkrijg passieve weerstand wanneer je geraakt wordt" - lore2 = "x Weerstandsstapels voor 3 seconden bij treffer" - lore3 = "x Stapelende honger" - lore4 = "Honger stapelduur en vermenigvuldiger." - lore5 = "Weerstand stapelvermenigvuldiger, niet duur." - lore = ["Verkrijg passieve weerstand wanneer je geraakt wordt", "x Weerstandsstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Weerstand stapelvermenigvuldiger, niet duur."] +name = "Jagersweerstand" +description = "Wanneer je geraakt wordt krijg je weerstand, ten koste van honger" +lore1 = "Verkrijg passieve weerstand wanneer je geraakt wordt" +lore2 = "x Weerstandsstapels voor 3 seconden bij treffer" +lore3 = "x Stapelende honger" +lore4 = "Honger stapelduur en vermenigvuldiger." +lore5 = "Weerstand stapelvermenigvuldiger, niet duur." +lore = ["Verkrijg passieve weerstand wanneer je geraakt wordt", "x Weerstandsstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Weerstand stapelvermenigvuldiger, niet duur."] [hunter.speed] - name = "Jagerssnelheid" - description = "Wanneer je geraakt wordt krijg je snelheid, ten koste van honger" - lore1 = "Verkrijg passieve snelheid wanneer je geraakt wordt" - lore2 = "x Snelheidsstapels voor 3 seconden bij treffer" - lore3 = "x Stapelende honger" - lore4 = "Honger stapelduur en vermenigvuldiger." - lore5 = "Snelheid stapelvermenigvuldiger, niet duur." - lore = ["Verkrijg passieve snelheid wanneer je geraakt wordt", "x Snelheidsstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Snelheid stapelvermenigvuldiger, niet duur."] +name = "Jagerssnelheid" +description = "Wanneer je geraakt wordt krijg je snelheid, ten koste van honger" +lore1 = "Verkrijg passieve snelheid wanneer je geraakt wordt" +lore2 = "x Snelheidsstapels voor 3 seconden bij treffer" +lore3 = "x Stapelende honger" +lore4 = "Honger stapelduur en vermenigvuldiger." +lore5 = "Snelheid stapelvermenigvuldiger, niet duur." +lore = ["Verkrijg passieve snelheid wanneer je geraakt wordt", "x Snelheidsstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Snelheid stapelvermenigvuldiger, niet duur."] [hunter.strength] - name = "Jagerskracht" - description = "Wanneer je geraakt wordt krijg je kracht, ten koste van honger" - lore1 = "Verkrijg passieve kracht wanneer je geraakt wordt" - lore2 = "x Krachtstapels voor 3 seconden bij treffer" - lore3 = "x Stapelende honger" - lore4 = "Honger stapelduur en vermenigvuldiger." - lore5 = "Kracht stapelvermenigvuldiger, niet duur." - lore = ["Verkrijg passieve kracht wanneer je geraakt wordt", "x Krachtstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Kracht stapelvermenigvuldiger, niet duur."] +name = "Jagerskracht" +description = "Wanneer je geraakt wordt krijg je kracht, ten koste van honger" +lore1 = "Verkrijg passieve kracht wanneer je geraakt wordt" +lore2 = "x Krachtstapels voor 3 seconden bij treffer" +lore3 = "x Stapelende honger" +lore4 = "Honger stapelduur en vermenigvuldiger." +lore5 = "Kracht stapelvermenigvuldiger, niet duur." +lore = ["Verkrijg passieve kracht wanneer je geraakt wordt", "x Krachtstapels voor 3 seconden bij treffer", "x Stapelende honger", "Honger stapelduur en vermenigvuldiger.", "Kracht stapelvermenigvuldiger, niet duur."] # nether [nether] [nether.skull_toss] - name = "Witherschedel Worp" - description1 = "Laat je innerlijke Wither los door" - description2 = "iemands" - description3 = "hoofd te gebruiken." - lore1 = "Seconden cooldown tussen schedelworpen." - lore2 = "Witherschedel gebruiken: Gooi een " - lore3 = "Witherschedel" - lore4 = "die explodeert bij impact." - lore = ["Seconden cooldown tussen schedelworpen.", "Witherschedel gebruiken: Gooi een ", "Witherschedel", "die explodeert bij impact."] +name = "Witherschedel Worp" +description1 = "Laat je innerlijke Wither los door" +description2 = "iemands" +description3 = "hoofd te gebruiken." +lore1 = "Seconden cooldown tussen schedelworpen." +lore2 = "Witherschedel gebruiken: Gooi een " +lore3 = "Witherschedel" +lore4 = "die explodeert bij impact." +lore = ["Seconden cooldown tussen schedelworpen.", "Witherschedel gebruiken: Gooi een ", "Witherschedel", "die explodeert bij impact."] [nether.wither_resist] - name = "Witherweerstand" - description = "Weerstaat withering door de kracht van Netherite." - lore1 = "kans om withering te negeren (per stuk)." - lore2 = "Passief: Het dragen van Netherite Pantser heeft een kans om " - lore3 = "withering te negeren." - lore = ["kans om withering te negeren (per stuk).", "Passief: Het dragen van Netherite Pantser heeft een kans om ", "withering te negeren."] +name = "Witherweerstand" +description = "Weerstaat withering door de kracht van Netherite." +lore1 = "kans om withering te negeren (per stuk)." +lore2 = "Passief: Het dragen van Netherite Pantser heeft een kans om " +lore3 = "withering te negeren." +lore = ["kans om withering te negeren (per stuk).", "Passief: Het dragen van Netherite Pantser heeft een kans om ", "withering te negeren."] [nether.fire_resist] - name = "Vuurweerstand" - description = "Weerstaat vuur door je huid te verharden." - lore1 = "kans om het brandeffect te negeren!" - lore = ["kans om het brandeffect te negeren!"] +name = "Vuurweerstand" +description = "Weerstaat vuur door je huid te verharden." +lore1 = "kans om het brandeffect te negeren!" +lore = ["kans om het brandeffect te negeren!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Automatisch Smelten" - description = "Hiermee kun je gedolven vanilla ertsen automatisch smelten" - lore1 = "Ertsen die gesmolten kunnen worden, worden automatisch gesmolten" - lore2 = "% kans op een extra" - lore = ["Ertsen die gesmolten kunnen worden, worden automatisch gesmolten", "% kans op een extra"] +name = "Automatisch Smelten" +description = "Hiermee kun je gedolven vanilla ertsen automatisch smelten" +lore1 = "Ertsen die gesmolten kunnen worden, worden automatisch gesmolten" +lore2 = "% kans op een extra" +lore = ["Ertsen die gesmolten kunnen worden, worden automatisch gesmolten", "% kans op een extra"] [pickaxe.chisel] - name = "Ertsbeitel" - description = "Rechtsklik op ertsen om er meer erts uit te beitelen, tegen hoge duurzaamheidskosten." - lore1 = "Kans om te Droppen" - lore2 = "Gereedschapsslijtage" - lore = ["Kans om te Droppen", "Gereedschapsslijtage"] +name = "Ertsbeitel" +description = "Rechtsklik op ertsen om er meer erts uit te beitelen, tegen hoge duurzaamheidskosten." +lore1 = "Kans om te Droppen" +lore2 = "Gereedschapsslijtage" +lore = ["Kans om te Droppen", "Gereedschapsslijtage"] [pickaxe.drop_to_inventory] - name = "Pikhouweel Drop-naar-Inventaris" - description = "Wanneer je een blok breekt wordt het item naar je inventaris geteleporteerd" - lore1 = "Telkens wanneer een item uit een gebroken blok valt, gaat het naar je inventaris als dat past." - lore = ["Telkens wanneer een item uit een gebroken blok valt, gaat het naar je inventaris als dat past."] +name = "Pikhouweel Drop-naar-Inventaris" +description = "Wanneer je een blok breekt wordt het item naar je inventaris geteleporteerd" +lore1 = "Telkens wanneer een item uit een gebroken blok valt, gaat het naar je inventaris als dat past." +lore = ["Telkens wanneer een item uit een gebroken blok valt, gaat het naar je inventaris als dat past."] [pickaxe.silk_spawner] - name = "Pikhouweel Silk-Spawner" - description = "Zorgt dat spawners droppen wanneer ze gebroken worden" - lore1 = "Maakt spawners breekbaar met silk touch." - lore2 = "Maakt spawners breekbaar terwijl je sluipt." - lore = ["Maakt spawners breekbaar met silk touch.", "Maakt spawners breekbaar terwijl je sluipt."] +name = "Pikhouweel Silk-Spawner" +description = "Zorgt dat spawners droppen wanneer ze gebroken worden" +lore1 = "Maakt spawners breekbaar met silk touch." +lore2 = "Maakt spawners breekbaar terwijl je sluipt." +lore = ["Maakt spawners breekbaar met silk touch.", "Maakt spawners breekbaar terwijl je sluipt."] [pickaxe.vein_miner] - name = "Adermijnbouw" - description = "Hiermee kun je blokken breken in een ader/cluster van vanilla ertsen" - lore1 = "Sluip en mijn ERTSEN" - lore2 = "bereik van adermijnbouw" - lore3 = "Deze vaardigheid groepeert NIET alle drops bij elkaar!" - lore = ["Sluip en mijn ERTSEN", "bereik van adermijnbouw", "Deze vaardigheid groepeert NIET alle drops bij elkaar!"] +name = "Adermijnbouw" +description = "Hiermee kun je blokken breken in een ader/cluster van vanilla ertsen" +lore1 = "Sluip en mijn ERTSEN" +lore2 = "bereik van adermijnbouw" +lore3 = "Deze vaardigheid groepeert NIET alle drops bij elkaar!" +lore = ["Sluip en mijn ERTSEN", "bereik van adermijnbouw", "Deze vaardigheid groepeert NIET alle drops bij elkaar!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Pijlherstel" - description = "Herstel pijlen nadat je een vijand hebt gedood." - lore1 = "Kans om pijlen te herstellen bij treffer/dood" - lore2 = "Kans: " - lore = ["Kans om pijlen te herstellen bij treffer/dood", "Kans: "] +name = "Pijlherstel" +description = "Herstel pijlen nadat je een vijand hebt gedood." +lore1 = "Kans om pijlen te herstellen bij treffer/dood" +lore2 = "Kans: " +lore = ["Kans om pijlen te herstellen bij treffer/dood", "Kans: "] [ranged.web_shot] - name = "Webstrik" - description = "Omring je doelwit met spinnenwebben wanneer je ze raakt!" - lore1 = "8 spinnenwebben rond een sneeuwbal, en gooi!" - lore2 = "seconden in een kooi, ongeveer." - lore = ["8 spinnenwebben rond een sneeuwbal, en gooi!", "seconden in een kooi, ongeveer."] +name = "Webstrik" +description = "Omring je doelwit met spinnenwebben wanneer je ze raakt!" +lore1 = "8 spinnenwebben rond een sneeuwbal, en gooi!" +lore2 = "seconden in een kooi, ongeveer." +lore = ["8 spinnenwebben rond een sneeuwbal, en gooi!", "seconden in een kooi, ongeveer."] [ranged.force_shot] - name = "Krachtschot" - description = "Schiet projectielen verder en sneller!" - advancementname = "Lange Afstandsschot" - advancementlore = "Land een schot van meer dan 30 blokken afstand!" - lore1 = "Projectielsnelheid" - lore = ["Projectielsnelheid"] +name = "Krachtschot" +description = "Schiet projectielen verder en sneller!" +advancementname = "Lange Afstandsschot" +advancementlore = "Land een schot van meer dan 30 blokken afstand!" +lore1 = "Projectielsnelheid" +lore = ["Projectielsnelheid"] [ranged.lunge_shot] - name = "Uitvalschot" - description = "Terwijl je valt gooien je pijlen je in een willekeurige richting" - lore1 = "Willekeurige Burst Snelheid" - lore = ["Willekeurige Burst Snelheid"] +name = "Uitvalschot" +description = "Terwijl je valt gooien je pijlen je in een willekeurige richting" +lore1 = "Willekeurige Burst Snelheid" +lore = ["Willekeurige Burst Snelheid"] [ranged.arrow_piercing] - name = "Pijl Doorboring" - description = "Voegt doorboring toe aan projectielen! Schiet dwars door dingen!" - lore1 = "Doorboorde Doelen" - lore = ["Doorboorde Doelen"] +name = "Pijl Doorboring" +description = "Voegt doorboring toe aan projectielen! Schiet dwars door dingen!" +lore1 = "Doorboorde Doelen" +lore = ["Doorboorde Doelen"] # rift [rift] [rift.remote_access] - name = "Toegang op Afstand" - description = "Trek vanuit de leegte en open een gemarkeerde container." - lore1 = "Enderparel + Kompas = Reliekschrijn Portkey" - lore2 = "Met dit item kun je containers op afstand openen" - lore3 = "Eenmaal gemaakt, bekijk het item om het gebruik te zien" - notcontainer = "Dat is geen container" - lore = ["Enderparel + Kompas = Reliekschrijn Portkey", "Met dit item kun je containers op afstand openen", "Eenmaal gemaakt, bekijk het item om het gebruik te zien"] +name = "Toegang op Afstand" +description = "Trek vanuit de leegte en open een gemarkeerde container." +lore1 = "Enderparel + Kompas = Reliekschrijn Portkey" +lore2 = "Met dit item kun je containers op afstand openen" +lore3 = "Eenmaal gemaakt, bekijk het item om het gebruik te zien" +notcontainer = "Dat is geen container" +lore = ["Enderparel + Kompas = Reliekschrijn Portkey", "Met dit item kun je containers op afstand openen", "Eenmaal gemaakt, bekijk het item om het gebruik te zien"] [rift.blink] - name = "Rift Flits" - description = "Korte afstand directe teleportatie, slechts een oogwenk verwijderd!" - lore1 = "Blokken per flits (2x verticaal)" - lore2 = "Tijdens het sprinten: Dubbeltik op Springen om te " - lore3 = "Flitsen" - lore = ["Blokken per flits (2x verticaal)", "Tijdens het sprinten: Dubbeltik op Springen om te ", "Flitsen"] +name = "Rift Flits" +description = "Korte afstand directe teleportatie, slechts een oogwenk verwijderd!" +lore1 = "Blokken per flits (2x verticaal)" +lore2 = "Tijdens het sprinten: Dubbeltik op Springen om te " +lore3 = "Flitsen" +lore = ["Blokken per flits (2x verticaal)", "Tijdens het sprinten: Dubbeltik op Springen om te ", "Flitsen"] [rift.chest] - name = "Gemakkelijke Enderkist" - description = "Open een enderkist door er met links op te klikken in je hand." - lore1 = "Klik op een Enderkist in je hand om te openen (plaats hem gewoon niet)" - lore = ["Klik op een Enderkist in je hand om te openen (plaats hem gewoon niet)"] +name = "Gemakkelijke Enderkist" +description = "Open een enderkist door er met links op te klikken in je hand." +lore1 = "Klik op een Enderkist in je hand om te openen (plaats hem gewoon niet)" +lore = ["Klik op een Enderkist in je hand om te openen (plaats hem gewoon niet)"] [rift.descent] - name = "Anti-Levitatie" - description = "Ben je het zat om vast te zitten in de lucht? Dit is de vaardigheid voor jou!" - lore1 = "Sluip gewoon om af te dalen, en je valt langzamer dan normaal!" - lore2 = "Cooldown:" - lore = ["Sluip gewoon om af te dalen, en je valt langzamer dan normaal!", "Cooldown:"] +name = "Anti-Levitatie" +description = "Ben je het zat om vast te zitten in de lucht? Dit is de vaardigheid voor jou!" +lore1 = "Sluip gewoon om af te dalen, en je valt langzamer dan normaal!" +lore2 = "Cooldown:" +lore = ["Sluip gewoon om af te dalen, en je valt langzamer dan normaal!", "Cooldown:"] [rift.gate] - name = "Rift Poort" - description = "Teleporteer naar een gemarkeerde locatie." - lore1 = "KNUTSELEN: Smaragd + Amethist Scherf + Enderparel" - lore2 = "Lees voor gebruik!" - lore3 = "5s vertraging, " - lore4 = "je kunt sterven tijdens deze animatie" - lore = ["KNUTSELEN: Smaragd + Amethist Scherf + Enderparel", "Lees voor gebruik!", "5s vertraging, ", "je kunt sterven tijdens deze animatie"] +name = "Rift Poort" +description = "Teleporteer naar een gemarkeerde locatie." +lore1 = "KNUTSELEN: Smaragd + Amethist Scherf + Enderparel" +lore2 = "Lees voor gebruik!" +lore3 = "5s vertraging, " +lore4 = "je kunt sterven tijdens deze animatie" +lore = ["KNUTSELEN: Smaragd + Amethist Scherf + Enderparel", "Lees voor gebruik!", "5s vertraging, ", "je kunt sterven tijdens deze animatie"] [rift.resist] - name = "Rift Weerstand" - description = "Verkrijg weerstand bij het gebruik van Ender-items & vaardigheden" - lore1 = "+ Passief: Biedt weerstand wanneer je rift-vaardigheden of Ender-items gebruikt" - lore2 = "NIET inclusief draagbare Enderkist, alleen dingen die je kunt consumeren" - lore = ["+ Passief: Biedt weerstand wanneer je rift-vaardigheden of Ender-items gebruikt", "NIET inclusief draagbare Enderkist, alleen dingen die je kunt consumeren"] +name = "Rift Weerstand" +description = "Verkrijg weerstand bij het gebruik van Ender-items & vaardigheden" +lore1 = "+ Passief: Biedt weerstand wanneer je rift-vaardigheden of Ender-items gebruikt" +lore2 = "NIET inclusief draagbare Enderkist, alleen dingen die je kunt consumeren" +lore = ["+ Passief: Biedt weerstand wanneer je rift-vaardigheden of Ender-items gebruikt", "NIET inclusief draagbare Enderkist, alleen dingen die je kunt consumeren"] [rift.visage] - name = "Rift Gelaat" - description = "Voorkomt dat Endermen agressief worden als je Enderparels in je inventaris hebt." - lore1 = "Endermen worden niet agressief als je Enderparels in je inventaris hebt." - lore = ["Endermen worden niet agressief als je Enderparels in je inventaris hebt."] +name = "Rift Gelaat" +description = "Voorkomt dat Endermen agressief worden als je Enderparels in je inventaris hebt." +lore1 = "Endermen worden niet agressief als je Enderparels in je inventaris hebt." +lore = ["Endermen worden niet agressief als je Enderparels in je inventaris hebt."] # seaborn [seaborn] [seaborn.oxygen] - name = "Organische Zuurstoftank" - description = "Houd meer zuurstof vast in je kleine longen!" - lore1 = "Zuurstofcapaciteit Verhoging" - lore = ["Zuurstofcapaciteit Verhoging"] +name = "Organische Zuurstoftank" +description = "Houd meer zuurstof vast in je kleine longen!" +lore1 = "Zuurstofcapaciteit Verhoging" +lore = ["Zuurstofcapaciteit Verhoging"] [seaborn.fishers_fantasy] - name = "Vissersfantasie" - description = "Verdien meer XP van vissen en vang meer vis!" - lore1 = "Voor elk niveau is er een kans om meer XP en vis te krijgen!" - lore = ["Voor elk niveau is er een kans om meer XP en vis te krijgen!"] +name = "Vissersfantasie" +description = "Verdien meer XP van vissen en vang meer vis!" +lore1 = "Voor elk niveau is er een kans om meer XP en vis te krijgen!" +lore = ["Voor elk niveau is er een kans om meer XP en vis te krijgen!"] [seaborn.haste] - name = "Schildpadmijnwerker" - description = "Tijdens het mijnen onder water krijg je haast!" - lore1 = "Haast 3 wordt onder water toegepast tijdens het mijnen (stapelt met AquaAffinity) nadat je waterademeffect is uitgewerkt!" - lore = ["Haast 3 wordt onder water toegepast tijdens het mijnen (stapelt met AquaAffinity) nadat je waterademeffect is uitgewerkt!"] +name = "Schildpadmijnwerker" +description = "Tijdens het mijnen onder water krijg je haast!" +lore1 = "Haast 3 wordt onder water toegepast tijdens het mijnen (stapelt met AquaAffinity) nadat je waterademeffect is uitgewerkt!" +lore = ["Haast 3 wordt onder water toegepast tijdens het mijnen (stapelt met AquaAffinity) nadat je waterademeffect is uitgewerkt!"] [seaborn.night_vision] - name = "Schildpadzicht" - description = "Onder water krijg je nachtzicht" - lore1 = "Verkrijg simpelweg nachtzicht terwijl je onder water bent nadat je waterademeffect is uitgewerkt!" - lore = ["Verkrijg simpelweg nachtzicht terwijl je onder water bent nadat je waterademeffect is uitgewerkt!"] +name = "Schildpadzicht" +description = "Onder water krijg je nachtzicht" +lore1 = "Verkrijg simpelweg nachtzicht terwijl je onder water bent nadat je waterademeffect is uitgewerkt!" +lore = ["Verkrijg simpelweg nachtzicht terwijl je onder water bent nadat je waterademeffect is uitgewerkt!"] [seaborn.dolphin_grace] - name = "Dolfijnengratie" - description = "Zwem als een dolfijn, zonder de dolfijnen" - lore1 = "+ Passief: verkrijg " - lore2 = "x snelheid (dolfijnengratie)" - lore3 = "Duitse precisie-ingenieur- wacht, dat klopt niet... Niet compatibel met Depth Strider" - lore = ["+ Passief: verkrijg ", "x snelheid (dolfijnengratie)", "Duitse precisie-ingenieur- wacht, dat klopt niet... Niet compatibel met Depth Strider"] +name = "Dolfijnengratie" +description = "Zwem als een dolfijn, zonder de dolfijnen" +lore1 = "+ Passief: verkrijg " +lore2 = "x snelheid (dolfijnengratie)" +lore3 = "Duitse precisie-ingenieur- wacht, dat klopt niet... Niet compatibel met Depth Strider" +lore = ["+ Passief: verkrijg ", "x snelheid (dolfijnengratie)", "Duitse precisie-ingenieur- wacht, dat klopt niet... Niet compatibel met Depth Strider"] # stealth [stealth] [stealth.ghost_armor] - name = "Geestenpantser" - description = "Langzaam opbouwend pantser wanneer je geen schade ontvangt, duurt 1 treffer" - lore1 = "Max Pantser" - lore2 = "Snelheid" - lore = ["Max Pantser", "Snelheid"] +name = "Geestenpantser" +description = "Langzaam opbouwend pantser wanneer je geen schade ontvangt, duurt 1 treffer" +lore1 = "Max Pantser" +lore2 = "Snelheid" +lore = ["Max Pantser", "Snelheid"] [stealth.night_vision] - name = "Stealthzicht" - description = "Verkrijg nachtzicht terwijl je sluipt" - lore1 = "Verkrijg een burst van " - lore2 = "nachtzicht" - lore3 = "terwijl je sluipt" - lore = ["Verkrijg een burst van ", "nachtzicht", "terwijl je sluipt"] +name = "Stealthzicht" +description = "Verkrijg nachtzicht terwijl je sluipt" +lore1 = "Verkrijg een burst van " +lore2 = "nachtzicht" +lore3 = "terwijl je sluipt" +lore = ["Verkrijg een burst van ", "nachtzicht", "terwijl je sluipt"] [stealth.snatch] - name = "Voorwerp Grijpen" - description = "Grijp gevallen voorwerpen direct terwijl je sluipt!" - lore1 = "Grijpradius" - lore = ["Grijpradius"] +name = "Voorwerp Grijpen" +description = "Grijp gevallen voorwerpen direct terwijl je sluipt!" +lore1 = "Grijpradius" +lore = ["Grijpradius"] [stealth.speed] - name = "Sluipsnelheid" - description = "Verkrijg snelheid terwijl je sluipt" - lore1 = "Sluipsnelheid" - lore = ["Sluipsnelheid"] +name = "Sluipsnelheid" +description = "Verkrijg snelheid terwijl je sluipt" +lore1 = "Sluipsnelheid" +lore = ["Sluipsnelheid"] [stealth.ender_veil] - name = "Endersluier" - description = "Geen pompoenen meer nodig om Enderman-aanvallen te voorkomen" - lore1 = "Voorkom Enderman-aanvallen terwijl je sluipt" - lore2 = "Voorkom alle Enderman-aanvallen" - lore = ["Voorkom Enderman-aanvallen terwijl je sluipt", "Voorkom alle Enderman-aanvallen"] +name = "Endersluier" +description = "Geen pompoenen meer nodig om Enderman-aanvallen te voorkomen" +lore1 = "Voorkom Enderman-aanvallen terwijl je sluipt" +lore2 = "Voorkom alle Enderman-aanvallen" +lore = ["Voorkom Enderman-aanvallen terwijl je sluipt", "Voorkom alle Enderman-aanvallen"] # sword [sword] [sword.machete] - name = "Machete" - description = "Snijd moeiteloos door gebladerte!" - lore1 = "Snijradius" - lore2 = "Hak-Cooldown" - lore3 = "Gereedschapsslijtage" - lore = ["Snijradius", "Hak-Cooldown", "Gereedschapsslijtage"] +name = "Machete" +description = "Snijd moeiteloos door gebladerte!" +lore1 = "Snijradius" +lore2 = "Hak-Cooldown" +lore3 = "Gereedschapsslijtage" +lore = ["Snijradius", "Hak-Cooldown", "Gereedschapsslijtage"] [sword.bloody_blade] - name = "Bloederig Zwaard" - description = "Slagen met je zwaard veroorzaken bloeding!" - lore1 = "Een levend wezen raken met je zwaard veroorzaakt bloeding" - lore2 = "Bloedingsduur" - lore3 = "Bloedings-Cooldown" - lore = ["Een levend wezen raken met je zwaard veroorzaakt bloeding", "Bloedingsduur", "Bloedings-Cooldown"] +name = "Bloederig Zwaard" +description = "Slagen met je zwaard veroorzaken bloeding!" +lore1 = "Een levend wezen raken met je zwaard veroorzaakt bloeding" +lore2 = "Bloedingsduur" +lore3 = "Bloedings-Cooldown" +lore = ["Een levend wezen raken met je zwaard veroorzaakt bloeding", "Bloedingsduur", "Bloedings-Cooldown"] [sword.poisoned_blade] - name = "Vergiftigd Zwaard" - description = "Slagen met je zwaard veroorzaken vergiftiging!" - lore1 = "Een levend wezen raken met je zwaard veroorzaakt vergiftiging" - lore2 = "Vergiftigingsduur" - lore3 = "Vergiftigings-Cooldown" - lore = ["Een levend wezen raken met je zwaard veroorzaakt vergiftiging", "Vergiftigingsduur", "Vergiftigings-Cooldown"] +name = "Vergiftigd Zwaard" +description = "Slagen met je zwaard veroorzaken vergiftiging!" +lore1 = "Een levend wezen raken met je zwaard veroorzaakt vergiftiging" +lore2 = "Vergiftigingsduur" +lore3 = "Vergiftigings-Cooldown" +lore = ["Een levend wezen raken met je zwaard veroorzaakt vergiftiging", "Vergiftigingsduur", "Vergiftigings-Cooldown"] # taming [taming] [taming.damage] - name = "Temschade" - description = "Verhoog de schade die je getemde dieren toebrengen." - lore1 = "Verhoogde Schade" - lore = ["Verhoogde Schade"] +name = "Temschade" +description = "Verhoog de schade die je getemde dieren toebrengen." +lore1 = "Verhoogde Schade" +lore = ["Verhoogde Schade"] [taming.health] - name = "Temgezondheid" - description = "Verhoog de gezondheid van je getemde dieren." - lore1 = "Verhoogde Gezondheid" - lore = ["Verhoogde Gezondheid"] +name = "Temgezondheid" +description = "Verhoog de gezondheid van je getemde dieren." +lore1 = "Verhoogde Gezondheid" +lore = ["Verhoogde Gezondheid"] [taming.regeneration] - name = "Temregeneratie" - description = "Verhoog de regeneratie van je getemde dieren." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Temregeneratie" +description = "Verhoog de regeneratie van je getemde dieren." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Doornen" - description = "Weerkaats schade terug naar je aanvaller!" - lore1 = "Schade teruggeslagen bij treffer" - lore = ["Schade teruggeslagen bij treffer"] +name = "Doornen" +description = "Weerkaats schade terug naar je aanvaller!" +lore1 = "Schade teruggeslagen bij treffer" +lore = ["Schade teruggeslagen bij treffer"] [tragoul.globe] - name = "Bol van Pijn" - description = "Verdeel de schade die je toebrengt op basis van het aantal vijanden om je heen!" - lore1 = "Hoe meer vijanden om je heen, hoe minder schade je aan elk van hen toebrengt" - lore2 = "Bereik: " - lore3 = "Toegevoegde Schade aan alle Entiteiten: " - lore = ["Hoe meer vijanden om je heen, hoe minder schade je aan elk van hen toebrengt", "Bereik: ", "Toegevoegde Schade aan alle Entiteiten: "] +name = "Bol van Pijn" +description = "Verdeel de schade die je toebrengt op basis van het aantal vijanden om je heen!" +lore1 = "Hoe meer vijanden om je heen, hoe minder schade je aan elk van hen toebrengt" +lore2 = "Bereik: " +lore3 = "Toegevoegde Schade aan alle Entiteiten: " +lore = ["Hoe meer vijanden om je heen, hoe minder schade je aan elk van hen toebrengt", "Bereik: ", "Toegevoegde Schade aan alle Entiteiten: "] [tragoul.healing] - name = "Wil van Pijn" - description = "Krijg gezondheid terug op basis van de schade die je toebrengt!" - lore1 = "Dingen pijn doen heeft nog nooit zo goed gevoeld! Genees van toegebrachte schade" - lore2 = "Er is een schadevenster van 3 seconden voor genezing en een cooldown van 1 seconde " - lore3 = "Genezing Per Schadepercentage: " - lore = ["Dingen pijn doen heeft nog nooit zo goed gevoeld! Genees van toegebrachte schade", "Er is een schadevenster van 3 seconden voor genezing en een cooldown van 1 seconde ", "Genezing Per Schadepercentage: "] +name = "Wil van Pijn" +description = "Krijg gezondheid terug op basis van de schade die je toebrengt!" +lore1 = "Dingen pijn doen heeft nog nooit zo goed gevoeld! Genees van toegebrachte schade" +lore2 = "Er is een schadevenster van 3 seconden voor genezing en een cooldown van 1 seconde " +lore3 = "Genezing Per Schadepercentage: " +lore = ["Dingen pijn doen heeft nog nooit zo goed gevoeld! Genees van toegebrachte schade", "Er is een schadevenster van 3 seconden voor genezing en een cooldown van 1 seconde ", "Genezing Per Schadepercentage: "] [tragoul.lance] - name = "Lijklansen" - description = "Een vijand doden of een vaardigheid die een vijand doodt, spawnt een lans die schade toebrengt aan een nabije vijand!" - lore1 = "Lansen zoeken vanuit alles wat je doodt, EN als deze vaardigheid een vijand doodt." - lore2 = "Offer een deel van je leven om de lansen te maken (dit kan je doden)" - lore3 = "Max Lansen: 1 + " - lore = ["Lansen zoeken vanuit alles wat je doodt, EN als deze vaardigheid een vijand doodt.", "Offer een deel van je leven om de lansen te maken (dit kan je doden)", "Max Lansen: 1 + "] +name = "Lijklansen" +description = "Een vijand doden of een vaardigheid die een vijand doodt, spawnt een lans die schade toebrengt aan een nabije vijand!" +lore1 = "Lansen zoeken vanuit alles wat je doodt, EN als deze vaardigheid een vijand doodt." +lore2 = "Offer een deel van je leven om de lansen te maken (dit kan je doden)" +lore3 = "Max Lansen: 1 + " +lore = ["Lansen zoeken vanuit alles wat je doodt, EN als deze vaardigheid een vijand doodt.", "Offer een deel van je leven om de lansen te maken (dit kan je doden)", "Max Lansen: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Glazen Kanon" - description = "Bonus ongewapende schade hoe lager je pantserwaarde is" - lore1 = "x Schade bij 0 pantser" - lore2 = "Bonusschade Per Niveau" - lore = ["x Schade bij 0 pantser", "Bonusschade Per Niveau"] +name = "Glazen Kanon" +description = "Bonus ongewapende schade hoe lager je pantserwaarde is" +lore1 = "x Schade bij 0 pantser" +lore2 = "Bonusschade Per Niveau" +lore = ["x Schade bij 0 pantser", "Bonusschade Per Niveau"] [unarmed.power] - name = "Ongewapende Kracht" - description = "Verbeterde ongewapende schade" - lore1 = "Schade" - lore = ["Schade"] +name = "Ongewapende Kracht" +description = "Verbeterde ongewapende schade" +lore1 = "Schade" +lore = ["Schade"] [unarmed.sucker_punch] - name = "Sprintstoot" - description = "Sprintstoten, maar dodelijker." - lore1 = "Schade" - lore2 = "Schade neemt toe met je snelheid tijdens het stoten" - lore = ["Schade", "Schade neemt toe met je snelheid tijdens het stoten"] +name = "Sprintstoot" +description = "Sprintstoten, maar dodelijker." +lore1 = "Schade" +lore2 = "Schade neemt toe met je snelheid tijdens het stoten" +lore = ["Schade", "Schade neemt toe met je snelheid tijdens het stoten"] diff --git a/src/main/resources/pl_PL.toml b/src/main/resources/pl_PL.toml index de879ca17..69db20751 100644 --- a/src/main/resources/pl_PL.toml +++ b/src/main/resources/pl_PL.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "Ruszaj sie!" - description = "Przejdz ponad 1 kilometr (1000 blokow)" +title = "Ruszaj sie!" +description = "Przejdz ponad 1 kilometr (1000 blokow)" [advancement.challenge_sprint_5k] - title = "Przebieg 5K!" - description = "Przejdz ponad 5 kilometrow (5000 blokow)" +title = "Przebieg 5K!" +description = "Przejdz ponad 5 kilometrow (5000 blokow)" [advancement.challenge_sprint_50k] - title = "Przesmiglaj 50K!" - description = "Przejdz ponad 50 kilometrow (50 000 blokow)" +title = "Przesmiglaj 50K!" +description = "Przejdz ponad 50 kilometrow (50 000 blokow)" [advancement.challenge_sprint_500k] - title = "Przemierz Wszechswiat!!" - description = "Przejdz ponad 500 kilometrow (500 000 blokow)" +title = "Przemierz Wszechswiat!!" +description = "Przejdz ponad 500 kilometrow (500 000 blokow)" [advancement.challenge_sprint_marathon] - title = "Przebieg (doslownego) Maratonu!" - description = "Przebiegnij ponad 42 195 blokow!" +title = "Przebieg (doslownego) Maratonu!" +description = "Przebiegnij ponad 42 195 blokow!" [advancement.challenge_place_1k] - title = "Poczatkujacy Budowniczy!" - description = "Umiesc 1000 blokow" +title = "Poczatkujacy Budowniczy!" +description = "Umiesc 1000 blokow" [advancement.challenge_place_5k] - title = "Sredniozaawansowany Budowniczy!" - description = "Umiesc 5000 blokow" +title = "Sredniozaawansowany Budowniczy!" +description = "Umiesc 5000 blokow" [advancement.challenge_place_50k] - title = "Zaawansowany Budowniczy!" - description = "Umiesc 50 000 blokow" +title = "Zaawansowany Budowniczy!" +description = "Umiesc 50 000 blokow" [advancement.challenge_place_500k] - title = "Mistrz Budowniczy!" - description = "Umiesc 500 000 blokow" +title = "Mistrz Budowniczy!" +description = "Umiesc 500 000 blokow" [advancement.challenge_place_5m] - title = "Akolita Symetrii!" - description = "RZECZYWISTOSC TO TWOJ PLAC ZABAW! (5 milionow blokow)" +title = "Akolita Symetrii!" +description = "RZECZYWISTOSC TO TWOJ PLAC ZABAW! (5 milionow blokow)" [advancement.challenge_chop_1k] - title = "Poczatkujacy Drwal!" - description = "Wytnij 1000 blokow" +title = "Poczatkujacy Drwal!" +description = "Wytnij 1000 blokow" [advancement.challenge_chop_5k] - title = "Sredniozaawansowany Drwal!" - description = "Wytnij 5000 blokow" +title = "Sredniozaawansowany Drwal!" +description = "Wytnij 5000 blokow" [advancement.challenge_chop_50k] - title = "Zaawansowany Drwal!" - description = "Wytnij 50 000 blokow" +title = "Zaawansowany Drwal!" +description = "Wytnij 50 000 blokow" [advancement.challenge_chop_500k] - title = "Mistrz Drwal!" - description = "Wytnij 500 000 blokow" +title = "Mistrz Drwal!" +description = "Wytnij 500 000 blokow" [advancement.challenge_chop_5m] - title = "Jackson Piesek" - description = "Najlepszy dobry chlopak! (5 milionow blokow)" +title = "Jackson Piesek" +description = "Najlepszy dobry chlopak! (5 milionow blokow)" [advancement.challenge_block_1k] - title = "Ledwo Blokujesz!" - description = "Zablokuj 1000 trafien" +title = "Ledwo Blokujesz!" +description = "Zablokuj 1000 trafien" [advancement.challenge_block_5k] - title = "Blokowanie jest Fajne!" - description = "Zablokuj 5000 trafien" +title = "Blokowanie jest Fajne!" +description = "Zablokuj 5000 trafien" [advancement.challenge_block_50k] - title = "Blokowanie to moje zycie!" - description = "Zablokuj 50 000 trafien" +title = "Blokowanie to moje zycie!" +description = "Zablokuj 50 000 trafien" [advancement.challenge_block_500k] - title = "Blokowanie to moje Powolanie!" - description = "Zablokuj 500 000 trafien" +title = "Blokowanie to moje Powolanie!" +description = "Zablokuj 500 000 trafien" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Zablokuj 5 000 000 trafien" +title = "Die Hand Die Verletzt" +description = "Zablokuj 5 000 000 trafien" [advancement.challenge_brew_1k] - title = "Poczatkujacy Alchemik!" - description = "Wypij 1000 mikstur" +title = "Poczatkujacy Alchemik!" +description = "Wypij 1000 mikstur" [advancement.challenge_brew_5k] - title = "Sredniozaawansowany Alchemik!" - description = "Wypij 5000 mikstur" +title = "Sredniozaawansowany Alchemik!" +description = "Wypij 5000 mikstur" [advancement.challenge_brew_50k] - title = "Zaawansowany Alchemik!" - description = "Wypij 50 000 mikstur" +title = "Zaawansowany Alchemik!" +description = "Wypij 50 000 mikstur" [advancement.challenge_brew_500k] - title = "Mistrz Alchemik!" - description = "Wypij 500 000 mikstur" +title = "Mistrz Alchemik!" +description = "Wypij 500 000 mikstur" [advancement.challenge_brew_5m] - title = "Alchemik" - description = "Wypij 5 000 000 mikstur" +title = "Alchemik" +description = "Wypij 5 000 000 mikstur" [advancement.challenge_brewsplash_1k] - title = "Poczatkujacy Rozpryskiwacz Mikstur!" - description = "Rozpryskaj 1000 mikstur" +title = "Poczatkujacy Rozpryskiwacz Mikstur!" +description = "Rozpryskaj 1000 mikstur" [advancement.challenge_brewsplash_5k] - title = "Sredniozaawansowany Rozpryskiwacz Mikstur!" - description = "Rozpryskaj 5000 mikstur" +title = "Sredniozaawansowany Rozpryskiwacz Mikstur!" +description = "Rozpryskaj 5000 mikstur" [advancement.challenge_brewsplash_50k] - title = "Zaawansowany Rozpryskiwacz Mikstur!" - description = "Rozpryskaj 50 000 mikstur" +title = "Zaawansowany Rozpryskiwacz Mikstur!" +description = "Rozpryskaj 50 000 mikstur" [advancement.challenge_brewsplash_500k] - title = "Mistrz Rozpryskiwania Mikstur!" - description = "Rozpryskaj 500 000 mikstur" +title = "Mistrz Rozpryskiwania Mikstur!" +description = "Rozpryskaj 500 000 mikstur" [advancement.challenge_brewsplash_5m] - title = "Mistrz Splasha" - description = "Rozpryskaj 5 000 000 mikstur" +title = "Mistrz Splasha" +description = "Rozpryskaj 5 000 000 mikstur" [advancement.challenge_craft_1k] - title = "Sprytny Rzemieslnik!" - description = "Wytworz 1000 przedmiotow" +title = "Sprytny Rzemieslnik!" +description = "Wytworz 1000 przedmiotow" [advancement.challenge_craft_5k] - title = "Zrzedliwy Rzemieslnik!" - description = "Wytworz 5000 przedmiotow" +title = "Zrzedliwy Rzemieslnik!" +description = "Wytworz 5000 przedmiotow" [advancement.challenge_craft_50k] - title = "Pokorny Rzemieslnik!" - description = "Wytworz 50 000 przedmiotow" +title = "Pokorny Rzemieslnik!" +description = "Wytworz 50 000 przedmiotow" [advancement.challenge_craft_500k] - title = "Kakofoniczny Rzemieslnik!" - description = "Wytworz 500 000 przedmiotow" +title = "Kakofoniczny Rzemieslnik!" +description = "Wytworz 500 000 przedmiotow" [advancement.challenge_craft_5m] - title = "Kleskowy McCraftface" - description = "Wytworz 5 000 000 przedmiotow" +title = "Kleskowy McCraftface" +description = "Wytworz 5 000 000 przedmiotow" [advancement.challenge_enchant_1k] - title = "Poczatkujacy Zaklinacz!" - description = "Zaczaruj 1000 przedmiotow" +title = "Poczatkujacy Zaklinacz!" +description = "Zaczaruj 1000 przedmiotow" [advancement.challenge_enchant_5k] - title = "Sredniozaawansowany Zaklinacz!" - description = "Zaczaruj 5000 przedmiotow" +title = "Sredniozaawansowany Zaklinacz!" +description = "Zaczaruj 5000 przedmiotow" [advancement.challenge_enchant_50k] - title = "Zaawansowany Zaklinacz!" - description = "Zaczaruj 50 000 przedmiotow" +title = "Zaawansowany Zaklinacz!" +description = "Zaczaruj 50 000 przedmiotow" [advancement.challenge_enchant_500k] - title = "Mistrz Zaklinacz!" - description = "Zaczaruj 500 000 przedmiotow" +title = "Mistrz Zaklinacz!" +description = "Zaczaruj 500 000 przedmiotow" [advancement.challenge_enchant_5m] - title = "Enigmatyczny Zaklinacz" - description = "Zaczaruj 5 000 000 przedmiotow" +title = "Enigmatyczny Zaklinacz" +description = "Zaczaruj 5 000 000 przedmiotow" [advancement.challenge_excavate_1k] - title = "Poczatkujacy Kopacz!" - description = "Wykop 1000 blokow" +title = "Poczatkujacy Kopacz!" +description = "Wykop 1000 blokow" [advancement.challenge_excavate_5k] - title = "Sredniozaawansowany Kopacz!" - description = "Wykop 5000 blokow" +title = "Sredniozaawansowany Kopacz!" +description = "Wykop 5000 blokow" [advancement.challenge_excavate_50k] - title = "Zaawansowany Kopacz!" - description = "Wykop 50 000 blokow" +title = "Zaawansowany Kopacz!" +description = "Wykop 50 000 blokow" [advancement.challenge_excavate_500k] - title = "Mistrz Kopacz!" - description = "Wykop 500 000 blokow" +title = "Mistrz Kopacz!" +description = "Wykop 500 000 blokow" [advancement.challenge_excavate_5m] - title = "Enigmatyczny Kopacz" - description = "Wykop 5 000 000 blokow" +title = "Enigmatyczny Kopacz" +description = "Wykop 5 000 000 blokow" [advancement.horrible_person] - title = "Jestes okropna osoba" - description = "Niepojete, naprawde" +title = "Jestes okropna osoba" +description = "Niepojete, naprawde" [advancement.challenge_turtle_egg_smasher] - title = "Rozbijacz Jaj Zolwi!" - description = "Zniszcz 100 jaj zolwia" +title = "Rozbijacz Jaj Zolwi!" +description = "Zniszcz 100 jaj zolwia" [advancement.challenge_turtle_egg_annihilator] - title = "Anihilator Jaj Zolwi!" - description = "Zniszcz 500 jaj zolwia" +title = "Anihilator Jaj Zolwi!" +description = "Zniszcz 500 jaj zolwia" [advancement.challenge_novice_hunter] - title = "Poczatkujacy Lowca!" - description = "Zabij 100 istot" +title = "Poczatkujacy Lowca!" +description = "Zabij 100 istot" [advancement.challenge_intermediate_hunter] - title = "Sredniozaawansowany Lowca!" - description = "Zabij 500 istot" +title = "Sredniozaawansowany Lowca!" +description = "Zabij 500 istot" [advancement.challenge_advanced_hunter] - title = "Zaawansowany Lowca!" - description = "Zabij 5000 istot" +title = "Zaawansowany Lowca!" +description = "Zabij 5000 istot" [advancement.challenge_creeper_conqueror] - title = "Pogromca Creeperow!" - description = "Zabij 50 creeperow" +title = "Pogromca Creeperow!" +description = "Zabij 50 creeperow" [advancement.challenge_creeper_annihilator] - title = "Anihilator Creeperow!" - description = "Zabij 200 creeperow" +title = "Anihilator Creeperow!" +description = "Zabij 200 creeperow" [advancement.challenge_pickaxe_1k] - title = "Poczatkujacy Gornik" - description = "Zniszcz 1000 blokow" +title = "Poczatkujacy Gornik" +description = "Zniszcz 1000 blokow" [advancement.challenge_pickaxe_5k] - title = "Wykwalifikowany Gornik" - description = "Zniszcz 5000 blokow" +title = "Wykwalifikowany Gornik" +description = "Zniszcz 5000 blokow" [advancement.challenge_pickaxe_50k] - title = "Ekspert Gornik" - description = "Zniszcz 50 000 blokow" +title = "Ekspert Gornik" +description = "Zniszcz 50 000 blokow" [advancement.challenge_pickaxe_500k] - title = "Mistrz Gornik" - description = "Zniszcz 500 000 blokow" +title = "Mistrz Gornik" +description = "Zniszcz 500 000 blokow" [advancement.challenge_pickaxe_5m] - title = "Legendarny Gornik" - description = "Zniszcz 5 000 000 blokow" +title = "Legendarny Gornik" +description = "Zniszcz 5 000 000 blokow" [advancement.challenge_eat_100] - title = "Tyle do jedzenia!" - description = "Zjedz ponad 100 przedmiotow!" +title = "Tyle do jedzenia!" +description = "Zjedz ponad 100 przedmiotow!" [advancement.challenge_eat_1000] - title = "Nienasycony Glod!" - description = "Zjedz ponad 1000 przedmiotow!" +title = "Nienasycony Glod!" +description = "Zjedz ponad 1000 przedmiotow!" [advancement.challenge_eat_10000] - title = "WIECZNY GLOD!" - description = "Zjedz ponad 10 000 przedmiotow!" +title = "WIECZNY GLOD!" +description = "Zjedz ponad 10 000 przedmiotow!" [advancement.challenge_harvest_100] - title = "Pelne Zniwa" - description = "Zbierz ponad 100 upraw!" +title = "Pelne Zniwa" +description = "Zbierz ponad 100 upraw!" [advancement.challenge_harvest_1000] - title = "Wielkie Zniwa" - description = "Zbierz ponad 1000 upraw!" +title = "Wielkie Zniwa" +description = "Zbierz ponad 1000 upraw!" [advancement.challenge_swim_1nm] - title = "Ludzki Okret Podwodny!" - description = "Preplyn 1 Mile Morska (1852 bloki)" +title = "Ludzki Okret Podwodny!" +description = "Preplyn 1 Mile Morska (1852 bloki)" [advancement.challenge_sneak_1k] - title = "Bol Kolan" - description = "Skradaj sie ponad kilometr (1000 blokow)" +title = "Bol Kolan" +description = "Skradaj sie ponad kilometr (1000 blokow)" [advancement.challenge_sneak_5k] - title = "Wedrowiec Cieni" - description = "Skradaj sie ponad 5000 blokow" +title = "Wedrowiec Cieni" +description = "Skradaj sie ponad 5000 blokow" [advancement.challenge_sneak_20k] - title = "Duch" - description = "Skradaj sie ponad 20 000 blokow" +title = "Duch" +description = "Skradaj sie ponad 20 000 blokow" [advancement.challenge_swim_5k] - title = "Gleboki Nurek" - description = "Preplyn ponad 5000 blokow" +title = "Gleboki Nurek" +description = "Preplyn ponad 5000 blokow" [advancement.challenge_swim_20k] - title = "Wybranik Posejdona" - description = "Preplyn ponad 20 000 blokow" +title = "Wybranik Posejdona" +description = "Preplyn ponad 20 000 blokow" [advancement.challenge_sword_100] - title = "Pierwsza Krew" - description = "Zadaj 100 ciosow mieczem" +title = "Pierwsza Krew" +description = "Zadaj 100 ciosow mieczem" [advancement.challenge_sword_1k] - title = "Taniec Ostrzy" - description = "Zadaj 1000 ciosow mieczem" +title = "Taniec Ostrzy" +description = "Zadaj 1000 ciosow mieczem" [advancement.challenge_sword_10k] - title = "Tysiac Ciec" - description = "Zadaj 10 000 ciosow mieczem" +title = "Tysiac Ciec" +description = "Zadaj 10 000 ciosow mieczem" [advancement.challenge_unarmed_100] - title = "Barowy Zawadiaka" - description = "Zadaj 100 ciosow golymi rekami" +title = "Barowy Zawadiaka" +description = "Zadaj 100 ciosow golymi rekami" [advancement.challenge_unarmed_1k] - title = "Zelazne Piesci" - description = "Zadaj 1000 ciosow golymi rekami" +title = "Zelazne Piesci" +description = "Zadaj 1000 ciosow golymi rekami" [advancement.challenge_unarmed_10k] - title = "Jeden Cios" - description = "Zadaj 10 000 ciosow golymi rekami" +title = "Jeden Cios" +description = "Zadaj 10 000 ciosow golymi rekami" [advancement.challenge_trag_1k] - title = "Cena Krwi" - description = "Otrzymaj 1000 obrazen" +title = "Cena Krwi" +description = "Otrzymaj 1000 obrazen" [advancement.challenge_trag_10k] - title = "Szkarlatna Fala" - description = "Otrzymaj 10 000 obrazen" +title = "Szkarlatna Fala" +description = "Otrzymaj 10 000 obrazen" [advancement.challenge_trag_100k] - title = "Awatar Cierpienia" - description = "Otrzymaj 100 000 obrazen" +title = "Awatar Cierpienia" +description = "Otrzymaj 100 000 obrazen" [advancement.challenge_ranged_100] - title = "Strzelnica" - description = "Wystrzel 100 pociskow" +title = "Strzelnica" +description = "Wystrzel 100 pociskow" [advancement.challenge_ranged_1k] - title = "Sokole Oko" - description = "Wystrzel 1000 pociskow" +title = "Sokole Oko" +description = "Wystrzel 1000 pociskow" [advancement.challenge_ranged_10k] - title = "Burza Strzal" - description = "Wystrzel 10 000 pociskow" +title = "Burza Strzal" +description = "Wystrzel 10 000 pociskow" [advancement.challenge_chronos_1h] - title = "Tik Tak" - description = "Spedz 1 godzine online" +title = "Tik Tak" +description = "Spedz 1 godzine online" [advancement.challenge_chronos_24h] - title = "Piaski Czasu" - description = "Spedz 24 godziny online" +title = "Piaski Czasu" +description = "Spedz 24 godziny online" [advancement.challenge_chronos_168h] - title = "Ponadczasowy" - description = "Spedz 168 godzin (1 tydzien) online" +title = "Ponadczasowy" +description = "Spedz 168 godzin (1 tydzien) online" [advancement.challenge_nether_50] - title = "Straznik Piekiel" - description = "Zabij 50 stworzen z Netheru" +title = "Straznik Piekiel" +description = "Zabij 50 stworzen z Netheru" [advancement.challenge_nether_500] - title = "Straznik Otchlani" - description = "Zabij 500 stworzen z Netheru" +title = "Straznik Otchlani" +description = "Zabij 500 stworzen z Netheru" [advancement.challenge_nether_5k] - title = "Wladca Netheru" - description = "Zabij 5000 stworzen z Netheru" +title = "Wladca Netheru" +description = "Zabij 5000 stworzen z Netheru" [advancement.challenge_rift_50] - title = "Anomalia Przestrzenna" - description = "Teleportuj sie 50 razy" +title = "Anomalia Przestrzenna" +description = "Teleportuj sie 50 razy" [advancement.challenge_rift_500] - title = "Wedrowiec Pustki" - description = "Teleportuj sie 500 razy" +title = "Wedrowiec Pustki" +description = "Teleportuj sie 500 razy" [advancement.challenge_rift_5k] - title = "Miedzy Swiatami" - description = "Teleportuj sie 5000 razy" +title = "Miedzy Swiatami" +description = "Teleportuj sie 5000 razy" [advancement.challenge_taming_10] - title = "Zaklinacz Zwierzat" - description = "Rozmnoz 10 zwierzat" +title = "Zaklinacz Zwierzat" +description = "Rozmnoz 10 zwierzat" [advancement.challenge_taming_50] - title = "Przywodca Stada" - description = "Rozmnoz 50 zwierzat" +title = "Przywodca Stada" +description = "Rozmnoz 50 zwierzat" [advancement.challenge_taming_500] - title = "Wladca Bestii" - description = "Rozmnoz 500 zwierzat" +title = "Wladca Bestii" +description = "Rozmnoz 500 zwierzat" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Demon Predkosci" - description = "Przebiegaj ponad 5 Kilometrow (5,000 blokow)" +title = "Demon Predkosci" +description = "Przebiegaj ponad 5 Kilometrow (5,000 blokow)" [advancement.challenge_sprint_dist_50k] - title = "Blyskawiczne Nogi" - description = "Przebiegaj ponad 50 Kilometrow (50,000 blokow)" +title = "Blyskawiczne Nogi" +description = "Przebiegaj ponad 50 Kilometrow (50,000 blokow)" [advancement.challenge_agility_swim_1k] - title = "Biegacz po Wodzie" - description = "Przeplyn ponad 1 Kilometr (1,000 blokow)" +title = "Biegacz po Wodzie" +description = "Przeplyn ponad 1 Kilometr (1,000 blokow)" [advancement.challenge_agility_swim_10k] - title = "Wodny Podroznik" - description = "Przeplyn ponad 10 Kilometrow (10,000 blokow)" +title = "Wodny Podroznik" +description = "Przeplyn ponad 10 Kilometrow (10,000 blokow)" [advancement.challenge_fly_1k] - title = "Tancerz Nieba" - description = "Przeleć ponad 1 Kilometr (1,000 blokow)" +title = "Tancerz Nieba" +description = "Przeleć ponad 1 Kilometr (1,000 blokow)" [advancement.challenge_fly_10k] - title = "Jezdiec Wiatru" - description = "Przeleć ponad 10 Kilometrow (10,000 blokow)" +title = "Jezdiec Wiatru" +description = "Przeleć ponad 10 Kilometrow (10,000 blokow)" [advancement.challenge_agility_sneak_500] - title = "Ciche Kroki" - description = "Przekradnij sie ponad 500 blokow" +title = "Ciche Kroki" +description = "Przekradnij sie ponad 500 blokow" [advancement.challenge_agility_sneak_5k] - title = "Widmowe Kroki" - description = "Przekradnij sie ponad 5 Kilometrow (5,000 blokow)" +title = "Widmowe Kroki" +description = "Przekradnij sie ponad 5 Kilometrow (5,000 blokow)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Ekipa Rozborkowa" - description = "Zniszcz 500 blokow" +title = "Ekipa Rozborkowa" +description = "Zniszcz 500 blokow" [advancement.challenge_demolish_5k] - title = "Kula Burzaca" - description = "Zniszcz 5,000 blokow" +title = "Kula Burzaca" +description = "Zniszcz 5,000 blokow" [advancement.challenge_value_placed_10k] - title = "Wartosciowy Budowniczy" - description = "Ustaw bloki o wartosci 10,000" +title = "Wartosciowy Budowniczy" +description = "Ustaw bloki o wartosci 10,000" [advancement.challenge_value_placed_100k] - title = "Mistrz Architektury" - description = "Ustaw bloki o wartosci 100,000" +title = "Mistrz Architektury" +description = "Ustaw bloki o wartosci 100,000" [advancement.challenge_demolish_val_5k] - title = "Ekspert Odzysku" - description = "Odzyskaj 5,000 wartosci blokow z rozbiórki" +title = "Ekspert Odzysku" +description = "Odzyskaj 5,000 wartosci blokow z rozbiórki" [advancement.challenge_demolish_val_50k] - title = "Calkowita Dekonstrukcja" - description = "Odzyskaj 50,000 wartosci blokow z rozbiórki" +title = "Calkowita Dekonstrukcja" +description = "Odzyskaj 50,000 wartosci blokow z rozbiórki" [advancement.challenge_high_build_100] - title = "Budowniczy Nieba" - description = "Ustaw 100 blokow powyzej Y=128" +title = "Budowniczy Nieba" +description = "Ustaw 100 blokow powyzej Y=128" [advancement.challenge_high_build_1k] - title = "Architekt Chmur" - description = "Ustaw 1,000 blokow powyzej Y=128" +title = "Architekt Chmur" +description = "Ustaw 1,000 blokow powyzej Y=128" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Drwal" - description = "Zamachnij sie toporem 500 razy" +title = "Drwal" +description = "Zamachnij sie toporem 500 razy" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Zamachnij sie toporem 5,000 razy" +title = "Berserker" +description = "Zamachnij sie toporem 5,000 razy" [advancement.challenge_axe_damage_1k] - title = "Tasak" - description = "Zadaj 1,000 obrazen toporem" +title = "Tasak" +description = "Zadaj 1,000 obrazen toporem" [advancement.challenge_axe_damage_10k] - title = "Topor Kata" - description = "Zadaj 10,000 obrazen toporem" +title = "Topor Kata" +description = "Zadaj 10,000 obrazen toporem" [advancement.challenge_axe_value_5k] - title = "Handlarz Drewnem" - description = "Zbierz drewno o wartosci 5,000" +title = "Handlarz Drewnem" +description = "Zbierz drewno o wartosci 5,000" [advancement.challenge_axe_value_50k] - title = "Baron Tartaku" - description = "Zbierz drewno o wartosci 50,000" +title = "Baron Tartaku" +description = "Zbierz drewno o wartosci 50,000" [advancement.challenge_leaves_500] - title = "Dmuchawa do Lisci" - description = "Usun 500 blokow lisci toporem" +title = "Dmuchawa do Lisci" +description = "Usun 500 blokow lisci toporem" [advancement.challenge_leaves_5k] - title = "Defoliator" - description = "Usun 5,000 blokow lisci toporem" +title = "Defoliator" +description = "Usun 5,000 blokow lisci toporem" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Absorber Obrazen" - description = "Zablokuj 1,000 obrazen tarcza" +title = "Absorber Obrazen" +description = "Zablokuj 1,000 obrazen tarcza" [advancement.challenge_block_dmg_10k] - title = "Zywa Tarcza" - description = "Zablokuj 10,000 obrazen tarcza" +title = "Zywa Tarcza" +description = "Zablokuj 10,000 obrazen tarcza" [advancement.challenge_block_proj_100] - title = "Odbijacz Strzal" - description = "Zablokuj 100 pociskow tarcza" +title = "Odbijacz Strzal" +description = "Zablokuj 100 pociskow tarcza" [advancement.challenge_block_proj_1k] - title = "Tarcza Przeciwpociskowa" - description = "Zablokuj 1,000 pociskow tarcza" +title = "Tarcza Przeciwpociskowa" +description = "Zablokuj 1,000 pociskow tarcza" [advancement.challenge_block_melee_500] - title = "Mistrz Parowania" - description = "Zablokuj 500 atakow wrecz tarcza" +title = "Mistrz Parowania" +description = "Zablokuj 500 atakow wrecz tarcza" [advancement.challenge_block_melee_5k] - title = "Zelazna Twierdza" - description = "Zablokuj 5,000 atakow wrecz tarcza" +title = "Zelazna Twierdza" +description = "Zablokuj 5,000 atakow wrecz tarcza" [advancement.challenge_block_heavy_50] - title = "Czolg" - description = "Zablokuj 50 ciezkich atakow (powyzej 5 obrazen)" +title = "Czolg" +description = "Zablokuj 50 ciezkich atakow (powyzej 5 obrazen)" [advancement.challenge_block_heavy_500] - title = "Nieruchomy Obiekt" - description = "Zablokuj 500 ciezkich atakow (powyzej 5 obrazen)" +title = "Nieruchomy Obiekt" +description = "Zablokuj 500 ciezkich atakow (powyzej 5 obrazen)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "Warsztat Piwowara" - description = "Postaw 10 stojakow do warzenia" +title = "Warsztat Piwowara" +description = "Postaw 10 stojakow do warzenia" [advancement.challenge_brew_stands_50] - title = "Fabryka Mikstur" - description = "Postaw 50 stojakow do warzenia" +title = "Fabryka Mikstur" +description = "Postaw 50 stojakow do warzenia" [advancement.challenge_brew_strong_25] - title = "Mocny Napar" - description = "Wypij 25 ulepszonych mikstur" +title = "Mocny Napar" +description = "Wypij 25 ulepszonych mikstur" [advancement.challenge_brew_strong_250] - title = "Maksymalna Moc" - description = "Wypij 250 ulepszonych mikstur" +title = "Maksymalna Moc" +description = "Wypij 250 ulepszonych mikstur" [advancement.challenge_brew_splash_hits_50] - title = "Strefa Rozbryzgu" - description = "Traf 50 istot miksturami rozbryzgowymi" +title = "Strefa Rozbryzgu" +description = "Traf 50 istot miksturami rozbryzgowymi" [advancement.challenge_brew_splash_hits_500] - title = "Doktor Zarazy" - description = "Traf 500 istot miksturami rozbryzgowymi" +title = "Doktor Zarazy" +description = "Traf 500 istot miksturami rozbryzgowymi" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Niespokojny" - description = "Przebadz 1 Kilometr bedac aktywnym" +title = "Niespokojny" +description = "Przebadz 1 Kilometr bedac aktywnym" [advancement.challenge_active_dist_10k] - title = "Tropiciel" - description = "Przebadz 10 Kilometrow bedac aktywnym" +title = "Tropiciel" +description = "Przebadz 10 Kilometrow bedac aktywnym" [advancement.challenge_active_dist_100k] - title = "Wieczny Ruch" - description = "Przebadz 100 Kilometrow bedac aktywnym" +title = "Wieczny Ruch" +description = "Przebadz 100 Kilometrow bedac aktywnym" [advancement.challenge_beds_10] - title = "Ranny Ptaszek" - description = "Przespij sie w lozku 10 razy" +title = "Ranny Ptaszek" +description = "Przespij sie w lozku 10 razy" [advancement.challenge_beds_100] - title = "Pomijacz Czasu" - description = "Przespij sie w lozku 100 razy" +title = "Pomijacz Czasu" +description = "Przespij sie w lozku 100 razy" [advancement.challenge_chronos_tp_50] - title = "Przesuniecie Czasowe" - description = "Teleportuj sie 50 razy" +title = "Przesuniecie Czasowe" +description = "Teleportuj sie 50 razy" [advancement.challenge_chronos_tp_500] - title = "Zakrzywienie Czasu" - description = "Teleportuj sie 500 razy" +title = "Zakrzywienie Czasu" +description = "Teleportuj sie 500 razy" [advancement.challenge_chronos_deaths_10] - title = "Smiertelnik" - description = "Zgin 10 razy" +title = "Smiertelnik" +description = "Zgin 10 razy" [advancement.challenge_chronos_deaths_100] - title = "Pogromca Smierci" - description = "Zgin 100 razy" +title = "Pogromca Smierci" +description = "Zgin 100 razy" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Wartosc Rzemiosla" - description = "Wytwórz przedmioty o lacznej wartosci 10,000" +title = "Wartosc Rzemiosla" +description = "Wytwórz przedmioty o lacznej wartosci 10,000" [advancement.challenge_craft_value_100k] - title = "Rzemielnik" - description = "Wytwórz przedmioty o lacznej wartosci 100,000" +title = "Rzemielnik" +description = "Wytwórz przedmioty o lacznej wartosci 100,000" [advancement.challenge_craft_tools_25] - title = "Kowal Narzedzi" - description = "Wytwórz 25 narzedzi" +title = "Kowal Narzedzi" +description = "Wytwórz 25 narzedzi" [advancement.challenge_craft_tools_250] - title = "Mistrz Kuznicy" - description = "Wytwórz 250 narzedzi" +title = "Mistrz Kuznicy" +description = "Wytwórz 250 narzedzi" [advancement.challenge_craft_armor_25] - title = "Platnerz" - description = "Wytwórz 25 czesci zbroi" +title = "Platnerz" +description = "Wytwórz 25 czesci zbroi" [advancement.challenge_craft_armor_250] - title = "Mistrz Zbrojmistrz" - description = "Wytwórz 250 czesci zbroi" +title = "Mistrz Zbrojmistrz" +description = "Wytwórz 250 czesci zbroi" [advancement.challenge_craft_mass_25k] - title = "Masowa Produkcja" - description = "Wytwórz 25,000 przedmiotow" +title = "Masowa Produkcja" +description = "Wytwórz 25,000 przedmiotow" [advancement.challenge_craft_mass_250k] - title = "Rewolucja Przemyslowa" - description = "Wytwórz 250,000 przedmiotow" +title = "Rewolucja Przemyslowa" +description = "Wytwórz 250,000 przedmiotow" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Kolekcjoner" - description = "Odkryj 50 unikalnych przedmiotow" +title = "Kolekcjoner" +description = "Odkryj 50 unikalnych przedmiotow" [advancement.challenge_discover_items_250] - title = "Katalogista" - description = "Odkryj 250 unikalnych przedmiotow" +title = "Katalogista" +description = "Odkryj 250 unikalnych przedmiotow" [advancement.challenge_discover_blocks_50] - title = "Geodeta" - description = "Odkryj 50 unikalnych blokow" +title = "Geodeta" +description = "Odkryj 50 unikalnych blokow" [advancement.challenge_discover_blocks_250] - title = "Geolog" - description = "Odkryj 250 unikalnych blokow" +title = "Geolog" +description = "Odkryj 250 unikalnych blokow" [advancement.challenge_discover_mobs_25] - title = "Obserwator" - description = "Odkryj 25 unikalnych mobow" +title = "Obserwator" +description = "Odkryj 25 unikalnych mobow" [advancement.challenge_discover_mobs_75] - title = "Przyrodnik" - description = "Odkryj 75 unikalnych mobow" +title = "Przyrodnik" +description = "Odkryj 75 unikalnych mobow" [advancement.challenge_discover_biomes_10] - title = "Wedrowiec" - description = "Odkryj 10 unikalnych biomow" +title = "Wedrowiec" +description = "Odkryj 10 unikalnych biomow" [advancement.challenge_discover_biomes_40] - title = "Obiezyswiat" - description = "Odkryj 40 unikalnych biomow" +title = "Obiezyswiat" +description = "Odkryj 40 unikalnych biomow" [advancement.challenge_discover_foods_10] - title = "Smakosz" - description = "Odkryj 10 unikalnych potraw" +title = "Smakosz" +description = "Odkryj 10 unikalnych potraw" [advancement.challenge_discover_foods_30] - title = "Mistrz Kuchni" - description = "Odkryj 30 unikalnych potraw" +title = "Mistrz Kuchni" +description = "Odkryj 30 unikalnych potraw" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Tkacz Mocy" - description = "Zgromadz 100 mocy zaklinania" +title = "Tkacz Mocy" +description = "Zgromadz 100 mocy zaklinania" [advancement.challenge_enchant_power_1k] - title = "Mistrz Magii" - description = "Zgromadz 1,000 mocy zaklinania" +title = "Mistrz Magii" +description = "Zgromadz 1,000 mocy zaklinania" [advancement.challenge_enchant_levels_1k] - title = "Wydawca Poziomow" - description = "Wydaj 1,000 poziomow doswiadczenia na zaklinanie" +title = "Wydawca Poziomow" +description = "Wydaj 1,000 poziomow doswiadczenia na zaklinanie" [advancement.challenge_enchant_levels_10k] - title = "Pochłaniacz XP" - description = "Wydaj 10,000 poziomow doswiadczenia na zaklinanie" +title = "Pochłaniacz XP" +description = "Wydaj 10,000 poziomow doswiadczenia na zaklinanie" [advancement.challenge_enchant_high_25] - title = "Wysokie Stawki" - description = "Wykonaj 25 zaklinań maksymalnego poziomu" +title = "Wysokie Stawki" +description = "Wykonaj 25 zaklinań maksymalnego poziomu" [advancement.challenge_enchant_high_250] - title = "Legendarny Zaklinacz" - description = "Wykonaj 250 zaklinań maksymalnego poziomu" +title = "Legendarny Zaklinacz" +description = "Wykonaj 250 zaklinań maksymalnego poziomu" [advancement.challenge_enchant_total_500] - title = "Palacz Poziomow" - description = "Wydaj 500 łącznych poziomow na wszystkie zaklecia" +title = "Palacz Poziomow" +description = "Wydaj 500 łącznych poziomow na wszystkie zaklecia" [advancement.challenge_enchant_total_5k] - title = "Magiczna Inwestycja" - description = "Wydaj 5,000 łącznych poziomow na wszystkie zaklecia" +title = "Magiczna Inwestycja" +description = "Wydaj 5,000 łącznych poziomow na wszystkie zaklecia" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Kopacz" - description = "Zamachnij sie łopata 500 razy" +title = "Kopacz" +description = "Zamachnij sie łopata 500 razy" [advancement.challenge_dig_swing_5k] - title = "Koparka" - description = "Zamachnij sie łopata 5,000 razy" +title = "Koparka" +description = "Zamachnij sie łopata 5,000 razy" [advancement.challenge_dig_damage_1k] - title = "Rycerz Łopaty" - description = "Zadaj 1,000 obrazen łopata" +title = "Rycerz Łopaty" +description = "Zadaj 1,000 obrazen łopata" [advancement.challenge_dig_damage_10k] - title = "Mistrz Łopaty" - description = "Zadaj 10,000 obrazen łopata" +title = "Mistrz Łopaty" +description = "Zadaj 10,000 obrazen łopata" [advancement.challenge_dig_value_5k] - title = "Handlarz Ziemia" - description = "Wykop bloki o wartosci 5,000" +title = "Handlarz Ziemia" +description = "Wykop bloki o wartosci 5,000" [advancement.challenge_dig_value_50k] - title = "Baron Ziemi" - description = "Wykop bloki o wartosci 50,000" +title = "Baron Ziemi" +description = "Wykop bloki o wartosci 50,000" [advancement.challenge_dig_gravel_500] - title = "Młynarz Zwiru" - description = "Wykop 500 blokow zwiru, piasku lub gliny" +title = "Młynarz Zwiru" +description = "Wykop 500 blokow zwiru, piasku lub gliny" [advancement.challenge_dig_gravel_5k] - title = "Przesiewacz Piasku" - description = "Wykop 5,000 blokow zwiru, piasku lub gliny" +title = "Przesiewacz Piasku" +description = "Wykop 5,000 blokow zwiru, piasku lub gliny" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Siewca" - description = "Posadz 100 roslin" +title = "Siewca" +description = "Posadz 100 roslin" [advancement.challenge_plant_1k] - title = "Zielony Kciuk" - description = "Posadz 1,000 roslin" +title = "Zielony Kciuk" +description = "Posadz 1,000 roslin" [advancement.challenge_plant_5k] - title = "Baron Rolnictwa" - description = "Posadz 5,000 roslin" +title = "Baron Rolnictwa" +description = "Posadz 5,000 roslin" [advancement.challenge_compost_50] - title = "Recykler" - description = "Kompostuj 50 przedmiotow" +title = "Recykler" +description = "Kompostuj 50 przedmiotow" [advancement.challenge_compost_500] - title = "Wzbogacacz Gleby" - description = "Kompostuj 500 przedmiotow" +title = "Wzbogacacz Gleby" +description = "Kompostuj 500 przedmiotow" [advancement.challenge_shear_50] - title = "Strzygacz" - description = "Ostrzyż 50 istot" +title = "Strzygacz" +description = "Ostrzyż 50 istot" [advancement.challenge_shear_250] - title = "Mistrz Stada" - description = "Ostrzyż 250 istot" +title = "Mistrz Stada" +description = "Ostrzyż 250 istot" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Zabójca" - description = "Zabij 500 stworzen" +title = "Zabójca" +description = "Zabij 500 stworzen" [advancement.challenge_kills_5k] - title = "Kat" - description = "Zabij 5,000 stworzen" +title = "Kat" +description = "Zabij 5,000 stworzen" [advancement.challenge_boss_1] - title = "Pogromca Bossow" - description = "Zabij bossa" +title = "Pogromca Bossow" +description = "Zabij bossa" [advancement.challenge_boss_10] - title = "Pogromca Legend" - description = "Zabij 10 bossow" +title = "Pogromca Legend" +description = "Zabij 10 bossow" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Zwiedły" - description = "Przetrwaj 500 obrazen od witheringu" +title = "Zwiedły" +description = "Przetrwaj 500 obrazen od witheringu" [advancement.challenge_wither_dmg_5k] - title = "Ocalały z Zarazy" - description = "Przetrwaj 5,000 obrazen od witheringu" +title = "Ocalały z Zarazy" +description = "Przetrwaj 5,000 obrazen od witheringu" [advancement.challenge_wither_skel_25] - title = "Zbieracz Kosci" - description = "Zabij 25 witherowych szkieletow" +title = "Zbieracz Kosci" +description = "Zabij 25 witherowych szkieletow" [advancement.challenge_wither_skel_250] - title = "Plaga Szkieletow" - description = "Zabij 250 witherowych szkieletow" +title = "Plaga Szkieletow" +description = "Zabij 250 witherowych szkieletow" [advancement.challenge_wither_boss_1] - title = "Zabójca Withera" - description = "Pokonaj Withera" +title = "Zabójca Withera" +description = "Pokonaj Withera" [advancement.challenge_wither_boss_10] - title = "Władca Netheru" - description = "Pokonaj Withera 10 razy" +title = "Władca Netheru" +description = "Pokonaj Withera 10 razy" [advancement.challenge_roses_10] - title = "Ogrodnik Smierci" - description = "Zniszcz 10 witherowych roz" +title = "Ogrodnik Smierci" +description = "Zniszcz 10 witherowych roz" [advancement.challenge_roses_100] - title = "Zbieracz Zarazy" - description = "Zniszcz 100 witherowych roz" +title = "Zbieracz Zarazy" +description = "Zniszcz 100 witherowych roz" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Ramie Gornika" - description = "Zamachnij sie kilofem 500 razy" +title = "Ramie Gornika" +description = "Zamachnij sie kilofem 500 razy" [advancement.challenge_pick_swing_5k] - title = "Budowniczy Tuneli" - description = "Zamachnij sie kilofem 5,000 razy" +title = "Budowniczy Tuneli" +description = "Zamachnij sie kilofem 5,000 razy" [advancement.challenge_pick_damage_1k] - title = "Wojownik Kilofa" - description = "Zadaj 1,000 obrazen kilofem" +title = "Wojownik Kilofa" +description = "Zadaj 1,000 obrazen kilofem" [advancement.challenge_pick_damage_10k] - title = "Bojowy Kilof" - description = "Zadaj 10,000 obrazen kilofem" +title = "Bojowy Kilof" +description = "Zadaj 10,000 obrazen kilofem" [advancement.challenge_pick_value_5k] - title = "Poszukiwacz Klejnotow" - description = "Wydobadz bloki o wartosci 5,000" +title = "Poszukiwacz Klejnotow" +description = "Wydobadz bloki o wartosci 5,000" [advancement.challenge_pick_value_50k] - title = "Baron Rud" - description = "Wydobadz bloki o wartosci 50,000" +title = "Baron Rud" +description = "Wydobadz bloki o wartosci 50,000" [advancement.challenge_pick_ores_500] - title = "Poszukiwacz" - description = "Wydobadz 500 blokow rudy" +title = "Poszukiwacz" +description = "Wydobadz 500 blokow rudy" [advancement.challenge_pick_ores_5k] - title = "Mistrz Gornik" - description = "Wydobadz 5,000 blokow rudy" +title = "Mistrz Gornik" +description = "Wydobadz 5,000 blokow rudy" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Celny Strzelec" - description = "Zadaj 1,000 obrazen dystansowych" +title = "Celny Strzelec" +description = "Zadaj 1,000 obrazen dystansowych" [advancement.challenge_ranged_dmg_10k] - title = "Smiercionosny Łucznik" - description = "Zadaj 10,000 obrazen dystansowych" +title = "Smiercionosny Łucznik" +description = "Zadaj 10,000 obrazen dystansowych" [advancement.challenge_ranged_dist_5k] - title = "Daleki Zasieg" - description = "Wystrzel pociski na łaczny dystans 5,000 blokow" +title = "Daleki Zasieg" +description = "Wystrzel pociski na łaczny dystans 5,000 blokow" [advancement.challenge_ranged_dist_50k] - title = "Strzelec na Mile" - description = "Wystrzel pociski na łaczny dystans 50,000 blokow" +title = "Strzelec na Mile" +description = "Wystrzel pociski na łaczny dystans 50,000 blokow" [advancement.challenge_ranged_kills_50] - title = "Łucznik" - description = "Zabij 50 mobow bronia dystansowa" +title = "Łucznik" +description = "Zabij 50 mobow bronia dystansowa" [advancement.challenge_ranged_kills_500] - title = "Mistrz Łucznik" - description = "Zabij 500 mobow bronia dystansowa" +title = "Mistrz Łucznik" +description = "Zabij 500 mobow bronia dystansowa" [advancement.challenge_longshot_25] - title = "Snajper" - description = "Traf 25 strzałow dalekiego zasiegu (ponad 30 blokow)" +title = "Snajper" +description = "Traf 25 strzałow dalekiego zasiegu (ponad 30 blokow)" [advancement.challenge_longshot_250] - title = "Orle Oko" - description = "Traf 250 strzałow dalekiego zasiegu (ponad 30 blokow)" +title = "Orle Oko" +description = "Traf 250 strzałow dalekiego zasiegu (ponad 30 blokow)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "Rzucacz Pereł" - description = "Rzuc 50 pereł Endu" +title = "Rzucacz Pereł" +description = "Rzuc 50 pereł Endu" [advancement.challenge_rift_pearls_500] - title = "Maniak Teleportacji" - description = "Rzuc 500 pereł Endu" +title = "Maniak Teleportacji" +description = "Rzuc 500 pereł Endu" [advancement.challenge_rift_enderman_50] - title = "Łowca Endermanow" - description = "Zabij 50 endermanow" +title = "Łowca Endermanow" +description = "Zabij 50 endermanow" [advancement.challenge_rift_enderman_500] - title = "Tropiciel Pustki" - description = "Zabij 500 endermanow" +title = "Tropiciel Pustki" +description = "Zabij 500 endermanow" [advancement.challenge_rift_dragon_500] - title = "Pogromca Smoka" - description = "Zadaj 500 obrazen Smokowi Endu" +title = "Pogromca Smoka" +description = "Zadaj 500 obrazen Smokowi Endu" [advancement.challenge_rift_dragon_5k] - title = "Smocza Zmora" - description = "Zadaj 5,000 obrazen Smokowi Endu" +title = "Smocza Zmora" +description = "Zadaj 5,000 obrazen Smokowi Endu" [advancement.challenge_rift_crystal_10] - title = "Łamacz Kryształow" - description = "Zniszcz 10 kryształow Endu" +title = "Łamacz Kryształow" +description = "Zniszcz 10 kryształow Endu" [advancement.challenge_rift_crystal_100] - title = "Burzyciel Endu" - description = "Zniszcz 100 kryształow Endu" +title = "Burzyciel Endu" +description = "Zniszcz 100 kryształow Endu" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Wedkarz" - description = "Złow 25 ryb" +title = "Wedkarz" +description = "Złow 25 ryb" [advancement.challenge_fish_250] - title = "Mistrz Rybak" - description = "Złow 250 ryb" +title = "Mistrz Rybak" +description = "Złow 250 ryb" [advancement.challenge_drowned_25] - title = "Łowca Utopcow" - description = "Zabij 25 utopcow" +title = "Łowca Utopcow" +description = "Zabij 25 utopcow" [advancement.challenge_drowned_250] - title = "Oczyszczacz Oceanu" - description = "Zabij 250 utopcow" +title = "Oczyszczacz Oceanu" +description = "Zabij 250 utopcow" [advancement.challenge_guardian_10] - title = "Zabójca Straznikow" - description = "Zabij 10 straznikow" +title = "Zabójca Straznikow" +description = "Zabij 10 straznikow" [advancement.challenge_guardian_100] - title = "Łupiezca Swiatyn" - description = "Zabij 100 straznikow" +title = "Łupiezca Swiatyn" +description = "Zabij 100 straznikow" [advancement.challenge_underwater_blocks_100] - title = "Podwodny Gornik" - description = "Zniszcz 100 blokow pod woda" +title = "Podwodny Gornik" +description = "Zniszcz 100 blokow pod woda" [advancement.challenge_underwater_blocks_1k] - title = "Inzynier Wodny" - description = "Zniszcz 1,000 blokow pod woda" +title = "Inzynier Wodny" +description = "Zniszcz 1,000 blokow pod woda" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Nozownik" - description = "Zadaj 500 obrazen podczas skradania" +title = "Nozownik" +description = "Zadaj 500 obrazen podczas skradania" [advancement.challenge_stealth_dmg_5k] - title = "Cichy Zabójca" - description = "Zadaj 5,000 obrazen podczas skradania" +title = "Cichy Zabójca" +description = "Zadaj 5,000 obrazen podczas skradania" [advancement.challenge_stealth_kills_10] - title = "Skrytobójca" - description = "Zabij 10 mobow podczas skradania" +title = "Skrytobójca" +description = "Zabij 10 mobow podczas skradania" [advancement.challenge_stealth_kills_100] - title = "Zniwiarz Cieni" - description = "Zabij 100 mobow podczas skradania" +title = "Zniwiarz Cieni" +description = "Zabij 100 mobow podczas skradania" [advancement.challenge_stealth_time_1h] - title = "Cierpliwy" - description = "Spedz 1 godzine skradajac sie (3,600 sekund)" +title = "Cierpliwy" +description = "Spedz 1 godzine skradajac sie (3,600 sekund)" [advancement.challenge_stealth_time_10h] - title = "Mistrz Cieni" - description = "Spedz 10 godzin skradajac sie (36,000 sekund)" +title = "Mistrz Cieni" +description = "Spedz 10 godzin skradajac sie (36,000 sekund)" [advancement.challenge_stealth_arrows_50] - title = "Cichy Łucznik" - description = "Wystrzel 50 strzal podczas skradania" +title = "Cichy Łucznik" +description = "Wystrzel 50 strzal podczas skradania" [advancement.challenge_stealth_arrows_500] - title = "Widmowy Łucznik" - description = "Wystrzel 500 strzal podczas skradania" +title = "Widmowy Łucznik" +description = "Wystrzel 500 strzal podczas skradania" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Uczen Ostrza" - description = "Zadaj 1,000 obrazen mieczem" +title = "Uczen Ostrza" +description = "Zadaj 1,000 obrazen mieczem" [advancement.challenge_sword_dmg_10k] - title = "Szermierz" - description = "Zadaj 10,000 obrazen mieczem" +title = "Szermierz" +description = "Zadaj 10,000 obrazen mieczem" [advancement.challenge_sword_kills_50] - title = "Pojedynkowicz" - description = "Zabij 50 mobow mieczem" +title = "Pojedynkowicz" +description = "Zabij 50 mobow mieczem" [advancement.challenge_sword_kills_500] - title = "Gladiator" - description = "Zabij 500 mobow mieczem" +title = "Gladiator" +description = "Zabij 500 mobow mieczem" [advancement.challenge_sword_crit_50] - title = "Krytyczny Cios" - description = "Wyląduj 50 trafien krytycznych mieczem" +title = "Krytyczny Cios" +description = "Wyląduj 50 trafien krytycznych mieczem" [advancement.challenge_sword_crit_500] - title = "Mistrz Precyzji" - description = "Wyląduj 500 trafien krytycznych mieczem" +title = "Mistrz Precyzji" +description = "Wyląduj 500 trafien krytycznych mieczem" [advancement.challenge_sword_heavy_25] - title = "Ciezki Cios" - description = "Wyląduj 25 ciezkich ciosow mieczem (powyzej 8 obrazen)" +title = "Ciezki Cios" +description = "Wyląduj 25 ciezkich ciosow mieczem (powyzej 8 obrazen)" [advancement.challenge_sword_heavy_250] - title = "Niszczycielski Cios" - description = "Wyląduj 250 ciezkich ciosow mieczem (powyzej 8 obrazen)" +title = "Niszczycielski Cios" +description = "Wyląduj 250 ciezkich ciosow mieczem (powyzej 8 obrazen)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Trener Bestii" - description = "Twoje zwierzeta zadaja łacznie 500 obrazen" +title = "Trener Bestii" +description = "Twoje zwierzeta zadaja łacznie 500 obrazen" [advancement.challenge_pet_dmg_5k] - title = "Mistrz Wojny" - description = "Twoje zwierzeta zadaja łacznie 5,000 obrazen" +title = "Mistrz Wojny" +description = "Twoje zwierzeta zadaja łacznie 5,000 obrazen" [advancement.challenge_tamed_10] - title = "Przyjaciel Zwierzat" - description = "Oswój 10 zwierzat" +title = "Przyjaciel Zwierzat" +description = "Oswój 10 zwierzat" [advancement.challenge_tamed_100] - title = "Opiekun Zoo" - description = "Oswój 100 zwierzat" +title = "Opiekun Zoo" +description = "Oswój 100 zwierzat" [advancement.challenge_pet_kills_25] - title = "Taktyka Stada" - description = "Twoje zwierzeta zabijaja 25 stworzen" +title = "Taktyka Stada" +description = "Twoje zwierzeta zabijaja 25 stworzen" [advancement.challenge_pet_kills_250] - title = "Alfa Dowódca" - description = "Twoje zwierzeta zabijaja 250 stworzen" +title = "Alfa Dowódca" +description = "Twoje zwierzeta zabijaja 250 stworzen" [advancement.challenge_taming_2500] - title = "Ekspert Hodowli" - description = "Rozmnóz 2,500 zwierzat" +title = "Ekspert Hodowli" +description = "Rozmnóz 2,500 zwierzat" [advancement.challenge_taming_25k] - title = "Mistrz Genetyki" - description = "Rozmnóz 25,000 zwierzat" +title = "Mistrz Genetyki" +description = "Rozmnóz 25,000 zwierzat" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Worek Treningowy" - description = "Otrzymaj 500 trafien" +title = "Worek Treningowy" +description = "Otrzymaj 500 trafien" [advancement.challenge_trag_hits_5k] - title = "Żarłok Kar" - description = "Otrzymaj 5,000 trafien" +title = "Żarłok Kar" +description = "Otrzymaj 5,000 trafien" [advancement.challenge_trag_deaths_10] - title = "Dziewiec Życ" - description = "Zgin 10 razy" +title = "Dziewiec Życ" +description = "Zgin 10 razy" [advancement.challenge_trag_deaths_100] - title = "Powracajacy Koszmar" - description = "Zgin 100 razy" +title = "Powracajacy Koszmar" +description = "Zgin 100 razy" [advancement.challenge_trag_fire_500] - title = "Ofiara Ognia" - description = "Przetrwaj 500 obrazen od ognia" +title = "Ofiara Ognia" +description = "Przetrwaj 500 obrazen od ognia" [advancement.challenge_trag_fire_5k] - title = "Feniks" - description = "Przetrwaj 5,000 obrazen od ognia" +title = "Feniks" +description = "Przetrwaj 5,000 obrazen od ognia" [advancement.challenge_trag_fall_500] - title = "Test Grawitacji" - description = "Przetrwaj 500 obrazen od upadku" +title = "Test Grawitacji" +description = "Przetrwaj 500 obrazen od upadku" [advancement.challenge_trag_fall_5k] - title = "Predkosc Graniczna" - description = "Przetrwaj 5,000 obrazen od upadku" +title = "Predkosc Graniczna" +description = "Przetrwaj 5,000 obrazen od upadku" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Osiłek" - description = "Zadaj 1,000 obrazen gołymi piesciami" +title = "Osiłek" +description = "Zadaj 1,000 obrazen gołymi piesciami" [advancement.challenge_unarmed_dmg_10k] - title = "Mistrz Sztuk Walki" - description = "Zadaj 10,000 obrazen gołymi piesciami" +title = "Mistrz Sztuk Walki" +description = "Zadaj 10,000 obrazen gołymi piesciami" [advancement.challenge_unarmed_kills_25] - title = "Goła Pięsc" - description = "Zabij 25 mobow gołymi piesciami" +title = "Goła Pięsc" +description = "Zabij 25 mobow gołymi piesciami" [advancement.challenge_unarmed_kills_250] - title = "Piesc Legendy" - description = "Zabij 250 mobow gołymi piesciami" +title = "Piesc Legendy" +description = "Zabij 250 mobow gołymi piesciami" [advancement.challenge_unarmed_crit_25] - title = "Krytyczny Cios Pięscia" - description = "Wyląduj 25 trafien krytycznych gołymi piesciami" +title = "Krytyczny Cios Pięscia" +description = "Wyląduj 25 trafien krytycznych gołymi piesciami" [advancement.challenge_unarmed_crit_250] - title = "Precyzyjna Piesc" - description = "Wyląduj 250 trafien krytycznych gołymi piesciami" +title = "Precyzyjna Piesc" +description = "Wyląduj 250 trafien krytycznych gołymi piesciami" [advancement.challenge_unarmed_heavy_25] - title = "Mocny Cios" - description = "Wyląduj 25 ciezkich ciosow gołymi piesciami (powyzej 6 obrazen)" +title = "Mocny Cios" +description = "Wyląduj 25 ciezkich ciosow gołymi piesciami (powyzej 6 obrazen)" [advancement.challenge_unarmed_heavy_250] - title = "Krol Nokautow" - description = "Wyląduj 250 ciezkich ciosow gołymi piesciami (powyzej 6 obrazen)" +title = "Krol Nokautow" +description = "Wyląduj 250 ciezkich ciosow gołymi piesciami (powyzej 6 obrazen)" # items [items] [items.bound_ender_peral] - name = "Relikwiarzowy Switoklin" - usage1 = "Shift + Lewy Przycisk Myszy aby przywiazac" - usage2 = "Prawy Przycisk Myszy aby uzyskac dostep do przywiazanego ekwipunku" +name = "Relikwiarzowy Switoklin" +usage1 = "Shift + Lewy Przycisk Myszy aby przywiazac" +usage2 = "Prawy Przycisk Myszy aby uzyskac dostep do przywiazanego ekwipunku" [items.bound_eye_of_ender] - name = "Kotwica Okularowa" - usage1 = "Prawy Przycisk Myszy aby zuzyc i teleportowac sie do przywiazanej lokalizacji" - usage2 = "Shift + Lewy Przycisk Myszy aby przywiazac do bloku" +name = "Kotwica Okularowa" +usage1 = "Prawy Przycisk Myszy aby zuzyc i teleportowac sie do przywiazanej lokalizacji" +usage2 = "Shift + Lewy Przycisk Myszy aby przywiazac do bloku" [items.bound_redstone_torch] - name = "Pilot Redstone" - usage1 = "Prawy Przycisk Myszy aby wytworzyc impuls Redstone 1-Tick" - usage2 = "Shift + Lewy Przycisk Myszy na bloku 'Cel' aby przywiazac" +name = "Pilot Redstone" +usage1 = "Prawy Przycisk Myszy aby wytworzyc impuls Redstone 1-Tick" +usage2 = "Shift + Lewy Przycisk Myszy na bloku 'Cel' aby przywiazac" [items.bound_snowball] - name = "Pajeczyna Pulapka!" - usage1 = "Rzuc aby stworzyc tymczasowa pulapke z pajeczyn w danym miejscu" +name = "Pajeczyna Pulapka!" +usage1 = "Rzuc aby stworzyc tymczasowa pulapke z pajeczyn w danym miejscu" [items.chrono_time_bottle] - name = "Czas w Butelce" - usage1 = "Pasywnie gromadzi czas bedac w ekwipunku" - usage2 = "Kliknij prawym przyciskiem myszy na bloki czasowe lub mlode zwierzeta aby wydac zgromadzony czas" - stored = "Zgromadzony Czas" +name = "Czas w Butelce" +usage1 = "Pasywnie gromadzi czas bedac w ekwipunku" +usage2 = "Kliknij prawym przyciskiem myszy na bloki czasowe lub mlode zwierzeta aby wydac zgromadzony czas" +stored = "Zgromadzony Czas" [items.chrono_time_bomb] - name = "Bomba Czasowa" - usage1 = "Kliknij prawym przyciskiem myszy aby wystrzelec bolid chrono tworzacy pole temporalne" +name = "Bomba Czasowa" +usage1 = "Kliknij prawym przyciskiem myszy aby wystrzelec bolid chrono tworzacy pole temporalne" [items.elevator_block] - name = "Blok Windy" - usage1 = "Skocz aby teleportowac sie w gore" - usage2 = "Kucnij aby teleportowac sie w dol" - usage3 = "Minimum 2 bloki powietrza miedzy windami" +name = "Blok Windy" +usage1 = "Skocz aby teleportowac sie w gore" +usage2 = "Kucnij aby teleportowac sie w dol" +usage3 = "Minimum 2 bloki powietrza miedzy windami" # snippets [snippets] [snippets.gui] - level = "Poziom" - knowledge = "wiedza" - power_used = "Uzyta Moc" - not_learned = "Nie Nauczone" - xp = "PD do" - welcome = "Witaj!" - welcome_back = "Witaj ponownie!" - xp_bonus_for_time = "PD za" - max_ability_power = "Maksymalna Moc Umiejetnosci" - unlock_this_by_clicking = "Odblokuj to klikajac Prawym Przyciskiem Myszy: " - back = "Wstecz" - unlearn_all = "Oducz sie wszystkiego" - unlearned_all = "Wszystko oduczone" +level = "Poziom" +knowledge = "wiedza" +power_used = "Uzyta Moc" +not_learned = "Nie Nauczone" +xp = "PD do" +welcome = "Witaj!" +welcome_back = "Witaj ponownie!" +xp_bonus_for_time = "PD za" +max_ability_power = "Maksymalna Moc Umiejetnosci" +unlock_this_by_clicking = "Odblokuj to klikajac Prawym Przyciskiem Myszy: " +back = "Wstecz" +unlearn_all = "Oducz sie wszystkiego" +unlearned_all = "Wszystko oduczone" [snippets.adapt_menu] - may_not_unlearn = "NIE MOZESZ SIE ODUCZYC" - may_unlearn = "MOZESZ SIE UCZYC/ODUCZYC" - knowledge_cost = "Koszt Wiedzy" - knowledge_available = "Dostepna Wiedza" - already_learned = "Juz Nauczone" - unlearn_refund = "Kliknij aby Oduczyc sie i Odzyskac" - no_refunds = "HARDCORE, ZWROTY WYLACZONE" - knowledge = "wiedza" - click_learn = "Kliknij aby sie Nauczyc" - no_knowledge = "(Nie masz zadnej Wiedzy)" - you_only_have = "Masz tylko" - how_to_level_up = "Rozwijaj umiejetnosci, aby zwiekszyc maksymalna moc." - not_enough_power = "Za malo mocy! Kazdy Poziom Umiejetnosci kosztuje 1 moc." - power = "moc" - power_drain = "Zuzycie Mocy" - learned = "Nauczone " - unlearned = "Oduczone " - activator_block = "Polka na ksiazki" +may_not_unlearn = "NIE MOZESZ SIE ODUCZYC" +may_unlearn = "MOZESZ SIE UCZYC/ODUCZYC" +knowledge_cost = "Koszt Wiedzy" +knowledge_available = "Dostepna Wiedza" +already_learned = "Juz Nauczone" +unlearn_refund = "Kliknij aby Oduczyc sie i Odzyskac" +no_refunds = "HARDCORE, ZWROTY WYLACZONE" +knowledge = "wiedza" +click_learn = "Kliknij aby sie Nauczyc" +no_knowledge = "(Nie masz zadnej Wiedzy)" +you_only_have = "Masz tylko" +how_to_level_up = "Rozwijaj umiejetnosci, aby zwiekszyc maksymalna moc." +not_enough_power = "Za malo mocy! Kazdy Poziom Umiejetnosci kosztuje 1 moc." +power = "moc" +power_drain = "Zuzycie Mocy" +learned = "Nauczone " +unlearned = "Oduczone " +activator_block = "Polka na ksiazki" [snippets.knowledge_orb] - contains = "zawiera" - knowledge = "wiedzy" - rightclick = "Kliknij Prawym Przyciskiem" - togainknowledge = "aby zdobyc te wiedze" - knowledge_orb = "Kula Wiedzy" +contains = "zawiera" +knowledge = "wiedzy" +rightclick = "Kliknij Prawym Przyciskiem" +togainknowledge = "aby zdobyc te wiedze" +knowledge_orb = "Kula Wiedzy" [snippets.experience_orb] - contains = "zawiera" - xp = "Doswiadczenia" - rightclick = "Kliknij Prawym Przyciskiem" - togainxp = "aby zdobyc to doswiadczenie" - xporb = "Kula Doswiadczenia" +contains = "zawiera" +xp = "Doswiadczenia" +rightclick = "Kliknij Prawym Przyciskiem" +togainxp = "aby zdobyc to doswiadczenie" +xporb = "Kula Doswiadczenia" # skill [skill] [skill.agility] - name = "Zwinnosc" - icon = "⇉" - description = "Zwinnosc to umiejetnosc szybkiego i plynnego poruszania sie w obliczu przeszkod." +name = "Zwinnosc" +icon = "⇉" +description = "Zwinnosc to umiejetnosc szybkiego i plynnego poruszania sie w obliczu przeszkod." [skill.architect] - name = "Architekt" - icon = "⬧" - description = "Struktury sa budulcem swiata. Rzeczywistosc jest w Twoich rekach i pod Twoja kontrola." +name = "Architekt" +icon = "⬧" +description = "Struktury sa budulcem swiata. Rzeczywistosc jest w Twoich rekach i pod Twoja kontrola." [skill.axes] - name = "Siekiery" - icon = "🪓" - description1 = "Po co scinac drzewa, skoro mozna rabac " - description2 = "rzeczy" - description3 = "zamiast tego, ten sam efekt koncowy!" +name = "Siekiery" +icon = "🪓" +description1 = "Po co scinac drzewa, skoro mozna rabac " +description2 = "rzeczy" +description3 = "zamiast tego, ten sam efekt koncowy!" [skill.brewing] - name = "Browarnictwo" - icon = "❦" - description = "Podwojny Babelek, Potrojny Babelek, Poczworny Babelek- Nadal nie moge wlac tej mikstury do kotla" +name = "Browarnictwo" +icon = "❦" +description = "Podwojny Babelek, Potrojny Babelek, Poczworny Babelek- Nadal nie moge wlac tej mikstury do kotla" [skill.blocking] - name = "Blokowanie" - icon = "🛡" - description = "Kije i kamienie nie zlamia Ci kosci, ale tarcza owszem." +name = "Blokowanie" +icon = "🛡" +description = "Kije i kamienie nie zlamia Ci kosci, ale tarcza owszem." [skill.crafting] - name = "Rzemioslo" - icon = "⌂" - description = "Skoro nie ma juz czesci do ulozenia, czemu nie zrobic kolejnej?" +name = "Rzemioslo" +icon = "⌂" +description = "Skoro nie ma juz czesci do ulozenia, czemu nie zrobic kolejnej?" [skill.discovery] - name = "Odkrycie" - icon = "⚛" - description = "Gdy twoja percepcja sie rozszerza, twoj umysl rozplatuje sie, by odkryc to, czego nie odkryles." +name = "Odkrycie" +icon = "⚛" +description = "Gdy twoja percepcja sie rozszerza, twoj umysl rozplatuje sie, by odkryc to, czego nie odkryles." [skill.enchanting] - name = "Zaklecia" - icon = "✠" - description = "O czym ty mowisz? Proroctwa, wizje, przesadny belkot?" +name = "Zaklecia" +icon = "✠" +description = "O czym ty mowisz? Proroctwa, wizje, przesadny belkot?" [skill.excavation] - name = "Wykopywanie" - icon = "ᛳ" - description = "Kopie kopie doleczek..." +name = "Wykopywanie" +icon = "ᛳ" +description = "Kopie kopie doleczek..." [skill.herbalism] - name = "Zielarstwo" - icon = "⚘" - description = "Nie moge znalezc zadnych roslin, ale moge znalezc nasiona i- czy to... Ziele?" +name = "Zielarstwo" +icon = "⚘" +description = "Nie moge znalezc zadnych roslin, ale moge znalezc nasiona i- czy to... Ziele?" [skill.hunter] - name = "Lowca" - icon = "☠" - description = "W polowaniu liczy sie podroz, nie wynik." +name = "Lowca" +icon = "☠" +description = "W polowaniu liczy sie podroz, nie wynik." [skill.nether] - name = "Nether" - icon = "₪" - description = "Z glebin samego Netheru." +name = "Nether" +icon = "₪" +description = "Z glebin samego Netheru." [skill.pickaxe] - name = "Kilof" - icon = "⛏" - description = "Krasnoludy sa gornikami, ale ja tez sie czegos nauczylem. JESTEM SZWEDEM" +name = "Kilof" +icon = "⛏" +description = "Krasnoludy sa gornikami, ale ja tez sie czegos nauczylem. JESTEM SZWEDEM" [skill.ranged] - name = "Dystansowy" - icon = "🏹" - description = "Dystans to klucz do zwyciestwa i klucz do przetrwania." +name = "Dystansowy" +icon = "🏹" +description = "Dystans to klucz do zwyciestwa i klucz do przetrwania." [skill.rift] - name = "Szczelina" - icon = "❍" - description = "Szczelina to zraca uprza, ale ty ja opanowalec." +name = "Szczelina" +icon = "❍" +description = "Szczelina to zraca uprza, ale ty ja opanowalec." [skill.seaborne] - name = "Morski" - icon = "🎣" - description = "Dzieki tej umiejetnosci mozesz obudzic cuda wody." +name = "Morski" +icon = "🎣" +description = "Dzieki tej umiejetnosci mozesz obudzic cuda wody." [skill.stealth] - name = "Skradanie" - icon = "☯" - description = "Sztuka niewidzialnosci. Krocz w cieniach." +name = "Skradanie" +icon = "☯" +description = "Sztuka niewidzialnosci. Krocz w cieniach." [skill.swords] - name = "Miecze" - icon = "⚔" - description = "Moca Szarego Kamienia!" +name = "Miecze" +icon = "⚔" +description = "Moca Szarego Kamienia!" [skill.taming] - name = "Oswajanie" - icon = "♥" - description = "Papugi i pszczoly... a ty?" +name = "Oswajanie" +icon = "♥" +description = "Papugi i pszczoly... a ty?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Krew plynie zylami wszechswiata. Scisnieta przez twoje dlonie." +name = "TragOul" +icon = "🗡" +description = "Krew plynie zylami wszechswiata. Scisnieta przez twoje dlonie." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Nakrec Zegar wszechswiata, poczuj przeplyw. Zlam zegar, stac sie nim." +name = "Chronos" +icon = "🕒" +description = "Nakrec Zegar wszechswiata, poczuj przeplyw. Zlam zegar, stac sie nim." [skill.unarmed] - name = "Walka Wreczna" - icon = "»" - description = "Bez broni nie znaczy bez sily." +name = "Walka Wreczna" +icon = "»" +description = "Bez broni nie znaczy bez sily." # agility [agility] [agility.armor_up] - name = "Pancerz-Up" - description = "Zdobywaj wiecej pancerza im dluzej sprintujesz!" - lore1 = "Maksymalny Pancerz" - lore2 = "Czas Narastania Pancerza" - lore = ["Maksymalny Pancerz", "Czas Narastania Pancerza"] +name = "Pancerz-Up" +description = "Zdobywaj wiecej pancerza im dluzej sprintujesz!" +lore1 = "Maksymalny Pancerz" +lore2 = "Czas Narastania Pancerza" +lore = ["Maksymalny Pancerz", "Czas Narastania Pancerza"] [agility.ladder_slide] - name = "Zjazd po Drabinie" - description = "Wspinaj sie i zesliguj po drabinach znacznie szybciej w obu kierunkach." - lore1 = "Mnoznik predkosci na drabinie" - lore2 = "Predkosc szybkiego zjazdu" - lore = ["Mnoznik predkosci na drabinie", "Predkosc szybkiego zjazdu"] +name = "Zjazd po Drabinie" +description = "Wspinaj sie i zesliguj po drabinach znacznie szybciej w obu kierunkach." +lore1 = "Mnoznik predkosci na drabinie" +lore2 = "Predkosc szybkiego zjazdu" +lore = ["Mnoznik predkosci na drabinie", "Predkosc szybkiego zjazdu"] [agility.super_jump] - name = "Super Skok" - description = "Wyjatkowa Przewaga Wysokosci." - lore1 = "Maksymalna Wysokosc Skoku" - lore2 = "Kucnij + Skok aby wykonac Super Skok!" - lore = ["Maksymalna Wysokosc Skoku", "Kucnij + Skok aby wykonac Super Skok!"] +name = "Super Skok" +description = "Wyjatkowa Przewaga Wysokosci." +lore1 = "Maksymalna Wysokosc Skoku" +lore2 = "Kucnij + Skok aby wykonac Super Skok!" +lore = ["Maksymalna Wysokosc Skoku", "Kucnij + Skok aby wykonac Super Skok!"] [agility.wall_jump] - name = "Skok od Sciany" - description = "Przytrzymaj Shift w powietrzu przy scianie aby sie przyczepic i odskoczycz!" - lore1 = "Maksymalna Ilosc Skokow" - lore2 = "Wysokosc Skoku" - lore = ["Maksymalna Ilosc Skokow", "Wysokosc Skoku"] +name = "Skok od Sciany" +description = "Przytrzymaj Shift w powietrzu przy scianie aby sie przyczepic i odskoczycz!" +lore1 = "Maksymalna Ilosc Skokow" +lore2 = "Wysokosc Skoku" +lore = ["Maksymalna Ilosc Skokow", "Wysokosc Skoku"] [agility.wind_up] - name = "Rozkrecanie" - description = "Nabieraj predkosci im dluzej biegniesz!" - lore1 = "Maksymalna Predkosc" - lore2 = "Czas Rozkrecania" - lore = ["Maksymalna Predkosc", "Czas Rozkrecania"] +name = "Rozkrecanie" +description = "Nabieraj predkosci im dluzej biegniesz!" +lore1 = "Maksymalna Predkosc" +lore2 = "Czas Rozkrecania" +lore = ["Maksymalna Predkosc", "Czas Rozkrecania"] # architect [architect] [architect.elevator] - name = "Winda" - description = "Pozwala zbudowac winde do szybkiego teleportowania sie w pionie!" - lore1 = "Odblokowuje przepis na winde: X=WELNA, Y=PERLA ENDERA" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Odblokowuje przepis na winde: X=WELNA, Y=PERLA ENDERA", "XXX", "XYX", "XXX"] +name = "Winda" +description = "Pozwala zbudowac winde do szybkiego teleportowania sie w pionie!" +lore1 = "Odblokowuje przepis na winde: X=WELNA, Y=PERLA ENDERA" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Odblokowuje przepis na winde: X=WELNA, Y=PERLA ENDERA", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Magiczny Fundament" - description = "Pozwala na kucniecie i umieszczenie tymczasowego fundamentu pod soba!" - lore1 = "Magicznie stworz: " - lore2 = "Blokow pod soba!" - lore = ["Magicznie stworz: ", "Blokow pod soba!"] +name = "Magiczny Fundament" +description = "Pozwala na kucniecie i umieszczenie tymczasowego fundamentu pod soba!" +lore1 = "Magicznie stworz: " +lore2 = "Blokow pod soba!" +lore = ["Magicznie stworz: ", "Blokow pod soba!"] [architect.glass] - name = "Jedwabny Dotyk Szkla" - description = "Zapobiega utracie blokow szklanych kiedy rozbijasz je gola reka!" - lore1 = "Twoje rece zyskuja Jedwabny Dotyk dla Szkla" - lore = ["Twoje rece zyskuja Jedwabny Dotyk dla Szkla"] +name = "Jedwabny Dotyk Szkla" +description = "Zapobiega utracie blokow szklanych kiedy rozbijasz je gola reka!" +lore1 = "Twoje rece zyskuja Jedwabny Dotyk dla Szkla" +lore = ["Twoje rece zyskuja Jedwabny Dotyk dla Szkla"] [architect.wireless_redstone] - name = "Pilot Redstone" - description = "Pozwala uzyc pochodni z redstone do zdalnego przelaczania redstone!" - lore1 = "Cel + Pochodnia Redstone + Perla Endera = 1 Pilot Redstone" - lore = ["Cel + Pochodnia Redstone + Perla Endera = 1 Pilot Redstone"] +name = "Pilot Redstone" +description = "Pozwala uzyc pochodni z redstone do zdalnego przelaczania redstone!" +lore1 = "Cel + Pochodnia Redstone + Perla Endera = 1 Pilot Redstone" +lore = ["Cel + Pochodnia Redstone + Perla Endera = 1 Pilot Redstone"] [architect.placement] - name = "Rozdzka Budowniczego" - description = "Pozwala umieszczac wiele blokow naraz. Aby aktywowac: kucnij i trzymaj blok pasujacy do bloku na ktory patrzysz, nastepnie postaw! Pamietaj, moze byc konieczne lekkie przesuniecie sie!" - lore1 = "Potrzebujesz" - lore2 = "blokow w rece aby to postawic" - lore3 = "Materialowa Rozdzka Budowniczego" - lore = ["Potrzebujesz", "blokow w rece aby to postawic", "Materialowa Rozdzka Budowniczego"] +name = "Rozdzka Budowniczego" +description = "Pozwala umieszczac wiele blokow naraz. Aby aktywowac: kucnij i trzymaj blok pasujacy do bloku na ktory patrzysz, nastepnie postaw! Pamietaj, moze byc konieczne lekkie przesuniecie sie!" +lore1 = "Potrzebujesz" +lore2 = "blokow w rece aby to postawic" +lore3 = "Materialowa Rozdzka Budowniczego" +lore = ["Potrzebujesz", "blokow w rece aby to postawic", "Materialowa Rozdzka Budowniczego"] # axe [axe] [axe.chop] - name = "Ciecie Siekiera" - description = "Scinaj drzewa klikajac prawym przyciskiem myszy na dolny klode!" - lore1 = "Blokow na Ciecie" - lore2 = "Czas Odnowienia Ciecia" - lore3 = "Zuzycie Narzedzia" - lore = ["Blokow na Ciecie", "Czas Odnowienia Ciecia", "Zuzycie Narzedzia"] +name = "Ciecie Siekiera" +description = "Scinaj drzewa klikajac prawym przyciskiem myszy na dolny klode!" +lore1 = "Blokow na Ciecie" +lore2 = "Czas Odnowienia Ciecia" +lore3 = "Zuzycie Narzedzia" +lore = ["Blokow na Ciecie", "Czas Odnowienia Ciecia", "Zuzycie Narzedzia"] [axe.log_swap] - name = "Wymiennik Klod Lucy" - description = "Zmien rodzaj klod na Stole Rzemicslniczym!" - lore1 = "8 Klod dowolnego rodzaju + 1 sadzonka = 8 klod typu sadzonki" - lore = ["8 Klod dowolnego rodzaju + 1 sadzonka = 8 klod typu sadzonki"] +name = "Wymiennik Klod Lucy" +description = "Zmien rodzaj klod na Stole Rzemicslniczym!" +lore1 = "8 Klod dowolnego rodzaju + 1 sadzonka = 8 klod typu sadzonki" +lore = ["8 Klod dowolnego rodzaju + 1 sadzonka = 8 klod typu sadzonki"] [axe.drop_to_inventory] - name = "Siekiera Zrzut-Do-Ekwipunku" +name = "Siekiera Zrzut-Do-Ekwipunku" [axe.ground_smash] - name = "Uderzenie w Ziemie Siekiera" - description = "Skocz, a potem kucnij i zmiazdzh wszystkich poblizskich wrogow." - lore1 = "Obrazenia" - lore2 = "Zasieg Blokow" - lore3 = "Sila" - lore4 = "Czas Odnowienia Uderzenia" - lore = ["Obrazenia", "Zasieg Blokow", "Sila", "Czas Odnowienia Uderzenia"] +name = "Uderzenie w Ziemie Siekiera" +description = "Skocz, a potem kucnij i zmiazdzh wszystkich poblizskich wrogow." +lore1 = "Obrazenia" +lore2 = "Zasieg Blokow" +lore3 = "Sila" +lore4 = "Czas Odnowienia Uderzenia" +lore = ["Obrazenia", "Zasieg Blokow", "Sila", "Czas Odnowienia Uderzenia"] [axe.leaf_miner] - name = "Gornik Lisci" - description = "Pozwala niszczyc duze ilosci lisci naraz!" - lore1 = "Kucnij i niszcz LISCIE" - lore2 = "zasieg niszczenia lisci" - lore3 = "Nie otrzymasz dropu z lisci (Zapobieganie Naduzywaniu)" - lore = ["Kucnij i niszcz LISCIE", "zasieg niszczenia lisci", "Nie otrzymasz dropu z lisci (Zapobieganie Naduzywaniu)"] +name = "Gornik Lisci" +description = "Pozwala niszczyc duze ilosci lisci naraz!" +lore1 = "Kucnij i niszcz LISCIE" +lore2 = "zasieg niszczenia lisci" +lore3 = "Nie otrzymasz dropu z lisci (Zapobieganie Naduzywaniu)" +lore = ["Kucnij i niszcz LISCIE", "zasieg niszczenia lisci", "Nie otrzymasz dropu z lisci (Zapobieganie Naduzywaniu)"] [axe.wood_miner] - name = "Gornik Drewna" - description = "Pozwala niszczyc duze ilosci drewna naraz!" - lore1 = "Kucnij i niszcz DREWNO/KLODY (Nie Deski)" - lore2 = "zasieg niszczenia drewna" - lore3 = "Dziala ze Zrzutem do Ekwipunku" - lore = ["Kucnij i niszcz DREWNO/KLODY (Nie Deski)", "zasieg niszczenia drewna", "Dziala ze Zrzutem do Ekwipunku"] +name = "Gornik Drewna" +description = "Pozwala niszczyc duze ilosci drewna naraz!" +lore1 = "Kucnij i niszcz DREWNO/KLODY (Nie Deski)" +lore2 = "zasieg niszczenia drewna" +lore3 = "Dziala ze Zrzutem do Ekwipunku" +lore = ["Kucnij i niszcz DREWNO/KLODY (Nie Deski)", "zasieg niszczenia drewna", "Dziala ze Zrzutem do Ekwipunku"] # brewing [brewing] [brewing.lingering] - name = "Dlugotrwaly Napar" - description = "Uwarzone mikstury trwaja dluzej!" - lore1 = "Czas Trwania" - lore2 = "Czas Trwania" - lore = ["Czas Trwania", "Czas Trwania"] +name = "Dlugotrwaly Napar" +description = "Uwarzone mikstury trwaja dluzej!" +lore1 = "Czas Trwania" +lore2 = "Czas Trwania" +lore = ["Czas Trwania", "Czas Trwania"] [brewing.super_heated] - name = "Super Podgrzany Napar" - description = "Stojaki alchemiczne dzialaja szybciej im sa goretsze." - lore1 = "Za Dotykajacy Blok Ognia" - lore2 = "Za Dotykajacy Blok Lawy" - lore = ["Za Dotykajacy Blok Ognia", "Za Dotykajacy Blok Lawy"] +name = "Super Podgrzany Napar" +description = "Stojaki alchemiczne dzialaja szybciej im sa goretsze." +lore1 = "Za Dotykajacy Blok Ognia" +lore2 = "Za Dotykajacy Blok Lawy" +lore = ["Za Dotykajacy Blok Ognia", "Za Dotykajacy Blok Lawy"] [brewing.darkness] - name = "Ciemnosc w Butelce" - description = "Nie wiem po co ci to, ale prosze bardzo!" - lore1 = "Mikstura Noktowizji + Czarny Beton = Mikstura Ciemnosci (30 sekund)" - lore2 = "Nalezy zauwazyc, ze uniemozliwia to sprintowanie!" - lore = ["Mikstura Noktowizji + Czarny Beton = Mikstura Ciemnosci (30 sekund)", "Nalezy zauwazyc, ze uniemozliwia to sprintowanie!"] +name = "Ciemnosc w Butelce" +description = "Nie wiem po co ci to, ale prosze bardzo!" +lore1 = "Mikstura Noktowizji + Czarny Beton = Mikstura Ciemnosci (30 sekund)" +lore2 = "Nalezy zauwazyc, ze uniemozliwia to sprintowanie!" +lore = ["Mikstura Noktowizji + Czarny Beton = Mikstura Ciemnosci (30 sekund)", "Nalezy zauwazyc, ze uniemozliwia to sprintowanie!"] [brewing.haste] - name = "Pospiech w Butelce" - description = "Kiedy Wydajnosc to za malo" - lore1 = "Mikstura Szybkosci + Odlamek Ametystu = Mikstura Pospiechu (60 sekund)" - lore2 = "Mikstura Szybkosci + Blok Ametystu = Mikstura Pospiechu-2 (30 sekund)" - lore = ["Mikstura Szybkosci + Odlamek Ametystu = Mikstura Pospiechu (60 sekund)", "Mikstura Szybkosci + Blok Ametystu = Mikstura Pospiechu-2 (30 sekund)"] +name = "Pospiech w Butelce" +description = "Kiedy Wydajnosc to za malo" +lore1 = "Mikstura Szybkosci + Odlamek Ametystu = Mikstura Pospiechu (60 sekund)" +lore2 = "Mikstura Szybkosci + Blok Ametystu = Mikstura Pospiechu-2 (30 sekund)" +lore = ["Mikstura Szybkosci + Odlamek Ametystu = Mikstura Pospiechu (60 sekund)", "Mikstura Szybkosci + Blok Ametystu = Mikstura Pospiechu-2 (30 sekund)"] [brewing.absorption] - name = "Absorpcja w Butelce" - description = "Wzmocnij cialo!" - lore1 = "Natychmiastowe Leczenie + Kwarc = Mikstura Absorpcji (60 sekund)" - lore2 = "Natychmiastowe Leczenie + Blok Kwarcu = Mikstura Absorpcji-2 (30 sekund)" - lore = ["Natychmiastowe Leczenie + Kwarc = Mikstura Absorpcji (60 sekund)", "Natychmiastowe Leczenie + Blok Kwarcu = Mikstura Absorpcji-2 (30 sekund)"] +name = "Absorpcja w Butelce" +description = "Wzmocnij cialo!" +lore1 = "Natychmiastowe Leczenie + Kwarc = Mikstura Absorpcji (60 sekund)" +lore2 = "Natychmiastowe Leczenie + Blok Kwarcu = Mikstura Absorpcji-2 (30 sekund)" +lore = ["Natychmiastowe Leczenie + Kwarc = Mikstura Absorpcji (60 sekund)", "Natychmiastowe Leczenie + Blok Kwarcu = Mikstura Absorpcji-2 (30 sekund)"] [brewing.fatigue] - name = "Zmeczenie w Butelce" - description = "Oslab cialo!" - lore1 = "Mikstura Slabosci + Kula Sluzu = Mikstura Zmeczenia (30 sekund)" - lore2 = "Mikstura Slabosci + Blok Sluzu = Mikstura Zmeczenia-2 (15 sekund)" - lore = ["Mikstura Slabosci + Kula Sluzu = Mikstura Zmeczenia (30 sekund)", "Mikstura Slabosci + Blok Sluzu = Mikstura Zmeczenia-2 (15 sekund)"] +name = "Zmeczenie w Butelce" +description = "Oslab cialo!" +lore1 = "Mikstura Slabosci + Kula Sluzu = Mikstura Zmeczenia (30 sekund)" +lore2 = "Mikstura Slabosci + Blok Sluzu = Mikstura Zmeczenia-2 (15 sekund)" +lore = ["Mikstura Slabosci + Kula Sluzu = Mikstura Zmeczenia (30 sekund)", "Mikstura Slabosci + Blok Sluzu = Mikstura Zmeczenia-2 (15 sekund)"] [brewing.hunger] - name = "Glod w Butelce" - description = "Nakarm Nienasyconego!" - lore1 = "Niezreczna Mikstura + Zgnile Mieso = Mikstura Glodu (30 sekund)" - lore2 = "Mikstura Slabosci + Zgnile Mieso = Mikstura Glodu-3 (15 sekund)" - lore = ["Niezreczna Mikstura + Zgnile Mieso = Mikstura Glodu (30 sekund)", "Mikstura Slabosci + Zgnile Mieso = Mikstura Glodu-3 (15 sekund)"] +name = "Glod w Butelce" +description = "Nakarm Nienasyconego!" +lore1 = "Niezreczna Mikstura + Zgnile Mieso = Mikstura Glodu (30 sekund)" +lore2 = "Mikstura Slabosci + Zgnile Mieso = Mikstura Glodu-3 (15 sekund)" +lore = ["Niezreczna Mikstura + Zgnile Mieso = Mikstura Glodu (30 sekund)", "Mikstura Slabosci + Zgnile Mieso = Mikstura Glodu-3 (15 sekund)"] [brewing.nausea] - name = "Mdlosci w Butelce" - description = "Robi mi sie od ciebie niedobrze!" - lore1 = "Niezreczna Mikstura + Brazowy Grzyb = Mikstura Mdlosci (16 sekund)" - lore2 = "Niezreczna Mikstura + Szkarlatny Grzyb = Mikstura Mdlosci-2 (8 sekund)" - lore = ["Niezreczna Mikstura + Brazowy Grzyb = Mikstura Mdlosci (16 sekund)", "Niezreczna Mikstura + Szkarlatny Grzyb = Mikstura Mdlosci-2 (8 sekund)"] +name = "Mdlosci w Butelce" +description = "Robi mi sie od ciebie niedobrze!" +lore1 = "Niezreczna Mikstura + Brazowy Grzyb = Mikstura Mdlosci (16 sekund)" +lore2 = "Niezreczna Mikstura + Szkarlatny Grzyb = Mikstura Mdlosci-2 (8 sekund)" +lore = ["Niezreczna Mikstura + Brazowy Grzyb = Mikstura Mdlosci (16 sekund)", "Niezreczna Mikstura + Szkarlatny Grzyb = Mikstura Mdlosci-2 (8 sekund)"] [brewing.blindness] - name = "Slepota w Butelce" - description = "Jestes okropna osoba..." - lore1 = "Niezreczna Mikstura + Worek z Atramentem = Mikstura Slepoty (30 sekund)" - lore2 = "Niezreczna Mikstura + Swiecacy Worek z Atramentem = Mikstura Slepoty-2 (15 sekund)" - lore = ["Niezreczna Mikstura + Worek z Atramentem = Mikstura Slepoty (30 sekund)", "Niezreczna Mikstura + Swiecacy Worek z Atramentem = Mikstura Slepoty-2 (15 sekund)"] +name = "Slepota w Butelce" +description = "Jestes okropna osoba..." +lore1 = "Niezreczna Mikstura + Worek z Atramentem = Mikstura Slepoty (30 sekund)" +lore2 = "Niezreczna Mikstura + Swiecacy Worek z Atramentem = Mikstura Slepoty-2 (15 sekund)" +lore = ["Niezreczna Mikstura + Worek z Atramentem = Mikstura Slepoty (30 sekund)", "Niezreczna Mikstura + Swiecacy Worek z Atramentem = Mikstura Slepoty-2 (15 sekund)"] [brewing.resistance] - name = "Odpornosc w Butelce" - description = "Fortyfikacja w najlepszym wydaniu!" - lore1 = "Niezreczna Mikstura + Sztabka Zelaza = Mikstura Odpornosci (60 sekund)" - lore2 = "Niezreczna Mikstura + Blok Zelaza = Mikstura Odpornosci-2 (30 sekund)" - lore = ["Niezreczna Mikstura + Sztabka Zelaza = Mikstura Odpornosci (60 sekund)", "Niezreczna Mikstura + Blok Zelaza = Mikstura Odpornosci-2 (30 sekund)"] +name = "Odpornosc w Butelce" +description = "Fortyfikacja w najlepszym wydaniu!" +lore1 = "Niezreczna Mikstura + Sztabka Zelaza = Mikstura Odpornosci (60 sekund)" +lore2 = "Niezreczna Mikstura + Blok Zelaza = Mikstura Odpornosci-2 (30 sekund)" +lore = ["Niezreczna Mikstura + Sztabka Zelaza = Mikstura Odpornosci (60 sekund)", "Niezreczna Mikstura + Blok Zelaza = Mikstura Odpornosci-2 (30 sekund)"] [brewing.health_boost] - name = "Zycie w Butelce" - description = "Kiedy maksymalne zdrowie to za malo..." - lore1 = "Mikstura Natychmiastowego Leczenia + Zlote Jablko = Mikstura Wzmocnienia Zdrowia (120 sekund)" - lore2 = "Mikstura Natychmiastowego Leczenia + Zaczarowane Zlote Jablko = Mikstura Wzmocnienia Zdrowia-2 (120 sekund)" - lore = ["Mikstura Natychmiastowego Leczenia + Zlote Jablko = Mikstura Wzmocnienia Zdrowia (120 sekund)", "Mikstura Natychmiastowego Leczenia + Zaczarowane Zlote Jablko = Mikstura Wzmocnienia Zdrowia-2 (120 sekund)"] +name = "Zycie w Butelce" +description = "Kiedy maksymalne zdrowie to za malo..." +lore1 = "Mikstura Natychmiastowego Leczenia + Zlote Jablko = Mikstura Wzmocnienia Zdrowia (120 sekund)" +lore2 = "Mikstura Natychmiastowego Leczenia + Zaczarowane Zlote Jablko = Mikstura Wzmocnienia Zdrowia-2 (120 sekund)" +lore = ["Mikstura Natychmiastowego Leczenia + Zlote Jablko = Mikstura Wzmocnienia Zdrowia (120 sekund)", "Mikstura Natychmiastowego Leczenia + Zaczarowane Zlote Jablko = Mikstura Wzmocnienia Zdrowia-2 (120 sekund)"] [brewing.decay] - name = "Rozklad w Butelce" - description = "Kto by pomyslal, ze Detrytus bedzie tak przydatny?" - lore1 = "Mikstura Slabosci + Trujacy Ziemniak = Mikstura Wiecenia (16 sekund)" - lore2 = "Mikstura Slabosci + Szkarlatne Korzenie = Mikstura Wiecenia-2 (8 sekund)" - lore = ["Mikstura Slabosci + Trujacy Ziemniak = Mikstura Wiecenia (16 sekund)", "Mikstura Slabosci + Szkarlatne Korzenie = Mikstura Wiecenia-2 (8 sekund)"] +name = "Rozklad w Butelce" +description = "Kto by pomyslal, ze Detrytus bedzie tak przydatny?" +lore1 = "Mikstura Slabosci + Trujacy Ziemniak = Mikstura Wiecenia (16 sekund)" +lore2 = "Mikstura Slabosci + Szkarlatne Korzenie = Mikstura Wiecenia-2 (8 sekund)" +lore = ["Mikstura Slabosci + Trujacy Ziemniak = Mikstura Wiecenia (16 sekund)", "Mikstura Slabosci + Szkarlatne Korzenie = Mikstura Wiecenia-2 (8 sekund)"] [brewing.saturation] - name = "Nasycenie w Butelce" - description = "Wiesz co... nawet nie jestem glodny..." - lore1 = "Mikstura Regeneracji + Pieczony Ziemniak = Mikstura Nasycenia" - lore2 = "Mikstura Regeneracji + Bela Siana = Mikstura Nasycenia-2" - lore = ["Mikstura Regeneracji + Pieczony Ziemniak = Mikstura Nasycenia", "Mikstura Regeneracji + Bela Siana = Mikstura Nasycenia-2"] +name = "Nasycenie w Butelce" +description = "Wiesz co... nawet nie jestem glodny..." +lore1 = "Mikstura Regeneracji + Pieczony Ziemniak = Mikstura Nasycenia" +lore2 = "Mikstura Regeneracji + Bela Siana = Mikstura Nasycenia-2" +lore = ["Mikstura Regeneracji + Pieczony Ziemniak = Mikstura Nasycenia", "Mikstura Regeneracji + Bela Siana = Mikstura Nasycenia-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Lancuchy Mefistofelesa" - description = "Pozwala tworzyc zbroje kolczuga" - lore1 = "Przepis jest taki sam jak na kazda inna zbroje, ale z brylek zelaza" - lore = ["Przepis jest taki sam jak na kazda inna zbroje, ale z brylek zelaza"] +name = "Lancuchy Mefistofelesa" +description = "Pozwala tworzyc zbroje kolczuga" +lore1 = "Przepis jest taki sam jak na kazda inna zbroje, ale z brylek zelaza" +lore = ["Przepis jest taki sam jak na kazda inna zbroje, ale z brylek zelaza"] [blocking.horse_armorer] - name = "Wytwarzalna Zbroja Konia" - description = "Pozwala tworzyc zbroje konia" - lore1 = "Otocz siodlo materialem, ktorego chcesz uzyc do wytworzenia zbroi" - lore = ["Otocz siodlo materialem, ktorego chcesz uzyc do wytworzenia zbroi"] +name = "Wytwarzalna Zbroja Konia" +description = "Pozwala tworzyc zbroje konia" +lore1 = "Otocz siodlo materialem, ktorego chcesz uzyc do wytworzenia zbroi" +lore = ["Otocz siodlo materialem, ktorego chcesz uzyc do wytworzenia zbroi"] [blocking.saddle_crafter] - name = "Wytwarzalne Siodlo" - description = "Wytworz siodlo ze skory" - lore1 = "Przepis: 5 Skora:" - lore = ["Przepis: 5 Skora:"] +name = "Wytwarzalne Siodlo" +description = "Wytworz siodlo ze skory" +lore1 = "Przepis: 5 Skora:" +lore = ["Przepis: 5 Skora:"] [blocking.multi_armor] - name = "Multi-Pancerz" - description = "Wiaz Elytry ze zbroja" - lore1 = "To niesamowita umiejetnosc do podrozowania." - lore2 = "Dynamicznie lacz i zmieniaj Zbroje/Elytry w locie!" - lore3 = "Aby polaczyc, kliknij z Shiftem na przedmiot nad innym w ekwipunku." - lore4 = "Aby rozdzielic zbroje, upusc przedmiot z Shiftem, a rozlozy sie." - lore5 = "Jesli twoj Multi-Pancerz zostanie zniszczony, stracisz wszystkie przedmioty w nim." - lore6 = "Nie potrzebuje zbroi, to mnie rozczarowuje..." - lore = ["To niesamowita umiejetnosc do podrozowania.", "Dynamicznie lacz i zmieniaj Zbroje/Elytry w locie!", "Aby polaczyc, kliknij z Shiftem na przedmiot nad innym w ekwipunku.", "Aby rozdzielic zbroje, upusc przedmiot z Shiftem, a rozlozy sie.", "Jesli twoj Multi-Pancerz zostanie zniszczony, stracisz wszystkie przedmioty w nim.", "Nie potrzebuje zbroi, to mnie rozczarowuje..."] +name = "Multi-Pancerz" +description = "Wiaz Elytry ze zbroja" +lore1 = "To niesamowita umiejetnosc do podrozowania." +lore2 = "Dynamicznie lacz i zmieniaj Zbroje/Elytry w locie!" +lore3 = "Aby polaczyc, kliknij z Shiftem na przedmiot nad innym w ekwipunku." +lore4 = "Aby rozdzielic zbroje, upusc przedmiot z Shiftem, a rozlozy sie." +lore5 = "Jesli twoj Multi-Pancerz zostanie zniszczony, stracisz wszystkie przedmioty w nim." +lore6 = "Nie potrzebuje zbroi, to mnie rozczarowuje..." +lore = ["To niesamowita umiejetnosc do podrozowania.", "Dynamicznie lacz i zmieniaj Zbroje/Elytry w locie!", "Aby polaczyc, kliknij z Shiftem na przedmiot nad innym w ekwipunku.", "Aby rozdzielic zbroje, upusc przedmiot z Shiftem, a rozlozy sie.", "Jesli twoj Multi-Pancerz zostanie zniszczony, stracisz wszystkie przedmioty w nim.", "Nie potrzebuje zbroi, to mnie rozczarowuje..."] # crafting [crafting] [crafting.deconstruction] - name = "Dekonstrukcja" - description = "Rozloz bloki i przedmioty na odzyskiwalne elementy bazowe!" - lore1 = "Upusc dowolny przedmiot na ziemie." - lore2 = "Nastepnie kucnij i kliknij prawym przyciskiem nozycami" - lore = ["Upusc dowolny przedmiot na ziemie.", "Nastepnie kucnij i kliknij prawym przyciskiem nozycami"] +name = "Dekonstrukcja" +description = "Rozloz bloki i przedmioty na odzyskiwalne elementy bazowe!" +lore1 = "Upusc dowolny przedmiot na ziemie." +lore2 = "Nastepnie kucnij i kliknij prawym przyciskiem nozycami" +lore = ["Upusc dowolny przedmiot na ziemie.", "Nastepnie kucnij i kliknij prawym przyciskiem nozycami"] [crafting.xp] - name = "PD z Rzemiosla" - description = "Zdobywaj pasywne PD podczas wytwarzania" - lore1 = "Zdobywaj PD podczas wytwarzania" - lore = ["Zdobywaj PD podczas wytwarzania"] +name = "PD z Rzemiosla" +description = "Zdobywaj pasywne PD podczas wytwarzania" +lore1 = "Zdobywaj PD podczas wytwarzania" +lore = ["Zdobywaj PD podczas wytwarzania"] [crafting.reconstruction] - name = "Rekonstrukcja Rud" - description = "Przerabiaj rudy z ich podstawowych skladnikow!" - lore1 = "8 Dropu i 1 Nosnik = 1 Ruda (bezformowe)" - lore2 = "Dropy musza byc przetopione (jesli dotyczy)" - lore3 = "Nie obejmuje: Zlomu, Kwarcu, Szmaragdow itp..." - lore4 = "Nosnik = Otoczenie. np.: Kamien, Netherrack, Lupek" - lore = ["8 Dropu i 1 Nosnik = 1 Ruda (bezformowe)", "Dropy musza byc przetopione (jesli dotyczy)", "Nie obejmuje: Zlomu, Kwarcu, Szmaragdow itp...", "Nosnik = Otoczenie. np.: Kamien, Netherrack, Lupek"] +name = "Rekonstrukcja Rud" +description = "Przerabiaj rudy z ich podstawowych skladnikow!" +lore1 = "8 Dropu i 1 Nosnik = 1 Ruda (bezformowe)" +lore2 = "Dropy musza byc przetopione (jesli dotyczy)" +lore3 = "Nie obejmuje: Zlomu, Kwarcu, Szmaragdow itp..." +lore4 = "Nosnik = Otoczenie. np.: Kamien, Netherrack, Lupek" +lore = ["8 Dropu i 1 Nosnik = 1 Ruda (bezformowe)", "Dropy musza byc przetopione (jesli dotyczy)", "Nie obejmuje: Zlomu, Kwarcu, Szmaragdow itp...", "Nosnik = Otoczenie. np.: Kamien, Netherrack, Lupek"] [crafting.leather] - name = "Wytwarzalna Skora" - description = "Wytworz Skore ze Zgnilego Miesa" - lore1 = "Po prostu wrzuc to (zgnile mieso) na ognisko!" - lore = ["Po prostu wrzuc to (zgnile mieso) na ognisko!"] +name = "Wytwarzalna Skora" +description = "Wytworz Skore ze Zgnilego Miesa" +lore1 = "Po prostu wrzuc to (zgnile mieso) na ognisko!" +lore = ["Po prostu wrzuc to (zgnile mieso) na ognisko!"] [crafting.backpacks] - name = "Plecaki Boutiliera!" - description = "Wprowadza do gry pakiet Mojang!" - lore1 = "Musisz byc w trybie Przetrwania aby tego uzywac" - lore2 = "XLX : Skora, Smycz, Skora" - lore3 = "XSX : Skora, Skrzynka Barrelowa, Skora" - lore4 = "XCX : Skora, Skrzynia, Skora" - lore = ["Musisz byc w trybie Przetrwania aby tego uzywac", "XLX : Skora, Smycz, Skora", "XSX : Skora, Skrzynka Barrelowa, Skora", "XCX : Skora, Skrzynia, Skora"] +name = "Plecaki Boutiliera!" +description = "Wprowadza do gry pakiet Mojang!" +lore1 = "Musisz byc w trybie Przetrwania aby tego uzywac" +lore2 = "XLX : Skora, Smycz, Skora" +lore3 = "XSX : Skora, Skrzynka Barrelowa, Skora" +lore4 = "XCX : Skora, Skrzynia, Skora" +lore = ["Musisz byc w trybie Przetrwania aby tego uzywac", "XLX : Skora, Smycz, Skora", "XSX : Skora, Skrzynka Barrelowa, Skora", "XCX : Skora, Skrzynia, Skora"] [crafting.stations] - name = "Przenosne Stoly!" - description = "Uzyj stolu prosto z dloni!" - lore2 = "WSZELKIE PRZEDMIOTY ZAPOMNIANE W STOLE PO ZAMKNIECIU SA STRACONE NA ZAWSZE!" - lore3 = "Prawidlowe stoly: Kowadlo, Stol Rzemicslniczy, Szlifierka, Kartografia, Kamieniarka, Krosno" - lore = ["WSZELKIE PRZEDMIOTY ZAPOMNIANE W STOLE PO ZAMKNIECIU SA STRACONE NA ZAWSZE!", "Prawidlowe stoly: Kowadlo, Stol Rzemicslniczy, Szlifierka, Kartografia, Kamieniarka, Krosno"] +name = "Przenosne Stoly!" +description = "Uzyj stolu prosto z dloni!" +lore2 = "WSZELKIE PRZEDMIOTY ZAPOMNIANE W STOLE PO ZAMKNIECIU SA STRACONE NA ZAWSZE!" +lore3 = "Prawidlowe stoly: Kowadlo, Stol Rzemicslniczy, Szlifierka, Kartografia, Kamieniarka, Krosno" +lore = ["WSZELKIE PRZEDMIOTY ZAPOMNIANE W STOLE PO ZAMKNIECIU SA STRACONE NA ZAWSZE!", "Prawidlowe stoly: Kowadlo, Stol Rzemicslniczy, Szlifierka, Kartografia, Kamieniarka, Krosno"] [crafting.skulls] - name = "Wytwarzalne Czaszki!" - description = "Uzywajac materialow mozesz tworzyc czaszki mobow!" - lore1 = "Otocz Blok Kosci nastepujacymi aby uzyskac czaszke:" - lore2 = "Zombie: Zgnile Mieso" - lore3 = "Szkielet: Kosc" - lore4 = "Creeper: Proch" - lore5 = "Wither: Cegla Netheru" - lore6 = "Smok: Oddech Smoka" - lore = ["Otocz Blok Kosci nastepujacymi aby uzyskac czaszke:", "Zombie: Zgnile Mieso", "Szkielet: Kosc", "Creeper: Proch", "Wither: Cegla Netheru", "Smok: Oddech Smoka"] +name = "Wytwarzalne Czaszki!" +description = "Uzywajac materialow mozesz tworzyc czaszki mobow!" +lore1 = "Otocz Blok Kosci nastepujacymi aby uzyskac czaszke:" +lore2 = "Zombie: Zgnile Mieso" +lore3 = "Szkielet: Kosc" +lore4 = "Creeper: Proch" +lore5 = "Wither: Cegla Netheru" +lore6 = "Smok: Oddech Smoka" +lore = ["Otocz Blok Kosci nastepujacymi aby uzyskac czaszke:", "Zombie: Zgnile Mieso", "Szkielet: Kosc", "Creeper: Proch", "Wither: Cegla Netheru", "Smok: Oddech Smoka"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Czas w Butelce" - description = "Nos butelke temporalna, ktora gromadzi czas i wydawaj go na przyspieszanie blokow czasowych, upraw i dorastajaych istot, takich jak mlode zwierzeta. Przepis (Bezformowy): Mikstura Szybkosci + Zegar + Szklana Butelka." - lore1 = "Zgromadzone sekundy ladowane co tick" - lore2 = "Przyspieszenie czasu na zgromadzona sekunde" - lore3 = "Przepis (Bezformowy): Mikstura Szybkosci + Zegar + Szklana Butelka" - lore = ["Zgromadzone sekundy ladowane co tick", "Przyspieszenie czasu na zgromadzona sekunde", "Przepis (Bezformowy): Mikstura Szybkosci + Zegar + Szklana Butelka"] +name = "Czas w Butelce" +description = "Nos butelke temporalna, ktora gromadzi czas i wydawaj go na przyspieszanie blokow czasowych, upraw i dorastajaych istot, takich jak mlode zwierzeta. Przepis (Bezformowy): Mikstura Szybkosci + Zegar + Szklana Butelka." +lore1 = "Zgromadzone sekundy ladowane co tick" +lore2 = "Przyspieszenie czasu na zgromadzona sekunde" +lore3 = "Przepis (Bezformowy): Mikstura Szybkosci + Zegar + Szklana Butelka" +lore = ["Zgromadzone sekundy ladowane co tick", "Przyspieszenie czasu na zgromadzona sekunde", "Przepis (Bezformowy): Mikstura Szybkosci + Zegar + Szklana Butelka"] [chronos.aberrant_touch] - name = "Aberracyjny Dotyk" - description = "Ataki w zwarciu nakladaja kumulujace spowolnienie kosztem glodu, z ograniczeniami PvP, i unieruchamiaja cele przy 5 kumulacjach." - lore1 = "Ataki w zwarciu nakladaja kumulujace spowolnienie" - lore2 = "Limit czasu trwania spowolnienia PvE" - lore3 = "Limit poziomu spowolnienia PvP" - lore = ["Ataki w zwarciu nakladaja kumulujace spowolnienie", "Limit czasu trwania spowolnienia PvE", "Limit poziomu spowolnienia PvP"] +name = "Aberracyjny Dotyk" +description = "Ataki w zwarciu nakladaja kumulujace spowolnienie kosztem glodu, z ograniczeniami PvP, i unieruchamiaja cele przy 5 kumulacjach." +lore1 = "Ataki w zwarciu nakladaja kumulujace spowolnienie" +lore2 = "Limit czasu trwania spowolnienia PvE" +lore3 = "Limit poziomu spowolnienia PvP" +lore = ["Ataki w zwarciu nakladaja kumulujace spowolnienie", "Limit czasu trwania spowolnienia PvE", "Limit poziomu spowolnienia PvP"] [chronos.instant_recall] - name = "Natychmiastowe Przywolanie" - description = "Kliknij lewym lub prawym przyciskiem z zegarem w rece aby cofnac sie do niedawnego stanu ze przywroconym zdrowiem i glodem." - lore1 = "Czas cofniecia" - lore2 = "Czas odnowienia" - lore3 = "Brak cofania ekwipunku" - lore = ["Czas cofniecia", "Czas odnowienia", "Brak cofania ekwipunku"] +name = "Natychmiastowe Przywolanie" +description = "Kliknij lewym lub prawym przyciskiem z zegarem w rece aby cofnac sie do niedawnego stanu ze przywroconym zdrowiem i glodem." +lore1 = "Czas cofniecia" +lore2 = "Czas odnowienia" +lore3 = "Brak cofania ekwipunku" +lore = ["Czas cofniecia", "Czas odnowienia", "Brak cofania ekwipunku"] [chronos.time_bomb] - name = "Bomba Czasowa" - description = "Rzuc wytworzona bombe chrono, ktora tworzy pole temporalne, spowalnia istoty i zamraza pociski." - lore1 = "Zasieg pola temporalnego" - lore2 = "Czas trwania pola temporalnego" - lore3 = "Czas odnowienia bomby" - lore4 = "Przepis (Bezformowy): Zegar + Sniezka + Diament + Piasek" - lore = ["Zasieg pola temporalnego", "Czas trwania pola temporalnego", "Czas odnowienia bomby", "Przepis (Bezformowy): Zegar + Sniezka + Diament + Piasek"] +name = "Bomba Czasowa" +description = "Rzuc wytworzona bombe chrono, ktora tworzy pole temporalne, spowalnia istoty i zamraza pociski." +lore1 = "Zasieg pola temporalnego" +lore2 = "Czas trwania pola temporalnego" +lore3 = "Czas odnowienia bomby" +lore4 = "Przepis (Bezformowy): Zegar + Sniezka + Diament + Piasek" +lore = ["Zasieg pola temporalnego", "Czas trwania pola temporalnego", "Czas odnowienia bomby", "Przepis (Bezformowy): Zegar + Sniezka + Diament + Piasek"] # discovery [discovery] [discovery.armor] - name = "Pancerz Swiata" - description = "Pasywny pancerz zalezny od twardosci poblizskich blokow." - lore1 = "Pasywny Pancerz" - lore2 = "Na podstawie twardosci poblizskich blokow" - lore3 = "Sila Pancerza:" - lore = ["Pasywny Pancerz", "Na podstawie twardosci poblizskich blokow", "Sila Pancerza:"] +name = "Pancerz Swiata" +description = "Pasywny pancerz zalezny od twardosci poblizskich blokow." +lore1 = "Pasywny Pancerz" +lore2 = "Na podstawie twardosci poblizskich blokow" +lore3 = "Sila Pancerza:" +lore = ["Pasywny Pancerz", "Na podstawie twardosci poblizskich blokow", "Sila Pancerza:"] [discovery.unity] - name = "Eksperymentalna Jednosc" - description = "Zbieranie Kul Doswiadczenia dodaje PD do losowych umiejetnosci." - lore1 = "PD " - lore2 = "Za Kule" - lore = ["PD ", "Za Kule"] +name = "Eksperymentalna Jednosc" +description = "Zbieranie Kul Doswiadczenia dodaje PD do losowych umiejetnosci." +lore1 = "PD " +lore2 = "Za Kule" +lore = ["PD ", "Za Kule"] [discovery.resist] - name = "Eksperymentalna Odpornosc" - description = "Zuzyj doswiadczenie aby zlagodzic obrazenia, ale tylko gdy trafienie zrzuciloby cie ponizej 5 serc lub by cie zabilo." - lore0 = "Aktywuje sie tylko przy krytycznym zdrowiu (<= 5 serc) raz na 15 sekund" - lore1 = " Zmniejszone Obrazenia" - lore2 = "wyczerpane doswiadczenie" - lore = ["Aktywuje sie tylko przy krytycznym zdrowiu (<= 5 serc) raz na 15 sekund", " Zmniejszone Obrazenia", "wyczerpane doswiadczenie"] +name = "Eksperymentalna Odpornosc" +description = "Zuzyj doswiadczenie aby zlagodzic obrazenia, ale tylko gdy trafienie zrzuciloby cie ponizej 5 serc lub by cie zabilo." +lore0 = "Aktywuje sie tylko przy krytycznym zdrowiu (<= 5 serc) raz na 15 sekund" +lore1 = " Zmniejszone Obrazenia" +lore2 = "wyczerpane doswiadczenie" +lore = ["Aktywuje sie tylko przy krytycznym zdrowiu (<= 5 serc) raz na 15 sekund", " Zmniejszone Obrazenia", "wyczerpane doswiadczenie"] [discovery.villager] - name = "Atrakcja Osadnikow" - description = "Pozwala na uzyskanie lepszych transakcji z mieszkancami wioski!" - lore1 = "Zuzywa PD za kazda interakcje z osadnikami" - lore2 = "Szansa na zuzycie PD i ulepszenie handlu za interakcje" - lore3 = "wymagane zuzycie PD za interakcje" - lore = ["Zuzywa PD za kazda interakcje z osadnikami", "Szansa na zuzycie PD i ulepszenie handlu za interakcje", "wymagane zuzycie PD za interakcje"] +name = "Atrakcja Osadnikow" +description = "Pozwala na uzyskanie lepszych transakcji z mieszkancami wioski!" +lore1 = "Zuzywa PD za kazda interakcje z osadnikami" +lore2 = "Szansa na zuzycie PD i ulepszenie handlu za interakcje" +lore3 = "wymagane zuzycie PD za interakcje" +lore = ["Zuzywa PD za kazda interakcje z osadnikami", "Szansa na zuzycie PD i ulepszenie handlu za interakcje", "wymagane zuzycie PD za interakcje"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Zwrot Lazurytu" - description = "Kosztem 1 dodatkowego poziomu PD, masz szanse na darmowy lazuryt w zamian" - lore1 = "Za kazdy poziom zwieksza koszt zaklecia o 1, ale moze zwrocic do 3 Lazurytu" - lore = ["Za kazdy poziom zwieksza koszt zaklecia o 1, ale moze zwrocic do 3 Lazurytu"] +name = "Zwrot Lazurytu" +description = "Kosztem 1 dodatkowego poziomu PD, masz szanse na darmowy lazuryt w zamian" +lore1 = "Za kazdy poziom zwieksza koszt zaklecia o 1, ale moze zwrocic do 3 Lazurytu" +lore = ["Za kazdy poziom zwieksza koszt zaklecia o 1, ale moze zwrocic do 3 Lazurytu"] [enchanting.quick_enchant] - name = "Szybkie Zaklecie Kliknieciem" - description = "Zaklinaj przedmioty klikajac ksiazkami zaklec bezposrednio na nich." - lore1 = "Maks. Polaczone Poziomy" - lore2 = "Nie mozna zaklinac przedmiotu z wiecej niz " - lore3 = "mocy" - lore = ["Maks. Polaczone Poziomy", "Nie mozna zaklinac przedmiotu z wiecej niz ", "mocy"] +name = "Szybkie Zaklecie Kliknieciem" +description = "Zaklinaj przedmioty klikajac ksiazkami zaklec bezposrednio na nich." +lore1 = "Maks. Polaczone Poziomy" +lore2 = "Nie mozna zaklinac przedmiotu z wiecej niz " +lore3 = "mocy" +lore = ["Maks. Polaczone Poziomy", "Nie mozna zaklinac przedmiotu z wiecej niz ", "mocy"] [enchanting.return] - name = "Zwrot PD" - description = "PD z zaklec jest ci zwracane gdy zaklinasz przedmiot." - lore1 = "Wydane doswiadczenie ma szanse zostac zwrocone gdy zaklinasz przedmiot" - lore2 = "Doswiadczenie za Zaklecie" - lore = ["Wydane doswiadczenie ma szanse zostac zwrocone gdy zaklinasz przedmiot", "Doswiadczenie za Zaklecie"] +name = "Zwrot PD" +description = "PD z zaklec jest ci zwracane gdy zaklinasz przedmiot." +lore1 = "Wydane doswiadczenie ma szanse zostac zwrocone gdy zaklinasz przedmiot" +lore2 = "Doswiadczenie za Zaklecie" +lore = ["Wydane doswiadczenie ma szanse zostac zwrocone gdy zaklinasz przedmiot", "Doswiadczenie za Zaklecie"] # excavation [excavation] [excavation.haste] - name = "Pospieszny Kopacz" - description = "Przyspiesza kopanie dzieki POSPIECOWI!" - lore1 = "Zyskaj pospiech podczas kopania" - lore2 = "x Poziomow pospiechu gdy zaczniesz niszczyc DOWOLNY blok." - lore = ["Zyskaj pospiech podczas kopania", "x Poziomow pospiechu gdy zaczniesz niszczyc DOWOLNY blok."] +name = "Pospieszny Kopacz" +description = "Przyspiesza kopanie dzieki POSPIECOWI!" +lore1 = "Zyskaj pospiech podczas kopania" +lore2 = "x Poziomow pospiechu gdy zaczniesz niszczyc DOWOLNY blok." +lore = ["Zyskaj pospiech podczas kopania", "x Poziomow pospiechu gdy zaczniesz niszczyc DOWOLNY blok."] [excavation.spelunker] - name = "Super-Widzacy Grotolaz!" - description = "Zobacz rudy na wlasne oczy, ale przez ziemie!" - lore1 = "Ruda w drugiej rece, Swiecace Jagody w glownej rece i Kucnij!" - lore2 = "Zasieg Blokow: " - lore3 = "Zuzywa Swiecace Jagody przy uzyciu" - lore = ["Ruda w drugiej rece, Swiecace Jagody w glownej rece i Kucnij!", "Zasieg Blokow: ", "Zuzywa Swiecace Jagody przy uzyciu"] +name = "Super-Widzacy Grotolaz!" +description = "Zobacz rudy na wlasne oczy, ale przez ziemie!" +lore1 = "Ruda w drugiej rece, Swiecace Jagody w glownej rece i Kucnij!" +lore2 = "Zasieg Blokow: " +lore3 = "Zuzywa Swiecace Jagody przy uzyciu" +lore = ["Ruda w drugiej rece, Swiecace Jagody w glownej rece i Kucnij!", "Zasieg Blokow: ", "Zuzywa Swiecace Jagody przy uzyciu"] [excavation.drop_to_inventory] - name = "Lopata Zrzut-Do-Ekwipunku" +name = "Lopata Zrzut-Do-Ekwipunku" [excavation.omni_tool] - name = "OMNI - N.A.R.Z." - description = "Przesadnie zaprojektowany bogaty Multitool Tackle'a" - lore1 = "Prawdopodobnie najpotezniejszy z wielu, pozwala ci" - lore2 = "dynamicznie laczyc i zmieniac narzedzia w locie, wedlug potrzeb." - lore3 = "Aby polaczyc, kliknij z Shiftem na przedmiot nad innym w ekwipunku." - lore4 = "Aby rozdzielic narzedzia, upusc przedmiot z Shiftem, a zostanie zdemontowany." - lore5 = "Nie mozesz zniszczyc narzedzi w tym multitoolu ale nie mozesz uzywac zepsutych narzedzi" - lore6 = "laczna ilosc przedmiotow do polaczenia." - lore7 = "Mozesz uzyc pieciu albo szesciu narzedzi, albo tylko jednego!" - lore = ["Prawdopodobnie najpotezniejszy z wielu, pozwala ci", "dynamicznie laczyc i zmieniac narzedzia w locie, wedlug potrzeb.", "Aby polaczyc, kliknij z Shiftem na przedmiot nad innym w ekwipunku.", "Aby rozdzielic narzedzia, upusc przedmiot z Shiftem, a zostanie zdemontowany.", "Nie mozesz zniszczyc narzedzi w tym multitoolu ale nie mozesz uzywac zepsutych narzedzi", "laczna ilosc przedmiotow do polaczenia.", "Mozesz uzyc pieciu albo szesciu narzedzi, albo tylko jednego!"] +name = "OMNI - N.A.R.Z." +description = "Przesadnie zaprojektowany bogaty Multitool Tackle'a" +lore1 = "Prawdopodobnie najpotezniejszy z wielu, pozwala ci" +lore2 = "dynamicznie laczyc i zmieniac narzedzia w locie, wedlug potrzeb." +lore3 = "Aby polaczyc, kliknij z Shiftem na przedmiot nad innym w ekwipunku." +lore4 = "Aby rozdzielic narzedzia, upusc przedmiot z Shiftem, a zostanie zdemontowany." +lore5 = "Nie mozesz zniszczyc narzedzi w tym multitoolu ale nie mozesz uzywac zepsutych narzedzi" +lore6 = "laczna ilosc przedmiotow do polaczenia." +lore7 = "Mozesz uzyc pieciu albo szesciu narzedzi, albo tylko jednego!" +lore = ["Prawdopodobnie najpotezniejszy z wielu, pozwala ci", "dynamicznie laczyc i zmieniac narzedzia w locie, wedlug potrzeb.", "Aby polaczyc, kliknij z Shiftem na przedmiot nad innym w ekwipunku.", "Aby rozdzielic narzedzia, upusc przedmiot z Shiftem, a zostanie zdemontowany.", "Nie mozesz zniszczyc narzedzi w tym multitoolu ale nie mozesz uzywac zepsutych narzedzi", "laczna ilosc przedmiotow do polaczenia.", "Mozesz uzyc pieciu albo szesciu narzedzi, albo tylko jednego!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Aura Wzrostu" - description = "Rozwijaj nature wokol siebie w aurze" - lore1 = "Zasieg Blokow" - lore2 = "Sila Aury Wzrostu" - lore3 = "Koszt Jedzenia" - lore = ["Zasieg Blokow", "Sila Aury Wzrostu", "Koszt Jedzenia"] +name = "Aura Wzrostu" +description = "Rozwijaj nature wokol siebie w aurze" +lore1 = "Zasieg Blokow" +lore2 = "Sila Aury Wzrostu" +lore3 = "Koszt Jedzenia" +lore = ["Zasieg Blokow", "Sila Aury Wzrostu", "Koszt Jedzenia"] [herbalism.hippo] - name = "Hipopotam Zielarza" - description = "Spozywanie jedzenia daje wiecej nasycenia" - lore1 = "Jedzenie) dodatkowe punkty nasycenia przy spozyciu" - lore = ["Jedzenie) dodatkowe punkty nasycenia przy spozyciu"] +name = "Hipopotam Zielarza" +description = "Spozywanie jedzenia daje wiecej nasycenia" +lore1 = "Jedzenie) dodatkowe punkty nasycenia przy spozyciu" +lore = ["Jedzenie) dodatkowe punkty nasycenia przy spozyciu"] [herbalism.myconid] - name = "Mykonid Zielarza" - description = "Daje mozliwosc wytwarzania Grzybni" - lore1 = "Dowolna Ziemia + Brazowy i Czerwony Grzyb wytworzya Grzybnie." - lore = ["Dowolna Ziemia + Brazowy i Czerwony Grzyb wytworzya Grzybnie."] +name = "Mykonid Zielarza" +description = "Daje mozliwosc wytwarzania Grzybni" +lore1 = "Dowolna Ziemia + Brazowy i Czerwony Grzyb wytworzya Grzybnie." +lore = ["Dowolna Ziemia + Brazowy i Czerwony Grzyb wytworzya Grzybnie."] [herbalism.terralid] - name = "Terralid Zielarza" - description = "Daje mozliwosc wytwarzania Blokow Trawy" - lore1 = "Trzy Nasiona nad 3 Ziemia wytworzya 3 Bloki Trawy." - lore = ["Trzy Nasiona nad 3 Ziemia wytworzya 3 Bloki Trawy."] +name = "Terralid Zielarza" +description = "Daje mozliwosc wytwarzania Blokow Trawy" +lore1 = "Trzy Nasiona nad 3 Ziemia wytworzya 3 Bloki Trawy." +lore = ["Trzy Nasiona nad 3 Ziemia wytworzya 3 Bloki Trawy."] [herbalism.cobweb] - name = "Tworca Pajeczyn" - description = "Daje mozliwosc wytwarzania Pajeczyn na Stole Rzemicslniczym" - lore1 = "Dziewiec Nitek wytworzy Pajeczyne." - lore = ["Dziewiec Nitek wytworzy Pajeczyne."] +name = "Tworca Pajeczyn" +description = "Daje mozliwosc wytwarzania Pajeczyn na Stole Rzemicslniczym" +lore1 = "Dziewiec Nitek wytworzy Pajeczyne." +lore = ["Dziewiec Nitek wytworzy Pajeczyne."] [herbalism.mushroom_blocks] - name = "Grzybiarz" - description = "Daje mozliwosc wytwarzania Blokow Grzybowych na Stole Rzemicslniczym" - lore1 = "Cztery Grzyby zrobia blok, albo blok zrobi lodyge." - lore = ["Cztery Grzyby zrobia blok, albo blok zrobi lodyge."] +name = "Grzybiarz" +description = "Daje mozliwosc wytwarzania Blokow Grzybowych na Stole Rzemicslniczym" +lore1 = "Cztery Grzyby zrobia blok, albo blok zrobi lodyge." +lore = ["Cztery Grzyby zrobia blok, albo blok zrobi lodyge."] [herbalism.drop_to_inventory] - name = "Motyka Zrzut-Do-Ekwipunku" +name = "Motyka Zrzut-Do-Ekwipunku" [herbalism.hungry_shield] - name = "Glodna Tarcza" - description = "Obrazenia odejmuja sie od glodu zanim od zdrowia." - lore1 = "Odparte przez Glod" - lore = ["Odparte przez Glod"] +name = "Glodna Tarcza" +description = "Obrazenia odejmuja sie od glodu zanim od zdrowia." +lore1 = "Odparte przez Glod" +lore = ["Odparte przez Glod"] [herbalism.luck] - name = "Szczescie Zielarza" - description = "Gdy niszczysz Trawe/Kwiaty, masz szanse na losowy przedmiot" - lore0 = "Kwiaty = Jedzenie, a Trawa = Nasiona" - lore1 = "Szansa na zdobycie przedmiotu z niszczenia Kwiatow" - lore2 = "Szansa na zdobycie przedmiotu z niszczenia Trawy" - lore = ["Kwiaty = Jedzenie, a Trawa = Nasiona", "Szansa na zdobycie przedmiotu z niszczenia Kwiatow", "Szansa na zdobycie przedmiotu z niszczenia Trawy"] +name = "Szczescie Zielarza" +description = "Gdy niszczysz Trawe/Kwiaty, masz szanse na losowy przedmiot" +lore0 = "Kwiaty = Jedzenie, a Trawa = Nasiona" +lore1 = "Szansa na zdobycie przedmiotu z niszczenia Kwiatow" +lore2 = "Szansa na zdobycie przedmiotu z niszczenia Trawy" +lore = ["Kwiaty = Jedzenie, a Trawa = Nasiona", "Szansa na zdobycie przedmiotu z niszczenia Kwiatow", "Szansa na zdobycie przedmiotu z niszczenia Trawy"] [herbalism.replant] - name = "Zbior i Przesadzanie" - description = "Kliknij prawym przyciskiem na uprawe z motyka aby zebrac i przesadzic." - lore1 = "Zasieg Przesadzania Blokow" - lore = ["Zasieg Przesadzania Blokow"] +name = "Zbior i Przesadzanie" +description = "Kliknij prawym przyciskiem na uprawe z motyka aby zebrac i przesadzic." +lore1 = "Zasieg Przesadzania Blokow" +lore = ["Zasieg Przesadzania Blokow"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenalina" - description = "Zadawaj wiecej obrazen im mniej masz zdrowia (Walka Wreczna)" - lore1 = "Maksymalne Obrazenia" - lore = ["Maksymalne Obrazenia"] +name = "Adrenalina" +description = "Zadawaj wiecej obrazen im mniej masz zdrowia (Walka Wreczna)" +lore1 = "Maksymalne Obrazenia" +lore = ["Maksymalne Obrazenia"] [hunter.penalty] - name = "" - description = "" - lore1 = "Otrzymasz kumulacje Trucizny jesli zabraknie ci glodu" - lore = ["Otrzymasz kumulacje Trucizny jesli zabraknie ci glodu"] +name = "" +description = "" +lore1 = "Otrzymasz kumulacje Trucizny jesli zabraknie ci glodu" +lore = ["Otrzymasz kumulacje Trucizny jesli zabraknie ci glodu"] [hunter.drop_to_inventory] - name = "Przedmioty Zrzut-Do-Ekwipunku" - description = "Gdy cos zabijesz / zniszczysz blok mieczem, drop teleportuje sie do twojego ekwipunku" - lore1 = "Za kazdym razem gdy przedmiot wypada z moba/bloku ktory niszczysz, trafia do twojego ekwipunku jesli to mozliwe." - lore = ["Za kazdym razem gdy przedmiot wypada z moba/bloku ktory niszczysz, trafia do twojego ekwipunku jesli to mozliwe."] +name = "Przedmioty Zrzut-Do-Ekwipunku" +description = "Gdy cos zabijesz / zniszczysz blok mieczem, drop teleportuje sie do twojego ekwipunku" +lore1 = "Za kazdym razem gdy przedmiot wypada z moba/bloku ktory niszczysz, trafia do twojego ekwipunku jesli to mozliwe." +lore = ["Za kazdym razem gdy przedmiot wypada z moba/bloku ktory niszczysz, trafia do twojego ekwipunku jesli to mozliwe."] [hunter.invisibility] - name = "Znikajacy Krok" - description = "Gdy zostaniesz trafiony zyskujesz niewidzialnosc, kosztem glodu" - lore1 = "Zyskaj pasywna niewidzialnosc po trafieniu" - lore2 = "x Kumulacje Niewidzialnosci na 3 sekundy po trafieniu" - lore3 = "x Kumulowany glod" - lore4 = "Czas trwania i mnoznik kumulacji glodu." - lore5 = "Czas trwania niewidzialnosci" - lore = ["Zyskaj pasywna niewidzialnosc po trafieniu", "x Kumulacje Niewidzialnosci na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Czas trwania niewidzialnosci"] +name = "Znikajacy Krok" +description = "Gdy zostaniesz trafiony zyskujesz niewidzialnosc, kosztem glodu" +lore1 = "Zyskaj pasywna niewidzialnosc po trafieniu" +lore2 = "x Kumulacje Niewidzialnosci na 3 sekundy po trafieniu" +lore3 = "x Kumulowany glod" +lore4 = "Czas trwania i mnoznik kumulacji glodu." +lore5 = "Czas trwania niewidzialnosci" +lore = ["Zyskaj pasywna niewidzialnosc po trafieniu", "x Kumulacje Niewidzialnosci na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Czas trwania niewidzialnosci"] [hunter.jump_boost] - name = "Wyskoki Lowcy" - description = "Gdy zostaniesz trafiony zyskujesz wzmocnienie skoku, kosztem glodu" - lore1 = "Zyskaj pasywne wzmocnienie skoku po trafieniu" - lore2 = "x Kumulacje Wzmocnienia Skoku na 3 sekundy po trafieniu" - lore3 = "x Kumulowany glod" - lore4 = "Czas trwania i mnoznik kumulacji glodu." - lore5 = "Mnoznik kumulacji Wzmocnienia Skoku, nie czas trwania." - lore = ["Zyskaj pasywne wzmocnienie skoku po trafieniu", "x Kumulacje Wzmocnienia Skoku na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji Wzmocnienia Skoku, nie czas trwania."] +name = "Wyskoki Lowcy" +description = "Gdy zostaniesz trafiony zyskujesz wzmocnienie skoku, kosztem glodu" +lore1 = "Zyskaj pasywne wzmocnienie skoku po trafieniu" +lore2 = "x Kumulacje Wzmocnienia Skoku na 3 sekundy po trafieniu" +lore3 = "x Kumulowany glod" +lore4 = "Czas trwania i mnoznik kumulacji glodu." +lore5 = "Mnoznik kumulacji Wzmocnienia Skoku, nie czas trwania." +lore = ["Zyskaj pasywne wzmocnienie skoku po trafieniu", "x Kumulacje Wzmocnienia Skoku na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji Wzmocnienia Skoku, nie czas trwania."] [hunter.luck] - name = "Szczescie Lowcy" - description = "Gdy zostaniesz trafiony zyskujesz szczescie, kosztem glodu" - lore1 = "Zyskaj pasywne szczescie po trafieniu" - lore2 = "x Kumulacje Szczescia na 3 sekundy po trafieniu" - lore3 = "x Kumulowany glod" - lore4 = "Czas trwania i mnoznik kumulacji glodu." - lore5 = "Mnoznik kumulacji szczescia, nie czas trwania." - lore = ["Zyskaj pasywne szczescie po trafieniu", "x Kumulacje Szczescia na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji szczescia, nie czas trwania."] +name = "Szczescie Lowcy" +description = "Gdy zostaniesz trafiony zyskujesz szczescie, kosztem glodu" +lore1 = "Zyskaj pasywne szczescie po trafieniu" +lore2 = "x Kumulacje Szczescia na 3 sekundy po trafieniu" +lore3 = "x Kumulowany glod" +lore4 = "Czas trwania i mnoznik kumulacji glodu." +lore5 = "Mnoznik kumulacji szczescia, nie czas trwania." +lore = ["Zyskaj pasywne szczescie po trafieniu", "x Kumulacje Szczescia na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji szczescia, nie czas trwania."] [hunter.regen] - name = "Regeneracja Lowcy" - description = "Gdy zostaniesz trafiony zyskujesz regeneracje, kosztem glodu" - lore1 = "Zyskaj pasywna regeneracje po trafieniu" - lore2 = "x Kumulacje Regeneracji na 3 sekundy po trafieniu" - lore3 = "x Kumulowany glod" - lore4 = "Czas trwania i mnoznik kumulacji glodu." - lore5 = "Mnoznik kumulacji regeneracji, nie czas trwania." - lore = ["Zyskaj pasywna regeneracje po trafieniu", "x Kumulacje Regeneracji na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji regeneracji, nie czas trwania."] +name = "Regeneracja Lowcy" +description = "Gdy zostaniesz trafiony zyskujesz regeneracje, kosztem glodu" +lore1 = "Zyskaj pasywna regeneracje po trafieniu" +lore2 = "x Kumulacje Regeneracji na 3 sekundy po trafieniu" +lore3 = "x Kumulowany glod" +lore4 = "Czas trwania i mnoznik kumulacji glodu." +lore5 = "Mnoznik kumulacji regeneracji, nie czas trwania." +lore = ["Zyskaj pasywna regeneracje po trafieniu", "x Kumulacje Regeneracji na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji regeneracji, nie czas trwania."] [hunter.resistance] - name = "Odpornosc Lowcy" - description = "Gdy zostaniesz trafiony zyskujesz odpornosc, kosztem glodu" - lore1 = "Zyskaj pasywna odpornosc po trafieniu" - lore2 = "x Kumulacje Odpornosci na 3 sekundy po trafieniu" - lore3 = "x Kumulowany glod" - lore4 = "Czas trwania i mnoznik kumulacji glodu." - lore5 = "Mnoznik kumulacji odpornosci, nie czas trwania." - lore = ["Zyskaj pasywna odpornosc po trafieniu", "x Kumulacje Odpornosci na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji odpornosci, nie czas trwania."] +name = "Odpornosc Lowcy" +description = "Gdy zostaniesz trafiony zyskujesz odpornosc, kosztem glodu" +lore1 = "Zyskaj pasywna odpornosc po trafieniu" +lore2 = "x Kumulacje Odpornosci na 3 sekundy po trafieniu" +lore3 = "x Kumulowany glod" +lore4 = "Czas trwania i mnoznik kumulacji glodu." +lore5 = "Mnoznik kumulacji odpornosci, nie czas trwania." +lore = ["Zyskaj pasywna odpornosc po trafieniu", "x Kumulacje Odpornosci na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji odpornosci, nie czas trwania."] [hunter.speed] - name = "Szybkosc Lowcy" - description = "Gdy zostaniesz trafiony zyskujesz szybkosc, kosztem glodu" - lore1 = "Zyskaj pasywna szybkosc po trafieniu" - lore2 = "x Kumulacje Szybkosci na 3 sekundy po trafieniu" - lore3 = "x Kumulowany glod" - lore4 = "Czas trwania i mnoznik kumulacji glodu." - lore5 = "Mnoznik kumulacji szybkosci, nie czas trwania." - lore = ["Zyskaj pasywna szybkosc po trafieniu", "x Kumulacje Szybkosci na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji szybkosci, nie czas trwania."] +name = "Szybkosc Lowcy" +description = "Gdy zostaniesz trafiony zyskujesz szybkosc, kosztem glodu" +lore1 = "Zyskaj pasywna szybkosc po trafieniu" +lore2 = "x Kumulacje Szybkosci na 3 sekundy po trafieniu" +lore3 = "x Kumulowany glod" +lore4 = "Czas trwania i mnoznik kumulacji glodu." +lore5 = "Mnoznik kumulacji szybkosci, nie czas trwania." +lore = ["Zyskaj pasywna szybkosc po trafieniu", "x Kumulacje Szybkosci na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji szybkosci, nie czas trwania."] [hunter.strength] - name = "Sila Lowcy" - description = "Gdy zostaniesz trafiony zyskujesz sile, kosztem glodu" - lore1 = "Zyskaj pasywna sile po trafieniu" - lore2 = "x Kumulacje Sily na 3 sekundy po trafieniu" - lore3 = "x Kumulowany glod" - lore4 = "Czas trwania i mnoznik kumulacji glodu." - lore5 = "Mnoznik kumulacji sily, nie czas trwania." - lore = ["Zyskaj pasywna sile po trafieniu", "x Kumulacje Sily na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji sily, nie czas trwania."] +name = "Sila Lowcy" +description = "Gdy zostaniesz trafiony zyskujesz sile, kosztem glodu" +lore1 = "Zyskaj pasywna sile po trafieniu" +lore2 = "x Kumulacje Sily na 3 sekundy po trafieniu" +lore3 = "x Kumulowany glod" +lore4 = "Czas trwania i mnoznik kumulacji glodu." +lore5 = "Mnoznik kumulacji sily, nie czas trwania." +lore = ["Zyskaj pasywna sile po trafieniu", "x Kumulacje Sily na 3 sekundy po trafieniu", "x Kumulowany glod", "Czas trwania i mnoznik kumulacji glodu.", "Mnoznik kumulacji sily, nie czas trwania."] # nether [nether] [nether.skull_toss] - name = "Rzut Czaszka Withera" - description1 = "Uwolnij swego wewnetrznego Withera uzywajac" - description2 = "czyjejs" - description3 = "glowy." - lore1 = "Sekundy odnowienia miedzy rzutami czaszka." - lore2 = "Uzywajac Czaszki Withera: Rzuc " - lore3 = "Czaszke Withera" - lore4 = "eksplodujaca przy uderzeniu." - lore = ["Sekundy odnowienia miedzy rzutami czaszka.", "Uzywajac Czaszki Withera: Rzuc ", "Czaszke Withera", "eksplodujaca przy uderzeniu."] +name = "Rzut Czaszka Withera" +description1 = "Uwolnij swego wewnetrznego Withera uzywajac" +description2 = "czyjejs" +description3 = "glowy." +lore1 = "Sekundy odnowienia miedzy rzutami czaszka." +lore2 = "Uzywajac Czaszki Withera: Rzuc " +lore3 = "Czaszke Withera" +lore4 = "eksplodujaca przy uderzeniu." +lore = ["Sekundy odnowienia miedzy rzutami czaszka.", "Uzywajac Czaszki Withera: Rzuc ", "Czaszke Withera", "eksplodujaca przy uderzeniu."] [nether.wither_resist] - name = "Odpornosc na Wiecenie" - description = "Odpiera wiecenie dzieki mocy Netheritu." - lore1 = "szansa na zanegowanie wiecenia (za czesc)." - lore2 = "Pasywne: Noszenie Zbroi z Netheritu ma szanse zanegowac " - lore3 = "wiecenie." - lore = ["szansa na zanegowanie wiecenia (za czesc).", "Pasywne: Noszenie Zbroi z Netheritu ma szanse zanegowac ", "wiecenie."] +name = "Odpornosc na Wiecenie" +description = "Odpiera wiecenie dzieki mocy Netheritu." +lore1 = "szansa na zanegowanie wiecenia (za czesc)." +lore2 = "Pasywne: Noszenie Zbroi z Netheritu ma szanse zanegowac " +lore3 = "wiecenie." +lore = ["szansa na zanegowanie wiecenia (za czesc).", "Pasywne: Noszenie Zbroi z Netheritu ma szanse zanegowac ", "wiecenie."] [nether.fire_resist] - name = "Odpornosc na Ogien" - description = "Odpiera ogien utwardzajac twoja skore." - lore1 = "szansa na zanegowanie efektu palenia!" - lore = ["szansa na zanegowanie efektu palenia!"] +name = "Odpornosc na Ogien" +description = "Odpiera ogien utwardzajac twoja skore." +lore1 = "szansa na zanegowanie efektu palenia!" +lore = ["szansa na zanegowanie efektu palenia!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Autotopienie" - description = "Pozwala automatycznie wytapiac wydobywane standardowe rudy" - lore1 = "Rudy ktore mozna przetopic sa przetapiane automatycznie" - lore2 = "% szansy na dodatkowy" - lore = ["Rudy ktore mozna przetopic sa przetapiane automatycznie", "% szansy na dodatkowy"] +name = "Autotopienie" +description = "Pozwala automatycznie wytapiac wydobywane standardowe rudy" +lore1 = "Rudy ktore mozna przetopic sa przetapiane automatycznie" +lore2 = "% szansy na dodatkowy" +lore = ["Rudy ktore mozna przetopic sa przetapiane automatycznie", "% szansy na dodatkowy"] [pickaxe.chisel] - name = "Dluto do Rud" - description = "Kliknij prawym przyciskiem na rudy aby wydlutac z nich wiecej, kosztem duzej wytrzymalosci." - lore1 = "Szansa na Drop" - lore2 = "Zuzycie Narzedzia" - lore = ["Szansa na Drop", "Zuzycie Narzedzia"] +name = "Dluto do Rud" +description = "Kliknij prawym przyciskiem na rudy aby wydlutac z nich wiecej, kosztem duzej wytrzymalosci." +lore1 = "Szansa na Drop" +lore2 = "Zuzycie Narzedzia" +lore = ["Szansa na Drop", "Zuzycie Narzedzia"] [pickaxe.drop_to_inventory] - name = "Kilof Zrzut-Do-Ekwipunku" - description = "Gdy zniszczysz blok, przedmiot teleportuje sie do twojego ekwipunku" - lore1 = "Za kazdym razem gdy przedmiot wypada z bloku ktory niszczysz, trafia do twojego ekwipunku jesli to mozliwe." - lore = ["Za kazdym razem gdy przedmiot wypada z bloku ktory niszczysz, trafia do twojego ekwipunku jesli to mozliwe."] +name = "Kilof Zrzut-Do-Ekwipunku" +description = "Gdy zniszczysz blok, przedmiot teleportuje sie do twojego ekwipunku" +lore1 = "Za kazdym razem gdy przedmiot wypada z bloku ktory niszczysz, trafia do twojego ekwipunku jesli to mozliwe." +lore = ["Za kazdym razem gdy przedmiot wypada z bloku ktory niszczysz, trafia do twojego ekwipunku jesli to mozliwe."] [pickaxe.silk_spawner] - name = "Kilof Jedwabny-Spawner" - description = "Spawnery wypadaja gdy sa niszczone" - lore1 = "Spawnery mozna niszczyc z Jedwabnym Dotykiem." - lore2 = "Spawnery mozna niszczyc podczas kucania." - lore = ["Spawnery mozna niszczyc z Jedwabnym Dotykiem.", "Spawnery mozna niszczyc podczas kucania."] +name = "Kilof Jedwabny-Spawner" +description = "Spawnery wypadaja gdy sa niszczone" +lore1 = "Spawnery mozna niszczyc z Jedwabnym Dotykiem." +lore2 = "Spawnery mozna niszczyc podczas kucania." +lore = ["Spawnery mozna niszczyc z Jedwabnym Dotykiem.", "Spawnery mozna niszczyc podczas kucania."] [pickaxe.vein_miner] - name = "Gornik Zyl" - description = "Pozwala niszczyc bloki w Zyle/Skupisku standardowych rud" - lore1 = "Kucnij i niszcz RUDY" - lore2 = "zasieg wydobycia zylowego" - lore3 = "Ta umiejetnosc NIE grupuje wszystkich dropow razem!" - lore = ["Kucnij i niszcz RUDY", "zasieg wydobycia zylowego", "Ta umiejetnosc NIE grupuje wszystkich dropow razem!"] +name = "Gornik Zyl" +description = "Pozwala niszczyc bloki w Zyle/Skupisku standardowych rud" +lore1 = "Kucnij i niszcz RUDY" +lore2 = "zasieg wydobycia zylowego" +lore3 = "Ta umiejetnosc NIE grupuje wszystkich dropow razem!" +lore = ["Kucnij i niszcz RUDY", "zasieg wydobycia zylowego", "Ta umiejetnosc NIE grupuje wszystkich dropow razem!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Odzyskiwanie Strzal" - description = "Odzyskuj strzaly po zabiciu wroga." - lore1 = "Szansa na odzyskanie strzal po trafieniu/zabiciu" - lore2 = "Szansa: " - lore = ["Szansa na odzyskanie strzal po trafieniu/zabiciu", "Szansa: "] +name = "Odzyskiwanie Strzal" +description = "Odzyskuj strzaly po zabiciu wroga." +lore1 = "Szansa na odzyskanie strzal po trafieniu/zabiciu" +lore2 = "Szansa: " +lore = ["Szansa na odzyskanie strzal po trafieniu/zabiciu", "Szansa: "] [ranged.web_shot] - name = "Pajeczyna Pulapka" - description = "Otocz cel pajeczyna gdy go trafisz!" - lore1 = "8 Pajeczyn wokol Sniezki i rzuc!" - lore2 = "sekundy klatki, mniej wiecej." - lore = ["8 Pajeczyn wokol Sniezki i rzuc!", "sekundy klatki, mniej wiecej."] +name = "Pajeczyna Pulapka" +description = "Otocz cel pajeczyna gdy go trafisz!" +lore1 = "8 Pajeczyn wokol Sniezki i rzuc!" +lore2 = "sekundy klatki, mniej wiecej." +lore = ["8 Pajeczyn wokol Sniezki i rzuc!", "sekundy klatki, mniej wiecej."] [ranged.force_shot] - name = "Silowy Strzal" - description = "Strzelaj pociskami dalej i szybciej!" - advancementname = "Dlugi Strzal" - advancementlore = "Traf strzalem z ponad 30 blokow!" - lore1 = "Predkosc Pocisku" - lore = ["Predkosc Pocisku"] +name = "Silowy Strzal" +description = "Strzelaj pociskami dalej i szybciej!" +advancementname = "Dlugi Strzal" +advancementlore = "Traf strzalem z ponad 30 blokow!" +lore1 = "Predkosc Pocisku" +lore = ["Predkosc Pocisku"] [ranged.lunge_shot] - name = "Strzal z Wyskoku" - description = "Podczas spadania strzaly rzucaja cie w losowym kierunku" - lore1 = "Losowa Predkosc Zrywu" - lore = ["Losowa Predkosc Zrywu"] +name = "Strzal z Wyskoku" +description = "Podczas spadania strzaly rzucaja cie w losowym kierunku" +lore1 = "Losowa Predkosc Zrywu" +lore = ["Losowa Predkosc Zrywu"] [ranged.arrow_piercing] - name = "Przeszywanie Strzalami" - description = "Dodaje przeszywanie do pociskow! Strzelaj przez przeszkody!" - lore1 = "Przeszyte Cele" - lore = ["Przeszyte Cele"] +name = "Przeszywanie Strzalami" +description = "Dodaje przeszywanie do pociskow! Strzelaj przez przeszkody!" +lore1 = "Przeszyte Cele" +lore = ["Przeszyte Cele"] # rift [rift] [rift.remote_access] - name = "Zdalny Dostep" - description = "Siegnij przez pustke i dostac sie do oznaczonego pojemnika." - lore1 = "Perla Endera + Kompas = Relikwiarzowy Switoklin" - lore2 = "Ten przedmiot pozwala na zdalny dostep do pojemnikow" - lore3 = "Po wytworzeniu spjorz na przedmiot aby zobaczyc uzycie" - notcontainer = "To nie jest pojemnik" - lore = ["Perla Endera + Kompas = Relikwiarzowy Switoklin", "Ten przedmiot pozwala na zdalny dostep do pojemnikow", "Po wytworzeniu spjorz na przedmiot aby zobaczyc uzycie"] +name = "Zdalny Dostep" +description = "Siegnij przez pustke i dostac sie do oznaczonego pojemnika." +lore1 = "Perla Endera + Kompas = Relikwiarzowy Switoklin" +lore2 = "Ten przedmiot pozwala na zdalny dostep do pojemnikow" +lore3 = "Po wytworzeniu spjorz na przedmiot aby zobaczyc uzycie" +notcontainer = "To nie jest pojemnik" +lore = ["Perla Endera + Kompas = Relikwiarzowy Switoklin", "Ten przedmiot pozwala na zdalny dostep do pojemnikow", "Po wytworzeniu spjorz na przedmiot aby zobaczyc uzycie"] [rift.blink] - name = "Mrgniecie Szczeliny" - description = "Natychmiastowa teleportacja krotkiego zasiegu, jedno mrgniecie!" - lore1 = "Blokow na mrgniecie (2x w pionie)" - lore2 = "Podczas Sprintowania: Podwojnie wcisnij Skok aby " - lore3 = "Mrgnac" - lore = ["Blokow na mrgniecie (2x w pionie)", "Podczas Sprintowania: Podwojnie wcisnij Skok aby ", "Mrgnac"] +name = "Mrgniecie Szczeliny" +description = "Natychmiastowa teleportacja krotkiego zasiegu, jedno mrgniecie!" +lore1 = "Blokow na mrgniecie (2x w pionie)" +lore2 = "Podczas Sprintowania: Podwojnie wcisnij Skok aby " +lore3 = "Mrgnac" +lore = ["Blokow na mrgniecie (2x w pionie)", "Podczas Sprintowania: Podwojnie wcisnij Skok aby ", "Mrgnac"] [rift.chest] - name = "Latwa Enderchest" - description = "Otworz enderchest klikajac na nia lewym przyciskiem w rece." - lore1 = "Kliknij Enderchest w rece aby otworzyc (Tylko jej nie stawiaj)" - lore = ["Kliknij Enderchest w rece aby otworzyc (Tylko jej nie stawiaj)"] +name = "Latwa Enderchest" +description = "Otworz enderchest klikajac na nia lewym przyciskiem w rece." +lore1 = "Kliknij Enderchest w rece aby otworzyc (Tylko jej nie stawiaj)" +lore = ["Kliknij Enderchest w rece aby otworzyc (Tylko jej nie stawiaj)"] [rift.descent] - name = "Anty-Lewitacja" - description = "Masz dosc utknieccia w powietrzu? Ta umiejetnosc jest dla ciebie!" - lore1 = "Po prostu kucnij aby opadac, a bedziesz spadac wolniej niz normalnie!" - lore2 = "Czas odnowienia:" - lore = ["Po prostu kucnij aby opadac, a bedziesz spadac wolniej niz normalnie!", "Czas odnowienia:"] +name = "Anty-Lewitacja" +description = "Masz dosc utknieccia w powietrzu? Ta umiejetnosc jest dla ciebie!" +lore1 = "Po prostu kucnij aby opadac, a bedziesz spadac wolniej niz normalnie!" +lore2 = "Czas odnowienia:" +lore = ["Po prostu kucnij aby opadac, a bedziesz spadac wolniej niz normalnie!", "Czas odnowienia:"] [rift.gate] - name = "Brama Szczeliny" - description = "Teleportuj sie do oznaczonej lokalizacji." - lore1 = "WYTWARZANIE: Szmaragd + Odlamek Ametystu + Perla Endera" - lore2 = "Przeczytaj przed uzyciem!" - lore3 = "5 sek. opoznienia, " - lore4 = "mozesz umrzec podczas tej animacji" - lore = ["WYTWARZANIE: Szmaragd + Odlamek Ametystu + Perla Endera", "Przeczytaj przed uzyciem!", "5 sek. opoznienia, ", "mozesz umrzec podczas tej animacji"] +name = "Brama Szczeliny" +description = "Teleportuj sie do oznaczonej lokalizacji." +lore1 = "WYTWARZANIE: Szmaragd + Odlamek Ametystu + Perla Endera" +lore2 = "Przeczytaj przed uzyciem!" +lore3 = "5 sek. opoznienia, " +lore4 = "mozesz umrzec podczas tej animacji" +lore = ["WYTWARZANIE: Szmaragd + Odlamek Ametystu + Perla Endera", "Przeczytaj przed uzyciem!", "5 sek. opoznienia, ", "mozesz umrzec podczas tej animacji"] [rift.resist] - name = "Odpornosc Szczeliny" - description = "Zyskaj Odpornosc gdy uzywasz przedmiotow i zdolnosci Endera" - lore1 = "+ Pasywne: Zapewnia odpornosc gdy uzywasz zdolnosci Szczeliny lub Przedmiotow Endera" - lore2 = "NIE wliczajac przenosnej Enderchest, tylko rzeczy ktore mozesz zuzyc" - lore = ["+ Pasywne: Zapewnia odpornosc gdy uzywasz zdolnosci Szczeliny lub Przedmiotow Endera", "NIE wliczajac przenosnej Enderchest, tylko rzeczy ktore mozesz zuzyc"] +name = "Odpornosc Szczeliny" +description = "Zyskaj Odpornosc gdy uzywasz przedmiotow i zdolnosci Endera" +lore1 = "+ Pasywne: Zapewnia odpornosc gdy uzywasz zdolnosci Szczeliny lub Przedmiotow Endera" +lore2 = "NIE wliczajac przenosnej Enderchest, tylko rzeczy ktore mozesz zuzyc" +lore = ["+ Pasywne: Zapewnia odpornosc gdy uzywasz zdolnosci Szczeliny lub Przedmiotow Endera", "NIE wliczajac przenosnej Enderchest, tylko rzeczy ktore mozesz zuzyc"] [rift.visage] - name = "Oblicze Szczeliny" - description = "Zapobiega agresji Endermanow jesli masz Perly Endera w ekwipunku." - lore1 = "Endermany nie stana sie agresywne jesli masz Perly Endera w ekwipunku." - lore = ["Endermany nie stana sie agresywne jesli masz Perly Endera w ekwipunku."] +name = "Oblicze Szczeliny" +description = "Zapobiega agresji Endermanow jesli masz Perly Endera w ekwipunku." +lore1 = "Endermany nie stana sie agresywne jesli masz Perly Endera w ekwipunku." +lore = ["Endermany nie stana sie agresywne jesli masz Perly Endera w ekwipunku."] # seaborn [seaborn] [seaborn.oxygen] - name = "Organiczny Zbiornik Tlenu" - description = "Przechowuj wiecej tlenu w swoich malych plucach!" - lore1 = "Zwiekszenie Pojemnosci Tlenowej" - lore = ["Zwiekszenie Pojemnosci Tlenowej"] +name = "Organiczny Zbiornik Tlenu" +description = "Przechowuj wiecej tlenu w swoich malych plucach!" +lore1 = "Zwiekszenie Pojemnosci Tlenowej" +lore = ["Zwiekszenie Pojemnosci Tlenowej"] [seaborn.fishers_fantasy] - name = "Fantazja Rybaka" - description = "Zdobywaj wiecej PD z lowienia i lapaj wiecej ryb!" - lore1 = "Za kazdy poziom jest szansa na wiecej PD i wiecej Ryb!" - lore = ["Za kazdy poziom jest szansa na wiecej PD i wiecej Ryb!"] +name = "Fantazja Rybaka" +description = "Zdobywaj wiecej PD z lowienia i lapaj wiecej ryb!" +lore1 = "Za kazdy poziom jest szansa na wiecej PD i wiecej Ryb!" +lore = ["Za kazdy poziom jest szansa na wiecej PD i wiecej Ryb!"] [seaborn.haste] - name = "Zolwi Gornik" - description = "Podczas kopania pod woda zyskujesz pospiech!" - lore1 = "Pospiech 3 jest nakladany pod woda podczas kopania (kumuluje sie z AquaAffinity) po zakonczeniu efektu oddychania pod woda!" - lore = ["Pospiech 3 jest nakladany pod woda podczas kopania (kumuluje sie z AquaAffinity) po zakonczeniu efektu oddychania pod woda!"] +name = "Zolwi Gornik" +description = "Podczas kopania pod woda zyskujesz pospiech!" +lore1 = "Pospiech 3 jest nakladany pod woda podczas kopania (kumuluje sie z AquaAffinity) po zakonczeniu efektu oddychania pod woda!" +lore = ["Pospiech 3 jest nakladany pod woda podczas kopania (kumuluje sie z AquaAffinity) po zakonczeniu efektu oddychania pod woda!"] [seaborn.night_vision] - name = "Wizja Zolwia" - description = "Pod woda zyskujesz Noktowizje" - lore1 = "Po prostu zyskaj Noktowizje pod woda po zakonczeniu efektu oddychania pod woda!" - lore = ["Po prostu zyskaj Noktowizje pod woda po zakonczeniu efektu oddychania pod woda!"] +name = "Wizja Zolwia" +description = "Pod woda zyskujesz Noktowizje" +lore1 = "Po prostu zyskaj Noktowizje pod woda po zakonczeniu efektu oddychania pod woda!" +lore = ["Po prostu zyskaj Noktowizje pod woda po zakonczeniu efektu oddychania pod woda!"] [seaborn.dolphin_grace] - name = "Laska Delfina" - description = "Plywaj jak delfin, bez delfinow" - lore1 = "+ Pasywne: zyskaj " - lore2 = "x predkosc (laska delfina)" - lore3 = "precyzyjna niemiecka inzynier- czekaj, to nie tak... Nie kompatybilne z Glebinowym Krokiem" - lore = ["+ Pasywne: zyskaj ", "x predkosc (laska delfina)", "precyzyjna niemiecka inzynier- czekaj, to nie tak... Nie kompatybilne z Glebinowym Krokiem"] +name = "Laska Delfina" +description = "Plywaj jak delfin, bez delfinow" +lore1 = "+ Pasywne: zyskaj " +lore2 = "x predkosc (laska delfina)" +lore3 = "precyzyjna niemiecka inzynier- czekaj, to nie tak... Nie kompatybilne z Glebinowym Krokiem" +lore = ["+ Pasywne: zyskaj ", "x predkosc (laska delfina)", "precyzyjna niemiecka inzynier- czekaj, to nie tak... Nie kompatybilne z Glebinowym Krokiem"] # stealth [stealth] [stealth.ghost_armor] - name = "Zbroja Ducha" - description = "Wolno narastajacy pancerz gdy nie otrzymujesz obrazen, wytrzymuje 1 trafienie" - lore1 = "Maksymalny Pancerz" - lore2 = "Szybkosc" - lore = ["Maksymalny Pancerz", "Szybkosc"] +name = "Zbroja Ducha" +description = "Wolno narastajacy pancerz gdy nie otrzymujesz obrazen, wytrzymuje 1 trafienie" +lore1 = "Maksymalny Pancerz" +lore2 = "Szybkosc" +lore = ["Maksymalny Pancerz", "Szybkosc"] [stealth.night_vision] - name = "Wizja Skradania" - description = "Zyskaj noktowizje podczas kucania" - lore1 = "Zyskaj porcje " - lore2 = "noktowizji" - lore3 = "podczas kucania" - lore = ["Zyskaj porcje ", "noktowizji", "podczas kucania"] +name = "Wizja Skradania" +description = "Zyskaj noktowizje podczas kucania" +lore1 = "Zyskaj porcje " +lore2 = "noktowizji" +lore3 = "podczas kucania" +lore = ["Zyskaj porcje ", "noktowizji", "podczas kucania"] [stealth.snatch] - name = "Chwytanie Przedmiotow" - description = "Chwytaj Upuszczone Przedmioty natychmiast podczas kucania!" - lore1 = "Zasieg Chwytania" - lore = ["Zasieg Chwytania"] +name = "Chwytanie Przedmiotow" +description = "Chwytaj Upuszczone Przedmioty natychmiast podczas kucania!" +lore1 = "Zasieg Chwytania" +lore = ["Zasieg Chwytania"] [stealth.speed] - name = "Szybkosc Skradania" - description = "Zyskaj szybkosc podczas kucania" - lore1 = "Szybkosc Kucania" - lore = ["Szybkosc Kucania"] +name = "Szybkosc Skradania" +description = "Zyskaj szybkosc podczas kucania" +lore1 = "Szybkosc Kucania" +lore = ["Szybkosc Kucania"] [stealth.ender_veil] - name = "Zaslona Endera" - description = "Koniec z dyniami aby zapobiec atakom Endermanow" - lore1 = "Zapobiega atakom Endermanow podczas kucania" - lore2 = "Zapobiega wszystkim atakom Endermanow" - lore = ["Zapobiega atakom Endermanow podczas kucania", "Zapobiega wszystkim atakom Endermanow"] +name = "Zaslona Endera" +description = "Koniec z dyniami aby zapobiec atakom Endermanow" +lore1 = "Zapobiega atakom Endermanow podczas kucania" +lore2 = "Zapobiega wszystkim atakom Endermanow" +lore = ["Zapobiega atakom Endermanow podczas kucania", "Zapobiega wszystkim atakom Endermanow"] # sword [sword] [sword.machete] - name = "Maczeta" - description = "Przedzieraj sie przez roslinnosc z latwoscia!" - lore1 = "Zasieg Ciecia" - lore2 = "Czas Odnowienia Ciecia" - lore3 = "Zuzycie Narzedzia" - lore = ["Zasieg Ciecia", "Czas Odnowienia Ciecia", "Zuzycie Narzedzia"] +name = "Maczeta" +description = "Przedzieraj sie przez roslinnosc z latwoscia!" +lore1 = "Zasieg Ciecia" +lore2 = "Czas Odnowienia Ciecia" +lore3 = "Zuzycie Narzedzia" +lore = ["Zasieg Ciecia", "Czas Odnowienia Ciecia", "Zuzycie Narzedzia"] [sword.bloody_blade] - name = "Krwawe Ostrze" - description = "Uderzenia mieczem powoduja Krwawienie!" - lore1 = "Trafienie zywej istoty mieczem powoduje Krwawienie" - lore2 = "Czas Trwania Krwawienia" - lore3 = "Czas Odnowienia Krwawienia" - lore = ["Trafienie zywej istoty mieczem powoduje Krwawienie", "Czas Trwania Krwawienia", "Czas Odnowienia Krwawienia"] +name = "Krwawe Ostrze" +description = "Uderzenia mieczem powoduja Krwawienie!" +lore1 = "Trafienie zywej istoty mieczem powoduje Krwawienie" +lore2 = "Czas Trwania Krwawienia" +lore3 = "Czas Odnowienia Krwawienia" +lore = ["Trafienie zywej istoty mieczem powoduje Krwawienie", "Czas Trwania Krwawienia", "Czas Odnowienia Krwawienia"] [sword.poisoned_blade] - name = "Zatrute Ostrze" - description = "Uderzenia mieczem powoduja Truizne!" - lore1 = "Trafienie zywej istoty mieczem powoduje Truizne" - lore2 = "Czas Trwania Trucizny" - lore3 = "Czas Odnowienia Trucizny" - lore = ["Trafienie zywej istoty mieczem powoduje Truizne", "Czas Trwania Trucizny", "Czas Odnowienia Trucizny"] +name = "Zatrute Ostrze" +description = "Uderzenia mieczem powoduja Truizne!" +lore1 = "Trafienie zywej istoty mieczem powoduje Truizne" +lore2 = "Czas Trwania Trucizny" +lore3 = "Czas Odnowienia Trucizny" +lore = ["Trafienie zywej istoty mieczem powoduje Truizne", "Czas Trwania Trucizny", "Czas Odnowienia Trucizny"] # taming [taming] [taming.damage] - name = "Obrazenia Oswojonych" - description = "Zwieksz obrazenia zadawane przez oswojone zwierzeta." - lore1 = "Zwiekszone Obrazenia" - lore = ["Zwiekszone Obrazenia"] +name = "Obrazenia Oswojonych" +description = "Zwieksz obrazenia zadawane przez oswojone zwierzeta." +lore1 = "Zwiekszone Obrazenia" +lore = ["Zwiekszone Obrazenia"] [taming.health] - name = "Zdrowie Oswojonych" - description = "Zwieksz zdrowie swoich oswojonych zwierzat." - lore1 = "Zwiekszone Zdrowie" - lore = ["Zwiekszone Zdrowie"] +name = "Zdrowie Oswojonych" +description = "Zwieksz zdrowie swoich oswojonych zwierzat." +lore1 = "Zwiekszone Zdrowie" +lore = ["Zwiekszone Zdrowie"] [taming.regeneration] - name = "Regeneracja Oswojonych" - description = "Zwieksz regeneracje swoich oswojonych zwierzat." - lore1 = "PZ/s" - lore = ["PZ/s"] +name = "Regeneracja Oswojonych" +description = "Zwieksz regeneracje swoich oswojonych zwierzat." +lore1 = "PZ/s" +lore = ["PZ/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Ciernie" - description = "Odbij obrazenia z powrotem na atakujacego!" - lore1 = "Obrazenia odwetowe po trafieniu" - lore = ["Obrazenia odwetowe po trafieniu"] +name = "Ciernie" +description = "Odbij obrazenia z powrotem na atakujacego!" +lore1 = "Obrazenia odwetowe po trafieniu" +lore = ["Obrazenia odwetowe po trafieniu"] [tragoul.globe] - name = "Kula Bolu" - description = "Podziel zadawane obrazenia na podstawie liczby wrogow wokol ciebie!" - lore1 = "Im wiecej wrogow wokol ciebie, tym mniej obrazen zadasz kazdemu" - lore2 = "Zasieg: " - lore3 = "Dodane Obrazenia dla wszystkich Istot: " - lore = ["Im wiecej wrogow wokol ciebie, tym mniej obrazen zadasz kazdemu", "Zasieg: ", "Dodane Obrazenia dla wszystkich Istot: "] +name = "Kula Bolu" +description = "Podziel zadawane obrazenia na podstawie liczby wrogow wokol ciebie!" +lore1 = "Im wiecej wrogow wokol ciebie, tym mniej obrazen zadasz kazdemu" +lore2 = "Zasieg: " +lore3 = "Dodane Obrazenia dla wszystkich Istot: " +lore = ["Im wiecej wrogow wokol ciebie, tym mniej obrazen zadasz kazdemu", "Zasieg: ", "Dodane Obrazenia dla wszystkich Istot: "] [tragoul.healing] - name = "Wola Bolu" - description = "Odzyskaj zdrowie na podstawie zadawanych obrazen!" - lore1 = "Krzywdzenie jeszcze nigdy nie bylo tak przyjemne! Lecz sie z Zadanych Obrazen" - lore2 = "Jest 3-sekundowe okno obrazen na leczenie i 1-sekundowy czas odnowienia " - lore3 = "Procent Leczenia za Obrazenia: " - lore = ["Krzywdzenie jeszcze nigdy nie bylo tak przyjemne! Lecz sie z Zadanych Obrazen", "Jest 3-sekundowe okno obrazen na leczenie i 1-sekundowy czas odnowienia ", "Procent Leczenia za Obrazenia: "] +name = "Wola Bolu" +description = "Odzyskaj zdrowie na podstawie zadawanych obrazen!" +lore1 = "Krzywdzenie jeszcze nigdy nie bylo tak przyjemne! Lecz sie z Zadanych Obrazen" +lore2 = "Jest 3-sekundowe okno obrazen na leczenie i 1-sekundowy czas odnowienia " +lore3 = "Procent Leczenia za Obrazenia: " +lore = ["Krzywdzenie jeszcze nigdy nie bylo tak przyjemne! Lecz sie z Zadanych Obrazen", "Jest 3-sekundowe okno obrazen na leczenie i 1-sekundowy czas odnowienia ", "Procent Leczenia za Obrazenia: "] [tragoul.lance] - name = "Lance Trupow" - description = "Zabicie wroga lub zabojstwo przez umiejetnosc tworzy lance zadajaca obrazenia pobliskiemu wrogowi!" - lore1 = "Lance beda szukac ze wszystkiego co zabijesz ORAZ jesli ta umiejetnosc zabije wroga." - lore2 = "Poswiec czesc swojego zycia aby stworzyc lance (to moze cie zabic)" - lore3 = "Maks. Lanc: 1 + " - lore = ["Lance beda szukac ze wszystkiego co zabijesz ORAZ jesli ta umiejetnosc zabije wroga.", "Poswiec czesc swojego zycia aby stworzyc lance (to moze cie zabic)", "Maks. Lanc: 1 + "] +name = "Lance Trupow" +description = "Zabicie wroga lub zabojstwo przez umiejetnosc tworzy lance zadajaca obrazenia pobliskiemu wrogowi!" +lore1 = "Lance beda szukac ze wszystkiego co zabijesz ORAZ jesli ta umiejetnosc zabije wroga." +lore2 = "Poswiec czesc swojego zycia aby stworzyc lance (to moze cie zabic)" +lore3 = "Maks. Lanc: 1 + " +lore = ["Lance beda szukac ze wszystkiego co zabijesz ORAZ jesli ta umiejetnosc zabije wroga.", "Poswiec czesc swojego zycia aby stworzyc lance (to moze cie zabic)", "Maks. Lanc: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Szklane Dzialo" - description = "Dodatkowe obrazenia wreczna im nizsza wartosc twojego pancerza" - lore1 = "x Obrazenia przy 0 pancerza" - lore2 = "Dodatkowe Obrazenia za Poziom" - lore = ["x Obrazenia przy 0 pancerza", "Dodatkowe Obrazenia za Poziom"] +name = "Szklane Dzialo" +description = "Dodatkowe obrazenia wreczna im nizsza wartosc twojego pancerza" +lore1 = "x Obrazenia przy 0 pancerza" +lore2 = "Dodatkowe Obrazenia za Poziom" +lore = ["x Obrazenia przy 0 pancerza", "Dodatkowe Obrazenia za Poziom"] [unarmed.power] - name = "Moc Walki Wreczna" - description = "Ulepszone obrazenia wreczna" - lore1 = "Obrazenia" - lore = ["Obrazenia"] +name = "Moc Walki Wreczna" +description = "Ulepszone obrazenia wreczna" +lore1 = "Obrazenia" +lore = ["Obrazenia"] [unarmed.sucker_punch] - name = "Cios z Zaskoczenia" - description = "Sprintowe ciosy, ale bardziej smiercionosne." - lore1 = "Obrazenia" - lore2 = "Obrazenia rosna wraz z szybkoscia podczas ciosow" - lore = ["Obrazenia", "Obrazenia rosna wraz z szybkoscia podczas ciosow"] +name = "Cios z Zaskoczenia" +description = "Sprintowe ciosy, ale bardziej smiercionosne." +lore1 = "Obrazenia" +lore2 = "Obrazenia rosna wraz z szybkoscia podczas ciosow" +lore = ["Obrazenia", "Obrazenia rosna wraz z szybkoscia podczas ciosow"] diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0a8195790..1dac13608 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,20 +1,22 @@ name: ${name} -main: com.volmit.adapt.Adapt +main: art.arcane.adapt.Adapt version: ${version} authors: - NextdoorPsycho - Cyberpwn - Vatuu api-version: '${apiVersion}' +folia-supported: true softdepend: - PlaceholderAPI - WorldGuard - Factions - ChestProtect - Residence + - HiddenOre commands: - adapt: {} + adapt: { } permissions: adapt.idontknowwhatimdoingiswear: @@ -46,411 +48,411 @@ permissions: adapt.clear: description: Allows clearing player progression data default: op - adapt.blacklist.agility: - description: Blacklist the skill Agility - default: false + adapt.use.agility: + description: Allow use of the skill Agility + default: true children: - adapt.blacklist.agilityarmorup: - description: Blacklist the adaptation AgilityArmorUp - default: false - adapt.blacklist.agilitysuperjump: - description: Blacklist the adaptation AgilitySuperJump - default: false - adapt.blacklist.agilitywalljump: - description: Blacklist the adaptation AgilityWallJump - default: false - adapt.blacklist.agilitywindup: - description: Blacklist the adaptation AgilityWindUp - default: false - adapt.blacklist.architect: - description: Blacklist the skill Architect - default: false + adapt.use.agilityarmorup: + description: Allow use of the adaptation AgilityArmorUp + default: true + adapt.use.agilitysuperjump: + description: Allow use of the adaptation AgilitySuperJump + default: true + adapt.use.agilitywalljump: + description: Allow use of the adaptation AgilityWallJump + default: true + adapt.use.agilitywindup: + description: Allow use of the adaptation AgilityWindUp + default: true + adapt.use.architect: + description: Allow use of the skill Architect + default: true children: - adapt.blacklist.architectfoundation: - description: Blacklist the adaptation ArchitectFoundation - default: false - adapt.blacklist.architectglass: - description: Blacklist the adaptation ArchitectGlass - default: false - adapt.blacklist.architectplacement: - description: Blacklist the adaptation ArchitectPlacement - default: false - adapt.blacklist.architectwirelessredstone: - description: Blacklist the adaptation ArchitectWirelessRedstone - default: false - adapt.blacklist.axes: - description: Blacklist the skill Axes - default: false + adapt.use.architectfoundation: + description: Allow use of the adaptation ArchitectFoundation + default: true + adapt.use.architectglass: + description: Allow use of the adaptation ArchitectGlass + default: true + adapt.use.architectplacement: + description: Allow use of the adaptation ArchitectPlacement + default: true + adapt.use.architectwirelessredstone: + description: Allow use of the adaptation ArchitectWirelessRedstone + default: true + adapt.use.axes: + description: Allow use of the skill Axes + default: true children: - adapt.blacklist.axechop: - description: Blacklist the adaptation AxesChop - default: false - adapt.blacklist.axedroptoinventory: - description: Blacklist the adaptation AxesDropToInventory - default: false - adapt.blacklist.axegroundsmash: - description: Blacklist the adaptation AxesGroundSmash - default: false - adapt.blacklist.axeleafveinminer: - description: Blacklist the adaptation AxesLeafVeinMiner - default: false - adapt.blacklist.axelogswap: - description: Blacklist the adaptation AxesLogSwap - default: false - adapt.blacklist.axewoodveinminer: - description: Blacklist the adaptation AxesWoodVeinMiner - default: false - adapt.blacklist.blocking: - description: Blacklist the skill Blocking - default: false + adapt.use.axechop: + description: Allow use of the adaptation AxesChop + default: true + adapt.use.axedroptoinventory: + description: Allow use of the adaptation AxesDropToInventory + default: true + adapt.use.axegroundsmash: + description: Allow use of the adaptation AxesGroundSmash + default: true + adapt.use.axeleafveinminer: + description: Allow use of the adaptation AxesLeafVeinMiner + default: true + adapt.use.axelogswap: + description: Allow use of the adaptation AxesLogSwap + default: true + adapt.use.axewoodveinminer: + description: Allow use of the adaptation AxesWoodVeinMiner + default: true + adapt.use.blocking: + description: Allow use of the skill Blocking + default: true children: - adapt.blacklist.blockingchainarmorer: - description: Blacklist the adaptation BlockingChainArmorer - default: false - adapt.blacklist.blockinghorsearmorer: - description: Blacklist the adaptation BlockingHorseArmorer - default: false - adapt.blacklist.blockingmultiarmor: - description: Blacklist the adaptation BlockingMultiArmor - default: false - adapt.blacklist.blockingsaddlecrafter: - description: Blacklist the adaptation BlockingSaddleCrafter - default: false - adapt.blacklist.brewing: - description: Blacklist the skill Brewing - default: false + adapt.use.blockingchainarmorer: + description: Allow use of the adaptation BlockingChainArmorer + default: true + adapt.use.blockinghorsearmorer: + description: Allow use of the adaptation BlockingHorseArmorer + default: true + adapt.use.blockingmultiarmor: + description: Allow use of the adaptation BlockingMultiArmor + default: true + adapt.use.blockingsaddlecrafter: + description: Allow use of the adaptation BlockingSaddleCrafter + default: true + adapt.use.brewing: + description: Allow use of the skill Brewing + default: true children: - adapt.blacklist.brewingabsorption: - description: Blacklist the adaptation BrewingAbsorption - default: false - adapt.blacklist.brewingblindness: - description: Blacklist the adaptation BrewingBlindness - default: false - adapt.blacklist.brewingdarkness: - description: Blacklist the adaptation BrewingDarkness - default: false - adapt.blacklist.brewingdecay: - description: Blacklist the adaptation BrewingDecay - default: false - adapt.blacklist.brewingfatigue: - description: Blacklist the adaptation BrewingFatigue - default: false - adapt.blacklist.brewinghaste: - description: Blacklist the adaptation BrewingHaste - default: false - adapt.blacklist.brewinghealthboost: - description: Blacklist the adaptation BrewingHealthBoost - default: false - adapt.blacklist.brewinghunger: - description: Blacklist the adaptation BrewingHunger - default: false - adapt.blacklist.brewinglingering: - description: Blacklist the adaptation BrewingLingering - default: false - adapt.blacklist.brewingnausea: - description: Blacklist the adaptation BrewingNausea - default: false - adapt.blacklist.brewingresistance: - description: Blacklist the adaptation BrewingResistance - default: false - adapt.blacklist.brewingsaturation: - description: Blacklist the adaptation BrewingSaturation - default: false - adapt.blacklist.brewingsuperheated: - description: Blacklist the adaptation BrewingSuperheated - default: false - adapt.blacklist.crafting: - description: Blacklist the skill Crafting - default: false + adapt.use.brewingabsorption: + description: Allow use of the adaptation BrewingAbsorption + default: true + adapt.use.brewingblindness: + description: Allow use of the adaptation BrewingBlindness + default: true + adapt.use.brewingdarkness: + description: Allow use of the adaptation BrewingDarkness + default: true + adapt.use.brewingdecay: + description: Allow use of the adaptation BrewingDecay + default: true + adapt.use.brewingfatigue: + description: Allow use of the adaptation BrewingFatigue + default: true + adapt.use.brewinghaste: + description: Allow use of the adaptation BrewingHaste + default: true + adapt.use.brewinghealthboost: + description: Allow use of the adaptation BrewingHealthBoost + default: true + adapt.use.brewinghunger: + description: Allow use of the adaptation BrewingHunger + default: true + adapt.use.brewinglingering: + description: Allow use of the adaptation BrewingLingering + default: true + adapt.use.brewingnausea: + description: Allow use of the adaptation BrewingNausea + default: true + adapt.use.brewingresistance: + description: Allow use of the adaptation BrewingResistance + default: true + adapt.use.brewingsaturation: + description: Allow use of the adaptation BrewingSaturation + default: true + adapt.use.brewingsuperheated: + description: Allow use of the adaptation BrewingSuperheated + default: true + adapt.use.crafting: + description: Allow use of the skill Crafting + default: true children: - adapt.blacklist.craftingbackpacks: - description: Blacklist the adaptation CraftingBackpacks - default: false - adapt.blacklist.craftingdeconstruction: - description: Blacklist the adaptation CraftingDeconstruction - default: false - adapt.blacklist.craftingleather: - description: Blacklist the adaptation CraftingLeather - default: false - adapt.blacklist.craftingreconstruction: - description: Blacklist the adaptation CraftingReconstruction - default: false - adapt.blacklist.craftingskulls: - description: Blacklist the adaptation CraftingSkulls - default: false - adapt.blacklist.craftingstations: - description: Blacklist the adaptation CraftingStations - default: false - adapt.blacklist.craftingxp: - description: Blacklist the adaptation CraftingXP - default: false - adapt.blacklist.chronos: - description: Blacklist the skill Chronos - default: false + adapt.use.craftingbackpacks: + description: Allow use of the adaptation CraftingBackpacks + default: true + adapt.use.craftingdeconstruction: + description: Allow use of the adaptation CraftingDeconstruction + default: true + adapt.use.craftingleather: + description: Allow use of the adaptation CraftingLeather + default: true + adapt.use.craftingreconstruction: + description: Allow use of the adaptation CraftingReconstruction + default: true + adapt.use.craftingskulls: + description: Allow use of the adaptation CraftingSkulls + default: true + adapt.use.craftingstations: + description: Allow use of the adaptation CraftingStations + default: true + adapt.use.craftingxp: + description: Allow use of the adaptation CraftingXP + default: true + adapt.use.chronos: + description: Allow use of the skill Chronos + default: true children: - adapt.blacklist.chronostimebottle: - description: Blacklist the adaptation ChronosTimeInABottle - default: false - adapt.blacklist.chronosaberranttouch: - description: Blacklist the adaptation ChronosAberrantTouch - default: false - adapt.blacklist.chronosinstantrecall: - description: Blacklist the adaptation ChronosInstantRecall - default: false - adapt.blacklist.chronostimebomb: - description: Blacklist the adaptation ChronosTimeBomb - default: false - adapt.blacklist.discovery: - description: Blacklist the skill Discovery - default: false + adapt.use.chronostimebottle: + description: Allow use of the adaptation ChronosTimeInABottle + default: true + adapt.use.chronosaberranttouch: + description: Allow use of the adaptation ChronosAberrantTouch + default: true + adapt.use.chronosinstantrecall: + description: Allow use of the adaptation ChronosInstantRecall + default: true + adapt.use.chronostimebomb: + description: Allow use of the adaptation ChronosTimeBomb + default: true + adapt.use.discovery: + description: Allow use of the skill Discovery + default: true children: - adapt.blacklist.discoveryunity: - description: Blacklist the adaptation DiscoveryUnity - default: false - adapt.blacklist.discoveryvillageratt: - description: Blacklist the adaptation DiscoveryVillagerAtt - default: false - adapt.blacklist.discoveryworldarmor: - description: Blacklist the adaptation DiscoveryWorldArmor - default: false - adapt.blacklist.discoveryxpresist: - description: Blacklist the adaptation DiscoveryXpResist - default: false - adapt.blacklist.enchanting: - description: Blacklist the skill Enchanting - default: false + adapt.use.discoveryunity: + description: Allow use of the adaptation DiscoveryUnity + default: true + adapt.use.discoveryvillageratt: + description: Allow use of the adaptation DiscoveryVillagerAtt + default: true + adapt.use.discoveryworldarmor: + description: Allow use of the adaptation DiscoveryWorldArmor + default: true + adapt.use.discoveryxpresist: + description: Allow use of the adaptation DiscoveryXpResist + default: true + adapt.use.enchanting: + description: Allow use of the skill Enchanting + default: true children: - adapt.blacklist.enchantinglapisreturn: - description: Blacklist the adaptation EnchantingLapisReturn - default: false - adapt.blacklist.enchantingquickenchant: - description: Blacklist the adaptation EnchantingQuickEnchant - default: false - adapt.blacklist.enchantingxpreturn: - description: Blacklist the adaptation EnchantingXpReturn - default: false - adapt.blacklist.excavation: - description: Blacklist the skill Excavation - default: false + adapt.use.enchantinglapisreturn: + description: Allow use of the adaptation EnchantingLapisReturn + default: true + adapt.use.enchantingquickenchant: + description: Allow use of the adaptation EnchantingQuickEnchant + default: true + adapt.use.enchantingxpreturn: + description: Allow use of the adaptation EnchantingXpReturn + default: true + adapt.use.excavation: + description: Allow use of the skill Excavation + default: true children: - adapt.blacklist.excavationdroptoinventory: - description: Blacklist the adaptation ExcavationDropToInventory - default: false - adapt.blacklist.excavationhaste: - description: Blacklist the adaptation ExcavationHaste - default: false - adapt.blacklist.excavationomnitool: - description: Blacklist the adaptation ExcavationOmnitool - default: false - adapt.blacklist.excavationspelunker: - description: Blacklist the adaptation ExcavationSpelunker - default: false - adapt.blacklist.herbalism: - description: Blacklist the skill Herbalism - default: false + adapt.use.excavationdroptoinventory: + description: Allow use of the adaptation ExcavationDropToInventory + default: true + adapt.use.excavationhaste: + description: Allow use of the adaptation ExcavationHaste + default: true + adapt.use.excavationomnitool: + description: Allow use of the adaptation ExcavationOmnitool + default: true + adapt.use.excavationspelunker: + description: Allow use of the adaptation ExcavationSpelunker + default: true + adapt.use.herbalism: + description: Allow use of the skill Herbalism + default: true children: - adapt.blacklist.herbalismcobweb: - description: Blacklist the adaptation HerbalismCobweb - default: false - adapt.blacklist.herbalismdroptoinventory: - description: Blacklist the adaptation HerbalismDropToInventory - default: false - adapt.blacklist.herbalismgrowthaura: - description: Blacklist the adaptation HerbalismGrowthAura - default: false - adapt.blacklist.herbalismhippo: - description: Blacklist the adaptation HerbalismHippo - default: false - adapt.blacklist.herbalismluck: - description: Blacklist the adaptation HerbalismLuck - default: false - adapt.blacklist.herbalismmyconid: - description: Blacklist the adaptation HerbalismMyconid - default: false - adapt.blacklist.herbalismreplant: - description: Blacklist the adaptation HerbalismReplant - default: false - adapt.blacklist.herbalismterralid: - description: Blacklist the adaptation HerbalismTerralid - default: false - adapt.blacklist.herbalismhungryshield: - description: Blacklist the adaptation HerbalismHungryShield - default: false - adapt.blacklist.hunter: - description: Blacklist the skill Hunter - default: false + adapt.use.herbalismcobweb: + description: Allow use of the adaptation HerbalismCobweb + default: true + adapt.use.herbalismdroptoinventory: + description: Allow use of the adaptation HerbalismDropToInventory + default: true + adapt.use.herbalismgrowthaura: + description: Allow use of the adaptation HerbalismGrowthAura + default: true + adapt.use.herbalismhippo: + description: Allow use of the adaptation HerbalismHippo + default: true + adapt.use.herbalismluck: + description: Allow use of the adaptation HerbalismLuck + default: true + adapt.use.herbalismmyconid: + description: Allow use of the adaptation HerbalismMyconid + default: true + adapt.use.herbalismreplant: + description: Allow use of the adaptation HerbalismReplant + default: true + adapt.use.herbalismterralid: + description: Allow use of the adaptation HerbalismTerralid + default: true + adapt.use.herbalismhungryshield: + description: Allow use of the adaptation HerbalismHungryShield + default: true + adapt.use.hunter: + description: Allow use of the skill Hunter + default: true children: - adapt.blacklist.hunteradrenaline: - description: Blacklist the adaptation HunterAdrenaline - default: false - adapt.blacklist.hunterdroptoinventory: - description: Blacklist the adaptation HunterDropToInventory - default: false - adapt.blacklist.hunterinvis: - description: Blacklist the adaptation HunterInvis - default: false - adapt.blacklist.hunterjumpboost: - description: Blacklist the adaptation HunterJumpBoost - default: false - adapt.blacklist.hunterluck: - description: Blacklist the adaptation HunterLuck - default: false - adapt.blacklist.hunterspeed: - description: Blacklist the adaptation HunterSpeed - default: false - adapt.blacklist.hunterstrength: - description: Blacklist the adaptation HunterStrength - default: false - adapt.blacklist.hunterregen: - description: Blacklist the adaptation HunterRegen - default: false - adapt.blacklist.hunterresistance: - description: Blacklist the adaptation HunterResistance - default: false - adapt.blacklist.nether: - description: Blacklist the skill Nether - default: false + adapt.use.hunteradrenaline: + description: Allow use of the adaptation HunterAdrenaline + default: true + adapt.use.hunterdroptoinventory: + description: Allow use of the adaptation HunterDropToInventory + default: true + adapt.use.hunterinvis: + description: Allow use of the adaptation HunterInvis + default: true + adapt.use.hunterjumpboost: + description: Allow use of the adaptation HunterJumpBoost + default: true + adapt.use.hunterluck: + description: Allow use of the adaptation HunterLuck + default: true + adapt.use.hunterspeed: + description: Allow use of the adaptation HunterSpeed + default: true + adapt.use.hunterstrength: + description: Allow use of the adaptation HunterStrength + default: true + adapt.use.hunterregen: + description: Allow use of the adaptation HunterRegen + default: true + adapt.use.hunterresistance: + description: Allow use of the adaptation HunterResistance + default: true + adapt.use.nether: + description: Allow use of the skill Nether + default: true children: - adapt.blacklist.netherskulltoss: - description: Blacklist the adaptation NetherSkullToss - default: false - adapt.blacklist.netherwitherresist: - description: Blacklist the adaptation NetherWitherResist - default: false - adapt.blacklist.netherfireresist: - description: Blacklist the adaptation NetherFireResist - default: false - adapt.blacklist.pickaxe: - description: Blacklist the skill Pickaxe - default: false + adapt.use.netherskulltoss: + description: Allow use of the adaptation NetherSkullToss + default: true + adapt.use.netherwitherresist: + description: Allow use of the adaptation NetherWitherResist + default: true + adapt.use.netherfireresist: + description: Allow use of the adaptation NetherFireResist + default: true + adapt.use.pickaxe: + description: Allow use of the skill Pickaxe + default: true children: - adapt.blacklist.pickaxeautosmelt: - description: Blacklist the adaptation PickaxeAutoSmelt - default: false - adapt.blacklist.pickaxedroptoinventory: - description: Blacklist the adaptation PickaxeDropToInventory - default: false - adapt.blacklist.pickaxechisel: - description: Blacklist the adaptation PickaxeChisel - default: false - adapt.blacklist.pickaxeveinminer: - description: Blacklist the adaptation PickaxeVeinMiner - default: false - adapt.blacklist.ranged: - description: Blacklist the skill Ranged - default: false + adapt.use.pickaxeautosmelt: + description: Allow use of the adaptation PickaxeAutoSmelt + default: true + adapt.use.pickaxedroptoinventory: + description: Allow use of the adaptation PickaxeDropToInventory + default: true + adapt.use.pickaxechisel: + description: Allow use of the adaptation PickaxeChisel + default: true + adapt.use.pickaxeveinminer: + description: Allow use of the adaptation PickaxeVeinMiner + default: true + adapt.use.ranged: + description: Allow use of the skill Ranged + default: true children: - adapt.blacklist.rangedforce: - description: Blacklist the adaptation RangedForce - default: false - adapt.blacklist.rangedlungeshot: - description: Blacklist the adaptation RangedLungeShot - default: false - adapt.blacklist.rangedpiercing: - description: Blacklist the adaptation RangedPiercing - default: false - adapt.blacklist.rangedrecovery: - description: Blacklist the adaptation RangedRecovery - default: false - adapt.blacklist.rangedwebshot: - description: Blacklist the adaptation RangedWebShot - default: false - adapt.blacklist.rift: - description: Blacklist the skill Rift - default: false + adapt.use.rangedforce: + description: Allow use of the adaptation RangedForce + default: true + adapt.use.rangedlungeshot: + description: Allow use of the adaptation RangedLungeShot + default: true + adapt.use.rangedpiercing: + description: Allow use of the adaptation RangedPiercing + default: true + adapt.use.rangedrecovery: + description: Allow use of the adaptation RangedRecovery + default: true + adapt.use.rangedwebshot: + description: Allow use of the adaptation RangedWebShot + default: true + adapt.use.rift: + description: Allow use of the skill Rift + default: true children: - adapt.blacklist.riftaccess: - description: Blacklist the adaptation RiftAccess - default: false - adapt.blacklist.riftblink: - description: Blacklist the adaptation RiftBlink - default: false - adapt.blacklist.riftdescent: - description: Blacklist the adaptation RiftDescent - default: false - adapt.blacklist.riftenderchest: - description: Blacklist the adaptation RiftEnderChest - default: false - adapt.blacklist.riftgate: - description: Blacklist the adaptation RiftGate - default: false - adapt.blacklist.riftresist: - description: Blacklist the adaptation RiftResist - default: false - adapt.blacklist.riftvisage: - description: Blacklist the adaptation RiftVisage - default: false - adapt.blacklist.seaborne: - description: Blacklist the skill Seaborne - default: false + adapt.use.riftaccess: + description: Allow use of the adaptation RiftAccess + default: true + adapt.use.riftblink: + description: Allow use of the adaptation RiftBlink + default: true + adapt.use.riftdescent: + description: Allow use of the adaptation RiftDescent + default: true + adapt.use.riftenderchest: + description: Allow use of the adaptation RiftEnderChest + default: true + adapt.use.riftgate: + description: Allow use of the adaptation RiftGate + default: true + adapt.use.riftresist: + description: Allow use of the adaptation RiftResist + default: true + adapt.use.riftvisage: + description: Allow use of the adaptation RiftVisage + default: true + adapt.use.seaborne: + description: Allow use of the skill Seaborne + default: true children: - adapt.blacklist.seabornefishersfantasy: - description: Blacklist the adaptation SeaborneFishersFantasy - default: false - adapt.blacklist.seabornespeed: - description: Blacklist the adaptation SeaborneSpeed - default: false - adapt.blacklist.seaborneoxygen: - description: Blacklist the adaptation SeaborneOxygen - default: false - adapt.blacklist.seaborneturtlesminingspeed: - description: Blacklist the adaptation SeaborneTurtlesMiningSpeed - default: false - adapt.blacklist.seaborneturtlesvision: - description: Blacklist the adaptation SeaborneTurtlesVision - default: false - adapt.blacklist.swords: - description: Blacklist the skill Swords - default: false + adapt.use.seabornefishersfantasy: + description: Allow use of the adaptation SeaborneFishersFantasy + default: true + adapt.use.seabornespeed: + description: Allow use of the adaptation SeaborneSpeed + default: true + adapt.use.seaborneoxygen: + description: Allow use of the adaptation SeaborneOxygen + default: true + adapt.use.seaborneturtlesminingspeed: + description: Allow use of the adaptation SeaborneTurtlesMiningSpeed + default: true + adapt.use.seaborneturtlesvision: + description: Allow use of the adaptation SeaborneTurtlesVision + default: true + adapt.use.swords: + description: Allow use of the skill Swords + default: true children: - adapt.blacklist.swordbloodyblade: - description: Blacklist the adaptation SwordBloodyBlade - default: false - adapt.blacklist.swordmachete: - description: Blacklist the adaptation SwordMachete - default: false - adapt.blacklist.swordpoisonblade: - description: Blacklist the adaptation SwordPoisonBlade - default: false - adapt.blacklist.taming: - description: Blacklist the skill Taming - default: false + adapt.use.swordbloodyblade: + description: Allow use of the adaptation SwordBloodyBlade + default: true + adapt.use.swordmachete: + description: Allow use of the adaptation SwordMachete + default: true + adapt.use.swordpoisonblade: + description: Allow use of the adaptation SwordPoisonBlade + default: true + adapt.use.taming: + description: Allow use of the skill Taming + default: true children: - adapt.blacklist.tamehealth: - description: Blacklist the adaptation TameHealth - default: false - adapt.blacklist.tamedamage: - description: Blacklist the adaptation TameDamage - default: false - adapt.blacklist.tamehealthregeneration: - description: Blacklist the adaptation TameHealthRegeneration - default: false - adapt.blacklist.tragoul: - description: Blacklist the skill Tragoul - default: false + adapt.use.tamehealth: + description: Allow use of the adaptation TameHealth + default: true + adapt.use.tamedamage: + description: Allow use of the adaptation TameDamage + default: true + adapt.use.tamehealthregeneration: + description: Allow use of the adaptation TameHealthRegeneration + default: true + adapt.use.tragoul: + description: Allow use of the skill Tragoul + default: true children: - adapt.blacklist.tragoulglobe: - description: Blacklist the adaptation TragoulGlobe - default: false - adapt.blacklist.tragoulhealing: - description: Blacklist the adaptation TragoulHealing - default: false - adapt.blacklist.tragoullance: - description: Blacklist the adaptation TragoulLance - default: false - adapt.blacklist.tragoulthorns: - description: Blacklist the adaptation TragoulThorns - default: false - adapt.blacklist.unarmed: - description: Blacklist the skill Unarmed - default: false + adapt.use.tragoulglobe: + description: Allow use of the adaptation TragoulGlobe + default: true + adapt.use.tragoulhealing: + description: Allow use of the adaptation TragoulHealing + default: true + adapt.use.tragoullance: + description: Allow use of the adaptation TragoulLance + default: true + adapt.use.tragoulthorns: + description: Allow use of the adaptation TragoulThorns + default: true + adapt.use.unarmed: + description: Allow use of the skill Unarmed + default: true children: - adapt.blacklist.unarmedglasscannon: - description: Blacklist the adaptation UnarmedGlassCannon - default: false - adapt.blacklist.unarmedpower: - description: Blacklist the adaptation UnarmedPower - default: false - adapt.blacklist.unarmedsuckerpunch: - description: Blacklist the adaptation UnarmedSuckerPunch - default: false + adapt.use.unarmedglasscannon: + description: Allow use of the adaptation UnarmedGlassCannon + default: true + adapt.use.unarmedpower: + description: Allow use of the adaptation UnarmedPower + default: true + adapt.use.unarmedsuckerpunch: + description: Allow use of the adaptation UnarmedSuckerPunch + default: true diff --git a/src/main/resources/pt_PT.toml b/src/main/resources/pt_PT.toml index 1398b9e14..cfc16a177 100644 --- a/src/main/resources/pt_PT.toml +++ b/src/main/resources/pt_PT.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "Tem que se mexer!" - description = "Caminhe mais de 1 quilometro (1.000 blocos)" +title = "Tem que se mexer!" +description = "Caminhe mais de 1 quilometro (1.000 blocos)" [advancement.challenge_sprint_5k] - title = "Corra 5K!" - description = "Caminhe mais de 5 quilometros (5.000 blocos)" +title = "Corra 5K!" +description = "Caminhe mais de 5 quilometros (5.000 blocos)" [advancement.challenge_sprint_50k] - title = "Zoom de 50K!" - description = "Caminhe mais de 50 quilometros (50.000 blocos)" +title = "Zoom de 50K!" +description = "Caminhe mais de 50 quilometros (50.000 blocos)" [advancement.challenge_sprint_500k] - title = "Atravesse o Universo!!" - description = "Caminhe mais de 500 quilometros (500.000 blocos)" +title = "Atravesse o Universo!!" +description = "Caminhe mais de 500 quilometros (500.000 blocos)" [advancement.challenge_sprint_marathon] - title = "Corra uma maratona (literal)!" - description = "Corra mais de 42.195 blocos!" +title = "Corra uma maratona (literal)!" +description = "Corra mais de 42.195 blocos!" [advancement.challenge_place_1k] - title = "Construtor Iniciante!" - description = "Coloque 1.000 Blocos" +title = "Construtor Iniciante!" +description = "Coloque 1.000 Blocos" [advancement.challenge_place_5k] - title = "Construtor Intermediario!" - description = "Coloque 5.000 Blocos" +title = "Construtor Intermediario!" +description = "Coloque 5.000 Blocos" [advancement.challenge_place_50k] - title = "Construtor Avancado!" - description = "Coloque 50.000 Blocos" +title = "Construtor Avancado!" +description = "Coloque 50.000 Blocos" [advancement.challenge_place_500k] - title = "Mestre Construtor!" - description = "Coloque 500.000 Blocos" +title = "Mestre Construtor!" +description = "Coloque 500.000 Blocos" [advancement.challenge_place_5m] - title = "Acolito da Simetria!" - description = "A REALIDADE E O SEU PARQUE! (5 Milhoes de Blocos)" +title = "Acolito da Simetria!" +description = "A REALIDADE E O SEU PARQUE! (5 Milhoes de Blocos)" [advancement.challenge_chop_1k] - title = "Lenhador Iniciante!" - description = "Corte 1.000 Blocos" +title = "Lenhador Iniciante!" +description = "Corte 1.000 Blocos" [advancement.challenge_chop_5k] - title = "Lenhador Intermediario!" - description = "Corte 5.000 Blocos" +title = "Lenhador Intermediario!" +description = "Corte 5.000 Blocos" [advancement.challenge_chop_50k] - title = "Lenhador Avancado!" - description = "Corte 50.000 Blocos" +title = "Lenhador Avancado!" +description = "Corte 50.000 Blocos" [advancement.challenge_chop_500k] - title = "Mestre Lenhador!" - description = "Corte 500.000 Blocos" +title = "Mestre Lenhador!" +description = "Corte 500.000 Blocos" [advancement.challenge_chop_5m] - title = "Jackson, o Cao" - description = "O melhor bom menino! (5 Milhoes de Blocos)" +title = "Jackson, o Cao" +description = "O melhor bom menino! (5 Milhoes de Blocos)" [advancement.challenge_block_1k] - title = "Mal Bloqueando!" - description = "Bloqueie 1000 Golpes" +title = "Mal Bloqueando!" +description = "Bloqueie 1000 Golpes" [advancement.challenge_block_5k] - title = "Bloquear e Divertido!" - description = "Bloqueie 5000 Golpes" +title = "Bloquear e Divertido!" +description = "Bloqueie 5000 Golpes" [advancement.challenge_block_50k] - title = "Bloquear e a minha vida!" - description = "Bloqueie 50.000 Golpes" +title = "Bloquear e a minha vida!" +description = "Bloqueie 50.000 Golpes" [advancement.challenge_block_500k] - title = "Bloquear e o meu Proposito!" - description = "Bloqueie 500.000 Golpes" +title = "Bloquear e o meu Proposito!" +description = "Bloqueie 500.000 Golpes" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Bloqueie 5.000.000 Golpes" +title = "Die Hand Die Verletzt" +description = "Bloqueie 5.000.000 Golpes" [advancement.challenge_brew_1k] - title = "Alquimista Iniciante!" - description = "Consuma 1000 Pocoes" +title = "Alquimista Iniciante!" +description = "Consuma 1000 Pocoes" [advancement.challenge_brew_5k] - title = "Alquimista Intermediario!" - description = "Consuma 5000 Pocoes" +title = "Alquimista Intermediario!" +description = "Consuma 5000 Pocoes" [advancement.challenge_brew_50k] - title = "Alquimista Avancado!" - description = "Consuma 50.000 Pocoes" +title = "Alquimista Avancado!" +description = "Consuma 50.000 Pocoes" [advancement.challenge_brew_500k] - title = "Mestre Alquimista!" - description = "Consuma 500.000 Pocoes" +title = "Mestre Alquimista!" +description = "Consuma 500.000 Pocoes" [advancement.challenge_brew_5m] - title = "O Alquimista" - description = "Consuma 5.000.000 Pocoes" +title = "O Alquimista" +description = "Consuma 5.000.000 Pocoes" [advancement.challenge_brewsplash_1k] - title = "Lancador de Pocoes Iniciante!" - description = "Lance 1000 Pocoes" +title = "Lancador de Pocoes Iniciante!" +description = "Lance 1000 Pocoes" [advancement.challenge_brewsplash_5k] - title = "Lancador de Pocoes Intermediario!" - description = "Lance 5000 Pocoes" +title = "Lancador de Pocoes Intermediario!" +description = "Lance 5000 Pocoes" [advancement.challenge_brewsplash_50k] - title = "Lancador de Pocoes Avancado!" - description = "Lance 50.000 Pocoes" +title = "Lancador de Pocoes Avancado!" +description = "Lance 50.000 Pocoes" [advancement.challenge_brewsplash_500k] - title = "Mestre Lancador de Pocoes!" - description = "Lance 500.000 Pocoes" +title = "Mestre Lancador de Pocoes!" +description = "Lance 500.000 Pocoes" [advancement.challenge_brewsplash_5m] - title = "O Mestre do Respingo" - description = "Lance 5.000.000 Pocoes" +title = "O Mestre do Respingo" +description = "Lance 5.000.000 Pocoes" [advancement.challenge_craft_1k] - title = "Artesao Astuto!" - description = "Fabrique 1000 Itens" +title = "Artesao Astuto!" +description = "Fabrique 1000 Itens" [advancement.challenge_craft_5k] - title = "Artesao Rabugento!" - description = "Fabrique 5000 Itens" +title = "Artesao Rabugento!" +description = "Fabrique 5000 Itens" [advancement.challenge_craft_50k] - title = "Artesao Servil!" - description = "Fabrique 50.000 Itens" +title = "Artesao Servil!" +description = "Fabrique 50.000 Itens" [advancement.challenge_craft_500k] - title = "Artesao Cacofonico!" - description = "Fabrique 500.000 Itens" +title = "Artesao Cacofonico!" +description = "Fabrique 500.000 Itens" [advancement.challenge_craft_5m] - title = "Calamitoso McCraftface" - description = "Fabrique 5.000.000 Itens" +title = "Calamitoso McCraftface" +description = "Fabrique 5.000.000 Itens" [advancement.challenge_enchant_1k] - title = "Encantador Iniciante!" - description = "Encante 1000 Itens" +title = "Encantador Iniciante!" +description = "Encante 1000 Itens" [advancement.challenge_enchant_5k] - title = "Encantador Intermediario!" - description = "Encante 5000 Itens" +title = "Encantador Intermediario!" +description = "Encante 5000 Itens" [advancement.challenge_enchant_50k] - title = "Encantador Avancado!" - description = "Encante 50.000 Itens" +title = "Encantador Avancado!" +description = "Encante 50.000 Itens" [advancement.challenge_enchant_500k] - title = "Mestre Encantador!" - description = "Encante 500.000 Itens" +title = "Mestre Encantador!" +description = "Encante 500.000 Itens" [advancement.challenge_enchant_5m] - title = "Encantador Enigmatico" - description = "Encante 5.000.000 Itens" +title = "Encantador Enigmatico" +description = "Encante 5.000.000 Itens" [advancement.challenge_excavate_1k] - title = "Escavador Entusiasmado!" - description = "Escave 1000 Blocos" +title = "Escavador Entusiasmado!" +description = "Escave 1000 Blocos" [advancement.challenge_excavate_5k] - title = "Escavador Intermediario!" - description = "Escave 5000 Blocos" +title = "Escavador Intermediario!" +description = "Escave 5000 Blocos" [advancement.challenge_excavate_50k] - title = "Escavador Avancado!" - description = "Escave 50.000 Blocos" +title = "Escavador Avancado!" +description = "Escave 50.000 Blocos" [advancement.challenge_excavate_500k] - title = "Mestre Escavador!" - description = "Escave 500.000 Blocos" +title = "Mestre Escavador!" +description = "Escave 500.000 Blocos" [advancement.challenge_excavate_5m] - title = "Escavador Enigmatico" - description = "Escave 5.000.000 Blocos" +title = "Escavador Enigmatico" +description = "Escave 5.000.000 Blocos" [advancement.horrible_person] - title = "Voce e uma Pessoa Horrivel" - description = "Insondavel, realmente" +title = "Voce e uma Pessoa Horrivel" +description = "Insondavel, realmente" [advancement.challenge_turtle_egg_smasher] - title = "Esmagador de Ovos de Tartaruga!" - description = "Quebre 100 ovos de tartaruga" +title = "Esmagador de Ovos de Tartaruga!" +description = "Quebre 100 ovos de tartaruga" [advancement.challenge_turtle_egg_annihilator] - title = "Aniquilador de Ovos de Tartaruga!" - description = "Quebre 500 ovos de tartaruga" +title = "Aniquilador de Ovos de Tartaruga!" +description = "Quebre 500 ovos de tartaruga" [advancement.challenge_novice_hunter] - title = "Cacador Iniciante!" - description = "Mate 100 entidades" +title = "Cacador Iniciante!" +description = "Mate 100 entidades" [advancement.challenge_intermediate_hunter] - title = "Cacador Intermediario!" - description = "Mate 500 entidades" +title = "Cacador Intermediario!" +description = "Mate 500 entidades" [advancement.challenge_advanced_hunter] - title = "Cacador Avancado!" - description = "Mate 5000 entidades" +title = "Cacador Avancado!" +description = "Mate 5000 entidades" [advancement.challenge_creeper_conqueror] - title = "Conquistador de Creepers!" - description = "Mate 50 creepers" +title = "Conquistador de Creepers!" +description = "Mate 50 creepers" [advancement.challenge_creeper_annihilator] - title = "Aniquilador de Creepers!" - description = "Mate 200 creepers" +title = "Aniquilador de Creepers!" +description = "Mate 200 creepers" [advancement.challenge_pickaxe_1k] - title = "Mineiro Iniciante" - description = "Quebre 1000 Blocos" +title = "Mineiro Iniciante" +description = "Quebre 1000 Blocos" [advancement.challenge_pickaxe_5k] - title = "Mineiro Habilidoso" - description = "Quebre 5000 Blocos" +title = "Mineiro Habilidoso" +description = "Quebre 5000 Blocos" [advancement.challenge_pickaxe_50k] - title = "Mineiro Especialista" - description = "Quebre 50.000 Blocos" +title = "Mineiro Especialista" +description = "Quebre 50.000 Blocos" [advancement.challenge_pickaxe_500k] - title = "Mestre Mineiro" - description = "Quebre 500.000 Blocos" +title = "Mestre Mineiro" +description = "Quebre 500.000 Blocos" [advancement.challenge_pickaxe_5m] - title = "Mineiro Lendario" - description = "Quebre 5.000.000 Blocos" +title = "Mineiro Lendario" +description = "Quebre 5.000.000 Blocos" [advancement.challenge_eat_100] - title = "Tanta coisa para comer!" - description = "Coma mais de 100 Itens!" +title = "Tanta coisa para comer!" +description = "Coma mais de 100 Itens!" [advancement.challenge_eat_1000] - title = "Fome Insaciavel!" - description = "Coma mais de 1.000 Itens!" +title = "Fome Insaciavel!" +description = "Coma mais de 1.000 Itens!" [advancement.challenge_eat_10000] - title = "FOME ETERNA!" - description = "Coma mais de 10.000 Itens!" +title = "FOME ETERNA!" +description = "Coma mais de 10.000 Itens!" [advancement.challenge_harvest_100] - title = "Colheita Completa" - description = "Colha mais de 100 cultivos!" +title = "Colheita Completa" +description = "Colha mais de 100 cultivos!" [advancement.challenge_harvest_1000] - title = "Grande Colheita" - description = "Colha mais de 1.000 cultivos!" +title = "Grande Colheita" +description = "Colha mais de 1.000 cultivos!" [advancement.challenge_swim_1nm] - title = "Submarino Humano!" - description = "Nade 1 Milha Nautica (1.852 blocos)" +title = "Submarino Humano!" +description = "Nade 1 Milha Nautica (1.852 blocos)" [advancement.challenge_sneak_1k] - title = "Dor nos Joelhos" - description = "Agache-se por mais de um quilometro (1.000 blocos)" +title = "Dor nos Joelhos" +description = "Agache-se por mais de um quilometro (1.000 blocos)" [advancement.challenge_sneak_5k] - title = "Caminhante das Sombras" - description = "Agache-se por mais de 5.000 blocos" +title = "Caminhante das Sombras" +description = "Agache-se por mais de 5.000 blocos" [advancement.challenge_sneak_20k] - title = "Fantasma" - description = "Agache-se por mais de 20.000 blocos" +title = "Fantasma" +description = "Agache-se por mais de 20.000 blocos" [advancement.challenge_swim_5k] - title = "Mergulhador Profundo" - description = "Nade mais de 5.000 blocos" +title = "Mergulhador Profundo" +description = "Nade mais de 5.000 blocos" [advancement.challenge_swim_20k] - title = "Escolhido de Poseidon" - description = "Nade mais de 20.000 blocos" +title = "Escolhido de Poseidon" +description = "Nade mais de 20.000 blocos" [advancement.challenge_sword_100] - title = "Primeiro Sangue" - description = "Acerte 100 golpes com uma espada" +title = "Primeiro Sangue" +description = "Acerte 100 golpes com uma espada" [advancement.challenge_sword_1k] - title = "Dancarina das Laminas" - description = "Acerte 1.000 golpes com uma espada" +title = "Dancarina das Laminas" +description = "Acerte 1.000 golpes com uma espada" [advancement.challenge_sword_10k] - title = "Mil Cortes" - description = "Acerte 10.000 golpes com uma espada" +title = "Mil Cortes" +description = "Acerte 10.000 golpes com uma espada" [advancement.challenge_unarmed_100] - title = "Brigao de Bar" - description = "Acerte 100 golpes desarmado" +title = "Brigao de Bar" +description = "Acerte 100 golpes desarmado" [advancement.challenge_unarmed_1k] - title = "Punhos de Ferro" - description = "Acerte 1.000 golpes desarmado" +title = "Punhos de Ferro" +description = "Acerte 1.000 golpes desarmado" [advancement.challenge_unarmed_10k] - title = "Um Soco" - description = "Acerte 10.000 golpes desarmado" +title = "Um Soco" +description = "Acerte 10.000 golpes desarmado" [advancement.challenge_trag_1k] - title = "Preco de Sangue" - description = "Receba 1.000 de dano" +title = "Preco de Sangue" +description = "Receba 1.000 de dano" [advancement.challenge_trag_10k] - title = "Mare Vermelha" - description = "Receba 10.000 de dano" +title = "Mare Vermelha" +description = "Receba 10.000 de dano" [advancement.challenge_trag_100k] - title = "Avatar do Sofrimento" - description = "Receba 100.000 de dano" +title = "Avatar do Sofrimento" +description = "Receba 100.000 de dano" [advancement.challenge_ranged_100] - title = "Pratica de Tiro" - description = "Dispare 100 projeteis" +title = "Pratica de Tiro" +description = "Dispare 100 projeteis" [advancement.challenge_ranged_1k] - title = "Olho de Falcao" - description = "Dispare 1.000 projeteis" +title = "Olho de Falcao" +description = "Dispare 1.000 projeteis" [advancement.challenge_ranged_10k] - title = "Tempestade de Flechas" - description = "Dispare 10.000 projeteis" +title = "Tempestade de Flechas" +description = "Dispare 10.000 projeteis" [advancement.challenge_chronos_1h] - title = "Tic-Tac" - description = "Passe 1 hora online" +title = "Tic-Tac" +description = "Passe 1 hora online" [advancement.challenge_chronos_24h] - title = "Areias do Tempo" - description = "Passe 24 horas online" +title = "Areias do Tempo" +description = "Passe 24 horas online" [advancement.challenge_chronos_168h] - title = "Eterno" - description = "Passe 168 horas (1 semana) online" +title = "Eterno" +description = "Passe 168 horas (1 semana) online" [advancement.challenge_nether_50] - title = "Guardiao do Inferno" - description = "Mate 50 criaturas do Nether" +title = "Guardiao do Inferno" +description = "Mate 50 criaturas do Nether" [advancement.challenge_nether_500] - title = "Sentinela Abissal" - description = "Mate 500 criaturas do Nether" +title = "Sentinela Abissal" +description = "Mate 500 criaturas do Nether" [advancement.challenge_nether_5k] - title = "Senhor do Nether" - description = "Mate 5.000 criaturas do Nether" +title = "Senhor do Nether" +description = "Mate 5.000 criaturas do Nether" [advancement.challenge_rift_50] - title = "Anomalia Espacial" - description = "Teleporte-se 50 vezes" +title = "Anomalia Espacial" +description = "Teleporte-se 50 vezes" [advancement.challenge_rift_500] - title = "Caminhante do Vazio" - description = "Teleporte-se 500 vezes" +title = "Caminhante do Vazio" +description = "Teleporte-se 500 vezes" [advancement.challenge_rift_5k] - title = "Entre Mundos" - description = "Teleporte-se 5.000 vezes" +title = "Entre Mundos" +description = "Teleporte-se 5.000 vezes" [advancement.challenge_taming_10] - title = "Encantador de Animais" - description = "Crie 10 animais" +title = "Encantador de Animais" +description = "Crie 10 animais" [advancement.challenge_taming_50] - title = "Lider da Matilha" - description = "Crie 50 animais" +title = "Lider da Matilha" +description = "Crie 50 animais" [advancement.challenge_taming_500] - title = "Mestre das Feras" - description = "Crie 500 animais" +title = "Mestre das Feras" +description = "Crie 500 animais" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Demonio da Velocidade" - description = "Correr mais de 5 Quilometros (5,000 blocos)" +title = "Demonio da Velocidade" +description = "Correr mais de 5 Quilometros (5,000 blocos)" [advancement.challenge_sprint_dist_50k] - title = "Pernas Relampago" - description = "Correr mais de 50 Quilometros (50,000 blocos)" +title = "Pernas Relampago" +description = "Correr mais de 50 Quilometros (50,000 blocos)" [advancement.challenge_agility_swim_1k] - title = "Caminhante das Aguas" - description = "Nadar mais de 1 Quilometro (1,000 blocos)" +title = "Caminhante das Aguas" +description = "Nadar mais de 1 Quilometro (1,000 blocos)" [advancement.challenge_agility_swim_10k] - title = "Viajante Aquatico" - description = "Nadar mais de 10 Quilometros (10,000 blocos)" +title = "Viajante Aquatico" +description = "Nadar mais de 10 Quilometros (10,000 blocos)" [advancement.challenge_fly_1k] - title = "Dancarino Celeste" - description = "Voar mais de 1 Quilometro (1,000 blocos)" +title = "Dancarino Celeste" +description = "Voar mais de 1 Quilometro (1,000 blocos)" [advancement.challenge_fly_10k] - title = "Cavaleiro do Vento" - description = "Voar mais de 10 Quilometros (10,000 blocos)" +title = "Cavaleiro do Vento" +description = "Voar mais de 10 Quilometros (10,000 blocos)" [advancement.challenge_agility_sneak_500] - title = "Passos Silenciosos" - description = "Agachar-se por mais de 500 blocos" +title = "Passos Silenciosos" +description = "Agachar-se por mais de 500 blocos" [advancement.challenge_agility_sneak_5k] - title = "Passos Fantasma" - description = "Agachar-se por mais de 5 Quilometros (5,000 blocos)" +title = "Passos Fantasma" +description = "Agachar-se por mais de 5 Quilometros (5,000 blocos)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Equipa de Demolicao" - description = "Destruir 500 blocos" +title = "Equipa de Demolicao" +description = "Destruir 500 blocos" [advancement.challenge_demolish_5k] - title = "Bola de Demolicao" - description = "Destruir 5,000 blocos" +title = "Bola de Demolicao" +description = "Destruir 5,000 blocos" [advancement.challenge_value_placed_10k] - title = "Construtor Valioso" - description = "Colocar blocos no valor de 10,000" +title = "Construtor Valioso" +description = "Colocar blocos no valor de 10,000" [advancement.challenge_value_placed_100k] - title = "Mestre Arquiteto" - description = "Colocar blocos no valor de 100,000" +title = "Mestre Arquiteto" +description = "Colocar blocos no valor de 100,000" [advancement.challenge_demolish_val_5k] - title = "Especialista em Salvamento" - description = "Recuperar 5,000 de valor em demolicao" +title = "Especialista em Salvamento" +description = "Recuperar 5,000 de valor em demolicao" [advancement.challenge_demolish_val_50k] - title = "Desconstrucao Total" - description = "Recuperar 50,000 de valor em demolicao" +title = "Desconstrucao Total" +description = "Recuperar 50,000 de valor em demolicao" [advancement.challenge_high_build_100] - title = "Construtor Celeste" - description = "Colocar 100 blocos acima de Y=128" +title = "Construtor Celeste" +description = "Colocar 100 blocos acima de Y=128" [advancement.challenge_high_build_1k] - title = "Arquiteto das Nuvens" - description = "Colocar 1,000 blocos acima de Y=128" +title = "Arquiteto das Nuvens" +description = "Colocar 1,000 blocos acima de Y=128" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Lenhador" - description = "Balancear o machado 500 vezes" +title = "Lenhador" +description = "Balancear o machado 500 vezes" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Balancear o machado 5,000 vezes" +title = "Berserker" +description = "Balancear o machado 5,000 vezes" [advancement.challenge_axe_damage_1k] - title = "Cortador" - description = "Causar 1,000 de dano com machados" +title = "Cortador" +description = "Causar 1,000 de dano com machados" [advancement.challenge_axe_damage_10k] - title = "Machado do Carrasco" - description = "Causar 10,000 de dano com machados" +title = "Machado do Carrasco" +description = "Causar 10,000 de dano com machados" [advancement.challenge_axe_value_5k] - title = "Comerciante de Madeira" - description = "Colher 5,000 de valor em madeira" +title = "Comerciante de Madeira" +description = "Colher 5,000 de valor em madeira" [advancement.challenge_axe_value_50k] - title = "Barao da Madeira" - description = "Colher 50,000 de valor em madeira" +title = "Barao da Madeira" +description = "Colher 50,000 de valor em madeira" [advancement.challenge_leaves_500] - title = "Soprador de Folhas" - description = "Limpar 500 blocos de folhas com um machado" +title = "Soprador de Folhas" +description = "Limpar 500 blocos de folhas com um machado" [advancement.challenge_leaves_5k] - title = "Desfolhador" - description = "Limpar 5,000 blocos de folhas com um machado" +title = "Desfolhador" +description = "Limpar 5,000 blocos de folhas com um machado" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Absorvedor de Dano" - description = "Bloquear 1,000 de dano com um escudo" +title = "Absorvedor de Dano" +description = "Bloquear 1,000 de dano com um escudo" [advancement.challenge_block_dmg_10k] - title = "Escudo Humano" - description = "Bloquear 10,000 de dano com um escudo" +title = "Escudo Humano" +description = "Bloquear 10,000 de dano com um escudo" [advancement.challenge_block_proj_100] - title = "Deflector de Flechas" - description = "Bloquear 100 projeteis com um escudo" +title = "Deflector de Flechas" +description = "Bloquear 100 projeteis com um escudo" [advancement.challenge_block_proj_1k] - title = "Escudo Anti-Projeteis" - description = "Bloquear 1,000 projeteis com um escudo" +title = "Escudo Anti-Projeteis" +description = "Bloquear 1,000 projeteis com um escudo" [advancement.challenge_block_melee_500] - title = "Mestre da Defesa" - description = "Bloquear 500 ataques corpo a corpo com um escudo" +title = "Mestre da Defesa" +description = "Bloquear 500 ataques corpo a corpo com um escudo" [advancement.challenge_block_melee_5k] - title = "Fortaleza de Ferro" - description = "Bloquear 5,000 ataques corpo a corpo com um escudo" +title = "Fortaleza de Ferro" +description = "Bloquear 5,000 ataques corpo a corpo com um escudo" [advancement.challenge_block_heavy_50] - title = "Tanque" - description = "Bloquear 50 ataques pesados (mais de 5 de dano)" +title = "Tanque" +description = "Bloquear 50 ataques pesados (mais de 5 de dano)" [advancement.challenge_block_heavy_500] - title = "Objeto Imovel" - description = "Bloquear 500 ataques pesados (mais de 5 de dano)" +title = "Objeto Imovel" +description = "Bloquear 500 ataques pesados (mais de 5 de dano)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "Preparacao do Alquimista" - description = "Colocar 10 suportes de pocoes" +title = "Preparacao do Alquimista" +description = "Colocar 10 suportes de pocoes" [advancement.challenge_brew_stands_50] - title = "Fabrica de Pocoes" - description = "Colocar 50 suportes de pocoes" +title = "Fabrica de Pocoes" +description = "Colocar 50 suportes de pocoes" [advancement.challenge_brew_strong_25] - title = "Pocao Forte" - description = "Consumir 25 pocoes melhoradas" +title = "Pocao Forte" +description = "Consumir 25 pocoes melhoradas" [advancement.challenge_brew_strong_250] - title = "Potencia Maxima" - description = "Consumir 250 pocoes melhoradas" +title = "Potencia Maxima" +description = "Consumir 250 pocoes melhoradas" [advancement.challenge_brew_splash_hits_50] - title = "Zona de Impacto" - description = "Acertar 50 entidades com pocoes de arremesso" +title = "Zona de Impacto" +description = "Acertar 50 entidades com pocoes de arremesso" [advancement.challenge_brew_splash_hits_500] - title = "Medico da Peste" - description = "Acertar 500 entidades com pocoes de arremesso" +title = "Medico da Peste" +description = "Acertar 500 entidades com pocoes de arremesso" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Inquieto" - description = "Viajar 1 Quilometro enquanto ativo" +title = "Inquieto" +description = "Viajar 1 Quilometro enquanto ativo" [advancement.challenge_active_dist_10k] - title = "Desbravador" - description = "Viajar 10 Quilometros enquanto ativo" +title = "Desbravador" +description = "Viajar 10 Quilometros enquanto ativo" [advancement.challenge_active_dist_100k] - title = "Movimento Perpetuo" - description = "Viajar 100 Quilometros enquanto ativo" +title = "Movimento Perpetuo" +description = "Viajar 100 Quilometros enquanto ativo" [advancement.challenge_beds_10] - title = "Madrugador" - description = "Dormir numa cama 10 vezes" +title = "Madrugador" +description = "Dormir numa cama 10 vezes" [advancement.challenge_beds_100] - title = "Saltador do Tempo" - description = "Dormir numa cama 100 vezes" +title = "Saltador do Tempo" +description = "Dormir numa cama 100 vezes" [advancement.challenge_chronos_tp_50] - title = "Mudanca Temporal" - description = "Teletransportar-se 50 vezes" +title = "Mudanca Temporal" +description = "Teletransportar-se 50 vezes" [advancement.challenge_chronos_tp_500] - title = "Distorcao Temporal" - description = "Teletransportar-se 500 vezes" +title = "Distorcao Temporal" +description = "Teletransportar-se 500 vezes" [advancement.challenge_chronos_deaths_10] - title = "Mortal" - description = "Morrer 10 vezes" +title = "Mortal" +description = "Morrer 10 vezes" [advancement.challenge_chronos_deaths_100] - title = "Desafiador da Morte" - description = "Morrer 100 vezes" +title = "Desafiador da Morte" +description = "Morrer 100 vezes" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Valor Artesanal" - description = "Fabricar itens no valor total de 10,000" +title = "Valor Artesanal" +description = "Fabricar itens no valor total de 10,000" [advancement.challenge_craft_value_100k] - title = "Artesao" - description = "Fabricar itens no valor total de 100,000" +title = "Artesao" +description = "Fabricar itens no valor total de 100,000" [advancement.challenge_craft_tools_25] - title = "Ferreiro de Ferramentas" - description = "Fabricar 25 ferramentas" +title = "Ferreiro de Ferramentas" +description = "Fabricar 25 ferramentas" [advancement.challenge_craft_tools_250] - title = "Mestre Forjador" - description = "Fabricar 250 ferramentas" +title = "Mestre Forjador" +description = "Fabricar 250 ferramentas" [advancement.challenge_craft_armor_25] - title = "Ferreiro de Armaduras" - description = "Fabricar 25 pecas de armadura" +title = "Ferreiro de Armaduras" +description = "Fabricar 25 pecas de armadura" [advancement.challenge_craft_armor_250] - title = "Mestre Armeiro" - description = "Fabricar 250 pecas de armadura" +title = "Mestre Armeiro" +description = "Fabricar 250 pecas de armadura" [advancement.challenge_craft_mass_25k] - title = "Producao em Massa" - description = "Fabricar 25,000 itens" +title = "Producao em Massa" +description = "Fabricar 25,000 itens" [advancement.challenge_craft_mass_250k] - title = "Revolucao Industrial" - description = "Fabricar 250,000 itens" +title = "Revolucao Industrial" +description = "Fabricar 250,000 itens" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Colecionador" - description = "Descobrir 50 itens unicos" +title = "Colecionador" +description = "Descobrir 50 itens unicos" [advancement.challenge_discover_items_250] - title = "Catalogador" - description = "Descobrir 250 itens unicos" +title = "Catalogador" +description = "Descobrir 250 itens unicos" [advancement.challenge_discover_blocks_50] - title = "Topografo" - description = "Descobrir 50 blocos unicos" +title = "Topografo" +description = "Descobrir 50 blocos unicos" [advancement.challenge_discover_blocks_250] - title = "Geologo" - description = "Descobrir 250 blocos unicos" +title = "Geologo" +description = "Descobrir 250 blocos unicos" [advancement.challenge_discover_mobs_25] - title = "Observador" - description = "Descobrir 25 mobs unicos" +title = "Observador" +description = "Descobrir 25 mobs unicos" [advancement.challenge_discover_mobs_75] - title = "Naturalista" - description = "Descobrir 75 mobs unicos" +title = "Naturalista" +description = "Descobrir 75 mobs unicos" [advancement.challenge_discover_biomes_10] - title = "Andarilho" - description = "Descobrir 10 biomas unicos" +title = "Andarilho" +description = "Descobrir 10 biomas unicos" [advancement.challenge_discover_biomes_40] - title = "Viajante do Mundo" - description = "Descobrir 40 biomas unicos" +title = "Viajante do Mundo" +description = "Descobrir 40 biomas unicos" [advancement.challenge_discover_foods_10] - title = "Gastronomista" - description = "Descobrir 10 alimentos unicos" +title = "Gastronomista" +description = "Descobrir 10 alimentos unicos" [advancement.challenge_discover_foods_30] - title = "Mestre Culinario" - description = "Descobrir 30 alimentos unicos" +title = "Mestre Culinario" +description = "Descobrir 30 alimentos unicos" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Tecelao de Poder" - description = "Acumular 100 de poder de encantamento" +title = "Tecelao de Poder" +description = "Acumular 100 de poder de encantamento" [advancement.challenge_enchant_power_1k] - title = "Mestre Arcano" - description = "Acumular 1,000 de poder de encantamento" +title = "Mestre Arcano" +description = "Acumular 1,000 de poder de encantamento" [advancement.challenge_enchant_levels_1k] - title = "Gastador de Niveis" - description = "Gastar 1,000 niveis de experiencia em encantamentos" +title = "Gastador de Niveis" +description = "Gastar 1,000 niveis de experiencia em encantamentos" [advancement.challenge_enchant_levels_10k] - title = "Poco de Experiencia" - description = "Gastar 10,000 niveis de experiencia em encantamentos" +title = "Poco de Experiencia" +description = "Gastar 10,000 niveis de experiencia em encantamentos" [advancement.challenge_enchant_high_25] - title = "Alto Risco" - description = "Realizar 25 encantamentos de nivel maximo" +title = "Alto Risco" +description = "Realizar 25 encantamentos de nivel maximo" [advancement.challenge_enchant_high_250] - title = "Encantador Lendario" - description = "Realizar 250 encantamentos de nivel maximo" +title = "Encantador Lendario" +description = "Realizar 250 encantamentos de nivel maximo" [advancement.challenge_enchant_total_500] - title = "Queimador de Niveis" - description = "Gastar 500 niveis totais em todos os encantamentos" +title = "Queimador de Niveis" +description = "Gastar 500 niveis totais em todos os encantamentos" [advancement.challenge_enchant_total_5k] - title = "Investimento Arcano" - description = "Gastar 5,000 niveis totais em todos os encantamentos" +title = "Investimento Arcano" +description = "Gastar 5,000 niveis totais em todos os encantamentos" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Escavador" - description = "Balancear a pa 500 vezes" +title = "Escavador" +description = "Balancear a pa 500 vezes" [advancement.challenge_dig_swing_5k] - title = "Escavadeira" - description = "Balancear a pa 5,000 vezes" +title = "Escavadeira" +description = "Balancear a pa 5,000 vezes" [advancement.challenge_dig_damage_1k] - title = "Cavaleiro da Pa" - description = "Causar 1,000 de dano com uma pa" +title = "Cavaleiro da Pa" +description = "Causar 1,000 de dano com uma pa" [advancement.challenge_dig_damage_10k] - title = "Mestre da Pa" - description = "Causar 10,000 de dano com uma pa" +title = "Mestre da Pa" +description = "Causar 10,000 de dano com uma pa" [advancement.challenge_dig_value_5k] - title = "Comerciante de Terra" - description = "Escavar 5,000 de valor em blocos" +title = "Comerciante de Terra" +description = "Escavar 5,000 de valor em blocos" [advancement.challenge_dig_value_50k] - title = "Barao da Terra" - description = "Escavar 50,000 de valor em blocos" +title = "Barao da Terra" +description = "Escavar 50,000 de valor em blocos" [advancement.challenge_dig_gravel_500] - title = "Triturador de Cascalho" - description = "Cavar 500 blocos de cascalho, areia ou argila" +title = "Triturador de Cascalho" +description = "Cavar 500 blocos de cascalho, areia ou argila" [advancement.challenge_dig_gravel_5k] - title = "Peneirador de Areia" - description = "Cavar 5,000 blocos de cascalho, areia ou argila" +title = "Peneirador de Areia" +description = "Cavar 5,000 blocos de cascalho, areia ou argila" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Semeador" - description = "Plantar 100 cultivos" +title = "Semeador" +description = "Plantar 100 cultivos" [advancement.challenge_plant_1k] - title = "Mao Verde" - description = "Plantar 1,000 cultivos" +title = "Mao Verde" +description = "Plantar 1,000 cultivos" [advancement.challenge_plant_5k] - title = "Barao Agricola" - description = "Plantar 5,000 cultivos" +title = "Barao Agricola" +description = "Plantar 5,000 cultivos" [advancement.challenge_compost_50] - title = "Reciclador" - description = "Compostar 50 itens" +title = "Reciclador" +description = "Compostar 50 itens" [advancement.challenge_compost_500] - title = "Enriquecedor de Solo" - description = "Compostar 500 itens" +title = "Enriquecedor de Solo" +description = "Compostar 500 itens" [advancement.challenge_shear_50] - title = "Tosquiador" - description = "Tosquiar 50 entidades" +title = "Tosquiador" +description = "Tosquiar 50 entidades" [advancement.challenge_shear_250] - title = "Mestre do Rebanho" - description = "Tosquiar 250 entidades" +title = "Mestre do Rebanho" +description = "Tosquiar 250 entidades" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Cacador" - description = "Abater 500 criaturas" +title = "Cacador" +description = "Abater 500 criaturas" [advancement.challenge_kills_5k] - title = "Carrasco" - description = "Abater 5,000 criaturas" +title = "Carrasco" +description = "Abater 5,000 criaturas" [advancement.challenge_boss_1] - title = "Desafiador de Chefes" - description = "Abater um mob chefe" +title = "Desafiador de Chefes" +description = "Abater um mob chefe" [advancement.challenge_boss_10] - title = "Matador de Lendas" - description = "Abater 10 mobs chefes" +title = "Matador de Lendas" +description = "Abater 10 mobs chefes" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Murcho" - description = "Suportar 500 de dano de wither" +title = "Murcho" +description = "Suportar 500 de dano de wither" [advancement.challenge_wither_dmg_5k] - title = "Sobrevivente da Praga" - description = "Suportar 5,000 de dano de wither" +title = "Sobrevivente da Praga" +description = "Suportar 5,000 de dano de wither" [advancement.challenge_wither_skel_25] - title = "Colecionador de Ossos" - description = "Abater 25 wither skeletons" +title = "Colecionador de Ossos" +description = "Abater 25 wither skeletons" [advancement.challenge_wither_skel_250] - title = "Flagelo dos Esqueletos" - description = "Abater 250 wither skeletons" +title = "Flagelo dos Esqueletos" +description = "Abater 250 wither skeletons" [advancement.challenge_wither_boss_1] - title = "Destruidor do Wither" - description = "Derrotar o Wither" +title = "Destruidor do Wither" +description = "Derrotar o Wither" [advancement.challenge_wither_boss_10] - title = "Dominador do Nether" - description = "Derrotar o Wither 10 vezes" +title = "Dominador do Nether" +description = "Derrotar o Wither 10 vezes" [advancement.challenge_roses_10] - title = "Jardineiro da Morte" - description = "Partir 10 rosas do wither" +title = "Jardineiro da Morte" +description = "Partir 10 rosas do wither" [advancement.challenge_roses_100] - title = "Colecionador da Praga" - description = "Partir 100 rosas do wither" +title = "Colecionador da Praga" +description = "Partir 100 rosas do wither" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Braco de Mineiro" - description = "Balancear a picareta 500 vezes" +title = "Braco de Mineiro" +description = "Balancear a picareta 500 vezes" [advancement.challenge_pick_swing_5k] - title = "Abridor de Tuneis" - description = "Balancear a picareta 5,000 vezes" +title = "Abridor de Tuneis" +description = "Balancear a picareta 5,000 vezes" [advancement.challenge_pick_damage_1k] - title = "Lutador de Picareta" - description = "Causar 1,000 de dano com uma picareta" +title = "Lutador de Picareta" +description = "Causar 1,000 de dano com uma picareta" [advancement.challenge_pick_damage_10k] - title = "Picareta de Guerra" - description = "Causar 10,000 de dano com uma picareta" +title = "Picareta de Guerra" +description = "Causar 10,000 de dano com uma picareta" [advancement.challenge_pick_value_5k] - title = "Descobridor de Gemas" - description = "Minerar 5,000 de valor em blocos" +title = "Descobridor de Gemas" +description = "Minerar 5,000 de valor em blocos" [advancement.challenge_pick_value_50k] - title = "Barao dos Minerios" - description = "Minerar 50,000 de valor em blocos" +title = "Barao dos Minerios" +description = "Minerar 50,000 de valor em blocos" [advancement.challenge_pick_ores_500] - title = "Garimpeiro" - description = "Minerar 500 blocos de minerio" +title = "Garimpeiro" +description = "Minerar 500 blocos de minerio" [advancement.challenge_pick_ores_5k] - title = "Mestre Mineiro" - description = "Minerar 5,000 blocos de minerio" +title = "Mestre Mineiro" +description = "Minerar 5,000 blocos de minerio" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Atirador Certeiro" - description = "Causar 1,000 de dano a distancia" +title = "Atirador Certeiro" +description = "Causar 1,000 de dano a distancia" [advancement.challenge_ranged_dmg_10k] - title = "Arqueiro Letal" - description = "Causar 10,000 de dano a distancia" +title = "Arqueiro Letal" +description = "Causar 10,000 de dano a distancia" [advancement.challenge_ranged_dist_5k] - title = "Longo Alcance" - description = "Disparar projeteis cobrindo 5,000 blocos de distancia total" +title = "Longo Alcance" +description = "Disparar projeteis cobrindo 5,000 blocos de distancia total" [advancement.challenge_ranged_dist_50k] - title = "Atirador Quilometrico" - description = "Disparar projeteis cobrindo 50,000 blocos de distancia total" +title = "Atirador Quilometrico" +description = "Disparar projeteis cobrindo 50,000 blocos de distancia total" [advancement.challenge_ranged_kills_50] - title = "Arqueiro" - description = "Matar 50 mobs com armas de longo alcance" +title = "Arqueiro" +description = "Matar 50 mobs com armas de longo alcance" [advancement.challenge_ranged_kills_500] - title = "Mestre Arqueiro" - description = "Matar 500 mobs com armas de longo alcance" +title = "Mestre Arqueiro" +description = "Matar 500 mobs com armas de longo alcance" [advancement.challenge_longshot_25] - title = "Franco-atirador" - description = "Acertar 25 tiros de longa distancia (mais de 30 blocos)" +title = "Franco-atirador" +description = "Acertar 25 tiros de longa distancia (mais de 30 blocos)" [advancement.challenge_longshot_250] - title = "Olho de Aguia" - description = "Acertar 250 tiros de longa distancia (mais de 30 blocos)" +title = "Olho de Aguia" +description = "Acertar 250 tiros de longa distancia (mais de 30 blocos)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "Lancador de Perolas" - description = "Lancar 50 perolas do ender" +title = "Lancador de Perolas" +description = "Lancar 50 perolas do ender" [advancement.challenge_rift_pearls_500] - title = "Viciado em Teletransporte" - description = "Lancar 500 perolas do ender" +title = "Viciado em Teletransporte" +description = "Lancar 500 perolas do ender" [advancement.challenge_rift_enderman_50] - title = "Cacador de Endermen" - description = "Abater 50 endermen" +title = "Cacador de Endermen" +description = "Abater 50 endermen" [advancement.challenge_rift_enderman_500] - title = "Perseguidor do Vazio" - description = "Abater 500 endermen" +title = "Perseguidor do Vazio" +description = "Abater 500 endermen" [advancement.challenge_rift_dragon_500] - title = "Lutador de Dragoes" - description = "Causar 500 de dano ao Ender Dragon" +title = "Lutador de Dragoes" +description = "Causar 500 de dano ao Ender Dragon" [advancement.challenge_rift_dragon_5k] - title = "Flagelo dos Dragoes" - description = "Causar 5,000 de dano ao Ender Dragon" +title = "Flagelo dos Dragoes" +description = "Causar 5,000 de dano ao Ender Dragon" [advancement.challenge_rift_crystal_10] - title = "Quebrador de Cristais" - description = "Destruir 10 cristais do end" +title = "Quebrador de Cristais" +description = "Destruir 10 cristais do end" [advancement.challenge_rift_crystal_100] - title = "Demolidor do End" - description = "Destruir 100 cristais do end" +title = "Demolidor do End" +description = "Destruir 100 cristais do end" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Pescador" - description = "Pescar 25 peixes" +title = "Pescador" +description = "Pescar 25 peixes" [advancement.challenge_fish_250] - title = "Mestre Pescador" - description = "Pescar 250 peixes" +title = "Mestre Pescador" +description = "Pescar 250 peixes" [advancement.challenge_drowned_25] - title = "Cacador de Afogados" - description = "Abater 25 afogados" +title = "Cacador de Afogados" +description = "Abater 25 afogados" [advancement.challenge_drowned_250] - title = "Purificador do Oceano" - description = "Abater 250 afogados" +title = "Purificador do Oceano" +description = "Abater 250 afogados" [advancement.challenge_guardian_10] - title = "Destruidor de Guardioes" - description = "Abater 10 guardioes" +title = "Destruidor de Guardioes" +description = "Abater 10 guardioes" [advancement.challenge_guardian_100] - title = "Saqueador de Templos" - description = "Abater 100 guardioes" +title = "Saqueador de Templos" +description = "Abater 100 guardioes" [advancement.challenge_underwater_blocks_100] - title = "Mineiro Submarino" - description = "Quebrar 100 blocos debaixo de agua" +title = "Mineiro Submarino" +description = "Quebrar 100 blocos debaixo de agua" [advancement.challenge_underwater_blocks_1k] - title = "Engenheiro Aquatico" - description = "Quebrar 1,000 blocos debaixo de agua" +title = "Engenheiro Aquatico" +description = "Quebrar 1,000 blocos debaixo de agua" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Punhalada nas Costas" - description = "Causar 500 de dano enquanto agachado" +title = "Punhalada nas Costas" +description = "Causar 500 de dano enquanto agachado" [advancement.challenge_stealth_dmg_5k] - title = "Assassino Silencioso" - description = "Causar 5,000 de dano enquanto agachado" +title = "Assassino Silencioso" +description = "Causar 5,000 de dano enquanto agachado" [advancement.challenge_stealth_kills_10] - title = "Assassino" - description = "Matar 10 mobs enquanto agachado" +title = "Assassino" +description = "Matar 10 mobs enquanto agachado" [advancement.challenge_stealth_kills_100] - title = "Ceifador das Sombras" - description = "Matar 100 mobs enquanto agachado" +title = "Ceifador das Sombras" +description = "Matar 100 mobs enquanto agachado" [advancement.challenge_stealth_time_1h] - title = "Paciente" - description = "Passar 1 hora agachado (3,600 segundos)" +title = "Paciente" +description = "Passar 1 hora agachado (3,600 segundos)" [advancement.challenge_stealth_time_10h] - title = "Mestre das Sombras" - description = "Passar 10 horas agachado (36,000 segundos)" +title = "Mestre das Sombras" +description = "Passar 10 horas agachado (36,000 segundos)" [advancement.challenge_stealth_arrows_50] - title = "Arqueiro Silencioso" - description = "Disparar 50 flechas enquanto agachado" +title = "Arqueiro Silencioso" +description = "Disparar 50 flechas enquanto agachado" [advancement.challenge_stealth_arrows_500] - title = "Arqueiro Fantasma" - description = "Disparar 500 flechas enquanto agachado" +title = "Arqueiro Fantasma" +description = "Disparar 500 flechas enquanto agachado" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Aprendiz de Espada" - description = "Causar 1,000 de dano com espadas" +title = "Aprendiz de Espada" +description = "Causar 1,000 de dano com espadas" [advancement.challenge_sword_dmg_10k] - title = "Espadachim" - description = "Causar 10,000 de dano com espadas" +title = "Espadachim" +description = "Causar 10,000 de dano com espadas" [advancement.challenge_sword_kills_50] - title = "Duelista" - description = "Matar 50 mobs com espadas" +title = "Duelista" +description = "Matar 50 mobs com espadas" [advancement.challenge_sword_kills_500] - title = "Gladiador" - description = "Matar 500 mobs com espadas" +title = "Gladiador" +description = "Matar 500 mobs com espadas" [advancement.challenge_sword_crit_50] - title = "Golpe Critico" - description = "Acertar 50 golpes criticos com espadas" +title = "Golpe Critico" +description = "Acertar 50 golpes criticos com espadas" [advancement.challenge_sword_crit_500] - title = "Mestre da Precisao" - description = "Acertar 500 golpes criticos com espadas" +title = "Mestre da Precisao" +description = "Acertar 500 golpes criticos com espadas" [advancement.challenge_sword_heavy_25] - title = "Golpe Pesado" - description = "Acertar 25 golpes pesados com espadas (mais de 8 de dano)" +title = "Golpe Pesado" +description = "Acertar 25 golpes pesados com espadas (mais de 8 de dano)" [advancement.challenge_sword_heavy_250] - title = "Golpe Devastador" - description = "Acertar 250 golpes pesados com espadas (mais de 8 de dano)" +title = "Golpe Devastador" +description = "Acertar 250 golpes pesados com espadas (mais de 8 de dano)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Treinador de Feras" - description = "Os seus animais causam 500 de dano total" +title = "Treinador de Feras" +description = "Os seus animais causam 500 de dano total" [advancement.challenge_pet_dmg_5k] - title = "Mestre de Guerra" - description = "Os seus animais causam 5,000 de dano total" +title = "Mestre de Guerra" +description = "Os seus animais causam 5,000 de dano total" [advancement.challenge_tamed_10] - title = "Amigo dos Animais" - description = "Domesticar 10 animais" +title = "Amigo dos Animais" +description = "Domesticar 10 animais" [advancement.challenge_tamed_100] - title = "Tratador do Zoo" - description = "Domesticar 100 animais" +title = "Tratador do Zoo" +description = "Domesticar 100 animais" [advancement.challenge_pet_kills_25] - title = "Taticas de Matilha" - description = "Os seus animais abatem 25 criaturas" +title = "Taticas de Matilha" +description = "Os seus animais abatem 25 criaturas" [advancement.challenge_pet_kills_250] - title = "Comandante Alfa" - description = "Os seus animais abatem 250 criaturas" +title = "Comandante Alfa" +description = "Os seus animais abatem 250 criaturas" [advancement.challenge_taming_2500] - title = "Especialista em Criacao" - description = "Criar 2,500 animais" +title = "Especialista em Criacao" +description = "Criar 2,500 animais" [advancement.challenge_taming_25k] - title = "Mestre Geneticista" - description = "Criar 25,000 animais" +title = "Mestre Geneticista" +description = "Criar 25,000 animais" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Saco de Pancadas" - description = "Receber 500 golpes" +title = "Saco de Pancadas" +description = "Receber 500 golpes" [advancement.challenge_trag_hits_5k] - title = "Glutao por Castigo" - description = "Receber 5,000 golpes" +title = "Glutao por Castigo" +description = "Receber 5,000 golpes" [advancement.challenge_trag_deaths_10] - title = "Sete Vidas" - description = "Morrer 10 vezes" +title = "Sete Vidas" +description = "Morrer 10 vezes" [advancement.challenge_trag_deaths_100] - title = "Pesadelo Recorrente" - description = "Morrer 100 vezes" +title = "Pesadelo Recorrente" +description = "Morrer 100 vezes" [advancement.challenge_trag_fire_500] - title = "Vitima das Chamas" - description = "Suportar 500 de dano de fogo" +title = "Vitima das Chamas" +description = "Suportar 500 de dano de fogo" [advancement.challenge_trag_fire_5k] - title = "Fenix" - description = "Suportar 5,000 de dano de fogo" +title = "Fenix" +description = "Suportar 5,000 de dano de fogo" [advancement.challenge_trag_fall_500] - title = "Teste de Gravidade" - description = "Suportar 500 de dano de queda" +title = "Teste de Gravidade" +description = "Suportar 500 de dano de queda" [advancement.challenge_trag_fall_5k] - title = "Velocidade Terminal" - description = "Suportar 5,000 de dano de queda" +title = "Velocidade Terminal" +description = "Suportar 5,000 de dano de queda" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Brigao" - description = "Causar 1,000 de dano com os punhos" +title = "Brigao" +description = "Causar 1,000 de dano com os punhos" [advancement.challenge_unarmed_dmg_10k] - title = "Artista Marcial" - description = "Causar 10,000 de dano com os punhos" +title = "Artista Marcial" +description = "Causar 10,000 de dano com os punhos" [advancement.challenge_unarmed_kills_25] - title = "Punhos de Ferro" - description = "Matar 25 mobs com os punhos" +title = "Punhos de Ferro" +description = "Matar 25 mobs com os punhos" [advancement.challenge_unarmed_kills_250] - title = "Punho Lendario" - description = "Matar 250 mobs com os punhos" +title = "Punho Lendario" +description = "Matar 250 mobs com os punhos" [advancement.challenge_unarmed_crit_25] - title = "Soco Critico" - description = "Acertar 25 golpes criticos com os punhos" +title = "Soco Critico" +description = "Acertar 25 golpes criticos com os punhos" [advancement.challenge_unarmed_crit_250] - title = "Punho Preciso" - description = "Acertar 250 golpes criticos com os punhos" +title = "Punho Preciso" +description = "Acertar 250 golpes criticos com os punhos" [advancement.challenge_unarmed_heavy_25] - title = "Soco Potente" - description = "Acertar 25 golpes pesados com os punhos (mais de 6 de dano)" +title = "Soco Potente" +description = "Acertar 25 golpes pesados com os punhos (mais de 6 de dano)" [advancement.challenge_unarmed_heavy_250] - title = "Rei do Nocaute" - description = "Acertar 250 golpes pesados com os punhos (mais de 6 de dano)" +title = "Rei do Nocaute" +description = "Acertar 250 golpes pesados com os punhos (mais de 6 de dano)" # items [items] [items.bound_ender_peral] - name = "Chave Portal do Relicario" - usage1 = "Shift + Clique Esquerdo para vincular" - usage2 = "Clique Direito para aceder ao Inventario vinculado" +name = "Chave Portal do Relicario" +usage1 = "Shift + Clique Esquerdo para vincular" +usage2 = "Clique Direito para aceder ao Inventario vinculado" [items.bound_eye_of_ender] - name = "Ancora Ocular" - usage1 = "Clique Direito para consumir e teletransportar para o local vinculado" - usage2 = "Shift + Clique Esquerdo para vincular a um bloco" +name = "Ancora Ocular" +usage1 = "Clique Direito para consumir e teletransportar para o local vinculado" +usage2 = "Shift + Clique Esquerdo para vincular a um bloco" [items.bound_redstone_torch] - name = "Controlo Remoto de Redstone" - usage1 = "Clique Direito para criar um pulso Redstone de 1 Tick" - usage2 = "Shift + Clique Esquerdo num bloco 'Alvo' para vincular" +name = "Controlo Remoto de Redstone" +usage1 = "Clique Direito para criar um pulso Redstone de 1 Tick" +usage2 = "Shift + Clique Esquerdo num bloco 'Alvo' para vincular" [items.bound_snowball] - name = "Armadilha de Teia!" - usage1 = "Atire para criar uma armadilha de teia temporaria no local" +name = "Armadilha de Teia!" +usage1 = "Atire para criar uma armadilha de teia temporaria no local" [items.chrono_time_bottle] - name = "Tempo Numa Garrafa" - usage1 = "Armazena tempo passivamente enquanto esta no seu inventario" - usage2 = "Clique direito em blocos temporais ou animais bebes para gastar tempo armazenado" - stored = "Tempo Armazenado" +name = "Tempo Numa Garrafa" +usage1 = "Armazena tempo passivamente enquanto esta no seu inventario" +usage2 = "Clique direito em blocos temporais ou animais bebes para gastar tempo armazenado" +stored = "Tempo Armazenado" [items.chrono_time_bomb] - name = "Bomba Temporal" - usage1 = "Clique direito para lancar um projetil crono que cria um campo temporal" +name = "Bomba Temporal" +usage1 = "Clique direito para lancar um projetil crono que cria um campo temporal" [items.elevator_block] - name = "Bloco Elevador" - usage1 = "Salte para teletransportar para cima" - usage2 = "Agache para teletransportar para baixo" - usage3 = "Minimo de 2 blocos de ar entre os elevadores" +name = "Bloco Elevador" +usage1 = "Salte para teletransportar para cima" +usage2 = "Agache para teletransportar para baixo" +usage3 = "Minimo de 2 blocos de ar entre os elevadores" # snippets [snippets] [snippets.gui] - level = "Nivel" - knowledge = "conhecimento" - power_used = "Energia Utilizada" - not_learned = "Nao Aprendido" - xp = "XP para" - welcome = "Bem-vindo!" - welcome_back = "Bem-vindo de volta!" - xp_bonus_for_time = "XP para" - max_ability_power = "Poder Maximo de Habilidade" - unlock_this_by_clicking = "Desbloqueie isto com Clique Direito: " - back = "Voltar" - unlearn_all = "Desaprender tudo" - unlearned_all = "Tudo desaprendido" +level = "Nivel" +knowledge = "conhecimento" +power_used = "Energia Utilizada" +not_learned = "Nao Aprendido" +xp = "XP para" +welcome = "Bem-vindo!" +welcome_back = "Bem-vindo de volta!" +xp_bonus_for_time = "XP para" +max_ability_power = "Poder Maximo de Habilidade" +unlock_this_by_clicking = "Desbloqueie isto com Clique Direito: " +back = "Voltar" +unlearn_all = "Desaprender tudo" +unlearned_all = "Tudo desaprendido" [snippets.adapt_menu] - may_not_unlearn = "NAO PODE DESAPRENDER" - may_unlearn = "PODE APRENDER/DESAPRENDER" - knowledge_cost = "Custo de Conhecimento" - knowledge_available = "Conhecimento Disponivel" - already_learned = "Ja Aprendido" - unlearn_refund = "Clique para Desaprender e Reembolsar" - no_refunds = "HARDCORE, REEMBOLSOS DESATIVADOS" - knowledge = "conhecimento" - click_learn = "Clique para Aprender" - no_knowledge = "(Nao tens nenhum Conhecimento)" - you_only_have = "So tens" - how_to_level_up = "Sobe o nivel das habilidades para aumentar o teu poder maximo." - not_enough_power = "Poder insuficiente! Cada Nivel de Habilidade custa 1 poder." - power = "poder" - power_drain = "Consumo de Poder" - learned = "Aprendido " - unlearned = "Desaprendido " - activator_block = "Estante de Livros" +may_not_unlearn = "NAO PODE DESAPRENDER" +may_unlearn = "PODE APRENDER/DESAPRENDER" +knowledge_cost = "Custo de Conhecimento" +knowledge_available = "Conhecimento Disponivel" +already_learned = "Ja Aprendido" +unlearn_refund = "Clique para Desaprender e Reembolsar" +no_refunds = "HARDCORE, REEMBOLSOS DESATIVADOS" +knowledge = "conhecimento" +click_learn = "Clique para Aprender" +no_knowledge = "(Nao tens nenhum Conhecimento)" +you_only_have = "So tens" +how_to_level_up = "Sobe o nivel das habilidades para aumentar o teu poder maximo." +not_enough_power = "Poder insuficiente! Cada Nivel de Habilidade custa 1 poder." +power = "poder" +power_drain = "Consumo de Poder" +learned = "Aprendido " +unlearned = "Desaprendido " +activator_block = "Estante de Livros" [snippets.knowledge_orb] - contains = "contem" - knowledge = "conhecimento" - rightclick = "Clique Direito" - togainknowledge = "para obter este conhecimento" - knowledge_orb = "Orbe de Conhecimento" +contains = "contem" +knowledge = "conhecimento" +rightclick = "Clique Direito" +togainknowledge = "para obter este conhecimento" +knowledge_orb = "Orbe de Conhecimento" [snippets.experience_orb] - contains = "contem" - xp = "Experiencia" - rightclick = "Clique Direito" - togainxp = "para ganhar esta experiencia" - xporb = "Orbe de Experiencia" +contains = "contem" +xp = "Experiencia" +rightclick = "Clique Direito" +togainxp = "para ganhar esta experiencia" +xporb = "Orbe de Experiencia" # skill [skill] [skill.agility] - name = "Agilidade" - icon = "⇉" - description = "Agilidade e a capacidade de se mover rapida e fluidamente perante obstaculos." +name = "Agilidade" +icon = "⇉" +description = "Agilidade e a capacidade de se mover rapida e fluidamente perante obstaculos." [skill.architect] - name = "Arquiteto" - icon = "⬧" - description = "As estruturas sao os blocos de construcao do mundo. A realidade esta nas tuas maos, tua para controlar." +name = "Arquiteto" +icon = "⬧" +description = "As estruturas sao os blocos de construcao do mundo. A realidade esta nas tuas maos, tua para controlar." [skill.axes] - name = "Machados" - icon = "🪓" - description1 = "Porque cortar arvores, quando podes cortar " - description2 = "coisas" - description3 = "em vez disso, o mesmo resultado final!" +name = "Machados" +icon = "🪓" +description1 = "Porque cortar arvores, quando podes cortar " +description2 = "coisas" +description3 = "em vez disso, o mesmo resultado final!" [skill.brewing] - name = "Alquimia" - icon = "❦" - description = "Bolha Dupla, Bolha Tripla, Bolha Quadrupla - ainda nao consigo colocar esta pocao num caldeirao" +name = "Alquimia" +icon = "❦" +description = "Bolha Dupla, Bolha Tripla, Bolha Quadrupla - ainda nao consigo colocar esta pocao num caldeirao" [skill.blocking] - name = "Bloqueio" - icon = "🛡" - description = "Paus e pedras nao vao partir os teus ossos, mas um escudo sim." +name = "Bloqueio" +icon = "🛡" +description = "Paus e pedras nao vao partir os teus ossos, mas um escudo sim." [skill.crafting] - name = "Fabrico" - icon = "⌂" - description = "Sem mais pecas para colocar, porque nao fazer outra?" +name = "Fabrico" +icon = "⌂" +description = "Sem mais pecas para colocar, porque nao fazer outra?" [skill.discovery] - name = "Descoberta" - icon = "⚛" - description = "A medida que a tua percepcao se expande, a tua mente desvenda aquilo que nao conhecias." +name = "Descoberta" +icon = "⚛" +description = "A medida que a tua percepcao se expande, a tua mente desvenda aquilo que nao conhecias." [skill.enchanting] - name = "Encantamento" - icon = "✠" - description = "De que estas a falar? Profecias, visoes, tretas supersticiosas?" +name = "Encantamento" +icon = "✠" +description = "De que estas a falar? Profecias, visoes, tretas supersticiosas?" [skill.excavation] - name = "Escavacao" - icon = "ᛳ" - description = "Cava, cava o buraco..." +name = "Escavacao" +icon = "ᛳ" +description = "Cava, cava o buraco..." [skill.herbalism] - name = "Herbalismo" - icon = "⚘" - description = "Nao encontro plantas nenhumas, mas encontro sementes e - isso e... Erva?" +name = "Herbalismo" +icon = "⚘" +description = "Nao encontro plantas nenhumas, mas encontro sementes e - isso e... Erva?" [skill.hunter] - name = "Cacador" - icon = "☠" - description = "A caca e sobre a jornada, nao o resultado." +name = "Cacador" +icon = "☠" +description = "A caca e sobre a jornada, nao o resultado." [skill.nether] - name = "Nether" - icon = "₪" - description = "Das profundezas do proprio Nether." +name = "Nether" +icon = "₪" +description = "Das profundezas do proprio Nether." [skill.pickaxe] - name = "Picareta" - icon = "⛏" - description = "Os anoes sao os mineiros, mas aprendi uma coisa ou duas no meu tempo. SOU SUECO" +name = "Picareta" +icon = "⛏" +description = "Os anoes sao os mineiros, mas aprendi uma coisa ou duas no meu tempo. SOU SUECO" [skill.ranged] - name = "Distancia" - icon = "🏹" - description = "A distancia e a chave para a vitoria, e a chave para a sobrevivencia." +name = "Distancia" +icon = "🏹" +description = "A distancia e a chave para a vitoria, e a chave para a sobrevivencia." [skill.rift] - name = "Fenda" - icon = "❍" - description = "A Fenda e um arnes caustico, mas tu dominaste o arnes." +name = "Fenda" +icon = "❍" +description = "A Fenda e um arnes caustico, mas tu dominaste o arnes." [skill.seaborne] - name = "Maritimo" - icon = "🎣" - description = "Com esta habilidade, podes dominar as maravilhas da agua." +name = "Maritimo" +icon = "🎣" +description = "Com esta habilidade, podes dominar as maravilhas da agua." [skill.stealth] - name = "Furtividade" - icon = "☯" - description = "A arte do invisivel. Caminha nas sombras." +name = "Furtividade" +icon = "☯" +description = "A arte do invisivel. Caminha nas sombras." [skill.swords] - name = "Espadas" - icon = "⚔" - description = "Pelo poder de GreyStone!" +name = "Espadas" +icon = "⚔" +description = "Pelo poder de GreyStone!" [skill.taming] - name = "Domesticacao" - icon = "♥" - description = "Os papagaios e as abelhas... e tu?" +name = "Domesticacao" +icon = "♥" +description = "Os papagaios e as abelhas... e tu?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "O sangue flui pelas veias do universo. Constrangido pelas tuas maos." +name = "TragOul" +icon = "🗡" +description = "O sangue flui pelas veias do universo. Constrangido pelas tuas maos." [skill.chronos] - name = "Cronos" - icon = "🕒" - description = "Da corda ao Relogio do universo, experimenta o fluxo. Parte o relogio, torna-te nele." +name = "Cronos" +icon = "🕒" +description = "Da corda ao Relogio do universo, experimenta o fluxo. Parte o relogio, torna-te nele." [skill.unarmed] - name = "Desarmado" - icon = "»" - description = "Sem arma nao significa sem forca." +name = "Desarmado" +icon = "»" +description = "Sem arma nao significa sem forca." # agility [agility] [agility.armor_up] - name = "Armadura Crescente" - description = "Ganha mais armadura quanto mais tempo correres!" - lore1 = "Armadura Maxima" - lore2 = "Tempo de Armadura" - lore = ["Armadura Maxima", "Tempo de Armadura"] +name = "Armadura Crescente" +description = "Ganha mais armadura quanto mais tempo correres!" +lore1 = "Armadura Maxima" +lore2 = "Tempo de Armadura" +lore = ["Armadura Maxima", "Tempo de Armadura"] [agility.ladder_slide] - name = "Deslizar na Escada" - description = "Sobe e desce escadas muito mais rapido em ambas as direcoes." - lore1 = "Multiplicador de velocidade na escada" - lore2 = "Velocidade de descida rapida" - lore = ["Multiplicador de velocidade na escada", "Velocidade de descida rapida"] +name = "Deslizar na Escada" +description = "Sobe e desce escadas muito mais rapido em ambas as direcoes." +lore1 = "Multiplicador de velocidade na escada" +lore2 = "Velocidade de descida rapida" +lore = ["Multiplicador de velocidade na escada", "Velocidade de descida rapida"] [agility.super_jump] - name = "Super Salto" - description = "Vantagem de Altura Excecional." - lore1 = "Altura Maxima de Salto" - lore2 = "Agachar + Saltar para Super Salto!" - lore = ["Altura Maxima de Salto", "Agachar + Saltar para Super Salto!"] +name = "Super Salto" +description = "Vantagem de Altura Excecional." +lore1 = "Altura Maxima de Salto" +lore2 = "Agachar + Saltar para Super Salto!" +lore = ["Altura Maxima de Salto", "Agachar + Saltar para Super Salto!"] [agility.wall_jump] - name = "Salto na Parede" - description = "Segura shift enquanto no ar junto a uma parede para te agarrares e saltares!" - lore1 = "Saltos Maximos" - lore2 = "Altura do Salto" - lore = ["Saltos Maximos", "Altura do Salto"] +name = "Salto na Parede" +description = "Segura shift enquanto no ar junto a uma parede para te agarrares e saltares!" +lore1 = "Saltos Maximos" +lore2 = "Altura do Salto" +lore = ["Saltos Maximos", "Altura do Salto"] [agility.wind_up] - name = "Aceleracao" - description = "Fica mais rapido quanto mais tempo correres!" - lore1 = "Velocidade Maxima" - lore2 = "Tempo de Aceleracao" - lore = ["Velocidade Maxima", "Tempo de Aceleracao"] +name = "Aceleracao" +description = "Fica mais rapido quanto mais tempo correres!" +lore1 = "Velocidade Maxima" +lore2 = "Tempo de Aceleracao" +lore = ["Velocidade Maxima", "Tempo de Aceleracao"] # architect [architect] [architect.elevator] - name = "Elevador" - description = "Permite-te construir um elevador para teletransportar verticalmente com rapidez!" - lore1 = "Desbloqueia receita do elevador: X=LA, Y=PEROLA DO ENDER" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Desbloqueia receita do elevador: X=LA, Y=PEROLA DO ENDER", "XXX", "XYX", "XXX"] +name = "Elevador" +description = "Permite-te construir um elevador para teletransportar verticalmente com rapidez!" +lore1 = "Desbloqueia receita do elevador: X=LA, Y=PEROLA DO ENDER" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Desbloqueia receita do elevador: X=LA, Y=PEROLA DO ENDER", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Fundacao Magica" - description = "Permite-te agachar e colocar uma fundacao temporaria debaixo de ti!" - lore1 = "Cria magicamente: " - lore2 = "Blocos debaixo de ti!" - lore = ["Cria magicamente: ", "Blocos debaixo de ti!"] +name = "Fundacao Magica" +description = "Permite-te agachar e colocar uma fundacao temporaria debaixo de ti!" +lore1 = "Cria magicamente: " +lore2 = "Blocos debaixo de ti!" +lore = ["Cria magicamente: ", "Blocos debaixo de ti!"] [architect.glass] - name = "Vidro com Toque de Seda" - description = "Permite-te essencialmente evitar a perda de blocos de vidro quando os partes com a mao vazia!" - lore1 = "As tuas maos ganham toque de seda para Vidro" - lore = ["As tuas maos ganham toque de seda para Vidro"] +name = "Vidro com Toque de Seda" +description = "Permite-te essencialmente evitar a perda de blocos de vidro quando os partes com a mao vazia!" +lore1 = "As tuas maos ganham toque de seda para Vidro" +lore = ["As tuas maos ganham toque de seda para Vidro"] [architect.wireless_redstone] - name = "Controlo Remoto de Redstone" - description = "Permite-te usar uma tocha de redstone para alternar redstone, remotamente!" - lore1 = "Alvo + Tocha de Redstone + Perola do Ender = 1 Controlo Remoto de Redstone" - lore = ["Alvo + Tocha de Redstone + Perola do Ender = 1 Controlo Remoto de Redstone"] +name = "Controlo Remoto de Redstone" +description = "Permite-te usar uma tocha de redstone para alternar redstone, remotamente!" +lore1 = "Alvo + Tocha de Redstone + Perola do Ender = 1 Controlo Remoto de Redstone" +lore = ["Alvo + Tocha de Redstone + Perola do Ender = 1 Controlo Remoto de Redstone"] [architect.placement] - name = "Varinha de Construtor" - description = "Permite-te colocar multiplos blocos de uma vez. Para ativar, agacha-te e segura um bloco que corresponda ao bloco para o qual estas a olhar e coloca! Tem em conta que podes precisar de te mover um pouco para acionar a delimitacao!" - lore1 = "Precisas de" - lore2 = "blocos na tua mao para colocar isto" - lore3 = "Uma Varinha de Construtor de Material" - lore = ["Precisas de", "blocos na tua mao para colocar isto", "Uma Varinha de Construtor de Material"] +name = "Varinha de Construtor" +description = "Permite-te colocar multiplos blocos de uma vez. Para ativar, agacha-te e segura um bloco que corresponda ao bloco para o qual estas a olhar e coloca! Tem em conta que podes precisar de te mover um pouco para acionar a delimitacao!" +lore1 = "Precisas de" +lore2 = "blocos na tua mao para colocar isto" +lore3 = "Uma Varinha de Construtor de Material" +lore = ["Precisas de", "blocos na tua mao para colocar isto", "Uma Varinha de Construtor de Material"] # axe [axe] [axe.chop] - name = "Corte de Machado" - description = "Corta arvores clicando com o botao direito no tronco base!" - lore1 = "Blocos Por Corte" - lore2 = "Tempo de Espera do Corte" - lore3 = "Desgaste da Ferramenta" - lore = ["Blocos Por Corte", "Tempo de Espera do Corte", "Desgaste da Ferramenta"] +name = "Corte de Machado" +description = "Corta arvores clicando com o botao direito no tronco base!" +lore1 = "Blocos Por Corte" +lore2 = "Tempo de Espera do Corte" +lore3 = "Desgaste da Ferramenta" +lore = ["Blocos Por Corte", "Tempo de Espera do Corte", "Desgaste da Ferramenta"] [axe.log_swap] - name = "Troca de Troncos da Lucy" - description = "Muda o tipo de troncos numa Mesa de Fabrico!" - lore1 = "8 Troncos de qualquer tipo + 1 muda = 8 troncos do tipo da muda" - lore = ["8 Troncos de qualquer tipo + 1 muda = 8 troncos do tipo da muda"] +name = "Troca de Troncos da Lucy" +description = "Muda o tipo de troncos numa Mesa de Fabrico!" +lore1 = "8 Troncos de qualquer tipo + 1 muda = 8 troncos do tipo da muda" +lore = ["8 Troncos de qualquer tipo + 1 muda = 8 troncos do tipo da muda"] [axe.drop_to_inventory] - name = "Machado Direto-Para-Inventario" +name = "Machado Direto-Para-Inventario" [axe.ground_smash] - name = "Esmagar o Chao com Machado" - description = "Salta, depois agacha-te e esmaga todos os inimigos proximos." - lore1 = "Dano" - lore2 = "Raio de Blocos" - lore3 = "Forca" - lore4 = "Tempo de Espera do Esmagar" - lore = ["Dano", "Raio de Blocos", "Forca", "Tempo de Espera do Esmagar"] +name = "Esmagar o Chao com Machado" +description = "Salta, depois agacha-te e esmaga todos os inimigos proximos." +lore1 = "Dano" +lore2 = "Raio de Blocos" +lore3 = "Forca" +lore4 = "Tempo de Espera do Esmagar" +lore = ["Dano", "Raio de Blocos", "Forca", "Tempo de Espera do Esmagar"] [axe.leaf_miner] - name = "Mineiro de Folhas" - description = "Permite-te partir folhas em massa de uma so vez!" - lore1 = "Agacha e minera FOLHAS" - lore2 = "alcance da mineracao de folhas" - lore3 = "Nao recebes os drops das folhas (Prevencao de Exploits)" - lore = ["Agacha e minera FOLHAS", "alcance da mineracao de folhas", "Nao recebes os drops das folhas (Prevencao de Exploits)"] +name = "Mineiro de Folhas" +description = "Permite-te partir folhas em massa de uma so vez!" +lore1 = "Agacha e minera FOLHAS" +lore2 = "alcance da mineracao de folhas" +lore3 = "Nao recebes os drops das folhas (Prevencao de Exploits)" +lore = ["Agacha e minera FOLHAS", "alcance da mineracao de folhas", "Nao recebes os drops das folhas (Prevencao de Exploits)"] [axe.wood_miner] - name = "Mineiro de Madeira" - description = "Permite-te partir madeira em massa de uma so vez!" - lore1 = "Agacha e minera MADEIRA/TRONCOS (Nao Tabuas)" - lore2 = "alcance da mineracao de madeira" - lore3 = "Funciona com Direto-Para-Inventario" - lore = ["Agacha e minera MADEIRA/TRONCOS (Nao Tabuas)", "alcance da mineracao de madeira", "Funciona com Direto-Para-Inventario"] +name = "Mineiro de Madeira" +description = "Permite-te partir madeira em massa de uma so vez!" +lore1 = "Agacha e minera MADEIRA/TRONCOS (Nao Tabuas)" +lore2 = "alcance da mineracao de madeira" +lore3 = "Funciona com Direto-Para-Inventario" +lore = ["Agacha e minera MADEIRA/TRONCOS (Nao Tabuas)", "alcance da mineracao de madeira", "Funciona com Direto-Para-Inventario"] # brewing [brewing] [brewing.lingering] - name = "Pocao Prolongada" - description = "Pocoes fabricadas duram mais tempo!" - lore1 = "Duracao" - lore2 = "Duracao" - lore = ["Duracao", "Duracao"] +name = "Pocao Prolongada" +description = "Pocoes fabricadas duram mais tempo!" +lore1 = "Duracao" +lore2 = "Duracao" +lore = ["Duracao", "Duracao"] [brewing.super_heated] - name = "Pocao Super Aquecida" - description = "Suportes de pocoes funcionam mais rapido quanto mais quentes estiverem." - lore1 = "Por Bloco de Fogo a Tocar" - lore2 = "Por Bloco de Lava a Tocar" - lore = ["Por Bloco de Fogo a Tocar", "Por Bloco de Lava a Tocar"] +name = "Pocao Super Aquecida" +description = "Suportes de pocoes funcionam mais rapido quanto mais quentes estiverem." +lore1 = "Por Bloco de Fogo a Tocar" +lore2 = "Por Bloco de Lava a Tocar" +lore = ["Por Bloco de Fogo a Tocar", "Por Bloco de Lava a Tocar"] [brewing.darkness] - name = "Escuridao Engarrafada" - description = "Nao sei porque precisas disto, mas aqui tens!" - lore1 = "Pocao de Visao Noturna + Betao Preto = Pocao de Escuridao (30 segundos)" - lore2 = "Nota que isto impede o utilizador de correr!" - lore = ["Pocao de Visao Noturna + Betao Preto = Pocao de Escuridao (30 segundos)", "Nota que isto impede o utilizador de correr!"] +name = "Escuridao Engarrafada" +description = "Nao sei porque precisas disto, mas aqui tens!" +lore1 = "Pocao de Visao Noturna + Betao Preto = Pocao de Escuridao (30 segundos)" +lore2 = "Nota que isto impede o utilizador de correr!" +lore = ["Pocao de Visao Noturna + Betao Preto = Pocao de Escuridao (30 segundos)", "Nota que isto impede o utilizador de correr!"] [brewing.haste] - name = "Pressa Engarrafada" - description = "Quando a Eficiencia nao e suficiente" - lore1 = "Pocao de Velocidade + Fragmento de Ametista = Pocao de Pressa (60 segundos)" - lore2 = "Pocao de Velocidade + Bloco de Ametista = Pocao de Pressa-2 (30 segundos)" - lore = ["Pocao de Velocidade + Fragmento de Ametista = Pocao de Pressa (60 segundos)", "Pocao de Velocidade + Bloco de Ametista = Pocao de Pressa-2 (30 segundos)"] +name = "Pressa Engarrafada" +description = "Quando a Eficiencia nao e suficiente" +lore1 = "Pocao de Velocidade + Fragmento de Ametista = Pocao de Pressa (60 segundos)" +lore2 = "Pocao de Velocidade + Bloco de Ametista = Pocao de Pressa-2 (30 segundos)" +lore = ["Pocao de Velocidade + Fragmento de Ametista = Pocao de Pressa (60 segundos)", "Pocao de Velocidade + Bloco de Ametista = Pocao de Pressa-2 (30 segundos)"] [brewing.absorption] - name = "Absorcao Engarrafada" - description = "Endurece o corpo!" - lore1 = "Cura Instantanea + Quartzo = Pocao de Absorcao (60 segundos)" - lore2 = "Cura Instantanea + Bloco de Quartzo = Pocao de Absorcao-2 (30 segundos)" - lore = ["Cura Instantanea + Quartzo = Pocao de Absorcao (60 segundos)", "Cura Instantanea + Bloco de Quartzo = Pocao de Absorcao-2 (30 segundos)"] +name = "Absorcao Engarrafada" +description = "Endurece o corpo!" +lore1 = "Cura Instantanea + Quartzo = Pocao de Absorcao (60 segundos)" +lore2 = "Cura Instantanea + Bloco de Quartzo = Pocao de Absorcao-2 (30 segundos)" +lore = ["Cura Instantanea + Quartzo = Pocao de Absorcao (60 segundos)", "Cura Instantanea + Bloco de Quartzo = Pocao de Absorcao-2 (30 segundos)"] [brewing.fatigue] - name = "Fadiga Engarrafada" - description = "Enfraquece o corpo!" - lore1 = "Pocao de Fraqueza + Bola de Slime = Pocao de Fadiga (30 segundos)" - lore2 = "Pocao de Fraqueza + Bloco de Slime = Pocao de Fadiga-2 (15 segundos)" - lore = ["Pocao de Fraqueza + Bola de Slime = Pocao de Fadiga (30 segundos)", "Pocao de Fraqueza + Bloco de Slime = Pocao de Fadiga-2 (15 segundos)"] +name = "Fadiga Engarrafada" +description = "Enfraquece o corpo!" +lore1 = "Pocao de Fraqueza + Bola de Slime = Pocao de Fadiga (30 segundos)" +lore2 = "Pocao de Fraqueza + Bloco de Slime = Pocao de Fadiga-2 (15 segundos)" +lore = ["Pocao de Fraqueza + Bola de Slime = Pocao de Fadiga (30 segundos)", "Pocao de Fraqueza + Bloco de Slime = Pocao de Fadiga-2 (15 segundos)"] [brewing.hunger] - name = "Fome Engarrafada" - description = "Alimenta o Insaciavel!" - lore1 = "Pocao Estranha + Carne Podre = Pocao de Fome (30 segundos)" - lore2 = "Pocao de Fraqueza + Carne Podre = Pocao de Fome-3 (15 segundos)" - lore = ["Pocao Estranha + Carne Podre = Pocao de Fome (30 segundos)", "Pocao de Fraqueza + Carne Podre = Pocao de Fome-3 (15 segundos)"] +name = "Fome Engarrafada" +description = "Alimenta o Insaciavel!" +lore1 = "Pocao Estranha + Carne Podre = Pocao de Fome (30 segundos)" +lore2 = "Pocao de Fraqueza + Carne Podre = Pocao de Fome-3 (15 segundos)" +lore = ["Pocao Estranha + Carne Podre = Pocao de Fome (30 segundos)", "Pocao de Fraqueza + Carne Podre = Pocao de Fome-3 (15 segundos)"] [brewing.nausea] - name = "Nausea Engarrafada" - description = "Voce me da enjoo!" - lore1 = "Pocao Estranha + Cogumelo Castanho = Pocao de Nausea (16 segundos)" - lore2 = "Pocao Estranha + Fungo Carmesim = Pocao de Nausea-2 (8 segundos)" - lore = ["Pocao Estranha + Cogumelo Castanho = Pocao de Nausea (16 segundos)", "Pocao Estranha + Fungo Carmesim = Pocao de Nausea-2 (8 segundos)"] +name = "Nausea Engarrafada" +description = "Voce me da enjoo!" +lore1 = "Pocao Estranha + Cogumelo Castanho = Pocao de Nausea (16 segundos)" +lore2 = "Pocao Estranha + Fungo Carmesim = Pocao de Nausea-2 (8 segundos)" +lore = ["Pocao Estranha + Cogumelo Castanho = Pocao de Nausea (16 segundos)", "Pocao Estranha + Fungo Carmesim = Pocao de Nausea-2 (8 segundos)"] [brewing.blindness] - name = "Cegueira Engarrafada" - description = "Es uma pessoa horrivel..." - lore1 = "Pocao Estranha + Saco de Tinta = Pocao de Cegueira (30 segundos)" - lore2 = "Pocao Estranha + Saco de Tinta Brilhante = Pocao de Cegueira-2 (15 segundos)" - lore = ["Pocao Estranha + Saco de Tinta = Pocao de Cegueira (30 segundos)", "Pocao Estranha + Saco de Tinta Brilhante = Pocao de Cegueira-2 (15 segundos)"] +name = "Cegueira Engarrafada" +description = "Es uma pessoa horrivel..." +lore1 = "Pocao Estranha + Saco de Tinta = Pocao de Cegueira (30 segundos)" +lore2 = "Pocao Estranha + Saco de Tinta Brilhante = Pocao de Cegueira-2 (15 segundos)" +lore = ["Pocao Estranha + Saco de Tinta = Pocao de Cegueira (30 segundos)", "Pocao Estranha + Saco de Tinta Brilhante = Pocao de Cegueira-2 (15 segundos)"] [brewing.resistance] - name = "Resistencia Engarrafada" - description = "Fortificacao no seu melhor!" - lore1 = "Pocao Estranha + Lingote de Ferro = Pocao de Resistencia (60 segundos)" - lore2 = "Pocao Estranha + Bloco de Ferro = Pocao de Resistencia-2 (30 segundos)" - lore = ["Pocao Estranha + Lingote de Ferro = Pocao de Resistencia (60 segundos)", "Pocao Estranha + Bloco de Ferro = Pocao de Resistencia-2 (30 segundos)"] +name = "Resistencia Engarrafada" +description = "Fortificacao no seu melhor!" +lore1 = "Pocao Estranha + Lingote de Ferro = Pocao de Resistencia (60 segundos)" +lore2 = "Pocao Estranha + Bloco de Ferro = Pocao de Resistencia-2 (30 segundos)" +lore = ["Pocao Estranha + Lingote de Ferro = Pocao de Resistencia (60 segundos)", "Pocao Estranha + Bloco de Ferro = Pocao de Resistencia-2 (30 segundos)"] [brewing.health_boost] - name = "Vida Engarrafada" - description = "Quando a saude maxima nao e suficiente..." - lore1 = "Pocao de Cura Instantanea + Maca Dourada = Pocao de Aumento de Saude (120 segundos)" - lore2 = "Pocao de Cura Instantanea + Maca Dourada Encantada = Pocao de Aumento de Saude-2 (120 segundos)" - lore = ["Pocao de Cura Instantanea + Maca Dourada = Pocao de Aumento de Saude (120 segundos)", "Pocao de Cura Instantanea + Maca Dourada Encantada = Pocao de Aumento de Saude-2 (120 segundos)"] +name = "Vida Engarrafada" +description = "Quando a saude maxima nao e suficiente..." +lore1 = "Pocao de Cura Instantanea + Maca Dourada = Pocao de Aumento de Saude (120 segundos)" +lore2 = "Pocao de Cura Instantanea + Maca Dourada Encantada = Pocao de Aumento de Saude-2 (120 segundos)" +lore = ["Pocao de Cura Instantanea + Maca Dourada = Pocao de Aumento de Saude (120 segundos)", "Pocao de Cura Instantanea + Maca Dourada Encantada = Pocao de Aumento de Saude-2 (120 segundos)"] [brewing.decay] - name = "Decomposicao Engarrafada" - description = "Quem diria que detritos seriam tao uteis?" - lore1 = "Pocao de Fraqueza + Batata Venenosa = Pocao de Wither (16 segundos)" - lore2 = "Pocao de Fraqueza + Raizes Carmesim = Pocao de Wither-2 (8 segundos)" - lore = ["Pocao de Fraqueza + Batata Venenosa = Pocao de Wither (16 segundos)", "Pocao de Fraqueza + Raizes Carmesim = Pocao de Wither-2 (8 segundos)"] +name = "Decomposicao Engarrafada" +description = "Quem diria que detritos seriam tao uteis?" +lore1 = "Pocao de Fraqueza + Batata Venenosa = Pocao de Wither (16 segundos)" +lore2 = "Pocao de Fraqueza + Raizes Carmesim = Pocao de Wither-2 (8 segundos)" +lore = ["Pocao de Fraqueza + Batata Venenosa = Pocao de Wither (16 segundos)", "Pocao de Fraqueza + Raizes Carmesim = Pocao de Wither-2 (8 segundos)"] [brewing.saturation] - name = "Saturacao Engarrafada" - description = "Sabes que... Nem estou com fome..." - lore1 = "Pocao de Regeneracao + Batata Assada = Pocao de Saturacao" - lore2 = "Pocao de Regeneracao + Fardo de Feno = Pocao de Saturacao-2" - lore = ["Pocao de Regeneracao + Batata Assada = Pocao de Saturacao", "Pocao de Regeneracao + Fardo de Feno = Pocao de Saturacao-2"] +name = "Saturacao Engarrafada" +description = "Sabes que... Nem estou com fome..." +lore1 = "Pocao de Regeneracao + Batata Assada = Pocao de Saturacao" +lore2 = "Pocao de Regeneracao + Fardo de Feno = Pocao de Saturacao-2" +lore = ["Pocao de Regeneracao + Batata Assada = Pocao de Saturacao", "Pocao de Regeneracao + Fardo de Feno = Pocao de Saturacao-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Correntes de Mefistofeles" - description = "Permite-te fabricar Armadura de Malha" - lore1 = "A receita de fabrico e igual a qualquer outra, mas com pepitas de ferro em vez disso" - lore = ["A receita de fabrico e igual a qualquer outra, mas com pepitas de ferro em vez disso"] +name = "Correntes de Mefistofeles" +description = "Permite-te fabricar Armadura de Malha" +lore1 = "A receita de fabrico e igual a qualquer outra, mas com pepitas de ferro em vez disso" +lore = ["A receita de fabrico e igual a qualquer outra, mas com pepitas de ferro em vez disso"] [blocking.horse_armorer] - name = "Armadura de Cavalo Fabricavel" - description = "Permite-te fabricar Armadura de Cavalo" - lore1 = "Rodeia uma sela com o material que queres usar para fabricar a armadura" - lore = ["Rodeia uma sela com o material que queres usar para fabricar a armadura"] +name = "Armadura de Cavalo Fabricavel" +description = "Permite-te fabricar Armadura de Cavalo" +lore1 = "Rodeia uma sela com o material que queres usar para fabricar a armadura" +lore = ["Rodeia uma sela com o material que queres usar para fabricar a armadura"] [blocking.saddle_crafter] - name = "Sela Fabricavel" - description = "Fabrica uma Sela com Couro" - lore1 = "Receita: 5 Couro:" - lore = ["Receita: 5 Couro:"] +name = "Sela Fabricavel" +description = "Fabrica uma Sela com Couro" +lore1 = "Receita: 5 Couro:" +lore = ["Receita: 5 Couro:"] [blocking.multi_armor] - name = "Multi-Armadura" - description = "Liga Elytras a Armadura" - lore1 = "Esta e uma habilidade incrivel para viajar." - lore2 = "Funde e altera dinamicamente Armadura/Elytra em tempo real!" - lore3 = "Para fundir, clica com shift num item sobre outro no teu inventario." - lore4 = "Para desvincular Armadura, larga o item agachado e ele sera desmontado." - lore5 = "Se a tua MultiArmadura for destruida, perdes todos os itens nela." - lore6 = "Nao preciso de armadura, isso decepciona-me..." - lore = ["Esta e uma habilidade incrivel para viajar.", "Funde e altera dinamicamente Armadura/Elytra em tempo real!", "Para fundir, clica com shift num item sobre outro no teu inventario.", "Para desvincular Armadura, larga o item agachado e ele sera desmontado.", "Se a tua MultiArmadura for destruida, perdes todos os itens nela.", "Nao preciso de armadura, isso decepciona-me..."] +name = "Multi-Armadura" +description = "Liga Elytras a Armadura" +lore1 = "Esta e uma habilidade incrivel para viajar." +lore2 = "Funde e altera dinamicamente Armadura/Elytra em tempo real!" +lore3 = "Para fundir, clica com shift num item sobre outro no teu inventario." +lore4 = "Para desvincular Armadura, larga o item agachado e ele sera desmontado." +lore5 = "Se a tua MultiArmadura for destruida, perdes todos os itens nela." +lore6 = "Nao preciso de armadura, isso decepciona-me..." +lore = ["Esta e uma habilidade incrivel para viajar.", "Funde e altera dinamicamente Armadura/Elytra em tempo real!", "Para fundir, clica com shift num item sobre outro no teu inventario.", "Para desvincular Armadura, larga o item agachado e ele sera desmontado.", "Se a tua MultiArmadura for destruida, perdes todos os itens nela.", "Nao preciso de armadura, isso decepciona-me..."] # crafting [crafting] [crafting.deconstruction] - name = "Desconstrucao" - description = "Desconstroi blocos e itens em componentes base recuperaveis!" - lore1 = "Larga qualquer item no chao." - lore2 = "Depois, agacha-te e clica direito com Tesouras" - lore = ["Larga qualquer item no chao.", "Depois, agacha-te e clica direito com Tesouras"] +name = "Desconstrucao" +description = "Desconstroi blocos e itens em componentes base recuperaveis!" +lore1 = "Larga qualquer item no chao." +lore2 = "Depois, agacha-te e clica direito com Tesouras" +lore = ["Larga qualquer item no chao.", "Depois, agacha-te e clica direito com Tesouras"] [crafting.xp] - name = "XP de Fabrico" - description = "Ganha XP passivo ao fabricar" - lore1 = "Ganha XP ao fabricar" - lore = ["Ganha XP ao fabricar"] +name = "XP de Fabrico" +description = "Ganha XP passivo ao fabricar" +lore1 = "Ganha XP ao fabricar" +lore = ["Ganha XP ao fabricar"] [crafting.reconstruction] - name = "Reconstrucao de Minerio" - description = "Refabrica minerios a partir dos seus componentes base!" - lore1 = "8 dos Drops e 1 Hospedeiro = 1 Minerio (sem forma)" - lore2 = "Os drops devem ser fundidos (se aplicavel)" - lore3 = "Nao inclui: Sucatas, Quartzo, e Esmeraldas etc..." - lore4 = "Hospedeiro = Revestimento. ex: Pedra, Netherrack, Ardosia Profunda" - lore = ["8 dos Drops e 1 Hospedeiro = 1 Minerio (sem forma)", "Os drops devem ser fundidos (se aplicavel)", "Nao inclui: Sucatas, Quartzo, e Esmeraldas etc...", "Hospedeiro = Revestimento. ex: Pedra, Netherrack, Ardosia Profunda"] +name = "Reconstrucao de Minerio" +description = "Refabrica minerios a partir dos seus componentes base!" +lore1 = "8 dos Drops e 1 Hospedeiro = 1 Minerio (sem forma)" +lore2 = "Os drops devem ser fundidos (se aplicavel)" +lore3 = "Nao inclui: Sucatas, Quartzo, e Esmeraldas etc..." +lore4 = "Hospedeiro = Revestimento. ex: Pedra, Netherrack, Ardosia Profunda" +lore = ["8 dos Drops e 1 Hospedeiro = 1 Minerio (sem forma)", "Os drops devem ser fundidos (se aplicavel)", "Nao inclui: Sucatas, Quartzo, e Esmeraldas etc...", "Hospedeiro = Revestimento. ex: Pedra, Netherrack, Ardosia Profunda"] [crafting.leather] - name = "Couro Fabricavel" - description = "Fabrica Couro a partir de Carne Podre" - lore1 = "Basta atirar (carne podre) para a fogueira!" - lore = ["Basta atirar (carne podre) para a fogueira!"] +name = "Couro Fabricavel" +description = "Fabrica Couro a partir de Carne Podre" +lore1 = "Basta atirar (carne podre) para a fogueira!" +lore = ["Basta atirar (carne podre) para a fogueira!"] [crafting.backpacks] - name = "Mochilas de Boutilier!" - description = "Isto simplesmente traz o Pacote do Mojang para o jogo!" - lore1 = "Precisas de estar em Sobrevivencia para usar isto" - lore2 = "XLX : Couro, Trela, Couro" - lore3 = "XSX : Couro, Caixa de Barril, Couro" - lore4 = "XCX : Couro, Bau, Couro" - lore = ["Precisas de estar em Sobrevivencia para usar isto", "XLX : Couro, Trela, Couro", "XSX : Couro, Caixa de Barril, Couro", "XCX : Couro, Bau, Couro"] +name = "Mochilas de Boutilier!" +description = "Isto simplesmente traz o Pacote do Mojang para o jogo!" +lore1 = "Precisas de estar em Sobrevivencia para usar isto" +lore2 = "XLX : Couro, Trela, Couro" +lore3 = "XSX : Couro, Caixa de Barril, Couro" +lore4 = "XCX : Couro, Bau, Couro" +lore = ["Precisas de estar em Sobrevivencia para usar isto", "XLX : Couro, Trela, Couro", "XSX : Couro, Caixa de Barril, Couro", "XCX : Couro, Bau, Couro"] [crafting.stations] - name = "Bancadas Portateis!" - description = "Usa uma bancada na palma da tua mao!" - lore2 = "QUAISQUER ITENS QUE ESQUECAS NA BANCADA AO FECHAR SAO PERDIDOS PARA SEMPRE!" - lore3 = "Bancadas validas: Bigorna, Fabrico, Rebolo, Cartografia, Corta-Pedras, Tear" - lore = ["QUAISQUER ITENS QUE ESQUECAS NA BANCADA AO FECHAR SAO PERDIDOS PARA SEMPRE!", "Bancadas validas: Bigorna, Fabrico, Rebolo, Cartografia, Corta-Pedras, Tear"] +name = "Bancadas Portateis!" +description = "Usa uma bancada na palma da tua mao!" +lore2 = "QUAISQUER ITENS QUE ESQUECAS NA BANCADA AO FECHAR SAO PERDIDOS PARA SEMPRE!" +lore3 = "Bancadas validas: Bigorna, Fabrico, Rebolo, Cartografia, Corta-Pedras, Tear" +lore = ["QUAISQUER ITENS QUE ESQUECAS NA BANCADA AO FECHAR SAO PERDIDOS PARA SEMPRE!", "Bancadas validas: Bigorna, Fabrico, Rebolo, Cartografia, Corta-Pedras, Tear"] [crafting.skulls] - name = "Cranios Fabricaveis!" - description = "Usando Materiais podes Fabricar Cranios de Mobs!" - lore1 = "Rodeia um Bloco de Osso com o seguinte para obter um cranio:" - lore2 = "Zombie: Carne Podre" - lore3 = "Esqueleto: Osso" - lore4 = "Creeper: Polvora" - lore5 = "Wither: Tijolo do Nether" - lore6 = "Dragao: Sopro do Dragao" - lore = ["Rodeia um Bloco de Osso com o seguinte para obter um cranio:", "Zombie: Carne Podre", "Esqueleto: Osso", "Creeper: Polvora", "Wither: Tijolo do Nether", "Dragao: Sopro do Dragao"] +name = "Cranios Fabricaveis!" +description = "Usando Materiais podes Fabricar Cranios de Mobs!" +lore1 = "Rodeia um Bloco de Osso com o seguinte para obter um cranio:" +lore2 = "Zombie: Carne Podre" +lore3 = "Esqueleto: Osso" +lore4 = "Creeper: Polvora" +lore5 = "Wither: Tijolo do Nether" +lore6 = "Dragao: Sopro do Dragao" +lore = ["Rodeia um Bloco de Osso com o seguinte para obter um cranio:", "Zombie: Carne Podre", "Esqueleto: Osso", "Creeper: Polvora", "Wither: Tijolo do Nether", "Dragao: Sopro do Dragao"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Tempo Numa Garrafa" - description = "Carrega uma garrafa temporal que armazena tempo e gasta-o para acelerar blocos temporizados, cultivos e entidades envelheciveis como animais bebes. Receita (Sem Forma): Pocao de Rapidez + Relogio + Garrafa de Vidro." - lore1 = "Segundos armazenados carregados por tick" - lore2 = "Aceleracao temporal por segundo armazenado" - lore3 = "Receita (Sem Forma): Pocao de Rapidez + Relogio + Garrafa de Vidro" - lore = ["Segundos armazenados carregados por tick", "Aceleracao temporal por segundo armazenado", "Receita (Sem Forma): Pocao de Rapidez + Relogio + Garrafa de Vidro"] +name = "Tempo Numa Garrafa" +description = "Carrega uma garrafa temporal que armazena tempo e gasta-o para acelerar blocos temporizados, cultivos e entidades envelheciveis como animais bebes. Receita (Sem Forma): Pocao de Rapidez + Relogio + Garrafa de Vidro." +lore1 = "Segundos armazenados carregados por tick" +lore2 = "Aceleracao temporal por segundo armazenado" +lore3 = "Receita (Sem Forma): Pocao de Rapidez + Relogio + Garrafa de Vidro" +lore = ["Segundos armazenados carregados por tick", "Aceleracao temporal por segundo armazenado", "Receita (Sem Forma): Pocao de Rapidez + Relogio + Garrafa de Vidro"] [chronos.aberrant_touch] - name = "Toque Aberrante" - description = "Ataques corpo a corpo aplicam lentidao acumulativa a custo de fome, com limites estritos em PvP, e enraizam alvos a 5 acumulacoes." - lore1 = "Ataques corpo a corpo aplicam lentidao acumulativa" - lore2 = "Limite de duracao de lentidao em PvE" - lore3 = "Limite de amplificador de lentidao em PvP" - lore = ["Ataques corpo a corpo aplicam lentidao acumulativa", "Limite de duracao de lentidao em PvE", "Limite de amplificador de lentidao em PvP"] +name = "Toque Aberrante" +description = "Ataques corpo a corpo aplicam lentidao acumulativa a custo de fome, com limites estritos em PvP, e enraizam alvos a 5 acumulacoes." +lore1 = "Ataques corpo a corpo aplicam lentidao acumulativa" +lore2 = "Limite de duracao de lentidao em PvE" +lore3 = "Limite de amplificador de lentidao em PvP" +lore = ["Ataques corpo a corpo aplicam lentidao acumulativa", "Limite de duracao de lentidao em PvE", "Limite de amplificador de lentidao em PvP"] [chronos.instant_recall] - name = "Retorno Instantaneo" - description = "Clica esquerdo ou direito com um relogio na mao para retroceder a um registo recente com saude e fome restauradas." - lore1 = "Duracao do retrocesso" - lore2 = "Tempo de espera" - lore3 = "Sem reversao de inventario" - lore = ["Duracao do retrocesso", "Tempo de espera", "Sem reversao de inventario"] +name = "Retorno Instantaneo" +description = "Clica esquerdo ou direito com um relogio na mao para retroceder a um registo recente com saude e fome restauradas." +lore1 = "Duracao do retrocesso" +lore2 = "Tempo de espera" +lore3 = "Sem reversao de inventario" +lore = ["Duracao do retrocesso", "Tempo de espera", "Sem reversao de inventario"] [chronos.time_bomb] - name = "Bomba Temporal" - description = "Lanca uma bomba crono fabricada que cria um campo temporal, abranda entidades e congela projeteis." - lore1 = "Raio do campo temporal" - lore2 = "Duracao do campo temporal" - lore3 = "Tempo de espera da bomba" - lore4 = "Receita (Sem Forma): Relogio + Bola de Neve + Diamante + Areia" - lore = ["Raio do campo temporal", "Duracao do campo temporal", "Tempo de espera da bomba", "Receita (Sem Forma): Relogio + Bola de Neve + Diamante + Areia"] +name = "Bomba Temporal" +description = "Lanca uma bomba crono fabricada que cria um campo temporal, abranda entidades e congela projeteis." +lore1 = "Raio do campo temporal" +lore2 = "Duracao do campo temporal" +lore3 = "Tempo de espera da bomba" +lore4 = "Receita (Sem Forma): Relogio + Bola de Neve + Diamante + Areia" +lore = ["Raio do campo temporal", "Duracao do campo temporal", "Tempo de espera da bomba", "Receita (Sem Forma): Relogio + Bola de Neve + Diamante + Areia"] # discovery [discovery] [discovery.armor] - name = "Armadura do Mundo" - description = "Armadura passiva dependendo da dureza dos blocos proximos." - lore1 = "Armadura Passiva" - lore2 = "Baseada na dureza dos blocos proximos" - lore3 = "Forca da Armadura:" - lore = ["Armadura Passiva", "Baseada na dureza dos blocos proximos", "Forca da Armadura:"] +name = "Armadura do Mundo" +description = "Armadura passiva dependendo da dureza dos blocos proximos." +lore1 = "Armadura Passiva" +lore2 = "Baseada na dureza dos blocos proximos" +lore3 = "Forca da Armadura:" +lore = ["Armadura Passiva", "Baseada na dureza dos blocos proximos", "Forca da Armadura:"] [discovery.unity] - name = "Unidade Experimental" - description = "Recolher Orbes de Experiencia adiciona XP a habilidades aleatorias." - lore1 = "XP " - lore2 = "Por Orbe" - lore = ["XP ", "Por Orbe"] +name = "Unidade Experimental" +description = "Recolher Orbes de Experiencia adiciona XP a habilidades aleatorias." +lore1 = "XP " +lore2 = "Por Orbe" +lore = ["XP ", "Por Orbe"] [discovery.resist] - name = "Resistencia Experimental" - description = "Consome experiencia para mitigar dano apenas quando um golpe te deixaria abaixo de 5 coracoes ou te mataria." - lore0 = "Ativa apenas em saude critica (<= 5 coracoes) uma vez a cada 15 segundos" - lore1 = " Dano Reduzido" - lore2 = "experiencia drenada" - lore = ["Ativa apenas em saude critica (<= 5 coracoes) uma vez a cada 15 segundos", " Dano Reduzido", "experiencia drenada"] +name = "Resistencia Experimental" +description = "Consome experiencia para mitigar dano apenas quando um golpe te deixaria abaixo de 5 coracoes ou te mataria." +lore0 = "Ativa apenas em saude critica (<= 5 coracoes) uma vez a cada 15 segundos" +lore1 = " Dano Reduzido" +lore2 = "experiencia drenada" +lore = ["Ativa apenas em saude critica (<= 5 coracoes) uma vez a cada 15 segundos", " Dano Reduzido", "experiencia drenada"] [discovery.villager] - name = "Atracao de Aldeao" - description = "Permite-te obter melhores trocas com aldeoes!" - lore1 = "Isto consome XP por interacao com Aldeoes" - lore2 = "Chance por interacao de consumir XP e melhorar trocas" - lore3 = "drenagem de XP necessaria por Interacao" - lore = ["Isto consome XP por interacao com Aldeoes", "Chance por interacao de consumir XP e melhorar trocas", "drenagem de XP necessaria por Interacao"] +name = "Atracao de Aldeao" +description = "Permite-te obter melhores trocas com aldeoes!" +lore1 = "Isto consome XP por interacao com Aldeoes" +lore2 = "Chance por interacao de consumir XP e melhorar trocas" +lore3 = "drenagem de XP necessaria por Interacao" +lore = ["Isto consome XP por interacao com Aldeoes", "Chance por interacao de consumir XP e melhorar trocas", "drenagem de XP necessaria por Interacao"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Retorno de Lapis" - description = "Ao custo de mais 1 nivel de XP, tem chance de te dar lapis gratis em troca" - lore1 = "Por cada nivel, aumenta o custo de encantamento em 1, mas pode devolver ate 3 Lapis" - lore = ["Por cada nivel, aumenta o custo de encantamento em 1, mas pode devolver ate 3 Lapis"] +name = "Retorno de Lapis" +description = "Ao custo de mais 1 nivel de XP, tem chance de te dar lapis gratis em troca" +lore1 = "Por cada nivel, aumenta o custo de encantamento em 1, mas pode devolver ate 3 Lapis" +lore = ["Por cada nivel, aumenta o custo de encantamento em 1, mas pode devolver ate 3 Lapis"] [enchanting.quick_enchant] - name = "Encantamento Rapido" - description = "Encanta itens clicando livros de encantamento diretamente neles." - lore1 = "Niveis Maximos Combinados" - lore2 = "Nao e possivel encantar um item com mais de " - lore3 = "potencia" - lore = ["Niveis Maximos Combinados", "Nao e possivel encantar um item com mais de ", "potencia"] +name = "Encantamento Rapido" +description = "Encanta itens clicando livros de encantamento diretamente neles." +lore1 = "Niveis Maximos Combinados" +lore2 = "Nao e possivel encantar um item com mais de " +lore3 = "potencia" +lore = ["Niveis Maximos Combinados", "Nao e possivel encantar um item com mais de ", "potencia"] [enchanting.return] - name = "Retorno de XP" - description = "XP de encantamento e devolvido quando encantas um item." - lore1 = "Experiencia gasta tem chance de ser reembolsada quando encantas um item" - lore2 = "Experiencia por Encantamento" - lore = ["Experiencia gasta tem chance de ser reembolsada quando encantas um item", "Experiencia por Encantamento"] +name = "Retorno de XP" +description = "XP de encantamento e devolvido quando encantas um item." +lore1 = "Experiencia gasta tem chance de ser reembolsada quando encantas um item" +lore2 = "Experiencia por Encantamento" +lore = ["Experiencia gasta tem chance de ser reembolsada quando encantas um item", "Experiencia por Encantamento"] # excavation [excavation] [excavation.haste] - name = "Escavador Apressado" - description = "Isto acelera o processo de escavacao, com PRESSA!" - lore1 = "Ganha Pressa durante a escavacao" - lore2 = "x Niveis de pressa quando comecas a minerar QUALQUER bloco." - lore = ["Ganha Pressa durante a escavacao", "x Niveis de pressa quando comecas a minerar QUALQUER bloco."] +name = "Escavador Apressado" +description = "Isto acelera o processo de escavacao, com PRESSA!" +lore1 = "Ganha Pressa durante a escavacao" +lore2 = "x Niveis de pressa quando comecas a minerar QUALQUER bloco." +lore = ["Ganha Pressa durante a escavacao", "x Niveis de pressa quando comecas a minerar QUALQUER bloco."] [excavation.spelunker] - name = "Espeleologista Supervidente!" - description = "Ve Minerios com os teus olhos, mas atraves do chao!" - lore1 = "Minerio na mao secundaria, Bagas Luminosas na mao principal, e Agacha!" - lore2 = "Alcance de Blocos: " - lore3 = "Consome Baga Luminosa ao usar" - lore = ["Minerio na mao secundaria, Bagas Luminosas na mao principal, e Agacha!", "Alcance de Blocos: ", "Consome Baga Luminosa ao usar"] +name = "Espeleologista Supervidente!" +description = "Ve Minerios com os teus olhos, mas atraves do chao!" +lore1 = "Minerio na mao secundaria, Bagas Luminosas na mao principal, e Agacha!" +lore2 = "Alcance de Blocos: " +lore3 = "Consome Baga Luminosa ao usar" +lore = ["Minerio na mao secundaria, Bagas Luminosas na mao principal, e Agacha!", "Alcance de Blocos: ", "Consome Baga Luminosa ao usar"] [excavation.drop_to_inventory] - name = "Pa Direto-Para-Inventario" +name = "Pa Direto-Para-Inventario" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "O opulento canivete suico de Tackle" - lore1 = "Provavelmente a mais poderosa de muitas permite-te" - lore2 = "fundir e trocar ferramentas dinamicamente em tempo real, com base nas tuas necessidades." - lore3 = "Para fundir, clica com shift num item sobre outro no teu inventario." - lore4 = "Para desvincular ferramentas, larga o item agachado e ele sera desmontado." - lore5 = "Nao podes partir ferramentas neste canivete mas nao podes usar ferramentas partidas" - lore6 = "total de itens fundiveis." - lore7 = "Podes usar cinco ou seis ferramentas, ou apenas uma!" - lore = ["Provavelmente a mais poderosa de muitas permite-te", "fundir e trocar ferramentas dinamicamente em tempo real, com base nas tuas necessidades.", "Para fundir, clica com shift num item sobre outro no teu inventario.", "Para desvincular ferramentas, larga o item agachado e ele sera desmontado.", "Nao podes partir ferramentas neste canivete mas nao podes usar ferramentas partidas", "total de itens fundiveis.", "Podes usar cinco ou seis ferramentas, ou apenas uma!"] +name = "OMNI - T.O.O.L." +description = "O opulento canivete suico de Tackle" +lore1 = "Provavelmente a mais poderosa de muitas permite-te" +lore2 = "fundir e trocar ferramentas dinamicamente em tempo real, com base nas tuas necessidades." +lore3 = "Para fundir, clica com shift num item sobre outro no teu inventario." +lore4 = "Para desvincular ferramentas, larga o item agachado e ele sera desmontado." +lore5 = "Nao podes partir ferramentas neste canivete mas nao podes usar ferramentas partidas" +lore6 = "total de itens fundiveis." +lore7 = "Podes usar cinco ou seis ferramentas, ou apenas uma!" +lore = ["Provavelmente a mais poderosa de muitas permite-te", "fundir e trocar ferramentas dinamicamente em tempo real, com base nas tuas necessidades.", "Para fundir, clica com shift num item sobre outro no teu inventario.", "Para desvincular ferramentas, larga o item agachado e ele sera desmontado.", "Nao podes partir ferramentas neste canivete mas nao podes usar ferramentas partidas", "total de itens fundiveis.", "Podes usar cinco ou seis ferramentas, ou apenas uma!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Aura de Crescimento" - description = "Faz crescer a natureza a tua volta numa aura" - lore1 = "Raio de Blocos" - lore2 = "Forca da Aura de Crescimento" - lore3 = "Custo de Comida" - lore = ["Raio de Blocos", "Forca da Aura de Crescimento", "Custo de Comida"] +name = "Aura de Crescimento" +description = "Faz crescer a natureza a tua volta numa aura" +lore1 = "Raio de Blocos" +lore2 = "Forca da Aura de Crescimento" +lore3 = "Custo de Comida" +lore = ["Raio de Blocos", "Forca da Aura de Crescimento", "Custo de Comida"] [herbalism.hippo] - name = "Hipopotamo do Herbalista" - description = "Consumir comida da-te mais saturacao" - lore1 = "Comida) pontos de saturacao adicionais ao consumir" - lore = ["Comida) pontos de saturacao adicionais ao consumir"] +name = "Hipopotamo do Herbalista" +description = "Consumir comida da-te mais saturacao" +lore1 = "Comida) pontos de saturacao adicionais ao consumir" +lore = ["Comida) pontos de saturacao adicionais ao consumir"] [herbalism.myconid] - name = "Miconide do Herbalista" - description = "Da-te a capacidade de fabricar Micelio" - lore1 = "Qualquer Terra e um Cogumelo Castanho e Vermelho fabricam Micelio." - lore = ["Qualquer Terra e um Cogumelo Castanho e Vermelho fabricam Micelio."] +name = "Miconide do Herbalista" +description = "Da-te a capacidade de fabricar Micelio" +lore1 = "Qualquer Terra e um Cogumelo Castanho e Vermelho fabricam Micelio." +lore = ["Qualquer Terra e um Cogumelo Castanho e Vermelho fabricam Micelio."] [herbalism.terralid] - name = "Terralide do Herbalista" - description = "Da-te a capacidade de fabricar Blocos de Relva" - lore1 = "Tres Sementes, sobre 3 Terra, fabricam 3 Blocos de Relva." - lore = ["Tres Sementes, sobre 3 Terra, fabricam 3 Blocos de Relva."] +name = "Terralide do Herbalista" +description = "Da-te a capacidade de fabricar Blocos de Relva" +lore1 = "Tres Sementes, sobre 3 Terra, fabricam 3 Blocos de Relva." +lore = ["Tres Sementes, sobre 3 Terra, fabricam 3 Blocos de Relva."] [herbalism.cobweb] - name = "Criador de Teias" - description = "Da-te a capacidade de fabricar Teias de Aranha numa Mesa de Fabrico" - lore1 = "Nove Fios fabricam uma Teia de Aranha." - lore = ["Nove Fios fabricam uma Teia de Aranha."] +name = "Criador de Teias" +description = "Da-te a capacidade de fabricar Teias de Aranha numa Mesa de Fabrico" +lore1 = "Nove Fios fabricam uma Teia de Aranha." +lore = ["Nove Fios fabricam uma Teia de Aranha."] [herbalism.mushroom_blocks] - name = "Fabricante de Cogumelos" - description = "Da-te a capacidade de fabricar Blocos de Cogumelo numa Mesa de Fabrico" - lore1 = "Quatro Cogumelos para fazer um bloco, ou um bloco para fazer um caule." - lore = ["Quatro Cogumelos para fazer um bloco, ou um bloco para fazer um caule."] +name = "Fabricante de Cogumelos" +description = "Da-te a capacidade de fabricar Blocos de Cogumelo numa Mesa de Fabrico" +lore1 = "Quatro Cogumelos para fazer um bloco, ou um bloco para fazer um caule." +lore = ["Quatro Cogumelos para fazer um bloco, ou um bloco para fazer um caule."] [herbalism.drop_to_inventory] - name = "Enxada Direto-Para-Inventario" +name = "Enxada Direto-Para-Inventario" [herbalism.hungry_shield] - name = "Escudo Faminto" - description = "Recebe dano na fome antes da saude." - lore1 = "Resistido pela Fome" - lore = ["Resistido pela Fome"] +name = "Escudo Faminto" +description = "Recebe dano na fome antes da saude." +lore1 = "Resistido pela Fome" +lore = ["Resistido pela Fome"] [herbalism.luck] - name = "Sorte do Herbalista" - description = "Ao partires Relva/Flores, tens chance de obter um item aleatorio" - lore0 = "Flores = Comida, e Relva = Sementes" - lore1 = "Chance de obter um item ao partir Flores" - lore2 = "Chance de obter um item ao partir Relva" - lore = ["Flores = Comida, e Relva = Sementes", "Chance de obter um item ao partir Flores", "Chance de obter um item ao partir Relva"] +name = "Sorte do Herbalista" +description = "Ao partires Relva/Flores, tens chance de obter um item aleatorio" +lore0 = "Flores = Comida, e Relva = Sementes" +lore1 = "Chance de obter um item ao partir Flores" +lore2 = "Chance de obter um item ao partir Relva" +lore = ["Flores = Comida, e Relva = Sementes", "Chance de obter um item ao partir Flores", "Chance de obter um item ao partir Relva"] [herbalism.replant] - name = "Colheita e Replantacao" - description = "Clica direito numa colheita com uma enxada para colher e replantar." - lore1 = "Raio de Blocos de Replantacao" - lore = ["Raio de Blocos de Replantacao"] +name = "Colheita e Replantacao" +description = "Clica direito numa colheita com uma enxada para colher e replantar." +lore1 = "Raio de Blocos de Replantacao" +lore = ["Raio de Blocos de Replantacao"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenalina" - description = "Causa mais dano quanto menos saude tiveres (Corpo a corpo)" - lore1 = "Dano Maximo" - lore = ["Dano Maximo"] +name = "Adrenalina" +description = "Causa mais dano quanto menos saude tiveres (Corpo a corpo)" +lore1 = "Dano Maximo" +lore = ["Dano Maximo"] [hunter.penalty] - name = "" - description = "" - lore1 = "Ganhas acumulacoes de Veneno se ficares sem fome" - lore = ["Ganhas acumulacoes de Veneno se ficares sem fome"] +name = "" +description = "" +lore1 = "Ganhas acumulacoes de Veneno se ficares sem fome" +lore = ["Ganhas acumulacoes de Veneno se ficares sem fome"] [hunter.drop_to_inventory] - name = "Itens Direto-Para-Inventario" - description = "Quando matas algo / Partes um bloco com uma espada teleporta os drops para o teu inventario" - lore1 = "Sempre que um item cai de um mob/bloco que partes, vai para o teu inventario se possivel." - lore = ["Sempre que um item cai de um mob/bloco que partes, vai para o teu inventario se possivel."] +name = "Itens Direto-Para-Inventario" +description = "Quando matas algo / Partes um bloco com uma espada teleporta os drops para o teu inventario" +lore1 = "Sempre que um item cai de um mob/bloco que partes, vai para o teu inventario se possivel." +lore = ["Sempre que um item cai de um mob/bloco que partes, vai para o teu inventario se possivel."] [hunter.invisibility] - name = "Passo Fantasma" - description = "Quando es atingido ganhas invisibilidade, a custo de fome" - lore1 = "Ganha invisibilidade passiva ao ser atingido" - lore2 = "x Acumulacoes de Invisibilidade por 3 segundos ao ser atingido" - lore3 = "x Fome acumulativa" - lore4 = "Duracao e multiplicador da fome acumulativa." - lore5 = "Duracao da invisibilidade" - lore = ["Ganha invisibilidade passiva ao ser atingido", "x Acumulacoes de Invisibilidade por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Duracao da invisibilidade"] +name = "Passo Fantasma" +description = "Quando es atingido ganhas invisibilidade, a custo de fome" +lore1 = "Ganha invisibilidade passiva ao ser atingido" +lore2 = "x Acumulacoes de Invisibilidade por 3 segundos ao ser atingido" +lore3 = "x Fome acumulativa" +lore4 = "Duracao e multiplicador da fome acumulativa." +lore5 = "Duracao da invisibilidade" +lore = ["Ganha invisibilidade passiva ao ser atingido", "x Acumulacoes de Invisibilidade por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Duracao da invisibilidade"] [hunter.jump_boost] - name = "Alturas do Cacador" - description = "Quando es atingido ganhas impulso de salto, a custo de fome" - lore1 = "Ganha impulso de salto passivo ao ser atingido" - lore2 = "x Acumulacoes de Impulso de Salto por 3 segundos ao ser atingido" - lore3 = "x Fome acumulativa" - lore4 = "Duracao e multiplicador da fome acumulativa." - lore5 = "Multiplicador de acumulacoes de Impulso de Salto, nao duracao." - lore = ["Ganha impulso de salto passivo ao ser atingido", "x Acumulacoes de Impulso de Salto por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Impulso de Salto, nao duracao."] +name = "Alturas do Cacador" +description = "Quando es atingido ganhas impulso de salto, a custo de fome" +lore1 = "Ganha impulso de salto passivo ao ser atingido" +lore2 = "x Acumulacoes de Impulso de Salto por 3 segundos ao ser atingido" +lore3 = "x Fome acumulativa" +lore4 = "Duracao e multiplicador da fome acumulativa." +lore5 = "Multiplicador de acumulacoes de Impulso de Salto, nao duracao." +lore = ["Ganha impulso de salto passivo ao ser atingido", "x Acumulacoes de Impulso de Salto por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Impulso de Salto, nao duracao."] [hunter.luck] - name = "Sorte do Cacador" - description = "Quando es atingido ganhas sorte, a custo de fome" - lore1 = "Ganha sorte passiva ao ser atingido" - lore2 = "x Acumulacoes de Sorte por 3 segundos ao ser atingido" - lore3 = "x Fome acumulativa" - lore4 = "Duracao e multiplicador da fome acumulativa." - lore5 = "Multiplicador de acumulacoes de Sorte, nao duracao." - lore = ["Ganha sorte passiva ao ser atingido", "x Acumulacoes de Sorte por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Sorte, nao duracao."] +name = "Sorte do Cacador" +description = "Quando es atingido ganhas sorte, a custo de fome" +lore1 = "Ganha sorte passiva ao ser atingido" +lore2 = "x Acumulacoes de Sorte por 3 segundos ao ser atingido" +lore3 = "x Fome acumulativa" +lore4 = "Duracao e multiplicador da fome acumulativa." +lore5 = "Multiplicador de acumulacoes de Sorte, nao duracao." +lore = ["Ganha sorte passiva ao ser atingido", "x Acumulacoes de Sorte por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Sorte, nao duracao."] [hunter.regen] - name = "Regeneracao do Cacador" - description = "Quando es atingido ganhas regeneracao, a custo de fome" - lore1 = "Ganha regeneracao passiva ao ser atingido" - lore2 = "x Acumulacoes de Regeneracao por 3 segundos ao ser atingido" - lore3 = "x Fome acumulativa" - lore4 = "Duracao e multiplicador da fome acumulativa." - lore5 = "Multiplicador de acumulacoes de Regeneracao, nao duracao." - lore = ["Ganha regeneracao passiva ao ser atingido", "x Acumulacoes de Regeneracao por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Regeneracao, nao duracao."] +name = "Regeneracao do Cacador" +description = "Quando es atingido ganhas regeneracao, a custo de fome" +lore1 = "Ganha regeneracao passiva ao ser atingido" +lore2 = "x Acumulacoes de Regeneracao por 3 segundos ao ser atingido" +lore3 = "x Fome acumulativa" +lore4 = "Duracao e multiplicador da fome acumulativa." +lore5 = "Multiplicador de acumulacoes de Regeneracao, nao duracao." +lore = ["Ganha regeneracao passiva ao ser atingido", "x Acumulacoes de Regeneracao por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Regeneracao, nao duracao."] [hunter.resistance] - name = "Resistencia do Cacador" - description = "Quando es atingido ganhas resistencia, a custo de fome" - lore1 = "Ganha resistencia passiva ao ser atingido" - lore2 = "x Acumulacoes de Resistencia por 3 segundos ao ser atingido" - lore3 = "x Fome acumulativa" - lore4 = "Duracao e multiplicador da fome acumulativa." - lore5 = "Multiplicador de acumulacoes de Resistencia, nao duracao." - lore = ["Ganha resistencia passiva ao ser atingido", "x Acumulacoes de Resistencia por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Resistencia, nao duracao."] +name = "Resistencia do Cacador" +description = "Quando es atingido ganhas resistencia, a custo de fome" +lore1 = "Ganha resistencia passiva ao ser atingido" +lore2 = "x Acumulacoes de Resistencia por 3 segundos ao ser atingido" +lore3 = "x Fome acumulativa" +lore4 = "Duracao e multiplicador da fome acumulativa." +lore5 = "Multiplicador de acumulacoes de Resistencia, nao duracao." +lore = ["Ganha resistencia passiva ao ser atingido", "x Acumulacoes de Resistencia por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Resistencia, nao duracao."] [hunter.speed] - name = "Velocidade do Cacador" - description = "Quando es atingido ganhas velocidade, a custo de fome" - lore1 = "Ganha velocidade passiva ao ser atingido" - lore2 = "x Acumulacoes de Velocidade por 3 segundos ao ser atingido" - lore3 = "x Fome acumulativa" - lore4 = "Duracao e multiplicador da fome acumulativa." - lore5 = "Multiplicador de acumulacoes de Velocidade, nao duracao." - lore = ["Ganha velocidade passiva ao ser atingido", "x Acumulacoes de Velocidade por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Velocidade, nao duracao."] +name = "Velocidade do Cacador" +description = "Quando es atingido ganhas velocidade, a custo de fome" +lore1 = "Ganha velocidade passiva ao ser atingido" +lore2 = "x Acumulacoes de Velocidade por 3 segundos ao ser atingido" +lore3 = "x Fome acumulativa" +lore4 = "Duracao e multiplicador da fome acumulativa." +lore5 = "Multiplicador de acumulacoes de Velocidade, nao duracao." +lore = ["Ganha velocidade passiva ao ser atingido", "x Acumulacoes de Velocidade por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Velocidade, nao duracao."] [hunter.strength] - name = "Forca do Cacador" - description = "Quando es atingido ganhas forca, a custo de fome" - lore1 = "Ganha forca passiva ao ser atingido" - lore2 = "x Acumulacoes de Forca por 3 segundos ao ser atingido" - lore3 = "x Fome acumulativa" - lore4 = "Duracao e multiplicador da fome acumulativa." - lore5 = "Multiplicador de acumulacoes de Forca, nao duracao." - lore = ["Ganha forca passiva ao ser atingido", "x Acumulacoes de Forca por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Forca, nao duracao."] +name = "Forca do Cacador" +description = "Quando es atingido ganhas forca, a custo de fome" +lore1 = "Ganha forca passiva ao ser atingido" +lore2 = "x Acumulacoes de Forca por 3 segundos ao ser atingido" +lore3 = "x Fome acumulativa" +lore4 = "Duracao e multiplicador da fome acumulativa." +lore5 = "Multiplicador de acumulacoes de Forca, nao duracao." +lore = ["Ganha forca passiva ao ser atingido", "x Acumulacoes de Forca por 3 segundos ao ser atingido", "x Fome acumulativa", "Duracao e multiplicador da fome acumulativa.", "Multiplicador de acumulacoes de Forca, nao duracao."] # nether [nether] [nether.skull_toss] - name = "Arremesso de Cranio Wither" - description1 = "Liberta o teu Wither interior usando" - description2 = "a cabeca de" - description3 = "alguem." - lore1 = "Segundos de tempo de espera entre arremessos de cranio." - lore2 = "Usando Cranio Wither: Lanca um " - lore3 = "Cranio Wither" - lore4 = "que explode ao impacto." - lore = ["Segundos de tempo de espera entre arremessos de cranio.", "Usando Cranio Wither: Lanca um ", "Cranio Wither", "que explode ao impacto."] +name = "Arremesso de Cranio Wither" +description1 = "Liberta o teu Wither interior usando" +description2 = "a cabeca de" +description3 = "alguem." +lore1 = "Segundos de tempo de espera entre arremessos de cranio." +lore2 = "Usando Cranio Wither: Lanca um " +lore3 = "Cranio Wither" +lore4 = "que explode ao impacto." +lore = ["Segundos de tempo de espera entre arremessos de cranio.", "Usando Cranio Wither: Lanca um ", "Cranio Wither", "que explode ao impacto."] [nether.wither_resist] - name = "Resistencia ao Wither" - description = "Resiste ao wither atraves do poder da Netherite." - lore1 = "chance de negar o wither (por peca)." - lore2 = "Passivo: Usar Armadura de Netherite tem chance de negar " - lore3 = "o wither." - lore = ["chance de negar o wither (por peca).", "Passivo: Usar Armadura de Netherite tem chance de negar ", "o wither."] +name = "Resistencia ao Wither" +description = "Resiste ao wither atraves do poder da Netherite." +lore1 = "chance de negar o wither (por peca)." +lore2 = "Passivo: Usar Armadura de Netherite tem chance de negar " +lore3 = "o wither." +lore = ["chance de negar o wither (por peca).", "Passivo: Usar Armadura de Netherite tem chance de negar ", "o wither."] [nether.fire_resist] - name = "Resistencia ao Fogo" - description = "Resiste ao fogo endurecendo a tua pele." - lore1 = "chance de negar o efeito de queimadura!" - lore = ["chance de negar o efeito de queimadura!"] +name = "Resistencia ao Fogo" +description = "Resiste ao fogo endurecendo a tua pele." +lore1 = "chance de negar o efeito de queimadura!" +lore = ["chance de negar o efeito de queimadura!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Fundicao Automatica" - description = "Permite-te fundir minerios Vanilla extraidos" - lore1 = "Minerios que podem ser fundidos sao fundidos automaticamente" - lore2 = "% de chance de um extra" - lore = ["Minerios que podem ser fundidos sao fundidos automaticamente", "% de chance de um extra"] +name = "Fundicao Automatica" +description = "Permite-te fundir minerios Vanilla extraidos" +lore1 = "Minerios que podem ser fundidos sao fundidos automaticamente" +lore2 = "% de chance de um extra" +lore = ["Minerios que podem ser fundidos sao fundidos automaticamente", "% de chance de um extra"] [pickaxe.chisel] - name = "Cinzel de Minerio" - description = "Clica direito em Minerios para Cinzelar mais minerio deles, a um custo severo de durabilidade." - lore1 = "Chance de Drop" - lore2 = "Desgaste da Ferramenta" - lore = ["Chance de Drop", "Desgaste da Ferramenta"] +name = "Cinzel de Minerio" +description = "Clica direito em Minerios para Cinzelar mais minerio deles, a um custo severo de durabilidade." +lore1 = "Chance de Drop" +lore2 = "Desgaste da Ferramenta" +lore = ["Chance de Drop", "Desgaste da Ferramenta"] [pickaxe.drop_to_inventory] - name = "Picareta Direto-Para-Inventario" - description = "Quando partes um bloco, teleporta o item para o teu inventario" - lore1 = "Sempre que um item cai de um bloco que partes, vai para o teu inventario se possivel." - lore = ["Sempre que um item cai de um bloco que partes, vai para o teu inventario se possivel."] +name = "Picareta Direto-Para-Inventario" +description = "Quando partes um bloco, teleporta o item para o teu inventario" +lore1 = "Sempre que um item cai de um bloco que partes, vai para o teu inventario se possivel." +lore = ["Sempre que um item cai de um bloco que partes, vai para o teu inventario se possivel."] [pickaxe.silk_spawner] - name = "Picareta Silk-Spawner" - description = "Faz Spawners caírem quando partidos" - lore1 = "Torna Spawners quebraveis com toque de seda." - lore2 = "Torna Spawners quebraveis enquanto agachado." - lore = ["Torna Spawners quebraveis com toque de seda.", "Torna Spawners quebraveis enquanto agachado."] +name = "Picareta Silk-Spawner" +description = "Faz Spawners caírem quando partidos" +lore1 = "Torna Spawners quebraveis com toque de seda." +lore2 = "Torna Spawners quebraveis enquanto agachado." +lore = ["Torna Spawners quebraveis com toque de seda.", "Torna Spawners quebraveis enquanto agachado."] [pickaxe.vein_miner] - name = "Mineiro de Veios" - description = "Permite-te partir blocos num Veio/Grupo de minerios Vanilla" - lore1 = "Agacha e minera MINERIOS" - lore2 = "alcance da mineracao de veios" - lore3 = "Esta habilidade NAO agrupa todos os drops juntos!" - lore = ["Agacha e minera MINERIOS", "alcance da mineracao de veios", "Esta habilidade NAO agrupa todos os drops juntos!"] +name = "Mineiro de Veios" +description = "Permite-te partir blocos num Veio/Grupo de minerios Vanilla" +lore1 = "Agacha e minera MINERIOS" +lore2 = "alcance da mineracao de veios" +lore3 = "Esta habilidade NAO agrupa todos os drops juntos!" +lore = ["Agacha e minera MINERIOS", "alcance da mineracao de veios", "Esta habilidade NAO agrupa todos os drops juntos!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Recuperacao de Flechas" - description = "Recupera Flechas depois de matares um inimigo." - lore1 = "Chance de Recuperar Flechas ao Acertar/Matar" - lore2 = "Chance: " - lore = ["Chance de Recuperar Flechas ao Acertar/Matar", "Chance: "] +name = "Recuperacao de Flechas" +description = "Recupera Flechas depois de matares um inimigo." +lore1 = "Chance de Recuperar Flechas ao Acertar/Matar" +lore2 = "Chance: " +lore = ["Chance de Recuperar Flechas ao Acertar/Matar", "Chance: "] [ranged.web_shot] - name = "Armadilha de Teia" - description = "Rodeia o teu alvo com teias de aranha quando o acertas!" - lore1 = "8 Teias de Aranha a volta de uma Bola de Neve, e atira!" - lore2 = "segundos de gaiola, aproximadamente." - lore = ["8 Teias de Aranha a volta de uma Bola de Neve, e atira!", "segundos de gaiola, aproximadamente."] +name = "Armadilha de Teia" +description = "Rodeia o teu alvo com teias de aranha quando o acertas!" +lore1 = "8 Teias de Aranha a volta de uma Bola de Neve, e atira!" +lore2 = "segundos de gaiola, aproximadamente." +lore = ["8 Teias de Aranha a volta de uma Bola de Neve, e atira!", "segundos de gaiola, aproximadamente."] [ranged.force_shot] - name = "Tiro Forcado" - description = "Dispara projeteis mais longe, mais rapido!" - advancementname = "Tiro Longo" - advancementlore = "Acerta um tiro a mais de 30 blocos de distancia!" - lore1 = "Velocidade do Projetil" - lore = ["Velocidade do Projetil"] +name = "Tiro Forcado" +description = "Dispara projeteis mais longe, mais rapido!" +advancementname = "Tiro Longo" +advancementlore = "Acerta um tiro a mais de 30 blocos de distancia!" +lore1 = "Velocidade do Projetil" +lore = ["Velocidade do Projetil"] [ranged.lunge_shot] - name = "Tiro de Estocada" - description = "Enquanto cais, as tuas flechas lancam-te numa direcao aleatoria" - lore1 = "Velocidade de Rajada Aleatoria" - lore = ["Velocidade de Rajada Aleatoria"] +name = "Tiro de Estocada" +description = "Enquanto cais, as tuas flechas lancam-te numa direcao aleatoria" +lore1 = "Velocidade de Rajada Aleatoria" +lore = ["Velocidade de Rajada Aleatoria"] [ranged.arrow_piercing] - name = "Perfuracao de Flechas" - description = "Adiciona Perfuracao a projeteis! Dispara atraves de coisas!" - lore1 = "Alvos Perfurados" - lore = ["Alvos Perfurados"] +name = "Perfuracao de Flechas" +description = "Adiciona Perfuracao a projeteis! Dispara atraves de coisas!" +lore1 = "Alvos Perfurados" +lore = ["Alvos Perfurados"] # rift [rift] [rift.remote_access] - name = "Acesso Remoto" - description = "Puxa do vazio e acede a um contentor marcado." - lore1 = "Perola do Ender + Bussola = Chave Portal do Relicario" - lore2 = "Este item permite-te aceder a contentores remotamente" - lore3 = "Depois de fabricar, olha para o item para ver como usar" - notcontainer = "Isso nao e um contentor" - lore = ["Perola do Ender + Bussola = Chave Portal do Relicario", "Este item permite-te aceder a contentores remotamente", "Depois de fabricar, olha para o item para ver como usar"] +name = "Acesso Remoto" +description = "Puxa do vazio e acede a um contentor marcado." +lore1 = "Perola do Ender + Bussola = Chave Portal do Relicario" +lore2 = "Este item permite-te aceder a contentores remotamente" +lore3 = "Depois de fabricar, olha para o item para ver como usar" +notcontainer = "Isso nao e um contentor" +lore = ["Perola do Ender + Bussola = Chave Portal do Relicario", "Este item permite-te aceder a contentores remotamente", "Depois de fabricar, olha para o item para ver como usar"] [rift.blink] - name = "Piscar da Fenda" - description = "Teletransporte instantaneo de curto alcance, apenas um piscar de olhos!" - lore1 = "Blocos no piscar (2x Vertical)" - lore2 = "Enquanto a Correr: Toca duas vezes em Saltar para " - lore3 = "Piscar" - lore = ["Blocos no piscar (2x Vertical)", "Enquanto a Correr: Toca duas vezes em Saltar para ", "Piscar"] +name = "Piscar da Fenda" +description = "Teletransporte instantaneo de curto alcance, apenas um piscar de olhos!" +lore1 = "Blocos no piscar (2x Vertical)" +lore2 = "Enquanto a Correr: Toca duas vezes em Saltar para " +lore3 = "Piscar" +lore = ["Blocos no piscar (2x Vertical)", "Enquanto a Correr: Toca duas vezes em Saltar para ", "Piscar"] [rift.chest] - name = "Bau do Ender Facil" - description = "Abre um bau do ender clicando nele com o botao esquerdo na tua mao." - lore1 = "Clica num Bau do Ender na tua mao para abrir (Nao o coloques)" - lore = ["Clica num Bau do Ender na tua mao para abrir (Nao o coloques)"] +name = "Bau do Ender Facil" +description = "Abre um bau do ender clicando nele com o botao esquerdo na tua mao." +lore1 = "Clica num Bau do Ender na tua mao para abrir (Nao o coloques)" +lore = ["Clica num Bau do Ender na tua mao para abrir (Nao o coloques)"] [rift.descent] - name = "Anti-Levitacao" - description = "Estas cansado de ficar preso no ar? Esta e a habilidade para ti!" - lore1 = "Basta agachares para descer, e vais cair a uma velocidade menor que o normal!" - lore2 = "Tempo de espera:" - lore = ["Basta agachares para descer, e vais cair a uma velocidade menor que o normal!", "Tempo de espera:"] +name = "Anti-Levitacao" +description = "Estas cansado de ficar preso no ar? Esta e a habilidade para ti!" +lore1 = "Basta agachares para descer, e vais cair a uma velocidade menor que o normal!" +lore2 = "Tempo de espera:" +lore = ["Basta agachares para descer, e vais cair a uma velocidade menor que o normal!", "Tempo de espera:"] [rift.gate] - name = "Portal da Fenda" - description = "Teletransporta para um local marcado." - lore1 = "FABRICO: Esmeralda + Fragmento de Ametista + Perola do Ender" - lore2 = "Le antes de usar!" - lore3 = "5s de atraso, " - lore4 = "podes morrer enquanto estiveres nesta animacao" - lore = ["FABRICO: Esmeralda + Fragmento de Ametista + Perola do Ender", "Le antes de usar!", "5s de atraso, ", "podes morrer enquanto estiveres nesta animacao"] +name = "Portal da Fenda" +description = "Teletransporta para um local marcado." +lore1 = "FABRICO: Esmeralda + Fragmento de Ametista + Perola do Ender" +lore2 = "Le antes de usar!" +lore3 = "5s de atraso, " +lore4 = "podes morrer enquanto estiveres nesta animacao" +lore = ["FABRICO: Esmeralda + Fragmento de Ametista + Perola do Ender", "Le antes de usar!", "5s de atraso, ", "podes morrer enquanto estiveres nesta animacao"] [rift.resist] - name = "Resistencia da Fenda" - description = "Ganha Resistencia ao usar Itens e Habilidades do Ender" - lore1 = "+ Passivo: Fornece resistencia quando usas habilidades da fenda, ou Itens do Ender" - lore2 = "NAO incluindo Bau do Ender Portatil, apenas coisas que podes Consumir" - lore = ["+ Passivo: Fornece resistencia quando usas habilidades da fenda, ou Itens do Ender", "NAO incluindo Bau do Ender Portatil, apenas coisas que podes Consumir"] +name = "Resistencia da Fenda" +description = "Ganha Resistencia ao usar Itens e Habilidades do Ender" +lore1 = "+ Passivo: Fornece resistencia quando usas habilidades da fenda, ou Itens do Ender" +lore2 = "NAO incluindo Bau do Ender Portatil, apenas coisas que podes Consumir" +lore = ["+ Passivo: Fornece resistencia quando usas habilidades da fenda, ou Itens do Ender", "NAO incluindo Bau do Ender Portatil, apenas coisas que podes Consumir"] [rift.visage] - name = "Visagem da Fenda" - description = "Impede Endermen de ficarem agressivos se tiveres Perolas do Ender no teu inventario." - lore1 = "Endermen nao ficam agressivos se tiveres Perolas do Ender no teu inventario." - lore = ["Endermen nao ficam agressivos se tiveres Perolas do Ender no teu inventario."] +name = "Visagem da Fenda" +description = "Impede Endermen de ficarem agressivos se tiveres Perolas do Ender no teu inventario." +lore1 = "Endermen nao ficam agressivos se tiveres Perolas do Ender no teu inventario." +lore = ["Endermen nao ficam agressivos se tiveres Perolas do Ender no teu inventario."] # seaborn [seaborn] [seaborn.oxygen] - name = "Tanque de Oxigenio Organico" - description = "Mantem mais oxigenio nos teus pequenos pulmoes!" - lore1 = "Aumento da Capacidade de Oxigenio" - lore = ["Aumento da Capacidade de Oxigenio"] +name = "Tanque de Oxigenio Organico" +description = "Mantem mais oxigenio nos teus pequenos pulmoes!" +lore1 = "Aumento da Capacidade de Oxigenio" +lore = ["Aumento da Capacidade de Oxigenio"] [seaborn.fishers_fantasy] - name = "Fantasia do Pescador" - description = "Ganha mais XP ao pescar e apanha mais peixe!" - lore1 = "Por cada nivel ha uma chance de obter mais XP e Peixe!" - lore = ["Por cada nivel ha uma chance de obter mais XP e Peixe!"] +name = "Fantasia do Pescador" +description = "Ganha mais XP ao pescar e apanha mais peixe!" +lore1 = "Por cada nivel ha uma chance de obter mais XP e Peixe!" +lore = ["Por cada nivel ha uma chance de obter mais XP e Peixe!"] [seaborn.haste] - name = "Tartaruga Mineira" - description = "Ao minerar debaixo de agua ganhas pressa!" - lore1 = "Pressa 3 e aplicada debaixo de agua enquanto mineras (acumula com AquaAffinity) depois do efeito de respiracao aquatica acabar!" - lore = ["Pressa 3 e aplicada debaixo de agua enquanto mineras (acumula com AquaAffinity) depois do efeito de respiracao aquatica acabar!"] +name = "Tartaruga Mineira" +description = "Ao minerar debaixo de agua ganhas pressa!" +lore1 = "Pressa 3 e aplicada debaixo de agua enquanto mineras (acumula com AquaAffinity) depois do efeito de respiracao aquatica acabar!" +lore = ["Pressa 3 e aplicada debaixo de agua enquanto mineras (acumula com AquaAffinity) depois do efeito de respiracao aquatica acabar!"] [seaborn.night_vision] - name = "Visao da Tartaruga" - description = "Enquanto debaixo de agua, ganhas Visao Noturna" - lore1 = "Simplesmente ganha Visao Noturna enquanto debaixo de agua depois do efeito de respiracao aquatica acabar!" - lore = ["Simplesmente ganha Visao Noturna enquanto debaixo de agua depois do efeito de respiracao aquatica acabar!"] +name = "Visao da Tartaruga" +description = "Enquanto debaixo de agua, ganhas Visao Noturna" +lore1 = "Simplesmente ganha Visao Noturna enquanto debaixo de agua depois do efeito de respiracao aquatica acabar!" +lore = ["Simplesmente ganha Visao Noturna enquanto debaixo de agua depois do efeito de respiracao aquatica acabar!"] [seaborn.dolphin_grace] - name = "Graca do Golfinho" - description = "Nada como um golfinho, sem os golfinhos" - lore1 = "+ Passivo: ganha " - lore2 = "x velocidade (graca do golfinho)" - lore3 = "engenharia alema de precisao- espera, isso nao esta certo... Nao Compativel com Profundidade" - lore = ["+ Passivo: ganha ", "x velocidade (graca do golfinho)", "engenharia alema de precisao- espera, isso nao esta certo... Nao Compativel com Profundidade"] +name = "Graca do Golfinho" +description = "Nada como um golfinho, sem os golfinhos" +lore1 = "+ Passivo: ganha " +lore2 = "x velocidade (graca do golfinho)" +lore3 = "engenharia alema de precisao- espera, isso nao esta certo... Nao Compativel com Profundidade" +lore = ["+ Passivo: ganha ", "x velocidade (graca do golfinho)", "engenharia alema de precisao- espera, isso nao esta certo... Nao Compativel com Profundidade"] # stealth [stealth] [stealth.ghost_armor] - name = "Armadura Fantasma" - description = "Armadura que se constroi lentamente quando nao recebes dano, dura 1 golpe" - lore1 = "Armadura Maxima" - lore2 = "Velocidade" - lore = ["Armadura Maxima", "Velocidade"] +name = "Armadura Fantasma" +description = "Armadura que se constroi lentamente quando nao recebes dano, dura 1 golpe" +lore1 = "Armadura Maxima" +lore2 = "Velocidade" +lore = ["Armadura Maxima", "Velocidade"] [stealth.night_vision] - name = "Visao Furtiva" - description = "Ganha visao noturna enquanto agachado" - lore1 = "Ganha um surto de " - lore2 = "visao noturna" - lore3 = "enquanto agachado" - lore = ["Ganha um surto de ", "visao noturna", "enquanto agachado"] +name = "Visao Furtiva" +description = "Ganha visao noturna enquanto agachado" +lore1 = "Ganha um surto de " +lore2 = "visao noturna" +lore3 = "enquanto agachado" +lore = ["Ganha um surto de ", "visao noturna", "enquanto agachado"] [stealth.snatch] - name = "Apanhar Itens" - description = "Apanha itens largados instantaneamente enquanto agachado!" - lore1 = "Raio de Apanhar" - lore = ["Raio de Apanhar"] +name = "Apanhar Itens" +description = "Apanha itens largados instantaneamente enquanto agachado!" +lore1 = "Raio de Apanhar" +lore = ["Raio de Apanhar"] [stealth.speed] - name = "Velocidade Furtiva" - description = "Ganha velocidade enquanto agachado" - lore1 = "Velocidade ao Agachar" - lore = ["Velocidade ao Agachar"] +name = "Velocidade Furtiva" +description = "Ganha velocidade enquanto agachado" +lore1 = "Velocidade ao Agachar" +lore = ["Velocidade ao Agachar"] [stealth.ender_veil] - name = "Veu do Ender" - description = "Sem mais Aboboras para prevenir ataques de Endermen" - lore1 = "Previne ataques de endermen enquanto agachado" - lore2 = "Previne todos os ataques de endermen" - lore = ["Previne ataques de endermen enquanto agachado", "Previne todos os ataques de endermen"] +name = "Veu do Ender" +description = "Sem mais Aboboras para prevenir ataques de Endermen" +lore1 = "Previne ataques de endermen enquanto agachado" +lore2 = "Previne todos os ataques de endermen" +lore = ["Previne ataques de endermen enquanto agachado", "Previne todos os ataques de endermen"] # sword [sword] [sword.machete] - name = "Machete" - description = "Corta folhagem com facilidade!" - lore1 = "Raio de Corte" - lore2 = "Tempo de Espera do Corte" - lore3 = "Desgaste da Ferramenta" - lore = ["Raio de Corte", "Tempo de Espera do Corte", "Desgaste da Ferramenta"] +name = "Machete" +description = "Corta folhagem com facilidade!" +lore1 = "Raio de Corte" +lore2 = "Tempo de Espera do Corte" +lore3 = "Desgaste da Ferramenta" +lore = ["Raio de Corte", "Tempo de Espera do Corte", "Desgaste da Ferramenta"] [sword.bloody_blade] - name = "Lamina Sangrenta" - description = "Golpes com a tua espada causam Sangramento!" - lore1 = "Atingir uma entidade viva com a tua Espada causa Sangramento" - lore2 = "Duracao do Sangramento" - lore3 = "Tempo de Espera do Sangramento" - lore = ["Atingir uma entidade viva com a tua Espada causa Sangramento", "Duracao do Sangramento", "Tempo de Espera do Sangramento"] +name = "Lamina Sangrenta" +description = "Golpes com a tua espada causam Sangramento!" +lore1 = "Atingir uma entidade viva com a tua Espada causa Sangramento" +lore2 = "Duracao do Sangramento" +lore3 = "Tempo de Espera do Sangramento" +lore = ["Atingir uma entidade viva com a tua Espada causa Sangramento", "Duracao do Sangramento", "Tempo de Espera do Sangramento"] [sword.poisoned_blade] - name = "Lamina Envenenada" - description = "Golpes com a tua espada causam Veneno!" - lore1 = "Atingir uma entidade viva com a tua Espada causa Veneno" - lore2 = "Duracao do Veneno" - lore3 = "Tempo de Espera do Veneno" - lore = ["Atingir uma entidade viva com a tua Espada causa Veneno", "Duracao do Veneno", "Tempo de Espera do Veneno"] +name = "Lamina Envenenada" +description = "Golpes com a tua espada causam Veneno!" +lore1 = "Atingir uma entidade viva com a tua Espada causa Veneno" +lore2 = "Duracao do Veneno" +lore3 = "Tempo de Espera do Veneno" +lore = ["Atingir uma entidade viva com a tua Espada causa Veneno", "Duracao do Veneno", "Tempo de Espera do Veneno"] # taming [taming] [taming.damage] - name = "Dano de Domesticado" - description = "Aumenta o dano causado pelo teu animal domesticado." - lore1 = "Dano Aumentado" - lore = ["Dano Aumentado"] +name = "Dano de Domesticado" +description = "Aumenta o dano causado pelo teu animal domesticado." +lore1 = "Dano Aumentado" +lore = ["Dano Aumentado"] [taming.health] - name = "Saude de Domesticado" - description = "Aumenta a saude do teu animal domesticado." - lore1 = "Saude Aumentada" - lore = ["Saude Aumentada"] +name = "Saude de Domesticado" +description = "Aumenta a saude do teu animal domesticado." +lore1 = "Saude Aumentada" +lore = ["Saude Aumentada"] [taming.regeneration] - name = "Regeneracao de Domesticado" - description = "Aumenta a regeneracao do teu animal domesticado." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Regeneracao de Domesticado" +description = "Aumenta a regeneracao do teu animal domesticado." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Espinhos" - description = "Reflete dano de volta ao teu atacante!" - lore1 = "Dano retaliado quando atingido" - lore = ["Dano retaliado quando atingido"] +name = "Espinhos" +description = "Reflete dano de volta ao teu atacante!" +lore1 = "Dano retaliado quando atingido" +lore = ["Dano retaliado quando atingido"] [tragoul.globe] - name = "Globo de Dor" - description = "Divide o Dano que causas baseado no numero de inimigos a tua volta!" - lore1 = "Quanto mais inimigos a tua volta, menos dano causas a cada um" - lore2 = "Alcance: " - lore3 = "Dano Adicionado a todas as Entidades: " - lore = ["Quanto mais inimigos a tua volta, menos dano causas a cada um", "Alcance: ", "Dano Adicionado a todas as Entidades: "] +name = "Globo de Dor" +description = "Divide o Dano que causas baseado no numero de inimigos a tua volta!" +lore1 = "Quanto mais inimigos a tua volta, menos dano causas a cada um" +lore2 = "Alcance: " +lore3 = "Dano Adicionado a todas as Entidades: " +lore = ["Quanto mais inimigos a tua volta, menos dano causas a cada um", "Alcance: ", "Dano Adicionado a todas as Entidades: "] [tragoul.healing] - name = "Vontade da Dor" - description = "Recupera saude baseado no dano que causas!" - lore1 = "Magoar coisas nunca soube tao bem! Cura com o Dano Causado" - lore2 = "Ha uma janela de dano de 3 Segundos, para cura e um Tempo de Espera de 1 Segundo " - lore3 = "Cura Por Percentagem de Dano: " - lore = ["Magoar coisas nunca soube tao bem! Cura com o Dano Causado", "Ha uma janela de dano de 3 Segundos, para cura e um Tempo de Espera de 1 Segundo ", "Cura Por Percentagem de Dano: "] +name = "Vontade da Dor" +description = "Recupera saude baseado no dano que causas!" +lore1 = "Magoar coisas nunca soube tao bem! Cura com o Dano Causado" +lore2 = "Ha uma janela de dano de 3 Segundos, para cura e um Tempo de Espera de 1 Segundo " +lore3 = "Cura Por Percentagem de Dano: " +lore = ["Magoar coisas nunca soube tao bem! Cura com o Dano Causado", "Ha uma janela de dano de 3 Segundos, para cura e um Tempo de Espera de 1 Segundo ", "Cura Por Percentagem de Dano: "] [tragoul.lance] - name = "Lancas Cadavericas" - description = "Matar um inimigo ou uma habilidade matar um inimigo gera uma lanca que causa dano a um inimigo proximo!" - lore1 = "As Lancas procuram a partir de tudo o que matas, E se esta habilidade matar um inimigo." - lore2 = "Sacrifica uma porcao da tua vida para criar as lancas (isto pode matar-te)" - lore3 = "Lancas Maximas: 1 + " - lore = ["As Lancas procuram a partir de tudo o que matas, E se esta habilidade matar um inimigo.", "Sacrifica uma porcao da tua vida para criar as lancas (isto pode matar-te)", "Lancas Maximas: 1 + "] +name = "Lancas Cadavericas" +description = "Matar um inimigo ou uma habilidade matar um inimigo gera uma lanca que causa dano a um inimigo proximo!" +lore1 = "As Lancas procuram a partir de tudo o que matas, E se esta habilidade matar um inimigo." +lore2 = "Sacrifica uma porcao da tua vida para criar as lancas (isto pode matar-te)" +lore3 = "Lancas Maximas: 1 + " +lore = ["As Lancas procuram a partir de tudo o que matas, E se esta habilidade matar um inimigo.", "Sacrifica uma porcao da tua vida para criar as lancas (isto pode matar-te)", "Lancas Maximas: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Canhao de Vidro" - description = "Bonus de Dano Desarmado quanto menor for o teu valor de armadura" - lore1 = "x Dano com 0 de armadura" - lore2 = "Dano Bonus PorNivel" - lore = ["x Dano com 0 de armadura", "Dano Bonus PorNivel"] +name = "Canhao de Vidro" +description = "Bonus de Dano Desarmado quanto menor for o teu valor de armadura" +lore1 = "x Dano com 0 de armadura" +lore2 = "Dano Bonus PorNivel" +lore = ["x Dano com 0 de armadura", "Dano Bonus PorNivel"] [unarmed.power] - name = "Poder Desarmado" - description = "Dano Desarmado Melhorado" - lore1 = "Dano" - lore = ["Dano"] +name = "Poder Desarmado" +description = "Dano Desarmado Melhorado" +lore1 = "Dano" +lore = ["Dano"] [unarmed.sucker_punch] - name = "Soco Surpresa" - description = "Socos a correr, mas mais mortiferos." - lore1 = "Dano" - lore2 = "O dano aumenta com a tua velocidade enquanto socas" - lore = ["Dano", "O dano aumenta com a tua velocidade enquanto socas"] +name = "Soco Surpresa" +description = "Socos a correr, mas mais mortiferos." +lore1 = "Dano" +lore2 = "O dano aumenta com a tua velocidade enquanto socas" +lore = ["Dano", "O dano aumenta com a tua velocidade enquanto socas"] diff --git a/src/main/resources/ru_RU.toml b/src/main/resources/ru_RU.toml index a1a84dadf..8adee7f6c 100644 --- a/src/main/resources/ru_RU.toml +++ b/src/main/resources/ru_RU.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "Нужно двигаться!" - description = "Пройдите более 1 километра (1 000 блоков)" +title = "Нужно двигаться!" +description = "Пройдите более 1 километра (1 000 блоков)" [advancement.challenge_sprint_5k] - title = "Пробегите 5 км!" - description = "Пройдите более 5 километров (5 000 блоков)" +title = "Пробегите 5 км!" +description = "Пройдите более 5 километров (5 000 блоков)" [advancement.challenge_sprint_50k] - title = "Промчитесь 50 км!" - description = "Пройдите более 50 километров (50 000 блоков)" +title = "Промчитесь 50 км!" +description = "Пройдите более 50 километров (50 000 блоков)" [advancement.challenge_sprint_500k] - title = "Путешествие по Вселенной!!" - description = "Пройдите более 500 километров (500 000 блоков)" +title = "Путешествие по Вселенной!!" +description = "Пройдите более 500 километров (500 000 блоков)" [advancement.challenge_sprint_marathon] - title = "Пробегите (буквально) марафон!" - description = "Пробегите более 42 195 блоков!" +title = "Пробегите (буквально) марафон!" +description = "Пробегите более 42 195 блоков!" [advancement.challenge_place_1k] - title = "Начинающий строитель!" - description = "Разместите 1 000 блоков" +title = "Начинающий строитель!" +description = "Разместите 1 000 блоков" [advancement.challenge_place_5k] - title = "Продвинутый строитель!" - description = "Разместите 5 000 блоков" +title = "Продвинутый строитель!" +description = "Разместите 5 000 блоков" [advancement.challenge_place_50k] - title = "Опытный строитель!" - description = "Разместите 50 000 блоков" +title = "Опытный строитель!" +description = "Разместите 50 000 блоков" [advancement.challenge_place_500k] - title = "Мастер-строитель!" - description = "Разместите 500 000 блоков" +title = "Мастер-строитель!" +description = "Разместите 500 000 блоков" [advancement.challenge_place_5m] - title = "Аколит Симметрии!" - description = "РЕАЛЬНОСТЬ -- ВАША ИГРОВАЯ ПЛОЩАДКА! (5 миллионов блоков)" +title = "Аколит Симметрии!" +description = "РЕАЛЬНОСТЬ -- ВАША ИГРОВАЯ ПЛОЩАДКА! (5 миллионов блоков)" [advancement.challenge_chop_1k] - title = "Начинающий лесоруб!" - description = "Срубите 1 000 блоков" +title = "Начинающий лесоруб!" +description = "Срубите 1 000 блоков" [advancement.challenge_chop_5k] - title = "Продвинутый лесоруб!" - description = "Срубите 5 000 блоков" +title = "Продвинутый лесоруб!" +description = "Срубите 5 000 блоков" [advancement.challenge_chop_50k] - title = "Опытный лесоруб!" - description = "Срубите 50 000 блоков" +title = "Опытный лесоруб!" +description = "Срубите 50 000 блоков" [advancement.challenge_chop_500k] - title = "Мастер-лесоруб!" - description = "Срубите 500 000 блоков" +title = "Мастер-лесоруб!" +description = "Срубите 500 000 блоков" [advancement.challenge_chop_5m] - title = "Пёс Джексон" - description = "Самый лучший, добрый мальчик! (5 миллионов блоков)" +title = "Пёс Джексон" +description = "Самый лучший, добрый мальчик! (5 миллионов блоков)" [advancement.challenge_block_1k] - title = "Едва блокирую!" - description = "Заблокируйте 1 000 ударов" +title = "Едва блокирую!" +description = "Заблокируйте 1 000 ударов" [advancement.challenge_block_5k] - title = "Блокировать -- это весело!" - description = "Заблокируйте 5 000 ударов" +title = "Блокировать -- это весело!" +description = "Заблокируйте 5 000 ударов" [advancement.challenge_block_50k] - title = "Блокирование -- моя жизнь!" - description = "Заблокируйте 50 000 ударов" +title = "Блокирование -- моя жизнь!" +description = "Заблокируйте 50 000 ударов" [advancement.challenge_block_500k] - title = "Блокирование -- моё предназначение!" - description = "Заблокируйте 500 000 ударов" +title = "Блокирование -- моё предназначение!" +description = "Заблокируйте 500 000 ударов" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Заблокируйте 5 000 000 ударов" +title = "Die Hand Die Verletzt" +description = "Заблокируйте 5 000 000 ударов" [advancement.challenge_brew_1k] - title = "Начинающий алхимик!" - description = "Выпейте 1 000 зелий" +title = "Начинающий алхимик!" +description = "Выпейте 1 000 зелий" [advancement.challenge_brew_5k] - title = "Продвинутый алхимик!" - description = "Выпейте 5 000 зелий" +title = "Продвинутый алхимик!" +description = "Выпейте 5 000 зелий" [advancement.challenge_brew_50k] - title = "Опытный алхимик!" - description = "Выпейте 50 000 зелий" +title = "Опытный алхимик!" +description = "Выпейте 50 000 зелий" [advancement.challenge_brew_500k] - title = "Мастер-алхимик!" - description = "Выпейте 500 000 зелий" +title = "Мастер-алхимик!" +description = "Выпейте 500 000 зелий" [advancement.challenge_brew_5m] - title = "Великий Алхимик" - description = "Выпейте 5 000 000 зелий" +title = "Великий Алхимик" +description = "Выпейте 5 000 000 зелий" [advancement.challenge_brewsplash_1k] - title = "Начинающий зельевар!" - description = "Выплесните 1 000 зелий" +title = "Начинающий зельевар!" +description = "Выплесните 1 000 зелий" [advancement.challenge_brewsplash_5k] - title = "Продвинутый зельевар!" - description = "Выплесните 5 000 зелий" +title = "Продвинутый зельевар!" +description = "Выплесните 5 000 зелий" [advancement.challenge_brewsplash_50k] - title = "Опытный зельевар!" - description = "Выплесните 50 000 зелий" +title = "Опытный зельевар!" +description = "Выплесните 50 000 зелий" [advancement.challenge_brewsplash_500k] - title = "Мастер-зельевар!" - description = "Выплесните 500 000 зелий" +title = "Мастер-зельевар!" +description = "Выплесните 500 000 зелий" [advancement.challenge_brewsplash_5m] - title = "Брызгомейстер" - description = "Выплесните 5 000 000 зелий" +title = "Брызгомейстер" +description = "Выплесните 5 000 000 зелий" [advancement.challenge_craft_1k] - title = "Ловкий ремесленник!" - description = "Создайте 1 000 предметов" +title = "Ловкий ремесленник!" +description = "Создайте 1 000 предметов" [advancement.challenge_craft_5k] - title = "Сварливый ремесленник!" - description = "Создайте 5 000 предметов" +title = "Сварливый ремесленник!" +description = "Создайте 5 000 предметов" [advancement.challenge_craft_50k] - title = "Усердный ремесленник!" - description = "Создайте 50 000 предметов" +title = "Усердный ремесленник!" +description = "Создайте 50 000 предметов" [advancement.challenge_craft_500k] - title = "Неугомонный ремесленник!" - description = "Создайте 500 000 предметов" +title = "Неугомонный ремесленник!" +description = "Создайте 500 000 предметов" [advancement.challenge_craft_5m] - title = "Катастрофический Маккрафтфейс" - description = "Создайте 5 000 000 предметов" +title = "Катастрофический Маккрафтфейс" +description = "Создайте 5 000 000 предметов" [advancement.challenge_enchant_1k] - title = "Начинающий зачарователь!" - description = "Зачаруйте 1 000 предметов" +title = "Начинающий зачарователь!" +description = "Зачаруйте 1 000 предметов" [advancement.challenge_enchant_5k] - title = "Продвинутый зачарователь!" - description = "Зачаруйте 5 000 предметов" +title = "Продвинутый зачарователь!" +description = "Зачаруйте 5 000 предметов" [advancement.challenge_enchant_50k] - title = "Опытный зачарователь!" - description = "Зачаруйте 50 000 предметов" +title = "Опытный зачарователь!" +description = "Зачаруйте 50 000 предметов" [advancement.challenge_enchant_500k] - title = "Мастер-зачарователь!" - description = "Зачаруйте 500 000 предметов" +title = "Мастер-зачарователь!" +description = "Зачаруйте 500 000 предметов" [advancement.challenge_enchant_5m] - title = "Загадочный зачарователь" - description = "Зачаруйте 5 000 000 предметов" +title = "Загадочный зачарователь" +description = "Зачаруйте 5 000 000 предметов" [advancement.challenge_excavate_1k] - title = "Начинающий копатель!" - description = "Вскопайте 1 000 блоков" +title = "Начинающий копатель!" +description = "Вскопайте 1 000 блоков" [advancement.challenge_excavate_5k] - title = "Продвинутый копатель!" - description = "Вскопайте 5 000 блоков" +title = "Продвинутый копатель!" +description = "Вскопайте 5 000 блоков" [advancement.challenge_excavate_50k] - title = "Опытный копатель!" - description = "Вскопайте 50 000 блоков" +title = "Опытный копатель!" +description = "Вскопайте 50 000 блоков" [advancement.challenge_excavate_500k] - title = "Мастер-копатель!" - description = "Вскопайте 500 000 блоков" +title = "Мастер-копатель!" +description = "Вскопайте 500 000 блоков" [advancement.challenge_excavate_5m] - title = "Загадочный копатель" - description = "Вскопайте 5 000 000 блоков" +title = "Загадочный копатель" +description = "Вскопайте 5 000 000 блоков" [advancement.horrible_person] - title = "Ты ужасный человек" - description = "Непостижимо, правда" +title = "Ты ужасный человек" +description = "Непостижимо, правда" [advancement.challenge_turtle_egg_smasher] - title = "Крушитель черепашьих яиц!" - description = "Разбейте 100 черепашьих яиц" +title = "Крушитель черепашьих яиц!" +description = "Разбейте 100 черепашьих яиц" [advancement.challenge_turtle_egg_annihilator] - title = "Уничтожитель черепашьих яиц!" - description = "Разбейте 500 черепашьих яиц" +title = "Уничтожитель черепашьих яиц!" +description = "Разбейте 500 черепашьих яиц" [advancement.challenge_novice_hunter] - title = "Начинающий охотник!" - description = "Убейте 100 существ" +title = "Начинающий охотник!" +description = "Убейте 100 существ" [advancement.challenge_intermediate_hunter] - title = "Продвинутый охотник!" - description = "Убейте 500 существ" +title = "Продвинутый охотник!" +description = "Убейте 500 существ" [advancement.challenge_advanced_hunter] - title = "Опытный охотник!" - description = "Убейте 5 000 существ" +title = "Опытный охотник!" +description = "Убейте 5 000 существ" [advancement.challenge_creeper_conqueror] - title = "Покоритель криперов!" - description = "Убейте 50 криперов" +title = "Покоритель криперов!" +description = "Убейте 50 криперов" [advancement.challenge_creeper_annihilator] - title = "Уничтожитель криперов!" - description = "Убейте 200 криперов" +title = "Уничтожитель криперов!" +description = "Убейте 200 криперов" [advancement.challenge_pickaxe_1k] - title = "Начинающий шахтёр" - description = "Сломайте 1 000 блоков" +title = "Начинающий шахтёр" +description = "Сломайте 1 000 блоков" [advancement.challenge_pickaxe_5k] - title = "Опытный шахтёр" - description = "Сломайте 5 000 блоков" +title = "Опытный шахтёр" +description = "Сломайте 5 000 блоков" [advancement.challenge_pickaxe_50k] - title = "Шахтёр-эксперт" - description = "Сломайте 50 000 блоков" +title = "Шахтёр-эксперт" +description = "Сломайте 50 000 блоков" [advancement.challenge_pickaxe_500k] - title = "Мастер-шахтёр" - description = "Сломайте 500 000 блоков" +title = "Мастер-шахтёр" +description = "Сломайте 500 000 блоков" [advancement.challenge_pickaxe_5m] - title = "Легендарный шахтёр" - description = "Сломайте 5 000 000 блоков" +title = "Легендарный шахтёр" +description = "Сломайте 5 000 000 блоков" [advancement.challenge_eat_100] - title = "Сколько всего вкусного!" - description = "Съешьте более 100 предметов!" +title = "Сколько всего вкусного!" +description = "Съешьте более 100 предметов!" [advancement.challenge_eat_1000] - title = "Неутолимый голод!" - description = "Съешьте более 1 000 предметов!" +title = "Неутолимый голод!" +description = "Съешьте более 1 000 предметов!" [advancement.challenge_eat_10000] - title = "ВЕЧНЫЙ ГОЛОД!" - description = "Съешьте более 10 000 предметов!" +title = "ВЕЧНЫЙ ГОЛОД!" +description = "Съешьте более 10 000 предметов!" [advancement.challenge_harvest_100] - title = "Полный урожай" - description = "Соберите более 100 культур!" +title = "Полный урожай" +description = "Соберите более 100 культур!" [advancement.challenge_harvest_1000] - title = "Великий урожай" - description = "Соберите более 1 000 культур!" +title = "Великий урожай" +description = "Соберите более 1 000 культур!" [advancement.challenge_swim_1nm] - title = "Человек-подлодка!" - description = "Проплывите 1 морскую милю (1 852 блока)" +title = "Человек-подлодка!" +description = "Проплывите 1 морскую милю (1 852 блока)" [advancement.challenge_sneak_1k] - title = "Больные колени" - description = "Прокрадитесь более километра (1 000 блоков)" +title = "Больные колени" +description = "Прокрадитесь более километра (1 000 блоков)" [advancement.challenge_sneak_5k] - title = "Ходок Теней" - description = "Прокрадитесь более 5 000 блоков" +title = "Ходок Теней" +description = "Прокрадитесь более 5 000 блоков" [advancement.challenge_sneak_20k] - title = "Призрак" - description = "Прокрадитесь более 20 000 блоков" +title = "Призрак" +description = "Прокрадитесь более 20 000 блоков" [advancement.challenge_swim_5k] - title = "Глубоководный Ныряльщик" - description = "Проплывите более 5 000 блоков" +title = "Глубоководный Ныряльщик" +description = "Проплывите более 5 000 блоков" [advancement.challenge_swim_20k] - title = "Избранник Посейдона" - description = "Проплывите более 20 000 блоков" +title = "Избранник Посейдона" +description = "Проплывите более 20 000 блоков" [advancement.challenge_sword_100] - title = "Первая Кровь" - description = "Нанесите 100 ударов мечом" +title = "Первая Кровь" +description = "Нанесите 100 ударов мечом" [advancement.challenge_sword_1k] - title = "Танцор Клинка" - description = "Нанесите 1 000 ударов мечом" +title = "Танцор Клинка" +description = "Нанесите 1 000 ударов мечом" [advancement.challenge_sword_10k] - title = "Тысяча Порезов" - description = "Нанесите 10 000 ударов мечом" +title = "Тысяча Порезов" +description = "Нанесите 10 000 ударов мечом" [advancement.challenge_unarmed_100] - title = "Барный Драчун" - description = "Нанесите 100 ударов голыми руками" +title = "Барный Драчун" +description = "Нанесите 100 ударов голыми руками" [advancement.challenge_unarmed_1k] - title = "Железные Кулаки" - description = "Нанесите 1 000 ударов голыми руками" +title = "Железные Кулаки" +description = "Нанесите 1 000 ударов голыми руками" [advancement.challenge_unarmed_10k] - title = "Один Удар" - description = "Нанесите 10 000 ударов голыми руками" +title = "Один Удар" +description = "Нанесите 10 000 ударов голыми руками" [advancement.challenge_trag_1k] - title = "Кровавая Цена" - description = "Получите 1 000 урона" +title = "Кровавая Цена" +description = "Получите 1 000 урона" [advancement.challenge_trag_10k] - title = "Багровый Прилив" - description = "Получите 10 000 урона" +title = "Багровый Прилив" +description = "Получите 10 000 урона" [advancement.challenge_trag_100k] - title = "Аватар Страдания" - description = "Получите 100 000 урона" +title = "Аватар Страдания" +description = "Получите 100 000 урона" [advancement.challenge_ranged_100] - title = "Учебная Стрельба" - description = "Выпустите 100 снарядов" +title = "Учебная Стрельба" +description = "Выпустите 100 снарядов" [advancement.challenge_ranged_1k] - title = "Соколиный Глаз" - description = "Выпустите 1 000 снарядов" +title = "Соколиный Глаз" +description = "Выпустите 1 000 снарядов" [advancement.challenge_ranged_10k] - title = "Буря Стрел" - description = "Выпустите 10 000 снарядов" +title = "Буря Стрел" +description = "Выпустите 10 000 снарядов" [advancement.challenge_chronos_1h] - title = "Тик-Так" - description = "Проведите 1 час в сети" +title = "Тик-Так" +description = "Проведите 1 час в сети" [advancement.challenge_chronos_24h] - title = "Пески Времени" - description = "Проведите 24 часа в сети" +title = "Пески Времени" +description = "Проведите 24 часа в сети" [advancement.challenge_chronos_168h] - title = "Вне Времени" - description = "Проведите 168 часов (1 неделю) в сети" +title = "Вне Времени" +description = "Проведите 168 часов (1 неделю) в сети" [advancement.challenge_nether_50] - title = "Привратник Ада" - description = "Убейте 50 существ Нижнего мира" +title = "Привратник Ада" +description = "Убейте 50 существ Нижнего мира" [advancement.challenge_nether_500] - title = "Страж Бездны" - description = "Убейте 500 существ Нижнего мира" +title = "Страж Бездны" +description = "Убейте 500 существ Нижнего мира" [advancement.challenge_nether_5k] - title = "Повелитель Нижнего Мира" - description = "Убейте 5 000 существ Нижнего мира" +title = "Повелитель Нижнего Мира" +description = "Убейте 5 000 существ Нижнего мира" [advancement.challenge_rift_50] - title = "Пространственная Аномалия" - description = "Телепортируйтесь 50 раз" +title = "Пространственная Аномалия" +description = "Телепортируйтесь 50 раз" [advancement.challenge_rift_500] - title = "Ходок Пустоты" - description = "Телепортируйтесь 500 раз" +title = "Ходок Пустоты" +description = "Телепортируйтесь 500 раз" [advancement.challenge_rift_5k] - title = "Между Мирами" - description = "Телепортируйтесь 5 000 раз" +title = "Между Мирами" +description = "Телепортируйтесь 5 000 раз" [advancement.challenge_taming_10] - title = "Шептун Зверей" - description = "Разведите 10 животных" +title = "Шептун Зверей" +description = "Разведите 10 животных" [advancement.challenge_taming_50] - title = "Вожак Стаи" - description = "Разведите 50 животных" +title = "Вожак Стаи" +description = "Разведите 50 животных" [advancement.challenge_taming_500] - title = "Повелитель Зверей" - description = "Разведите 500 животных" +title = "Повелитель Зверей" +description = "Разведите 500 животных" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Демон Скорости" - description = "Пробегите более 5 километров (5,000 блоков)" +title = "Демон Скорости" +description = "Пробегите более 5 километров (5,000 блоков)" [advancement.challenge_sprint_dist_50k] - title = "Молниеносные Ноги" - description = "Пробегите более 50 километров (50,000 блоков)" +title = "Молниеносные Ноги" +description = "Пробегите более 50 километров (50,000 блоков)" [advancement.challenge_agility_swim_1k] - title = "Водомерка" - description = "Проплывите более 1 километра (1,000 блоков)" +title = "Водомерка" +description = "Проплывите более 1 километра (1,000 блоков)" [advancement.challenge_agility_swim_10k] - title = "Морской Странник" - description = "Проплывите более 10 километров (10,000 блоков)" +title = "Морской Странник" +description = "Проплывите более 10 километров (10,000 блоков)" [advancement.challenge_fly_1k] - title = "Танцор Небес" - description = "Пролетите более 1 километра (1,000 блоков)" +title = "Танцор Небес" +description = "Пролетите более 1 километра (1,000 блоков)" [advancement.challenge_fly_10k] - title = "Наездник Ветра" - description = "Пролетите более 10 километров (10,000 блоков)" +title = "Наездник Ветра" +description = "Пролетите более 10 километров (10,000 блоков)" [advancement.challenge_agility_sneak_500] - title = "Тихие Шаги" - description = "Прокрадитесь более 500 блоков" +title = "Тихие Шаги" +description = "Прокрадитесь более 500 блоков" [advancement.challenge_agility_sneak_5k] - title = "Призрачная Поступь" - description = "Прокрадитесь более 5 километров (5,000 блоков)" +title = "Призрачная Поступь" +description = "Прокрадитесь более 5 километров (5,000 блоков)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Бригада Сноса" - description = "Сломайте 500 блоков" +title = "Бригада Сноса" +description = "Сломайте 500 блоков" [advancement.challenge_demolish_5k] - title = "Шар-Разрушитель" - description = "Сломайте 5,000 блоков" +title = "Шар-Разрушитель" +description = "Сломайте 5,000 блоков" [advancement.challenge_value_placed_10k] - title = "Ценный Строитель" - description = "Разместите блоки стоимостью 10,000" +title = "Ценный Строитель" +description = "Разместите блоки стоимостью 10,000" [advancement.challenge_value_placed_100k] - title = "Мастер Архитектор" - description = "Разместите блоки стоимостью 100,000" +title = "Мастер Архитектор" +description = "Разместите блоки стоимостью 100,000" [advancement.challenge_demolish_val_5k] - title = "Специалист по Утилизации" - description = "Утилизируйте блоки стоимостью 5,000 при сносе" +title = "Специалист по Утилизации" +description = "Утилизируйте блоки стоимостью 5,000 при сносе" [advancement.challenge_demolish_val_50k] - title = "Полная Деконструкция" - description = "Утилизируйте блоки стоимостью 50,000 при сносе" +title = "Полная Деконструкция" +description = "Утилизируйте блоки стоимостью 50,000 при сносе" [advancement.challenge_high_build_100] - title = "Высотный Строитель" - description = "Разместите 100 блоков выше Y=128" +title = "Высотный Строитель" +description = "Разместите 100 блоков выше Y=128" [advancement.challenge_high_build_1k] - title = "Облачный Архитектор" - description = "Разместите 1,000 блоков выше Y=128" +title = "Облачный Архитектор" +description = "Разместите 1,000 блоков выше Y=128" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Дровосек" - description = "Взмахните топором 500 раз" +title = "Дровосек" +description = "Взмахните топором 500 раз" [advancement.challenge_axe_swing_5k] - title = "Берсерк" - description = "Взмахните топором 5,000 раз" +title = "Берсерк" +description = "Взмахните топором 5,000 раз" [advancement.challenge_axe_damage_1k] - title = "Рассекатель" - description = "Нанесите 1,000 урона топорами" +title = "Рассекатель" +description = "Нанесите 1,000 урона топорами" [advancement.challenge_axe_damage_10k] - title = "Топор Палача" - description = "Нанесите 10,000 урона топорами" +title = "Топор Палача" +description = "Нанесите 10,000 урона топорами" [advancement.challenge_axe_value_5k] - title = "Торговец Древесиной" - description = "Нарубите древесины стоимостью 5,000" +title = "Торговец Древесиной" +description = "Нарубите древесины стоимостью 5,000" [advancement.challenge_axe_value_50k] - title = "Лесной Барон" - description = "Нарубите древесины стоимостью 50,000" +title = "Лесной Барон" +description = "Нарубите древесины стоимостью 50,000" [advancement.challenge_leaves_500] - title = "Воздуходув" - description = "Срубите 500 блоков листвы топором" +title = "Воздуходув" +description = "Срубите 500 блоков листвы топором" [advancement.challenge_leaves_5k] - title = "Листобой" - description = "Срубите 5,000 блоков листвы топором" +title = "Листобой" +description = "Срубите 5,000 блоков листвы топором" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Поглотитель Урона" - description = "Заблокируйте 1,000 урона щитом" +title = "Поглотитель Урона" +description = "Заблокируйте 1,000 урона щитом" [advancement.challenge_block_dmg_10k] - title = "Живой Щит" - description = "Заблокируйте 10,000 урона щитом" +title = "Живой Щит" +description = "Заблокируйте 10,000 урона щитом" [advancement.challenge_block_proj_100] - title = "Отражатель Стрел" - description = "Заблокируйте 100 снарядов щитом" +title = "Отражатель Стрел" +description = "Заблокируйте 100 снарядов щитом" [advancement.challenge_block_proj_1k] - title = "Снарядный Щит" - description = "Заблокируйте 1,000 снарядов щитом" +title = "Снарядный Щит" +description = "Заблокируйте 1,000 снарядов щитом" [advancement.challenge_block_melee_500] - title = "Мастер Парирования" - description = "Заблокируйте 500 атак ближнего боя щитом" +title = "Мастер Парирования" +description = "Заблокируйте 500 атак ближнего боя щитом" [advancement.challenge_block_melee_5k] - title = "Железная Крепость" - description = "Заблокируйте 5,000 атак ближнего боя щитом" +title = "Железная Крепость" +description = "Заблокируйте 5,000 атак ближнего боя щитом" [advancement.challenge_block_heavy_50] - title = "Танк" - description = "Заблокируйте 50 тяжёлых атак (свыше 5 урона)" +title = "Танк" +description = "Заблокируйте 50 тяжёлых атак (свыше 5 урона)" [advancement.challenge_block_heavy_500] - title = "Недвижимый Объект" - description = "Заблокируйте 500 тяжёлых атак (свыше 5 урона)" +title = "Недвижимый Объект" +description = "Заблокируйте 500 тяжёлых атак (свыше 5 урона)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "Пивоварня" - description = "Установите 10 варочных стоек" +title = "Пивоварня" +description = "Установите 10 варочных стоек" [advancement.challenge_brew_stands_50] - title = "Фабрика Зелий" - description = "Установите 50 варочных стоек" +title = "Фабрика Зелий" +description = "Установите 50 варочных стоек" [advancement.challenge_brew_strong_25] - title = "Крепкое Зелье" - description = "Выпейте 25 усиленных зелий" +title = "Крепкое Зелье" +description = "Выпейте 25 усиленных зелий" [advancement.challenge_brew_strong_250] - title = "Максимальная Мощь" - description = "Выпейте 250 усиленных зелий" +title = "Максимальная Мощь" +description = "Выпейте 250 усиленных зелий" [advancement.challenge_brew_splash_hits_50] - title = "Зона Брызг" - description = "Попадите в 50 существ взрывными зельями" +title = "Зона Брызг" +description = "Попадите в 50 существ взрывными зельями" [advancement.challenge_brew_splash_hits_500] - title = "Чумной Доктор" - description = "Попадите в 500 существ взрывными зельями" +title = "Чумной Доктор" +description = "Попадите в 500 существ взрывными зельями" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Неугомонный" - description = "Пройдите 1 километр в активном состоянии" +title = "Неугомонный" +description = "Пройдите 1 километр в активном состоянии" [advancement.challenge_active_dist_10k] - title = "Следопыт" - description = "Пройдите 10 километров в активном состоянии" +title = "Следопыт" +description = "Пройдите 10 километров в активном состоянии" [advancement.challenge_active_dist_100k] - title = "Вечное Движение" - description = "Пройдите 100 километров в активном состоянии" +title = "Вечное Движение" +description = "Пройдите 100 километров в активном состоянии" [advancement.challenge_beds_10] - title = "Ранняя Пташка" - description = "Поспите в кровати 10 раз" +title = "Ранняя Пташка" +description = "Поспите в кровати 10 раз" [advancement.challenge_beds_100] - title = "Прыгун во Времени" - description = "Поспите в кровати 100 раз" +title = "Прыгун во Времени" +description = "Поспите в кровати 100 раз" [advancement.challenge_chronos_tp_50] - title = "Временной Сдвиг" - description = "Телепортируйтесь 50 раз" +title = "Временной Сдвиг" +description = "Телепортируйтесь 50 раз" [advancement.challenge_chronos_tp_500] - title = "Искривление Времени" - description = "Телепортируйтесь 500 раз" +title = "Искривление Времени" +description = "Телепортируйтесь 500 раз" [advancement.challenge_chronos_deaths_10] - title = "Смертный" - description = "Умрите 10 раз" +title = "Смертный" +description = "Умрите 10 раз" [advancement.challenge_chronos_deaths_100] - title = "Бросивший Вызов Смерти" - description = "Умрите 100 раз" +title = "Бросивший Вызов Смерти" +description = "Умрите 100 раз" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Ценный Крафт" - description = "Скрафтите предметы общей стоимостью 10,000" +title = "Ценный Крафт" +description = "Скрафтите предметы общей стоимостью 10,000" [advancement.challenge_craft_value_100k] - title = "Ремесленник" - description = "Скрафтите предметы общей стоимостью 100,000" +title = "Ремесленник" +description = "Скрафтите предметы общей стоимостью 100,000" [advancement.challenge_craft_tools_25] - title = "Инструментальщик" - description = "Скрафтите 25 инструментов" +title = "Инструментальщик" +description = "Скрафтите 25 инструментов" [advancement.challenge_craft_tools_250] - title = "Мастер Ковки" - description = "Скрафтите 250 инструментов" +title = "Мастер Ковки" +description = "Скрафтите 250 инструментов" [advancement.challenge_craft_armor_25] - title = "Бронник" - description = "Скрафтите 25 единиц брони" +title = "Бронник" +description = "Скрафтите 25 единиц брони" [advancement.challenge_craft_armor_250] - title = "Мастер Бронник" - description = "Скрафтите 250 единиц брони" +title = "Мастер Бронник" +description = "Скрафтите 250 единиц брони" [advancement.challenge_craft_mass_25k] - title = "Массовое Производство" - description = "Скрафтите 25,000 предметов" +title = "Массовое Производство" +description = "Скрафтите 25,000 предметов" [advancement.challenge_craft_mass_250k] - title = "Промышленная Революция" - description = "Скрафтите 250,000 предметов" +title = "Промышленная Революция" +description = "Скрафтите 250,000 предметов" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Собиратель" - description = "Откройте 50 уникальных предметов" +title = "Собиратель" +description = "Откройте 50 уникальных предметов" [advancement.challenge_discover_items_250] - title = "Каталогизатор" - description = "Откройте 250 уникальных предметов" +title = "Каталогизатор" +description = "Откройте 250 уникальных предметов" [advancement.challenge_discover_blocks_50] - title = "Исследователь" - description = "Откройте 50 уникальных блоков" +title = "Исследователь" +description = "Откройте 50 уникальных блоков" [advancement.challenge_discover_blocks_250] - title = "Геолог" - description = "Откройте 250 уникальных блоков" +title = "Геолог" +description = "Откройте 250 уникальных блоков" [advancement.challenge_discover_mobs_25] - title = "Наблюдатель" - description = "Откройте 25 уникальных мобов" +title = "Наблюдатель" +description = "Откройте 25 уникальных мобов" [advancement.challenge_discover_mobs_75] - title = "Натуралист" - description = "Откройте 75 уникальных мобов" +title = "Натуралист" +description = "Откройте 75 уникальных мобов" [advancement.challenge_discover_biomes_10] - title = "Странник" - description = "Откройте 10 уникальных биомов" +title = "Странник" +description = "Откройте 10 уникальных биомов" [advancement.challenge_discover_biomes_40] - title = "Путешественник" - description = "Откройте 40 уникальных биомов" +title = "Путешественник" +description = "Откройте 40 уникальных биомов" [advancement.challenge_discover_foods_10] - title = "Гурман" - description = "Откройте 10 уникальных блюд" +title = "Гурман" +description = "Откройте 10 уникальных блюд" [advancement.challenge_discover_foods_30] - title = "Мастер Кулинарии" - description = "Откройте 30 уникальных блюд" +title = "Мастер Кулинарии" +description = "Откройте 30 уникальных блюд" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Ткач Силы" - description = "Накопите 100 силы зачарования" +title = "Ткач Силы" +description = "Накопите 100 силы зачарования" [advancement.challenge_enchant_power_1k] - title = "Магистр Тайных Сил" - description = "Накопите 1,000 силы зачарования" +title = "Магистр Тайных Сил" +description = "Накопите 1,000 силы зачарования" [advancement.challenge_enchant_levels_1k] - title = "Расточитель Уровней" - description = "Потратьте 1,000 уровней опыта на зачарование" +title = "Расточитель Уровней" +description = "Потратьте 1,000 уровней опыта на зачарование" [advancement.challenge_enchant_levels_10k] - title = "Поглотитель Опыта" - description = "Потратьте 10,000 уровней опыта на зачарование" +title = "Поглотитель Опыта" +description = "Потратьте 10,000 уровней опыта на зачарование" [advancement.challenge_enchant_high_25] - title = "По-Крупному" - description = "Выполните 25 зачарований максимального уровня" +title = "По-Крупному" +description = "Выполните 25 зачарований максимального уровня" [advancement.challenge_enchant_high_250] - title = "Легендарный Зачарователь" - description = "Выполните 250 зачарований максимального уровня" +title = "Легендарный Зачарователь" +description = "Выполните 250 зачарований максимального уровня" [advancement.challenge_enchant_total_500] - title = "Сжигатель Уровней" - description = "Потратьте 500 уровней на все зачарования" +title = "Сжигатель Уровней" +description = "Потратьте 500 уровней на все зачарования" [advancement.challenge_enchant_total_5k] - title = "Магические Вложения" - description = "Потратьте 5,000 уровней на все зачарования" +title = "Магические Вложения" +description = "Потратьте 5,000 уровней на все зачарования" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Копатель" - description = "Взмахните лопатой 500 раз" +title = "Копатель" +description = "Взмахните лопатой 500 раз" [advancement.challenge_dig_swing_5k] - title = "Экскаватор" - description = "Взмахните лопатой 5,000 раз" +title = "Экскаватор" +description = "Взмахните лопатой 5,000 раз" [advancement.challenge_dig_damage_1k] - title = "Рыцарь Лопаты" - description = "Нанесите 1,000 урона лопатой" +title = "Рыцарь Лопаты" +description = "Нанесите 1,000 урона лопатой" [advancement.challenge_dig_damage_10k] - title = "Мастер Лопаты" - description = "Нанесите 10,000 урона лопатой" +title = "Мастер Лопаты" +description = "Нанесите 10,000 урона лопатой" [advancement.challenge_dig_value_5k] - title = "Торговец Грунтом" - description = "Выкопайте блоки стоимостью 5,000" +title = "Торговец Грунтом" +description = "Выкопайте блоки стоимостью 5,000" [advancement.challenge_dig_value_50k] - title = "Земляной Барон" - description = "Выкопайте блоки стоимостью 50,000" +title = "Земляной Барон" +description = "Выкопайте блоки стоимостью 50,000" [advancement.challenge_dig_gravel_500] - title = "Гравийный Дробильщик" - description = "Выкопайте 500 блоков гравия, песка или глины" +title = "Гравийный Дробильщик" +description = "Выкопайте 500 блоков гравия, песка или глины" [advancement.challenge_dig_gravel_5k] - title = "Просеиватель Песка" - description = "Выкопайте 5,000 блоков гравия, песка или глины" +title = "Просеиватель Песка" +description = "Выкопайте 5,000 блоков гравия, песка или глины" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Сеятель" - description = "Посадите 100 культур" +title = "Сеятель" +description = "Посадите 100 культур" [advancement.challenge_plant_1k] - title = "Зелёный Палец" - description = "Посадите 1,000 культур" +title = "Зелёный Палец" +description = "Посадите 1,000 культур" [advancement.challenge_plant_5k] - title = "Аграрный Барон" - description = "Посадите 5,000 культур" +title = "Аграрный Барон" +description = "Посадите 5,000 культур" [advancement.challenge_compost_50] - title = "Переработчик" - description = "Компостируйте 50 предметов" +title = "Переработчик" +description = "Компостируйте 50 предметов" [advancement.challenge_compost_500] - title = "Обогатитель Почвы" - description = "Компостируйте 500 предметов" +title = "Обогатитель Почвы" +description = "Компостируйте 500 предметов" [advancement.challenge_shear_50] - title = "Стригаль" - description = "Остригите 50 существ" +title = "Стригаль" +description = "Остригите 50 существ" [advancement.challenge_shear_250] - title = "Мастер Стада" - description = "Остригите 250 существ" +title = "Мастер Стада" +description = "Остригите 250 существ" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Истребитель" - description = "Уничтожьте 500 существ" +title = "Истребитель" +description = "Уничтожьте 500 существ" [advancement.challenge_kills_5k] - title = "Палач" - description = "Уничтожьте 5,000 существ" +title = "Палач" +description = "Уничтожьте 5,000 существ" [advancement.challenge_boss_1] - title = "Претендент на Босса" - description = "Убейте босс-моба" +title = "Претендент на Босса" +description = "Убейте босс-моба" [advancement.challenge_boss_10] - title = "Убийца Легенд" - description = "Убейте 10 босс-мобов" +title = "Убийца Легенд" +description = "Убейте 10 босс-мобов" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Иссушённый" - description = "Перенесите 500 урона от иссушения" +title = "Иссушённый" +description = "Перенесите 500 урона от иссушения" [advancement.challenge_wither_dmg_5k] - title = "Выживший Среди Скверны" - description = "Перенесите 5,000 урона от иссушения" +title = "Выживший Среди Скверны" +description = "Перенесите 5,000 урона от иссушения" [advancement.challenge_wither_skel_25] - title = "Собиратель Костей" - description = "Убейте 25 скелетов-иссушителей" +title = "Собиратель Костей" +description = "Убейте 25 скелетов-иссушителей" [advancement.challenge_wither_skel_250] - title = "Бич Скелетов" - description = "Убейте 250 скелетов-иссушителей" +title = "Бич Скелетов" +description = "Убейте 250 скелетов-иссушителей" [advancement.challenge_wither_boss_1] - title = "Победитель Визера" - description = "Победите Визера" +title = "Победитель Визера" +description = "Победите Визера" [advancement.challenge_wither_boss_10] - title = "Властелин Незера" - description = "Победите Визера 10 раз" +title = "Властелин Незера" +description = "Победите Визера 10 раз" [advancement.challenge_roses_10] - title = "Садовник Смерти" - description = "Сломайте 10 роз иссушения" +title = "Садовник Смерти" +description = "Сломайте 10 роз иссушения" [advancement.challenge_roses_100] - title = "Собиратель Скверны" - description = "Сломайте 100 роз иссушения" +title = "Собиратель Скверны" +description = "Сломайте 100 роз иссушения" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Рука Шахтёра" - description = "Взмахните киркой 500 раз" +title = "Рука Шахтёра" +description = "Взмахните киркой 500 раз" [advancement.challenge_pick_swing_5k] - title = "Строитель Туннелей" - description = "Взмахните киркой 5,000 раз" +title = "Строитель Туннелей" +description = "Взмахните киркой 5,000 раз" [advancement.challenge_pick_damage_1k] - title = "Боевая Кирка" - description = "Нанесите 1,000 урона киркой" +title = "Боевая Кирка" +description = "Нанесите 1,000 урона киркой" [advancement.challenge_pick_damage_10k] - title = "Военный Клевец" - description = "Нанесите 10,000 урона киркой" +title = "Военный Клевец" +description = "Нанесите 10,000 урона киркой" [advancement.challenge_pick_value_5k] - title = "Искатель Самоцветов" - description = "Добудьте блоки стоимостью 5,000" +title = "Искатель Самоцветов" +description = "Добудьте блоки стоимостью 5,000" [advancement.challenge_pick_value_50k] - title = "Рудный Барон" - description = "Добудьте блоки стоимостью 50,000" +title = "Рудный Барон" +description = "Добудьте блоки стоимостью 50,000" [advancement.challenge_pick_ores_500] - title = "Старатель" - description = "Добудьте 500 рудных блоков" +title = "Старатель" +description = "Добудьте 500 рудных блоков" [advancement.challenge_pick_ores_5k] - title = "Мастер Шахтёр" - description = "Добудьте 5,000 рудных блоков" +title = "Мастер Шахтёр" +description = "Добудьте 5,000 рудных блоков" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Меткий Стрелок" - description = "Нанесите 1,000 дальнего урона" +title = "Меткий Стрелок" +description = "Нанесите 1,000 дальнего урона" [advancement.challenge_ranged_dmg_10k] - title = "Смертоносный Лучник" - description = "Нанесите 10,000 дальнего урона" +title = "Смертоносный Лучник" +description = "Нанесите 10,000 дальнего урона" [advancement.challenge_ranged_dist_5k] - title = "Дальнобойный" - description = "Выпустите снаряды на общую дистанцию 5,000 блоков" +title = "Дальнобойный" +description = "Выпустите снаряды на общую дистанцию 5,000 блоков" [advancement.challenge_ranged_dist_50k] - title = "Километровый Выстрел" - description = "Выпустите снаряды на общую дистанцию 50,000 блоков" +title = "Километровый Выстрел" +description = "Выпустите снаряды на общую дистанцию 50,000 блоков" [advancement.challenge_ranged_kills_50] - title = "Лучник" - description = "Убейте 50 мобов дальнобойным оружием" +title = "Лучник" +description = "Убейте 50 мобов дальнобойным оружием" [advancement.challenge_ranged_kills_500] - title = "Мастер Лучник" - description = "Убейте 500 мобов дальнобойным оружием" +title = "Мастер Лучник" +description = "Убейте 500 мобов дальнобойным оружием" [advancement.challenge_longshot_25] - title = "Снайпер" - description = "Совершите 25 дальних попаданий (свыше 30 блоков)" +title = "Снайпер" +description = "Совершите 25 дальних попаданий (свыше 30 блоков)" [advancement.challenge_longshot_250] - title = "Орлиный Глаз" - description = "Совершите 250 дальних попаданий (свыше 30 блоков)" +title = "Орлиный Глаз" +description = "Совершите 250 дальних попаданий (свыше 30 блоков)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "Метатель Жемчуга" - description = "Бросьте 50 жемчужин Края" +title = "Метатель Жемчуга" +description = "Бросьте 50 жемчужин Края" [advancement.challenge_rift_pearls_500] - title = "Телепорт-Маньяк" - description = "Бросьте 500 жемчужин Края" +title = "Телепорт-Маньяк" +description = "Бросьте 500 жемчужин Края" [advancement.challenge_rift_enderman_50] - title = "Охотник на Эндерменов" - description = "Убейте 50 эндерменов" +title = "Охотник на Эндерменов" +description = "Убейте 50 эндерменов" [advancement.challenge_rift_enderman_500] - title = "Преследователь Пустоты" - description = "Убейте 500 эндерменов" +title = "Преследователь Пустоты" +description = "Убейте 500 эндерменов" [advancement.challenge_rift_dragon_500] - title = "Боец с Драконом" - description = "Нанесите 500 урона Дракону Края" +title = "Боец с Драконом" +description = "Нанесите 500 урона Дракону Края" [advancement.challenge_rift_dragon_5k] - title = "Драконоборец" - description = "Нанесите 5,000 урона Дракону Края" +title = "Драконоборец" +description = "Нанесите 5,000 урона Дракону Края" [advancement.challenge_rift_crystal_10] - title = "Разрушитель Кристаллов" - description = "Уничтожьте 10 кристаллов Края" +title = "Разрушитель Кристаллов" +description = "Уничтожьте 10 кристаллов Края" [advancement.challenge_rift_crystal_100] - title = "Демонтажник Края" - description = "Уничтожьте 100 кристаллов Края" +title = "Демонтажник Края" +description = "Уничтожьте 100 кристаллов Края" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Рыболов" - description = "Поймайте 25 рыб" +title = "Рыболов" +description = "Поймайте 25 рыб" [advancement.challenge_fish_250] - title = "Мастер Рыбалки" - description = "Поймайте 250 рыб" +title = "Мастер Рыбалки" +description = "Поймайте 250 рыб" [advancement.challenge_drowned_25] - title = "Охотник на Утопленников" - description = "Убейте 25 утопленников" +title = "Охотник на Утопленников" +description = "Убейте 25 утопленников" [advancement.challenge_drowned_250] - title = "Чистильщик Океана" - description = "Убейте 250 утопленников" +title = "Чистильщик Океана" +description = "Убейте 250 утопленников" [advancement.challenge_guardian_10] - title = "Убийца Стражей" - description = "Убейте 10 стражей" +title = "Убийца Стражей" +description = "Убейте 10 стражей" [advancement.challenge_guardian_100] - title = "Расхититель Храмов" - description = "Убейте 100 стражей" +title = "Расхититель Храмов" +description = "Убейте 100 стражей" [advancement.challenge_underwater_blocks_100] - title = "Подводный Шахтёр" - description = "Сломайте 100 блоков под водой" +title = "Подводный Шахтёр" +description = "Сломайте 100 блоков под водой" [advancement.challenge_underwater_blocks_1k] - title = "Подводный Инженер" - description = "Сломайте 1,000 блоков под водой" +title = "Подводный Инженер" +description = "Сломайте 1,000 блоков под водой" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Удар в Спину" - description = "Нанесите 500 урона крадучись" +title = "Удар в Спину" +description = "Нанесите 500 урона крадучись" [advancement.challenge_stealth_dmg_5k] - title = "Бесшумный Убийца" - description = "Нанесите 5,000 урона крадучись" +title = "Бесшумный Убийца" +description = "Нанесите 5,000 урона крадучись" [advancement.challenge_stealth_kills_10] - title = "Ассасин" - description = "Убейте 10 мобов крадучись" +title = "Ассасин" +description = "Убейте 10 мобов крадучись" [advancement.challenge_stealth_kills_100] - title = "Теневой Жнец" - description = "Убейте 100 мобов крадучись" +title = "Теневой Жнец" +description = "Убейте 100 мобов крадучись" [advancement.challenge_stealth_time_1h] - title = "Терпеливый" - description = "Проведите 1 час крадучись (3,600 секунд)" +title = "Терпеливый" +description = "Проведите 1 час крадучись (3,600 секунд)" [advancement.challenge_stealth_time_10h] - title = "Повелитель Теней" - description = "Проведите 10 часов крадучись (36,000 секунд)" +title = "Повелитель Теней" +description = "Проведите 10 часов крадучись (36,000 секунд)" [advancement.challenge_stealth_arrows_50] - title = "Тихий Лучник" - description = "Выпустите 50 стрел крадучись" +title = "Тихий Лучник" +description = "Выпустите 50 стрел крадучись" [advancement.challenge_stealth_arrows_500] - title = "Призрачный Стрелок" - description = "Выпустите 500 стрел крадучись" +title = "Призрачный Стрелок" +description = "Выпустите 500 стрел крадучись" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Ученик Меча" - description = "Нанесите 1,000 урона мечами" +title = "Ученик Меча" +description = "Нанесите 1,000 урона мечами" [advancement.challenge_sword_dmg_10k] - title = "Фехтовальщик" - description = "Нанесите 10,000 урона мечами" +title = "Фехтовальщик" +description = "Нанесите 10,000 урона мечами" [advancement.challenge_sword_kills_50] - title = "Дуэлянт" - description = "Убейте 50 мобов мечами" +title = "Дуэлянт" +description = "Убейте 50 мобов мечами" [advancement.challenge_sword_kills_500] - title = "Гладиатор" - description = "Убейте 500 мобов мечами" +title = "Гладиатор" +description = "Убейте 500 мобов мечами" [advancement.challenge_sword_crit_50] - title = "Критический Удар" - description = "Нанесите 50 критических ударов мечами" +title = "Критический Удар" +description = "Нанесите 50 критических ударов мечами" [advancement.challenge_sword_crit_500] - title = "Мастер Точности" - description = "Нанесите 500 критических ударов мечами" +title = "Мастер Точности" +description = "Нанесите 500 критических ударов мечами" [advancement.challenge_sword_heavy_25] - title = "Тяжёлый Удар" - description = "Нанесите 25 тяжёлых ударов мечами (свыше 8 урона)" +title = "Тяжёлый Удар" +description = "Нанесите 25 тяжёлых ударов мечами (свыше 8 урона)" [advancement.challenge_sword_heavy_250] - title = "Сокрушительный Удар" - description = "Нанесите 250 тяжёлых ударов мечами (свыше 8 урона)" +title = "Сокрушительный Удар" +description = "Нанесите 250 тяжёлых ударов мечами (свыше 8 урона)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Дрессировщик Зверей" - description = "Ваши питомцы наносят 500 общего урона" +title = "Дрессировщик Зверей" +description = "Ваши питомцы наносят 500 общего урона" [advancement.challenge_pet_dmg_5k] - title = "Военачальник" - description = "Ваши питомцы наносят 5,000 общего урона" +title = "Военачальник" +description = "Ваши питомцы наносят 5,000 общего урона" [advancement.challenge_tamed_10] - title = "Друг Животных" - description = "Приручите 10 животных" +title = "Друг Животных" +description = "Приручите 10 животных" [advancement.challenge_tamed_100] - title = "Смотритель Зоопарка" - description = "Приручите 100 животных" +title = "Смотритель Зоопарка" +description = "Приручите 100 животных" [advancement.challenge_pet_kills_25] - title = "Стайная Тактика" - description = "Ваши питомцы уничтожат 25 существ" +title = "Стайная Тактика" +description = "Ваши питомцы уничтожат 25 существ" [advancement.challenge_pet_kills_250] - title = "Командир Стаи" - description = "Ваши питомцы уничтожат 250 существ" +title = "Командир Стаи" +description = "Ваши питомцы уничтожат 250 существ" [advancement.challenge_taming_2500] - title = "Эксперт по Разведению" - description = "Разведите 2,500 животных" +title = "Эксперт по Разведению" +description = "Разведите 2,500 животных" [advancement.challenge_taming_25k] - title = "Мастер Генетики" - description = "Разведите 25,000 животных" +title = "Мастер Генетики" +description = "Разведите 25,000 животных" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Боксёрская Груша" - description = "Получите 500 ударов" +title = "Боксёрская Груша" +description = "Получите 500 ударов" [advancement.challenge_trag_hits_5k] - title = "Любитель Боли" - description = "Получите 5,000 ударов" +title = "Любитель Боли" +description = "Получите 5,000 ударов" [advancement.challenge_trag_deaths_10] - title = "Девять Жизней" - description = "Умрите 10 раз" +title = "Девять Жизней" +description = "Умрите 10 раз" [advancement.challenge_trag_deaths_100] - title = "Повторяющийся Кошмар" - description = "Умрите 100 раз" +title = "Повторяющийся Кошмар" +description = "Умрите 100 раз" [advancement.challenge_trag_fire_500] - title = "Жертва Огня" - description = "Перенесите 500 урона от огня" +title = "Жертва Огня" +description = "Перенесите 500 урона от огня" [advancement.challenge_trag_fire_5k] - title = "Феникс" - description = "Перенесите 5,000 урона от огня" +title = "Феникс" +description = "Перенесите 5,000 урона от огня" [advancement.challenge_trag_fall_500] - title = "Проверка Гравитации" - description = "Перенесите 500 урона от падения" +title = "Проверка Гравитации" +description = "Перенесите 500 урона от падения" [advancement.challenge_trag_fall_5k] - title = "Предельная Скорость" - description = "Перенесите 5,000 урона от падения" +title = "Предельная Скорость" +description = "Перенесите 5,000 урона от падения" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Драчун" - description = "Нанесите 1,000 урона голыми кулаками" +title = "Драчун" +description = "Нанесите 1,000 урона голыми кулаками" [advancement.challenge_unarmed_dmg_10k] - title = "Мастер Боевых Искусств" - description = "Нанесите 10,000 урона голыми кулаками" +title = "Мастер Боевых Искусств" +description = "Нанесите 10,000 урона голыми кулаками" [advancement.challenge_unarmed_kills_25] - title = "Голые Кулаки" - description = "Убейте 25 мобов голыми кулаками" +title = "Голые Кулаки" +description = "Убейте 25 мобов голыми кулаками" [advancement.challenge_unarmed_kills_250] - title = "Кулак Легенды" - description = "Убейте 250 мобов голыми кулаками" +title = "Кулак Легенды" +description = "Убейте 250 мобов голыми кулаками" [advancement.challenge_unarmed_crit_25] - title = "Критический Кулак" - description = "Нанесите 25 критических ударов голыми кулаками" +title = "Критический Кулак" +description = "Нанесите 25 критических ударов голыми кулаками" [advancement.challenge_unarmed_crit_250] - title = "Точный Кулак" - description = "Нанесите 250 критических ударов голыми кулаками" +title = "Точный Кулак" +description = "Нанесите 250 критических ударов голыми кулаками" [advancement.challenge_unarmed_heavy_25] - title = "Мощный Удар" - description = "Нанесите 25 тяжёлых ударов голыми кулаками (свыше 6 урона)" +title = "Мощный Удар" +description = "Нанесите 25 тяжёлых ударов голыми кулаками (свыше 6 урона)" [advancement.challenge_unarmed_heavy_250] - title = "Король Нокаутов" - description = "Нанесите 250 тяжёлых ударов голыми кулаками (свыше 6 урона)" +title = "Король Нокаутов" +description = "Нанесите 250 тяжёлых ударов голыми кулаками (свыше 6 урона)" # items [items] [items.bound_ender_peral] - name = "Портключ Реликвария" - usage1 = "Shift + ЛКМ для привязки" - usage2 = "ПКМ для доступа к привязанному инвентарю" +name = "Портключ Реликвария" +usage1 = "Shift + ЛКМ для привязки" +usage2 = "ПКМ для доступа к привязанному инвентарю" [items.bound_eye_of_ender] - name = "Зрительный якорь" - usage1 = "ПКМ, чтобы использовать и телепортироваться к привязанному месту" - usage2 = "Shift + ЛКМ для привязки к блоку" +name = "Зрительный якорь" +usage1 = "ПКМ, чтобы использовать и телепортироваться к привязанному месту" +usage2 = "Shift + ЛКМ для привязки к блоку" [items.bound_redstone_torch] - name = "Пульт редстоуна" - usage1 = "ПКМ для создания 1-тикового импульса редстоуна" - usage2 = "Shift + ЛКМ по целевому блоку для привязки" +name = "Пульт редстоуна" +usage1 = "ПКМ для создания 1-тикового импульса редстоуна" +usage2 = "Shift + ЛКМ по целевому блоку для привязки" [items.bound_snowball] - name = "Паутинная ловушка!" - usage1 = "Бросьте для создания временной паутинной ловушки" +name = "Паутинная ловушка!" +usage1 = "Бросьте для создания временной паутинной ловушки" [items.chrono_time_bottle] - name = "Время в бутылке" - usage1 = "Пассивно накапливает время, пока находится в инвентаре" - usage2 = "ПКМ по тикающим блокам или детёнышам, чтобы потратить накопленное время" - stored = "Накопленное время" +name = "Время в бутылке" +usage1 = "Пассивно накапливает время, пока находится в инвентаре" +usage2 = "ПКМ по тикающим блокам или детёнышам, чтобы потратить накопленное время" +stored = "Накопленное время" [items.chrono_time_bomb] - name = "Хронобомба" - usage1 = "ПКМ для запуска хроноснаряда, создающего временное поле" +name = "Хронобомба" +usage1 = "ПКМ для запуска хроноснаряда, создающего временное поле" [items.elevator_block] - name = "Блок лифта" - usage1 = "Прыжок для телепортации вверх" - usage2 = "Присесть для телепортации вниз" - usage3 = "Минимум 2 воздушных блока между лифтами" +name = "Блок лифта" +usage1 = "Прыжок для телепортации вверх" +usage2 = "Присесть для телепортации вниз" +usage3 = "Минимум 2 воздушных блока между лифтами" # snippets [snippets] [snippets.gui] - level = "Уровень" - knowledge = "знаний" - power_used = "Энергии использовано" - not_learned = "Не изучено" - xp = "Опыта до" - welcome = "Добро пожаловать!" - welcome_back = "С возвращением!" - xp_bonus_for_time = "Опыта за" - max_ability_power = "Максимальная сила способностей" - unlock_this_by_clicking = "Разблокируйте с помощью ПКМ: " - back = "Назад" - unlearn_all = "Забыть всё" - unlearned_all = "Всё забыто" +level = "Уровень" +knowledge = "знаний" +power_used = "Энергии использовано" +not_learned = "Не изучено" +xp = "Опыта до" +welcome = "Добро пожаловать!" +welcome_back = "С возвращением!" +xp_bonus_for_time = "Опыта за" +max_ability_power = "Максимальная сила способностей" +unlock_this_by_clicking = "Разблокируйте с помощью ПКМ: " +back = "Назад" +unlearn_all = "Забыть всё" +unlearned_all = "Всё забыто" [snippets.adapt_menu] - may_not_unlearn = "ВЫ НЕ МОЖЕТЕ ЗАБЫТЬ" - may_unlearn = "ВЫ МОЖЕТЕ ИЗУЧИТЬ/ЗАБЫТЬ" - knowledge_cost = "Стоимость знаний" - knowledge_available = "Доступно знаний" - already_learned = "Уже изучено" - unlearn_refund = "Нажмите, чтобы забыть и вернуть" - no_refunds = "ХАРДКОР, ВОЗВРАТ ОТКЛЮЧЁН" - knowledge = "знаний" - click_learn = "Нажмите, чтобы изучить" - no_knowledge = "(У вас нет знаний)" - you_only_have = "У вас только" - how_to_level_up = "Повышайте уровень навыков, чтобы увеличить максимальную энергию." - not_enough_power = "Недостаточно энергии! Каждый уровень способности стоит 1 энергию." - power = "энергия" - power_drain = "Расход энергии" - learned = "Изучено " - unlearned = "Забыто " - activator_block = "Книжная полка" +may_not_unlearn = "ВЫ НЕ МОЖЕТЕ ЗАБЫТЬ" +may_unlearn = "ВЫ МОЖЕТЕ ИЗУЧИТЬ/ЗАБЫТЬ" +knowledge_cost = "Стоимость знаний" +knowledge_available = "Доступно знаний" +already_learned = "Уже изучено" +unlearn_refund = "Нажмите, чтобы забыть и вернуть" +no_refunds = "ХАРДКОР, ВОЗВРАТ ОТКЛЮЧЁН" +knowledge = "знаний" +click_learn = "Нажмите, чтобы изучить" +no_knowledge = "(У вас нет знаний)" +you_only_have = "У вас только" +how_to_level_up = "Повышайте уровень навыков, чтобы увеличить максимальную энергию." +not_enough_power = "Недостаточно энергии! Каждый уровень способности стоит 1 энергию." +power = "энергия" +power_drain = "Расход энергии" +learned = "Изучено " +unlearned = "Забыто " +activator_block = "Книжная полка" [snippets.knowledge_orb] - contains = "содержит" - knowledge = "знаний" - rightclick = "ПКМ" - togainknowledge = "чтобы получить эти знания" - knowledge_orb = "Сфера знаний" +contains = "содержит" +knowledge = "знаний" +rightclick = "ПКМ" +togainknowledge = "чтобы получить эти знания" +knowledge_orb = "Сфера знаний" [snippets.experience_orb] - contains = "содержит" - xp = "Опыта" - rightclick = "ПКМ" - togainxp = "чтобы получить этот опыт" - xporb = "Сфера опыта" +contains = "содержит" +xp = "Опыта" +rightclick = "ПКМ" +togainxp = "чтобы получить этот опыт" +xporb = "Сфера опыта" # skill [skill] [skill.agility] - name = "Ловкость" - icon = "⇉" - description = "Ловкость -- это способность быстро и плавно передвигаться, преодолевая препятствия." +name = "Ловкость" +icon = "⇉" +description = "Ловкость -- это способность быстро и плавно передвигаться, преодолевая препятствия." [skill.architect] - name = "Архитектура" - icon = "⬧" - description = "Структуры -- строительные блоки мира. Реальность в ваших руках, вам ею управлять." +name = "Архитектура" +icon = "⬧" +description = "Структуры -- строительные блоки мира. Реальность в ваших руках, вам ею управлять." [skill.axes] - name = "Топоры" - icon = "🪓" - description1 = "Зачем рубить деревья, когда можно рубить " - description2 = "вещи" - description3 = " -- результат тот же!" +name = "Топоры" +icon = "🪓" +description1 = "Зачем рубить деревья, когда можно рубить " +description2 = "вещи" +description3 = " -- результат тот же!" [skill.brewing] - name = "Зельеварение" - icon = "❦" - description = "Двойной пузырёк, тройной пузырёк, четверной пузырёк -- а зелье в котёл так и не лезет" +name = "Зельеварение" +icon = "❦" +description = "Двойной пузырёк, тройной пузырёк, четверной пузырёк -- а зелье в котёл так и не лезет" [skill.blocking] - name = "Блокирование" - icon = "🛡" - description = "Палки и камни костей не сломают, а вот щит -- запросто." +name = "Блокирование" +icon = "🛡" +description = "Палки и камни костей не сломают, а вот щит -- запросто." [skill.crafting] - name = "Ремесло" - icon = "⌂" - description = "Когда деталей больше не осталось, почему бы не сделать ещё?" +name = "Ремесло" +icon = "⌂" +description = "Когда деталей больше не осталось, почему бы не сделать ещё?" [skill.discovery] - name = "Исследование" - icon = "⚛" - description = "По мере расширения восприятия ваш разум раскрывается, обнаруживая то, чего вы не замечали." +name = "Исследование" +icon = "⚛" +description = "По мере расширения восприятия ваш разум раскрывается, обнаруживая то, чего вы не замечали." [skill.enchanting] - name = "Зачарование" - icon = "♰" - description = "О чём ты говоришь? Пророчества, видения, суеверная болтовня?" +name = "Зачарование" +icon = "♰" +description = "О чём ты говоришь? Пророчества, видения, суеверная болтовня?" [skill.excavation] - name = "Раскопки" - icon = "ᛳ" - description = "Копай, копай яму..." +name = "Раскопки" +icon = "ᛳ" +description = "Копай, копай яму..." [skill.herbalism] - name = "Травничество" - icon = "⚘" - description = "Не могу найти растения, но нашёл несколько семян и... это что... Травка?" +name = "Травничество" +icon = "⚘" +description = "Не могу найти растения, но нашёл несколько семян и... это что... Травка?" [skill.hunter] - name = "Охота" - icon = "☠" - description = "Охота -- это путешествие, а не результат." +name = "Охота" +icon = "☠" +description = "Охота -- это путешествие, а не результат." [skill.nether] - name = "Нижний мир" - icon = "₪" - description = "Из самых глубин Нижнего мира." +name = "Нижний мир" +icon = "₪" +description = "Из самых глубин Нижнего мира." [skill.pickaxe] - name = "Кирки" - icon = "⛏" - description = "Гномы -- шахтёры, но я тоже кое-чему научился. Я ШВЕД" +name = "Кирки" +icon = "⛏" +description = "Гномы -- шахтёры, но я тоже кое-чему научился. Я ШВЕД" [skill.ranged] - name = "Дальний бой" - icon = "🏹" - description = "Расстояние -- ключ к победе и ключ к выживанию." +name = "Дальний бой" +icon = "🏹" +description = "Расстояние -- ключ к победе и ключ к выживанию." [skill.rift] - name = "Разлом" - icon = "❍" - description = "Разлом -- опасная упряжь, но вы обуздали упряжь." +name = "Разлом" +icon = "❍" +description = "Разлом -- опасная упряжь, но вы обуздали упряжь." [skill.seaborne] - name = "Дитя моря" - icon = "🎣" - description = "С этим навыком вы повелеваете чудесами водной стихии." +name = "Дитя моря" +icon = "🎣" +description = "С этим навыком вы повелеваете чудесами водной стихии." [skill.stealth] - name = "Скрытность" - icon = "☯" - description = "Искусство невидимого. Ступайте в тенях." +name = "Скрытность" +icon = "☯" +description = "Искусство невидимого. Ступайте в тенях." [skill.swords] - name = "Мечи" - icon = "⚔" - description = "Силой Серого Камня!" +name = "Мечи" +icon = "⚔" +description = "Силой Серого Камня!" [skill.taming] - name = "Приручение" - icon = "♥" - description = "Попугаи и пчёлы... а вы?" +name = "Приручение" +icon = "♥" +description = "Попугаи и пчёлы... а вы?" [skill.tragoul] - name = "Трагул" - icon = "🗡" - description = "Кровь течёт по венам вселенной. Сжатая вашими руками." +name = "Трагул" +icon = "🗡" +description = "Кровь течёт по венам вселенной. Сжатая вашими руками." [skill.chronos] - name = "Хронос" - icon = "🕒" - description = "Заведите часы вселенной, ощутите поток. Сломайте часы, станьте ими." +name = "Хронос" +icon = "🕒" +description = "Заведите часы вселенной, ощутите поток. Сломайте часы, станьте ими." [skill.unarmed] - name = "Без оружия" - icon = "»" - description = "Без оружия -- не значит без силы." +name = "Без оружия" +icon = "»" +description = "Без оружия -- не значит без силы." # agility [agility] [agility.armor_up] - name = "Укрепление брони" - description = "Чем дольше бежите, тем больше брони!" - lore1 = "Максимум брони" - lore2 = "Время набора брони" - lore = ["Максимум брони", "Время набора брони"] +name = "Укрепление брони" +description = "Чем дольше бежите, тем больше брони!" +lore1 = "Максимум брони" +lore2 = "Время набора брони" +lore = ["Максимум брони", "Время набора брони"] [agility.ladder_slide] - name = "Скольжение по лестнице" - description = "Поднимайтесь и скользите по лестницам гораздо быстрее в обоих направлениях." - lore1 = "Множитель скорости на лестнице" - lore2 = "Скорость быстрого спуска" - lore = ["Множитель скорости на лестнице", "Скорость быстрого спуска"] +name = "Скольжение по лестнице" +description = "Поднимайтесь и скользите по лестницам гораздо быстрее в обоих направлениях." +lore1 = "Множитель скорости на лестнице" +lore2 = "Скорость быстрого спуска" +lore = ["Множитель скорости на лестнице", "Скорость быстрого спуска"] [agility.super_jump] - name = "Суперпрыжок" - description = "Исключительное преимущество в высоте." - lore1 = "Максимальная высота прыжка" - lore2 = "Shift + Прыжок для суперпрыжка!" - lore = ["Максимальная высота прыжка", "Shift + Прыжок для суперпрыжка!"] +name = "Суперпрыжок" +description = "Исключительное преимущество в высоте." +lore1 = "Максимальная высота прыжка" +lore2 = "Shift + Прыжок для суперпрыжка!" +lore = ["Максимальная высота прыжка", "Shift + Прыжок для суперпрыжка!"] [agility.wall_jump] - name = "Прыжок от стены" - description = "Удерживайте Shift в воздухе у стены, чтобы зацепиться и прыгнуть!" - lore1 = "Максимум прыжков" - lore2 = "Высота прыжка" - lore = ["Максимум прыжков", "Высота прыжка"] +name = "Прыжок от стены" +description = "Удерживайте Shift в воздухе у стены, чтобы зацепиться и прыгнуть!" +lore1 = "Максимум прыжков" +lore2 = "Высота прыжка" +lore = ["Максимум прыжков", "Высота прыжка"] [agility.wind_up] - name = "Разгон" - description = "Чем дольше бежите, тем быстрее становитесь!" - lore1 = "Максимальная скорость" - lore2 = "Время разгона" - lore = ["Максимальная скорость", "Время разгона"] +name = "Разгон" +description = "Чем дольше бежите, тем быстрее становитесь!" +lore1 = "Максимальная скорость" +lore2 = "Время разгона" +lore = ["Максимальная скорость", "Время разгона"] # architect [architect] [architect.elevator] - name = "Лифт" - description = "Позволяет построить лифт для быстрой вертикальной телепортации!" - lore1 = "Открывает рецепт лифта: X=ШЕРСТЬ, Y=ЭНДЕР-ЖЕМЧУГ" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Открывает рецепт лифта: X=ШЕРСТЬ, Y=ЭНДЕР-ЖЕМЧУГ", "XXX", "XYX", "XXX"] +name = "Лифт" +description = "Позволяет построить лифт для быстрой вертикальной телепортации!" +lore1 = "Открывает рецепт лифта: X=ШЕРСТЬ, Y=ЭНДЕР-ЖЕМЧУГ" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Открывает рецепт лифта: X=ШЕРСТЬ, Y=ЭНДЕР-ЖЕМЧУГ", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Магический фундамент" - description = "Позволяет создавать временный фундамент под собой при приседании!" - lore1 = "Магически создать: " - lore2 = "блоков под собой!" - lore = ["Магически создать: ", "блоков под собой!"] +name = "Магический фундамент" +description = "Позволяет создавать временный фундамент под собой при приседании!" +lore1 = "Магически создать: " +lore2 = "блоков под собой!" +lore = ["Магически создать: ", "блоков под собой!"] [architect.glass] - name = "Шёлковое касание: стекло" - description = "Позволяет предотвратить потерю стеклянных блоков, когда вы ломаете их пустой рукой!" - lore1 = "Ваши руки получают Шёлковое касание для стекла" - lore = ["Ваши руки получают Шёлковое касание для стекла"] +name = "Шёлковое касание: стекло" +description = "Позволяет предотвратить потерю стеклянных блоков, когда вы ломаете их пустой рукой!" +lore1 = "Ваши руки получают Шёлковое касание для стекла" +lore = ["Ваши руки получают Шёлковое касание для стекла"] [architect.wireless_redstone] - name = "Пульт редстоуна" - description = "Позволяет переключать редстоун-сигнал дистанционно с помощью редстоунового факела!" - lore1 = "Мишень + Редстоуновый факел + Эндер-жемчуг = 1 пульт редстоуна" - lore = ["Мишень + Редстоуновый факел + Эндер-жемчуг = 1 пульт редстоуна"] +name = "Пульт редстоуна" +description = "Позволяет переключать редстоун-сигнал дистанционно с помощью редстоунового факела!" +lore1 = "Мишень + Редстоуновый факел + Эндер-жемчуг = 1 пульт редстоуна" +lore = ["Мишень + Редстоуновый факел + Эндер-жемчуг = 1 пульт редстоуна"] [architect.placement] - name = "Жезл строителя" - description = "Позволяет размещать несколько блоков одновременно! Для активации присядьте и держите блок, совпадающий с тем, на который вы смотрите, и ставьте! Возможно, придётся чуть сдвинуться!" - lore1 = "Вам нужно" - lore2 = "блоков в руке, чтобы разместить это" - lore3 = "Жезл строителя из материала" - lore = ["Вам нужно", "блоков в руке, чтобы разместить это", "Жезл строителя из материала"] +name = "Жезл строителя" +description = "Позволяет размещать несколько блоков одновременно! Для активации присядьте и держите блок, совпадающий с тем, на который вы смотрите, и ставьте! Возможно, придётся чуть сдвинуться!" +lore1 = "Вам нужно" +lore2 = "блоков в руке, чтобы разместить это" +lore3 = "Жезл строителя из материала" +lore = ["Вам нужно", "блоков в руке, чтобы разместить это", "Жезл строителя из материала"] # axe [axe] [axe.chop] - name = "Рубка топором" - description = "Срубайте деревья, нажав ПКМ по нижнему бревну!" - lore1 = "Блоков за рубку" - lore2 = "Перезарядка рубки" - lore3 = "Износ инструмента" - lore = ["Блоков за рубку", "Перезарядка рубки", "Износ инструмента"] +name = "Рубка топором" +description = "Срубайте деревья, нажав ПКМ по нижнему бревну!" +lore1 = "Блоков за рубку" +lore2 = "Перезарядка рубки" +lore3 = "Износ инструмента" +lore = ["Блоков за рубку", "Перезарядка рубки", "Износ инструмента"] [axe.log_swap] - name = "Обменник брёвен Люси" - description = "Меняйте тип брёвен на верстаке!" - lore1 = "8 брёвен любого вида + 1 саженец = 8 брёвен типа саженца" - lore = ["8 брёвен любого вида + 1 саженец = 8 брёвен типа саженца"] +name = "Обменник брёвен Люси" +description = "Меняйте тип брёвен на верстаке!" +lore1 = "8 брёвен любого вида + 1 саженец = 8 брёвен типа саженца" +lore = ["8 брёвен любого вида + 1 саженец = 8 брёвен типа саженца"] [axe.drop_to_inventory] - name = "Автосбор с топора" +name = "Автосбор с топора" [axe.ground_smash] - name = "Удар топором по земле" - description = "Прыгните, затем присядьте и ударьте всех ближайших врагов." - lore1 = "Урон" - lore2 = "Радиус в блоках" - lore3 = "Сила" - lore4 = "Перезарядка удара" - lore = ["Урон", "Радиус в блоках", "Сила", "Перезарядка удара"] +name = "Удар топором по земле" +description = "Прыгните, затем присядьте и ударьте всех ближайших врагов." +lore1 = "Урон" +lore2 = "Радиус в блоках" +lore3 = "Сила" +lore4 = "Перезарядка удара" +lore = ["Урон", "Радиус в блоках", "Сила", "Перезарядка удара"] [axe.leaf_miner] - name = "Сборщик листвы" - description = "Позволяет ломать массу листвы сразу!" - lore1 = "Присядьте и рубите ЛИСТВУ" - lore2 = "Радиус сбора листвы" - lore3 = "Вы не получите дроп с листвы (защита от эксплойтов)" - lore = ["Присядьте и рубите ЛИСТВУ", "Радиус сбора листвы", "Вы не получите дроп с листвы (защита от эксплойтов)"] +name = "Сборщик листвы" +description = "Позволяет ломать массу листвы сразу!" +lore1 = "Присядьте и рубите ЛИСТВУ" +lore2 = "Радиус сбора листвы" +lore3 = "Вы не получите дроп с листвы (защита от эксплойтов)" +lore = ["Присядьте и рубите ЛИСТВУ", "Радиус сбора листвы", "Вы не получите дроп с листвы (защита от эксплойтов)"] [axe.wood_miner] - name = "Лесоруб" - description = "Позволяет ломать массу древесины сразу!" - lore1 = "Присядьте и рубите ДЕРЕВО/БРЁВНА (не доски)" - lore2 = "Радиус рубки древесины" - lore3 = "Работает с автосбором в инвентарь" - lore = ["Присядьте и рубите ДЕРЕВО/БРЁВНА (не доски)", "Радиус рубки древесины", "Работает с автосбором в инвентарь"] +name = "Лесоруб" +description = "Позволяет ломать массу древесины сразу!" +lore1 = "Присядьте и рубите ДЕРЕВО/БРЁВНА (не доски)" +lore2 = "Радиус рубки древесины" +lore3 = "Работает с автосбором в инвентарь" +lore = ["Присядьте и рубите ДЕРЕВО/БРЁВНА (не доски)", "Радиус рубки древесины", "Работает с автосбором в инвентарь"] # brewing [brewing] [brewing.lingering] - name = "Стойкое зелье" - description = "Сваренные зелья действуют дольше!" - lore1 = "Длительность" - lore2 = "Длительность" - lore = ["Длительность", "Длительность"] +name = "Стойкое зелье" +description = "Сваренные зелья действуют дольше!" +lore1 = "Длительность" +lore2 = "Длительность" +lore = ["Длительность", "Длительность"] [brewing.super_heated] - name = "Сверхнагретое зелье" - description = "Варочные стойки работают быстрее, чем горячее вокруг них." - lore1 = "За каждый касающийся блок огня" - lore2 = "За каждый касающийся блок лавы" - lore = ["За каждый касающийся блок огня", "За каждый касающийся блок лавы"] +name = "Сверхнагретое зелье" +description = "Варочные стойки работают быстрее, чем горячее вокруг них." +lore1 = "За каждый касающийся блок огня" +lore2 = "За каждый касающийся блок лавы" +lore = ["За каждый касающийся блок огня", "За каждый касающийся блок лавы"] [brewing.darkness] - name = "Тьма в бутылке" - description = "Не знаю, зачем вам это, но вот, держите!" - lore1 = "Зелье ночного зрения + Чёрный бетон = Зелье тьмы (30 секунд)" - lore2 = "Следует отметить, что это не позволяет бегать!" - lore = ["Зелье ночного зрения + Чёрный бетон = Зелье тьмы (30 секунд)", "Следует отметить, что это не позволяет бегать!"] +name = "Тьма в бутылке" +description = "Не знаю, зачем вам это, но вот, держите!" +lore1 = "Зелье ночного зрения + Чёрный бетон = Зелье тьмы (30 секунд)" +lore2 = "Следует отметить, что это не позволяет бегать!" +lore = ["Зелье ночного зрения + Чёрный бетон = Зелье тьмы (30 секунд)", "Следует отметить, что это не позволяет бегать!"] [brewing.haste] - name = "Спешка в бутылке" - description = "Когда эффективности недостаточно" - lore1 = "Зелье скорости + Осколок аметиста = Зелье спешки (60 секунд)" - lore2 = "Зелье скорости + Блок аметиста = Зелье спешки II (30 секунд)" - lore = ["Зелье скорости + Осколок аметиста = Зелье спешки (60 секунд)", "Зелье скорости + Блок аметиста = Зелье спешки II (30 секунд)"] +name = "Спешка в бутылке" +description = "Когда эффективности недостаточно" +lore1 = "Зелье скорости + Осколок аметиста = Зелье спешки (60 секунд)" +lore2 = "Зелье скорости + Блок аметиста = Зелье спешки II (30 секунд)" +lore = ["Зелье скорости + Осколок аметиста = Зелье спешки (60 секунд)", "Зелье скорости + Блок аметиста = Зелье спешки II (30 секунд)"] [brewing.absorption] - name = "Поглощение в бутылке" - description = "Закали тело!" - lore1 = "Зелье исцеления + Кварц = Зелье поглощения (60 секунд)" - lore2 = "Зелье исцеления + Кварцевый блок = Зелье поглощения II (30 секунд)" - lore = ["Зелье исцеления + Кварц = Зелье поглощения (60 секунд)", "Зелье исцеления + Кварцевый блок = Зелье поглощения II (30 секунд)"] +name = "Поглощение в бутылке" +description = "Закали тело!" +lore1 = "Зелье исцеления + Кварц = Зелье поглощения (60 секунд)" +lore2 = "Зелье исцеления + Кварцевый блок = Зелье поглощения II (30 секунд)" +lore = ["Зелье исцеления + Кварц = Зелье поглощения (60 секунд)", "Зелье исцеления + Кварцевый блок = Зелье поглощения II (30 секунд)"] [brewing.fatigue] - name = "Усталость в бутылке" - description = "Ослабь тело!" - lore1 = "Зелье слабости + Сгусток слизи = Зелье усталости (30 секунд)" - lore2 = "Зелье слабости + Блок слизи = Зелье усталости II (15 секунд)" - lore = ["Зелье слабости + Сгусток слизи = Зелье усталости (30 секунд)", "Зелье слабости + Блок слизи = Зелье усталости II (15 секунд)"] +name = "Усталость в бутылке" +description = "Ослабь тело!" +lore1 = "Зелье слабости + Сгусток слизи = Зелье усталости (30 секунд)" +lore2 = "Зелье слабости + Блок слизи = Зелье усталости II (15 секунд)" +lore = ["Зелье слабости + Сгусток слизи = Зелье усталости (30 секунд)", "Зелье слабости + Блок слизи = Зелье усталости II (15 секунд)"] [brewing.hunger] - name = "Голод в бутылке" - description = "Накорми ненасытного!" - lore1 = "Мутное зелье + Гнилая плоть = Зелье голода (30 секунд)" - lore2 = "Зелье слабости + Гнилая плоть = Зелье голода III (15 секунд)" - lore = ["Мутное зелье + Гнилая плоть = Зелье голода (30 секунд)", "Зелье слабости + Гнилая плоть = Зелье голода III (15 секунд)"] +name = "Голод в бутылке" +description = "Накорми ненасытного!" +lore1 = "Мутное зелье + Гнилая плоть = Зелье голода (30 секунд)" +lore2 = "Зелье слабости + Гнилая плоть = Зелье голода III (15 секунд)" +lore = ["Мутное зелье + Гнилая плоть = Зелье голода (30 секунд)", "Зелье слабости + Гнилая плоть = Зелье голода III (15 секунд)"] [brewing.nausea] - name = "Тошнота в бутылке" - description = "Меня от тебя тошнит!" - lore1 = "Мутное зелье + Коричневый гриб = Зелье тошноты (16 секунд)" - lore2 = "Мутное зелье + Багровый гриб = Зелье тошноты II (8 секунд)" - lore = ["Мутное зелье + Коричневый гриб = Зелье тошноты (16 секунд)", "Мутное зелье + Багровый гриб = Зелье тошноты II (8 секунд)"] +name = "Тошнота в бутылке" +description = "Меня от тебя тошнит!" +lore1 = "Мутное зелье + Коричневый гриб = Зелье тошноты (16 секунд)" +lore2 = "Мутное зелье + Багровый гриб = Зелье тошноты II (8 секунд)" +lore = ["Мутное зелье + Коричневый гриб = Зелье тошноты (16 секунд)", "Мутное зелье + Багровый гриб = Зелье тошноты II (8 секунд)"] [brewing.blindness] - name = "Слепота в бутылке" - description = "Ты ужасный человек..." - lore1 = "Мутное зелье + Чернильный мешок = Зелье слепоты (30 секунд)" - lore2 = "Мутное зелье + Светящийся чернильный мешок = Зелье слепоты II (15 секунд)" - lore = ["Мутное зелье + Чернильный мешок = Зелье слепоты (30 секунд)", "Мутное зелье + Светящийся чернильный мешок = Зелье слепоты II (15 секунд)"] +name = "Слепота в бутылке" +description = "Ты ужасный человек..." +lore1 = "Мутное зелье + Чернильный мешок = Зелье слепоты (30 секунд)" +lore2 = "Мутное зелье + Светящийся чернильный мешок = Зелье слепоты II (15 секунд)" +lore = ["Мутное зелье + Чернильный мешок = Зелье слепоты (30 секунд)", "Мутное зелье + Светящийся чернильный мешок = Зелье слепоты II (15 секунд)"] [brewing.resistance] - name = "Сопротивление в бутылке" - description = "Укрепление в лучшем виде!" - lore1 = "Мутное зелье + Железный слиток = Зелье сопротивления (60 секунд)" - lore2 = "Мутное зелье + Железный блок = Зелье сопротивления II (30 секунд)" - lore = ["Мутное зелье + Железный слиток = Зелье сопротивления (60 секунд)", "Мутное зелье + Железный блок = Зелье сопротивления II (30 секунд)"] +name = "Сопротивление в бутылке" +description = "Укрепление в лучшем виде!" +lore1 = "Мутное зелье + Железный слиток = Зелье сопротивления (60 секунд)" +lore2 = "Мутное зелье + Железный блок = Зелье сопротивления II (30 секунд)" +lore = ["Мутное зелье + Железный слиток = Зелье сопротивления (60 секунд)", "Мутное зелье + Железный блок = Зелье сопротивления II (30 секунд)"] [brewing.health_boost] - name = "Жизнь в бутылке" - description = "Когда максимального здоровья недостаточно..." - lore1 = "Зелье исцеления + Золотое яблоко = Зелье усиления здоровья (120 секунд)" - lore2 = "Зелье исцеления + Зачарованное золотое яблоко = Зелье усиления здоровья II (120 секунд)" - lore = ["Зелье исцеления + Золотое яблоко = Зелье усиления здоровья (120 секунд)", "Зелье исцеления + Зачарованное золотое яблоко = Зелье усиления здоровья II (120 секунд)"] +name = "Жизнь в бутылке" +description = "Когда максимального здоровья недостаточно..." +lore1 = "Зелье исцеления + Золотое яблоко = Зелье усиления здоровья (120 секунд)" +lore2 = "Зелье исцеления + Зачарованное золотое яблоко = Зелье усиления здоровья II (120 секунд)" +lore = ["Зелье исцеления + Золотое яблоко = Зелье усиления здоровья (120 секунд)", "Зелье исцеления + Зачарованное золотое яблоко = Зелье усиления здоровья II (120 секунд)"] [brewing.decay] - name = "Распад в бутылке" - description = "Кто бы мог подумать, что Детрит окажется таким полезным?" - lore1 = "Зелье слабости + Ядовитый картофель = Зелье иссушения (16 секунд)" - lore2 = "Зелье слабости + Багровые корни = Зелье иссушения II (8 секунд)" - lore = ["Зелье слабости + Ядовитый картофель = Зелье иссушения (16 секунд)", "Зелье слабости + Багровые корни = Зелье иссушения II (8 секунд)"] +name = "Распад в бутылке" +description = "Кто бы мог подумать, что Детрит окажется таким полезным?" +lore1 = "Зелье слабости + Ядовитый картофель = Зелье иссушения (16 секунд)" +lore2 = "Зелье слабости + Багровые корни = Зелье иссушения II (8 секунд)" +lore = ["Зелье слабости + Ядовитый картофель = Зелье иссушения (16 секунд)", "Зелье слабости + Багровые корни = Зелье иссушения II (8 секунд)"] [brewing.saturation] - name = "Насыщение в бутылке" - description = "Знаешь... я даже не голоден..." - lore1 = "Зелье регенерации + Печёный картофель = Зелье насыщения" - lore2 = "Зелье регенерации + Сноп сена = Зелье насыщения II" - lore = ["Зелье регенерации + Печёный картофель = Зелье насыщения", "Зелье регенерации + Сноп сена = Зелье насыщения II"] +name = "Насыщение в бутылке" +description = "Знаешь... я даже не голоден..." +lore1 = "Зелье регенерации + Печёный картофель = Зелье насыщения" +lore2 = "Зелье регенерации + Сноп сена = Зелье насыщения II" +lore = ["Зелье регенерации + Печёный картофель = Зелье насыщения", "Зелье регенерации + Сноп сена = Зелье насыщения II"] # blocking [blocking] [blocking.chain_armorer] - name = "Цепи Мефистофеля" - description = "Позволяет создавать кольчужную броню" - lore1 = "Рецепт крафта такой же, как и для любой другой брони, но с железными самородками" - lore = ["Рецепт крафта такой же, как и для любой другой брони, но с железными самородками"] +name = "Цепи Мефистофеля" +description = "Позволяет создавать кольчужную броню" +lore1 = "Рецепт крафта такой же, как и для любой другой брони, но с железными самородками" +lore = ["Рецепт крафта такой же, как и для любой другой брони, но с железными самородками"] [blocking.horse_armorer] - name = "Крафт конской брони" - description = "Позволяет создавать конскую броню" - lore1 = "Окружите седло материалом, из которого хотите изготовить броню" - lore = ["Окружите седло материалом, из которого хотите изготовить броню"] +name = "Крафт конской брони" +description = "Позволяет создавать конскую броню" +lore1 = "Окружите седло материалом, из которого хотите изготовить броню" +lore = ["Окружите седло материалом, из которого хотите изготовить броню"] [blocking.saddle_crafter] - name = "Крафт седла" - description = "Изготовьте седло из кожи" - lore1 = "Рецепт: 5 кожи:" - lore = ["Рецепт: 5 кожи:"] +name = "Крафт седла" +description = "Изготовьте седло из кожи" +lore1 = "Рецепт: 5 кожи:" +lore = ["Рецепт: 5 кожи:"] [blocking.multi_armor] - name = "Мультиброня" - description = "Привяжите элитры к броне" - lore1 = "Невероятный навык для путешествий." - lore2 = "Динамически объединяйте и меняйте броню/элитры на лету!" - lore3 = "Для объединения: Shift + клик по предмету поверх другого в инвентаре." - lore4 = "Для разборки: бросьте предмет с зажатым Shift, и он будет разобран." - lore5 = "Если ваша мультиброня уничтожена, вы потеряете все предметы в ней." - lore6 = "Мне не нужна броня, она меня разочаровывает..." - lore = ["Невероятный навык для путешествий.", "Динамически объединяйте и меняйте броню/элитры на лету!", "Для объединения: Shift + клик по предмету поверх другого в инвентаре.", "Для разборки: бросьте предмет с зажатым Shift, и он будет разобран.", "Если ваша мультиброня уничтожена, вы потеряете все предметы в ней.", "Мне не нужна броня, она меня разочаровывает..."] +name = "Мультиброня" +description = "Привяжите элитры к броне" +lore1 = "Невероятный навык для путешествий." +lore2 = "Динамически объединяйте и меняйте броню/элитры на лету!" +lore3 = "Для объединения: Shift + клик по предмету поверх другого в инвентаре." +lore4 = "Для разборки: бросьте предмет с зажатым Shift, и он будет разобран." +lore5 = "Если ваша мультиброня уничтожена, вы потеряете все предметы в ней." +lore6 = "Мне не нужна броня, она меня разочаровывает..." +lore = ["Невероятный навык для путешествий.", "Динамически объединяйте и меняйте броню/элитры на лету!", "Для объединения: Shift + клик по предмету поверх другого в инвентаре.", "Для разборки: бросьте предмет с зажатым Shift, и он будет разобран.", "Если ваша мультиброня уничтожена, вы потеряете все предметы в ней.", "Мне не нужна броня, она меня разочаровывает..."] # crafting [crafting] [crafting.deconstruction] - name = "Деконструкция" - description = "Разбирайте блоки и предметы на базовые компоненты!" - lore1 = "Бросьте любой предмет на землю." - lore2 = "Затем присядьте и нажмите ПКМ ножницами" - lore = ["Бросьте любой предмет на землю.", "Затем присядьте и нажмите ПКМ ножницами"] +name = "Деконструкция" +description = "Разбирайте блоки и предметы на базовые компоненты!" +lore1 = "Бросьте любой предмет на землю." +lore2 = "Затем присядьте и нажмите ПКМ ножницами" +lore = ["Бросьте любой предмет на землю.", "Затем присядьте и нажмите ПКМ ножницами"] [crafting.xp] - name = "Опыт за крафт" - description = "Получайте пассивный опыт при крафте" - lore1 = "Получайте опыт при крафте" - lore = ["Получайте опыт при крафте"] +name = "Опыт за крафт" +description = "Получайте пассивный опыт при крафте" +lore1 = "Получайте опыт при крафте" +lore = ["Получайте опыт при крафте"] [crafting.reconstruction] - name = "Реконструкция руды" - description = "Воссоздавайте руды из их базовых компонентов!" - lore1 = "8 единиц дропа и 1 основа = 1 руда (бесформенный рецепт)" - lore2 = "Дроп должен быть переплавлен (если применимо)" - lore3 = "Не включает: обломки, кварц, изумруды и т.д." - lore4 = "Основа = оболочка, т.е.: камень, незеррак, глубинный сланец" - lore = ["8 единиц дропа и 1 основа = 1 руда (бесформенный рецепт)", "Дроп должен быть переплавлен (если применимо)", "Не включает: обломки, кварц, изумруды и т.д.", "Основа = оболочка, т.е.: камень, незеррак, глубинный сланец"] +name = "Реконструкция руды" +description = "Воссоздавайте руды из их базовых компонентов!" +lore1 = "8 единиц дропа и 1 основа = 1 руда (бесформенный рецепт)" +lore2 = "Дроп должен быть переплавлен (если применимо)" +lore3 = "Не включает: обломки, кварц, изумруды и т.д." +lore4 = "Основа = оболочка, т.е.: камень, незеррак, глубинный сланец" +lore = ["8 единиц дропа и 1 основа = 1 руда (бесформенный рецепт)", "Дроп должен быть переплавлен (если применимо)", "Не включает: обломки, кварц, изумруды и т.д.", "Основа = оболочка, т.е.: камень, незеррак, глубинный сланец"] [crafting.leather] - name = "Крафт кожи" - description = "Изготавливайте кожу из гнилой плоти" - lore1 = "Просто положите гнилую плоть на костёр!" - lore = ["Просто положите гнилую плоть на костёр!"] +name = "Крафт кожи" +description = "Изготавливайте кожу из гнилой плоти" +lore1 = "Просто положите гнилую плоть на костёр!" +lore = ["Просто положите гнилую плоть на костёр!"] [crafting.backpacks] - name = "Рюкзаки Бутильера!" - description = "Мешки Mojang теперь в игре!" - lore1 = "Вы должны быть в режиме выживания" - lore2 = "XLX : Кожа, Поводок, Кожа" - lore3 = "XSX : Кожа, Бочка, Кожа" - lore4 = "XCX : Кожа, Сундук, Кожа" - lore = ["Вы должны быть в режиме выживания", "XLX : Кожа, Поводок, Кожа", "XSX : Кожа, Бочка, Кожа", "XCX : Кожа, Сундук, Кожа"] +name = "Рюкзаки Бутильера!" +description = "Мешки Mojang теперь в игре!" +lore1 = "Вы должны быть в режиме выживания" +lore2 = "XLX : Кожа, Поводок, Кожа" +lore3 = "XSX : Кожа, Бочка, Кожа" +lore4 = "XCX : Кожа, Сундук, Кожа" +lore = ["Вы должны быть в режиме выживания", "XLX : Кожа, Поводок, Кожа", "XSX : Кожа, Бочка, Кожа", "XCX : Кожа, Сундук, Кожа"] [crafting.stations] - name = "Портативные столы!" - description = "Используйте верстак прямо в руке!" - lore2 = "ВСЕ ПРЕДМЕТЫ, ЗАБЫТЫЕ В СТОЛЕ ПРИ ЗАКРЫТИИ, БУДУТ ПОТЕРЯНЫ НАВСЕГДА!" - lore3 = "Доступные столы: Наковальня, Верстак, Точило, Стол картографа, Камнерез, Ткацкий станок" - lore = ["ВСЕ ПРЕДМЕТЫ, ЗАБЫТЫЕ В СТОЛЕ ПРИ ЗАКРЫТИИ, БУДУТ ПОТЕРЯНЫ НАВСЕГДА!", "Доступные столы: Наковальня, Верстак, Точило, Стол картографа, Камнерез, Ткацкий станок"] +name = "Портативные столы!" +description = "Используйте верстак прямо в руке!" +lore2 = "ВСЕ ПРЕДМЕТЫ, ЗАБЫТЫЕ В СТОЛЕ ПРИ ЗАКРЫТИИ, БУДУТ ПОТЕРЯНЫ НАВСЕГДА!" +lore3 = "Доступные столы: Наковальня, Верстак, Точило, Стол картографа, Камнерез, Ткацкий станок" +lore = ["ВСЕ ПРЕДМЕТЫ, ЗАБЫТЫЕ В СТОЛЕ ПРИ ЗАКРЫТИИ, БУДУТ ПОТЕРЯНЫ НАВСЕГДА!", "Доступные столы: Наковальня, Верстак, Точило, Стол картографа, Камнерез, Ткацкий станок"] [crafting.skulls] - name = "Крафт черепов!" - description = "Используя материалы, вы можете создавать черепа мобов!" - lore1 = "Окружите костяной блок следующим, чтобы получить череп:" - lore2 = "Зомби: Гнилая плоть" - lore3 = "Скелет: Кость" - lore4 = "Крипер: Порох" - lore5 = "Иссушитель: Незеритовый кирпич" - lore6 = "Дракон: Дыхание дракона" - lore = ["Окружите костяной блок следующим, чтобы получить череп:", "Зомби: Гнилая плоть", "Скелет: Кость", "Крипер: Порох", "Иссушитель: Незеритовый кирпич", "Дракон: Дыхание дракона"] +name = "Крафт черепов!" +description = "Используя материалы, вы можете создавать черепа мобов!" +lore1 = "Окружите костяной блок следующим, чтобы получить череп:" +lore2 = "Зомби: Гнилая плоть" +lore3 = "Скелет: Кость" +lore4 = "Крипер: Порох" +lore5 = "Иссушитель: Незеритовый кирпич" +lore6 = "Дракон: Дыхание дракона" +lore = ["Окружите костяной блок следующим, чтобы получить череп:", "Зомби: Гнилая плоть", "Скелет: Кость", "Крипер: Порох", "Иссушитель: Незеритовый кирпич", "Дракон: Дыхание дракона"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Время в бутылке" - description = "Носите временную бутылку, которая накапливает время. Тратьте его на ускорение тикающих блоков, растений и детёнышей животных. Рецепт (бесформенный): Зелье скорости + Часы + Стеклянная бутылка." - lore1 = "Секунд заряжается за тик" - lore2 = "Ускорение времени за накопленную секунду" - lore3 = "Рецепт (бесформенный): Зелье скорости + Часы + Стеклянная бутылка" - lore = ["Секунд заряжается за тик", "Ускорение времени за накопленную секунду", "Рецепт (бесформенный): Зелье скорости + Часы + Стеклянная бутылка"] +name = "Время в бутылке" +description = "Носите временную бутылку, которая накапливает время. Тратьте его на ускорение тикающих блоков, растений и детёнышей животных. Рецепт (бесформенный): Зелье скорости + Часы + Стеклянная бутылка." +lore1 = "Секунд заряжается за тик" +lore2 = "Ускорение времени за накопленную секунду" +lore3 = "Рецепт (бесформенный): Зелье скорости + Часы + Стеклянная бутылка" +lore = ["Секунд заряжается за тик", "Ускорение времени за накопленную секунду", "Рецепт (бесформенный): Зелье скорости + Часы + Стеклянная бутылка"] [chronos.aberrant_touch] - name = "Аберрантное касание" - description = "Атаки ближнего боя накладывают накапливающееся замедление за счёт голода, с ограничениями для PvP, и обездвиживают цель при 5 стаках." - lore1 = "Атаки ближнего боя накладывают замедление" - lore2 = "Ограничение длительности замедления для PvE" - lore3 = "Ограничение силы замедления для PvP" - lore = ["Атаки ближнего боя накладывают замедление", "Ограничение длительности замедления для PvE", "Ограничение силы замедления для PvP"] +name = "Аберрантное касание" +description = "Атаки ближнего боя накладывают накапливающееся замедление за счёт голода, с ограничениями для PvP, и обездвиживают цель при 5 стаках." +lore1 = "Атаки ближнего боя накладывают замедление" +lore2 = "Ограничение длительности замедления для PvE" +lore3 = "Ограничение силы замедления для PvP" +lore = ["Атаки ближнего боя накладывают замедление", "Ограничение длительности замедления для PvE", "Ограничение силы замедления для PvP"] [chronos.instant_recall] - name = "Мгновенный откат" - description = "Нажмите ЛКМ или ПКМ с часами в руке, чтобы перемотать время к недавнему снимку с восстановлением здоровья и голода." - lore1 = "Длительность перемотки" - lore2 = "Перезарядка" - lore3 = "Инвентарь не откатывается" - lore = ["Длительность перемотки", "Перезарядка", "Инвентарь не откатывается"] +name = "Мгновенный откат" +description = "Нажмите ЛКМ или ПКМ с часами в руке, чтобы перемотать время к недавнему снимку с восстановлением здоровья и голода." +lore1 = "Длительность перемотки" +lore2 = "Перезарядка" +lore3 = "Инвентарь не откатывается" +lore = ["Длительность перемотки", "Перезарядка", "Инвентарь не откатывается"] [chronos.time_bomb] - name = "Хронобомба" - description = "Бросьте скрафченную хронобомбу, которая создаёт временное поле, замедляет существ и замораживает снаряды." - lore1 = "Радиус временного поля" - lore2 = "Длительность временного поля" - lore3 = "Перезарядка бомбы" - lore4 = "Рецепт (бесформенный): Часы + Снежок + Алмаз + Песок" - lore = ["Радиус временного поля", "Длительность временного поля", "Перезарядка бомбы", "Рецепт (бесформенный): Часы + Снежок + Алмаз + Песок"] +name = "Хронобомба" +description = "Бросьте скрафченную хронобомбу, которая создаёт временное поле, замедляет существ и замораживает снаряды." +lore1 = "Радиус временного поля" +lore2 = "Длительность временного поля" +lore3 = "Перезарядка бомбы" +lore4 = "Рецепт (бесформенный): Часы + Снежок + Алмаз + Песок" +lore = ["Радиус временного поля", "Длительность временного поля", "Перезарядка бомбы", "Рецепт (бесформенный): Часы + Снежок + Алмаз + Песок"] # discovery [discovery] [discovery.armor] - name = "Мировая броня" - description = "Пассивная броня в зависимости от твёрдости ближайших блоков." - lore1 = "Пассивная броня" - lore2 = "На основе твёрдости ближайших блоков" - lore3 = "Сила брони:" - lore = ["Пассивная броня", "На основе твёрдости ближайших блоков", "Сила брони:"] +name = "Мировая броня" +description = "Пассивная броня в зависимости от твёрдости ближайших блоков." +lore1 = "Пассивная броня" +lore2 = "На основе твёрдости ближайших блоков" +lore3 = "Сила брони:" +lore = ["Пассивная броня", "На основе твёрдости ближайших блоков", "Сила брони:"] [discovery.unity] - name = "Экспериментальное единство" - description = "Сбор сфер опыта добавляет опыт к случайным навыкам." - lore1 = "Опыта " - lore2 = "за сферу" - lore = ["Опыта ", "за сферу"] +name = "Экспериментальное единство" +description = "Сбор сфер опыта добавляет опыт к случайным навыкам." +lore1 = "Опыта " +lore2 = "за сферу" +lore = ["Опыта ", "за сферу"] [discovery.resist] - name = "Экспериментальное сопротивление" - description = "Тратьте опыт для снижения урона, только когда удар может уронить вас ниже 5 сердец или убить." - lore0 = "Срабатывает только при критическом здоровье (<= 5 сердец) раз в 15 секунд" - lore1 = " снижения урона" - lore2 = "опыта потрачено" - lore = ["Срабатывает только при критическом здоровье (<= 5 сердец) раз в 15 секунд", " снижения урона", "опыта потрачено"] +name = "Экспериментальное сопротивление" +description = "Тратьте опыт для снижения урона, только когда удар может уронить вас ниже 5 сердец или убить." +lore0 = "Срабатывает только при критическом здоровье (<= 5 сердец) раз в 15 секунд" +lore1 = " снижения урона" +lore2 = "опыта потрачено" +lore = ["Срабатывает только при критическом здоровье (<= 5 сердец) раз в 15 секунд", " снижения урона", "опыта потрачено"] [discovery.villager] - name = "Привлекательность для жителей" - description = "Позволяет получать лучшие сделки у жителей!" - lore1 = "Тратится опыт за каждое взаимодействие с жителями" - lore2 = "Шанс за взаимодействие потратить опыт и улучшить торговлю" - lore3 = "Необходимый расход опыта за взаимодействие" - lore = ["Тратится опыт за каждое взаимодействие с жителями", "Шанс за взаимодействие потратить опыт и улучшить торговлю", "Необходимый расход опыта за взаимодействие"] +name = "Привлекательность для жителей" +description = "Позволяет получать лучшие сделки у жителей!" +lore1 = "Тратится опыт за каждое взаимодействие с жителями" +lore2 = "Шанс за взаимодействие потратить опыт и улучшить торговлю" +lore3 = "Необходимый расход опыта за взаимодействие" +lore = ["Тратится опыт за каждое взаимодействие с жителями", "Шанс за взаимодействие потратить опыт и улучшить торговлю", "Необходимый расход опыта за взаимодействие"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Возврат лазурита" - description = "За 1 дополнительный уровень опыта есть шанс получить бесплатный лазурит" - lore1 = "За каждый уровень увеличивается стоимость зачарования на 1, но может вернуться до 3 лазурита" - lore = ["За каждый уровень увеличивается стоимость зачарования на 1, но может вернуться до 3 лазурита"] +name = "Возврат лазурита" +description = "За 1 дополнительный уровень опыта есть шанс получить бесплатный лазурит" +lore1 = "За каждый уровень увеличивается стоимость зачарования на 1, но может вернуться до 3 лазурита" +lore = ["За каждый уровень увеличивается стоимость зачарования на 1, но может вернуться до 3 лазурита"] [enchanting.quick_enchant] - name = "Быстрое зачарование" - description = "Зачаровывайте предметы, нажимая книгами зачарований прямо по ним." - lore1 = "Максимум комбинированных уровней" - lore2 = "Нельзя зачаровать предмет с более чем " - lore3 = "силы" - lore = ["Максимум комбинированных уровней", "Нельзя зачаровать предмет с более чем ", "силы"] +name = "Быстрое зачарование" +description = "Зачаровывайте предметы, нажимая книгами зачарований прямо по ним." +lore1 = "Максимум комбинированных уровней" +lore2 = "Нельзя зачаровать предмет с более чем " +lore3 = "силы" +lore = ["Максимум комбинированных уровней", "Нельзя зачаровать предмет с более чем ", "силы"] [enchanting.return] - name = "Возврат опыта" - description = "Опыт зачарования возвращается вам при зачаровании предмета." - lore1 = "Потраченный опыт имеет шанс вернуться при зачаровании предмета" - lore2 = "Опыта за зачарование" - lore = ["Потраченный опыт имеет шанс вернуться при зачаровании предмета", "Опыта за зачарование"] +name = "Возврат опыта" +description = "Опыт зачарования возвращается вам при зачаровании предмета." +lore1 = "Потраченный опыт имеет шанс вернуться при зачаровании предмета" +lore2 = "Опыта за зачарование" +lore = ["Потраченный опыт имеет шанс вернуться при зачаровании предмета", "Опыта за зачарование"] # excavation [excavation] [excavation.haste] - name = "Торопливый копатель" - description = "Ускоряет процесс раскопок с помощью Спешки!" - lore1 = "Получайте Спешку при раскопках" - lore2 = "x уровней Спешки при начале добычи ЛЮБОГО блока." - lore = ["Получайте Спешку при раскопках", "x уровней Спешки при начале добычи ЛЮБОГО блока."] +name = "Торопливый копатель" +description = "Ускоряет процесс раскопок с помощью Спешки!" +lore1 = "Получайте Спешку при раскопках" +lore2 = "x уровней Спешки при начале добычи ЛЮБОГО блока." +lore = ["Получайте Спешку при раскопках", "x уровней Спешки при начале добычи ЛЮБОГО блока."] [excavation.spelunker] - name = "Суперзрячий спелеолог!" - description = "Видьте руды сквозь землю!" - lore1 = "Руда во второй руке, светящиеся ягоды в основной, и присядьте!" - lore2 = "Радиус в блоках: " - lore3 = "Расходует светящуюся ягоду при использовании" - lore = ["Руда во второй руке, светящиеся ягоды в основной, и присядьте!", "Радиус в блоках: ", "Расходует светящуюся ягоду при использовании"] +name = "Суперзрячий спелеолог!" +description = "Видьте руды сквозь землю!" +lore1 = "Руда во второй руке, светящиеся ягоды в основной, и присядьте!" +lore2 = "Радиус в блоках: " +lore3 = "Расходует светящуюся ягоду при использовании" +lore = ["Руда во второй руке, светящиеся ягоды в основной, и присядьте!", "Радиус в блоках: ", "Расходует светящуюся ягоду при использовании"] [excavation.drop_to_inventory] - name = "Автосбор с лопаты" +name = "Автосбор с лопаты" [excavation.omni_tool] - name = "МУЛЬТИ-И.Н.С.Т.Р.У.М.Е.Н.Т" - description = "Переусложнённый роскошный мультитул Такла" - lore1 = "Пожалуй, самый мощный навык, позволяющий" - lore2 = "динамически объединять и менять инструменты на лету по необходимости." - lore3 = "Для объединения: Shift + клик по предмету поверх другого в инвентаре." - lore4 = "Для разборки: бросьте предмет с зажатым Shift, и он будет разобран." - lore5 = "Нельзя сломать инструменты в мультитуле, но нельзя использовать сломанные" - lore6 = "всего объединяемых предметов." - lore7 = "Можно использовать пять-шесть инструментов, а можно всего один!" - lore = ["Пожалуй, самый мощный навык, позволяющий", "динамически объединять и менять инструменты на лету по необходимости.", "Для объединения: Shift + клик по предмету поверх другого в инвентаре.", "Для разборки: бросьте предмет с зажатым Shift, и он будет разобран.", "Нельзя сломать инструменты в мультитуле, но нельзя использовать сломанные", "всего объединяемых предметов.", "Можно использовать пять-шесть инструментов, а можно всего один!"] +name = "МУЛЬТИ-И.Н.С.Т.Р.У.М.Е.Н.Т" +description = "Переусложнённый роскошный мультитул Такла" +lore1 = "Пожалуй, самый мощный навык, позволяющий" +lore2 = "динамически объединять и менять инструменты на лету по необходимости." +lore3 = "Для объединения: Shift + клик по предмету поверх другого в инвентаре." +lore4 = "Для разборки: бросьте предмет с зажатым Shift, и он будет разобран." +lore5 = "Нельзя сломать инструменты в мультитуле, но нельзя использовать сломанные" +lore6 = "всего объединяемых предметов." +lore7 = "Можно использовать пять-шесть инструментов, а можно всего один!" +lore = ["Пожалуй, самый мощный навык, позволяющий", "динамически объединять и менять инструменты на лету по необходимости.", "Для объединения: Shift + клик по предмету поверх другого в инвентаре.", "Для разборки: бросьте предмет с зажатым Shift, и он будет разобран.", "Нельзя сломать инструменты в мультитуле, но нельзя использовать сломанные", "всего объединяемых предметов.", "Можно использовать пять-шесть инструментов, а можно всего один!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Аура роста" - description = "Выращивайте природу вокруг себя аурой" - lore1 = "Радиус в блоках" - lore2 = "Сила ауры роста" - lore3 = "Расход еды" - lore = ["Радиус в блоках", "Сила ауры роста", "Расход еды"] +name = "Аура роста" +description = "Выращивайте природу вокруг себя аурой" +lore1 = "Радиус в блоках" +lore2 = "Сила ауры роста" +lore3 = "Расход еды" +lore = ["Радиус в блоках", "Сила ауры роста", "Расход еды"] [herbalism.hippo] - name = "Бегемот травника" - description = "Употребление пищи даёт больше насыщения" - lore1 = "Еда) дополнительные очки насыщения при употреблении" - lore = ["Еда) дополнительные очки насыщения при употреблении"] +name = "Бегемот травника" +description = "Употребление пищи даёт больше насыщения" +lore1 = "Еда) дополнительные очки насыщения при употреблении" +lore = ["Еда) дополнительные очки насыщения при употреблении"] [herbalism.myconid] - name = "Миконид травника" - description = "Даёт возможность создавать мицелий" - lore1 = "Любая земля + коричневый и красный гриб = мицелий." - lore = ["Любая земля + коричневый и красный гриб = мицелий."] +name = "Миконид травника" +description = "Даёт возможность создавать мицелий" +lore1 = "Любая земля + коричневый и красный гриб = мицелий." +lore = ["Любая земля + коричневый и красный гриб = мицелий."] [herbalism.terralid] - name = "Терралид травника" - description = "Даёт возможность создавать блоки дёрна" - lore1 = "Три семечка над 3 блоками земли = 3 блока дёрна." - lore = ["Три семечка над 3 блоками земли = 3 блока дёрна."] +name = "Терралид травника" +description = "Даёт возможность создавать блоки дёрна" +lore1 = "Три семечка над 3 блоками земли = 3 блока дёрна." +lore = ["Три семечка над 3 блоками земли = 3 блока дёрна."] [herbalism.cobweb] - name = "Плетёльщик паутины" - description = "Даёт возможность создавать паутину на верстаке" - lore1 = "Девять нитей = одна паутина." - lore = ["Девять нитей = одна паутина."] +name = "Плетёльщик паутины" +description = "Даёт возможность создавать паутину на верстаке" +lore1 = "Девять нитей = одна паутина." +lore = ["Девять нитей = одна паутина."] [herbalism.mushroom_blocks] - name = "Грибник" - description = "Даёт возможность создавать грибные блоки на верстаке" - lore1 = "Четыре гриба = блок, или блок = стебель." - lore = ["Четыре гриба = блок, или блок = стебель."] +name = "Грибник" +description = "Даёт возможность создавать грибные блоки на верстаке" +lore1 = "Четыре гриба = блок, или блок = стебель." +lore = ["Четыре гриба = блок, или блок = стебель."] [herbalism.drop_to_inventory] - name = "Автосбор с мотыги" +name = "Автосбор с мотыги" [herbalism.hungry_shield] - name = "Голодный щит" - description = "Получайте урон в голод, а не в здоровье." - lore1 = "Сопротивление за счёт голода" - lore = ["Сопротивление за счёт голода"] +name = "Голодный щит" +description = "Получайте урон в голод, а не в здоровье." +lore1 = "Сопротивление за счёт голода" +lore = ["Сопротивление за счёт голода"] [herbalism.luck] - name = "Удача травника" - description = "При разрушении травы/цветов есть шанс получить случайный предмет" - lore0 = "Цветы = еда, Трава = семена" - lore1 = "Шанс получить предмет из цветов" - lore2 = "Шанс получить предмет из травы" - lore = ["Цветы = еда, Трава = семена", "Шанс получить предмет из цветов", "Шанс получить предмет из травы"] +name = "Удача травника" +description = "При разрушении травы/цветов есть шанс получить случайный предмет" +lore0 = "Цветы = еда, Трава = семена" +lore1 = "Шанс получить предмет из цветов" +lore2 = "Шанс получить предмет из травы" +lore = ["Цветы = еда, Трава = семена", "Шанс получить предмет из цветов", "Шанс получить предмет из травы"] [herbalism.replant] - name = "Сбор и пересадка" - description = "Нажмите ПКМ мотыгой по культуре, чтобы собрать и пересадить." - lore1 = "Радиус пересадки в блоках" - lore = ["Радиус пересадки в блоках"] +name = "Сбор и пересадка" +description = "Нажмите ПКМ мотыгой по культуре, чтобы собрать и пересадить." +lore1 = "Радиус пересадки в блоках" +lore = ["Радиус пересадки в блоках"] # hunter [hunter] [hunter.adrenaline] - name = "Адреналин" - description = "Наносите больше урона, чем ниже ваше здоровье (ближний бой)" - lore1 = "Максимальный урон" - lore = ["Максимальный урон"] +name = "Адреналин" +description = "Наносите больше урона, чем ниже ваше здоровье (ближний бой)" +lore1 = "Максимальный урон" +lore = ["Максимальный урон"] [hunter.penalty] - name = "" - description = "" - lore1 = "Вы получите отравление, если у вас закончится голод" - lore = ["Вы получите отравление, если у вас закончится голод"] +name = "" +description = "" +lore1 = "Вы получите отравление, если у вас закончится голод" +lore = ["Вы получите отравление, если у вас закончится голод"] [hunter.drop_to_inventory] - name = "Автосбор предметов" - description = "При убийстве или разрушении блока мечом дроп телепортируется в ваш инвентарь" - lore1 = "Когда предмет выпадает из моба/блока, он попадает в ваш инвентарь, если возможно." - lore = ["Когда предмет выпадает из моба/блока, он попадает в ваш инвентарь, если возможно."] +name = "Автосбор предметов" +description = "При убийстве или разрушении блока мечом дроп телепортируется в ваш инвентарь" +lore1 = "Когда предмет выпадает из моба/блока, он попадает в ваш инвентарь, если возможно." +lore = ["Когда предмет выпадает из моба/блока, он попадает в ваш инвентарь, если возможно."] [hunter.invisibility] - name = "Исчезающий шаг" - description = "При получении удара вы получаете невидимость ценой голода" - lore1 = "Пассивная невидимость при получении удара" - lore2 = "x уровней невидимости на 3 секунды при ударе" - lore3 = "x накопление голода" - lore4 = "Длительность и множитель голода." - lore5 = "Длительность невидимости" - lore = ["Пассивная невидимость при получении удара", "x уровней невидимости на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Длительность невидимости"] +name = "Исчезающий шаг" +description = "При получении удара вы получаете невидимость ценой голода" +lore1 = "Пассивная невидимость при получении удара" +lore2 = "x уровней невидимости на 3 секунды при ударе" +lore3 = "x накопление голода" +lore4 = "Длительность и множитель голода." +lore5 = "Длительность невидимости" +lore = ["Пассивная невидимость при получении удара", "x уровней невидимости на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Длительность невидимости"] [hunter.jump_boost] - name = "Высоты охотника" - description = "При получении удара вы получаете усиление прыжка ценой голода" - lore1 = "Пассивное усиление прыжка при получении удара" - lore2 = "x уровней усиления прыжка на 3 секунды при ударе" - lore3 = "x накопление голода" - lore4 = "Длительность и множитель голода." - lore5 = "Множитель усиления прыжка, не длительность." - lore = ["Пассивное усиление прыжка при получении удара", "x уровней усиления прыжка на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель усиления прыжка, не длительность."] +name = "Высоты охотника" +description = "При получении удара вы получаете усиление прыжка ценой голода" +lore1 = "Пассивное усиление прыжка при получении удара" +lore2 = "x уровней усиления прыжка на 3 секунды при ударе" +lore3 = "x накопление голода" +lore4 = "Длительность и множитель голода." +lore5 = "Множитель усиления прыжка, не длительность." +lore = ["Пассивное усиление прыжка при получении удара", "x уровней усиления прыжка на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель усиления прыжка, не длительность."] [hunter.luck] - name = "Удача охотника" - description = "При получении удара вы получаете удачу ценой голода" - lore1 = "Пассивная удача при получении удара" - lore2 = "x уровней удачи на 3 секунды при ударе" - lore3 = "x накопление голода" - lore4 = "Длительность и множитель голода." - lore5 = "Множитель удачи, не длительность." - lore = ["Пассивная удача при получении удара", "x уровней удачи на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель удачи, не длительность."] +name = "Удача охотника" +description = "При получении удара вы получаете удачу ценой голода" +lore1 = "Пассивная удача при получении удара" +lore2 = "x уровней удачи на 3 секунды при ударе" +lore3 = "x накопление голода" +lore4 = "Длительность и множитель голода." +lore5 = "Множитель удачи, не длительность." +lore = ["Пассивная удача при получении удара", "x уровней удачи на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель удачи, не длительность."] [hunter.regen] - name = "Регенерация охотника" - description = "При получении удара вы получаете регенерацию ценой голода" - lore1 = "Пассивная регенерация при получении удара" - lore2 = "x уровней регенерации на 3 секунды при ударе" - lore3 = "x накопление голода" - lore4 = "Длительность и множитель голода." - lore5 = "Множитель регенерации, не длительность." - lore = ["Пассивная регенерация при получении удара", "x уровней регенерации на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель регенерации, не длительность."] +name = "Регенерация охотника" +description = "При получении удара вы получаете регенерацию ценой голода" +lore1 = "Пассивная регенерация при получении удара" +lore2 = "x уровней регенерации на 3 секунды при ударе" +lore3 = "x накопление голода" +lore4 = "Длительность и множитель голода." +lore5 = "Множитель регенерации, не длительность." +lore = ["Пассивная регенерация при получении удара", "x уровней регенерации на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель регенерации, не длительность."] [hunter.resistance] - name = "Сопротивление охотника" - description = "При получении удара вы получаете сопротивление ценой голода" - lore1 = "Пассивное сопротивление при получении удара" - lore2 = "x уровней сопротивления на 3 секунды при ударе" - lore3 = "x накопление голода" - lore4 = "Длительность и множитель голода." - lore5 = "Множитель сопротивления, не длительность." - lore = ["Пассивное сопротивление при получении удара", "x уровней сопротивления на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель сопротивления, не длительность."] +name = "Сопротивление охотника" +description = "При получении удара вы получаете сопротивление ценой голода" +lore1 = "Пассивное сопротивление при получении удара" +lore2 = "x уровней сопротивления на 3 секунды при ударе" +lore3 = "x накопление голода" +lore4 = "Длительность и множитель голода." +lore5 = "Множитель сопротивления, не длительность." +lore = ["Пассивное сопротивление при получении удара", "x уровней сопротивления на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель сопротивления, не длительность."] [hunter.speed] - name = "Скорость охотника" - description = "При получении удара вы получаете скорость ценой голода" - lore1 = "Пассивная скорость при получении удара" - lore2 = "x уровней скорости на 3 секунды при ударе" - lore3 = "x накопление голода" - lore4 = "Длительность и множитель голода." - lore5 = "Множитель скорости, не длительность." - lore = ["Пассивная скорость при получении удара", "x уровней скорости на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель скорости, не длительность."] +name = "Скорость охотника" +description = "При получении удара вы получаете скорость ценой голода" +lore1 = "Пассивная скорость при получении удара" +lore2 = "x уровней скорости на 3 секунды при ударе" +lore3 = "x накопление голода" +lore4 = "Длительность и множитель голода." +lore5 = "Множитель скорости, не длительность." +lore = ["Пассивная скорость при получении удара", "x уровней скорости на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель скорости, не длительность."] [hunter.strength] - name = "Сила охотника" - description = "При получении удара вы получаете силу ценой голода" - lore1 = "Пассивная сила при получении удара" - lore2 = "x уровней силы на 3 секунды при ударе" - lore3 = "x накопление голода" - lore4 = "Длительность и множитель голода." - lore5 = "Множитель силы, не длительность." - lore = ["Пассивная сила при получении удара", "x уровней силы на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель силы, не длительность."] +name = "Сила охотника" +description = "При получении удара вы получаете силу ценой голода" +lore1 = "Пассивная сила при получении удара" +lore2 = "x уровней силы на 3 секунды при ударе" +lore3 = "x накопление голода" +lore4 = "Длительность и множитель голода." +lore5 = "Множитель силы, не длительность." +lore = ["Пассивная сила при получении удара", "x уровней силы на 3 секунды при ударе", "x накопление голода", "Длительность и множитель голода.", "Множитель силы, не длительность."] # nether [nether] [nether.skull_toss] - name = "Бросок черепа иссушителя" - description1 = "Выпустите своего внутреннего Иссушителя, используя" - description2 = "чью-то" - description3 = "голову." - lore1 = "Секунд перезарядки между бросками черепов." - lore2 = "С черепом иссушителя: бросьте " - lore3 = "Череп иссушителя" - lore4 = "взрывающийся при попадании." - lore = ["Секунд перезарядки между бросками черепов.", "С черепом иссушителя: бросьте ", "Череп иссушителя", "взрывающийся при попадании."] +name = "Бросок черепа иссушителя" +description1 = "Выпустите своего внутреннего Иссушителя, используя" +description2 = "чью-то" +description3 = "голову." +lore1 = "Секунд перезарядки между бросками черепов." +lore2 = "С черепом иссушителя: бросьте " +lore3 = "Череп иссушителя" +lore4 = "взрывающийся при попадании." +lore = ["Секунд перезарядки между бросками черепов.", "С черепом иссушителя: бросьте ", "Череп иссушителя", "взрывающийся при попадании."] [nether.wither_resist] - name = "Сопротивление иссушению" - description = "Противостоит иссушению силой незерита." - lore1 = "шанс отменить иссушение (за каждую деталь)." - lore2 = "Пассив: Незеритовая броня даёт шанс отменить " - lore3 = "иссушение." - lore = ["шанс отменить иссушение (за каждую деталь).", "Пассив: Незеритовая броня даёт шанс отменить ", "иссушение."] +name = "Сопротивление иссушению" +description = "Противостоит иссушению силой незерита." +lore1 = "шанс отменить иссушение (за каждую деталь)." +lore2 = "Пассив: Незеритовая броня даёт шанс отменить " +lore3 = "иссушение." +lore = ["шанс отменить иссушение (за каждую деталь).", "Пассив: Незеритовая броня даёт шанс отменить ", "иссушение."] [nether.fire_resist] - name = "Огнестойкость" - description = "Противостоит огню, закаляя кожу." - lore1 = "шанс отменить эффект горения!" - lore = ["шанс отменить эффект горения!"] +name = "Огнестойкость" +description = "Противостоит огню, закаляя кожу." +lore1 = "шанс отменить эффект горения!" +lore = ["шанс отменить эффект горения!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Автоплавка" - description = "Позволяет автоматически переплавлять стандартные руды" - lore1 = "Руды, которые можно переплавить, переплавляются автоматически" - lore2 = "% шанс на дополнительный" - lore = ["Руды, которые можно переплавить, переплавляются автоматически", "% шанс на дополнительный"] +name = "Автоплавка" +description = "Позволяет автоматически переплавлять стандартные руды" +lore1 = "Руды, которые можно переплавить, переплавляются автоматически" +lore2 = "% шанс на дополнительный" +lore = ["Руды, которые можно переплавить, переплавляются автоматически", "% шанс на дополнительный"] [pickaxe.chisel] - name = "Резец для руды" - description = "Нажмите ПКМ по руде, чтобы выбить из неё больше ресурсов, за счёт значительного износа инструмента." - lore1 = "Шанс выпадения" - lore2 = "Износ инструмента" - lore = ["Шанс выпадения", "Износ инструмента"] +name = "Резец для руды" +description = "Нажмите ПКМ по руде, чтобы выбить из неё больше ресурсов, за счёт значительного износа инструмента." +lore1 = "Шанс выпадения" +lore2 = "Износ инструмента" +lore = ["Шанс выпадения", "Износ инструмента"] [pickaxe.drop_to_inventory] - name = "Автосбор с кирки" - description = "При разрушении блока предмет телепортируется в ваш инвентарь" - lore1 = "Когда предмет выпадает из разрушенного блока, он попадает в ваш инвентарь, если возможно." - lore = ["Когда предмет выпадает из разрушенного блока, он попадает в ваш инвентарь, если возможно."] +name = "Автосбор с кирки" +description = "При разрушении блока предмет телепортируется в ваш инвентарь" +lore1 = "Когда предмет выпадает из разрушенного блока, он попадает в ваш инвентарь, если возможно." +lore = ["Когда предмет выпадает из разрушенного блока, он попадает в ваш инвентарь, если возможно."] [pickaxe.silk_spawner] - name = "Шёлковый спаунер" - description = "Спаунеры выпадают при разрушении" - lore1 = "Спаунеры можно добывать с помощью Шёлкового касания." - lore2 = "Спаунеры можно добывать при приседании." - lore = ["Спаунеры можно добывать с помощью Шёлкового касания.", "Спаунеры можно добывать при приседании."] +name = "Шёлковый спаунер" +description = "Спаунеры выпадают при разрушении" +lore1 = "Спаунеры можно добывать с помощью Шёлкового касания." +lore2 = "Спаунеры можно добывать при приседании." +lore = ["Спаунеры можно добывать с помощью Шёлкового касания.", "Спаунеры можно добывать при приседании."] [pickaxe.vein_miner] - name = "Жильный шахтёр" - description = "Позволяет разом добыть жилу стандартных руд" - lore1 = "Присядьте и добывайте РУДЫ" - lore2 = "Радиус жильной добычи" - lore3 = "Этот навык НЕ собирает весь дроп вместе!" - lore = ["Присядьте и добывайте РУДЫ", "Радиус жильной добычи", "Этот навык НЕ собирает весь дроп вместе!"] +name = "Жильный шахтёр" +description = "Позволяет разом добыть жилу стандартных руд" +lore1 = "Присядьте и добывайте РУДЫ" +lore2 = "Радиус жильной добычи" +lore3 = "Этот навык НЕ собирает весь дроп вместе!" +lore = ["Присядьте и добывайте РУДЫ", "Радиус жильной добычи", "Этот навык НЕ собирает весь дроп вместе!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Возврат стрел" - description = "Возвращайте стрелы после убийства врага." - lore1 = "Шанс вернуть стрелы при попадании/убийстве" - lore2 = "Шанс: " - lore = ["Шанс вернуть стрелы при попадании/убийстве", "Шанс: "] +name = "Возврат стрел" +description = "Возвращайте стрелы после убийства врага." +lore1 = "Шанс вернуть стрелы при попадании/убийстве" +lore2 = "Шанс: " +lore = ["Шанс вернуть стрелы при попадании/убийстве", "Шанс: "] [ranged.web_shot] - name = "Паутинная ловушка" - description = "Окружите цель паутиной при попадании!" - lore1 = "8 паутин вокруг снежка, и бросайте!" - lore2 = "секунд в клетке, примерно." - lore = ["8 паутин вокруг снежка, и бросайте!", "секунд в клетке, примерно."] +name = "Паутинная ловушка" +description = "Окружите цель паутиной при попадании!" +lore1 = "8 паутин вокруг снежка, и бросайте!" +lore2 = "секунд в клетке, примерно." +lore = ["8 паутин вокруг снежка, и бросайте!", "секунд в клетке, примерно."] [ranged.force_shot] - name = "Усиленный выстрел" - description = "Стреляйте снарядами дальше и быстрее!" - advancementname = "Дальний выстрел" - advancementlore = "Попадите с расстояния более 30 блоков!" - lore1 = "Скорость снаряда" - lore = ["Скорость снаряда"] +name = "Усиленный выстрел" +description = "Стреляйте снарядами дальше и быстрее!" +advancementname = "Дальний выстрел" +advancementlore = "Попадите с расстояния более 30 блоков!" +lore1 = "Скорость снаряда" +lore = ["Скорость снаряда"] [ranged.lunge_shot] - name = "Выстрел с выпадом" - description = "Во время падения ваши стрелы отбрасывают вас в случайном направлении" - lore1 = "Случайная скорость рывка" - lore = ["Случайная скорость рывка"] +name = "Выстрел с выпадом" +description = "Во время падения ваши стрелы отбрасывают вас в случайном направлении" +lore1 = "Случайная скорость рывка" +lore = ["Случайная скорость рывка"] [ranged.arrow_piercing] - name = "Пронзающая стрела" - description = "Добавляет пронзание снарядам! Стреляйте сквозь цели!" - lore1 = "Пронзённых целей" - lore = ["Пронзённых целей"] +name = "Пронзающая стрела" +description = "Добавляет пронзание снарядам! Стреляйте сквозь цели!" +lore1 = "Пронзённых целей" +lore = ["Пронзённых целей"] # rift [rift] [rift.remote_access] - name = "Удалённый доступ" - description = "Потяните из пустоты и получите доступ к отмеченному контейнеру." - lore1 = "Эндер-жемчуг + Компас = Портключ Реликвария" - lore2 = "Этот предмет позволяет удалённо открывать контейнеры" - lore3 = "После крафта посмотрите на предмет для инструкции" - notcontainer = "Это не контейнер" - lore = ["Эндер-жемчуг + Компас = Портключ Реликвария", "Этот предмет позволяет удалённо открывать контейнеры", "После крафта посмотрите на предмет для инструкции"] +name = "Удалённый доступ" +description = "Потяните из пустоты и получите доступ к отмеченному контейнеру." +lore1 = "Эндер-жемчуг + Компас = Портключ Реликвария" +lore2 = "Этот предмет позволяет удалённо открывать контейнеры" +lore3 = "После крафта посмотрите на предмет для инструкции" +notcontainer = "Это не контейнер" +lore = ["Эндер-жемчуг + Компас = Портключ Реликвария", "Этот предмет позволяет удалённо открывать контейнеры", "После крафта посмотрите на предмет для инструкции"] [rift.blink] - name = "Мерцание Разлома" - description = "Мгновенная телепортация на короткое расстояние, одним мерцанием!" - lore1 = "Блоков за мерцание (2x по вертикали)" - lore2 = "Во время бега: дважды нажмите Прыжок, чтобы " - lore3 = "мерцнуть" - lore = ["Блоков за мерцание (2x по вертикали)", "Во время бега: дважды нажмите Прыжок, чтобы ", "мерцнуть"] +name = "Мерцание Разлома" +description = "Мгновенная телепортация на короткое расстояние, одним мерцанием!" +lore1 = "Блоков за мерцание (2x по вертикали)" +lore2 = "Во время бега: дважды нажмите Прыжок, чтобы " +lore3 = "мерцнуть" +lore = ["Блоков за мерцание (2x по вертикали)", "Во время бега: дважды нажмите Прыжок, чтобы ", "мерцнуть"] [rift.chest] - name = "Портативный эндер-сундук" - description = "Откройте эндер-сундук, нажав ЛКМ по нему в руке." - lore1 = "Нажмите на эндер-сундук в руке, чтобы открыть (только не ставьте его)" - lore = ["Нажмите на эндер-сундук в руке, чтобы открыть (только не ставьте его)"] +name = "Портативный эндер-сундук" +description = "Откройте эндер-сундук, нажав ЛКМ по нему в руке." +lore1 = "Нажмите на эндер-сундук в руке, чтобы открыть (только не ставьте его)" +lore = ["Нажмите на эндер-сундук в руке, чтобы открыть (только не ставьте его)"] [rift.descent] - name = "Антилевитация" - description = "Устали зависать в воздухе? Этот навык для вас!" - lore1 = "Просто присядьте, чтобы спуститься, и вы будете падать медленнее обычного!" - lore2 = "Перезарядка:" - lore = ["Просто присядьте, чтобы спуститься, и вы будете падать медленнее обычного!", "Перезарядка:"] +name = "Антилевитация" +description = "Устали зависать в воздухе? Этот навык для вас!" +lore1 = "Просто присядьте, чтобы спуститься, и вы будете падать медленнее обычного!" +lore2 = "Перезарядка:" +lore = ["Просто присядьте, чтобы спуститься, и вы будете падать медленнее обычного!", "Перезарядка:"] [rift.gate] - name = "Врата Разлома" - description = "Телепортируйтесь к отмеченному месту." - lore1 = "КРАФТ: Изумруд + Осколок аметиста + Эндер-жемчуг" - lore2 = "Прочтите перед использованием!" - lore3 = "Задержка 5 секунд, " - lore4 = "вы можете погибнуть во время этой анимации" - lore = ["КРАФТ: Изумруд + Осколок аметиста + Эндер-жемчуг", "Прочтите перед использованием!", "Задержка 5 секунд, ", "вы можете погибнуть во время этой анимации"] +name = "Врата Разлома" +description = "Телепортируйтесь к отмеченному месту." +lore1 = "КРАФТ: Изумруд + Осколок аметиста + Эндер-жемчуг" +lore2 = "Прочтите перед использованием!" +lore3 = "Задержка 5 секунд, " +lore4 = "вы можете погибнуть во время этой анимации" +lore = ["КРАФТ: Изумруд + Осколок аметиста + Эндер-жемчуг", "Прочтите перед использованием!", "Задержка 5 секунд, ", "вы можете погибнуть во время этой анимации"] [rift.resist] - name = "Сопротивление Разлома" - description = "Получайте Сопротивление при использовании эндер-предметов и способностей" - lore1 = "+ Пассив: даёт сопротивление при использовании способностей Разлома или эндер-предметов" - lore2 = "НЕ включает портативный эндер-сундук, только расходуемые предметы" - lore = ["+ Пассив: даёт сопротивление при использовании способностей Разлома или эндер-предметов", "НЕ включает портативный эндер-сундук, только расходуемые предметы"] +name = "Сопротивление Разлома" +description = "Получайте Сопротивление при использовании эндер-предметов и способностей" +lore1 = "+ Пассив: даёт сопротивление при использовании способностей Разлома или эндер-предметов" +lore2 = "НЕ включает портативный эндер-сундук, только расходуемые предметы" +lore = ["+ Пассив: даёт сопротивление при использовании способностей Разлома или эндер-предметов", "НЕ включает портативный эндер-сундук, только расходуемые предметы"] [rift.visage] - name = "Облик Разлома" - description = "Эндермены не становятся агрессивными, если у вас в инвентаре есть эндер-жемчуг." - lore1 = "Эндермены не будут агрессивными, если у вас в инвентаре есть эндер-жемчуг." - lore = ["Эндермены не будут агрессивными, если у вас в инвентаре есть эндер-жемчуг."] +name = "Облик Разлома" +description = "Эндермены не становятся агрессивными, если у вас в инвентаре есть эндер-жемчуг." +lore1 = "Эндермены не будут агрессивными, если у вас в инвентаре есть эндер-жемчуг." +lore = ["Эндермены не будут агрессивными, если у вас в инвентаре есть эндер-жемчуг."] # seaborn [seaborn] [seaborn.oxygen] - name = "Органический кислородный баллон" - description = "Удерживайте больше кислорода в своих маленьких лёгких!" - lore1 = "Увеличение запаса кислорода" - lore = ["Увеличение запаса кислорода"] +name = "Органический кислородный баллон" +description = "Удерживайте больше кислорода в своих маленьких лёгких!" +lore1 = "Увеличение запаса кислорода" +lore = ["Увеличение запаса кислорода"] [seaborn.fishers_fantasy] - name = "Мечта рыбака" - description = "Получайте больше опыта от рыбалки и ловите больше рыбы!" - lore1 = "С каждым уровнем растёт шанс получить больше опыта и рыбы!" - lore = ["С каждым уровнем растёт шанс получить больше опыта и рыбы!"] +name = "Мечта рыбака" +description = "Получайте больше опыта от рыбалки и ловите больше рыбы!" +lore1 = "С каждым уровнем растёт шанс получить больше опыта и рыбы!" +lore = ["С каждым уровнем растёт шанс получить больше опыта и рыбы!"] [seaborn.haste] - name = "Черепаший шахтёр" - description = "При добыче под водой вы получаете Спешку!" - lore1 = "Спешка III применяется под водой при добыче (суммируется с Подводником) после окончания эффекта подводного дыхания!" - lore = ["Спешка III применяется под водой при добыче (суммируется с Подводником) после окончания эффекта подводного дыхания!"] +name = "Черепаший шахтёр" +description = "При добыче под водой вы получаете Спешку!" +lore1 = "Спешка III применяется под водой при добыче (суммируется с Подводником) после окончания эффекта подводного дыхания!" +lore = ["Спешка III применяется под водой при добыче (суммируется с Подводником) после окончания эффекта подводного дыхания!"] [seaborn.night_vision] - name = "Зрение черепахи" - description = "Под водой вы получаете ночное зрение" - lore1 = "Просто получайте ночное зрение под водой после окончания эффекта подводного дыхания!" - lore = ["Просто получайте ночное зрение под водой после окончания эффекта подводного дыхания!"] +name = "Зрение черепахи" +description = "Под водой вы получаете ночное зрение" +lore1 = "Просто получайте ночное зрение под водой после окончания эффекта подводного дыхания!" +lore = ["Просто получайте ночное зрение под водой после окончания эффекта подводного дыхания!"] [seaborn.dolphin_grace] - name = "Грация дельфина" - description = "Плавайте как дельфин, без самих дельфинов" - lore1 = "+ Пассив: получите " - lore2 = "x скорости (грация дельфина)" - lore3 = "Точная немецкая инженер... стоп, что-то не то... Не совместимо с Покорителем глубин" - lore = ["+ Пассив: получите ", "x скорости (грация дельфина)", "Точная немецкая инженер... стоп, что-то не то... Не совместимо с Покорителем глубин"] +name = "Грация дельфина" +description = "Плавайте как дельфин, без самих дельфинов" +lore1 = "+ Пассив: получите " +lore2 = "x скорости (грация дельфина)" +lore3 = "Точная немецкая инженер... стоп, что-то не то... Не совместимо с Покорителем глубин" +lore = ["+ Пассив: получите ", "x скорости (грация дельфина)", "Точная немецкая инженер... стоп, что-то не то... Не совместимо с Покорителем глубин"] # stealth [stealth] [stealth.ghost_armor] - name = "Призрачная броня" - description = "Медленно растущая броня, когда вы не получаете урон. Исчезает после 1 удара" - lore1 = "Максимум брони" - lore2 = "Скорость" - lore = ["Максимум брони", "Скорость"] +name = "Призрачная броня" +description = "Медленно растущая броня, когда вы не получаете урон. Исчезает после 1 удара" +lore1 = "Максимум брони" +lore2 = "Скорость" +lore = ["Максимум брони", "Скорость"] [stealth.night_vision] - name = "Скрытное зрение" - description = "Получайте ночное зрение при приседании" - lore1 = "Получите всплеск " - lore2 = "ночного зрения" - lore3 = "при приседании" - lore = ["Получите всплеск ", "ночного зрения", "при приседании"] +name = "Скрытное зрение" +description = "Получайте ночное зрение при приседании" +lore1 = "Получите всплеск " +lore2 = "ночного зрения" +lore3 = "при приседании" +lore = ["Получите всплеск ", "ночного зрения", "при приседании"] [stealth.snatch] - name = "Хватка" - description = "Мгновенно подбирайте выброшенные предметы при приседании!" - lore1 = "Радиус хватки" - lore = ["Радиус хватки"] +name = "Хватка" +description = "Мгновенно подбирайте выброшенные предметы при приседании!" +lore1 = "Радиус хватки" +lore = ["Радиус хватки"] [stealth.speed] - name = "Скорость в приседе" - description = "Получайте ускорение при приседании" - lore1 = "Скорость приседания" - lore = ["Скорость приседания"] +name = "Скорость в приседе" +description = "Получайте ускорение при приседании" +lore1 = "Скорость приседания" +lore = ["Скорость приседания"] [stealth.ender_veil] - name = "Завеса Края" - description = "Больше не нужны тыквы для защиты от эндерменов" - lore1 = "Защита от эндерменов при приседании" - lore2 = "Полная защита от всех эндерменов" - lore = ["Защита от эндерменов при приседании", "Полная защита от всех эндерменов"] +name = "Завеса Края" +description = "Больше не нужны тыквы для защиты от эндерменов" +lore1 = "Защита от эндерменов при приседании" +lore2 = "Полная защита от всех эндерменов" +lore = ["Защита от эндерменов при приседании", "Полная защита от всех эндерменов"] # sword [sword] [sword.machete] - name = "Мачете" - description = "С лёгкостью прорубайте листву!" - lore1 = "Радиус удара" - lore2 = "Перезарядка удара" - lore3 = "Износ инструмента" - lore = ["Радиус удара", "Перезарядка удара", "Износ инструмента"] +name = "Мачете" +description = "С лёгкостью прорубайте листву!" +lore1 = "Радиус удара" +lore2 = "Перезарядка удара" +lore3 = "Износ инструмента" +lore = ["Радиус удара", "Перезарядка удара", "Износ инструмента"] [sword.bloody_blade] - name = "Кровавый клинок" - description = "Удары мечом вызывают кровотечение!" - lore1 = "Удар мечом по живому существу вызывает кровотечение" - lore2 = "Длительность кровотечения" - lore3 = "Перезарядка кровотечения" - lore = ["Удар мечом по живому существу вызывает кровотечение", "Длительность кровотечения", "Перезарядка кровотечения"] +name = "Кровавый клинок" +description = "Удары мечом вызывают кровотечение!" +lore1 = "Удар мечом по живому существу вызывает кровотечение" +lore2 = "Длительность кровотечения" +lore3 = "Перезарядка кровотечения" +lore = ["Удар мечом по живому существу вызывает кровотечение", "Длительность кровотечения", "Перезарядка кровотечения"] [sword.poisoned_blade] - name = "Отравленный клинок" - description = "Удары мечом вызывают отравление!" - lore1 = "Удар мечом по живому существу вызывает отравление" - lore2 = "Длительность отравления" - lore3 = "Перезарядка отравления" - lore = ["Удар мечом по живому существу вызывает отравление", "Длительность отравления", "Перезарядка отравления"] +name = "Отравленный клинок" +description = "Удары мечом вызывают отравление!" +lore1 = "Удар мечом по живому существу вызывает отравление" +lore2 = "Длительность отравления" +lore3 = "Перезарядка отравления" +lore = ["Удар мечом по живому существу вызывает отравление", "Длительность отравления", "Перезарядка отравления"] # taming [taming] [taming.damage] - name = "Урон питомца" - description = "Увеличьте урон, наносимый прирученными животными." - lore1 = "Увеличение урона" - lore = ["Увеличение урона"] +name = "Урон питомца" +description = "Увеличьте урон, наносимый прирученными животными." +lore1 = "Увеличение урона" +lore = ["Увеличение урона"] [taming.health] - name = "Здоровье питомца" - description = "Увеличьте здоровье прирученных животных." - lore1 = "Увеличение здоровья" - lore = ["Увеличение здоровья"] +name = "Здоровье питомца" +description = "Увеличьте здоровье прирученных животных." +lore1 = "Увеличение здоровья" +lore = ["Увеличение здоровья"] [taming.regeneration] - name = "Регенерация питомца" - description = "Увеличьте регенерацию прирученных животных." - lore1 = "ХП/с" - lore = ["ХП/с"] +name = "Регенерация питомца" +description = "Увеличьте регенерацию прирученных животных." +lore1 = "ХП/с" +lore = ["ХП/с"] # tragoul [tragoul] [tragoul.thorns] - name = "Шипы" - description = "Отражайте урон обратно нападающему!" - lore1 = "Отражённый урон при получении удара" - lore = ["Отражённый урон при получении удара"] +name = "Шипы" +description = "Отражайте урон обратно нападающему!" +lore1 = "Отражённый урон при получении удара" +lore = ["Отражённый урон при получении удара"] [tragoul.globe] - name = "Сфера боли" - description = "Разделите наносимый урон по количеству врагов вокруг вас!" - lore1 = "Чем больше врагов вокруг, тем меньше урона по каждому" - lore2 = "Радиус: " - lore3 = "Добавленный урон всем существам: " - lore = ["Чем больше врагов вокруг, тем меньше урона по каждому", "Радиус: ", "Добавленный урон всем существам: "] +name = "Сфера боли" +description = "Разделите наносимый урон по количеству врагов вокруг вас!" +lore1 = "Чем больше врагов вокруг, тем меньше урона по каждому" +lore2 = "Радиус: " +lore3 = "Добавленный урон всем существам: " +lore = ["Чем больше врагов вокруг, тем меньше урона по каждому", "Радиус: ", "Добавленный урон всем существам: "] [tragoul.healing] - name = "Воля боли" - description = "Восстанавливайте здоровье в зависимости от нанесённого урона!" - lore1 = "Причинять вред ещё никогда не было так приятно! Исцеление от нанесённого урона" - lore2 = "3-секундное окно для урона и исцеления, затем 1 секунда перезарядки " - lore3 = "Исцеление за процент урона: " - lore = ["Причинять вред ещё никогда не было так приятно! Исцеление от нанесённого урона", "3-секундное окно для урона и исцеления, затем 1 секунда перезарядки ", "Исцеление за процент урона: "] +name = "Воля боли" +description = "Восстанавливайте здоровье в зависимости от нанесённого урона!" +lore1 = "Причинять вред ещё никогда не было так приятно! Исцеление от нанесённого урона" +lore2 = "3-секундное окно для урона и исцеления, затем 1 секунда перезарядки " +lore3 = "Исцеление за процент урона: " +lore = ["Причинять вред ещё никогда не было так приятно! Исцеление от нанесённого урона", "3-секундное окно для урона и исцеления, затем 1 секунда перезарядки ", "Исцеление за процент урона: "] [tragoul.lance] - name = "Трупные копья" - description = "Убийство врага или убийство способностью создаёт копьё, поражающее ближайшего врага!" - lore1 = "Копья вылетают из всего, что вы убьёте, И если эта способность убьёт врага." - lore2 = "Пожертвуйте частью здоровья для создания копий (это может убить вас)" - lore3 = "Максимум копий: 1 + " - lore = ["Копья вылетают из всего, что вы убьёте, И если эта способность убьёт врага.", "Пожертвуйте частью здоровья для создания копий (это может убить вас)", "Максимум копий: 1 + "] +name = "Трупные копья" +description = "Убийство врага или убийство способностью создаёт копьё, поражающее ближайшего врага!" +lore1 = "Копья вылетают из всего, что вы убьёте, И если эта способность убьёт врага." +lore2 = "Пожертвуйте частью здоровья для создания копий (это может убить вас)" +lore3 = "Максимум копий: 1 + " +lore = ["Копья вылетают из всего, что вы убьёте, И если эта способность убьёт врага.", "Пожертвуйте частью здоровья для создания копий (это может убить вас)", "Максимум копий: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Стеклянная пушка" - description = "Бонусный урон без оружия, чем ниже значение брони" - lore1 = "x урона при 0 брони" - lore2 = "Бонусный урон за уровень" - lore = ["x урона при 0 брони", "Бонусный урон за уровень"] +name = "Стеклянная пушка" +description = "Бонусный урон без оружия, чем ниже значение брони" +lore1 = "x урона при 0 брони" +lore2 = "Бонусный урон за уровень" +lore = ["x урона при 0 брони", "Бонусный урон за уровень"] [unarmed.power] - name = "Сила без оружия" - description = "Увеличенный урон без оружия" - lore1 = "Урон" - lore = ["Урон"] +name = "Сила без оружия" +description = "Увеличенный урон без оружия" +lore1 = "Урон" +lore = ["Урон"] [unarmed.sucker_punch] - name = "Удар исподтишка" - description = "Удары на бегу, но более смертоносные." - lore1 = "Урон" - lore2 = "Урон увеличивается со скоростью при ударе" - lore = ["Урон", "Урон увеличивается со скоростью при ударе"] +name = "Удар исподтишка" +description = "Удары на бегу, но более смертоносные." +lore1 = "Урон" +lore2 = "Урон увеличивается со скоростью при ударе" +lore = ["Урон", "Урон увеличивается со скоростью при ударе"] diff --git a/src/main/resources/tr_TR.toml b/src/main/resources/tr_TR.toml index 82255210b..4c7065ac1 100644 --- a/src/main/resources/tr_TR.toml +++ b/src/main/resources/tr_TR.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "Hareket Etmeliyim!" - description = "1 Kilometre yuru (1.000 blok)" +title = "Hareket Etmeliyim!" +description = "1 Kilometre yuru (1.000 blok)" [advancement.challenge_sprint_5k] - title = "5K Kosu Yap!" - description = "5 Kilometre yuru (5.000 blok)" +title = "5K Kosu Yap!" +description = "5 Kilometre yuru (5.000 blok)" [advancement.challenge_sprint_50k] - title = "Ucarcasina 50K!" - description = "50 Kilometre yuru (50.000 blok)" +title = "Ucarcasina 50K!" +description = "50 Kilometre yuru (50.000 blok)" [advancement.challenge_sprint_500k] - title = "Evreni Dolas!!" - description = "500 Kilometre yuru (500.000 blok)" +title = "Evreni Dolas!!" +description = "500 Kilometre yuru (500.000 blok)" [advancement.challenge_sprint_marathon] - title = "Gercek Bir Maraton Kos!" - description = "42.195 Blok kosar adim at!" +title = "Gercek Bir Maraton Kos!" +description = "42.195 Blok kosar adim at!" [advancement.challenge_place_1k] - title = "Acemi Insaatci!" - description = "1.000 Blok Yerlestir" +title = "Acemi Insaatci!" +description = "1.000 Blok Yerlestir" [advancement.challenge_place_5k] - title = "Orta Duzey Insaatci!" - description = "5.000 Blok Yerlestir" +title = "Orta Duzey Insaatci!" +description = "5.000 Blok Yerlestir" [advancement.challenge_place_50k] - title = "Ileri Duzey Insaatci!" - description = "50.000 Blok Yerlestir" +title = "Ileri Duzey Insaatci!" +description = "50.000 Blok Yerlestir" [advancement.challenge_place_500k] - title = "Usta Insaatci!" - description = "500.000 Blok Yerlestir" +title = "Usta Insaatci!" +description = "500.000 Blok Yerlestir" [advancement.challenge_place_5m] - title = "Simetrinin Ciragi!" - description = "GERCEKLIK SENIN OYUN ALANIN! (5 Milyon Blok)" +title = "Simetrinin Ciragi!" +description = "GERCEKLIK SENIN OYUN ALANIN! (5 Milyon Blok)" [advancement.challenge_chop_1k] - title = "Acemi Oduncu!" - description = "1.000 Blok Kes" +title = "Acemi Oduncu!" +description = "1.000 Blok Kes" [advancement.challenge_chop_5k] - title = "Orta Duzey Oduncu!" - description = "5.000 Blok Kes" +title = "Orta Duzey Oduncu!" +description = "5.000 Blok Kes" [advancement.challenge_chop_50k] - title = "Ileri Duzey Oduncu!" - description = "50.000 Blok Kes" +title = "Ileri Duzey Oduncu!" +description = "50.000 Blok Kes" [advancement.challenge_chop_500k] - title = "Usta Oduncu!" - description = "500.000 Blok Kes" +title = "Usta Oduncu!" +description = "500.000 Blok Kes" [advancement.challenge_chop_5m] - title = "Kopek Jackson" - description = "En iyi uslu cocuk! (5 Milyon Blok)" +title = "Kopek Jackson" +description = "En iyi uslu cocuk! (5 Milyon Blok)" [advancement.challenge_block_1k] - title = "Zorlukla Engelleme!" - description = "1000 Vurusu Engelle" +title = "Zorlukla Engelleme!" +description = "1000 Vurusu Engelle" [advancement.challenge_block_5k] - title = "Engellemek Eglencelidir!" - description = "5000 Vurusu Engelle" +title = "Engellemek Eglencelidir!" +description = "5000 Vurusu Engelle" [advancement.challenge_block_50k] - title = "Engellemek benim Hayatim!" - description = "50.000 Vurusu Engelle" +title = "Engellemek benim Hayatim!" +description = "50.000 Vurusu Engelle" [advancement.challenge_block_500k] - title = "Engellemek benim Amacim!" - description = "500.000 Vurusu Engelle" +title = "Engellemek benim Amacim!" +description = "500.000 Vurusu Engelle" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "5.000.000 Vurusu Engelle" +title = "Die Hand Die Verletzt" +description = "5.000.000 Vurusu Engelle" [advancement.challenge_brew_1k] - title = "Acemi Simyaci!" - description = "1000 Iksir Tuket" +title = "Acemi Simyaci!" +description = "1000 Iksir Tuket" [advancement.challenge_brew_5k] - title = "Orta Duzey Simyaci!" - description = "5000 Iksir Tuket" +title = "Orta Duzey Simyaci!" +description = "5000 Iksir Tuket" [advancement.challenge_brew_50k] - title = "Ileri Duzey Simyaci!" - description = "50.000 Iksir Tuket" +title = "Ileri Duzey Simyaci!" +description = "50.000 Iksir Tuket" [advancement.challenge_brew_500k] - title = "Usta Simyaci!" - description = "500.000 Iksir Tuket" +title = "Usta Simyaci!" +description = "500.000 Iksir Tuket" [advancement.challenge_brew_5m] - title = "Essiz Simyaci" - description = "5.000.000 Iksir Tuket" +title = "Essiz Simyaci" +description = "5.000.000 Iksir Tuket" [advancement.challenge_brewsplash_1k] - title = "Acemi Iksir-Patlatici!" - description = "1000 Iksir Patlat" +title = "Acemi Iksir-Patlatici!" +description = "1000 Iksir Patlat" [advancement.challenge_brewsplash_5k] - title = "Orta Duzey Iksir-Patlatici!" - description = "5000 Iksir Patlat" +title = "Orta Duzey Iksir-Patlatici!" +description = "5000 Iksir Patlat" [advancement.challenge_brewsplash_50k] - title = "Ileri Duzey Iksir-Patlatici!" - description = "50.000 Iksir Patlat" +title = "Ileri Duzey Iksir-Patlatici!" +description = "50.000 Iksir Patlat" [advancement.challenge_brewsplash_500k] - title = "Usta Iksir-Patlatici!" - description = "500.000 Iksir Patlat" +title = "Usta Iksir-Patlatici!" +description = "500.000 Iksir Patlat" [advancement.challenge_brewsplash_5m] - title = "Iksir Patlatma Ustadi" - description = "5.000.000 Iksir Patlat" +title = "Iksir Patlatma Ustadi" +description = "5.000.000 Iksir Patlat" [advancement.challenge_craft_1k] - title = "Kurnaz Zanaatkar!" - description = "1000 Esya Uret" +title = "Kurnaz Zanaatkar!" +description = "1000 Esya Uret" [advancement.challenge_craft_5k] - title = "Huysuz Zanaatkar!" - description = "5000 Esya Uret" +title = "Huysuz Zanaatkar!" +description = "5000 Esya Uret" [advancement.challenge_craft_50k] - title = "Calisan Zanaatkar!" - description = "50.000 Esya Uret" +title = "Calisan Zanaatkar!" +description = "50.000 Esya Uret" [advancement.challenge_craft_500k] - title = "Kakofonik Zanaatkar!" - description = "500.000 Esya Uret" +title = "Kakofonik Zanaatkar!" +description = "500.000 Esya Uret" [advancement.challenge_craft_5m] - title = "Felaketzede Ureticiyuz" - description = "5.000.000 Esya Uret" +title = "Felaketzede Ureticiyuz" +description = "5.000.000 Esya Uret" [advancement.challenge_enchant_1k] - title = "Acemi Buyucu!" - description = "1000 Esya Buyule" +title = "Acemi Buyucu!" +description = "1000 Esya Buyule" [advancement.challenge_enchant_5k] - title = "Orta Duzey Buyucu!" - description = "5000 Esya Buyule" +title = "Orta Duzey Buyucu!" +description = "5000 Esya Buyule" [advancement.challenge_enchant_50k] - title = "Ileri Duzey Buyucu!" - description = "50.000 Esya Buyule" +title = "Ileri Duzey Buyucu!" +description = "50.000 Esya Buyule" [advancement.challenge_enchant_500k] - title = "Usta Buyucu!" - description = "500.000 Esya Buyule" +title = "Usta Buyucu!" +description = "500.000 Esya Buyule" [advancement.challenge_enchant_5m] - title = "Esrarengiz Buyucu" - description = "5.000.000 Esya Buyule" +title = "Esrarengiz Buyucu" +description = "5.000.000 Esya Buyule" [advancement.challenge_excavate_1k] - title = "Hevesli Kazici!" - description = "1000 Blok Kaz" +title = "Hevesli Kazici!" +description = "1000 Blok Kaz" [advancement.challenge_excavate_5k] - title = "Orta Duzey Kazici!" - description = "5000 Blok Kaz" +title = "Orta Duzey Kazici!" +description = "5000 Blok Kaz" [advancement.challenge_excavate_50k] - title = "Ileri Duzey Kazici!" - description = "50.000 Blok Kaz" +title = "Ileri Duzey Kazici!" +description = "50.000 Blok Kaz" [advancement.challenge_excavate_500k] - title = "Usta Kazici!" - description = "500.000 Blok Kaz" +title = "Usta Kazici!" +description = "500.000 Blok Kaz" [advancement.challenge_excavate_5m] - title = "Esrarengiz Kazici" - description = "5.000.000 Blok Kaz" +title = "Esrarengiz Kazici" +description = "5.000.000 Blok Kaz" [advancement.horrible_person] - title = "Sen Korkunc bir Insansin" - description = "Akilalmaz, gercekten" +title = "Sen Korkunc bir Insansin" +description = "Akilalmaz, gercekten" [advancement.challenge_turtle_egg_smasher] - title = "Kaplumbaga Yumurtasi Ezici!" - description = "100 kaplumbaga yumurtasi kir" +title = "Kaplumbaga Yumurtasi Ezici!" +description = "100 kaplumbaga yumurtasi kir" [advancement.challenge_turtle_egg_annihilator] - title = "Kaplumbaga Yumurtasi Yok Edici!" - description = "500 kaplumbaga yumurtasi kir" +title = "Kaplumbaga Yumurtasi Yok Edici!" +description = "500 kaplumbaga yumurtasi kir" [advancement.challenge_novice_hunter] - title = "Acemi Avci!" - description = "100 varlik oldur" +title = "Acemi Avci!" +description = "100 varlik oldur" [advancement.challenge_intermediate_hunter] - title = "Orta Duzey Avci!" - description = "500 varlik oldur" +title = "Orta Duzey Avci!" +description = "500 varlik oldur" [advancement.challenge_advanced_hunter] - title = "Ileri Duzey Avci!" - description = "5000 varlik oldur" +title = "Ileri Duzey Avci!" +description = "5000 varlik oldur" [advancement.challenge_creeper_conqueror] - title = "Creeper Fatihi!" - description = "50 creeper oldur" +title = "Creeper Fatihi!" +description = "50 creeper oldur" [advancement.challenge_creeper_annihilator] - title = "Creeper Yok Edici!" - description = "200 creeper oldur" +title = "Creeper Yok Edici!" +description = "200 creeper oldur" [advancement.challenge_pickaxe_1k] - title = "Acemi Madenci" - description = "1000 Blok Kir" +title = "Acemi Madenci" +description = "1000 Blok Kir" [advancement.challenge_pickaxe_5k] - title = "Yetenekli Madenci" - description = "5000 Blok Kir" +title = "Yetenekli Madenci" +description = "5000 Blok Kir" [advancement.challenge_pickaxe_50k] - title = "Uzman Madenci" - description = "50.000 Blok Kir" +title = "Uzman Madenci" +description = "50.000 Blok Kir" [advancement.challenge_pickaxe_500k] - title = "Usta Madenci" - description = "500.000 Blok Kir" +title = "Usta Madenci" +description = "500.000 Blok Kir" [advancement.challenge_pickaxe_5m] - title = "Efsanevi Madenci" - description = "5.000.000 Blok Kir" +title = "Efsanevi Madenci" +description = "5.000.000 Blok Kir" [advancement.challenge_eat_100] - title = "Ne cok yiyecek!" - description = "100'den fazla Esya Ye!" +title = "Ne cok yiyecek!" +description = "100'den fazla Esya Ye!" [advancement.challenge_eat_1000] - title = "Doyurulamaz Aclik!" - description = "1.000'den fazla Esya Ye!" +title = "Doyurulamaz Aclik!" +description = "1.000'den fazla Esya Ye!" [advancement.challenge_eat_10000] - title = "SONSUZ ACLIK!" - description = "10.000'den fazla Esya Ye!" +title = "SONSUZ ACLIK!" +description = "10.000'den fazla Esya Ye!" [advancement.challenge_harvest_100] - title = "Dolu Hasat" - description = "100'den fazla ekin has!" +title = "Dolu Hasat" +description = "100'den fazla ekin has!" [advancement.challenge_harvest_1000] - title = "Buyuk Hasat" - description = "1.000'den fazla ekin has!" +title = "Buyuk Hasat" +description = "1.000'den fazla ekin has!" [advancement.challenge_swim_1nm] - title = "Insan Denizaltisi!" - description = "1 Deniz Mili yuz (1.852 blok)" +title = "Insan Denizaltisi!" +description = "1 Deniz Mili yuz (1.852 blok)" [advancement.challenge_sneak_1k] - title = "Diz Agrisi" - description = "Bir kilometreden fazla gizlen (1.000 blok)" +title = "Diz Agrisi" +description = "Bir kilometreden fazla gizlen (1.000 blok)" [advancement.challenge_sneak_5k] - title = "Golge Yolcusu" - description = "5.000 bloktan fazla gizlen" +title = "Golge Yolcusu" +description = "5.000 bloktan fazla gizlen" [advancement.challenge_sneak_20k] - title = "Hayalet" - description = "20.000 bloktan fazla gizlen" +title = "Hayalet" +description = "20.000 bloktan fazla gizlen" [advancement.challenge_swim_5k] - title = "Derin Dalgic" - description = "5.000 bloktan fazla yuz" +title = "Derin Dalgic" +description = "5.000 bloktan fazla yuz" [advancement.challenge_swim_20k] - title = "Poseidon'un Secilmisi" - description = "20.000 bloktan fazla yuz" +title = "Poseidon'un Secilmisi" +description = "20.000 bloktan fazla yuz" [advancement.challenge_sword_100] - title = "Ilk Kan" - description = "Kilicla 100 vurus yap" +title = "Ilk Kan" +description = "Kilicla 100 vurus yap" [advancement.challenge_sword_1k] - title = "Kilic Dansisi" - description = "Kilicla 1.000 vurus yap" +title = "Kilic Dansisi" +description = "Kilicla 1.000 vurus yap" [advancement.challenge_sword_10k] - title = "Bin Kesik" - description = "Kilicla 10.000 vurus yap" +title = "Bin Kesik" +description = "Kilicla 10.000 vurus yap" [advancement.challenge_unarmed_100] - title = "Bar Kavgacisi" - description = "Silahsiz 100 vurus yap" +title = "Bar Kavgacisi" +description = "Silahsiz 100 vurus yap" [advancement.challenge_unarmed_1k] - title = "Demir Yumruklar" - description = "Silahsiz 1.000 vurus yap" +title = "Demir Yumruklar" +description = "Silahsiz 1.000 vurus yap" [advancement.challenge_unarmed_10k] - title = "Tek Yumruk" - description = "Silahsiz 10.000 vurus yap" +title = "Tek Yumruk" +description = "Silahsiz 10.000 vurus yap" [advancement.challenge_trag_1k] - title = "Kan Bedeli" - description = "1.000 hasar al" +title = "Kan Bedeli" +description = "1.000 hasar al" [advancement.challenge_trag_10k] - title = "Kizil Dalga" - description = "10.000 hasar al" +title = "Kizil Dalga" +description = "10.000 hasar al" [advancement.challenge_trag_100k] - title = "Acinin Avatari" - description = "100.000 hasar al" +title = "Acinin Avatari" +description = "100.000 hasar al" [advancement.challenge_ranged_100] - title = "Hedef Talimi" - description = "100 mermi firla" +title = "Hedef Talimi" +description = "100 mermi firla" [advancement.challenge_ranged_1k] - title = "Sahin Gozu" - description = "1.000 mermi firla" +title = "Sahin Gozu" +description = "1.000 mermi firla" [advancement.challenge_ranged_10k] - title = "Ok Firtinasi" - description = "10.000 mermi firla" +title = "Ok Firtinasi" +description = "10.000 mermi firla" [advancement.challenge_chronos_1h] - title = "Tik Tak" - description = "1 saat cevrimici ol" +title = "Tik Tak" +description = "1 saat cevrimici ol" [advancement.challenge_chronos_24h] - title = "Zamanin Kumlari" - description = "24 saat cevrimici ol" +title = "Zamanin Kumlari" +description = "24 saat cevrimici ol" [advancement.challenge_chronos_168h] - title = "Zamansiz" - description = "168 saat (1 hafta) cevrimici ol" +title = "Zamansiz" +description = "168 saat (1 hafta) cevrimici ol" [advancement.challenge_nether_50] - title = "Cehennem Kapicisi" - description = "50 nether yaratigi oldur" +title = "Cehennem Kapicisi" +description = "50 nether yaratigi oldur" [advancement.challenge_nether_500] - title = "Ucurum Muhafizi" - description = "500 nether yaratigi oldur" +title = "Ucurum Muhafizi" +description = "500 nether yaratigi oldur" [advancement.challenge_nether_5k] - title = "Nether'in Efendisi" - description = "5.000 nether yaratigi oldur" +title = "Nether'in Efendisi" +description = "5.000 nether yaratigi oldur" [advancement.challenge_rift_50] - title = "Uzaysal Anomali" - description = "50 kez isinlan" +title = "Uzaysal Anomali" +description = "50 kez isinlan" [advancement.challenge_rift_500] - title = "Bosluk Gezgini" - description = "500 kez isinlan" +title = "Bosluk Gezgini" +description = "500 kez isinlan" [advancement.challenge_rift_5k] - title = "Dusler Arasi" - description = "5.000 kez isinlan" +title = "Dusler Arasi" +description = "5.000 kez isinlan" [advancement.challenge_taming_10] - title = "Hayvan Fisildacisi" - description = "10 hayvan cogalt" +title = "Hayvan Fisildacisi" +description = "10 hayvan cogalt" [advancement.challenge_taming_50] - title = "Suru Lideri" - description = "50 hayvan cogalt" +title = "Suru Lideri" +description = "50 hayvan cogalt" [advancement.challenge_taming_500] - title = "Canavar Ustasi" - description = "500 hayvan cogalt" +title = "Canavar Ustasi" +description = "500 hayvan cogalt" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Hiz Iblisi" - description = "5 Kilometreden fazla kos (5,000 blok)" +title = "Hiz Iblisi" +description = "5 Kilometreden fazla kos (5,000 blok)" [advancement.challenge_sprint_dist_50k] - title = "Yildirim Bacaklar" - description = "50 Kilometreden fazla kos (50,000 blok)" +title = "Yildirim Bacaklar" +description = "50 Kilometreden fazla kos (50,000 blok)" [advancement.challenge_agility_swim_1k] - title = "Su Yuruyucusu" - description = "1 Kilometreden fazla yuz (1,000 blok)" +title = "Su Yuruyucusu" +description = "1 Kilometreden fazla yuz (1,000 blok)" [advancement.challenge_agility_swim_10k] - title = "Deniz Gezgini" - description = "10 Kilometreden fazla yuz (10,000 blok)" +title = "Deniz Gezgini" +description = "10 Kilometreden fazla yuz (10,000 blok)" [advancement.challenge_fly_1k] - title = "Gok Dansci" - description = "1 Kilometreden fazla uc (1,000 blok)" +title = "Gok Dansci" +description = "1 Kilometreden fazla uc (1,000 blok)" [advancement.challenge_fly_10k] - title = "Ruzgar Binicisi" - description = "10 Kilometreden fazla uc (10,000 blok)" +title = "Ruzgar Binicisi" +description = "10 Kilometreden fazla uc (10,000 blok)" [advancement.challenge_agility_sneak_500] - title = "Sessiz Adimlar" - description = "500 bloktan fazla sinsi yuru" +title = "Sessiz Adimlar" +description = "500 bloktan fazla sinsi yuru" [advancement.challenge_agility_sneak_5k] - title = "Hayalet Adimlar" - description = "5 Kilometreden fazla sinsi yuru (5,000 blok)" +title = "Hayalet Adimlar" +description = "5 Kilometreden fazla sinsi yuru (5,000 blok)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Yikim Ekibi" - description = "500 blok kir" +title = "Yikim Ekibi" +description = "500 blok kir" [advancement.challenge_demolish_5k] - title = "Yikim Topu" - description = "5,000 blok kir" +title = "Yikim Topu" +description = "5,000 blok kir" [advancement.challenge_value_placed_10k] - title = "Degerli Insaatci" - description = "10,000 deger karsiligi blok yerlestir" +title = "Degerli Insaatci" +description = "10,000 deger karsiligi blok yerlestir" [advancement.challenge_value_placed_100k] - title = "Usta Mimar" - description = "100,000 deger karsiligi blok yerlestir" +title = "Usta Mimar" +description = "100,000 deger karsiligi blok yerlestir" [advancement.challenge_demolish_val_5k] - title = "Kurtarma Uzmani" - description = "Yikimdan 5,000 blok degeri kurtar" +title = "Kurtarma Uzmani" +description = "Yikimdan 5,000 blok degeri kurtar" [advancement.challenge_demolish_val_50k] - title = "Tam Sokuluş" - description = "Yikimdan 50,000 blok degeri kurtar" +title = "Tam Sokuluş" +description = "Yikimdan 50,000 blok degeri kurtar" [advancement.challenge_high_build_100] - title = "Gok Insaatcisi" - description = "Y=128 ustune 100 blok yerlestir" +title = "Gok Insaatcisi" +description = "Y=128 ustune 100 blok yerlestir" [advancement.challenge_high_build_1k] - title = "Bulut Mimari" - description = "Y=128 ustune 1,000 blok yerlestir" +title = "Bulut Mimari" +description = "Y=128 ustune 1,000 blok yerlestir" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Balta Sallayici" - description = "Baltayi 500 kez salla" +title = "Balta Sallayici" +description = "Baltayi 500 kez salla" [advancement.challenge_axe_swing_5k] - title = "Berserker" - description = "Baltayi 5,000 kez salla" +title = "Berserker" +description = "Baltayi 5,000 kez salla" [advancement.challenge_axe_damage_1k] - title = "Kesici" - description = "Balta ile 1,000 hasar ver" +title = "Kesici" +description = "Balta ile 1,000 hasar ver" [advancement.challenge_axe_damage_10k] - title = "Celladın Baltasi" - description = "Balta ile 10,000 hasar ver" +title = "Celladın Baltasi" +description = "Balta ile 10,000 hasar ver" [advancement.challenge_axe_value_5k] - title = "Kereste Tuccari" - description = "5,000 degerinde odun topla" +title = "Kereste Tuccari" +description = "5,000 degerinde odun topla" [advancement.challenge_axe_value_50k] - title = "Kereste Baronu" - description = "50,000 degerinde odun topla" +title = "Kereste Baronu" +description = "50,000 degerinde odun topla" [advancement.challenge_leaves_500] - title = "Yaprak Ufleyici" - description = "Balta ile 500 yaprak bloku temizle" +title = "Yaprak Ufleyici" +description = "Balta ile 500 yaprak bloku temizle" [advancement.challenge_leaves_5k] - title = "Yaprak Dorucu" - description = "Balta ile 5,000 yaprak bloku temizle" +title = "Yaprak Dorucu" +description = "Balta ile 5,000 yaprak bloku temizle" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Hasar Emici" - description = "Kalkan ile 1,000 hasar engelle" +title = "Hasar Emici" +description = "Kalkan ile 1,000 hasar engelle" [advancement.challenge_block_dmg_10k] - title = "Canli Kalkan" - description = "Kalkan ile 10,000 hasar engelle" +title = "Canli Kalkan" +description = "Kalkan ile 10,000 hasar engelle" [advancement.challenge_block_proj_100] - title = "Ok Deflektoru" - description = "Kalkan ile 100 mermi engelle" +title = "Ok Deflektoru" +description = "Kalkan ile 100 mermi engelle" [advancement.challenge_block_proj_1k] - title = "Mermi Kalkani" - description = "Kalkan ile 1,000 mermi engelle" +title = "Mermi Kalkani" +description = "Kalkan ile 1,000 mermi engelle" [advancement.challenge_block_melee_500] - title = "Pariş Ustasi" - description = "Kalkan ile 500 yakin dovus saldirisi engelle" +title = "Pariş Ustasi" +description = "Kalkan ile 500 yakin dovus saldirisi engelle" [advancement.challenge_block_melee_5k] - title = "Demir Kale" - description = "Kalkan ile 5,000 yakin dovus saldirisi engelle" +title = "Demir Kale" +description = "Kalkan ile 5,000 yakin dovus saldirisi engelle" [advancement.challenge_block_heavy_50] - title = "Tank" - description = "50 agir saldiri engelle (5 hasardan fazla)" +title = "Tank" +description = "50 agir saldiri engelle (5 hasardan fazla)" [advancement.challenge_block_heavy_500] - title = "Sarsilmaz Nesne" - description = "500 agir saldiri engelle (5 hasardan fazla)" +title = "Sarsilmaz Nesne" +description = "500 agir saldiri engelle (5 hasardan fazla)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "İksir Atolyesi" - description = "10 iksir tezgahi yerlestir" +title = "İksir Atolyesi" +description = "10 iksir tezgahi yerlestir" [advancement.challenge_brew_stands_50] - title = "İksir Fabrikasi" - description = "50 iksir tezgahi yerlestir" +title = "İksir Fabrikasi" +description = "50 iksir tezgahi yerlestir" [advancement.challenge_brew_strong_25] - title = "Guclu Demleme" - description = "25 guclendirilmis iksir ic" +title = "Guclu Demleme" +description = "25 guclendirilmis iksir ic" [advancement.challenge_brew_strong_250] - title = "Maksimum Guc" - description = "250 guclendirilmis iksir ic" +title = "Maksimum Guc" +description = "250 guclendirilmis iksir ic" [advancement.challenge_brew_splash_hits_50] - title = "Sicrama Bolgesi" - description = "Sicrama iksiri ile 50 varliga vur" +title = "Sicrama Bolgesi" +description = "Sicrama iksiri ile 50 varliga vur" [advancement.challenge_brew_splash_hits_500] - title = "Veba Doktoru" - description = "Sicrama iksiri ile 500 varliga vur" +title = "Veba Doktoru" +description = "Sicrama iksiri ile 500 varliga vur" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Huzursuz" - description = "Aktifken 1 Kilometre yol kat" +title = "Huzursuz" +description = "Aktifken 1 Kilometre yol kat" [advancement.challenge_active_dist_10k] - title = "Yol Bulucu" - description = "Aktifken 10 Kilometre yol kat" +title = "Yol Bulucu" +description = "Aktifken 10 Kilometre yol kat" [advancement.challenge_active_dist_100k] - title = "Surekli Hareket" - description = "Aktifken 100 Kilometre yol kat" +title = "Surekli Hareket" +description = "Aktifken 100 Kilometre yol kat" [advancement.challenge_beds_10] - title = "Erken Kalkan" - description = "10 kez yatakta uyu" +title = "Erken Kalkan" +description = "10 kez yatakta uyu" [advancement.challenge_beds_100] - title = "Zaman Atlayici" - description = "100 kez yatakta uyu" +title = "Zaman Atlayici" +description = "100 kez yatakta uyu" [advancement.challenge_chronos_tp_50] - title = "Zamansal Kayma" - description = "50 kez isinlan" +title = "Zamansal Kayma" +description = "50 kez isinlan" [advancement.challenge_chronos_tp_500] - title = "Zaman Bukulmesi" - description = "500 kez isinlan" +title = "Zaman Bukulmesi" +description = "500 kez isinlan" [advancement.challenge_chronos_deaths_10] - title = "Olumlu" - description = "10 kez ol" +title = "Olumlu" +description = "10 kez ol" [advancement.challenge_chronos_deaths_100] - title = "Olume Meydan Okuyan" - description = "100 kez ol" +title = "Olume Meydan Okuyan" +description = "100 kez ol" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Zanaat Degeri" - description = "Toplam 10,000 degerinde esya imal et" +title = "Zanaat Degeri" +description = "Toplam 10,000 degerinde esya imal et" [advancement.challenge_craft_value_100k] - title = "Usta Zanaatkar" - description = "Toplam 100,000 degerinde esya imal et" +title = "Usta Zanaatkar" +description = "Toplam 100,000 degerinde esya imal et" [advancement.challenge_craft_tools_25] - title = "Alet Ustasi" - description = "25 alet imal et" +title = "Alet Ustasi" +description = "25 alet imal et" [advancement.challenge_craft_tools_250] - title = "Demirci Ustasi" - description = "250 alet imal et" +title = "Demirci Ustasi" +description = "250 alet imal et" [advancement.challenge_craft_armor_25] - title = "Zirhci" - description = "25 parca zirh imal et" +title = "Zirhci" +description = "25 parca zirh imal et" [advancement.challenge_craft_armor_250] - title = "Usta Zirhci" - description = "250 parca zirh imal et" +title = "Usta Zirhci" +description = "250 parca zirh imal et" [advancement.challenge_craft_mass_25k] - title = "Seri Uretici" - description = "25,000 esya imal et" +title = "Seri Uretici" +description = "25,000 esya imal et" [advancement.challenge_craft_mass_250k] - title = "Endustri Devrimi" - description = "250,000 esya imal et" +title = "Endustri Devrimi" +description = "250,000 esya imal et" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Koleksiyoncu" - description = "50 benzersiz esya kesfet" +title = "Koleksiyoncu" +description = "50 benzersiz esya kesfet" [advancement.challenge_discover_items_250] - title = "Katalogcu" - description = "250 benzersiz esya kesfet" +title = "Katalogcu" +description = "250 benzersiz esya kesfet" [advancement.challenge_discover_blocks_50] - title = "Arastirmaci" - description = "50 benzersiz blok kesfet" +title = "Arastirmaci" +description = "50 benzersiz blok kesfet" [advancement.challenge_discover_blocks_250] - title = "Jeolog" - description = "250 benzersiz blok kesfet" +title = "Jeolog" +description = "250 benzersiz blok kesfet" [advancement.challenge_discover_mobs_25] - title = "Gozlemci" - description = "25 benzersiz yaratik kesfet" +title = "Gozlemci" +description = "25 benzersiz yaratik kesfet" [advancement.challenge_discover_mobs_75] - title = "Doga Bilimci" - description = "75 benzersiz yaratik kesfet" +title = "Doga Bilimci" +description = "75 benzersiz yaratik kesfet" [advancement.challenge_discover_biomes_10] - title = "Gezgin" - description = "10 benzersiz biyom kesfet" +title = "Gezgin" +description = "10 benzersiz biyom kesfet" [advancement.challenge_discover_biomes_40] - title = "Dunya Gezgini" - description = "40 benzersiz biyom kesfet" +title = "Dunya Gezgini" +description = "40 benzersiz biyom kesfet" [advancement.challenge_discover_foods_10] - title = "Gurmand" - description = "10 benzersiz yiyecek kesfet" +title = "Gurmand" +description = "10 benzersiz yiyecek kesfet" [advancement.challenge_discover_foods_30] - title = "Mutfak Ustasi" - description = "30 benzersiz yiyecek kesfet" +title = "Mutfak Ustasi" +description = "30 benzersiz yiyecek kesfet" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Guc Dokucu" - description = "100 buyuleme gucu biriktir" +title = "Guc Dokucu" +description = "100 buyuleme gucu biriktir" [advancement.challenge_enchant_power_1k] - title = "Buyu Ustasi" - description = "1,000 buyuleme gucu biriktir" +title = "Buyu Ustasi" +description = "1,000 buyuleme gucu biriktir" [advancement.challenge_enchant_levels_1k] - title = "Seviye Harcayici" - description = "Buyulemeye 1,000 tecrube seviyesi harca" +title = "Seviye Harcayici" +description = "Buyulemeye 1,000 tecrube seviyesi harca" [advancement.challenge_enchant_levels_10k] - title = "XP Yutagi" - description = "Buyulemeye 10,000 tecrube seviyesi harca" +title = "XP Yutagi" +description = "Buyulemeye 10,000 tecrube seviyesi harca" [advancement.challenge_enchant_high_25] - title = "Yuksek Bahisci" - description = "25 maksimum seviye buyuleme yap" +title = "Yuksek Bahisci" +description = "25 maksimum seviye buyuleme yap" [advancement.challenge_enchant_high_250] - title = "Efsanevi Buyucu" - description = "250 maksimum seviye buyuleme yap" +title = "Efsanevi Buyucu" +description = "250 maksimum seviye buyuleme yap" [advancement.challenge_enchant_total_500] - title = "Seviye Yakici" - description = "Tum buyulemelere toplam 500 seviye harca" +title = "Seviye Yakici" +description = "Tum buyulemelere toplam 500 seviye harca" [advancement.challenge_enchant_total_5k] - title = "Mistik Yatirim" - description = "Tum buyulemelere toplam 5,000 seviye harca" +title = "Mistik Yatirim" +description = "Tum buyulemelere toplam 5,000 seviye harca" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Kazici" - description = "Kuregi 500 kez salla" +title = "Kazici" +description = "Kuregi 500 kez salla" [advancement.challenge_dig_swing_5k] - title = "Hafriyatci" - description = "Kuregi 5,000 kez salla" +title = "Hafriyatci" +description = "Kuregi 5,000 kez salla" [advancement.challenge_dig_damage_1k] - title = "Kurek Savascisi" - description = "Kurekle 1,000 hasar ver" +title = "Kurek Savascisi" +description = "Kurekle 1,000 hasar ver" [advancement.challenge_dig_damage_10k] - title = "Kurek Ustasi" - description = "Kurekle 10,000 hasar ver" +title = "Kurek Ustasi" +description = "Kurekle 10,000 hasar ver" [advancement.challenge_dig_value_5k] - title = "Toprak Tuccari" - description = "5,000 degerinde blok kaz" +title = "Toprak Tuccari" +description = "5,000 degerinde blok kaz" [advancement.challenge_dig_value_50k] - title = "Toprak Baronu" - description = "50,000 degerinde blok kaz" +title = "Toprak Baronu" +description = "50,000 degerinde blok kaz" [advancement.challenge_dig_gravel_500] - title = "Cakil Ogutucusu" - description = "500 cakil, kum veya kil bloku kaz" +title = "Cakil Ogutucusu" +description = "500 cakil, kum veya kil bloku kaz" [advancement.challenge_dig_gravel_5k] - title = "Kum Elekcisi" - description = "5,000 cakil, kum veya kil bloku kaz" +title = "Kum Elekcisi" +description = "5,000 cakil, kum veya kil bloku kaz" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Tohum Ekici" - description = "100 ekin ek" +title = "Tohum Ekici" +description = "100 ekin ek" [advancement.challenge_plant_1k] - title = "Yesil Parmak" - description = "1,000 ekin ek" +title = "Yesil Parmak" +description = "1,000 ekin ek" [advancement.challenge_plant_5k] - title = "Tarim Baronu" - description = "5,000 ekin ek" +title = "Tarim Baronu" +description = "5,000 ekin ek" [advancement.challenge_compost_50] - title = "Geri Donusumcu" - description = "50 esya kompostla" +title = "Geri Donusumcu" +description = "50 esya kompostla" [advancement.challenge_compost_500] - title = "Toprak Zenginlestirici" - description = "500 esya kompostla" +title = "Toprak Zenginlestirici" +description = "500 esya kompostla" [advancement.challenge_shear_50] - title = "Kirkici" - description = "50 varlik kirk" +title = "Kirkici" +description = "50 varlik kirk" [advancement.challenge_shear_250] - title = "Suru Ustasi" - description = "250 varlik kirk" +title = "Suru Ustasi" +description = "250 varlik kirk" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Katil" - description = "500 yaratik oldur" +title = "Katil" +description = "500 yaratik oldur" [advancement.challenge_kills_5k] - title = "Cellat" - description = "5,000 yaratik oldur" +title = "Cellat" +description = "5,000 yaratik oldur" [advancement.challenge_boss_1] - title = "Patron Avcisi" - description = "Bir patron yaratigi oldur" +title = "Patron Avcisi" +description = "Bir patron yaratigi oldur" [advancement.challenge_boss_10] - title = "Efsane Avcisi" - description = "10 patron yaratik oldur" +title = "Efsane Avcisi" +description = "10 patron yaratik oldur" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Soldurulmus" - description = "500 solma hasari al" +title = "Soldurulmus" +description = "500 solma hasari al" [advancement.challenge_wither_dmg_5k] - title = "Bela Hayatta Kalan" - description = "5,000 solma hasari al" +title = "Bela Hayatta Kalan" +description = "5,000 solma hasari al" [advancement.challenge_wither_skel_25] - title = "Kemik Toplayici" - description = "25 wither iskeleti oldur" +title = "Kemik Toplayici" +description = "25 wither iskeleti oldur" [advancement.challenge_wither_skel_250] - title = "İskelet Belasi" - description = "250 wither iskeleti oldur" +title = "İskelet Belasi" +description = "250 wither iskeleti oldur" [advancement.challenge_wither_boss_1] - title = "Wither Avcisi" - description = "Wither'i yen" +title = "Wither Avcisi" +description = "Wither'i yen" [advancement.challenge_wither_boss_10] - title = "Nether Hakimi" - description = "Wither'i 10 kez yen" +title = "Nether Hakimi" +description = "Wither'i 10 kez yen" [advancement.challenge_roses_10] - title = "Olum Bahcivani" - description = "10 wither gulu kir" +title = "Olum Bahcivani" +description = "10 wither gulu kir" [advancement.challenge_roses_100] - title = "Bela Toplayicisi" - description = "100 wither gulu kir" +title = "Bela Toplayicisi" +description = "100 wither gulu kir" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Madenci Kolu" - description = "Kazmayi 500 kez salla" +title = "Madenci Kolu" +description = "Kazmayi 500 kez salla" [advancement.challenge_pick_swing_5k] - title = "Tunel Yapici" - description = "Kazmayi 5,000 kez salla" +title = "Tunel Yapici" +description = "Kazmayi 5,000 kez salla" [advancement.challenge_pick_damage_1k] - title = "Kazma Dovuscusu" - description = "Kazma ile 1,000 hasar ver" +title = "Kazma Dovuscusu" +description = "Kazma ile 1,000 hasar ver" [advancement.challenge_pick_damage_10k] - title = "Savas Kazmasi" - description = "Kazma ile 10,000 hasar ver" +title = "Savas Kazmasi" +description = "Kazma ile 10,000 hasar ver" [advancement.challenge_pick_value_5k] - title = "Mucevher Bulucu" - description = "5,000 degerinde blok kaz" +title = "Mucevher Bulucu" +description = "5,000 degerinde blok kaz" [advancement.challenge_pick_value_50k] - title = "Cevher Baronu" - description = "50,000 degerinde blok kaz" +title = "Cevher Baronu" +description = "50,000 degerinde blok kaz" [advancement.challenge_pick_ores_500] - title = "Arastirmaci" - description = "500 cevher bloku kaz" +title = "Arastirmaci" +description = "500 cevher bloku kaz" [advancement.challenge_pick_ores_5k] - title = "Usta Madenci" - description = "5,000 cevher bloku kaz" +title = "Usta Madenci" +description = "5,000 cevher bloku kaz" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Keskin Nisanci" - description = "1,000 uzak mesafe hasari ver" +title = "Keskin Nisanci" +description = "1,000 uzak mesafe hasari ver" [advancement.challenge_ranged_dmg_10k] - title = "Olumcul Okcu" - description = "10,000 uzak mesafe hasari ver" +title = "Olumcul Okcu" +description = "10,000 uzak mesafe hasari ver" [advancement.challenge_ranged_dist_5k] - title = "Uzun Menzil" - description = "Toplam 5,000 blok mesafe kapsayan mermiler at" +title = "Uzun Menzil" +description = "Toplam 5,000 blok mesafe kapsayan mermiler at" [advancement.challenge_ranged_dist_50k] - title = "Mil Atıcisi" - description = "Toplam 50,000 blok mesafe kapsayan mermiler at" +title = "Mil Atıcisi" +description = "Toplam 50,000 blok mesafe kapsayan mermiler at" [advancement.challenge_ranged_kills_50] - title = "Okcu" - description = "Uzak mesafe silahlariyla 50 yaratik oldur" +title = "Okcu" +description = "Uzak mesafe silahlariyla 50 yaratik oldur" [advancement.challenge_ranged_kills_500] - title = "Usta Okcu" - description = "Uzak mesafe silahlariyla 500 yaratik oldur" +title = "Usta Okcu" +description = "Uzak mesafe silahlariyla 500 yaratik oldur" [advancement.challenge_longshot_25] - title = "Keskin Nisanci" - description = "25 uzun mesafe atisi isabet ettir (30 bloktan fazla)" +title = "Keskin Nisanci" +description = "25 uzun mesafe atisi isabet ettir (30 bloktan fazla)" [advancement.challenge_longshot_250] - title = "Kartal Gozu" - description = "250 uzun mesafe atisi isabet ettir (30 bloktan fazla)" +title = "Kartal Gozu" +description = "250 uzun mesafe atisi isabet ettir (30 bloktan fazla)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "İnci Atici" - description = "50 ender incisi at" +title = "İnci Atici" +description = "50 ender incisi at" [advancement.challenge_rift_pearls_500] - title = "Isinlanma Bagimlisi" - description = "500 ender incisi at" +title = "Isinlanma Bagimlisi" +description = "500 ender incisi at" [advancement.challenge_rift_enderman_50] - title = "Enderman Avcisi" - description = "50 enderman oldur" +title = "Enderman Avcisi" +description = "50 enderman oldur" [advancement.challenge_rift_enderman_500] - title = "Bosluk Takipcisi" - description = "500 enderman oldur" +title = "Bosluk Takipcisi" +description = "500 enderman oldur" [advancement.challenge_rift_dragon_500] - title = "Ejderha Dovuscusu" - description = "Ender Ejderhasi'na 500 hasar ver" +title = "Ejderha Dovuscusu" +description = "Ender Ejderhasi'na 500 hasar ver" [advancement.challenge_rift_dragon_5k] - title = "Ejderha Belasi" - description = "Ender Ejderhasi'na 5,000 hasar ver" +title = "Ejderha Belasi" +description = "Ender Ejderhasi'na 5,000 hasar ver" [advancement.challenge_rift_crystal_10] - title = "Kristal Kirici" - description = "10 ender kristali yok et" +title = "Kristal Kirici" +description = "10 ender kristali yok et" [advancement.challenge_rift_crystal_100] - title = "Son Yikici" - description = "100 ender kristali yok et" +title = "Son Yikici" +description = "100 ender kristali yok et" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Balikci" - description = "25 balik yakala" +title = "Balikci" +description = "25 balik yakala" [advancement.challenge_fish_250] - title = "Usta Balikci" - description = "250 balik yakala" +title = "Usta Balikci" +description = "250 balik yakala" [advancement.challenge_drowned_25] - title = "Bogulmus Avcisi" - description = "25 bogulmus oldur" +title = "Bogulmus Avcisi" +description = "25 bogulmus oldur" [advancement.challenge_drowned_250] - title = "Okyanus Temizleyici" - description = "250 bogulmus oldur" +title = "Okyanus Temizleyici" +description = "250 bogulmus oldur" [advancement.challenge_guardian_10] - title = "Koruyucu Avcisi" - description = "10 koruyucu oldur" +title = "Koruyucu Avcisi" +description = "10 koruyucu oldur" [advancement.challenge_guardian_100] - title = "Tapınak Yakmacisi" - description = "100 koruyucu oldur" +title = "Tapınak Yakmacisi" +description = "100 koruyucu oldur" [advancement.challenge_underwater_blocks_100] - title = "Sualti Madencisi" - description = "Su altinda 100 blok kir" +title = "Sualti Madencisi" +description = "Su altinda 100 blok kir" [advancement.challenge_underwater_blocks_1k] - title = "Sualti Muhendisi" - description = "Su altinda 1,000 blok kir" +title = "Sualti Muhendisi" +description = "Su altinda 1,000 blok kir" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Sirt Biçaklayici" - description = "Sinsileyken 500 hasar ver" +title = "Sirt Biçaklayici" +description = "Sinsileyken 500 hasar ver" [advancement.challenge_stealth_dmg_5k] - title = "Sessiz Katil" - description = "Sinsileyken 5,000 hasar ver" +title = "Sessiz Katil" +description = "Sinsileyken 5,000 hasar ver" [advancement.challenge_stealth_kills_10] - title = "Suikastci" - description = "Sinsileyken 10 yaratik oldur" +title = "Suikastci" +description = "Sinsileyken 10 yaratik oldur" [advancement.challenge_stealth_kills_100] - title = "Golge Bicakçisi" - description = "Sinsileyken 100 yaratik oldur" +title = "Golge Bicakçisi" +description = "Sinsileyken 100 yaratik oldur" [advancement.challenge_stealth_time_1h] - title = "Sabırli" - description = "1 saat sinsi yuru (3,600 saniye)" +title = "Sabırli" +description = "1 saat sinsi yuru (3,600 saniye)" [advancement.challenge_stealth_time_10h] - title = "Golgelerin Efendisi" - description = "10 saat sinsi yuru (36,000 saniye)" +title = "Golgelerin Efendisi" +description = "10 saat sinsi yuru (36,000 saniye)" [advancement.challenge_stealth_arrows_50] - title = "Sessiz Okcu" - description = "Sinsileyken 50 ok at" +title = "Sessiz Okcu" +description = "Sinsileyken 50 ok at" [advancement.challenge_stealth_arrows_500] - title = "Hayalet Okcu" - description = "Sinsileyken 500 ok at" +title = "Hayalet Okcu" +description = "Sinsileyken 500 ok at" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Kilic Ciragi" - description = "Kılıcla 1,000 hasar ver" +title = "Kilic Ciragi" +description = "Kılıcla 1,000 hasar ver" [advancement.challenge_sword_dmg_10k] - title = "Kilicçi" - description = "Kılıcla 10,000 hasar ver" +title = "Kilicçi" +description = "Kılıcla 10,000 hasar ver" [advancement.challenge_sword_kills_50] - title = "Duellocu" - description = "Kılıcla 50 yaratik oldur" +title = "Duellocu" +description = "Kılıcla 50 yaratik oldur" [advancement.challenge_sword_kills_500] - title = "Gladyator" - description = "Kılıcla 500 yaratik oldur" +title = "Gladyator" +description = "Kılıcla 500 yaratik oldur" [advancement.challenge_sword_crit_50] - title = "Kritik Vurucu" - description = "Kılıcla 50 kritik vurus yap" +title = "Kritik Vurucu" +description = "Kılıcla 50 kritik vurus yap" [advancement.challenge_sword_crit_500] - title = "Hassasiyet Ustasi" - description = "Kılıcla 500 kritik vurus yap" +title = "Hassasiyet Ustasi" +description = "Kılıcla 500 kritik vurus yap" [advancement.challenge_sword_heavy_25] - title = "Agir Vurucu" - description = "Kılıcla 25 agir vurus yap (8 hasardan fazla)" +title = "Agir Vurucu" +description = "Kılıcla 25 agir vurus yap (8 hasardan fazla)" [advancement.challenge_sword_heavy_250] - title = "Yikici Darbe" - description = "Kılıcla 250 agir vurus yap (8 hasardan fazla)" +title = "Yikici Darbe" +description = "Kılıcla 250 agir vurus yap (8 hasardan fazla)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Canavar Egitici" - description = "Evcil hayvanlarin toplam 500 hasar versin" +title = "Canavar Egitici" +description = "Evcil hayvanlarin toplam 500 hasar versin" [advancement.challenge_pet_dmg_5k] - title = "Savas Ustasi" - description = "Evcil hayvanlarin toplam 5,000 hasar versin" +title = "Savas Ustasi" +description = "Evcil hayvanlarin toplam 5,000 hasar versin" [advancement.challenge_tamed_10] - title = "Hayvan Dostu" - description = "10 hayvan ehillestir" +title = "Hayvan Dostu" +description = "10 hayvan ehillestir" [advancement.challenge_tamed_100] - title = "Hayvanat Bahcesi Bakicisi" - description = "100 hayvan ehillestir" +title = "Hayvanat Bahcesi Bakicisi" +description = "100 hayvan ehillestir" [advancement.challenge_pet_kills_25] - title = "Suru Taktigi" - description = "Evcil hayvanlarin 25 yaratik oldursun" +title = "Suru Taktigi" +description = "Evcil hayvanlarin 25 yaratik oldursun" [advancement.challenge_pet_kills_250] - title = "Alfa Komutan" - description = "Evcil hayvanlarin 250 yaratik oldursun" +title = "Alfa Komutan" +description = "Evcil hayvanlarin 250 yaratik oldursun" [advancement.challenge_taming_2500] - title = "Uretim Uzmani" - description = "2,500 hayvan cogalt" +title = "Uretim Uzmani" +description = "2,500 hayvan cogalt" [advancement.challenge_taming_25k] - title = "Genetik Ustasi" - description = "25,000 hayvan cogalt" +title = "Genetik Ustasi" +description = "25,000 hayvan cogalt" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Kum Torbasi" - description = "500 darbe al" +title = "Kum Torbasi" +description = "500 darbe al" [advancement.challenge_trag_hits_5k] - title = "Ceza Aşigi" - description = "5,000 darbe al" +title = "Ceza Aşigi" +description = "5,000 darbe al" [advancement.challenge_trag_deaths_10] - title = "Dokuz Canli" - description = "10 kez ol" +title = "Dokuz Canli" +description = "10 kez ol" [advancement.challenge_trag_deaths_100] - title = "Tekrarlayan Kabus" - description = "100 kez ol" +title = "Tekrarlayan Kabus" +description = "100 kez ol" [advancement.challenge_trag_fire_500] - title = "Yanik Kurbani" - description = "500 ates hasari al" +title = "Yanik Kurbani" +description = "500 ates hasari al" [advancement.challenge_trag_fire_5k] - title = "Anka Kusu" - description = "5,000 ates hasari al" +title = "Anka Kusu" +description = "5,000 ates hasari al" [advancement.challenge_trag_fall_500] - title = "Yercekimi Testi" - description = "500 dusme hasari al" +title = "Yercekimi Testi" +description = "500 dusme hasari al" [advancement.challenge_trag_fall_5k] - title = "Son Hiz" - description = "5,000 dusme hasari al" +title = "Son Hiz" +description = "5,000 dusme hasari al" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Kavgaci" - description = "Ciplan yumruklarla 1,000 hasar ver" +title = "Kavgaci" +description = "Ciplan yumruklarla 1,000 hasar ver" [advancement.challenge_unarmed_dmg_10k] - title = "Dovus Sanatlari Ustasi" - description = "Ciplan yumruklarla 10,000 hasar ver" +title = "Dovus Sanatlari Ustasi" +description = "Ciplan yumruklarla 10,000 hasar ver" [advancement.challenge_unarmed_kills_25] - title = "Ciplan Yumruk" - description = "Ciplan yumruklarla 25 yaratik oldur" +title = "Ciplan Yumruk" +description = "Ciplan yumruklarla 25 yaratik oldur" [advancement.challenge_unarmed_kills_250] - title = "Efsane Yumruk" - description = "Ciplan yumruklarla 250 yaratik oldur" +title = "Efsane Yumruk" +description = "Ciplan yumruklarla 250 yaratik oldur" [advancement.challenge_unarmed_crit_25] - title = "Kritik Yumruk" - description = "Ciplan yumruklarla 25 kritik vurus yap" +title = "Kritik Yumruk" +description = "Ciplan yumruklarla 25 kritik vurus yap" [advancement.challenge_unarmed_crit_250] - title = "Hassas Yumruk" - description = "Ciplan yumruklarla 250 kritik vurus yap" +title = "Hassas Yumruk" +description = "Ciplan yumruklarla 250 kritik vurus yap" [advancement.challenge_unarmed_heavy_25] - title = "Guclu Yumruk" - description = "Ciplan yumruklarla 25 agir vurus yap (6 hasardan fazla)" +title = "Guclu Yumruk" +description = "Ciplan yumruklarla 25 agir vurus yap (6 hasardan fazla)" [advancement.challenge_unarmed_heavy_250] - title = "Nakaut Krali" - description = "Ciplan yumruklarla 250 agir vurus yap (6 hasardan fazla)" +title = "Nakaut Krali" +description = "Ciplan yumruklarla 250 agir vurus yap (6 hasardan fazla)" # items [items] [items.bound_ender_peral] - name = "Emanet Anahtari" - usage1 = "Baglamak icin Shift + Sol Tiklama" - usage2 = "Bagli Envantere erismek icin Sag Tiklayin" +name = "Emanet Anahtari" +usage1 = "Baglamak icin Shift + Sol Tiklama" +usage2 = "Bagli Envantere erismek icin Sag Tiklayin" [items.bound_eye_of_ender] - name = "Okuler Capa" - usage1 = "Tuketmek ve bagli konuma isinlanmak icin Sag Tiklayin" - usage2 = "Bir bloga baglamak icin Shift + Sol Tiklama" +name = "Okuler Capa" +usage1 = "Tuketmek ve bagli konuma isinlanmak icin Sag Tiklayin" +usage2 = "Bir bloga baglamak icin Shift + Sol Tiklama" [items.bound_redstone_torch] - name = "Redstone Kumandasi" - usage1 = "1-Tick Redstone darbesi olusturmak icin Sag Tiklayin" - usage2 = "Baglamak icin bir 'Hedef' Blok uzerinde Shift + Sol Tiklama" +name = "Redstone Kumandasi" +usage1 = "1-Tick Redstone darbesi olusturmak icin Sag Tiklayin" +usage2 = "Baglamak icin bir 'Hedef' Blok uzerinde Shift + Sol Tiklama" [items.bound_snowball] - name = "Ag Tuzagi!" - usage1 = "Konumda gecici bir ag tuzagi olusturmak icin atin" +name = "Ag Tuzagi!" +usage1 = "Konumda gecici bir ag tuzagi olusturmak icin atin" [items.chrono_time_bottle] - name = "Sisedeki Zaman" - usage1 = "Envanterinizdeyken pasif olarak zaman depolar" - usage2 = "Zamanli bloklara veya yavru hayvanlara sag tiklayarak depolanan zamani harcar" - stored = "Depolanmis Zaman" +name = "Sisedeki Zaman" +usage1 = "Envanterinizdeyken pasif olarak zaman depolar" +usage2 = "Zamanli bloklara veya yavru hayvanlara sag tiklayarak depolanan zamani harcar" +stored = "Depolanmis Zaman" [items.chrono_time_bomb] - name = "Zaman Bombasi" - usage1 = "Zamansal bir alan yaratan krono mermisi firlatmak icin sag tiklayin" +name = "Zaman Bombasi" +usage1 = "Zamansal bir alan yaratan krono mermisi firlatmak icin sag tiklayin" [items.elevator_block] - name = "Asansor Blogu" - usage1 = "Yukari isinlanmak icin ziplayin" - usage2 = "Asagi isinlanmak icin egilin" - usage3 = "Asansorler arasinda en az 2 hava blogu olmalidir" +name = "Asansor Blogu" +usage1 = "Yukari isinlanmak icin ziplayin" +usage2 = "Asagi isinlanmak icin egilin" +usage3 = "Asansorler arasinda en az 2 hava blogu olmalidir" # snippets [snippets] [snippets.gui] - level = "Seviye" - knowledge = "bilgi" - power_used = "Kullanilan Guc" - not_learned = "Ogrenilmedi" - xp = "XP'ye" - welcome = "Hos geldin!" - welcome_back = "Tekrar hosgeldiniz!" - xp_bonus_for_time = "XP icin" - max_ability_power = "Maksimum Yetenek Gucu" - unlock_this_by_clicking = "Sag Tiklayarak bunun kilidini acin: " - back = "Geri" - unlearn_all = "Tumunu unuttur" - unlearned_all = "Hepsi unutturuldu" +level = "Seviye" +knowledge = "bilgi" +power_used = "Kullanilan Guc" +not_learned = "Ogrenilmedi" +xp = "XP'ye" +welcome = "Hos geldin!" +welcome_back = "Tekrar hosgeldiniz!" +xp_bonus_for_time = "XP icin" +max_ability_power = "Maksimum Yetenek Gucu" +unlock_this_by_clicking = "Sag Tiklayarak bunun kilidini acin: " +back = "Geri" +unlearn_all = "Tumunu unuttur" +unlearned_all = "Hepsi unutturuldu" [snippets.adapt_menu] - may_not_unlearn = "OGRENMEYI GERI ALAMAZSINIZ" - may_unlearn = "OGRENEBiLiR/UNUTTURABILIRSINIZ" - knowledge_cost = "Bilgi Maliyeti" - knowledge_available = "Mevcut Bilgi" - already_learned = "Zaten Ogrenildi" - unlearn_refund = "Unutturmak ve Geri Almak Icin Tiklayin" - no_refunds = "HARDCORE, GERI ODEME ENGELLI" - knowledge = "bilgi" - click_learn = "Ogrenmek icin tiklayin" - no_knowledge = "(Hic bilginiz yok)" - you_only_have = "Sadece sahipsiniz" - how_to_level_up = "Maksimum gucunuzu artirmak icin becerilerinizi yukseltin." - not_enough_power = "Yetersiz guc! Her Yetenek Seviyesi 1 guce mal olur." - power = "guc" - power_drain = "Guc Tukenmesi" - learned = "Ogrenildi " - unlearned = "Unutturuldu " - activator_block = "Kitaplik" +may_not_unlearn = "OGRENMEYI GERI ALAMAZSINIZ" +may_unlearn = "OGRENEBiLiR/UNUTTURABILIRSINIZ" +knowledge_cost = "Bilgi Maliyeti" +knowledge_available = "Mevcut Bilgi" +already_learned = "Zaten Ogrenildi" +unlearn_refund = "Unutturmak ve Geri Almak Icin Tiklayin" +no_refunds = "HARDCORE, GERI ODEME ENGELLI" +knowledge = "bilgi" +click_learn = "Ogrenmek icin tiklayin" +no_knowledge = "(Hic bilginiz yok)" +you_only_have = "Sadece sahipsiniz" +how_to_level_up = "Maksimum gucunuzu artirmak icin becerilerinizi yukseltin." +not_enough_power = "Yetersiz guc! Her Yetenek Seviyesi 1 guce mal olur." +power = "guc" +power_drain = "Guc Tukenmesi" +learned = "Ogrenildi " +unlearned = "Unutturuldu " +activator_block = "Kitaplik" [snippets.knowledge_orb] - contains = "icerir" - knowledge = "bilgi" - rightclick = "Sag Tik" - togainknowledge = "bu bilgiyi kazanmak icin" - knowledge_orb = "Bilgi Kuresi" +contains = "icerir" +knowledge = "bilgi" +rightclick = "Sag Tik" +togainknowledge = "bu bilgiyi kazanmak icin" +knowledge_orb = "Bilgi Kuresi" [snippets.experience_orb] - contains = "icerir" - xp = "Deneyim" - rightclick = "Sag Tik" - togainxp = "bu deneyimi kazanmak icin" - xporb = "Deneyim Kuresi" +contains = "icerir" +xp = "Deneyim" +rightclick = "Sag Tik" +togainxp = "bu deneyimi kazanmak icin" +xporb = "Deneyim Kuresi" # skill [skill] [skill.agility] - name = "Ceviklik" - icon = "⇉" - description = "Ceviklik, engeller karsisinda hizli ve akici hareket etme yetenegidir." +name = "Ceviklik" +icon = "⇉" +description = "Ceviklik, engeller karsisinda hizli ve akici hareket etme yetenegidir." [skill.architect] - name = "Mimar" - icon = "⬧" - description = "Yapilar dunyanin yapi taslaridir. Gerceklik sizin elinizde, kontrol etmek sizin elinizde." +name = "Mimar" +icon = "⬧" +description = "Yapilar dunyanin yapi taslaridir. Gerceklik sizin elinizde, kontrol etmek sizin elinizde." [skill.axes] - name = "Baltalar" - icon = "🪓" - description1 = "Agaclari kesmek varken neden " - description2 = "seyleri" - description3 = "kesesin ki, ayni sonuc!" +name = "Baltalar" +icon = "🪓" +description1 = "Agaclari kesmek varken neden " +description2 = "seyleri" +description3 = "kesesin ki, ayni sonuc!" [skill.brewing] - name = "Demleme" - icon = "❦" - description = "Cift Balon, Uclu Balon, Dortlu Balon- Bu iksiri hala bir kazana koyamiyorum" +name = "Demleme" +icon = "❦" +description = "Cift Balon, Uclu Balon, Dortlu Balon- Bu iksiri hala bir kazana koyamiyorum" [skill.blocking] - name = "Engelleme" - icon = "🛡" - description = "Sopalar ve taslar kemiklerini kirmaz, Ama bir kalkan kirar." +name = "Engelleme" +icon = "🛡" +description = "Sopalar ve taslar kemiklerini kirmaz, Ama bir kalkan kirar." [skill.crafting] - name = "Uretim" - icon = "⌂" - description = "Yerlestirilecek parca kalmayinca, neden bir tane daha yapmayasiniz?" +name = "Uretim" +icon = "⌂" +description = "Yerlestirilecek parca kalmayinca, neden bir tane daha yapmayasiniz?" [skill.discovery] - name = "Kesif" - icon = "⚛" - description = "Alginiz genisledikce, zihniniz kesfetmediginizi kesfetmek icin cozulur." +name = "Kesif" +icon = "⚛" +description = "Alginiz genisledikce, zihniniz kesfetmediginizi kesfetmek icin cozulur." [skill.enchanting] - name = "Buyuleme" - icon = "✠" - description = "Neden bahsediyorsun? Kehanetler, vizyonlar, batil inancli gevezelik mi?" +name = "Buyuleme" +icon = "✠" +description = "Neden bahsediyorsun? Kehanetler, vizyonlar, batil inancli gevezelik mi?" [skill.excavation] - name = "Kazi" - icon = "ᛳ" - description = "Kaz Kaz Cukur..." +name = "Kazi" +icon = "ᛳ" +description = "Kaz Kaz Cukur..." [skill.herbalism] - name = "Bitkicilik" - icon = "⚘" - description = "Bitki bulamiyorum ama biraz tohum bulabiliyorum ve- bu... Ot mu?" +name = "Bitkicilik" +icon = "⚘" +description = "Bitki bulamiyorum ama biraz tohum bulabiliyorum ve- bu... Ot mu?" [skill.hunter] - name = "Avci" - icon = "☠" - description = "Avcilik sonucla degil yolculukla ilgilidir." +name = "Avci" +icon = "☠" +description = "Avcilik sonucla degil yolculukla ilgilidir." [skill.nether] - name = "Nether" - icon = "₪" - description = "Nether'in derinliklerinden." +name = "Nether" +icon = "₪" +description = "Nether'in derinliklerinden." [skill.pickaxe] - name = "Kazma" - icon = "⛏" - description = "Cuceler madencidir ama ben de zamanimda bir iki sey ogrendim. BEN ISVECLIYIM" +name = "Kazma" +icon = "⛏" +description = "Cuceler madencidir ama ben de zamanimda bir iki sey ogrendim. BEN ISVECLIYIM" [skill.ranged] - name = "Menzilli" - icon = "🏹" - description = "Mesafe zaferin anahtaridir ve hayatta kalmanin anahtaridir." +name = "Menzilli" +icon = "🏹" +description = "Mesafe zaferin anahtaridir ve hayatta kalmanin anahtaridir." [skill.rift] - name = "Yarik" - icon = "❍" - description = "Yarik, yakici bir kosumdir ama siz kosumu kullanmayi basardiniz." +name = "Yarik" +icon = "❍" +description = "Yarik, yakici bir kosumdir ama siz kosumu kullanmayi basardiniz." [skill.seaborne] - name = "Denizci" - icon = "🎣" - description = "Bu beceri ile suyun harikalarini yonetebilirsiniz." +name = "Denizci" +icon = "🎣" +description = "Bu beceri ile suyun harikalarini yonetebilirsiniz." [skill.stealth] - name = "Gizlilik" - icon = "☯" - description = "Gorunmezlik sanati. Golgelerde yuru." +name = "Gizlilik" +icon = "☯" +description = "Gorunmezlik sanati. Golgelerde yuru." [skill.swords] - name = "Kiliclar" - icon = "⚔" - description = "GreyStone'un gucu adina!" +name = "Kiliclar" +icon = "⚔" +description = "GreyStone'un gucu adina!" [skill.taming] - name = "Evcillestirme" - icon = "♥" - description = "Papganlar ve arilar... ya sen?" +name = "Evcillestirme" +icon = "♥" +description = "Papganlar ve arilar... ya sen?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Kan evrenin damarlarinda akar. Ellerinle kisitlanmis." +name = "TragOul" +icon = "🗡" +description = "Kan evrenin damarlarinda akar. Ellerinle kisitlanmis." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Evrenin Saatini kur, akisi deneyimle. Saati kir, saat ol." +name = "Chronos" +icon = "🕒" +description = "Evrenin Saatini kur, akisi deneyimle. Saati kir, saat ol." [skill.unarmed] - name = "Silahsiz" - icon = "»" - description = "Silahsiz olmak gucsuz olmak degildir." +name = "Silahsiz" +icon = "»" +description = "Silahsiz olmak gucsuz olmak degildir." # agility [agility] [agility.armor_up] - name = "Zirhlan" - description = "Ne kadar uzun sure kosarsan o kadar cok zirh al!" - lore1 = "Maksimum Zirh" - lore2 = "Zirhlanma Suresi" - lore = ["Maksimum Zirh", "Zirhlanma Suresi"] +name = "Zirhlan" +description = "Ne kadar uzun sure kosarsan o kadar cok zirh al!" +lore1 = "Maksimum Zirh" +lore2 = "Zirhlanma Suresi" +lore = ["Maksimum Zirh", "Zirhlanma Suresi"] [agility.ladder_slide] - name = "Merdiven Kayisi" - description = "Merdivenleri her iki yonde de cok daha hizli tirmanin ve kayarak inin." - lore1 = "Merdiven hiz carpani" - lore2 = "Hizli inis hizi" - lore = ["Merdiven hiz carpani", "Hizli inis hizi"] +name = "Merdiven Kayisi" +description = "Merdivenleri her iki yonde de cok daha hizli tirmanin ve kayarak inin." +lore1 = "Merdiven hiz carpani" +lore2 = "Hizli inis hizi" +lore = ["Merdiven hiz carpani", "Hizli inis hizi"] [agility.super_jump] - name = "Super Ziplama" - description = "Olaganustu Yukseklik Avantaji." - lore1 = "Maksimum Atlama Yuksekligi" - lore2 = "Egilme + Ziplama ile Super Zipla!" - lore = ["Maksimum Atlama Yuksekligi", "Egilme + Ziplama ile Super Zipla!"] +name = "Super Ziplama" +description = "Olaganustu Yukseklik Avantaji." +lore1 = "Maksimum Atlama Yuksekligi" +lore2 = "Egilme + Ziplama ile Super Zipla!" +lore = ["Maksimum Atlama Yuksekligi", "Egilme + Ziplama ile Super Zipla!"] [agility.wall_jump] - name = "Duvar Atlayisi" - description = "Havadayken bir duvara karsi shift basili tutarak duvara tutun ve ziplayin!" - lore1 = "Maksimum Atlama" - lore2 = "Atlama Yuksekligi" - lore = ["Maksimum Atlama", "Atlama Yuksekligi"] +name = "Duvar Atlayisi" +description = "Havadayken bir duvara karsi shift basili tutarak duvara tutun ve ziplayin!" +lore1 = "Maksimum Atlama" +lore2 = "Atlama Yuksekligi" +lore = ["Maksimum Atlama", "Atlama Yuksekligi"] [agility.wind_up] - name = "Hizlanma" - description = "Ne kadar uzun kosarsan o kadar hizli ol!" - lore1 = "Maksimum Hiz" - lore2 = "Hizlanma Suresi" - lore = ["Maksimum Hiz", "Hizlanma Suresi"] +name = "Hizlanma" +description = "Ne kadar uzun kosarsan o kadar hizli ol!" +lore1 = "Maksimum Hiz" +lore2 = "Hizlanma Suresi" +lore = ["Maksimum Hiz", "Hizlanma Suresi"] # architect [architect] [architect.elevator] - name = "Asansor" - description = "Dikey olarak hizla isinlanmak icin bir asansor insa etmenizi saglar!" - lore1 = "Asansor tarifi: X=YUN, Y=ENDER INCISI" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Asansor tarifi: X=YUN, Y=ENDER INCISI", "XXX", "XYX", "XXX"] +name = "Asansor" +description = "Dikey olarak hizla isinlanmak icin bir asansor insa etmenizi saglar!" +lore1 = "Asansor tarifi: X=YUN, Y=ENDER INCISI" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Asansor tarifi: X=YUN, Y=ENDER INCISI", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Sihirli Temel" - description = "Egilip altiniza gecici bir temel olusturmanizi saglar!" - lore1 = "Sihirli bir sekilde olusturun: " - lore2 = "Altinizdaki bloklar!" - lore = ["Sihirli bir sekilde olusturun: ", "Altinizdaki bloklar!"] +name = "Sihirli Temel" +description = "Egilip altiniza gecici bir temel olusturmanizi saglar!" +lore1 = "Sihirli bir sekilde olusturun: " +lore2 = "Altinizdaki bloklar!" +lore = ["Sihirli bir sekilde olusturun: ", "Altinizdaki bloklar!"] [architect.glass] - name = "Ipek Dokunuslu Cam" - description = "Cam bloklari bos elinizle kirdiginizda kaybolmasini onlemenizi saglar!" - lore1 = "Elleriniz Cam icin ipek dokunusu kazanir" - lore = ["Elleriniz Cam icin ipek dokunusu kazanir"] +name = "Ipek Dokunuslu Cam" +description = "Cam bloklari bos elinizle kirdiginizda kaybolmasini onlemenizi saglar!" +lore1 = "Elleriniz Cam icin ipek dokunusu kazanir" +lore = ["Elleriniz Cam icin ipek dokunusu kazanir"] [architect.wireless_redstone] - name = "Redstone Kumandasi" - description = "Redstone'u uzaktan degistirmek icin bir redstone mesalesi kullanmanizi saglar!" - lore1 = "Hedef + Redstone Mesalesi + Ender Incisi = 1 Redstone Kumandasi" - lore = ["Hedef + Redstone Mesalesi + Ender Incisi = 1 Redstone Kumandasi"] +name = "Redstone Kumandasi" +description = "Redstone'u uzaktan degistirmek icin bir redstone mesalesi kullanmanizi saglar!" +lore1 = "Hedef + Redstone Mesalesi + Ender Incisi = 1 Redstone Kumandasi" +lore = ["Hedef + Redstone Mesalesi + Ender Incisi = 1 Redstone Kumandasi"] [architect.placement] - name = "Insaatci Asasi" - description = "Egilme modunda ayni anda birden fazla blok yerlestirmenizi saglar. Baktiginiz blokla esesen bir blok tutun ve yerlestirin! Kutulari sinirlamak icin biraz hareket etmeniz gerekebilir!" - lore1 = "Ihtiyaciniz var" - lore2 = "bunu yerlestirmek icin elinizde bloklar" - lore3 = "Malzeme Insaatci Asasi" - lore = ["Ihtiyaciniz var", "bunu yerlestirmek icin elinizde bloklar", "Malzeme Insaatci Asasi"] +name = "Insaatci Asasi" +description = "Egilme modunda ayni anda birden fazla blok yerlestirmenizi saglar. Baktiginiz blokla esesen bir blok tutun ve yerlestirin! Kutulari sinirlamak icin biraz hareket etmeniz gerekebilir!" +lore1 = "Ihtiyaciniz var" +lore2 = "bunu yerlestirmek icin elinizde bloklar" +lore3 = "Malzeme Insaatci Asasi" +lore = ["Ihtiyaciniz var", "bunu yerlestirmek icin elinizde bloklar", "Malzeme Insaatci Asasi"] # axe [axe] [axe.chop] - name = "Balta Kesimi" - description = "Taban kutugune sag tiklayarak agaclari kesin!" - lore1 = "Kesim Basina Blok" - lore2 = "Kesim Bekleme Suresi" - lore3 = "Alet Asinmasi" - lore = ["Kesim Basina Blok", "Kesim Bekleme Suresi", "Alet Asinmasi"] +name = "Balta Kesimi" +description = "Taban kutugune sag tiklayarak agaclari kesin!" +lore1 = "Kesim Basina Blok" +lore2 = "Kesim Bekleme Suresi" +lore3 = "Alet Asinmasi" +lore = ["Kesim Basina Blok", "Kesim Bekleme Suresi", "Alet Asinmasi"] [axe.log_swap] - name = "Lucy'nin Kutuk Degistiricisi" - description = "Uretim Masasinda kutuklerin turunu degistirin!" - lore1 = "8 herhangi turde kutuk + 1 fidan = 8 fidan turunde kutuk" - lore = ["8 herhangi turde kutuk + 1 fidan = 8 fidan turunde kutuk"] +name = "Lucy'nin Kutuk Degistiricisi" +description = "Uretim Masasinda kutuklerin turunu degistirin!" +lore1 = "8 herhangi turde kutuk + 1 fidan = 8 fidan turunde kutuk" +lore = ["8 herhangi turde kutuk + 1 fidan = 8 fidan turunde kutuk"] [axe.drop_to_inventory] - name = "Balta Envantere Dusurme" +name = "Balta Envantere Dusurme" [axe.ground_smash] - name = "Balta Yer Ezmesi" - description = "Ziplayin, sonra egilin ve yakindaki tum dusmanlari parcalayin." - lore1 = "Hasar" - lore2 = "Blok Yaricapi" - lore3 = "Kuvvet" - lore4 = "Ezme Bekleme Suresi" - lore = ["Hasar", "Blok Yaricapi", "Kuvvet", "Ezme Bekleme Suresi"] +name = "Balta Yer Ezmesi" +description = "Ziplayin, sonra egilin ve yakindaki tum dusmanlari parcalayin." +lore1 = "Hasar" +lore2 = "Blok Yaricapi" +lore3 = "Kuvvet" +lore4 = "Ezme Bekleme Suresi" +lore = ["Hasar", "Blok Yaricapi", "Kuvvet", "Ezme Bekleme Suresi"] [axe.leaf_miner] - name = "Yaprak Madencisi" - description = "Toplu yapraklari bir seferde kirmanizi saglar!" - lore1 = "Egilin ve YAPRAK kazin" - lore2 = "yaprak madenciligi menzili" - lore3 = "Yapraklardan dusme elde edemeyeceksiniz (Istismar Onleme)" - lore = ["Egilin ve YAPRAK kazin", "yaprak madenciligi menzili", "Yapraklardan dusme elde edemeyeceksiniz (Istismar Onleme)"] +name = "Yaprak Madencisi" +description = "Toplu yapraklari bir seferde kirmanizi saglar!" +lore1 = "Egilin ve YAPRAK kazin" +lore2 = "yaprak madenciligi menzili" +lore3 = "Yapraklardan dusme elde edemeyeceksiniz (Istismar Onleme)" +lore = ["Egilin ve YAPRAK kazin", "yaprak madenciligi menzili", "Yapraklardan dusme elde edemeyeceksiniz (Istismar Onleme)"] [axe.wood_miner] - name = "Odun Madencisi" - description = "Toplu ahsabi bir seferde kirmanizi saglar!" - lore1 = "Egilin ve AHSAP/KUTUK (Tahta Degil) kazin" - lore2 = "odun madenciligi menzili" - lore3 = "Envantere dusurme ile calisir" - lore = ["Egilin ve AHSAP/KUTUK (Tahta Degil) kazin", "odun madenciligi menzili", "Envantere dusurme ile calisir"] +name = "Odun Madencisi" +description = "Toplu ahsabi bir seferde kirmanizi saglar!" +lore1 = "Egilin ve AHSAP/KUTUK (Tahta Degil) kazin" +lore2 = "odun madenciligi menzili" +lore3 = "Envantere dusurme ile calisir" +lore = ["Egilin ve AHSAP/KUTUK (Tahta Degil) kazin", "odun madenciligi menzili", "Envantere dusurme ile calisir"] # brewing [brewing] [brewing.lingering] - name = "Kalici Demleme" - description = "Demlenmis iksirler daha uzun surer!" - lore1 = "Sure" - lore2 = "Sure" - lore = ["Sure", "Sure"] +name = "Kalici Demleme" +description = "Demlenmis iksirler daha uzun surer!" +lore1 = "Sure" +lore2 = "Sure" +lore = ["Sure", "Sure"] [brewing.super_heated] - name = "Super Isitilmis Demleme" - description = "Demleme tezgahlari ne kadar sicaksa o kadar hizli calisir." - lore1 = "Dokunan Ates Blogu Basina" - lore2 = "Dokunan Lav Blogu Basina" - lore = ["Dokunan Ates Blogu Basina", "Dokunan Lav Blogu Basina"] +name = "Super Isitilmis Demleme" +description = "Demleme tezgahlari ne kadar sicaksa o kadar hizli calisir." +lore1 = "Dokunan Ates Blogu Basina" +lore2 = "Dokunan Lav Blogu Basina" +lore = ["Dokunan Ates Blogu Basina", "Dokunan Lav Blogu Basina"] [brewing.darkness] - name = "Siselenmiş Karanlik" - description = "Buna neden ihtiyacin oldugundan emin degilim ama buyur!" - lore1 = "Gece Gorusu Iksiri + Siyah Beton = Karanlik Iksiri (30 saniye)" - lore2 = "Kullanicinin kosamayacagini unutmayin!" - lore = ["Gece Gorusu Iksiri + Siyah Beton = Karanlik Iksiri (30 saniye)", "Kullanicinin kosamayacagini unutmayin!"] +name = "Siselenmiş Karanlik" +description = "Buna neden ihtiyacin oldugundan emin degilim ama buyur!" +lore1 = "Gece Gorusu Iksiri + Siyah Beton = Karanlik Iksiri (30 saniye)" +lore2 = "Kullanicinin kosamayacagini unutmayin!" +lore = ["Gece Gorusu Iksiri + Siyah Beton = Karanlik Iksiri (30 saniye)", "Kullanicinin kosamayacagini unutmayin!"] [brewing.haste] - name = "Siselenmiş Acele" - description = "Verimlilik yeterli olmadiginda" - lore1 = "Hiz Iksiri + Ametist Parcasi = Acele Iksiri (60 saniye)" - lore2 = "Hiz Iksiri + Ametist Bloku = Acele Iksiri-2 (30 saniye)" - lore = ["Hiz Iksiri + Ametist Parcasi = Acele Iksiri (60 saniye)", "Hiz Iksiri + Ametist Bloku = Acele Iksiri-2 (30 saniye)"] +name = "Siselenmiş Acele" +description = "Verimlilik yeterli olmadiginda" +lore1 = "Hiz Iksiri + Ametist Parcasi = Acele Iksiri (60 saniye)" +lore2 = "Hiz Iksiri + Ametist Bloku = Acele Iksiri-2 (30 saniye)" +lore = ["Hiz Iksiri + Ametist Parcasi = Acele Iksiri (60 saniye)", "Hiz Iksiri + Ametist Bloku = Acele Iksiri-2 (30 saniye)"] [brewing.absorption] - name = "Siselenmiş Emilim" - description = "Vuçudu sertlestirin!" - lore1 = "Aninda Iyilesme + Kuvars = Emilim Iksiri (60 saniye)" - lore2 = "Aninda Iyilesme + Kuvars Bloku = Emilim Iksiri-2 (30 saniye)" - lore = ["Aninda Iyilesme + Kuvars = Emilim Iksiri (60 saniye)", "Aninda Iyilesme + Kuvars Bloku = Emilim Iksiri-2 (30 saniye)"] +name = "Siselenmiş Emilim" +description = "Vuçudu sertlestirin!" +lore1 = "Aninda Iyilesme + Kuvars = Emilim Iksiri (60 saniye)" +lore2 = "Aninda Iyilesme + Kuvars Bloku = Emilim Iksiri-2 (30 saniye)" +lore = ["Aninda Iyilesme + Kuvars = Emilim Iksiri (60 saniye)", "Aninda Iyilesme + Kuvars Bloku = Emilim Iksiri-2 (30 saniye)"] [brewing.fatigue] - name = "Siselenmiş Yorgunluk" - description = "Vuçudu zayiflatin!" - lore1 = "Zayiflik Iksiri + Balcik Topu = Yorgunluk Iksiri (30 saniye)" - lore2 = "Zayiflik Iksiri + Balcik Bloku = Yorgunluk Iksiri-2 (15 saniye)" - lore = ["Zayiflik Iksiri + Balcik Topu = Yorgunluk Iksiri (30 saniye)", "Zayiflik Iksiri + Balcik Bloku = Yorgunluk Iksiri-2 (15 saniye)"] +name = "Siselenmiş Yorgunluk" +description = "Vuçudu zayiflatin!" +lore1 = "Zayiflik Iksiri + Balcik Topu = Yorgunluk Iksiri (30 saniye)" +lore2 = "Zayiflik Iksiri + Balcik Bloku = Yorgunluk Iksiri-2 (15 saniye)" +lore = ["Zayiflik Iksiri + Balcik Topu = Yorgunluk Iksiri (30 saniye)", "Zayiflik Iksiri + Balcik Bloku = Yorgunluk Iksiri-2 (15 saniye)"] [brewing.hunger] - name = "Siselenmiş Aclik" - description = "Doyumsuzlari besleyin!" - lore1 = "Garip Iksir + Curuk Et = Aclik Iksiri (30 saniye)" - lore2 = "Zayiflik Iksiri + Curuk Et = Aclik Iksiri-3 (15 saniye)" - lore = ["Garip Iksir + Curuk Et = Aclik Iksiri (30 saniye)", "Zayiflik Iksiri + Curuk Et = Aclik Iksiri-3 (15 saniye)"] +name = "Siselenmiş Aclik" +description = "Doyumsuzlari besleyin!" +lore1 = "Garip Iksir + Curuk Et = Aclik Iksiri (30 saniye)" +lore2 = "Zayiflik Iksiri + Curuk Et = Aclik Iksiri-3 (15 saniye)" +lore = ["Garip Iksir + Curuk Et = Aclik Iksiri (30 saniye)", "Zayiflik Iksiri + Curuk Et = Aclik Iksiri-3 (15 saniye)"] [brewing.nausea] - name = "Siselenmiş Bulanti" - description = "Midem bulaniyor senden!" - lore1 = "Garip Iksir + Kahverengi Mantar = Bulanti Iksiri (16 saniye)" - lore2 = "Garip Iksir + Kizil Mantar = Bulanti Iksiri-2 (8 saniye)" - lore = ["Garip Iksir + Kahverengi Mantar = Bulanti Iksiri (16 saniye)", "Garip Iksir + Kizil Mantar = Bulanti Iksiri-2 (8 saniye)"] +name = "Siselenmiş Bulanti" +description = "Midem bulaniyor senden!" +lore1 = "Garip Iksir + Kahverengi Mantar = Bulanti Iksiri (16 saniye)" +lore2 = "Garip Iksir + Kizil Mantar = Bulanti Iksiri-2 (8 saniye)" +lore = ["Garip Iksir + Kahverengi Mantar = Bulanti Iksiri (16 saniye)", "Garip Iksir + Kizil Mantar = Bulanti Iksiri-2 (8 saniye)"] [brewing.blindness] - name = "Siselenmiş Korluk" - description = "Korkunc bir insansin..." - lore1 = "Garip Iksir + Murekkep Kesesi = Korluk Iksiri (30 saniye)" - lore2 = "Garip Iksir + Parlayan Murekkep Kesesi = Korluk Iksiri-2 (15 saniye)" - lore = ["Garip Iksir + Murekkep Kesesi = Korluk Iksiri (30 saniye)", "Garip Iksir + Parlayan Murekkep Kesesi = Korluk Iksiri-2 (15 saniye)"] +name = "Siselenmiş Korluk" +description = "Korkunc bir insansin..." +lore1 = "Garip Iksir + Murekkep Kesesi = Korluk Iksiri (30 saniye)" +lore2 = "Garip Iksir + Parlayan Murekkep Kesesi = Korluk Iksiri-2 (15 saniye)" +lore = ["Garip Iksir + Murekkep Kesesi = Korluk Iksiri (30 saniye)", "Garip Iksir + Parlayan Murekkep Kesesi = Korluk Iksiri-2 (15 saniye)"] [brewing.resistance] - name = "Siselenmiş Direnc" - description = "En iyi sekilde guclendirme!" - lore1 = "Garip Iksir + Demir Kulce = Direnc Iksiri (60 saniye)" - lore2 = "Garip Iksir + Demir Blok = Direnc Iksiri-2 (30 saniye)" - lore = ["Garip Iksir + Demir Kulce = Direnc Iksiri (60 saniye)", "Garip Iksir + Demir Blok = Direnc Iksiri-2 (30 saniye)"] +name = "Siselenmiş Direnc" +description = "En iyi sekilde guclendirme!" +lore1 = "Garip Iksir + Demir Kulce = Direnc Iksiri (60 saniye)" +lore2 = "Garip Iksir + Demir Blok = Direnc Iksiri-2 (30 saniye)" +lore = ["Garip Iksir + Demir Kulce = Direnc Iksiri (60 saniye)", "Garip Iksir + Demir Blok = Direnc Iksiri-2 (30 saniye)"] [brewing.health_boost] - name = "Siselenmiş Yasam" - description = "Maksimum saglik yeterli olmadiginda..." - lore1 = "Aninda-Iyilestirme Iksiri + Altin Elma = Saglik Artisi Iksiri (120 saniye)" - lore2 = "Aninda-Iyilestirme Iksiri + Buyulu Altin Elma = Saglik Artisi Iksiri-2 (120 saniye)" - lore = ["Aninda-Iyilestirme Iksiri + Altin Elma = Saglik Artisi Iksiri (120 saniye)", "Aninda-Iyilestirme Iksiri + Buyulu Altin Elma = Saglik Artisi Iksiri-2 (120 saniye)"] +name = "Siselenmiş Yasam" +description = "Maksimum saglik yeterli olmadiginda..." +lore1 = "Aninda-Iyilestirme Iksiri + Altin Elma = Saglik Artisi Iksiri (120 saniye)" +lore2 = "Aninda-Iyilestirme Iksiri + Buyulu Altin Elma = Saglik Artisi Iksiri-2 (120 saniye)" +lore = ["Aninda-Iyilestirme Iksiri + Altin Elma = Saglik Artisi Iksiri (120 saniye)", "Aninda-Iyilestirme Iksiri + Buyulu Altin Elma = Saglik Artisi Iksiri-2 (120 saniye)"] [brewing.decay] - name = "Siselenmiş Curume" - description = "Curumenin bu kadar faydali olacagini kim bilebilirdi?" - lore1 = "Zayiflik Iksiri + Zehirli Patates = Solma Iksiri (16 saniye)" - lore2 = "Zayiflik Iksiri + Kizil Kokler = Solma Iksiri-2 (8 saniye)" - lore = ["Zayiflik Iksiri + Zehirli Patates = Solma Iksiri (16 saniye)", "Zayiflik Iksiri + Kizil Kokler = Solma Iksiri-2 (8 saniye)"] +name = "Siselenmiş Curume" +description = "Curumenin bu kadar faydali olacagini kim bilebilirdi?" +lore1 = "Zayiflik Iksiri + Zehirli Patates = Solma Iksiri (16 saniye)" +lore2 = "Zayiflik Iksiri + Kizil Kokler = Solma Iksiri-2 (8 saniye)" +lore = ["Zayiflik Iksiri + Zehirli Patates = Solma Iksiri (16 saniye)", "Zayiflik Iksiri + Kizil Kokler = Solma Iksiri-2 (8 saniye)"] [brewing.saturation] - name = "Siselenmiş Doygunluk" - description = "Biliyor musun... Ac bile degilim..." - lore1 = "Yenilenme Iksiri + Firinda Patates = Doygunluk Iksiri" - lore2 = "Yenilenme Iksiri + Saman Balyasi = Doygunluk Iksiri-2" - lore = ["Yenilenme Iksiri + Firinda Patates = Doygunluk Iksiri", "Yenilenme Iksiri + Saman Balyasi = Doygunluk Iksiri-2"] +name = "Siselenmiş Doygunluk" +description = "Biliyor musun... Ac bile degilim..." +lore1 = "Yenilenme Iksiri + Firinda Patates = Doygunluk Iksiri" +lore2 = "Yenilenme Iksiri + Saman Balyasi = Doygunluk Iksiri-2" +lore = ["Yenilenme Iksiri + Firinda Patates = Doygunluk Iksiri", "Yenilenme Iksiri + Saman Balyasi = Doygunluk Iksiri-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Mephistopheles Zincirleri" - description = "Zincir Zirh uretmenizi saglar" - lore1 = "Uretim tarifi digerlerinin aynisidir, ancak demir kulce yerine demir parcasi kullanilir" - lore = ["Uretim tarifi digerlerinin aynisidir, ancak demir kulce yerine demir parcasi kullanilir"] +name = "Mephistopheles Zincirleri" +description = "Zincir Zirh uretmenizi saglar" +lore1 = "Uretim tarifi digerlerinin aynisidir, ancak demir kulce yerine demir parcasi kullanilir" +lore = ["Uretim tarifi digerlerinin aynisidir, ancak demir kulce yerine demir parcasi kullanilir"] [blocking.horse_armorer] - name = "Uretilebilir At Zirhi" - description = "At Zirhi uretmenizi saglar" - lore1 = "Zirhi uretmek istediginiz malzemeyle bir eyeri cevreleyin" - lore = ["Zirhi uretmek istediginiz malzemeyle bir eyeri cevreleyin"] +name = "Uretilebilir At Zirhi" +description = "At Zirhi uretmenizi saglar" +lore1 = "Zirhi uretmek istediginiz malzemeyle bir eyeri cevreleyin" +lore = ["Zirhi uretmek istediginiz malzemeyle bir eyeri cevreleyin"] [blocking.saddle_crafter] - name = "Uretilebilir Eyer" - description = "Deri ile Eyer Uret" - lore1 = "Tarif: 5 Deri:" - lore = ["Tarif: 5 Deri:"] +name = "Uretilebilir Eyer" +description = "Deri ile Eyer Uret" +lore1 = "Tarif: 5 Deri:" +lore = ["Tarif: 5 Deri:"] [blocking.multi_armor] - name = "Coklu Zirh" - description = "Elytra'lari Zirha Baglayin" - lore1 = "Seyahat icin muhtesem bir beceri." - lore2 = "Zirh/Elytra'yi aninda dinamik olarak birlestirin ve degistirin!" - lore3 = "Birlestirmek icin envanterinizde bir esyayi digerinin uzerine shift tiklayarak koyun." - lore4 = "Zirhi cozmek icin esyayi Egilip-Birakin, parcalanacaktir." - lore5 = "Coklu Zirhiniz yok edilirse, icindeki tum esyalari kaybedersiniz." - lore6 = "Zirha ihtiyacim yok, beni hayal kirikligina ugratiyor..." - lore = ["Seyahat icin muhtesem bir beceri.", "Zirh/Elytra'yi aninda dinamik olarak birlestirin ve degistirin!", "Birlestirmek icin envanterinizde bir esyayi digerinin uzerine shift tiklayarak koyun.", "Zirhi cozmek icin esyayi Egilip-Birakin, parcalanacaktir.", "Coklu Zirhiniz yok edilirse, icindeki tum esyalari kaybedersiniz.", "Zirha ihtiyacim yok, beni hayal kirikligina ugratiyor..."] +name = "Coklu Zirh" +description = "Elytra'lari Zirha Baglayin" +lore1 = "Seyahat icin muhtesem bir beceri." +lore2 = "Zirh/Elytra'yi aninda dinamik olarak birlestirin ve degistirin!" +lore3 = "Birlestirmek icin envanterinizde bir esyayi digerinin uzerine shift tiklayarak koyun." +lore4 = "Zirhi cozmek icin esyayi Egilip-Birakin, parcalanacaktir." +lore5 = "Coklu Zirhiniz yok edilirse, icindeki tum esyalari kaybedersiniz." +lore6 = "Zirha ihtiyacim yok, beni hayal kirikligina ugratiyor..." +lore = ["Seyahat icin muhtesem bir beceri.", "Zirh/Elytra'yi aninda dinamik olarak birlestirin ve degistirin!", "Birlestirmek icin envanterinizde bir esyayi digerinin uzerine shift tiklayarak koyun.", "Zirhi cozmek icin esyayi Egilip-Birakin, parcalanacaktir.", "Coklu Zirhiniz yok edilirse, icindeki tum esyalari kaybedersiniz.", "Zirha ihtiyacim yok, beni hayal kirikligina ugratiyor..."] # crafting [crafting] [crafting.deconstruction] - name = "Yapi Sokum" - description = "Bloklari ve esyalari kurtarilabilir temel bilesenlere donusturun!" - lore1 = "Herhangi bir esyayi yere birakin." - lore2 = "Sonra, Egilip Makasla Sag Tiklayin" - lore = ["Herhangi bir esyayi yere birakin.", "Sonra, Egilip Makasla Sag Tiklayin"] +name = "Yapi Sokum" +description = "Bloklari ve esyalari kurtarilabilir temel bilesenlere donusturun!" +lore1 = "Herhangi bir esyayi yere birakin." +lore2 = "Sonra, Egilip Makasla Sag Tiklayin" +lore = ["Herhangi bir esyayi yere birakin.", "Sonra, Egilip Makasla Sag Tiklayin"] [crafting.xp] - name = "Uretim XP'si" - description = "Uretirken pasif XP kazanin" - lore1 = "Uretirken XP kazanin" - lore = ["Uretirken XP kazanin"] +name = "Uretim XP'si" +description = "Uretirken pasif XP kazanin" +lore1 = "Uretirken XP kazanin" +lore = ["Uretirken XP kazanin"] [crafting.reconstruction] - name = "Cevher Yeniden Insasi" - description = "Cevherleri temel bilesenlerinden yeniden uretin!" - lore1 = "8 Dusme ve 1 Ana Bilgisayar = 1 Cevher (sekillsiz)" - lore2 = "Dusmeler eritilmelidir (varsa)" - lore3 = "Dahil degil: Hurdalar, Kuvarslar ve Zumurutler vb..." - lore4 = "Ana Bilgisayar = Kaplama. orn: Tas, Netherrack, Deepslate" - lore = ["8 Dusme ve 1 Ana Bilgisayar = 1 Cevher (sekillsiz)", "Dusmeler eritilmelidir (varsa)", "Dahil degil: Hurdalar, Kuvarslar ve Zumurutler vb...", "Ana Bilgisayar = Kaplama. orn: Tas, Netherrack, Deepslate"] +name = "Cevher Yeniden Insasi" +description = "Cevherleri temel bilesenlerinden yeniden uretin!" +lore1 = "8 Dusme ve 1 Ana Bilgisayar = 1 Cevher (sekillsiz)" +lore2 = "Dusmeler eritilmelidir (varsa)" +lore3 = "Dahil degil: Hurdalar, Kuvarslar ve Zumurutler vb..." +lore4 = "Ana Bilgisayar = Kaplama. orn: Tas, Netherrack, Deepslate" +lore = ["8 Dusme ve 1 Ana Bilgisayar = 1 Cevher (sekillsiz)", "Dusmeler eritilmelidir (varsa)", "Dahil degil: Hurdalar, Kuvarslar ve Zumurutler vb...", "Ana Bilgisayar = Kaplama. orn: Tas, Netherrack, Deepslate"] [crafting.leather] - name = "Uretilebilir Deri" - description = "Curuk Etten Deri Uret" - lore1 = "Sadece onu (curuk eti) kamp atesine atin!" - lore = ["Sadece onu (curuk eti) kamp atesine atin!"] +name = "Uretilebilir Deri" +description = "Curuk Etten Deri Uret" +lore1 = "Sadece onu (curuk eti) kamp atesine atin!" +lore = ["Sadece onu (curuk eti) kamp atesine atin!"] [crafting.backpacks] - name = "Boutilier'in Sirt Cantalari!" - description = "Bu sadece Mojang Paketini oyuna getiriyor!" - lore1 = "Bunu kullanmak icin Hayatta Kalma modunda olmalisiniz" - lore2 = "XLX : Deri, Tasma, Deri" - lore3 = "XSX : Deri, Fici, Deri" - lore4 = "XCX : Deri, Sandik, Deri" - lore = ["Bunu kullanmak icin Hayatta Kalma modunda olmalisiniz", "XLX : Deri, Tasma, Deri", "XSX : Deri, Fici, Deri", "XCX : Deri, Sandik, Deri"] +name = "Boutilier'in Sirt Cantalari!" +description = "Bu sadece Mojang Paketini oyuna getiriyor!" +lore1 = "Bunu kullanmak icin Hayatta Kalma modunda olmalisiniz" +lore2 = "XLX : Deri, Tasma, Deri" +lore3 = "XSX : Deri, Fici, Deri" +lore4 = "XCX : Deri, Sandik, Deri" +lore = ["Bunu kullanmak icin Hayatta Kalma modunda olmalisiniz", "XLX : Deri, Tasma, Deri", "XSX : Deri, Fici, Deri", "XCX : Deri, Sandik, Deri"] [crafting.stations] - name = "Portatif Masalar!" - description = "Avucunuzun icinde bir masa kullanin!" - lore2 = "KAPATTIGINIZDA MASA ICINDE UNUTTUGUNUZ ESYALAR SONSUZA KADAR KAYBOLUR!" - lore3 = "Gecerli masalar: Ors, Uretim, Bileme Tasi, Haritacilik, Tas Kesici, Dokuma Tezgahi" - lore = ["KAPATTIGINIZDA MASA ICINDE UNUTTUGUNUZ ESYALAR SONSUZA KADAR KAYBOLUR!", "Gecerli masalar: Ors, Uretim, Bileme Tasi, Haritacilik, Tas Kesici, Dokuma Tezgahi"] +name = "Portatif Masalar!" +description = "Avucunuzun icinde bir masa kullanin!" +lore2 = "KAPATTIGINIZDA MASA ICINDE UNUTTUGUNUZ ESYALAR SONSUZA KADAR KAYBOLUR!" +lore3 = "Gecerli masalar: Ors, Uretim, Bileme Tasi, Haritacilik, Tas Kesici, Dokuma Tezgahi" +lore = ["KAPATTIGINIZDA MASA ICINDE UNUTTUGUNUZ ESYALAR SONSUZA KADAR KAYBOLUR!", "Gecerli masalar: Ors, Uretim, Bileme Tasi, Haritacilik, Tas Kesici, Dokuma Tezgahi"] [crafting.skulls] - name = "Uretilebilir Kafataslari!" - description = "Malzemeler kullanarak Yaratik Kafataslari Uretebilirsiniz!" - lore1 = "Bir kafatasi elde etmek icin bir Kemik Blogunu asagidakilerle cevreleyin:" - lore2 = "Zombi: Curuk Et" - lore3 = "Iskelet: Kemik" - lore4 = "Creeper: Barut" - lore5 = "Wither: Nether Tuglasi" - lore6 = "Ejderha: Ejderha Nefesi" - lore = ["Bir kafatasi elde etmek icin bir Kemik Blogunu asagidakilerle cevreleyin:", "Zombi: Curuk Et", "Iskelet: Kemik", "Creeper: Barut", "Wither: Nether Tuglasi", "Ejderha: Ejderha Nefesi"] +name = "Uretilebilir Kafataslari!" +description = "Malzemeler kullanarak Yaratik Kafataslari Uretebilirsiniz!" +lore1 = "Bir kafatasi elde etmek icin bir Kemik Blogunu asagidakilerle cevreleyin:" +lore2 = "Zombi: Curuk Et" +lore3 = "Iskelet: Kemik" +lore4 = "Creeper: Barut" +lore5 = "Wither: Nether Tuglasi" +lore6 = "Ejderha: Ejderha Nefesi" +lore = ["Bir kafatasi elde etmek icin bir Kemik Blogunu asagidakilerle cevreleyin:", "Zombi: Curuk Et", "Iskelet: Kemik", "Creeper: Barut", "Wither: Nether Tuglasi", "Ejderha: Ejderha Nefesi"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Sisedeki Zaman" - description = "Zaman depolayan ve zamanli bloklari, buyuyebilir bitkileri ve yavru hayvanlar gibi Yaslanabilir varliklari hizlandirmak icin harcayabileceginiz zamansal bir sise tasiyin. Tarif (Sekillsiz): Hizlilik Iksiri + Saat + Cam Sise." - lore1 = "Her tikte sarj edilen saniye" - lore2 = "Depolanan saniye basina zaman hizlandirmasi" - lore3 = "Tarif (Sekillsiz): Hizlilik Iksiri + Saat + Cam Sise" - lore = ["Her tikte sarj edilen saniye", "Depolanan saniye basina zaman hizlandirmasi", "Tarif (Sekillsiz): Hizlilik Iksiri + Saat + Cam Sise"] +name = "Sisedeki Zaman" +description = "Zaman depolayan ve zamanli bloklari, buyuyebilir bitkileri ve yavru hayvanlar gibi Yaslanabilir varliklari hizlandirmak icin harcayabileceginiz zamansal bir sise tasiyin. Tarif (Sekillsiz): Hizlilik Iksiri + Saat + Cam Sise." +lore1 = "Her tikte sarj edilen saniye" +lore2 = "Depolanan saniye basina zaman hizlandirmasi" +lore3 = "Tarif (Sekillsiz): Hizlilik Iksiri + Saat + Cam Sise" +lore = ["Her tikte sarj edilen saniye", "Depolanan saniye basina zaman hizlandirmasi", "Tarif (Sekillsiz): Hizlilik Iksiri + Saat + Cam Sise"] [chronos.aberrant_touch] - name = "Sapkin Dokunuş" - description = "Yakin dövüs saldiriları, açlik pahasina yigilan yavaslatma uygular, sıki PvP sinirlariyla ve 5 yiginlamada hedefleri kökler." - lore1 = "Yakin dovus saldirilari yigilan yavaslatma uygular" - lore2 = "PvE yavaslatma suresi siniri" - lore3 = "PvP yavaslatma guclendirici siniri" - lore = ["Yakin dovus saldirilari yigilan yavaslatma uygular", "PvE yavaslatma suresi siniri", "PvP yavaslatma guclendirici siniri"] +name = "Sapkin Dokunuş" +description = "Yakin dövüs saldiriları, açlik pahasina yigilan yavaslatma uygular, sıki PvP sinirlariyla ve 5 yiginlamada hedefleri kökler." +lore1 = "Yakin dovus saldirilari yigilan yavaslatma uygular" +lore2 = "PvE yavaslatma suresi siniri" +lore3 = "PvP yavaslatma guclendirici siniri" +lore = ["Yakin dovus saldirilari yigilan yavaslatma uygular", "PvE yavaslatma suresi siniri", "PvP yavaslatma guclendirici siniri"] [chronos.instant_recall] - name = "Aninda Geri Cagirma" - description = "Elde saat tutarken sol veya sag tiklayarak saglik ve aclik geri yuklenmis sekilde yakin bir ana geri sarın." - lore1 = "Geri sarim suresi" - lore2 = "Bekleme suresi" - lore3 = "Envanter geri alinmaz" - lore = ["Geri sarim suresi", "Bekleme suresi", "Envanter geri alinmaz"] +name = "Aninda Geri Cagirma" +description = "Elde saat tutarken sol veya sag tiklayarak saglik ve aclik geri yuklenmis sekilde yakin bir ana geri sarın." +lore1 = "Geri sarim suresi" +lore2 = "Bekleme suresi" +lore3 = "Envanter geri alinmaz" +lore = ["Geri sarim suresi", "Bekleme suresi", "Envanter geri alinmaz"] [chronos.time_bomb] - name = "Zaman Bombasi" - description = "Zamansal bir alan yaratan, varliklari yavalatan ve mermileleri donduran üretilmiş bir krono bombasi firlatın." - lore1 = "Zamansal alan yaricapi" - lore2 = "Zamansal alan suresi" - lore3 = "Bomba bekleme suresi" - lore4 = "Tarif (Sekillsiz): Saat + Kartopu + Elmas + Kum" - lore = ["Zamansal alan yaricapi", "Zamansal alan suresi", "Bomba bekleme suresi", "Tarif (Sekillsiz): Saat + Kartopu + Elmas + Kum"] +name = "Zaman Bombasi" +description = "Zamansal bir alan yaratan, varliklari yavalatan ve mermileleri donduran üretilmiş bir krono bombasi firlatın." +lore1 = "Zamansal alan yaricapi" +lore2 = "Zamansal alan suresi" +lore3 = "Bomba bekleme suresi" +lore4 = "Tarif (Sekillsiz): Saat + Kartopu + Elmas + Kum" +lore = ["Zamansal alan yaricapi", "Zamansal alan suresi", "Bomba bekleme suresi", "Tarif (Sekillsiz): Saat + Kartopu + Elmas + Kum"] # discovery [discovery] [discovery.armor] - name = "Dunya Zirhi" - description = "Yakindaki blok sertligine bagli olarak pasif zirh." - lore1 = "Pasif Zirh" - lore2 = "Yakindaki blok sertligine gore" - lore3 = "Zirh Gucu:" - lore = ["Pasif Zirh", "Yakindaki blok sertligine gore", "Zirh Gucu:"] +name = "Dunya Zirhi" +description = "Yakindaki blok sertligine bagli olarak pasif zirh." +lore1 = "Pasif Zirh" +lore2 = "Yakindaki blok sertligine gore" +lore3 = "Zirh Gucu:" +lore = ["Pasif Zirh", "Yakindaki blok sertligine gore", "Zirh Gucu:"] [discovery.unity] - name = "Deneysel Birlik" - description = "Deneyim Kureleri toplamak rastgele becerilere XP ekler." - lore1 = "XP " - lore2 = "Kure Basina" - lore = ["XP ", "Kure Basina"] +name = "Deneysel Birlik" +description = "Deneyim Kureleri toplamak rastgele becerilere XP ekler." +lore1 = "XP " +lore2 = "Kure Basina" +lore = ["XP ", "Kure Basina"] [discovery.resist] - name = "Deneysel Direnc" - description = "Bir vurus sizi 5 kalbin altina dusurduğünde veya öldüreceğinde hasari azaltmak icin deneyim tuketin." - lore0 = "Yalnizca kritik saglikta (<= 5 kalp) 15 saniyede bir tetiklenir" - lore1 = " Azaltilmis Hasar" - lore2 = "deneyim tukendi" - lore = ["Yalnizca kritik saglikta (<= 5 kalp) 15 saniyede bir tetiklenir", " Azaltilmis Hasar", "deneyim tukendi"] +name = "Deneysel Direnc" +description = "Bir vurus sizi 5 kalbin altina dusurduğünde veya öldüreceğinde hasari azaltmak icin deneyim tuketin." +lore0 = "Yalnizca kritik saglikta (<= 5 kalp) 15 saniyede bir tetiklenir" +lore1 = " Azaltilmis Hasar" +lore2 = "deneyim tukendi" +lore = ["Yalnizca kritik saglikta (<= 5 kalp) 15 saniyede bir tetiklenir", " Azaltilmis Hasar", "deneyim tukendi"] [discovery.villager] - name = "Koylu Cazibesi" - description = "Koylülerle daha iyi ticaret yapmanizi saglar!" - lore1 = "Bu, Koylülerle etkilesim basina XP tuketir" - lore2 = "XP tuketmek ve takasi gelistirmek icin etkilesim basina sans" - lore3 = "Etkilesim basina gereken XP tukenmesi" - lore = ["Bu, Koylülerle etkilesim basina XP tuketir", "XP tuketmek ve takasi gelistirmek icin etkilesim basina sans", "Etkilesim basina gereken XP tukenmesi"] +name = "Koylu Cazibesi" +description = "Koylülerle daha iyi ticaret yapmanizi saglar!" +lore1 = "Bu, Koylülerle etkilesim basina XP tuketir" +lore2 = "XP tuketmek ve takasi gelistirmek icin etkilesim basina sans" +lore3 = "Etkilesim basina gereken XP tukenmesi" +lore = ["Bu, Koylülerle etkilesim basina XP tuketir", "XP tuketmek ve takasi gelistirmek icin etkilesim basina sans", "Etkilesim basina gereken XP tukenmesi"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Lapis Iadesi" - description = "1 XP seviyesi daha fazla maliyetine, karsiliginda ucretsiz lapis verme sansi var" - lore1 = "Her seviye, buyuleme maliyetini 1 arttirir ama 3 Lapis'e kadar iade edebilir" - lore = ["Her seviye, buyuleme maliyetini 1 arttirir ama 3 Lapis'e kadar iade edebilir"] +name = "Lapis Iadesi" +description = "1 XP seviyesi daha fazla maliyetine, karsiliginda ucretsiz lapis verme sansi var" +lore1 = "Her seviye, buyuleme maliyetini 1 arttirir ama 3 Lapis'e kadar iade edebilir" +lore = ["Her seviye, buyuleme maliyetini 1 arttirir ama 3 Lapis'e kadar iade edebilir"] [enchanting.quick_enchant] - name = "Hizli Tikla Buyule" - description = "Esyalari dogrudan uzerlerindeki buyulu kitaplara tiklayarak buyuleyin." - lore1 = "Maksimum Birlesik Seviye" - lore2 = "Bir esyayi birden fazla buyuleyemezsiniz " - lore3 = "guc" - lore = ["Maksimum Birlesik Seviye", "Bir esyayi birden fazla buyuleyemezsiniz ", "guc"] +name = "Hizli Tikla Buyule" +description = "Esyalari dogrudan uzerlerindeki buyulu kitaplara tiklayarak buyuleyin." +lore1 = "Maksimum Birlesik Seviye" +lore2 = "Bir esyayi birden fazla buyuleyemezsiniz " +lore3 = "guc" +lore = ["Maksimum Birlesik Seviye", "Bir esyayi birden fazla buyuleyemezsiniz ", "guc"] [enchanting.return] - name = "XP Iadesi" - description = "Bir esyayi buyulediginizde Buyuleme XP'si size iade edilir." - lore1 = "Bir esyayi buyulediginizde harcanan deneyimin iade edilme sansi vardir" - lore2 = "Buyuleme Basina Deneyim" - lore = ["Bir esyayi buyulediginizde harcanan deneyimin iade edilme sansi vardir", "Buyuleme Basina Deneyim"] +name = "XP Iadesi" +description = "Bir esyayi buyulediginizde Buyuleme XP'si size iade edilir." +lore1 = "Bir esyayi buyulediginizde harcanan deneyimin iade edilme sansi vardir" +lore2 = "Buyuleme Basina Deneyim" +lore = ["Bir esyayi buyulediginizde harcanan deneyimin iade edilme sansi vardir", "Buyuleme Basina Deneyim"] # excavation [excavation] [excavation.haste] - name = "Aceleci Kazici" - description = "Kazi islemini ACELE ile hizlandirir!" - lore1 = "Kazi yaparken Acele kazanin" - lore2 = "x herhangi bir blok kazmaya basladiginizda Acele seviyeleri." - lore = ["Kazi yaparken Acele kazanin", "x herhangi bir blok kazmaya basladiginizda Acele seviyeleri."] +name = "Aceleci Kazici" +description = "Kazi islemini ACELE ile hizlandirir!" +lore1 = "Kazi yaparken Acele kazanin" +lore2 = "x herhangi bir blok kazmaya basladiginizda Acele seviyeleri." +lore = ["Kazi yaparken Acele kazanin", "x herhangi bir blok kazmaya basladiginizda Acele seviyeleri."] [excavation.spelunker] - name = "Super Goren Magara Kásifi!" - description = "Cevherleri gozlerinizle gorun, ama yerin altindan!" - lore1 = "Yan elinizde cevher, ana elinizde Parlakmeyveler ve Egilin!" - lore2 = "Blok Menzili: " - lore3 = "Kullanimda Parlakmeyve tuketir" - lore = ["Yan elinizde cevher, ana elinizde Parlakmeyveler ve Egilin!", "Blok Menzili: ", "Kullanimda Parlakmeyve tuketir"] +name = "Super Goren Magara Kásifi!" +description = "Cevherleri gozlerinizle gorun, ama yerin altindan!" +lore1 = "Yan elinizde cevher, ana elinizde Parlakmeyveler ve Egilin!" +lore2 = "Blok Menzili: " +lore3 = "Kullanimda Parlakmeyve tuketir" +lore = ["Yan elinizde cevher, ana elinizde Parlakmeyveler ve Egilin!", "Blok Menzili: ", "Kullanimda Parlakmeyve tuketir"] [excavation.drop_to_inventory] - name = "Kurek Envantere Dusurme" +name = "Kurek Envantere Dusurme" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "Tackle'in asiri tasarlanmis gosterisli Leatherman'i" - lore1 = "Muhtemelen en guclulerinden biri," - lore2 = "ihtiyaclariniza gore aletleri aninda dinamik olarak birlestirin ve degistirin." - lore3 = "Birlestirmek icin envanterinizde bir esyayi digerinin uzerine shift tiklayin." - lore4 = "Aletleri cozmek icin esyayi Egilip-Birakin, parcalanacaktir." - lore5 = "Bu leatherman'de aletleri kiramazsiniz ama kirik aletleri kullanamazsiniz" - lore6 = "toplam birlestirilebilir esya." - lore7 = "Bes veya alti alet kullanabilirsin ya da sadece bir tane!" - lore = ["Muhtemelen en guclulerinden biri,", "ihtiyaclariniza gore aletleri aninda dinamik olarak birlestirin ve degistirin.", "Birlestirmek icin envanterinizde bir esyayi digerinin uzerine shift tiklayin.", "Aletleri cozmek icin esyayi Egilip-Birakin, parcalanacaktir.", "Bu leatherman'de aletleri kiramazsiniz ama kirik aletleri kullanamazsiniz", "toplam birlestirilebilir esya.", "Bes veya alti alet kullanabilirsin ya da sadece bir tane!"] +name = "OMNI - T.O.O.L." +description = "Tackle'in asiri tasarlanmis gosterisli Leatherman'i" +lore1 = "Muhtemelen en guclulerinden biri," +lore2 = "ihtiyaclariniza gore aletleri aninda dinamik olarak birlestirin ve degistirin." +lore3 = "Birlestirmek icin envanterinizde bir esyayi digerinin uzerine shift tiklayin." +lore4 = "Aletleri cozmek icin esyayi Egilip-Birakin, parcalanacaktir." +lore5 = "Bu leatherman'de aletleri kiramazsiniz ama kirik aletleri kullanamazsiniz" +lore6 = "toplam birlestirilebilir esya." +lore7 = "Bes veya alti alet kullanabilirsin ya da sadece bir tane!" +lore = ["Muhtemelen en guclulerinden biri,", "ihtiyaclariniza gore aletleri aninda dinamik olarak birlestirin ve degistirin.", "Birlestirmek icin envanterinizde bir esyayi digerinin uzerine shift tiklayin.", "Aletleri cozmek icin esyayi Egilip-Birakin, parcalanacaktir.", "Bu leatherman'de aletleri kiramazsiniz ama kirik aletleri kullanamazsiniz", "toplam birlestirilebilir esya.", "Bes veya alti alet kullanabilirsin ya da sadece bir tane!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Buyume Aurasi" - description = "Cevrenizdeki dogayi bir aura icinde buyutun" - lore1 = "Blok Yaricapi" - lore2 = "Buyume Aurasi Gucu" - lore3 = "Yiyecek Maliyeti" - lore = ["Blok Yaricapi", "Buyume Aurasi Gucu", "Yiyecek Maliyeti"] +name = "Buyume Aurasi" +description = "Cevrenizdeki dogayi bir aura icinde buyutun" +lore1 = "Blok Yaricapi" +lore2 = "Buyume Aurasi Gucu" +lore3 = "Yiyecek Maliyeti" +lore = ["Blok Yaricapi", "Buyume Aurasi Gucu", "Yiyecek Maliyeti"] [herbalism.hippo] - name = "Bitkicinin Su Aygiri" - description = "Yiyecek tuketmek size daha fazla doygunluk saglar" - lore1 = "Yiyecek) tuketimde ek doygunluk puanlari" - lore = ["Yiyecek) tuketimde ek doygunluk puanlari"] +name = "Bitkicinin Su Aygiri" +description = "Yiyecek tuketmek size daha fazla doygunluk saglar" +lore1 = "Yiyecek) tuketimde ek doygunluk puanlari" +lore = ["Yiyecek) tuketimde ek doygunluk puanlari"] [herbalism.myconid] - name = "Bitkicinin Myconid'i" - description = "Size Miselyum yapma yetenegi verir" - lore1 = "Herhangi bir Toprak ve bir Kahverengi & Kirmizi Mantar, Miselyum uretecektir." - lore = ["Herhangi bir Toprak ve bir Kahverengi & Kirmizi Mantar, Miselyum uretecektir."] +name = "Bitkicinin Myconid'i" +description = "Size Miselyum yapma yetenegi verir" +lore1 = "Herhangi bir Toprak ve bir Kahverengi & Kirmizi Mantar, Miselyum uretecektir." +lore = ["Herhangi bir Toprak ve bir Kahverengi & Kirmizi Mantar, Miselyum uretecektir."] [herbalism.terralid] - name = "Bitkicinin Terralid'i" - description = "Size Cimenli Blok yapma yetenegi verir" - lore1 = "3 Toprak uzerinde Uc Tohum, 3 Cimenli Blok uretecektir." - lore = ["3 Toprak uzerinde Uc Tohum, 3 Cimenli Blok uretecektir."] +name = "Bitkicinin Terralid'i" +description = "Size Cimenli Blok yapma yetenegi verir" +lore1 = "3 Toprak uzerinde Uc Tohum, 3 Cimenli Blok uretecektir." +lore = ["3 Toprak uzerinde Uc Tohum, 3 Cimenli Blok uretecektir."] [herbalism.cobweb] - name = "Ag Oluşturucu" - description = "Uretim Masasinda Orumcek Agi yapma yetenegi verir" - lore1 = "Dokuz Iplik, bir Orumcek Agi yapacaktir." - lore = ["Dokuz Iplik, bir Orumcek Agi yapacaktir."] +name = "Ag Oluşturucu" +description = "Uretim Masasinda Orumcek Agi yapma yetenegi verir" +lore1 = "Dokuz Iplik, bir Orumcek Agi yapacaktir." +lore = ["Dokuz Iplik, bir Orumcek Agi yapacaktir."] [herbalism.mushroom_blocks] - name = "Mantar Yapici" - description = "Uretim Masasinda Mantar Bloklari yapma yetenegi verir" - lore1 = "Bir blok yapmak icin dort Mantar veya bir govde yapmak icin bir blok." - lore = ["Bir blok yapmak icin dort Mantar veya bir govde yapmak icin bir blok."] +name = "Mantar Yapici" +description = "Uretim Masasinda Mantar Bloklari yapma yetenegi verir" +lore1 = "Bir blok yapmak icin dort Mantar veya bir govde yapmak icin bir blok." +lore = ["Bir blok yapmak icin dort Mantar veya bir govde yapmak icin bir blok."] [herbalism.drop_to_inventory] - name = "Copa Envantere Dusurme" +name = "Copa Envantere Dusurme" [herbalism.hungry_shield] - name = "Ac Kalkan" - description = "Sagliginizdan once acliginiza hasar verin." - lore1 = "Aclik tarafindan direncli" - lore = ["Aclik tarafindan direncli"] +name = "Ac Kalkan" +description = "Sagliginizdan once acliginiza hasar verin." +lore1 = "Aclik tarafindan direncli" +lore = ["Aclik tarafindan direncli"] [herbalism.luck] - name = "Bitkicinin Sansi" - description = "Cim/Cicek kirdiginizda rastgele bir esya alma sansiniz olur" - lore0 = "Cicekler = Yiyecek ve Cim = Tohumlar" - lore1 = "Cicek kirarak esya kazanma sansi" - lore2 = "Cim kirarak esya kazanma sansi" - lore = ["Cicekler = Yiyecek ve Cim = Tohumlar", "Cicek kirarak esya kazanma sansi", "Cim kirarak esya kazanma sansi"] +name = "Bitkicinin Sansi" +description = "Cim/Cicek kirdiginizda rastgele bir esya alma sansiniz olur" +lore0 = "Cicekler = Yiyecek ve Cim = Tohumlar" +lore1 = "Cicek kirarak esya kazanma sansi" +lore2 = "Cim kirarak esya kazanma sansi" +lore = ["Cicekler = Yiyecek ve Cim = Tohumlar", "Cicek kirarak esya kazanma sansi", "Cim kirarak esya kazanma sansi"] [herbalism.replant] - name = "Hasat ve Tekrar Dikim" - description = "Copa ile bir ekine sag tiklayarak hasat edin ve yeniden dikin." - lore1 = "Blok Yeniden Dikim Yaricapi" - lore = ["Blok Yeniden Dikim Yaricapi"] +name = "Hasat ve Tekrar Dikim" +description = "Copa ile bir ekine sag tiklayarak hasat edin ve yeniden dikin." +lore1 = "Blok Yeniden Dikim Yaricapi" +lore = ["Blok Yeniden Dikim Yaricapi"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenalin" - description = "Caniniz azaldikca daha fazla hasar verin (Yakin Dovus)" - lore1 = "Maksimum Hasar" - lore = ["Maksimum Hasar"] +name = "Adrenalin" +description = "Caniniz azaldikca daha fazla hasar verin (Yakin Dovus)" +lore1 = "Maksimum Hasar" +lore = ["Maksimum Hasar"] [hunter.penalty] - name = "" - description = "" - lore1 = "Acliginiz biterse Zehir yiginlari kazanacaksiniz" - lore = ["Acliginiz biterse Zehir yiginlari kazanacaksiniz"] +name = "" +description = "" +lore1 = "Acliginiz biterse Zehir yiginlari kazanacaksiniz" +lore = ["Acliginiz biterse Zehir yiginlari kazanacaksiniz"] [hunter.drop_to_inventory] - name = "Esya Envantere Dusurme" - description = "Bir seyi oldurdugunuzde / Kilicla bir blok kirdiginizda dusmeleri envanterinize isinlar" - lore1 = "Bir yaratik/bloktan bir esya dustugunde, mumkunse envanterinize girer." - lore = ["Bir yaratik/bloktan bir esya dustugunde, mumkunse envanterinize girer."] +name = "Esya Envantere Dusurme" +description = "Bir seyi oldurdugunuzde / Kilicla bir blok kirdiginizda dusmeleri envanterinize isinlar" +lore1 = "Bir yaratik/bloktan bir esya dustugunde, mumkunse envanterinize girer." +lore = ["Bir yaratik/bloktan bir esya dustugunde, mumkunse envanterinize girer."] [hunter.invisibility] - name = "Kaybolan Adim" - description = "Vuruldugunuzda aclik pahasina gorunmezlik kazanirsiniz" - lore1 = "Vuruldugunuzda pasif gorunmezlik kazanin" - lore2 = "x Gorunmezlik vurusda 3 saniye boyunca birikir" - lore3 = "x Biriken aclik" - lore4 = "Aclik yiginlari suresi ve carpani." - lore5 = "Gorunmezlik suresi" - lore = ["Vuruldugunuzda pasif gorunmezlik kazanin", "x Gorunmezlik vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Gorunmezlik suresi"] +name = "Kaybolan Adim" +description = "Vuruldugunuzda aclik pahasina gorunmezlik kazanirsiniz" +lore1 = "Vuruldugunuzda pasif gorunmezlik kazanin" +lore2 = "x Gorunmezlik vurusda 3 saniye boyunca birikir" +lore3 = "x Biriken aclik" +lore4 = "Aclik yiginlari suresi ve carpani." +lore5 = "Gorunmezlik suresi" +lore = ["Vuruldugunuzda pasif gorunmezlik kazanin", "x Gorunmezlik vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Gorunmezlik suresi"] [hunter.jump_boost] - name = "Avcinin Yukseklikleri" - description = "Vuruldugunuzda aclik pahasina ziplama gucu kazanirsiniz" - lore1 = "Vuruldugunuzda pasif ziplama gucu kazanin" - lore2 = "x Vurusda 3 saniye boyunca Ziplama Gucu birikir" - lore3 = "x Biriken aclik" - lore4 = "Aclik yiginlari suresi ve carpani." - lore5 = "Ziplama Gucu yiginlari carpani, sureyi degil." - lore = ["Vuruldugunuzda pasif ziplama gucu kazanin", "x Vurusda 3 saniye boyunca Ziplama Gucu birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Ziplama Gucu yiginlari carpani, sureyi degil."] +name = "Avcinin Yukseklikleri" +description = "Vuruldugunuzda aclik pahasina ziplama gucu kazanirsiniz" +lore1 = "Vuruldugunuzda pasif ziplama gucu kazanin" +lore2 = "x Vurusda 3 saniye boyunca Ziplama Gucu birikir" +lore3 = "x Biriken aclik" +lore4 = "Aclik yiginlari suresi ve carpani." +lore5 = "Ziplama Gucu yiginlari carpani, sureyi degil." +lore = ["Vuruldugunuzda pasif ziplama gucu kazanin", "x Vurusda 3 saniye boyunca Ziplama Gucu birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Ziplama Gucu yiginlari carpani, sureyi degil."] [hunter.luck] - name = "Avcinin Sansi" - description = "Vuruldugunuzda aclik pahasina sans kazanirsiniz" - lore1 = "Vuruldugunuzda pasif sans kazanin" - lore2 = "x Sans vurusda 3 saniye boyunca birikir" - lore3 = "x Biriken aclik" - lore4 = "Aclik yiginlari suresi ve carpani." - lore5 = "Sans yiginlari carpani, sureyi degil." - lore = ["Vuruldugunuzda pasif sans kazanin", "x Sans vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Sans yiginlari carpani, sureyi degil."] +name = "Avcinin Sansi" +description = "Vuruldugunuzda aclik pahasina sans kazanirsiniz" +lore1 = "Vuruldugunuzda pasif sans kazanin" +lore2 = "x Sans vurusda 3 saniye boyunca birikir" +lore3 = "x Biriken aclik" +lore4 = "Aclik yiginlari suresi ve carpani." +lore5 = "Sans yiginlari carpani, sureyi degil." +lore = ["Vuruldugunuzda pasif sans kazanin", "x Sans vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Sans yiginlari carpani, sureyi degil."] [hunter.regen] - name = "Avcinin Yenilenmesi" - description = "Vuruldugunuzda aclik pahasina yenilenme kazanirsiniz" - lore1 = "Vuruldugunuzda pasif yenilenme kazanin" - lore2 = "x Yenilenme vurusda 3 saniye boyunca birikir" - lore3 = "x Biriken aclik" - lore4 = "Aclik yiginlari suresi ve carpani." - lore5 = "Yenilenme yiginlari carpani, sureyi degil." - lore = ["Vuruldugunuzda pasif yenilenme kazanin", "x Yenilenme vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Yenilenme yiginlari carpani, sureyi degil."] +name = "Avcinin Yenilenmesi" +description = "Vuruldugunuzda aclik pahasina yenilenme kazanirsiniz" +lore1 = "Vuruldugunuzda pasif yenilenme kazanin" +lore2 = "x Yenilenme vurusda 3 saniye boyunca birikir" +lore3 = "x Biriken aclik" +lore4 = "Aclik yiginlari suresi ve carpani." +lore5 = "Yenilenme yiginlari carpani, sureyi degil." +lore = ["Vuruldugunuzda pasif yenilenme kazanin", "x Yenilenme vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Yenilenme yiginlari carpani, sureyi degil."] [hunter.resistance] - name = "Avcinin Direnci" - description = "Vuruldugunuzda aclik pahasina direnc kazanirsiniz" - lore1 = "Vuruldugunuzda pasif direnc kazanin" - lore2 = "x Direnc vurusda 3 saniye boyunca birikir" - lore3 = "x Biriken aclik" - lore4 = "Aclik yiginlari suresi ve carpani." - lore5 = "Direnc yiginlari carpani, sureyi degil." - lore = ["Vuruldugunuzda pasif direnc kazanin", "x Direnc vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Direnc yiginlari carpani, sureyi degil."] +name = "Avcinin Direnci" +description = "Vuruldugunuzda aclik pahasina direnc kazanirsiniz" +lore1 = "Vuruldugunuzda pasif direnc kazanin" +lore2 = "x Direnc vurusda 3 saniye boyunca birikir" +lore3 = "x Biriken aclik" +lore4 = "Aclik yiginlari suresi ve carpani." +lore5 = "Direnc yiginlari carpani, sureyi degil." +lore = ["Vuruldugunuzda pasif direnc kazanin", "x Direnc vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Direnc yiginlari carpani, sureyi degil."] [hunter.speed] - name = "Avcinin Hizi" - description = "Vuruldugunuzda aclik pahasina hiz kazanirsiniz" - lore1 = "Vuruldugunuzda pasif hiz kazanin" - lore2 = "x Hiz vurusda 3 saniye boyunca birikir" - lore3 = "x Biriken aclik" - lore4 = "Aclik yiginlari suresi ve carpani." - lore5 = "Hiz yiginlari carpani, sureyi degil." - lore = ["Vuruldugunuzda pasif hiz kazanin", "x Hiz vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Hiz yiginlari carpani, sureyi degil."] +name = "Avcinin Hizi" +description = "Vuruldugunuzda aclik pahasina hiz kazanirsiniz" +lore1 = "Vuruldugunuzda pasif hiz kazanin" +lore2 = "x Hiz vurusda 3 saniye boyunca birikir" +lore3 = "x Biriken aclik" +lore4 = "Aclik yiginlari suresi ve carpani." +lore5 = "Hiz yiginlari carpani, sureyi degil." +lore = ["Vuruldugunuzda pasif hiz kazanin", "x Hiz vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Hiz yiginlari carpani, sureyi degil."] [hunter.strength] - name = "Avcinin Gucu" - description = "Vuruldugunuzda aclik pahasina guc kazanirsiniz" - lore1 = "Vuruldugunuzda pasif guc kazanin" - lore2 = "x Guc vurusda 3 saniye boyunca birikir" - lore3 = "x Biriken aclik" - lore4 = "Aclik yiginlari suresi ve carpani." - lore5 = "Guc yiginlari carpani, sureyi degil." - lore = ["Vuruldugunuzda pasif guc kazanin", "x Guc vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Guc yiginlari carpani, sureyi degil."] +name = "Avcinin Gucu" +description = "Vuruldugunuzda aclik pahasina guc kazanirsiniz" +lore1 = "Vuruldugunuzda pasif guc kazanin" +lore2 = "x Guc vurusda 3 saniye boyunca birikir" +lore3 = "x Biriken aclik" +lore4 = "Aclik yiginlari suresi ve carpani." +lore5 = "Guc yiginlari carpani, sureyi degil." +lore = ["Vuruldugunuzda pasif guc kazanin", "x Guc vurusda 3 saniye boyunca birikir", "x Biriken aclik", "Aclik yiginlari suresi ve carpani.", "Guc yiginlari carpani, sureyi degil."] # nether [nether] [nether.skull_toss] - name = "Wither Kafatasi Firlatma" - description1 = "Kullanarak icinizdeki Wither'i aciga cikarin" - description2 = "birinin" - description3 = "kafasini." - lore1 = "Kafatasi firlatlari arasinda saniye bekleme suresi." - lore2 = "Wither Kafatasi Kullanarak: Bir " - lore3 = "Wither Kafatasi" - lore4 = "carpma aninda patlayarak firlatin." - lore = ["Kafatasi firlatlari arasinda saniye bekleme suresi.", "Wither Kafatasi Kullanarak: Bir ", "Wither Kafatasi", "carpma aninda patlayarak firlatin."] +name = "Wither Kafatasi Firlatma" +description1 = "Kullanarak icinizdeki Wither'i aciga cikarin" +description2 = "birinin" +description3 = "kafasini." +lore1 = "Kafatasi firlatlari arasinda saniye bekleme suresi." +lore2 = "Wither Kafatasi Kullanarak: Bir " +lore3 = "Wither Kafatasi" +lore4 = "carpma aninda patlayarak firlatin." +lore = ["Kafatasi firlatlari arasinda saniye bekleme suresi.", "Wither Kafatasi Kullanarak: Bir ", "Wither Kafatasi", "carpma aninda patlayarak firlatin."] [nether.wither_resist] - name = "Wither Direnci" - description = "Netherite'in gucu ile solmaya karsi direnir." - lore1 = "solmayi engeleme sansi (parca basina)." - lore2 = "Pasif: Netherite Zirhi giymek solmayi engelleme" - lore3 = "sansina sahiptir." - lore = ["solmayi engeleme sansi (parca basina).", "Pasif: Netherite Zirhi giymek solmayi engelleme", "sansina sahiptir."] +name = "Wither Direnci" +description = "Netherite'in gucu ile solmaya karsi direnir." +lore1 = "solmayi engeleme sansi (parca basina)." +lore2 = "Pasif: Netherite Zirhi giymek solmayi engelleme" +lore3 = "sansina sahiptir." +lore = ["solmayi engeleme sansi (parca basina).", "Pasif: Netherite Zirhi giymek solmayi engelleme", "sansina sahiptir."] [nether.fire_resist] - name = "Ates Direnci" - description = "Cildinizi sertlestirerek atese karsi direnir." - lore1 = "yanma etkisini ortadan kaldirma sansi!" - lore = ["yanma etkisini ortadan kaldirma sansi!"] +name = "Ates Direnci" +description = "Cildinizi sertlestirerek atese karsi direnir." +lore1 = "yanma etkisini ortadan kaldirma sansi!" +lore = ["yanma etkisini ortadan kaldirma sansi!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Otomatik Eritme" - description = "Kaziilan Vanilya cevherlerini eritmenizi saglar" - lore1 = "Eritilebilen cevherler otomatik olarak eritilir" - lore2 = "% ekstra sans" - lore = ["Eritilebilen cevherler otomatik olarak eritilir", "% ekstra sans"] +name = "Otomatik Eritme" +description = "Kaziilan Vanilya cevherlerini eritmenizi saglar" +lore1 = "Eritilebilen cevherler otomatik olarak eritilir" +lore2 = "% ekstra sans" +lore = ["Eritilebilen cevherler otomatik olarak eritilir", "% ekstra sans"] [pickaxe.chisel] - name = "Cevher Keskisi" - description = "Cevherlerden daha fazla cevher cikarmak icin Sag Tiklayin, ciddi bir dayaniklilik maliyetiyle." - lore1 = "Dusme Sansi" - lore2 = "Alet Asinmasi" - lore = ["Dusme Sansi", "Alet Asinmasi"] +name = "Cevher Keskisi" +description = "Cevherlerden daha fazla cevher cikarmak icin Sag Tiklayin, ciddi bir dayaniklilik maliyetiyle." +lore1 = "Dusme Sansi" +lore2 = "Alet Asinmasi" +lore = ["Dusme Sansi", "Alet Asinmasi"] [pickaxe.drop_to_inventory] - name = "Kazma Envantere Dusurme" - description = "Bir blogu kirdiginizda esya envanterinize isinlanir" - lore1 = "Kirdiginiz bir bloktan dusen bir esya mumkunse envanterinize girer." - lore = ["Kirdiginiz bir bloktan dusen bir esya mumkunse envanterinize girer."] +name = "Kazma Envantere Dusurme" +description = "Bir blogu kirdiginizda esya envanterinize isinlanir" +lore1 = "Kirdiginiz bir bloktan dusen bir esya mumkunse envanterinize girer." +lore = ["Kirdiginiz bir bloktan dusen bir esya mumkunse envanterinize girer."] [pickaxe.silk_spawner] - name = "Kazma Ipek-Yaratici" - description = "Spawner'larin kirildiginda dusmesini saglar" - lore1 = "Spawner'lari ipek dokunusuyla kiriabilir hale getirir." - lore2 = "Spawner'lari egilirken kiriabilir hale getirir." - lore = ["Spawner'lari ipek dokunusuyla kiriabilir hale getirir.", "Spawner'lari egilirken kiriabilir hale getirir."] +name = "Kazma Ipek-Yaratici" +description = "Spawner'larin kirildiginda dusmesini saglar" +lore1 = "Spawner'lari ipek dokunusuyla kiriabilir hale getirir." +lore2 = "Spawner'lari egilirken kiriabilir hale getirir." +lore = ["Spawner'lari ipek dokunusuyla kiriabilir hale getirir.", "Spawner'lari egilirken kiriabilir hale getirir."] [pickaxe.vein_miner] - name = "Damar Madencisi" - description = "Vanilya cevheri Damarinda/Kumesinde bloklari kirmanizi saglar" - lore1 = "Egilin ve CEVHER kazin" - lore2 = "damar madenciligi menzili" - lore3 = "Bu beceri tum dusmeleri bir araya gruplandirmaz!" - lore = ["Egilin ve CEVHER kazin", "damar madenciligi menzili", "Bu beceri tum dusmeleri bir araya gruplandirmaz!"] +name = "Damar Madencisi" +description = "Vanilya cevheri Damarinda/Kumesinde bloklari kirmanizi saglar" +lore1 = "Egilin ve CEVHER kazin" +lore2 = "damar madenciligi menzili" +lore3 = "Bu beceri tum dusmeleri bir araya gruplandirmaz!" +lore = ["Egilin ve CEVHER kazin", "damar madenciligi menzili", "Bu beceri tum dusmeleri bir araya gruplandirmaz!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Ok Kurtarma" - description = "Bir dusmani oldurdukten sonra oklari kurtarin." - lore1 = "Vurus/Oldurme'de Ok Kurtarma Sansi" - lore2 = "Sans: " - lore = ["Vurus/Oldurme'de Ok Kurtarma Sansi", "Sans: "] +name = "Ok Kurtarma" +description = "Bir dusmani oldurdukten sonra oklari kurtarin." +lore1 = "Vurus/Oldurme'de Ok Kurtarma Sansi" +lore2 = "Sans: " +lore = ["Vurus/Oldurme'de Ok Kurtarma Sansi", "Sans: "] [ranged.web_shot] - name = "Ag Tuzagi" - description = "Vurdugunuzda hedefinizin etrafini orumcek aglariyla cevreleyin!" - lore1 = "Bir Kartopunun etrafinda 8 Orumcek Agi ve firlatın!" - lore2 = "saniye kadar bir kafes, yaklasik olarak." - lore = ["Bir Kartopunun etrafinda 8 Orumcek Agi ve firlatın!", "saniye kadar bir kafes, yaklasik olarak."] +name = "Ag Tuzagi" +description = "Vurdugunuzda hedefinizin etrafini orumcek aglariyla cevreleyin!" +lore1 = "Bir Kartopunun etrafinda 8 Orumcek Agi ve firlatın!" +lore2 = "saniye kadar bir kafes, yaklasik olarak." +lore = ["Bir Kartopunun etrafinda 8 Orumcek Agi ve firlatın!", "saniye kadar bir kafes, yaklasik olarak."] [ranged.force_shot] - name = "Guclu Atis" - description = "Mermileleri daha uzaga, daha hizli atin!" - advancementname = "Uzun Atis" - advancementlore = "30 blok oteden bir atis yapin!" - lore1 = "Mermilerin Hizi" - lore = ["Mermilerin Hizi"] +name = "Guclu Atis" +description = "Mermileleri daha uzaga, daha hizli atin!" +advancementname = "Uzun Atis" +advancementlore = "30 blok oteden bir atis yapin!" +lore1 = "Mermilerin Hizi" +lore = ["Mermilerin Hizi"] [ranged.lunge_shot] - name = "Hamle Atisi" - description = "Duserken oklariniz sizi rastgele bir yone firlatir" - lore1 = "Rastgele Patlama Hizi" - lore = ["Rastgele Patlama Hizi"] +name = "Hamle Atisi" +description = "Duserken oklariniz sizi rastgele bir yone firlatir" +lore1 = "Rastgele Patlama Hizi" +lore = ["Rastgele Patlama Hizi"] [ranged.arrow_piercing] - name = "Ok Delicisi" - description = "Mermilere Delicilik ekler! Seylerin icinden atin!" - lore1 = "Delebilecegi Hedef" - lore = ["Delebilecegi Hedef"] +name = "Ok Delicisi" +description = "Mermilere Delicilik ekler! Seylerin icinden atin!" +lore1 = "Delebilecegi Hedef" +lore = ["Delebilecegi Hedef"] # rift [rift] [rift.remote_access] - name = "Uzaktan Erisim" - description = "Bosluktan cekin ve isaretli bir konteynere erisin." - lore1 = "Ender Incisi + Pusula = Emanet Anahtari" - lore2 = "Bu esya konteynerlere uzaktan erismenizi saglar" - lore3 = "Uretildikten sonra kullanimi gormek icin esyaya bakin" - notcontainer = "Bu bir konteyner degil" - lore = ["Ender Incisi + Pusula = Emanet Anahtari", "Bu esya konteynerlere uzaktan erismenizi saglar", "Uretildikten sonra kullanimi gormek icin esyaya bakin"] +name = "Uzaktan Erisim" +description = "Bosluktan cekin ve isaretli bir konteynere erisin." +lore1 = "Ender Incisi + Pusula = Emanet Anahtari" +lore2 = "Bu esya konteynerlere uzaktan erismenizi saglar" +lore3 = "Uretildikten sonra kullanimi gormek icin esyaya bakin" +notcontainer = "Bu bir konteyner degil" +lore = ["Ender Incisi + Pusula = Emanet Anahtari", "Bu esya konteynerlere uzaktan erismenizi saglar", "Uretildikten sonra kullanimi gormek icin esyaya bakin"] [rift.blink] - name = "Yarik Gozu Kirpma" - description = "Kisa menzilli aninda isinlanma, Sadece bir goz kirpimi uzaklikta!" - lore1 = "Goz kirpmada blok (2x Dikey)" - lore2 = "Kosarken: Ziplama'ya cift basin " - lore3 = "Goz Kirp" - lore = ["Goz kirpmada blok (2x Dikey)", "Kosarken: Ziplama'ya cift basin ", "Goz Kirp"] +name = "Yarik Gozu Kirpma" +description = "Kisa menzilli aninda isinlanma, Sadece bir goz kirpimi uzaklikta!" +lore1 = "Goz kirpmada blok (2x Dikey)" +lore2 = "Kosarken: Ziplama'ya cift basin " +lore3 = "Goz Kirp" +lore = ["Goz kirpmada blok (2x Dikey)", "Kosarken: Ziplama'ya cift basin ", "Goz Kirp"] [rift.chest] - name = "Kolay Ender Sandigi" - description = "Elinizde Sol tiklayarak bir ender sandigi acin." - lore1 = "Acmak icin elinizdeki Ender Sandigina tiklayin (Sadece yerlestirmeyin)" - lore = ["Acmak icin elinizdeki Ender Sandigina tiklayin (Sadece yerlestirmeyin)"] +name = "Kolay Ender Sandigi" +description = "Elinizde Sol tiklayarak bir ender sandigi acin." +lore1 = "Acmak icin elinizdeki Ender Sandigina tiklayin (Sadece yerlestirmeyin)" +lore = ["Acmak icin elinizdeki Ender Sandigina tiklayin (Sadece yerlestirmeyin)"] [rift.descent] - name = "Anti-Havaya Kalkma" - description = "Havada kalmaktan biktiniz mi? Bu sizin icin olan beceri!" - lore1 = "Inmek icin egilmeniz yeterli, normalden daha yavas duseceksiniz!" - lore2 = "Bekleme suresi:" - lore = ["Inmek icin egilmeniz yeterli, normalden daha yavas duseceksiniz!", "Bekleme suresi:"] +name = "Anti-Havaya Kalkma" +description = "Havada kalmaktan biktiniz mi? Bu sizin icin olan beceri!" +lore1 = "Inmek icin egilmeniz yeterli, normalden daha yavas duseceksiniz!" +lore2 = "Bekleme suresi:" +lore = ["Inmek icin egilmeniz yeterli, normalden daha yavas duseceksiniz!", "Bekleme suresi:"] [rift.gate] - name = "Yarik Kapisi" - description = "Isaretli bir konuma isinlanin." - lore1 = "URETIM: Zumrut + Ametist parcasi + Ender Incisi" - lore2 = "Kullanmadan once okuyun!" - lore3 = "5sn gecikme, " - lore4 = "bu animasyondayken olebilirsiniz" - lore = ["URETIM: Zumrut + Ametist parcasi + Ender Incisi", "Kullanmadan once okuyun!", "5sn gecikme, ", "bu animasyondayken olebilirsiniz"] +name = "Yarik Kapisi" +description = "Isaretli bir konuma isinlanin." +lore1 = "URETIM: Zumrut + Ametist parcasi + Ender Incisi" +lore2 = "Kullanmadan once okuyun!" +lore3 = "5sn gecikme, " +lore4 = "bu animasyondayken olebilirsiniz" +lore = ["URETIM: Zumrut + Ametist parcasi + Ender Incisi", "Kullanmadan once okuyun!", "5sn gecikme, ", "bu animasyondayken olebilirsiniz"] [rift.resist] - name = "Yarik Direnci" - description = "Ender Esyalari ve Yeteneklerini kullanirken Direnc kazanin" - lore1 = "+ Pasif: Yarik yeteneklerini veya Ender Esyalarini kullandiginizda direnc saglar" - lore2 = "Portatif Ender Sandigi DAHiL DEGiLDiR, yalnizca tuketebileceginiz seyler" - lore = ["+ Pasif: Yarik yeteneklerini veya Ender Esyalarini kullandiginizda direnc saglar", "Portatif Ender Sandigi DAHiL DEGiLDiR, yalnizca tuketebileceginiz seyler"] +name = "Yarik Direnci" +description = "Ender Esyalari ve Yeteneklerini kullanirken Direnc kazanin" +lore1 = "+ Pasif: Yarik yeteneklerini veya Ender Esyalarini kullandiginizda direnc saglar" +lore2 = "Portatif Ender Sandigi DAHiL DEGiLDiR, yalnizca tuketebileceginiz seyler" +lore = ["+ Pasif: Yarik yeteneklerini veya Ender Esyalarini kullandiginizda direnc saglar", "Portatif Ender Sandigi DAHiL DEGiLDiR, yalnizca tuketebileceginiz seyler"] [rift.visage] - name = "Yarik Gorunumu" - description = "Envanterinizde Ender Incisi varsa Endermen'lerin saldirganlasmasini engeller." - lore1 = "Envanterinizde Ender Incisi varsa Endermen'ler saldirgan olmaz." - lore = ["Envanterinizde Ender Incisi varsa Endermen'ler saldirgan olmaz."] +name = "Yarik Gorunumu" +description = "Envanterinizde Ender Incisi varsa Endermen'lerin saldirganlasmasini engeller." +lore1 = "Envanterinizde Ender Incisi varsa Endermen'ler saldirgan olmaz." +lore = ["Envanterinizde Ender Incisi varsa Endermen'ler saldirgan olmaz."] # seaborn [seaborn] [seaborn.oxygen] - name = "Organik Oksijen Tanki" - description = "Minik cigerlerinizde daha fazla oksijen tutun!" - lore1 = "Oksijen Kapasitesi Artisi" - lore = ["Oksijen Kapasitesi Artisi"] +name = "Organik Oksijen Tanki" +description = "Minik cigerlerinizde daha fazla oksijen tutun!" +lore1 = "Oksijen Kapasitesi Artisi" +lore = ["Oksijen Kapasitesi Artisi"] [seaborn.fishers_fantasy] - name = "Balcinin Hayali" - description = "Balikciliktan daha fazla XP kazanin ve daha fazla balik alin!" - lore1 = "Her seviye icin daha fazla XP ve Balik kazanma sansi var!" - lore = ["Her seviye icin daha fazla XP ve Balik kazanma sansi var!"] +name = "Balcinin Hayali" +description = "Balikciliktan daha fazla XP kazanin ve daha fazla balik alin!" +lore1 = "Her seviye icin daha fazla XP ve Balik kazanma sansi var!" +lore = ["Her seviye icin daha fazla XP ve Balik kazanma sansi var!"] [seaborn.haste] - name = "Kaplumbaga Madencisi" - description = "Su altinda madencilik yaparken acele kazanirsiniz!" - lore1 = "Su soluma etkiniz bittikten sonra su altinda madencilik yaparken Acele 3 uygulanir (AquaAffinity ile birikir)!" - lore = ["Su soluma etkiniz bittikten sonra su altinda madencilik yaparken Acele 3 uygulanir (AquaAffinity ile birikir)!"] +name = "Kaplumbaga Madencisi" +description = "Su altinda madencilik yaparken acele kazanirsiniz!" +lore1 = "Su soluma etkiniz bittikten sonra su altinda madencilik yaparken Acele 3 uygulanir (AquaAffinity ile birikir)!" +lore = ["Su soluma etkiniz bittikten sonra su altinda madencilik yaparken Acele 3 uygulanir (AquaAffinity ile birikir)!"] [seaborn.night_vision] - name = "Kaplumbaganin Gorusu" - description = "Su altindayken Gece Gorusu kazanirsiniz" - lore1 = "Su soluma etkiniz bittikten sonra su altindayken Gece Gorusu kazanin!" - lore = ["Su soluma etkiniz bittikten sonra su altindayken Gece Gorusu kazanin!"] +name = "Kaplumbaganin Gorusu" +description = "Su altindayken Gece Gorusu kazanirsiniz" +lore1 = "Su soluma etkiniz bittikten sonra su altindayken Gece Gorusu kazanin!" +lore = ["Su soluma etkiniz bittikten sonra su altindayken Gece Gorusu kazanin!"] [seaborn.dolphin_grace] - name = "Yunusun Zarfeti" - description = "Yunuslar olmadan bir yunus gibi yuzun" - lore1 = "+ Pasif: kazan " - lore2 = "x hiz (yunus zarafeti)" - lore3 = "hassas Alman muhendisligi- dur bu dogru degil... Derinlik Kosucusu ile uyumlu degil" - lore = ["+ Pasif: kazan ", "x hiz (yunus zarafeti)", "hassas Alman muhendisligi- dur bu dogru degil... Derinlik Kosucusu ile uyumlu degil"] +name = "Yunusun Zarfeti" +description = "Yunuslar olmadan bir yunus gibi yuzun" +lore1 = "+ Pasif: kazan " +lore2 = "x hiz (yunus zarafeti)" +lore3 = "hassas Alman muhendisligi- dur bu dogru degil... Derinlik Kosucusu ile uyumlu degil" +lore = ["+ Pasif: kazan ", "x hiz (yunus zarafeti)", "hassas Alman muhendisligi- dur bu dogru degil... Derinlik Kosucusu ile uyumlu degil"] # stealth [stealth] [stealth.ghost_armor] - name = "Hayaletin Zirhi" - description = "Hasar almadiginizda yavas yavas zirh birikir, 1 vurus surer" - lore1 = "Maksimum Zirh" - lore2 = "Hiz" - lore = ["Maksimum Zirh", "Hiz"] +name = "Hayaletin Zirhi" +description = "Hasar almadiginizda yavas yavas zirh birikir, 1 vurus surer" +lore1 = "Maksimum Zirh" +lore2 = "Hiz" +lore = ["Maksimum Zirh", "Hiz"] [stealth.night_vision] - name = "Gizlilik Gorusu" - description = "Egilirken gece gorusu kazanin" - lore1 = "Bir patlama kazanin " - lore2 = "gece gorusu" - lore3 = "egilirken" - lore = ["Bir patlama kazanin ", "gece gorusu", "egilirken"] +name = "Gizlilik Gorusu" +description = "Egilirken gece gorusu kazanin" +lore1 = "Bir patlama kazanin " +lore2 = "gece gorusu" +lore3 = "egilirken" +lore = ["Bir patlama kazanin ", "gece gorusu", "egilirken"] [stealth.snatch] - name = "Esya Kapma" - description = "Egilirken aninda dusen esyalari kapin!" - lore1 = "Kapma Yaricapi" - lore = ["Kapma Yaricapi"] +name = "Esya Kapma" +description = "Egilirken aninda dusen esyalari kapin!" +lore1 = "Kapma Yaricapi" +lore = ["Kapma Yaricapi"] [stealth.speed] - name = "Gizli Hiz" - description = "Egilirken hiz kazanin" - lore1 = "Egilme Hizi" - lore = ["Egilme Hizi"] +name = "Gizli Hiz" +description = "Egilirken hiz kazanin" +lore1 = "Egilme Hizi" +lore = ["Egilme Hizi"] [stealth.ender_veil] - name = "Ender Ortüsü" - description = "Enderman saldirilarina karsi artik Balkabagi takmak yok" - lore1 = "Egilirken enderman saldirilarini onleyin" - lore2 = "Tum enderman saldirilarini onleyin" - lore = ["Egilirken enderman saldirilarini onleyin", "Tum enderman saldirilarini onleyin"] +name = "Ender Ortüsü" +description = "Enderman saldirilarina karsi artik Balkabagi takmak yok" +lore1 = "Egilirken enderman saldirilarini onleyin" +lore2 = "Tum enderman saldirilarini onleyin" +lore = ["Egilirken enderman saldirilarini onleyin", "Tum enderman saldirilarini onleyin"] # sword [sword] [sword.machete] - name = "Pala" - description = "Bitki ortusunu kolayca kesin!" - lore1 = "Kesme Yaricapi" - lore2 = "Kesme Bekleme Suresi" - lore3 = "Alet Asinmasi" - lore = ["Kesme Yaricapi", "Kesme Bekleme Suresi", "Alet Asinmasi"] +name = "Pala" +description = "Bitki ortusunu kolayca kesin!" +lore1 = "Kesme Yaricapi" +lore2 = "Kesme Bekleme Suresi" +lore3 = "Alet Asinmasi" +lore = ["Kesme Yaricapi", "Kesme Bekleme Suresi", "Alet Asinmasi"] [sword.bloody_blade] - name = "Kanli Bicak" - description = "Kilic darbeleri Kanamaya neden olur!" - lore1 = "Kilicinizla canli bir varlga vurmak Kanamaya neden olur" - lore2 = "Kanama Suresi" - lore3 = "Kanama Bekleme Suresi" - lore = ["Kilicinizla canli bir varlga vurmak Kanamaya neden olur", "Kanama Suresi", "Kanama Bekleme Suresi"] +name = "Kanli Bicak" +description = "Kilic darbeleri Kanamaya neden olur!" +lore1 = "Kilicinizla canli bir varlga vurmak Kanamaya neden olur" +lore2 = "Kanama Suresi" +lore3 = "Kanama Bekleme Suresi" +lore = ["Kilicinizla canli bir varlga vurmak Kanamaya neden olur", "Kanama Suresi", "Kanama Bekleme Suresi"] [sword.poisoned_blade] - name = "Zehirli Bicak" - description = "Kilic darbeleri Zehire neden olur!" - lore1 = "Kilicinizla canli bir varliga vurmak Zehire neden olur" - lore2 = "Zehir Suresi" - lore3 = "Zehir Bekleme Suresi" - lore = ["Kilicinizla canli bir varliga vurmak Zehire neden olur", "Zehir Suresi", "Zehir Bekleme Suresi"] +name = "Zehirli Bicak" +description = "Kilic darbeleri Zehire neden olur!" +lore1 = "Kilicinizla canli bir varliga vurmak Zehire neden olur" +lore2 = "Zehir Suresi" +lore3 = "Zehir Bekleme Suresi" +lore = ["Kilicinizla canli bir varliga vurmak Zehire neden olur", "Zehir Suresi", "Zehir Bekleme Suresi"] # taming [taming] [taming.damage] - name = "Evcil Hasar" - description = "Evcillestirilmis hayvaninizin verdigi hasari artirin." - lore1 = "Arttirilmis Hasar" - lore = ["Arttirilmis Hasar"] +name = "Evcil Hasar" +description = "Evcillestirilmis hayvaninizin verdigi hasari artirin." +lore1 = "Arttirilmis Hasar" +lore = ["Arttirilmis Hasar"] [taming.health] - name = "Evcil Saglik" - description = "Evcillestirilmis hayvaninizin sagligini artirin." - lore1 = "Arttirilmis Saglik" - lore = ["Arttirilmis Saglik"] +name = "Evcil Saglik" +description = "Evcillestirilmis hayvaninizin sagligini artirin." +lore1 = "Arttirilmis Saglik" +lore = ["Arttirilmis Saglik"] [taming.regeneration] - name = "Evcil Yenilenme" - description = "Evcillestirilmis hayvaninizin yenilenmesini artirin." - lore1 = "HP/s" - lore = ["HP/s"] +name = "Evcil Yenilenme" +description = "Evcillestirilmis hayvaninizin yenilenmesini artirin." +lore1 = "HP/s" +lore = ["HP/s"] # tragoul [tragoul] [tragoul.thorns] - name = "Dikenler" - description = "Hasari saldirganiniza geri yansitin!" - lore1 = "Vuruldugunuzda misilleme edilen hasar" - lore = ["Vuruldugunuzda misilleme edilen hasar"] +name = "Dikenler" +description = "Hasari saldirganiniza geri yansitin!" +lore1 = "Vuruldugunuzda misilleme edilen hasar" +lore = ["Vuruldugunuzda misilleme edilen hasar"] [tragoul.globe] - name = "Aci Kuresi" - description = "Verdiginiz Hasari cevrenizdeki dusman sayisina gore bolun!" - lore1 = "Cevrenizdeki dusman sayisi arttikca her birine verdiginiz hasar azalir" - lore2 = "Menzil: " - lore3 = "Tum Varliklara Eklenen Hasar: " - lore = ["Cevrenizdeki dusman sayisi arttikca her birine verdiginiz hasar azalir", "Menzil: ", "Tum Varliklara Eklenen Hasar: "] +name = "Aci Kuresi" +description = "Verdiginiz Hasari cevrenizdeki dusman sayisina gore bolun!" +lore1 = "Cevrenizdeki dusman sayisi arttikca her birine verdiginiz hasar azalir" +lore2 = "Menzil: " +lore3 = "Tum Varliklara Eklenen Hasar: " +lore = ["Cevrenizdeki dusman sayisi arttikca her birine verdiginiz hasar azalir", "Menzil: ", "Tum Varliklara Eklenen Hasar: "] [tragoul.healing] - name = "Acinin Iradesi" - description = "Verdiginiz hasara gore saglik geri kazanin!" - lore1 = "Bir seylere zarar vermek hic bu kadar iyi hissettirmemisti! Verilen Hasardan Iyilesme" - lore2 = "Iyilestirme icin 3 Saniyelik bir hasar penceresi ve 1 Saniyelik bekleme suresi vardir " - lore3 = "Hasar Yuzdesine Gore Iyilestirme: " - lore = ["Bir seylere zarar vermek hic bu kadar iyi hissettirmemisti! Verilen Hasardan Iyilesme", "Iyilestirme icin 3 Saniyelik bir hasar penceresi ve 1 Saniyelik bekleme suresi vardir ", "Hasar Yuzdesine Gore Iyilestirme: "] +name = "Acinin Iradesi" +description = "Verdiginiz hasara gore saglik geri kazanin!" +lore1 = "Bir seylere zarar vermek hic bu kadar iyi hissettirmemisti! Verilen Hasardan Iyilesme" +lore2 = "Iyilestirme icin 3 Saniyelik bir hasar penceresi ve 1 Saniyelik bekleme suresi vardir " +lore3 = "Hasar Yuzdesine Gore Iyilestirme: " +lore = ["Bir seylere zarar vermek hic bu kadar iyi hissettirmemisti! Verilen Hasardan Iyilesme", "Iyilestirme icin 3 Saniyelik bir hasar penceresi ve 1 Saniyelik bekleme suresi vardir ", "Hasar Yuzdesine Gore Iyilestirme: "] [tragoul.lance] - name = "Ceset Mizraklari" - description = "Bir dusmani oldurmek veya bir yetenegin bir dusmani oldulmesi, yakindaki bir dusmana hasar veren bir mizrak olusturur!" - lore1 = "Mizraklar oldedugunuz her seyden VE bu yetenek bir dusmani oldurse cikacaktir." - lore2 = "Mizraklari yaratmak icin hayatinizin bir kismini feda edin (bu sizi oldurebilir)" - lore3 = "Maksimum Mizrak: 1 + " - lore = ["Mizraklar oldedugunuz her seyden VE bu yetenek bir dusmani oldurse cikacaktir.", "Mizraklari yaratmak icin hayatinizin bir kismini feda edin (bu sizi oldurebilir)", "Maksimum Mizrak: 1 + "] +name = "Ceset Mizraklari" +description = "Bir dusmani oldurmek veya bir yetenegin bir dusmani oldulmesi, yakindaki bir dusmana hasar veren bir mizrak olusturur!" +lore1 = "Mizraklar oldedugunuz her seyden VE bu yetenek bir dusmani oldurse cikacaktir." +lore2 = "Mizraklari yaratmak icin hayatinizin bir kismini feda edin (bu sizi oldurebilir)" +lore3 = "Maksimum Mizrak: 1 + " +lore = ["Mizraklar oldedugunuz her seyden VE bu yetenek bir dusmani oldurse cikacaktir.", "Mizraklari yaratmak icin hayatinizin bir kismini feda edin (bu sizi oldurebilir)", "Maksimum Mizrak: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Cam Top" - description = "Zirh degeriniz ne kadar dusukse o kadar fazla bonus Silahsiz Hasar" - lore1 = "x 0 zirhta hasar" - lore2 = "Seviye Basina Bonus Hasar" - lore = ["x 0 zirhta hasar", "Seviye Basina Bonus Hasar"] +name = "Cam Top" +description = "Zirh degeriniz ne kadar dusukse o kadar fazla bonus Silahsiz Hasar" +lore1 = "x 0 zirhta hasar" +lore2 = "Seviye Basina Bonus Hasar" +lore = ["x 0 zirhta hasar", "Seviye Basina Bonus Hasar"] [unarmed.power] - name = "Silahsiz Guc" - description = "Gelistirilmis Silahsiz Hasar" - lore1 = "Hasar" - lore = ["Hasar"] +name = "Silahsiz Guc" +description = "Gelistirilmis Silahsiz Hasar" +lore1 = "Hasar" +lore = ["Hasar"] [unarmed.sucker_punch] - name = "Sinsi Yumruk" - description = "Sprint yumruklari, ama daha olumcul." - lore1 = "Hasar" - lore2 = "Yumruk atarken hizinizla birlikte hasar artar" - lore = ["Hasar", "Yumruk atarken hizinizla birlikte hasar artar"] +name = "Sinsi Yumruk" +description = "Sprint yumruklari, ama daha olumcul." +lore1 = "Hasar" +lore2 = "Yumruk atarken hizinizla birlikte hasar artar" +lore = ["Hasar", "Yumruk atarken hizinizla birlikte hasar artar"] diff --git a/src/main/resources/vi_VI.toml b/src/main/resources/vi_VI.toml index 9c8d0b26d..ec4c1edd8 100644 --- a/src/main/resources/vi_VI.toml +++ b/src/main/resources/vi_VI.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "Phải Di Chuyển!" - description = "Đi bộ hơn 1 Km (1.000 khối)" +title = "Phải Di Chuyển!" +description = "Đi bộ hơn 1 Km (1.000 khối)" [advancement.challenge_sprint_5k] - title = "Chạy Nước Rút 5K!" - description = "Đi bộ hơn 5 Km (5.000 khối)" +title = "Chạy Nước Rút 5K!" +description = "Đi bộ hơn 5 Km (5.000 khối)" [advancement.challenge_sprint_50k] - title = "Phóng Nhanh 50K!" - description = "Đi bộ hơn 50 Km (50.000 khối)" +title = "Phóng Nhanh 50K!" +description = "Đi bộ hơn 50 Km (50.000 khối)" [advancement.challenge_sprint_500k] - title = "Xuyên Qua Vũ Trụ!!" - description = "Đi bộ hơn 500 Km (500.000 khối)" +title = "Xuyên Qua Vũ Trụ!!" +description = "Đi bộ hơn 500 Km (500.000 khối)" [advancement.challenge_sprint_marathon] - title = "Chạy Marathon (theo nghĩa đen)!" - description = "Chạy nước rút hơn 42.195 Khối!" +title = "Chạy Marathon (theo nghĩa đen)!" +description = "Chạy nước rút hơn 42.195 Khối!" [advancement.challenge_place_1k] - title = "Thợ Xây Tập Sự!" - description = "Đặt 1.000 Khối" +title = "Thợ Xây Tập Sự!" +description = "Đặt 1.000 Khối" [advancement.challenge_place_5k] - title = "Thợ Xây Trung Cấp!" - description = "Đặt 5.000 Khối" +title = "Thợ Xây Trung Cấp!" +description = "Đặt 5.000 Khối" [advancement.challenge_place_50k] - title = "Thợ Xây Cao Cấp!" - description = "Đặt 50.000 Khối" +title = "Thợ Xây Cao Cấp!" +description = "Đặt 50.000 Khối" [advancement.challenge_place_500k] - title = "Bậc Thầy Xây Dựng!" - description = "Đặt 500.000 Khối" +title = "Bậc Thầy Xây Dựng!" +description = "Đặt 500.000 Khối" [advancement.challenge_place_5m] - title = "Tín Đồ Của Đối Xứng!" - description = "THỰC TẠI LÀ SÂN CHƠI CỦA BẠN! (5 Triệu Khối)" +title = "Tín Đồ Của Đối Xứng!" +description = "THỰC TẠI LÀ SÂN CHƠI CỦA BẠN! (5 Triệu Khối)" [advancement.challenge_chop_1k] - title = "Tiều Phu Tập Sự!" - description = "Chặt 1.000 Khối" +title = "Tiều Phu Tập Sự!" +description = "Chặt 1.000 Khối" [advancement.challenge_chop_5k] - title = "Tiều Phu Trung Cấp!" - description = "Chặt 5.000 Khối" +title = "Tiều Phu Trung Cấp!" +description = "Chặt 5.000 Khối" [advancement.challenge_chop_50k] - title = "Tiều Phu Cao Cấp!" - description = "Chặt 50.000 Khối" +title = "Tiều Phu Cao Cấp!" +description = "Chặt 50.000 Khối" [advancement.challenge_chop_500k] - title = "Bậc Thầy Tiều Phu!" - description = "Chặt 500.000 Khối" +title = "Bậc Thầy Tiều Phu!" +description = "Chặt 500.000 Khối" [advancement.challenge_chop_5m] - title = "Chú Chó Jackson" - description = "Cậu bé ngoan nhất quả đất! (5 Triệu Khối)" +title = "Chú Chó Jackson" +description = "Cậu bé ngoan nhất quả đất! (5 Triệu Khối)" [advancement.challenge_block_1k] - title = "Mới Tập Đỡ Đòn!" - description = "Đỡ 1000 Đòn Đánh" +title = "Mới Tập Đỡ Đòn!" +description = "Đỡ 1000 Đòn Đánh" [advancement.challenge_block_5k] - title = "Đỡ Đòn Thật Vui!" - description = "Đỡ 5000 Đòn Đánh" +title = "Đỡ Đòn Thật Vui!" +description = "Đỡ 5000 Đòn Đánh" [advancement.challenge_block_50k] - title = "Đỡ Đòn Là Cuộc Đời Tôi!" - description = "Đỡ 50.000 Đòn Đánh" +title = "Đỡ Đòn Là Cuộc Đời Tôi!" +description = "Đỡ 50.000 Đòn Đánh" [advancement.challenge_block_500k] - title = "Đỡ Đòn Là Sứ Mệnh Của Tôi!" - description = "Đỡ 500.000 Đòn Đánh" +title = "Đỡ Đòn Là Sứ Mệnh Của Tôi!" +description = "Đỡ 500.000 Đòn Đánh" [advancement.challenge_block_5m] - title = "Die Hand Die Verletzt" - description = "Đỡ 5.000.000 Đòn Đánh" +title = "Die Hand Die Verletzt" +description = "Đỡ 5.000.000 Đòn Đánh" [advancement.challenge_brew_1k] - title = "Nhà Giả Kim Tập Sự!" - description = "Uống 1000 Bình Thuốc" +title = "Nhà Giả Kim Tập Sự!" +description = "Uống 1000 Bình Thuốc" [advancement.challenge_brew_5k] - title = "Nhà Giả Kim Trung Cấp!" - description = "Uống 5000 Bình Thuốc" +title = "Nhà Giả Kim Trung Cấp!" +description = "Uống 5000 Bình Thuốc" [advancement.challenge_brew_50k] - title = "Nhà Giả Kim Cao Cấp!" - description = "Uống 50.000 Bình Thuốc" +title = "Nhà Giả Kim Cao Cấp!" +description = "Uống 50.000 Bình Thuốc" [advancement.challenge_brew_500k] - title = "Bậc Thầy Giả Kim!" - description = "Uống 500.000 Bình Thuốc" +title = "Bậc Thầy Giả Kim!" +description = "Uống 500.000 Bình Thuốc" [advancement.challenge_brew_5m] - title = "Nhà Giả Kim Thuật" - description = "Uống 5.000.000 Bình Thuốc" +title = "Nhà Giả Kim Thuật" +description = "Uống 5.000.000 Bình Thuốc" [advancement.challenge_brewsplash_1k] - title = "Người Tạt Thuốc Tập Sự!" - description = "Tạt 1000 Bình Thuốc" +title = "Người Tạt Thuốc Tập Sự!" +description = "Tạt 1000 Bình Thuốc" [advancement.challenge_brewsplash_5k] - title = "Người Tạt Thuốc Trung Cấp!" - description = "Tạt 5000 Bình Thuốc" +title = "Người Tạt Thuốc Trung Cấp!" +description = "Tạt 5000 Bình Thuốc" [advancement.challenge_brewsplash_50k] - title = "Người Tạt Thuốc Cao Cấp!" - description = "Tạt 50.000 Bình Thuốc" +title = "Người Tạt Thuốc Cao Cấp!" +description = "Tạt 50.000 Bình Thuốc" [advancement.challenge_brewsplash_500k] - title = "Bậc Thầy Tạt Thuốc!" - description = "Tạt 500.000 Bình Thuốc" +title = "Bậc Thầy Tạt Thuốc!" +description = "Tạt 500.000 Bình Thuốc" [advancement.challenge_brewsplash_5m] - title = "Bậc Thầy Tạt Nước" - description = "Tạt 5.000.000 Bình Thuốc" +title = "Bậc Thầy Tạt Nước" +description = "Tạt 5.000.000 Bình Thuốc" [advancement.challenge_craft_1k] - title = "Thợ Thủ Công Khéo Tay!" - description = "Chế tạo 1000 Vật Phẩm" +title = "Thợ Thủ Công Khéo Tay!" +description = "Chế tạo 1000 Vật Phẩm" [advancement.challenge_craft_5k] - title = "Thợ Thủ Công Gắt Gỏng!" - description = "Chế tạo 5000 Vật Phẩm" +title = "Thợ Thủ Công Gắt Gỏng!" +description = "Chế tạo 5000 Vật Phẩm" [advancement.challenge_craft_50k] - title = "Thợ Thủ Công Cần Mẫn!" - description = "Chế tạo 50.000 Vật Phẩm" +title = "Thợ Thủ Công Cần Mẫn!" +description = "Chế tạo 50.000 Vật Phẩm" [advancement.challenge_craft_500k] - title = "Thợ Thủ Công Ầm Ĩ!" - description = "Chế tạo 500.000 Vật Phẩm" +title = "Thợ Thủ Công Ầm Ĩ!" +description = "Chế tạo 500.000 Vật Phẩm" [advancement.challenge_craft_5m] - title = "McCraftface Tai Họa" - description = "Chế tạo 5.000.000 Vật Phẩm" +title = "McCraftface Tai Họa" +description = "Chế tạo 5.000.000 Vật Phẩm" [advancement.challenge_enchant_1k] - title = "Phù Phép Sư Tập Sự!" - description = "Phù phép 1000 Vật Phẩm" +title = "Phù Phép Sư Tập Sự!" +description = "Phù phép 1000 Vật Phẩm" [advancement.challenge_enchant_5k] - title = "Phù Phép Sư Trung Cấp!" - description = "Phù phép 5000 Vật Phẩm" +title = "Phù Phép Sư Trung Cấp!" +description = "Phù phép 5000 Vật Phẩm" [advancement.challenge_enchant_50k] - title = "Phù Phép Sư Cao Cấp!" - description = "Phù phép 50.000 Vật Phẩm" +title = "Phù Phép Sư Cao Cấp!" +description = "Phù phép 50.000 Vật Phẩm" [advancement.challenge_enchant_500k] - title = "Bậc Thầy Phù Phép!" - description = "Phù phép 500.000 Vật Phẩm" +title = "Bậc Thầy Phù Phép!" +description = "Phù phép 500.000 Vật Phẩm" [advancement.challenge_enchant_5m] - title = "Phù Phép Sư Bí Ẩn" - description = "Phù phép 5.000.000 Vật Phẩm" +title = "Phù Phép Sư Bí Ẩn" +description = "Phù phép 5.000.000 Vật Phẩm" [advancement.challenge_excavate_1k] - title = "Thợ Đào Hào Hứng!" - description = "Đào 1000 Khối" +title = "Thợ Đào Hào Hứng!" +description = "Đào 1000 Khối" [advancement.challenge_excavate_5k] - title = "Thợ Đào Trung Cấp!" - description = "Đào 5000 Khối" +title = "Thợ Đào Trung Cấp!" +description = "Đào 5000 Khối" [advancement.challenge_excavate_50k] - title = "Thợ Đào Cao Cấp!" - description = "Đào 50.000 Khối" +title = "Thợ Đào Cao Cấp!" +description = "Đào 50.000 Khối" [advancement.challenge_excavate_500k] - title = "Bậc Thầy Đào Bới!" - description = "Đào 500.000 Khối" +title = "Bậc Thầy Đào Bới!" +description = "Đào 500.000 Khối" [advancement.challenge_excavate_5m] - title = "Thợ Đào Bí Ẩn" - description = "Đào 5.000.000 Khối" +title = "Thợ Đào Bí Ẩn" +description = "Đào 5.000.000 Khối" [advancement.horrible_person] - title = "Bạn Là Người Tệ Hại" - description = "Thật không thể tưởng tượng nổi" +title = "Bạn Là Người Tệ Hại" +description = "Thật không thể tưởng tượng nổi" [advancement.challenge_turtle_egg_smasher] - title = "Kẻ Đập Trứng Rùa!" - description = "Đập vỡ 100 quả trứng rùa" +title = "Kẻ Đập Trứng Rùa!" +description = "Đập vỡ 100 quả trứng rùa" [advancement.challenge_turtle_egg_annihilator] - title = "Kẻ Hủy Diệt Trứng Rùa!" - description = "Đập vỡ 500 quả trứng rùa" +title = "Kẻ Hủy Diệt Trứng Rùa!" +description = "Đập vỡ 500 quả trứng rùa" [advancement.challenge_novice_hunter] - title = "Thợ Săn Tập Sự!" - description = "Tiêu diệt 100 thực thể" +title = "Thợ Săn Tập Sự!" +description = "Tiêu diệt 100 thực thể" [advancement.challenge_intermediate_hunter] - title = "Thợ Săn Trung Cấp!" - description = "Tiêu diệt 500 thực thể" +title = "Thợ Săn Trung Cấp!" +description = "Tiêu diệt 500 thực thể" [advancement.challenge_advanced_hunter] - title = "Thợ Săn Cao Cấp!" - description = "Tiêu diệt 5000 thực thể" +title = "Thợ Săn Cao Cấp!" +description = "Tiêu diệt 5000 thực thể" [advancement.challenge_creeper_conqueror] - title = "Kẻ Chinh Phục Creeper!" - description = "Tiêu diệt 50 Creeper" +title = "Kẻ Chinh Phục Creeper!" +description = "Tiêu diệt 50 Creeper" [advancement.challenge_creeper_annihilator] - title = "Kẻ Hủy Diệt Creeper!" - description = "Tiêu diệt 200 Creeper" +title = "Kẻ Hủy Diệt Creeper!" +description = "Tiêu diệt 200 Creeper" [advancement.challenge_pickaxe_1k] - title = "Thợ Mỏ Tập Sự" - description = "Phá 1000 Khối" +title = "Thợ Mỏ Tập Sự" +description = "Phá 1000 Khối" [advancement.challenge_pickaxe_5k] - title = "Thợ Mỏ Lành Nghề" - description = "Phá 5000 Khối" +title = "Thợ Mỏ Lành Nghề" +description = "Phá 5000 Khối" [advancement.challenge_pickaxe_50k] - title = "Thợ Mỏ Chuyên Gia" - description = "Phá 50.000 Khối" +title = "Thợ Mỏ Chuyên Gia" +description = "Phá 50.000 Khối" [advancement.challenge_pickaxe_500k] - title = "Bậc Thầy Thợ Mỏ" - description = "Phá 500.000 Khối" +title = "Bậc Thầy Thợ Mỏ" +description = "Phá 500.000 Khối" [advancement.challenge_pickaxe_5m] - title = "Thợ Mỏ Huyền Thoại" - description = "Phá 5.000.000 Khối" +title = "Thợ Mỏ Huyền Thoại" +description = "Phá 5.000.000 Khối" [advancement.challenge_eat_100] - title = "Bao Nhiêu Thứ Để Ăn!" - description = "Ăn hơn 100 Vật Phẩm!" +title = "Bao Nhiêu Thứ Để Ăn!" +description = "Ăn hơn 100 Vật Phẩm!" [advancement.challenge_eat_1000] - title = "Cơn Đói Không Thể Dập Tắt!" - description = "Ăn hơn 1.000 Vật Phẩm!" +title = "Cơn Đói Không Thể Dập Tắt!" +description = "Ăn hơn 1.000 Vật Phẩm!" [advancement.challenge_eat_10000] - title = "CƠN ĐÓI VĨNH CỬU!" - description = "Ăn hơn 10.000 Vật Phẩm!" +title = "CƠN ĐÓI VĨNH CỬU!" +description = "Ăn hơn 10.000 Vật Phẩm!" [advancement.challenge_harvest_100] - title = "Mùa Thu Hoạch Đầy Đủ" - description = "Thu hoạch hơn 100 cây trồng!" +title = "Mùa Thu Hoạch Đầy Đủ" +description = "Thu hoạch hơn 100 cây trồng!" [advancement.challenge_harvest_1000] - title = "Mùa Thu Hoạch Vĩ Đại" - description = "Thu hoạch hơn 1.000 cây trồng!" +title = "Mùa Thu Hoạch Vĩ Đại" +description = "Thu hoạch hơn 1.000 cây trồng!" [advancement.challenge_swim_1nm] - title = "Tàu Ngầm Người!" - description = "Bơi 1 Hải Lý (1.852 khối)" +title = "Tàu Ngầm Người!" +description = "Bơi 1 Hải Lý (1.852 khối)" [advancement.challenge_sneak_1k] - title = "Đau Đầu Gối" - description = "Rón rén hơn một km (1.000 khối)" +title = "Đau Đầu Gối" +description = "Rón rén hơn một km (1.000 khối)" [advancement.challenge_sneak_5k] - title = "Kẻ Đi Trong Bóng Tối" - description = "Rón rén hơn 5.000 khối" +title = "Kẻ Đi Trong Bóng Tối" +description = "Rón rén hơn 5.000 khối" [advancement.challenge_sneak_20k] - title = "Bóng Ma" - description = "Rón rén hơn 20.000 khối" +title = "Bóng Ma" +description = "Rón rén hơn 20.000 khối" [advancement.challenge_swim_5k] - title = "Thợ Lặn Sâu" - description = "Bơi hơn 5.000 khối" +title = "Thợ Lặn Sâu" +description = "Bơi hơn 5.000 khối" [advancement.challenge_swim_20k] - title = "Người Được Poseidon Chọn" - description = "Bơi hơn 20.000 khối" +title = "Người Được Poseidon Chọn" +description = "Bơi hơn 20.000 khối" [advancement.challenge_sword_100] - title = "Giọt Máu Đầu Tiên" - description = "Chém 100 nhát bằng kiếm" +title = "Giọt Máu Đầu Tiên" +description = "Chém 100 nhát bằng kiếm" [advancement.challenge_sword_1k] - title = "Vũ Công Lưỡi Kiếm" - description = "Chém 1.000 nhát bằng kiếm" +title = "Vũ Công Lưỡi Kiếm" +description = "Chém 1.000 nhát bằng kiếm" [advancement.challenge_sword_10k] - title = "Ngàn Nhát Chém" - description = "Chém 10.000 nhát bằng kiếm" +title = "Ngàn Nhát Chém" +description = "Chém 10.000 nhát bằng kiếm" [advancement.challenge_unarmed_100] - title = "Tay Đấm Quán Rượu" - description = "Đấm 100 cú tay không" +title = "Tay Đấm Quán Rượu" +description = "Đấm 100 cú tay không" [advancement.challenge_unarmed_1k] - title = "Nắm Đấm Thép" - description = "Đấm 1.000 cú tay không" +title = "Nắm Đấm Thép" +description = "Đấm 1.000 cú tay không" [advancement.challenge_unarmed_10k] - title = "Một Cú Đấm" - description = "Đấm 10.000 cú tay không" +title = "Một Cú Đấm" +description = "Đấm 10.000 cú tay không" [advancement.challenge_trag_1k] - title = "Giá Máu" - description = "Nhận 1.000 sát thương" +title = "Giá Máu" +description = "Nhận 1.000 sát thương" [advancement.challenge_trag_10k] - title = "Thủy Triều Đỏ" - description = "Nhận 10.000 sát thương" +title = "Thủy Triều Đỏ" +description = "Nhận 10.000 sát thương" [advancement.challenge_trag_100k] - title = "Hóa Thân Của Đau Khổ" - description = "Nhận 100.000 sát thương" +title = "Hóa Thân Của Đau Khổ" +description = "Nhận 100.000 sát thương" [advancement.challenge_ranged_100] - title = "Tập Bắn" - description = "Bắn 100 đạn" +title = "Tập Bắn" +description = "Bắn 100 đạn" [advancement.challenge_ranged_1k] - title = "Mắt Đại Bàng" - description = "Bắn 1.000 đạn" +title = "Mắt Đại Bàng" +description = "Bắn 1.000 đạn" [advancement.challenge_ranged_10k] - title = "Bão Tên" - description = "Bắn 10.000 đạn" +title = "Bão Tên" +description = "Bắn 10.000 đạn" [advancement.challenge_chronos_1h] - title = "Tích Tắc" - description = "Chơi trực tuyến 1 giờ" +title = "Tích Tắc" +description = "Chơi trực tuyến 1 giờ" [advancement.challenge_chronos_24h] - title = "Cát Thời Gian" - description = "Chơi trực tuyến 24 giờ" +title = "Cát Thời Gian" +description = "Chơi trực tuyến 24 giờ" [advancement.challenge_chronos_168h] - title = "Vượt Thời Gian" - description = "Chơi trực tuyến 168 giờ (1 tuần)" +title = "Vượt Thời Gian" +description = "Chơi trực tuyến 168 giờ (1 tuần)" [advancement.challenge_nether_50] - title = "Người Gác Cổng Địa Ngục" - description = "Giết 50 sinh vật Nether" +title = "Người Gác Cổng Địa Ngục" +description = "Giết 50 sinh vật Nether" [advancement.challenge_nether_500] - title = "Lính Canh Vực Thẳm" - description = "Giết 500 sinh vật Nether" +title = "Lính Canh Vực Thẳm" +description = "Giết 500 sinh vật Nether" [advancement.challenge_nether_5k] - title = "Chúa Tể Nether" - description = "Giết 5.000 sinh vật Nether" +title = "Chúa Tể Nether" +description = "Giết 5.000 sinh vật Nether" [advancement.challenge_rift_50] - title = "Dị Thường Không Gian" - description = "Dịch chuyển 50 lần" +title = "Dị Thường Không Gian" +description = "Dịch chuyển 50 lần" [advancement.challenge_rift_500] - title = "Kẻ Đi Trong Hư Không" - description = "Dịch chuyển 500 lần" +title = "Kẻ Đi Trong Hư Không" +description = "Dịch chuyển 500 lần" [advancement.challenge_rift_5k] - title = "Giữa Các Thế Giới" - description = "Dịch chuyển 5.000 lần" +title = "Giữa Các Thế Giới" +description = "Dịch chuyển 5.000 lần" [advancement.challenge_taming_10] - title = "Người Thì Thầm Với Thú" - description = "Nhân giống 10 động vật" +title = "Người Thì Thầm Với Thú" +description = "Nhân giống 10 động vật" [advancement.challenge_taming_50] - title = "Thủ Lĩnh Bầy" - description = "Nhân giống 50 động vật" +title = "Thủ Lĩnh Bầy" +description = "Nhân giống 50 động vật" [advancement.challenge_taming_500] - title = "Chúa Tể Muông Thú" - description = "Nhân giống 500 động vật" +title = "Chúa Tể Muông Thú" +description = "Nhân giống 500 động vật" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "Ác Quỷ Tốc Độ" - description = "Chạy nước rút hơn 5 Kilômét (5,000 khối)" +title = "Ác Quỷ Tốc Độ" +description = "Chạy nước rút hơn 5 Kilômét (5,000 khối)" [advancement.challenge_sprint_dist_50k] - title = "Chân Sấm Sét" - description = "Chạy nước rút hơn 50 Kilômét (50,000 khối)" +title = "Chân Sấm Sét" +description = "Chạy nước rút hơn 50 Kilômét (50,000 khối)" [advancement.challenge_agility_swim_1k] - title = "Người Lướt Nước" - description = "Bơi hơn 1 Kilômét (1,000 khối)" +title = "Người Lướt Nước" +description = "Bơi hơn 1 Kilômét (1,000 khối)" [advancement.challenge_agility_swim_10k] - title = "Nhà Hàng Hải" - description = "Bơi hơn 10 Kilômét (10,000 khối)" +title = "Nhà Hàng Hải" +description = "Bơi hơn 10 Kilômét (10,000 khối)" [advancement.challenge_fly_1k] - title = "Vũ Công Bầu Trời" - description = "Bay hơn 1 Kilômét (1,000 khối)" +title = "Vũ Công Bầu Trời" +description = "Bay hơn 1 Kilômét (1,000 khối)" [advancement.challenge_fly_10k] - title = "Kỵ Sĩ Gió" - description = "Bay hơn 10 Kilômét (10,000 khối)" +title = "Kỵ Sĩ Gió" +description = "Bay hơn 10 Kilômét (10,000 khối)" [advancement.challenge_agility_sneak_500] - title = "Bước Chân Nhẹ" - description = "Lén lút hơn 500 khối" +title = "Bước Chân Nhẹ" +description = "Lén lút hơn 500 khối" [advancement.challenge_agility_sneak_5k] - title = "Bước Chân Ma" - description = "Lén lút hơn 5 Kilômét (5,000 khối)" +title = "Bước Chân Ma" +description = "Lén lút hơn 5 Kilômét (5,000 khối)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "Đội Phá Hủy" - description = "Phá 500 khối" +title = "Đội Phá Hủy" +description = "Phá 500 khối" [advancement.challenge_demolish_5k] - title = "Quả Cầu Phá Hủy" - description = "Phá 5,000 khối" +title = "Quả Cầu Phá Hủy" +description = "Phá 5,000 khối" [advancement.challenge_value_placed_10k] - title = "Thợ Xây Giá Trị" - description = "Đặt khối có giá trị 10,000" +title = "Thợ Xây Giá Trị" +description = "Đặt khối có giá trị 10,000" [advancement.challenge_value_placed_100k] - title = "Bậc Thầy Kiến Trúc" - description = "Đặt khối có giá trị 100,000" +title = "Bậc Thầy Kiến Trúc" +description = "Đặt khối có giá trị 100,000" [advancement.challenge_demolish_val_5k] - title = "Chuyên Gia Thu Hồi" - description = "Thu hồi 5,000 giá trị khối từ phá hủy" +title = "Chuyên Gia Thu Hồi" +description = "Thu hồi 5,000 giá trị khối từ phá hủy" [advancement.challenge_demolish_val_50k] - title = "Tháo Dỡ Hoàn Toàn" - description = "Thu hồi 50,000 giá trị khối từ phá hủy" +title = "Tháo Dỡ Hoàn Toàn" +description = "Thu hồi 50,000 giá trị khối từ phá hủy" [advancement.challenge_high_build_100] - title = "Thợ Xây Trên Cao" - description = "Đặt 100 khối trên Y=128" +title = "Thợ Xây Trên Cao" +description = "Đặt 100 khối trên Y=128" [advancement.challenge_high_build_1k] - title = "Kiến Trúc Sư Mây" - description = "Đặt 1,000 khối trên Y=128" +title = "Kiến Trúc Sư Mây" +description = "Đặt 1,000 khối trên Y=128" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "Người Vung Rìu" - description = "Vung rìu 500 lần" +title = "Người Vung Rìu" +description = "Vung rìu 500 lần" [advancement.challenge_axe_swing_5k] - title = "Cuồng Chiến Binh" - description = "Vung rìu 5,000 lần" +title = "Cuồng Chiến Binh" +description = "Vung rìu 5,000 lần" [advancement.challenge_axe_damage_1k] - title = "Người Chẻ" - description = "Gây 1,000 sát thương bằng rìu" +title = "Người Chẻ" +description = "Gây 1,000 sát thương bằng rìu" [advancement.challenge_axe_damage_10k] - title = "Rìu Đao Phủ" - description = "Gây 10,000 sát thương bằng rìu" +title = "Rìu Đao Phủ" +description = "Gây 10,000 sát thương bằng rìu" [advancement.challenge_axe_value_5k] - title = "Thương Nhân Gỗ" - description = "Thu hoạch gỗ trị giá 5,000" +title = "Thương Nhân Gỗ" +description = "Thu hoạch gỗ trị giá 5,000" [advancement.challenge_axe_value_50k] - title = "Trùm Khai Thác Gỗ" - description = "Thu hoạch gỗ trị giá 50,000" +title = "Trùm Khai Thác Gỗ" +description = "Thu hoạch gỗ trị giá 50,000" [advancement.challenge_leaves_500] - title = "Máy Thổi Lá" - description = "Dọn 500 khối lá bằng rìu" +title = "Máy Thổi Lá" +description = "Dọn 500 khối lá bằng rìu" [advancement.challenge_leaves_5k] - title = "Người Rụng Lá" - description = "Dọn 5,000 khối lá bằng rìu" +title = "Người Rụng Lá" +description = "Dọn 5,000 khối lá bằng rìu" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "Người Hấp Thụ Sát Thương" - description = "Chặn 1,000 sát thương bằng khiên" +title = "Người Hấp Thụ Sát Thương" +description = "Chặn 1,000 sát thương bằng khiên" [advancement.challenge_block_dmg_10k] - title = "Khiên Sống" - description = "Chặn 10,000 sát thương bằng khiên" +title = "Khiên Sống" +description = "Chặn 10,000 sát thương bằng khiên" [advancement.challenge_block_proj_100] - title = "Người Đỡ Tên" - description = "Chặn 100 đạn bằng khiên" +title = "Người Đỡ Tên" +description = "Chặn 100 đạn bằng khiên" [advancement.challenge_block_proj_1k] - title = "Khiên Chống Đạn" - description = "Chặn 1,000 đạn bằng khiên" +title = "Khiên Chống Đạn" +description = "Chặn 1,000 đạn bằng khiên" [advancement.challenge_block_melee_500] - title = "Bậc Thầy Đỡ Đòn" - description = "Chặn 500 đòn cận chiến bằng khiên" +title = "Bậc Thầy Đỡ Đòn" +description = "Chặn 500 đòn cận chiến bằng khiên" [advancement.challenge_block_melee_5k] - title = "Pháo Đài Sắt" - description = "Chặn 5,000 đòn cận chiến bằng khiên" +title = "Pháo Đài Sắt" +description = "Chặn 5,000 đòn cận chiến bằng khiên" [advancement.challenge_block_heavy_50] - title = "Xe Tăng" - description = "Chặn 50 đòn nặng (trên 5 sát thương)" +title = "Xe Tăng" +description = "Chặn 50 đòn nặng (trên 5 sát thương)" [advancement.challenge_block_heavy_500] - title = "Vật Bất Di" - description = "Chặn 500 đòn nặng (trên 5 sát thương)" +title = "Vật Bất Di" +description = "Chặn 500 đòn nặng (trên 5 sát thương)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "Xưởng Pha Chế" - description = "Đặt 10 giá pha chế" +title = "Xưởng Pha Chế" +description = "Đặt 10 giá pha chế" [advancement.challenge_brew_stands_50] - title = "Nhà Máy Thuốc" - description = "Đặt 50 giá pha chế" +title = "Nhà Máy Thuốc" +description = "Đặt 50 giá pha chế" [advancement.challenge_brew_strong_25] - title = "Thuốc Mạnh" - description = "Uống 25 thuốc nâng cấp" +title = "Thuốc Mạnh" +description = "Uống 25 thuốc nâng cấp" [advancement.challenge_brew_strong_250] - title = "Công Lực Tối Đa" - description = "Uống 250 thuốc nâng cấp" +title = "Công Lực Tối Đa" +description = "Uống 250 thuốc nâng cấp" [advancement.challenge_brew_splash_hits_50] - title = "Vùng Bắn Tung Tóe" - description = "Trúng 50 thực thể bằng thuốc bắn tung tóe" +title = "Vùng Bắn Tung Tóe" +description = "Trúng 50 thực thể bằng thuốc bắn tung tóe" [advancement.challenge_brew_splash_hits_500] - title = "Thầy Thuốc Dịch Bệnh" - description = "Trúng 500 thực thể bằng thuốc bắn tung tóe" +title = "Thầy Thuốc Dịch Bệnh" +description = "Trúng 500 thực thể bằng thuốc bắn tung tóe" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "Không Yên" - description = "Di chuyển 1 Kilômét khi đang hoạt động" +title = "Không Yên" +description = "Di chuyển 1 Kilômét khi đang hoạt động" [advancement.challenge_active_dist_10k] - title = "Người Dẫn Đường" - description = "Di chuyển 10 Kilômét khi đang hoạt động" +title = "Người Dẫn Đường" +description = "Di chuyển 10 Kilômét khi đang hoạt động" [advancement.challenge_active_dist_100k] - title = "Chuyển Động Vĩnh Cửu" - description = "Di chuyển 100 Kilômét khi đang hoạt động" +title = "Chuyển Động Vĩnh Cửu" +description = "Di chuyển 100 Kilômét khi đang hoạt động" [advancement.challenge_beds_10] - title = "Người Dậy Sớm" - description = "Ngủ trên giường 10 lần" +title = "Người Dậy Sớm" +description = "Ngủ trên giường 10 lần" [advancement.challenge_beds_100] - title = "Người Bỏ Qua Thời Gian" - description = "Ngủ trên giường 100 lần" +title = "Người Bỏ Qua Thời Gian" +description = "Ngủ trên giường 100 lần" [advancement.challenge_chronos_tp_50] - title = "Dịch Chuyển Thời Gian" - description = "Dịch chuyển tức thời 50 lần" +title = "Dịch Chuyển Thời Gian" +description = "Dịch chuyển tức thời 50 lần" [advancement.challenge_chronos_tp_500] - title = "Xoắn Thời Gian" - description = "Dịch chuyển tức thời 500 lần" +title = "Xoắn Thời Gian" +description = "Dịch chuyển tức thời 500 lần" [advancement.challenge_chronos_deaths_10] - title = "Phàm Nhân" - description = "Chết 10 lần" +title = "Phàm Nhân" +description = "Chết 10 lần" [advancement.challenge_chronos_deaths_100] - title = "Người Thách Thức Tử Thần" - description = "Chết 100 lần" +title = "Người Thách Thức Tử Thần" +description = "Chết 100 lần" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "Giá Trị Chế Tạo" - description = "Chế tạo vật phẩm có tổng giá trị 10,000" +title = "Giá Trị Chế Tạo" +description = "Chế tạo vật phẩm có tổng giá trị 10,000" [advancement.challenge_craft_value_100k] - title = "Nghệ Nhân" - description = "Chế tạo vật phẩm có tổng giá trị 100,000" +title = "Nghệ Nhân" +description = "Chế tạo vật phẩm có tổng giá trị 100,000" [advancement.challenge_craft_tools_25] - title = "Thợ Rèn Dụng Cụ" - description = "Chế tạo 25 dụng cụ" +title = "Thợ Rèn Dụng Cụ" +description = "Chế tạo 25 dụng cụ" [advancement.challenge_craft_tools_250] - title = "Bậc Thầy Lò Rèn" - description = "Chế tạo 250 dụng cụ" +title = "Bậc Thầy Lò Rèn" +description = "Chế tạo 250 dụng cụ" [advancement.challenge_craft_armor_25] - title = "Thợ Rèn Giáp" - description = "Chế tạo 25 mảnh giáp" +title = "Thợ Rèn Giáp" +description = "Chế tạo 25 mảnh giáp" [advancement.challenge_craft_armor_250] - title = "Bậc Thầy Giáp" - description = "Chế tạo 250 mảnh giáp" +title = "Bậc Thầy Giáp" +description = "Chế tạo 250 mảnh giáp" [advancement.challenge_craft_mass_25k] - title = "Sản Xuất Hàng Loạt" - description = "Chế tạo 25,000 vật phẩm" +title = "Sản Xuất Hàng Loạt" +description = "Chế tạo 25,000 vật phẩm" [advancement.challenge_craft_mass_250k] - title = "Cách Mạng Công Nghiệp" - description = "Chế tạo 250,000 vật phẩm" +title = "Cách Mạng Công Nghiệp" +description = "Chế tạo 250,000 vật phẩm" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "Nhà Sưu Tập" - description = "Khám phá 50 vật phẩm độc nhất" +title = "Nhà Sưu Tập" +description = "Khám phá 50 vật phẩm độc nhất" [advancement.challenge_discover_items_250] - title = "Người Lập Danh Mục" - description = "Khám phá 250 vật phẩm độc nhất" +title = "Người Lập Danh Mục" +description = "Khám phá 250 vật phẩm độc nhất" [advancement.challenge_discover_blocks_50] - title = "Nhà Khảo Sát" - description = "Khám phá 50 khối độc nhất" +title = "Nhà Khảo Sát" +description = "Khám phá 50 khối độc nhất" [advancement.challenge_discover_blocks_250] - title = "Nhà Địa Chất" - description = "Khám phá 250 khối độc nhất" +title = "Nhà Địa Chất" +description = "Khám phá 250 khối độc nhất" [advancement.challenge_discover_mobs_25] - title = "Người Quan Sát" - description = "Khám phá 25 sinh vật độc nhất" +title = "Người Quan Sát" +description = "Khám phá 25 sinh vật độc nhất" [advancement.challenge_discover_mobs_75] - title = "Nhà Tự Nhiên Học" - description = "Khám phá 75 sinh vật độc nhất" +title = "Nhà Tự Nhiên Học" +description = "Khám phá 75 sinh vật độc nhất" [advancement.challenge_discover_biomes_10] - title = "Kẻ Lang Thang" - description = "Khám phá 10 quần xã sinh vật độc nhất" +title = "Kẻ Lang Thang" +description = "Khám phá 10 quần xã sinh vật độc nhất" [advancement.challenge_discover_biomes_40] - title = "Nhà Du Hành Thế Giới" - description = "Khám phá 40 quần xã sinh vật độc nhất" +title = "Nhà Du Hành Thế Giới" +description = "Khám phá 40 quần xã sinh vật độc nhất" [advancement.challenge_discover_foods_10] - title = "Người Sành Ăn" - description = "Khám phá 10 thực phẩm độc nhất" +title = "Người Sành Ăn" +description = "Khám phá 10 thực phẩm độc nhất" [advancement.challenge_discover_foods_30] - title = "Bậc Thầy Ẩm Thực" - description = "Khám phá 30 thực phẩm độc nhất" +title = "Bậc Thầy Ẩm Thực" +description = "Khám phá 30 thực phẩm độc nhất" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "Người Dệt Sức Mạnh" - description = "Tích lũy 100 sức mạnh phù phép" +title = "Người Dệt Sức Mạnh" +description = "Tích lũy 100 sức mạnh phù phép" [advancement.challenge_enchant_power_1k] - title = "Bậc Thầy Huyền Thuật" - description = "Tích lũy 1,000 sức mạnh phù phép" +title = "Bậc Thầy Huyền Thuật" +description = "Tích lũy 1,000 sức mạnh phù phép" [advancement.challenge_enchant_levels_1k] - title = "Người Tiêu Cấp" - description = "Chi 1,000 cấp kinh nghiệm cho phù phép" +title = "Người Tiêu Cấp" +description = "Chi 1,000 cấp kinh nghiệm cho phù phép" [advancement.challenge_enchant_levels_10k] - title = "Hố Đen XP" - description = "Chi 10,000 cấp kinh nghiệm cho phù phép" +title = "Hố Đen XP" +description = "Chi 10,000 cấp kinh nghiệm cho phù phép" [advancement.challenge_enchant_high_25] - title = "Tay Chơi Lớn" - description = "Thực hiện 25 phù phép cấp tối đa" +title = "Tay Chơi Lớn" +description = "Thực hiện 25 phù phép cấp tối đa" [advancement.challenge_enchant_high_250] - title = "Phù Thủy Huyền Thoại" - description = "Thực hiện 250 phù phép cấp tối đa" +title = "Phù Thủy Huyền Thoại" +description = "Thực hiện 250 phù phép cấp tối đa" [advancement.challenge_enchant_total_500] - title = "Người Đốt Cấp" - description = "Chi tổng cộng 500 cấp cho tất cả phù phép" +title = "Người Đốt Cấp" +description = "Chi tổng cộng 500 cấp cho tất cả phù phép" [advancement.challenge_enchant_total_5k] - title = "Đầu Tư Huyền Bí" - description = "Chi tổng cộng 5,000 cấp cho tất cả phù phép" +title = "Đầu Tư Huyền Bí" +description = "Chi tổng cộng 5,000 cấp cho tất cả phù phép" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "Người Đào" - description = "Vung xẻng 500 lần" +title = "Người Đào" +description = "Vung xẻng 500 lần" [advancement.challenge_dig_swing_5k] - title = "Máy Xúc" - description = "Vung xẻng 5,000 lần" +title = "Máy Xúc" +description = "Vung xẻng 5,000 lần" [advancement.challenge_dig_damage_1k] - title = "Hiệp Sĩ Xẻng" - description = "Gây 1,000 sát thương bằng xẻng" +title = "Hiệp Sĩ Xẻng" +description = "Gây 1,000 sát thương bằng xẻng" [advancement.challenge_dig_damage_10k] - title = "Bậc Thầy Xẻng" - description = "Gây 10,000 sát thương bằng xẻng" +title = "Bậc Thầy Xẻng" +description = "Gây 10,000 sát thương bằng xẻng" [advancement.challenge_dig_value_5k] - title = "Thương Nhân Đất" - description = "Đào khối trị giá 5,000" +title = "Thương Nhân Đất" +description = "Đào khối trị giá 5,000" [advancement.challenge_dig_value_50k] - title = "Trùm Đất Đai" - description = "Đào khối trị giá 50,000" +title = "Trùm Đất Đai" +description = "Đào khối trị giá 50,000" [advancement.challenge_dig_gravel_500] - title = "Người Nghiền Sỏi" - description = "Đào 500 khối sỏi, cát hoặc đất sét" +title = "Người Nghiền Sỏi" +description = "Đào 500 khối sỏi, cát hoặc đất sét" [advancement.challenge_dig_gravel_5k] - title = "Người Sàng Cát" - description = "Đào 5,000 khối sỏi, cát hoặc đất sét" +title = "Người Sàng Cát" +description = "Đào 5,000 khối sỏi, cát hoặc đất sét" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "Người Gieo Hạt" - description = "Trồng 100 cây trồng" +title = "Người Gieo Hạt" +description = "Trồng 100 cây trồng" [advancement.challenge_plant_1k] - title = "Ngón Tay Xanh" - description = "Trồng 1,000 cây trồng" +title = "Ngón Tay Xanh" +description = "Trồng 1,000 cây trồng" [advancement.challenge_plant_5k] - title = "Trùm Nông Nghiệp" - description = "Trồng 5,000 cây trồng" +title = "Trùm Nông Nghiệp" +description = "Trồng 5,000 cây trồng" [advancement.challenge_compost_50] - title = "Người Tái Chế" - description = "Ủ phân 50 vật phẩm" +title = "Người Tái Chế" +description = "Ủ phân 50 vật phẩm" [advancement.challenge_compost_500] - title = "Người Bồi Dưỡng Đất" - description = "Ủ phân 500 vật phẩm" +title = "Người Bồi Dưỡng Đất" +description = "Ủ phân 500 vật phẩm" [advancement.challenge_shear_50] - title = "Người Xén Lông" - description = "Xén 50 thực thể" +title = "Người Xén Lông" +description = "Xén 50 thực thể" [advancement.challenge_shear_250] - title = "Chủ Đàn" - description = "Xén 250 thực thể" +title = "Chủ Đàn" +description = "Xén 250 thực thể" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "Sát Thủ" - description = "Tiêu diệt 500 sinh vật" +title = "Sát Thủ" +description = "Tiêu diệt 500 sinh vật" [advancement.challenge_kills_5k] - title = "Đao Phủ" - description = "Tiêu diệt 5,000 sinh vật" +title = "Đao Phủ" +description = "Tiêu diệt 5,000 sinh vật" [advancement.challenge_boss_1] - title = "Người Thách Đấu Boss" - description = "Tiêu diệt một boss" +title = "Người Thách Đấu Boss" +description = "Tiêu diệt một boss" [advancement.challenge_boss_10] - title = "Sát Thủ Huyền Thoại" - description = "Tiêu diệt 10 boss" +title = "Sát Thủ Huyền Thoại" +description = "Tiêu diệt 10 boss" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "Héo Tàn" - description = "Chịu đựng 500 sát thương héo tàn" +title = "Héo Tàn" +description = "Chịu đựng 500 sát thương héo tàn" [advancement.challenge_wither_dmg_5k] - title = "Người Sống Sót Dịch Bệnh" - description = "Chịu đựng 5,000 sát thương héo tàn" +title = "Người Sống Sót Dịch Bệnh" +description = "Chịu đựng 5,000 sát thương héo tàn" [advancement.challenge_wither_skel_25] - title = "Người Thu Xương" - description = "Tiêu diệt 25 bộ xương wither" +title = "Người Thu Xương" +description = "Tiêu diệt 25 bộ xương wither" [advancement.challenge_wither_skel_250] - title = "Tai Ương Bộ Xương" - description = "Tiêu diệt 250 bộ xương wither" +title = "Tai Ương Bộ Xương" +description = "Tiêu diệt 250 bộ xương wither" [advancement.challenge_wither_boss_1] - title = "Diệt Wither" - description = "Đánh bại Wither" +title = "Diệt Wither" +description = "Đánh bại Wither" [advancement.challenge_wither_boss_10] - title = "Bá Chủ Nether" - description = "Đánh bại Wither 10 lần" +title = "Bá Chủ Nether" +description = "Đánh bại Wither 10 lần" [advancement.challenge_roses_10] - title = "Người Làm Vườn Tử Thần" - description = "Phá 10 hoa wither" +title = "Người Làm Vườn Tử Thần" +description = "Phá 10 hoa wither" [advancement.challenge_roses_100] - title = "Người Thu Hoạch Dịch Bệnh" - description = "Phá 100 hoa wither" +title = "Người Thu Hoạch Dịch Bệnh" +description = "Phá 100 hoa wither" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "Cánh Tay Thợ Mỏ" - description = "Vung cuốc 500 lần" +title = "Cánh Tay Thợ Mỏ" +description = "Vung cuốc 500 lần" [advancement.challenge_pick_swing_5k] - title = "Người Đào Hầm" - description = "Vung cuốc 5,000 lần" +title = "Người Đào Hầm" +description = "Vung cuốc 5,000 lần" [advancement.challenge_pick_damage_1k] - title = "Chiến Binh Cuốc" - description = "Gây 1,000 sát thương bằng cuốc" +title = "Chiến Binh Cuốc" +description = "Gây 1,000 sát thương bằng cuốc" [advancement.challenge_pick_damage_10k] - title = "Cuốc Chiến Tranh" - description = "Gây 10,000 sát thương bằng cuốc" +title = "Cuốc Chiến Tranh" +description = "Gây 10,000 sát thương bằng cuốc" [advancement.challenge_pick_value_5k] - title = "Người Tìm Đá Quý" - description = "Đào khối trị giá 5,000" +title = "Người Tìm Đá Quý" +description = "Đào khối trị giá 5,000" [advancement.challenge_pick_value_50k] - title = "Trùm Quặng" - description = "Đào khối trị giá 50,000" +title = "Trùm Quặng" +description = "Đào khối trị giá 50,000" [advancement.challenge_pick_ores_500] - title = "Người Thăm Dò" - description = "Đào 500 khối quặng" +title = "Người Thăm Dò" +description = "Đào 500 khối quặng" [advancement.challenge_pick_ores_5k] - title = "Thợ Mỏ Bậc Thầy" - description = "Đào 5,000 khối quặng" +title = "Thợ Mỏ Bậc Thầy" +description = "Đào 5,000 khối quặng" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "Xạ Thủ Thiện Xạ" - description = "Gây 1,000 sát thương tầm xa" +title = "Xạ Thủ Thiện Xạ" +description = "Gây 1,000 sát thương tầm xa" [advancement.challenge_ranged_dmg_10k] - title = "Cung Thủ Chết Chóc" - description = "Gây 10,000 sát thương tầm xa" +title = "Cung Thủ Chết Chóc" +description = "Gây 10,000 sát thương tầm xa" [advancement.challenge_ranged_dist_5k] - title = "Tầm Xa" - description = "Bắn đạn với tổng khoảng cách 5,000 khối" +title = "Tầm Xa" +description = "Bắn đạn với tổng khoảng cách 5,000 khối" [advancement.challenge_ranged_dist_50k] - title = "Bắn Xa Cả Dặm" - description = "Bắn đạn với tổng khoảng cách 50,000 khối" +title = "Bắn Xa Cả Dặm" +description = "Bắn đạn với tổng khoảng cách 50,000 khối" [advancement.challenge_ranged_kills_50] - title = "Cung Thủ" - description = "Giết 50 sinh vật bằng vũ khí tầm xa" +title = "Cung Thủ" +description = "Giết 50 sinh vật bằng vũ khí tầm xa" [advancement.challenge_ranged_kills_500] - title = "Cung Thủ Bậc Thầy" - description = "Giết 500 sinh vật bằng vũ khí tầm xa" +title = "Cung Thủ Bậc Thầy" +description = "Giết 500 sinh vật bằng vũ khí tầm xa" [advancement.challenge_longshot_25] - title = "Xạ Thủ Bắn Tỉa" - description = "Bắn trúng 25 phát tầm xa (hơn 30 khối)" +title = "Xạ Thủ Bắn Tỉa" +description = "Bắn trúng 25 phát tầm xa (hơn 30 khối)" [advancement.challenge_longshot_250] - title = "Mắt Đại Bàng" - description = "Bắn trúng 250 phát tầm xa (hơn 30 khối)" +title = "Mắt Đại Bàng" +description = "Bắn trúng 250 phát tầm xa (hơn 30 khối)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "Người Ném Ngọc" - description = "Ném 50 ngọc ender" +title = "Người Ném Ngọc" +description = "Ném 50 ngọc ender" [advancement.challenge_rift_pearls_500] - title = "Nghiện Dịch Chuyển" - description = "Ném 500 ngọc ender" +title = "Nghiện Dịch Chuyển" +description = "Ném 500 ngọc ender" [advancement.challenge_rift_enderman_50] - title = "Thợ Săn Enderman" - description = "Tiêu diệt 50 enderman" +title = "Thợ Săn Enderman" +description = "Tiêu diệt 50 enderman" [advancement.challenge_rift_enderman_500] - title = "Kẻ Rình Rập Hư Không" - description = "Tiêu diệt 500 enderman" +title = "Kẻ Rình Rập Hư Không" +description = "Tiêu diệt 500 enderman" [advancement.challenge_rift_dragon_500] - title = "Chiến Binh Rồng" - description = "Gây 500 sát thương cho Rồng Ender" +title = "Chiến Binh Rồng" +description = "Gây 500 sát thương cho Rồng Ender" [advancement.challenge_rift_dragon_5k] - title = "Tai Ương Rồng" - description = "Gây 5,000 sát thương cho Rồng Ender" +title = "Tai Ương Rồng" +description = "Gây 5,000 sát thương cho Rồng Ender" [advancement.challenge_rift_crystal_10] - title = "Người Phá Pha Lê" - description = "Phá hủy 10 pha lê ender" +title = "Người Phá Pha Lê" +description = "Phá hủy 10 pha lê ender" [advancement.challenge_rift_crystal_100] - title = "Phá Hủy Cõi Cuối" - description = "Phá hủy 100 pha lê ender" +title = "Phá Hủy Cõi Cuối" +description = "Phá hủy 100 pha lê ender" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "Người Câu Cá" - description = "Câu 25 con cá" +title = "Người Câu Cá" +description = "Câu 25 con cá" [advancement.challenge_fish_250] - title = "Ngư Dân Bậc Thầy" - description = "Câu 250 con cá" +title = "Ngư Dân Bậc Thầy" +description = "Câu 250 con cá" [advancement.challenge_drowned_25] - title = "Thợ Săn Thây Ma Nước" - description = "Tiêu diệt 25 thây ma nước" +title = "Thợ Săn Thây Ma Nước" +description = "Tiêu diệt 25 thây ma nước" [advancement.challenge_drowned_250] - title = "Người Dọn Đại Dương" - description = "Tiêu diệt 250 thây ma nước" +title = "Người Dọn Đại Dương" +description = "Tiêu diệt 250 thây ma nước" [advancement.challenge_guardian_10] - title = "Diệt Vệ Binh" - description = "Tiêu diệt 10 vệ binh" +title = "Diệt Vệ Binh" +description = "Tiêu diệt 10 vệ binh" [advancement.challenge_guardian_100] - title = "Kẻ Cướp Đền" - description = "Tiêu diệt 100 vệ binh" +title = "Kẻ Cướp Đền" +description = "Tiêu diệt 100 vệ binh" [advancement.challenge_underwater_blocks_100] - title = "Thợ Mỏ Dưới Nước" - description = "Phá 100 khối dưới nước" +title = "Thợ Mỏ Dưới Nước" +description = "Phá 100 khối dưới nước" [advancement.challenge_underwater_blocks_1k] - title = "Kỹ Sư Biển" - description = "Phá 1,000 khối dưới nước" +title = "Kỹ Sư Biển" +description = "Phá 1,000 khối dưới nước" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "Kẻ Đâm Sau Lưng" - description = "Gây 500 sát thương khi lén lút" +title = "Kẻ Đâm Sau Lưng" +description = "Gây 500 sát thương khi lén lút" [advancement.challenge_stealth_dmg_5k] - title = "Sát Thủ Thầm Lặng" - description = "Gây 5,000 sát thương khi lén lút" +title = "Sát Thủ Thầm Lặng" +description = "Gây 5,000 sát thương khi lén lút" [advancement.challenge_stealth_kills_10] - title = "Thích Khách" - description = "Giết 10 sinh vật khi lén lút" +title = "Thích Khách" +description = "Giết 10 sinh vật khi lén lút" [advancement.challenge_stealth_kills_100] - title = "Thần Chết Bóng Tối" - description = "Giết 100 sinh vật khi lén lút" +title = "Thần Chết Bóng Tối" +description = "Giết 100 sinh vật khi lén lút" [advancement.challenge_stealth_time_1h] - title = "Kiên Nhẫn" - description = "Dành 1 giờ lén lút (3,600 giây)" +title = "Kiên Nhẫn" +description = "Dành 1 giờ lén lút (3,600 giây)" [advancement.challenge_stealth_time_10h] - title = "Bậc Thầy Bóng Tối" - description = "Dành 10 giờ lén lút (36,000 giây)" +title = "Bậc Thầy Bóng Tối" +description = "Dành 10 giờ lén lút (36,000 giây)" [advancement.challenge_stealth_arrows_50] - title = "Cung Thủ Thầm Lặng" - description = "Bắn 50 mũi tên khi lén lút" +title = "Cung Thủ Thầm Lặng" +description = "Bắn 50 mũi tên khi lén lút" [advancement.challenge_stealth_arrows_500] - title = "Cung Thủ Ma" - description = "Bắn 500 mũi tên khi lén lút" +title = "Cung Thủ Ma" +description = "Bắn 500 mũi tên khi lén lút" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "Học Viên Kiếm" - description = "Gây 1,000 sát thương bằng kiếm" +title = "Học Viên Kiếm" +description = "Gây 1,000 sát thương bằng kiếm" [advancement.challenge_sword_dmg_10k] - title = "Kiếm Sĩ" - description = "Gây 10,000 sát thương bằng kiếm" +title = "Kiếm Sĩ" +description = "Gây 10,000 sát thương bằng kiếm" [advancement.challenge_sword_kills_50] - title = "Đấu Sĩ" - description = "Giết 50 sinh vật bằng kiếm" +title = "Đấu Sĩ" +description = "Giết 50 sinh vật bằng kiếm" [advancement.challenge_sword_kills_500] - title = "Võ Sĩ Giác Đấu" - description = "Giết 500 sinh vật bằng kiếm" +title = "Võ Sĩ Giác Đấu" +description = "Giết 500 sinh vật bằng kiếm" [advancement.challenge_sword_crit_50] - title = "Đòn Chí Mạng" - description = "Đánh trúng 50 đòn chí mạng bằng kiếm" +title = "Đòn Chí Mạng" +description = "Đánh trúng 50 đòn chí mạng bằng kiếm" [advancement.challenge_sword_crit_500] - title = "Bậc Thầy Chính Xác" - description = "Đánh trúng 500 đòn chí mạng bằng kiếm" +title = "Bậc Thầy Chính Xác" +description = "Đánh trúng 500 đòn chí mạng bằng kiếm" [advancement.challenge_sword_heavy_25] - title = "Đòn Nặng" - description = "Đánh trúng 25 đòn nặng bằng kiếm (trên 8 sát thương)" +title = "Đòn Nặng" +description = "Đánh trúng 25 đòn nặng bằng kiếm (trên 8 sát thương)" [advancement.challenge_sword_heavy_250] - title = "Đòn Hủy Diệt" - description = "Đánh trúng 250 đòn nặng bằng kiếm (trên 8 sát thương)" +title = "Đòn Hủy Diệt" +description = "Đánh trúng 250 đòn nặng bằng kiếm (trên 8 sát thương)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "Huấn Luyện Thú" - description = "Thú cưng gây tổng cộng 500 sát thương" +title = "Huấn Luyện Thú" +description = "Thú cưng gây tổng cộng 500 sát thương" [advancement.challenge_pet_dmg_5k] - title = "Chủ Tướng Chiến Tranh" - description = "Thú cưng gây tổng cộng 5,000 sát thương" +title = "Chủ Tướng Chiến Tranh" +description = "Thú cưng gây tổng cộng 5,000 sát thương" [advancement.challenge_tamed_10] - title = "Bạn Của Động Vật" - description = "Thuần hóa 10 động vật" +title = "Bạn Của Động Vật" +description = "Thuần hóa 10 động vật" [advancement.challenge_tamed_100] - title = "Người Giữ Sở Thú" - description = "Thuần hóa 100 động vật" +title = "Người Giữ Sở Thú" +description = "Thuần hóa 100 động vật" [advancement.challenge_pet_kills_25] - title = "Chiến Thuật Bầy Đàn" - description = "Thú cưng tiêu diệt 25 sinh vật" +title = "Chiến Thuật Bầy Đàn" +description = "Thú cưng tiêu diệt 25 sinh vật" [advancement.challenge_pet_kills_250] - title = "Chỉ Huy Alpha" - description = "Thú cưng tiêu diệt 250 sinh vật" +title = "Chỉ Huy Alpha" +description = "Thú cưng tiêu diệt 250 sinh vật" [advancement.challenge_taming_2500] - title = "Chuyên Gia Nhân Giống" - description = "Nhân giống 2,500 động vật" +title = "Chuyên Gia Nhân Giống" +description = "Nhân giống 2,500 động vật" [advancement.challenge_taming_25k] - title = "Bậc Thầy Di Truyền" - description = "Nhân giống 25,000 động vật" +title = "Bậc Thầy Di Truyền" +description = "Nhân giống 25,000 động vật" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "Bao Cát" - description = "Nhận 500 đòn đánh" +title = "Bao Cát" +description = "Nhận 500 đòn đánh" [advancement.challenge_trag_hits_5k] - title = "Kẻ Ham Trừng Phạt" - description = "Nhận 5,000 đòn đánh" +title = "Kẻ Ham Trừng Phạt" +description = "Nhận 5,000 đòn đánh" [advancement.challenge_trag_deaths_10] - title = "Chín Mạng Sống" - description = "Chết 10 lần" +title = "Chín Mạng Sống" +description = "Chết 10 lần" [advancement.challenge_trag_deaths_100] - title = "Ác Mộng Tái Diễn" - description = "Chết 100 lần" +title = "Ác Mộng Tái Diễn" +description = "Chết 100 lần" [advancement.challenge_trag_fire_500] - title = "Nạn Nhân Lửa" - description = "Chịu đựng 500 sát thương lửa" +title = "Nạn Nhân Lửa" +description = "Chịu đựng 500 sát thương lửa" [advancement.challenge_trag_fire_5k] - title = "Phượng Hoàng" - description = "Chịu đựng 5,000 sát thương lửa" +title = "Phượng Hoàng" +description = "Chịu đựng 5,000 sát thương lửa" [advancement.challenge_trag_fall_500] - title = "Thử Trọng Lực" - description = "Chịu đựng 500 sát thương rơi" +title = "Thử Trọng Lực" +description = "Chịu đựng 500 sát thương rơi" [advancement.challenge_trag_fall_5k] - title = "Vận Tốc Giới Hạn" - description = "Chịu đựng 5,000 sát thương rơi" +title = "Vận Tốc Giới Hạn" +description = "Chịu đựng 5,000 sát thương rơi" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "Tay Đấm" - description = "Gây 1,000 sát thương bằng tay trần" +title = "Tay Đấm" +description = "Gây 1,000 sát thương bằng tay trần" [advancement.challenge_unarmed_dmg_10k] - title = "Võ Sư" - description = "Gây 10,000 sát thương bằng tay trần" +title = "Võ Sư" +description = "Gây 10,000 sát thương bằng tay trần" [advancement.challenge_unarmed_kills_25] - title = "Nắm Đấm Trần" - description = "Giết 25 sinh vật bằng tay trần" +title = "Nắm Đấm Trần" +description = "Giết 25 sinh vật bằng tay trần" [advancement.challenge_unarmed_kills_250] - title = "Nắm Đấm Huyền Thoại" - description = "Giết 250 sinh vật bằng tay trần" +title = "Nắm Đấm Huyền Thoại" +description = "Giết 250 sinh vật bằng tay trần" [advancement.challenge_unarmed_crit_25] - title = "Cú Đấm Chí Mạng" - description = "Đánh trúng 25 đòn chí mạng bằng tay trần" +title = "Cú Đấm Chí Mạng" +description = "Đánh trúng 25 đòn chí mạng bằng tay trần" [advancement.challenge_unarmed_crit_250] - title = "Nắm Đấm Chính Xác" - description = "Đánh trúng 250 đòn chí mạng bằng tay trần" +title = "Nắm Đấm Chính Xác" +description = "Đánh trúng 250 đòn chí mạng bằng tay trần" [advancement.challenge_unarmed_heavy_25] - title = "Cú Đấm Mạnh" - description = "Đánh trúng 25 đòn nặng bằng tay trần (trên 6 sát thương)" +title = "Cú Đấm Mạnh" +description = "Đánh trúng 25 đòn nặng bằng tay trần (trên 6 sát thương)" [advancement.challenge_unarmed_heavy_250] - title = "Vua Hạ Gục" - description = "Đánh trúng 250 đòn nặng bằng tay trần (trên 6 sát thương)" +title = "Vua Hạ Gục" +description = "Đánh trúng 250 đòn nặng bằng tay trần (trên 6 sát thương)" # items [items] [items.bound_ender_peral] - name = "Chìa Khóa Thánh Tích" - usage1 = "Shift + Chuột Trái để liên kết" - usage2 = "Chuột Phải để truy cập Kho Đồ đã liên kết" +name = "Chìa Khóa Thánh Tích" +usage1 = "Shift + Chuột Trái để liên kết" +usage2 = "Chuột Phải để truy cập Kho Đồ đã liên kết" [items.bound_eye_of_ender] - name = "Neo Thị Giác" - usage1 = "Chuột Phải để tiêu thụ và dịch chuyển đến vị trí đã liên kết" - usage2 = "Shift + Chuột Trái để liên kết với một khối" +name = "Neo Thị Giác" +usage1 = "Chuột Phải để tiêu thụ và dịch chuyển đến vị trí đã liên kết" +usage2 = "Shift + Chuột Trái để liên kết với một khối" [items.bound_redstone_torch] - name = "Điều Khiển Redstone Từ Xa" - usage1 = "Chuột Phải để tạo xung Redstone 1-Tick" - usage2 = "Shift + Chuột Trái vào Khối 'Mục Tiêu' để liên kết" +name = "Điều Khiển Redstone Từ Xa" +usage1 = "Chuột Phải để tạo xung Redstone 1-Tick" +usage2 = "Shift + Chuột Trái vào Khối 'Mục Tiêu' để liên kết" [items.bound_snowball] - name = "Bẫy Mạng Nhện!" - usage1 = "Ném để tạo bẫy mạng nhện tạm thời tại vị trí đó" +name = "Bẫy Mạng Nhện!" +usage1 = "Ném để tạo bẫy mạng nhện tạm thời tại vị trí đó" [items.chrono_time_bottle] - name = "Thời Gian Trong Chai" - usage1 = "Tự động tích trữ thời gian khi nằm trong túi đồ" - usage2 = "Chuột phải vào khối có bộ hẹn giờ hoặc động vật con để tiêu thụ thời gian đã lưu" - stored = "Thời Gian Tích Trữ" +name = "Thời Gian Trong Chai" +usage1 = "Tự động tích trữ thời gian khi nằm trong túi đồ" +usage2 = "Chuột phải vào khối có bộ hẹn giờ hoặc động vật con để tiêu thụ thời gian đã lưu" +stored = "Thời Gian Tích Trữ" [items.chrono_time_bomb] - name = "Bom Thời Gian" - usage1 = "Chuột phải để phóng tia thời gian tạo ra trường thời không" +name = "Bom Thời Gian" +usage1 = "Chuột phải để phóng tia thời gian tạo ra trường thời không" [items.elevator_block] - name = "Khối Thang Máy" - usage1 = "Nhảy để dịch chuyển lên" - usage2 = "Ngồi để dịch chuyển xuống" - usage3 = "Tối thiểu 2 khối trống giữa các thang máy" +name = "Khối Thang Máy" +usage1 = "Nhảy để dịch chuyển lên" +usage2 = "Ngồi để dịch chuyển xuống" +usage3 = "Tối thiểu 2 khối trống giữa các thang máy" # snippets [snippets] [snippets.gui] - level = "Cấp Độ" - knowledge = "kiến thức" - power_used = "Năng Lượng Đã Dùng" - not_learned = "Chưa Học" - xp = "XP đến" - welcome = "Chào mừng!" - welcome_back = "Chào mừng trở lại!" - xp_bonus_for_time = "XP cho" - max_ability_power = "Năng Lượng Kỹ Năng Tối Đa" - unlock_this_by_clicking = "Mở khóa bằng cách Chuột Phải: " - back = "Quay Lại" - unlearn_all = "Quên tất cả" - unlearned_all = "Đã quên tất cả" +level = "Cấp Độ" +knowledge = "kiến thức" +power_used = "Năng Lượng Đã Dùng" +not_learned = "Chưa Học" +xp = "XP đến" +welcome = "Chào mừng!" +welcome_back = "Chào mừng trở lại!" +xp_bonus_for_time = "XP cho" +max_ability_power = "Năng Lượng Kỹ Năng Tối Đa" +unlock_this_by_clicking = "Mở khóa bằng cách Chuột Phải: " +back = "Quay Lại" +unlearn_all = "Quên tất cả" +unlearned_all = "Đã quên tất cả" [snippets.adapt_menu] - may_not_unlearn = "BẠN KHÔNG THỂ QUÊN" - may_unlearn = "BẠN CÓ THỂ HỌC/QUÊN" - knowledge_cost = "Chi Phí Kiến Thức" - knowledge_available = "Kiến Thức Khả Dụng" - already_learned = "Đã Học Rồi" - unlearn_refund = "Bấm để Quên & Hoàn Lại" - no_refunds = "CHẾ ĐỘ KHÓ, KHÔNG HOÀN LẠI" - knowledge = "kiến thức" - click_learn = "Bấm để Học" - no_knowledge = "(Bạn chưa có Kiến Thức nào)" - you_only_have = "Bạn chỉ có" - how_to_level_up = "Nâng cấp kỹ năng để tăng năng lượng tối đa." - not_enough_power = "Không đủ năng lượng! Mỗi Cấp Độ Kỹ Năng tốn 1 năng lượng." - power = "năng lượng" - power_drain = "Tiêu Hao Năng Lượng" - learned = "Đã Học " - unlearned = "Đã Quên " - activator_block = "Giá Sách" +may_not_unlearn = "BẠN KHÔNG THỂ QUÊN" +may_unlearn = "BẠN CÓ THỂ HỌC/QUÊN" +knowledge_cost = "Chi Phí Kiến Thức" +knowledge_available = "Kiến Thức Khả Dụng" +already_learned = "Đã Học Rồi" +unlearn_refund = "Bấm để Quên & Hoàn Lại" +no_refunds = "CHẾ ĐỘ KHÓ, KHÔNG HOÀN LẠI" +knowledge = "kiến thức" +click_learn = "Bấm để Học" +no_knowledge = "(Bạn chưa có Kiến Thức nào)" +you_only_have = "Bạn chỉ có" +how_to_level_up = "Nâng cấp kỹ năng để tăng năng lượng tối đa." +not_enough_power = "Không đủ năng lượng! Mỗi Cấp Độ Kỹ Năng tốn 1 năng lượng." +power = "năng lượng" +power_drain = "Tiêu Hao Năng Lượng" +learned = "Đã Học " +unlearned = "Đã Quên " +activator_block = "Giá Sách" [snippets.knowledge_orb] - contains = "chứa" - knowledge = "kiến thức" - rightclick = "Chuột Phải" - togainknowledge = "để nhận kiến thức này" - knowledge_orb = "Quả Cầu Kiến Thức" +contains = "chứa" +knowledge = "kiến thức" +rightclick = "Chuột Phải" +togainknowledge = "để nhận kiến thức này" +knowledge_orb = "Quả Cầu Kiến Thức" [snippets.experience_orb] - contains = "chứa" - xp = "Kinh Nghiệm" - rightclick = "Chuột Phải" - togainxp = "để nhận kinh nghiệm này" - xporb = "Quả Cầu Kinh Nghiệm" +contains = "chứa" +xp = "Kinh Nghiệm" +rightclick = "Chuột Phải" +togainxp = "để nhận kinh nghiệm này" +xporb = "Quả Cầu Kinh Nghiệm" # skill [skill] [skill.agility] - name = "Nhanh Nhẹn" - icon = "⇉" - description = "Nhanh nhẹn là khả năng di chuyển nhanh và linh hoạt trước mọi chướng ngại vật." +name = "Nhanh Nhẹn" +icon = "⇉" +description = "Nhanh nhẹn là khả năng di chuyển nhanh và linh hoạt trước mọi chướng ngại vật." [skill.architect] - name = "Kiến Trúc Sư" - icon = "⬧" - description = "Công trình là nền tảng của thế giới. Thực tại nằm trong tay bạn, do bạn điều khiển." +name = "Kiến Trúc Sư" +icon = "⬧" +description = "Công trình là nền tảng của thế giới. Thực tại nằm trong tay bạn, do bạn điều khiển." [skill.axes] - name = "Rìu" - icon = "🪓" - description1 = "Sao phải chặt cây, khi bạn có thể chặt " - description2 = "mọi thứ" - description3 = "thay vào đó, cũng cùng kết quả thôi!" +name = "Rìu" +icon = "🪓" +description1 = "Sao phải chặt cây, khi bạn có thể chặt " +description2 = "mọi thứ" +description3 = "thay vào đó, cũng cùng kết quả thôi!" [skill.brewing] - name = "Pha Chế" - icon = "❦" - description = "Bọt Đôi, Bọt Ba, Bọt Bốn- Tôi vẫn không thể đổ thuốc này vào vạc được" +name = "Pha Chế" +icon = "❦" +description = "Bọt Đôi, Bọt Ba, Bọt Bốn- Tôi vẫn không thể đổ thuốc này vào vạc được" [skill.blocking] - name = "Phòng Thủ" - icon = "🛡" - description = "Gậy và đá không thể bẻ gãy xương bạn, Nhưng một chiếc khiên thì có thể." +name = "Phòng Thủ" +icon = "🛡" +description = "Gậy và đá không thể bẻ gãy xương bạn, Nhưng một chiếc khiên thì có thể." [skill.crafting] - name = "Chế Tạo" - icon = "⌂" - description = "Không còn mảnh nào để đặt, sao không tạo thêm một cái nữa?" +name = "Chế Tạo" +icon = "⌂" +description = "Không còn mảnh nào để đặt, sao không tạo thêm một cái nữa?" [skill.discovery] - name = "Khám Phá" - icon = "⚛" - description = "Khi nhận thức mở rộng, tâm trí bạn sẽ hé lộ những điều bạn chưa từng biết." +name = "Khám Phá" +icon = "⚛" +description = "Khi nhận thức mở rộng, tâm trí bạn sẽ hé lộ những điều bạn chưa từng biết." [skill.enchanting] - name = "Phù Phép" - icon = "♰" - description = "Bạn đang nói gì vậy? Lời tiên tri, ảo giác, mấy thứ mê tín nhảm nhí?" +name = "Phù Phép" +icon = "♰" +description = "Bạn đang nói gì vậy? Lời tiên tri, ảo giác, mấy thứ mê tín nhảm nhí?" [skill.excavation] - name = "Khai Quật" - icon = "ᛳ" - description = "Đào đào cái hố..." +name = "Khai Quật" +icon = "ᛳ" +description = "Đào đào cái hố..." [skill.herbalism] - name = "Thảo Dược Học" - icon = "⚘" - description = "Tôi không tìm thấy cây nào, nhưng tôi tìm được vài hạt giống và- đó có phải là... Cỏ dại không?" +name = "Thảo Dược Học" +icon = "⚘" +description = "Tôi không tìm thấy cây nào, nhưng tôi tìm được vài hạt giống và- đó có phải là... Cỏ dại không?" [skill.hunter] - name = "Thợ Săn" - icon = "☠" - description = "Săn bắn là về hành trình chứ không phải kết quả." +name = "Thợ Săn" +icon = "☠" +description = "Săn bắn là về hành trình chứ không phải kết quả." [skill.nether] - name = "Địa Ngục" - icon = "₪" - description = "Từ tận sâu thẳm của Địa Ngục." +name = "Địa Ngục" +icon = "₪" +description = "Từ tận sâu thẳm của Địa Ngục." [skill.pickaxe] - name = "Cuốc Chim" - icon = "⛏" - description = "Người lùn mới là thợ mỏ, nhưng tôi cũng học được đôi điều. TÔI LÀ NGƯỜI THỤY ĐIỂN" +name = "Cuốc Chim" +icon = "⛏" +description = "Người lùn mới là thợ mỏ, nhưng tôi cũng học được đôi điều. TÔI LÀ NGƯỜI THỤY ĐIỂN" [skill.ranged] - name = "Tầm Xa" - icon = "🏹" - description = "Khoảng cách là chìa khóa chiến thắng, và là chìa khóa sinh tồn." +name = "Tầm Xa" +icon = "🏹" +description = "Khoảng cách là chìa khóa chiến thắng, và là chìa khóa sinh tồn." [skill.rift] - name = "Khe Nứt" - icon = "❍" - description = "Khe Nứt là một sức mạnh khắc nghiệt, nhưng bạn đã thuần phục được nó." +name = "Khe Nứt" +icon = "❍" +description = "Khe Nứt là một sức mạnh khắc nghiệt, nhưng bạn đã thuần phục được nó." [skill.seaborne] - name = "Hải Sinh" - icon = "🎣" - description = "Với kỹ năng này, bạn có thể điều khiển những điều kỳ diệu của đại dương." +name = "Hải Sinh" +icon = "🎣" +description = "Với kỹ năng này, bạn có thể điều khiển những điều kỳ diệu của đại dương." [skill.stealth] - name = "Tàng Hình" - icon = "☯" - description = "Nghệ thuật của sự vô hình. Bước đi trong bóng tối." +name = "Tàng Hình" +icon = "☯" +description = "Nghệ thuật của sự vô hình. Bước đi trong bóng tối." [skill.swords] - name = "Kiếm" - icon = "⚔" - description = "Bằng sức mạnh của GreyStone!" +name = "Kiếm" +icon = "⚔" +description = "Bằng sức mạnh của GreyStone!" [skill.taming] - name = "Thuần Hóa" - icon = "♥" - description = "Những con vẹt và đàn ong... còn bạn thì sao?" +name = "Thuần Hóa" +icon = "♥" +description = "Những con vẹt và đàn ong... còn bạn thì sao?" [skill.tragoul] - name = "TragOul" - icon = "🗡" - description = "Máu chảy trong huyết quản của vũ trụ. Bị siết chặt bởi đôi tay bạn." +name = "TragOul" +icon = "🗡" +description = "Máu chảy trong huyết quản của vũ trụ. Bị siết chặt bởi đôi tay bạn." [skill.chronos] - name = "Chronos" - icon = "🕒" - description = "Vặn Đồng Hồ của vũ trụ, trải nghiệm dòng chảy. Đập vỡ đồng hồ, trở thành nó." +name = "Chronos" +icon = "🕒" +description = "Vặn Đồng Hồ của vũ trụ, trải nghiệm dòng chảy. Đập vỡ đồng hồ, trở thành nó." [skill.unarmed] - name = "Tay Không" - icon = "»" - description = "Không vũ khí không có nghĩa là không có sức mạnh." +name = "Tay Không" +icon = "»" +description = "Không vũ khí không có nghĩa là không có sức mạnh." # agility [agility] [agility.armor_up] - name = "Tăng Giáp" - description = "Nhận thêm giáp khi bạn chạy nước rút càng lâu!" - lore1 = "Giáp Tối Đa" - lore2 = "Thời Gian Tăng Giáp" - lore = ["Giáp Tối Đa", "Thời Gian Tăng Giáp"] +name = "Tăng Giáp" +description = "Nhận thêm giáp khi bạn chạy nước rút càng lâu!" +lore1 = "Giáp Tối Đa" +lore2 = "Thời Gian Tăng Giáp" +lore = ["Giáp Tối Đa", "Thời Gian Tăng Giáp"] [agility.ladder_slide] - name = "Trượt Thang" - description = "Leo và trượt thang nhanh hơn nhiều theo cả hai hướng." - lore1 = "Hệ số tốc độ leo thang" - lore2 = "Tốc độ trượt xuống nhanh" - lore = ["Hệ số tốc độ leo thang", "Tốc độ trượt xuống nhanh"] +name = "Trượt Thang" +description = "Leo và trượt thang nhanh hơn nhiều theo cả hai hướng." +lore1 = "Hệ số tốc độ leo thang" +lore2 = "Tốc độ trượt xuống nhanh" +lore = ["Hệ số tốc độ leo thang", "Tốc độ trượt xuống nhanh"] [agility.super_jump] - name = "Siêu Nhảy" - description = "Lợi Thế Chiều Cao Vượt Trội." - lore1 = "Chiều Cao Nhảy Tối Đa" - lore2 = "Ngồi + Nhảy để Siêu Nhảy!" - lore = ["Chiều Cao Nhảy Tối Đa", "Ngồi + Nhảy để Siêu Nhảy!"] +name = "Siêu Nhảy" +description = "Lợi Thế Chiều Cao Vượt Trội." +lore1 = "Chiều Cao Nhảy Tối Đa" +lore2 = "Ngồi + Nhảy để Siêu Nhảy!" +lore = ["Chiều Cao Nhảy Tối Đa", "Ngồi + Nhảy để Siêu Nhảy!"] [agility.wall_jump] - name = "Nhảy Tường" - description = "Giữ shift khi đang lơ lửng sát tường để bám tường & nhảy!" - lore1 = "Số Lần Nhảy Tối Đa" - lore2 = "Chiều Cao Nhảy" - lore = ["Số Lần Nhảy Tối Đa", "Chiều Cao Nhảy"] +name = "Nhảy Tường" +description = "Giữ shift khi đang lơ lửng sát tường để bám tường & nhảy!" +lore1 = "Số Lần Nhảy Tối Đa" +lore2 = "Chiều Cao Nhảy" +lore = ["Số Lần Nhảy Tối Đa", "Chiều Cao Nhảy"] [agility.wind_up] - name = "Tăng Tốc" - description = "Chạy nhanh hơn khi bạn chạy nước rút càng lâu!" - lore1 = "Tốc Độ Tối Đa" - lore2 = "Thời Gian Tăng Tốc" - lore = ["Tốc Độ Tối Đa", "Thời Gian Tăng Tốc"] +name = "Tăng Tốc" +description = "Chạy nhanh hơn khi bạn chạy nước rút càng lâu!" +lore1 = "Tốc Độ Tối Đa" +lore2 = "Thời Gian Tăng Tốc" +lore = ["Tốc Độ Tối Đa", "Thời Gian Tăng Tốc"] # architect [architect] [architect.elevator] - name = "Thang Máy" - description = "Cho phép bạn xây thang máy để dịch chuyển theo chiều dọc nhanh chóng!" - lore1 = "Mở khóa công thức thang máy: X=LEN, Y=NGỌC TRAI ENDER" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["Mở khóa công thức thang máy: X=LEN, Y=NGỌC TRAI ENDER", "XXX", "XYX", "XXX"] +name = "Thang Máy" +description = "Cho phép bạn xây thang máy để dịch chuyển theo chiều dọc nhanh chóng!" +lore1 = "Mở khóa công thức thang máy: X=LEN, Y=NGỌC TRAI ENDER" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["Mở khóa công thức thang máy: X=LEN, Y=NGỌC TRAI ENDER", "XXX", "XYX", "XXX"] [architect.foundation] - name = "Nền Tảng Ma Thuật" - description = "Cho phép bạn ngồi và đặt một nền tảng tạm thời bên dưới bạn!" - lore1 = "Tạo ra một cách kỳ diệu: " - lore2 = "Khối bên dưới bạn!" - lore = ["Tạo ra một cách kỳ diệu: ", "Khối bên dưới bạn!"] +name = "Nền Tảng Ma Thuật" +description = "Cho phép bạn ngồi và đặt một nền tảng tạm thời bên dưới bạn!" +lore1 = "Tạo ra một cách kỳ diệu: " +lore2 = "Khối bên dưới bạn!" +lore = ["Tạo ra một cách kỳ diệu: ", "Khối bên dưới bạn!"] [architect.glass] - name = "Cảm Ứng Lụa Kính" - description = "Cho phép bạn ngăn mất khối kính khi bạn đập chúng bằng tay không!" - lore1 = "Tay bạn có được cảm ứng lụa cho Kính" - lore = ["Tay bạn có được cảm ứng lụa cho Kính"] +name = "Cảm Ứng Lụa Kính" +description = "Cho phép bạn ngăn mất khối kính khi bạn đập chúng bằng tay không!" +lore1 = "Tay bạn có được cảm ứng lụa cho Kính" +lore = ["Tay bạn có được cảm ứng lụa cho Kính"] [architect.wireless_redstone] - name = "Điều Khiển Redstone Từ Xa" - description = "Cho phép bạn dùng đuốc redstone để bật/tắt redstone từ xa!" - lore1 = "Mục Tiêu + Đuốc Redstone + Ngọc Trai Ender = 1 Điều Khiển Redstone Từ Xa" - lore = ["Mục Tiêu + Đuốc Redstone + Ngọc Trai Ender = 1 Điều Khiển Redstone Từ Xa"] +name = "Điều Khiển Redstone Từ Xa" +description = "Cho phép bạn dùng đuốc redstone để bật/tắt redstone từ xa!" +lore1 = "Mục Tiêu + Đuốc Redstone + Ngọc Trai Ender = 1 Điều Khiển Redstone Từ Xa" +lore = ["Mục Tiêu + Đuốc Redstone + Ngọc Trai Ender = 1 Điều Khiển Redstone Từ Xa"] [architect.placement] - name = "Gậy Xây Dựng" - description = "Cho phép bạn đặt nhiều khối cùng lúc - ngồi, cầm khối trùng với khối bạn đang nhìn và đặt! Lưu ý, bạn có thể cần di chuyển chút ít để kích hoạt vùng giới hạn!" - lore1 = "Bạn cần" - lore2 = "khối trong tay để đặt" - lore3 = "Gậy Xây Dựng Vật Liệu" - lore = ["Bạn cần", "khối trong tay để đặt", "Gậy Xây Dựng Vật Liệu"] +name = "Gậy Xây Dựng" +description = "Cho phép bạn đặt nhiều khối cùng lúc - ngồi, cầm khối trùng với khối bạn đang nhìn và đặt! Lưu ý, bạn có thể cần di chuyển chút ít để kích hoạt vùng giới hạn!" +lore1 = "Bạn cần" +lore2 = "khối trong tay để đặt" +lore3 = "Gậy Xây Dựng Vật Liệu" +lore = ["Bạn cần", "khối trong tay để đặt", "Gậy Xây Dựng Vật Liệu"] # axe [axe] [axe.chop] - name = "Chặt Rìu" - description = "Chặt cây bằng cách chuột phải vào khúc gỗ gốc!" - lore1 = "Khối Mỗi Lần Chặt" - lore2 = "Thời Gian Hồi Chặt" - lore3 = "Hao Mòn Công Cụ" - lore = ["Khối Mỗi Lần Chặt", "Thời Gian Hồi Chặt", "Hao Mòn Công Cụ"] +name = "Chặt Rìu" +description = "Chặt cây bằng cách chuột phải vào khúc gỗ gốc!" +lore1 = "Khối Mỗi Lần Chặt" +lore2 = "Thời Gian Hồi Chặt" +lore3 = "Hao Mòn Công Cụ" +lore = ["Khối Mỗi Lần Chặt", "Thời Gian Hồi Chặt", "Hao Mòn Công Cụ"] [axe.log_swap] - name = "Đổi Gỗ Của Lucy" - description = "Đổi loại gỗ trong Bàn Chế Tạo!" - lore1 = "8 Gỗ bất kỳ + 1 cây non = 8 gỗ cùng loại cây non" - lore = ["8 Gỗ bất kỳ + 1 cây non = 8 gỗ cùng loại cây non"] +name = "Đổi Gỗ Của Lucy" +description = "Đổi loại gỗ trong Bàn Chế Tạo!" +lore1 = "8 Gỗ bất kỳ + 1 cây non = 8 gỗ cùng loại cây non" +lore = ["8 Gỗ bất kỳ + 1 cây non = 8 gỗ cùng loại cây non"] [axe.drop_to_inventory] - name = "Rìu Nhặt Vào Túi Đồ" +name = "Rìu Nhặt Vào Túi Đồ" [axe.ground_smash] - name = "Rìu Đập Đất" - description = "Nhảy, rồi ngồi và đập tan tất cả kẻ thù gần đó." - lore1 = "Sát Thương" - lore2 = "Bán Kính Khối" - lore3 = "Lực Đẩy" - lore4 = "Thời Gian Hồi Đập" - lore = ["Sát Thương", "Bán Kính Khối", "Lực Đẩy", "Thời Gian Hồi Đập"] +name = "Rìu Đập Đất" +description = "Nhảy, rồi ngồi và đập tan tất cả kẻ thù gần đó." +lore1 = "Sát Thương" +lore2 = "Bán Kính Khối" +lore3 = "Lực Đẩy" +lore4 = "Thời Gian Hồi Đập" +lore = ["Sát Thương", "Bán Kính Khối", "Lực Đẩy", "Thời Gian Hồi Đập"] [axe.leaf_miner] - name = "Phá Lá Hàng Loạt" - description = "Cho phép bạn phá lá cây hàng loạt cùng lúc!" - lore1 = "Ngồi, và đào LÁ" - lore2 = "phạm vi phá lá" - lore3 = "Bạn sẽ không nhận được vật phẩm rơi từ lá (Chống Lợi Dụng)" - lore = ["Ngồi, và đào LÁ", "phạm vi phá lá", "Bạn sẽ không nhận được vật phẩm rơi từ lá (Chống Lợi Dụng)"] +name = "Phá Lá Hàng Loạt" +description = "Cho phép bạn phá lá cây hàng loạt cùng lúc!" +lore1 = "Ngồi, và đào LÁ" +lore2 = "phạm vi phá lá" +lore3 = "Bạn sẽ không nhận được vật phẩm rơi từ lá (Chống Lợi Dụng)" +lore = ["Ngồi, và đào LÁ", "phạm vi phá lá", "Bạn sẽ không nhận được vật phẩm rơi từ lá (Chống Lợi Dụng)"] [axe.wood_miner] - name = "Phá Gỗ Hàng Loạt" - description = "Cho phép bạn phá gỗ hàng loạt cùng lúc!" - lore1 = "Ngồi, và đào GỖ/THÂN CÂY (Không phải Ván)" - lore2 = "phạm vi phá gỗ" - lore3 = "Hoạt động với Nhặt Vào Túi Đồ" - lore = ["Ngồi, và đào GỖ/THÂN CÂY (Không phải Ván)", "phạm vi phá gỗ", "Hoạt động với Nhặt Vào Túi Đồ"] +name = "Phá Gỗ Hàng Loạt" +description = "Cho phép bạn phá gỗ hàng loạt cùng lúc!" +lore1 = "Ngồi, và đào GỖ/THÂN CÂY (Không phải Ván)" +lore2 = "phạm vi phá gỗ" +lore3 = "Hoạt động với Nhặt Vào Túi Đồ" +lore = ["Ngồi, và đào GỖ/THÂN CÂY (Không phải Ván)", "phạm vi phá gỗ", "Hoạt động với Nhặt Vào Túi Đồ"] # brewing [brewing] [brewing.lingering] - name = "Thuốc Kéo Dài" - description = "Thuốc pha chế có tác dụng lâu hơn!" - lore1 = "Thời Lượng" - lore2 = "Thời Lượng" - lore = ["Thời Lượng", "Thời Lượng"] +name = "Thuốc Kéo Dài" +description = "Thuốc pha chế có tác dụng lâu hơn!" +lore1 = "Thời Lượng" +lore2 = "Thời Lượng" +lore = ["Thời Lượng", "Thời Lượng"] [brewing.super_heated] - name = "Thuốc Siêu Nóng" - description = "Giá pha chế hoạt động nhanh hơn khi càng nóng." - lore1 = "Mỗi Khối Lửa Chạm Vào" - lore2 = "Mỗi Khối Dung Nham Chạm Vào" - lore = ["Mỗi Khối Lửa Chạm Vào", "Mỗi Khối Dung Nham Chạm Vào"] +name = "Thuốc Siêu Nóng" +description = "Giá pha chế hoạt động nhanh hơn khi càng nóng." +lore1 = "Mỗi Khối Lửa Chạm Vào" +lore2 = "Mỗi Khối Dung Nham Chạm Vào" +lore = ["Mỗi Khối Lửa Chạm Vào", "Mỗi Khối Dung Nham Chạm Vào"] [brewing.darkness] - name = "Bóng Tối Đóng Chai" - description = "Không chắc sao bạn cần cái này, nhưng đây nè!" - lore1 = "Thuốc Nhìn Đêm + Bê Tông Đen = Thuốc Bóng Tối (30 giây)" - lore2 = "Lưu ý rằng thuốc này ngăn người dùng chạy nước rút!" - lore = ["Thuốc Nhìn Đêm + Bê Tông Đen = Thuốc Bóng Tối (30 giây)", "Lưu ý rằng thuốc này ngăn người dùng chạy nước rút!"] +name = "Bóng Tối Đóng Chai" +description = "Không chắc sao bạn cần cái này, nhưng đây nè!" +lore1 = "Thuốc Nhìn Đêm + Bê Tông Đen = Thuốc Bóng Tối (30 giây)" +lore2 = "Lưu ý rằng thuốc này ngăn người dùng chạy nước rút!" +lore = ["Thuốc Nhìn Đêm + Bê Tông Đen = Thuốc Bóng Tối (30 giây)", "Lưu ý rằng thuốc này ngăn người dùng chạy nước rút!"] [brewing.haste] - name = "Tốc Độ Đóng Chai" - description = "Khi Hiệu Suất là chưa đủ" - lore1 = "Thuốc Tốc Độ + Mảnh Thạch Anh Tím = Thuốc Haste (60 giây)" - lore2 = "Thuốc Tốc Độ + Khối Thạch Anh Tím = Thuốc Haste-2 (30 giây)" - lore = ["Thuốc Tốc Độ + Mảnh Thạch Anh Tím = Thuốc Haste (60 giây)", "Thuốc Tốc Độ + Khối Thạch Anh Tím = Thuốc Haste-2 (30 giây)"] +name = "Tốc Độ Đóng Chai" +description = "Khi Hiệu Suất là chưa đủ" +lore1 = "Thuốc Tốc Độ + Mảnh Thạch Anh Tím = Thuốc Haste (60 giây)" +lore2 = "Thuốc Tốc Độ + Khối Thạch Anh Tím = Thuốc Haste-2 (30 giây)" +lore = ["Thuốc Tốc Độ + Mảnh Thạch Anh Tím = Thuốc Haste (60 giây)", "Thuốc Tốc Độ + Khối Thạch Anh Tím = Thuốc Haste-2 (30 giây)"] [brewing.absorption] - name = "Hấp Thụ Đóng Chai" - description = "Làm cứng cơ thể!" - lore1 = "Hồi Máu Tức Thì + Thạch Anh = Thuốc Hấp Thụ (60 giây)" - lore2 = "Hồi Máu Tức Thì + Khối Thạch Anh = Thuốc Hấp Thụ-2 (30 giây)" - lore = ["Hồi Máu Tức Thì + Thạch Anh = Thuốc Hấp Thụ (60 giây)", "Hồi Máu Tức Thì + Khối Thạch Anh = Thuốc Hấp Thụ-2 (30 giây)"] +name = "Hấp Thụ Đóng Chai" +description = "Làm cứng cơ thể!" +lore1 = "Hồi Máu Tức Thì + Thạch Anh = Thuốc Hấp Thụ (60 giây)" +lore2 = "Hồi Máu Tức Thì + Khối Thạch Anh = Thuốc Hấp Thụ-2 (30 giây)" +lore = ["Hồi Máu Tức Thì + Thạch Anh = Thuốc Hấp Thụ (60 giây)", "Hồi Máu Tức Thì + Khối Thạch Anh = Thuốc Hấp Thụ-2 (30 giây)"] [brewing.fatigue] - name = "Mệt Mỏi Đóng Chai" - description = "Làm suy yếu cơ thể!" - lore1 = "Thuốc Yếu Đuối + Bóng Slime = Thuốc Mệt Mỏi (30 giây)" - lore2 = "Thuốc Yếu Đuối + Khối Slime = Thuốc Mệt Mỏi-2 (15 giây)" - lore = ["Thuốc Yếu Đuối + Bóng Slime = Thuốc Mệt Mỏi (30 giây)", "Thuốc Yếu Đuối + Khối Slime = Thuốc Mệt Mỏi-2 (15 giây)"] +name = "Mệt Mỏi Đóng Chai" +description = "Làm suy yếu cơ thể!" +lore1 = "Thuốc Yếu Đuối + Bóng Slime = Thuốc Mệt Mỏi (30 giây)" +lore2 = "Thuốc Yếu Đuối + Khối Slime = Thuốc Mệt Mỏi-2 (15 giây)" +lore = ["Thuốc Yếu Đuối + Bóng Slime = Thuốc Mệt Mỏi (30 giây)", "Thuốc Yếu Đuối + Khối Slime = Thuốc Mệt Mỏi-2 (15 giây)"] [brewing.hunger] - name = "Đói Đóng Chai" - description = "Cho kẻ ăn không biết no!" - lore1 = "Thuốc Vụng Về + Thịt Thối = Thuốc Đói (30 giây)" - lore2 = "Thuốc Yếu Đuối + Thịt Thối = Thuốc Đói-3 (15 giây)" - lore = ["Thuốc Vụng Về + Thịt Thối = Thuốc Đói (30 giây)", "Thuốc Yếu Đuối + Thịt Thối = Thuốc Đói-3 (15 giây)"] +name = "Đói Đóng Chai" +description = "Cho kẻ ăn không biết no!" +lore1 = "Thuốc Vụng Về + Thịt Thối = Thuốc Đói (30 giây)" +lore2 = "Thuốc Yếu Đuối + Thịt Thối = Thuốc Đói-3 (15 giây)" +lore = ["Thuốc Vụng Về + Thịt Thối = Thuốc Đói (30 giây)", "Thuốc Yếu Đuối + Thịt Thối = Thuốc Đói-3 (15 giây)"] [brewing.nausea] - name = "Buồn Nôn Đóng Chai" - description = "Bạn làm tôi phát ốm!" - lore1 = "Thuốc Vụng Về + Nấm Nâu = Thuốc Buồn Nôn (16 giây)" - lore2 = "Thuốc Vụng Về + Nấm Đỏ Thẫm = Thuốc Buồn Nôn-2 (8 giây)" - lore = ["Thuốc Vụng Về + Nấm Nâu = Thuốc Buồn Nôn (16 giây)", "Thuốc Vụng Về + Nấm Đỏ Thẫm = Thuốc Buồn Nôn-2 (8 giây)"] +name = "Buồn Nôn Đóng Chai" +description = "Bạn làm tôi phát ốm!" +lore1 = "Thuốc Vụng Về + Nấm Nâu = Thuốc Buồn Nôn (16 giây)" +lore2 = "Thuốc Vụng Về + Nấm Đỏ Thẫm = Thuốc Buồn Nôn-2 (8 giây)" +lore = ["Thuốc Vụng Về + Nấm Nâu = Thuốc Buồn Nôn (16 giây)", "Thuốc Vụng Về + Nấm Đỏ Thẫm = Thuốc Buồn Nôn-2 (8 giây)"] [brewing.blindness] - name = "Mù Lòa Đóng Chai" - description = "Bạn là một người tệ hại..." - lore1 = "Thuốc Vụng Về + Túi Mực = Thuốc Mù Lòa (30 giây)" - lore2 = "Thuốc Vụng Về + Túi Mực Phát Sáng = Thuốc Mù Lòa-2 (15 giây)" - lore = ["Thuốc Vụng Về + Túi Mực = Thuốc Mù Lòa (30 giây)", "Thuốc Vụng Về + Túi Mực Phát Sáng = Thuốc Mù Lòa-2 (15 giây)"] +name = "Mù Lòa Đóng Chai" +description = "Bạn là một người tệ hại..." +lore1 = "Thuốc Vụng Về + Túi Mực = Thuốc Mù Lòa (30 giây)" +lore2 = "Thuốc Vụng Về + Túi Mực Phát Sáng = Thuốc Mù Lòa-2 (15 giây)" +lore = ["Thuốc Vụng Về + Túi Mực = Thuốc Mù Lòa (30 giây)", "Thuốc Vụng Về + Túi Mực Phát Sáng = Thuốc Mù Lòa-2 (15 giây)"] [brewing.resistance] - name = "Kháng Cự Đóng Chai" - description = "Phòng thủ ở đỉnh cao!" - lore1 = "Thuốc Vụng Về + Thỏi Sắt = Thuốc Kháng Cự (60 giây)" - lore2 = "Thuốc Vụng Về + Khối Sắt = Thuốc Kháng Cự-2 (30 giây)" - lore = ["Thuốc Vụng Về + Thỏi Sắt = Thuốc Kháng Cự (60 giây)", "Thuốc Vụng Về + Khối Sắt = Thuốc Kháng Cự-2 (30 giây)"] +name = "Kháng Cự Đóng Chai" +description = "Phòng thủ ở đỉnh cao!" +lore1 = "Thuốc Vụng Về + Thỏi Sắt = Thuốc Kháng Cự (60 giây)" +lore2 = "Thuốc Vụng Về + Khối Sắt = Thuốc Kháng Cự-2 (30 giây)" +lore = ["Thuốc Vụng Về + Thỏi Sắt = Thuốc Kháng Cự (60 giây)", "Thuốc Vụng Về + Khối Sắt = Thuốc Kháng Cự-2 (30 giây)"] [brewing.health_boost] - name = "Sinh Mệnh Đóng Chai" - description = "Khi máu tối đa là chưa đủ..." - lore1 = "Thuốc Hồi Máu Tức Thì + Táo Vàng = Thuốc Tăng Máu (120 giây)" - lore2 = "Thuốc Hồi Máu Tức Thì + Táo Vàng Phù Phép = Thuốc Tăng Máu-2 (120 giây)" - lore = ["Thuốc Hồi Máu Tức Thì + Táo Vàng = Thuốc Tăng Máu (120 giây)", "Thuốc Hồi Máu Tức Thì + Táo Vàng Phù Phép = Thuốc Tăng Máu-2 (120 giây)"] +name = "Sinh Mệnh Đóng Chai" +description = "Khi máu tối đa là chưa đủ..." +lore1 = "Thuốc Hồi Máu Tức Thì + Táo Vàng = Thuốc Tăng Máu (120 giây)" +lore2 = "Thuốc Hồi Máu Tức Thì + Táo Vàng Phù Phép = Thuốc Tăng Máu-2 (120 giây)" +lore = ["Thuốc Hồi Máu Tức Thì + Táo Vàng = Thuốc Tăng Máu (120 giây)", "Thuốc Hồi Máu Tức Thì + Táo Vàng Phù Phép = Thuốc Tăng Máu-2 (120 giây)"] [brewing.decay] - name = "Phân Hủy Đóng Chai" - description = "Ai biết rác rưởi lại hữu ích đến vậy?" - lore1 = "Thuốc Yếu Đuối + Khoai Tây Độc = Thuốc Héo Úa (16 giây)" - lore2 = "Thuốc Yếu Đuối + Rễ Đỏ Thẫm = Thuốc Héo Úa-2 (8 giây)" - lore = ["Thuốc Yếu Đuối + Khoai Tây Độc = Thuốc Héo Úa (16 giây)", "Thuốc Yếu Đuối + Rễ Đỏ Thẫm = Thuốc Héo Úa-2 (8 giây)"] +name = "Phân Hủy Đóng Chai" +description = "Ai biết rác rưởi lại hữu ích đến vậy?" +lore1 = "Thuốc Yếu Đuối + Khoai Tây Độc = Thuốc Héo Úa (16 giây)" +lore2 = "Thuốc Yếu Đuối + Rễ Đỏ Thẫm = Thuốc Héo Úa-2 (8 giây)" +lore = ["Thuốc Yếu Đuối + Khoai Tây Độc = Thuốc Héo Úa (16 giây)", "Thuốc Yếu Đuối + Rễ Đỏ Thẫm = Thuốc Héo Úa-2 (8 giây)"] [brewing.saturation] - name = "Bão Hòa Đóng Chai" - description = "Biết không... Tôi thậm chí còn không đói..." - lore1 = "Thuốc Hồi Sinh + Khoai Tây Nướng = Thuốc Bão Hòa" - lore2 = "Thuốc Hồi Sinh + Bó Cỏ Khô = Thuốc Bão Hòa-2" - lore = ["Thuốc Hồi Sinh + Khoai Tây Nướng = Thuốc Bão Hòa", "Thuốc Hồi Sinh + Bó Cỏ Khô = Thuốc Bão Hòa-2"] +name = "Bão Hòa Đóng Chai" +description = "Biết không... Tôi thậm chí còn không đói..." +lore1 = "Thuốc Hồi Sinh + Khoai Tây Nướng = Thuốc Bão Hòa" +lore2 = "Thuốc Hồi Sinh + Bó Cỏ Khô = Thuốc Bão Hòa-2" +lore = ["Thuốc Hồi Sinh + Khoai Tây Nướng = Thuốc Bão Hòa", "Thuốc Hồi Sinh + Bó Cỏ Khô = Thuốc Bão Hòa-2"] # blocking [blocking] [blocking.chain_armorer] - name = "Xích Của Mephistopheles" - description = "Cho phép bạn chế tạo Giáp Xích" - lore1 = "Công thức chế tạo giống các giáp khác, nhưng dùng cốm sắt thay thế" - lore = ["Công thức chế tạo giống các giáp khác, nhưng dùng cốm sắt thay thế"] +name = "Xích Của Mephistopheles" +description = "Cho phép bạn chế tạo Giáp Xích" +lore1 = "Công thức chế tạo giống các giáp khác, nhưng dùng cốm sắt thay thế" +lore = ["Công thức chế tạo giống các giáp khác, nhưng dùng cốm sắt thay thế"] [blocking.horse_armorer] - name = "Giáp Ngựa Chế Tạo Được" - description = "Cho phép bạn chế tạo Giáp Ngựa" - lore1 = "Bao quanh yên ngựa bằng vật liệu bạn muốn dùng để chế tạo giáp" - lore = ["Bao quanh yên ngựa bằng vật liệu bạn muốn dùng để chế tạo giáp"] +name = "Giáp Ngựa Chế Tạo Được" +description = "Cho phép bạn chế tạo Giáp Ngựa" +lore1 = "Bao quanh yên ngựa bằng vật liệu bạn muốn dùng để chế tạo giáp" +lore = ["Bao quanh yên ngựa bằng vật liệu bạn muốn dùng để chế tạo giáp"] [blocking.saddle_crafter] - name = "Yên Ngựa Chế Tạo Được" - description = "Chế tạo Yên Ngựa bằng Da" - lore1 = "Công thức: 5 Da:" - lore = ["Công thức: 5 Da:"] +name = "Yên Ngựa Chế Tạo Được" +description = "Chế tạo Yên Ngựa bằng Da" +lore1 = "Công thức: 5 Da:" +lore = ["Công thức: 5 Da:"] [blocking.multi_armor] - name = "Đa Giáp" - description = "Gắn Elytra vào Giáp" - lore1 = "Đây là kỹ năng tuyệt vời để di chuyển." - lore2 = "Kết hợp và thay đổi Giáp/Elytra linh hoạt khi đang bay!" - lore3 = "Để kết hợp, shift + chuột vào vật phẩm trong túi đồ." - lore4 = "Để tháo Giáp, Ngồi + Thả vật phẩm, nó sẽ tự tháo rời." - lore5 = "Nếu Đa Giáp bị phá hủy, bạn sẽ mất tất cả vật phẩm trong đó." - lore6 = "Tôi không cần giáp, Nó làm tôi thất vọng..." - lore = ["Đây là kỹ năng tuyệt vời để di chuyển.", "Kết hợp và thay đổi Giáp/Elytra linh hoạt khi đang bay!", "Để kết hợp, shift + chuột vào vật phẩm trong túi đồ.", "Để tháo Giáp, Ngồi + Thả vật phẩm, nó sẽ tự tháo rời.", "Nếu Đa Giáp bị phá hủy, bạn sẽ mất tất cả vật phẩm trong đó.", "Tôi không cần giáp, Nó làm tôi thất vọng..."] +name = "Đa Giáp" +description = "Gắn Elytra vào Giáp" +lore1 = "Đây là kỹ năng tuyệt vời để di chuyển." +lore2 = "Kết hợp và thay đổi Giáp/Elytra linh hoạt khi đang bay!" +lore3 = "Để kết hợp, shift + chuột vào vật phẩm trong túi đồ." +lore4 = "Để tháo Giáp, Ngồi + Thả vật phẩm, nó sẽ tự tháo rời." +lore5 = "Nếu Đa Giáp bị phá hủy, bạn sẽ mất tất cả vật phẩm trong đó." +lore6 = "Tôi không cần giáp, Nó làm tôi thất vọng..." +lore = ["Đây là kỹ năng tuyệt vời để di chuyển.", "Kết hợp và thay đổi Giáp/Elytra linh hoạt khi đang bay!", "Để kết hợp, shift + chuột vào vật phẩm trong túi đồ.", "Để tháo Giáp, Ngồi + Thả vật phẩm, nó sẽ tự tháo rời.", "Nếu Đa Giáp bị phá hủy, bạn sẽ mất tất cả vật phẩm trong đó.", "Tôi không cần giáp, Nó làm tôi thất vọng..."] # crafting [crafting] [crafting.deconstruction] - name = "Tháo Dỡ" - description = "Tháo dỡ khối & vật phẩm thành nguyên liệu cơ bản có thể tái sử dụng!" - lore1 = "Thả bất kỳ vật phẩm nào xuống đất." - lore2 = "Sau đó, Ngồi và Chuột Phải bằng Kéo" - lore = ["Thả bất kỳ vật phẩm nào xuống đất.", "Sau đó, Ngồi và Chuột Phải bằng Kéo"] +name = "Tháo Dỡ" +description = "Tháo dỡ khối & vật phẩm thành nguyên liệu cơ bản có thể tái sử dụng!" +lore1 = "Thả bất kỳ vật phẩm nào xuống đất." +lore2 = "Sau đó, Ngồi và Chuột Phải bằng Kéo" +lore = ["Thả bất kỳ vật phẩm nào xuống đất.", "Sau đó, Ngồi và Chuột Phải bằng Kéo"] [crafting.xp] - name = "XP Chế Tạo" - description = "Nhận XP thụ động khi chế tạo" - lore1 = "Nhận XP khi chế tạo" - lore = ["Nhận XP khi chế tạo"] +name = "XP Chế Tạo" +description = "Nhận XP thụ động khi chế tạo" +lore1 = "Nhận XP khi chế tạo" +lore = ["Nhận XP khi chế tạo"] [crafting.reconstruction] - name = "Tái Tạo Quặng" - description = "Chế tạo lại quặng từ nguyên liệu cơ bản!" - lore1 = "8 Vật Phẩm Rơi và 1 Vật Chủ = 1 Quặng (không định hình)" - lore2 = "Vật phẩm rơi phải được nung (nếu áp dụng)" - lore3 = "Không bao gồm: Mảnh Vụn, Thạch Anh, và Ngọc Lục Bảo v.v..." - lore4 = "Vật Chủ = Lớp Bọc. vd: Đá, Đá Địa Ngục, Đá Sâu" - lore = ["8 Vật Phẩm Rơi và 1 Vật Chủ = 1 Quặng (không định hình)", "Vật phẩm rơi phải được nung (nếu áp dụng)", "Không bao gồm: Mảnh Vụn, Thạch Anh, và Ngọc Lục Bảo v.v...", "Vật Chủ = Lớp Bọc. vd: Đá, Đá Địa Ngục, Đá Sâu"] +name = "Tái Tạo Quặng" +description = "Chế tạo lại quặng từ nguyên liệu cơ bản!" +lore1 = "8 Vật Phẩm Rơi và 1 Vật Chủ = 1 Quặng (không định hình)" +lore2 = "Vật phẩm rơi phải được nung (nếu áp dụng)" +lore3 = "Không bao gồm: Mảnh Vụn, Thạch Anh, và Ngọc Lục Bảo v.v..." +lore4 = "Vật Chủ = Lớp Bọc. vd: Đá, Đá Địa Ngục, Đá Sâu" +lore = ["8 Vật Phẩm Rơi và 1 Vật Chủ = 1 Quặng (không định hình)", "Vật phẩm rơi phải được nung (nếu áp dụng)", "Không bao gồm: Mảnh Vụn, Thạch Anh, và Ngọc Lục Bảo v.v...", "Vật Chủ = Lớp Bọc. vd: Đá, Đá Địa Ngục, Đá Sâu"] [crafting.leather] - name = "Da Chế Tạo Được" - description = "Chế tạo Da từ Thịt Thối" - lore1 = "Chỉ cần ném (thịt thối) lên lửa trại!" - lore = ["Chỉ cần ném (thịt thối) lên lửa trại!"] +name = "Da Chế Tạo Được" +description = "Chế tạo Da từ Thịt Thối" +lore1 = "Chỉ cần ném (thịt thối) lên lửa trại!" +lore = ["Chỉ cần ném (thịt thối) lên lửa trại!"] [crafting.backpacks] - name = "Ba Lô Của Boutilier!" - description = "Mang tính năng Bó Đồ của Mojang vào trò chơi!" - lore1 = "Bạn cần ở chế độ Sinh Tồn để sử dụng" - lore2 = "XLX : Da, Dây Dắt, Da" - lore3 = "XSX : Da, Thùng, Da" - lore4 = "XCX : Da, Rương, Da" - lore = ["Bạn cần ở chế độ Sinh Tồn để sử dụng", "XLX : Da, Dây Dắt, Da", "XSX : Da, Thùng, Da", "XCX : Da, Rương, Da"] +name = "Ba Lô Của Boutilier!" +description = "Mang tính năng Bó Đồ của Mojang vào trò chơi!" +lore1 = "Bạn cần ở chế độ Sinh Tồn để sử dụng" +lore2 = "XLX : Da, Dây Dắt, Da" +lore3 = "XSX : Da, Thùng, Da" +lore4 = "XCX : Da, Rương, Da" +lore = ["Bạn cần ở chế độ Sinh Tồn để sử dụng", "XLX : Da, Dây Dắt, Da", "XSX : Da, Thùng, Da", "XCX : Da, Rương, Da"] [crafting.stations] - name = "Bàn Di Động!" - description = "Sử dụng bàn chế tạo ngay trong lòng bàn tay!" - lore2 = "MỌI VẬT PHẨM BẠN QUÊN TRONG BÀN KHI ĐÓNG SẼ MẤT VĨNH VIỄN!" - lore3 = "Bàn hợp lệ: Đe, Chế Tạo, Đá Mài, Bản Đồ Học, Máy Cắt Đá, Máy Dệt" - lore = ["MỌI VẬT PHẨM BẠN QUÊN TRONG BÀN KHI ĐÓNG SẼ MẤT VĨNH VIỄN!", "Bàn hợp lệ: Đe, Chế Tạo, Đá Mài, Bản Đồ Học, Máy Cắt Đá, Máy Dệt"] +name = "Bàn Di Động!" +description = "Sử dụng bàn chế tạo ngay trong lòng bàn tay!" +lore2 = "MỌI VẬT PHẨM BẠN QUÊN TRONG BÀN KHI ĐÓNG SẼ MẤT VĨNH VIỄN!" +lore3 = "Bàn hợp lệ: Đe, Chế Tạo, Đá Mài, Bản Đồ Học, Máy Cắt Đá, Máy Dệt" +lore = ["MỌI VẬT PHẨM BẠN QUÊN TRONG BÀN KHI ĐÓNG SẼ MẤT VĨNH VIỄN!", "Bàn hợp lệ: Đe, Chế Tạo, Đá Mài, Bản Đồ Học, Máy Cắt Đá, Máy Dệt"] [crafting.skulls] - name = "Đầu Lâu Chế Tạo Được!" - description = "Dùng Nguyên Liệu để Chế Tạo Đầu Lâu Quái Vật!" - lore1 = "Bao quanh Khối Xương bằng các vật liệu sau để nhận đầu lâu:" - lore2 = "Zombie: Thịt Thối" - lore3 = "Skeleton: Xương" - lore4 = "Creeper: Thuốc Súng" - lore5 = "Wither: Gạch Địa Ngục" - lore6 = "Rồng: Hơi Thở Rồng" - lore = ["Bao quanh Khối Xương bằng các vật liệu sau để nhận đầu lâu:", "Zombie: Thịt Thối", "Skeleton: Xương", "Creeper: Thuốc Súng", "Wither: Gạch Địa Ngục", "Rồng: Hơi Thở Rồng"] +name = "Đầu Lâu Chế Tạo Được!" +description = "Dùng Nguyên Liệu để Chế Tạo Đầu Lâu Quái Vật!" +lore1 = "Bao quanh Khối Xương bằng các vật liệu sau để nhận đầu lâu:" +lore2 = "Zombie: Thịt Thối" +lore3 = "Skeleton: Xương" +lore4 = "Creeper: Thuốc Súng" +lore5 = "Wither: Gạch Địa Ngục" +lore6 = "Rồng: Hơi Thở Rồng" +lore = ["Bao quanh Khối Xương bằng các vật liệu sau để nhận đầu lâu:", "Zombie: Thịt Thối", "Skeleton: Xương", "Creeper: Thuốc Súng", "Wither: Gạch Địa Ngục", "Rồng: Hơi Thở Rồng"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "Thời Gian Trong Chai" - description = "Mang theo một chai thời gian tích trữ thời gian và tiêu thụ để tăng tốc khối có bộ hẹn giờ, cây trồng, và thực thể có tuổi như động vật con. Công thức (Không Định Hình): Thuốc Tốc Độ + Đồng Hồ + Chai Thủy Tinh." - lore1 = "Số giây tích trữ mỗi tick" - lore2 = "Tăng tốc thời gian mỗi giây tích trữ" - lore3 = "Công thức (Không Định Hình): Thuốc Tốc Độ + Đồng Hồ + Chai Thủy Tinh" - lore = ["Số giây tích trữ mỗi tick", "Tăng tốc thời gian mỗi giây tích trữ", "Công thức (Không Định Hình): Thuốc Tốc Độ + Đồng Hồ + Chai Thủy Tinh"] +name = "Thời Gian Trong Chai" +description = "Mang theo một chai thời gian tích trữ thời gian và tiêu thụ để tăng tốc khối có bộ hẹn giờ, cây trồng, và thực thể có tuổi như động vật con. Công thức (Không Định Hình): Thuốc Tốc Độ + Đồng Hồ + Chai Thủy Tinh." +lore1 = "Số giây tích trữ mỗi tick" +lore2 = "Tăng tốc thời gian mỗi giây tích trữ" +lore3 = "Công thức (Không Định Hình): Thuốc Tốc Độ + Đồng Hồ + Chai Thủy Tinh" +lore = ["Số giây tích trữ mỗi tick", "Tăng tốc thời gian mỗi giây tích trữ", "Công thức (Không Định Hình): Thuốc Tốc Độ + Đồng Hồ + Chai Thủy Tinh"] [chronos.aberrant_touch] - name = "Chạm Dị Thường" - description = "Đòn cận chiến áp dụng chậm cộng dồn với cái giá là đói, với giới hạn PvP nghiêm ngặt, và ghim mục tiêu tại 5 cộng dồn." - lore1 = "Đòn cận chiến áp dụng chậm cộng dồn" - lore2 = "Giới hạn thời lượng chậm PvE" - lore3 = "Giới hạn cường độ chậm PvP" - lore = ["Đòn cận chiến áp dụng chậm cộng dồn", "Giới hạn thời lượng chậm PvE", "Giới hạn cường độ chậm PvP"] +name = "Chạm Dị Thường" +description = "Đòn cận chiến áp dụng chậm cộng dồn với cái giá là đói, với giới hạn PvP nghiêm ngặt, và ghim mục tiêu tại 5 cộng dồn." +lore1 = "Đòn cận chiến áp dụng chậm cộng dồn" +lore2 = "Giới hạn thời lượng chậm PvE" +lore3 = "Giới hạn cường độ chậm PvP" +lore = ["Đòn cận chiến áp dụng chậm cộng dồn", "Giới hạn thời lượng chậm PvE", "Giới hạn cường độ chậm PvP"] [chronos.instant_recall] - name = "Triệu Hồi Tức Thì" - description = "Chuột trái hoặc phải với đồng hồ trong tay để tua lại đến ảnh chụp gần đây với máu và đói được phục hồi." - lore1 = "Thời lượng tua lại" - lore2 = "Thời gian hồi" - lore3 = "Không hoàn lại túi đồ" - lore = ["Thời lượng tua lại", "Thời gian hồi", "Không hoàn lại túi đồ"] +name = "Triệu Hồi Tức Thì" +description = "Chuột trái hoặc phải với đồng hồ trong tay để tua lại đến ảnh chụp gần đây với máu và đói được phục hồi." +lore1 = "Thời lượng tua lại" +lore2 = "Thời gian hồi" +lore3 = "Không hoàn lại túi đồ" +lore = ["Thời lượng tua lại", "Thời gian hồi", "Không hoàn lại túi đồ"] [chronos.time_bomb] - name = "Bom Thời Gian" - description = "Ném một quả bom thời gian đã chế tạo tạo ra trường thời không, làm chậm thực thể, và đóng băng đạn đạo." - lore1 = "Bán kính trường thời không" - lore2 = "Thời lượng trường thời không" - lore3 = "Thời gian hồi bom" - lore4 = "Công thức (Không Định Hình): Đồng Hồ + Cầu Tuyết + Kim Cương + Cát" - lore = ["Bán kính trường thời không", "Thời lượng trường thời không", "Thời gian hồi bom", "Công thức (Không Định Hình): Đồng Hồ + Cầu Tuyết + Kim Cương + Cát"] +name = "Bom Thời Gian" +description = "Ném một quả bom thời gian đã chế tạo tạo ra trường thời không, làm chậm thực thể, và đóng băng đạn đạo." +lore1 = "Bán kính trường thời không" +lore2 = "Thời lượng trường thời không" +lore3 = "Thời gian hồi bom" +lore4 = "Công thức (Không Định Hình): Đồng Hồ + Cầu Tuyết + Kim Cương + Cát" +lore = ["Bán kính trường thời không", "Thời lượng trường thời không", "Thời gian hồi bom", "Công thức (Không Định Hình): Đồng Hồ + Cầu Tuyết + Kim Cương + Cát"] # discovery [discovery] [discovery.armor] - name = "Giáp Thế Giới" - description = "Giáp thụ động dựa trên độ cứng khối gần đó." - lore1 = "Giáp Thụ Động" - lore2 = "Dựa trên độ cứng khối gần đó" - lore3 = "Sức Mạnh Giáp:" - lore = ["Giáp Thụ Động", "Dựa trên độ cứng khối gần đó", "Sức Mạnh Giáp:"] +name = "Giáp Thế Giới" +description = "Giáp thụ động dựa trên độ cứng khối gần đó." +lore1 = "Giáp Thụ Động" +lore2 = "Dựa trên độ cứng khối gần đó" +lore3 = "Sức Mạnh Giáp:" +lore = ["Giáp Thụ Động", "Dựa trên độ cứng khối gần đó", "Sức Mạnh Giáp:"] [discovery.unity] - name = "Thống Nhất Thực Nghiệm" - description = "Thu thập Quả Cầu Kinh Nghiệm sẽ thêm XP vào kỹ năng ngẫu nhiên." - lore1 = "XP " - lore2 = "Mỗi Quả Cầu" - lore = ["XP ", "Mỗi Quả Cầu"] +name = "Thống Nhất Thực Nghiệm" +description = "Thu thập Quả Cầu Kinh Nghiệm sẽ thêm XP vào kỹ năng ngẫu nhiên." +lore1 = "XP " +lore2 = "Mỗi Quả Cầu" +lore = ["XP ", "Mỗi Quả Cầu"] [discovery.resist] - name = "Kháng Cự Thực Nghiệm" - description = "Tiêu thụ kinh nghiệm để giảm sát thương chỉ khi một đòn đánh sẽ hạ bạn xuống dưới 5 trái tim hoặc giết bạn." - lore0 = "Chỉ kích hoạt khi máu nguy hiểm (<= 5 trái tim) mỗi 15 giây" - lore1 = " Giảm Sát Thương" - lore2 = "kinh nghiệm bị tiêu hao" - lore = ["Chỉ kích hoạt khi máu nguy hiểm (<= 5 trái tim) mỗi 15 giây", " Giảm Sát Thương", "kinh nghiệm bị tiêu hao"] +name = "Kháng Cự Thực Nghiệm" +description = "Tiêu thụ kinh nghiệm để giảm sát thương chỉ khi một đòn đánh sẽ hạ bạn xuống dưới 5 trái tim hoặc giết bạn." +lore0 = "Chỉ kích hoạt khi máu nguy hiểm (<= 5 trái tim) mỗi 15 giây" +lore1 = " Giảm Sát Thương" +lore2 = "kinh nghiệm bị tiêu hao" +lore = ["Chỉ kích hoạt khi máu nguy hiểm (<= 5 trái tim) mỗi 15 giây", " Giảm Sát Thương", "kinh nghiệm bị tiêu hao"] [discovery.villager] - name = "Thu Hút Dân Làng" - description = "Cho phép bạn có giao dịch tốt hơn với dân làng!" - lore1 = "Tiêu tốn XP mỗi lần tương tác với Dân Làng" - lore2 = "Cơ hội mỗi lần tương tác để tiêu XP và nâng cấp giao dịch" - lore3 = "XP cần tiêu hao mỗi Lần Tương Tác" - lore = ["Tiêu tốn XP mỗi lần tương tác với Dân Làng", "Cơ hội mỗi lần tương tác để tiêu XP và nâng cấp giao dịch", "XP cần tiêu hao mỗi Lần Tương Tác"] +name = "Thu Hút Dân Làng" +description = "Cho phép bạn có giao dịch tốt hơn với dân làng!" +lore1 = "Tiêu tốn XP mỗi lần tương tác với Dân Làng" +lore2 = "Cơ hội mỗi lần tương tác để tiêu XP và nâng cấp giao dịch" +lore3 = "XP cần tiêu hao mỗi Lần Tương Tác" +lore = ["Tiêu tốn XP mỗi lần tương tác với Dân Làng", "Cơ hội mỗi lần tương tác để tiêu XP và nâng cấp giao dịch", "XP cần tiêu hao mỗi Lần Tương Tác"] # enchanting [enchanting] [enchanting.lapis_return] - name = "Hoàn Lapis" - description = "Với cái giá tăng thêm 1 cấp XP, và có cơ hội nhận lại lapis miễn phí" - lore1 = "Mỗi cấp độ tăng chi phí phù phép thêm 1, nhưng có thể trả lại tối đa 3 Lapis" - lore = ["Mỗi cấp độ tăng chi phí phù phép thêm 1, nhưng có thể trả lại tối đa 3 Lapis"] +name = "Hoàn Lapis" +description = "Với cái giá tăng thêm 1 cấp XP, và có cơ hội nhận lại lapis miễn phí" +lore1 = "Mỗi cấp độ tăng chi phí phù phép thêm 1, nhưng có thể trả lại tối đa 3 Lapis" +lore = ["Mỗi cấp độ tăng chi phí phù phép thêm 1, nhưng có thể trả lại tối đa 3 Lapis"] [enchanting.quick_enchant] - name = "Phù Phép Nhanh" - description = "Phù phép vật phẩm bằng cách bấm sách phù phép trực tiếp lên chúng." - lore1 = "Cấp Kết Hợp Tối Đa" - lore2 = "Không thể phù phép vật phẩm có nhiều hơn " - lore3 = "năng lượng" - lore = ["Cấp Kết Hợp Tối Đa", "Không thể phù phép vật phẩm có nhiều hơn ", "năng lượng"] +name = "Phù Phép Nhanh" +description = "Phù phép vật phẩm bằng cách bấm sách phù phép trực tiếp lên chúng." +lore1 = "Cấp Kết Hợp Tối Đa" +lore2 = "Không thể phù phép vật phẩm có nhiều hơn " +lore3 = "năng lượng" +lore = ["Cấp Kết Hợp Tối Đa", "Không thể phù phép vật phẩm có nhiều hơn ", "năng lượng"] [enchanting.return] - name = "Hoàn XP" - description = "XP phù phép được trả lại khi bạn phù phép vật phẩm." - lore1 = "Kinh nghiệm đã tiêu có cơ hội được hoàn lại khi phù phép" - lore2 = "Kinh Nghiệm mỗi Lần Phù Phép" - lore = ["Kinh nghiệm đã tiêu có cơ hội được hoàn lại khi phù phép", "Kinh Nghiệm mỗi Lần Phù Phép"] +name = "Hoàn XP" +description = "XP phù phép được trả lại khi bạn phù phép vật phẩm." +lore1 = "Kinh nghiệm đã tiêu có cơ hội được hoàn lại khi phù phép" +lore2 = "Kinh Nghiệm mỗi Lần Phù Phép" +lore = ["Kinh nghiệm đã tiêu có cơ hội được hoàn lại khi phù phép", "Kinh Nghiệm mỗi Lần Phù Phép"] # excavation [excavation] [excavation.haste] - name = "Đào Nhanh" - description = "Tăng tốc quá trình khai quật với HASTE!" - lore1 = "Nhận Haste khi đang khai quật" - lore2 = "x Cấp Haste khi bạn bắt đầu đào BẤT KỲ khối nào." - lore = ["Nhận Haste khi đang khai quật", "x Cấp Haste khi bạn bắt đầu đào BẤT KỲ khối nào."] +name = "Đào Nhanh" +description = "Tăng tốc quá trình khai quật với HASTE!" +lore1 = "Nhận Haste khi đang khai quật" +lore2 = "x Cấp Haste khi bạn bắt đầu đào BẤT KỲ khối nào." +lore = ["Nhận Haste khi đang khai quật", "x Cấp Haste khi bạn bắt đầu đào BẤT KỲ khối nào."] [excavation.spelunker] - name = "Thám Hiểm Hang Siêu Nhìn!" - description = "Nhìn thấy Quặng bằng mắt xuyên qua đất!" - lore1 = "Quặng ở tay phụ, Glowberry ở tay chính, và Ngồi!" - lore2 = "Phạm Vi Khối: " - lore3 = "Tiêu thụ Glowberry khi sử dụng" - lore = ["Quặng ở tay phụ, Glowberry ở tay chính, và Ngồi!", "Phạm Vi Khối: ", "Tiêu thụ Glowberry khi sử dụng"] +name = "Thám Hiểm Hang Siêu Nhìn!" +description = "Nhìn thấy Quặng bằng mắt xuyên qua đất!" +lore1 = "Quặng ở tay phụ, Glowberry ở tay chính, và Ngồi!" +lore2 = "Phạm Vi Khối: " +lore3 = "Tiêu thụ Glowberry khi sử dụng" +lore = ["Quặng ở tay phụ, Glowberry ở tay chính, và Ngồi!", "Phạm Vi Khối: ", "Tiêu thụ Glowberry khi sử dụng"] [excavation.drop_to_inventory] - name = "Xẻng Nhặt Vào Túi Đồ" +name = "Xẻng Nhặt Vào Túi Đồ" [excavation.omni_tool] - name = "DỤNG CỤ TOÀN NĂNG" - description = "Chiếc dao đa năng thiết kế cầu kỳ của Tackle" - lore1 = "Có lẽ mạnh nhất trong tất cả, cho phép bạn" - lore2 = "linh hoạt kết hợp và thay đổi công cụ khi đang dùng, tùy nhu cầu." - lore3 = "Để kết hợp, shift + chuột vào vật phẩm trong túi đồ." - lore4 = "Để tháo công cụ, Ngồi + Thả vật phẩm, nó sẽ tự tháo rời." - lore5 = "Bạn không thể phá hủy công cụ trong dụng cụ toàn năng nhưng không thể dùng công cụ hỏng" - lore6 = "tổng vật phẩm có thể kết hợp." - lore7 = "Bạn có thể dùng năm sáu công cụ, hoặc chỉ một!" - lore = ["Có lẽ mạnh nhất trong tất cả, cho phép bạn", "linh hoạt kết hợp và thay đổi công cụ khi đang dùng, tùy nhu cầu.", "Để kết hợp, shift + chuột vào vật phẩm trong túi đồ.", "Để tháo công cụ, Ngồi + Thả vật phẩm, nó sẽ tự tháo rời.", "Bạn không thể phá hủy công cụ trong dụng cụ toàn năng nhưng không thể dùng công cụ hỏng", "tổng vật phẩm có thể kết hợp.", "Bạn có thể dùng năm sáu công cụ, hoặc chỉ một!"] +name = "DỤNG CỤ TOÀN NĂNG" +description = "Chiếc dao đa năng thiết kế cầu kỳ của Tackle" +lore1 = "Có lẽ mạnh nhất trong tất cả, cho phép bạn" +lore2 = "linh hoạt kết hợp và thay đổi công cụ khi đang dùng, tùy nhu cầu." +lore3 = "Để kết hợp, shift + chuột vào vật phẩm trong túi đồ." +lore4 = "Để tháo công cụ, Ngồi + Thả vật phẩm, nó sẽ tự tháo rời." +lore5 = "Bạn không thể phá hủy công cụ trong dụng cụ toàn năng nhưng không thể dùng công cụ hỏng" +lore6 = "tổng vật phẩm có thể kết hợp." +lore7 = "Bạn có thể dùng năm sáu công cụ, hoặc chỉ một!" +lore = ["Có lẽ mạnh nhất trong tất cả, cho phép bạn", "linh hoạt kết hợp và thay đổi công cụ khi đang dùng, tùy nhu cầu.", "Để kết hợp, shift + chuột vào vật phẩm trong túi đồ.", "Để tháo công cụ, Ngồi + Thả vật phẩm, nó sẽ tự tháo rời.", "Bạn không thể phá hủy công cụ trong dụng cụ toàn năng nhưng không thể dùng công cụ hỏng", "tổng vật phẩm có thể kết hợp.", "Bạn có thể dùng năm sáu công cụ, hoặc chỉ một!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "Hào Quang Tăng Trưởng" - description = "Phát triển thiên nhiên xung quanh bạn trong một hào quang" - lore1 = "Bán Kính Khối" - lore2 = "Sức Mạnh Hào Quang Tăng Trưởng" - lore3 = "Chi Phí Thức Ăn" - lore = ["Bán Kính Khối", "Sức Mạnh Hào Quang Tăng Trưởng", "Chi Phí Thức Ăn"] +name = "Hào Quang Tăng Trưởng" +description = "Phát triển thiên nhiên xung quanh bạn trong một hào quang" +lore1 = "Bán Kính Khối" +lore2 = "Sức Mạnh Hào Quang Tăng Trưởng" +lore3 = "Chi Phí Thức Ăn" +lore = ["Bán Kính Khối", "Sức Mạnh Hào Quang Tăng Trưởng", "Chi Phí Thức Ăn"] [herbalism.hippo] - name = "Hà Mã Thảo Dược" - description = "Ăn thức ăn cho bạn thêm độ no" - lore1 = "Thức ăn) điểm bão hòa thêm khi ăn" - lore = ["Thức ăn) điểm bão hòa thêm khi ăn"] +name = "Hà Mã Thảo Dược" +description = "Ăn thức ăn cho bạn thêm độ no" +lore1 = "Thức ăn) điểm bão hòa thêm khi ăn" +lore = ["Thức ăn) điểm bão hòa thêm khi ăn"] [herbalism.myconid] - name = "Nấm Sợi Thảo Dược" - description = "Cho bạn khả năng chế tạo Sợi Nấm" - lore1 = "Bất kỳ Đất nào, và một Nấm Nâu & Đỏ sẽ tạo ra Sợi Nấm." - lore = ["Bất kỳ Đất nào, và một Nấm Nâu & Đỏ sẽ tạo ra Sợi Nấm."] +name = "Nấm Sợi Thảo Dược" +description = "Cho bạn khả năng chế tạo Sợi Nấm" +lore1 = "Bất kỳ Đất nào, và một Nấm Nâu & Đỏ sẽ tạo ra Sợi Nấm." +lore = ["Bất kỳ Đất nào, và một Nấm Nâu & Đỏ sẽ tạo ra Sợi Nấm."] [herbalism.terralid] - name = "Khối Cỏ Thảo Dược" - description = "Cho bạn khả năng chế tạo Khối Cỏ" - lore1 = "Ba Hạt Giống, trên 3 Đất, sẽ tạo ra 3 Khối Cỏ." - lore = ["Ba Hạt Giống, trên 3 Đất, sẽ tạo ra 3 Khối Cỏ."] +name = "Khối Cỏ Thảo Dược" +description = "Cho bạn khả năng chế tạo Khối Cỏ" +lore1 = "Ba Hạt Giống, trên 3 Đất, sẽ tạo ra 3 Khối Cỏ." +lore = ["Ba Hạt Giống, trên 3 Đất, sẽ tạo ra 3 Khối Cỏ."] [herbalism.cobweb] - name = "Thợ Dệt Mạng" - description = "Cho bạn khả năng chế tạo Mạng Nhện trong Bàn Chế Tạo" - lore1 = "Chín Sợi Dây sẽ tạo ra một Mạng Nhện." - lore = ["Chín Sợi Dây sẽ tạo ra một Mạng Nhện."] +name = "Thợ Dệt Mạng" +description = "Cho bạn khả năng chế tạo Mạng Nhện trong Bàn Chế Tạo" +lore1 = "Chín Sợi Dây sẽ tạo ra một Mạng Nhện." +lore = ["Chín Sợi Dây sẽ tạo ra một Mạng Nhện."] [herbalism.mushroom_blocks] - name = "Chế Tạo Khối Nấm" - description = "Cho bạn khả năng chế tạo Khối Nấm trong Bàn Chế Tạo" - lore1 = "Bốn Nấm để tạo khối, hoặc một khối để tạo thân cây." - lore = ["Bốn Nấm để tạo khối, hoặc một khối để tạo thân cây."] +name = "Chế Tạo Khối Nấm" +description = "Cho bạn khả năng chế tạo Khối Nấm trong Bàn Chế Tạo" +lore1 = "Bốn Nấm để tạo khối, hoặc một khối để tạo thân cây." +lore = ["Bốn Nấm để tạo khối, hoặc một khối để tạo thân cây."] [herbalism.drop_to_inventory] - name = "Cuốc Nhặt Vào Túi Đồ" +name = "Cuốc Nhặt Vào Túi Đồ" [herbalism.hungry_shield] - name = "Khiên Đói" - description = "Nhận sát thương vào thanh đói trước thanh máu." - lore1 = "Kháng Bằng Đói" - lore = ["Kháng Bằng Đói"] +name = "Khiên Đói" +description = "Nhận sát thương vào thanh đói trước thanh máu." +lore1 = "Kháng Bằng Đói" +lore = ["Kháng Bằng Đói"] [herbalism.luck] - name = "May Mắn Thảo Dược" - description = "Khi phá Cỏ/Hoa, bạn có cơ hội nhận vật phẩm ngẫu nhiên" - lore0 = "Hoa = Thức Ăn, và Cỏ = Hạt Giống" - lore1 = "Cơ hội nhận vật phẩm từ phá Hoa" - lore2 = "Cơ hội nhận vật phẩm từ phá Cỏ" - lore = ["Hoa = Thức Ăn, và Cỏ = Hạt Giống", "Cơ hội nhận vật phẩm từ phá Hoa", "Cơ hội nhận vật phẩm từ phá Cỏ"] +name = "May Mắn Thảo Dược" +description = "Khi phá Cỏ/Hoa, bạn có cơ hội nhận vật phẩm ngẫu nhiên" +lore0 = "Hoa = Thức Ăn, và Cỏ = Hạt Giống" +lore1 = "Cơ hội nhận vật phẩm từ phá Hoa" +lore2 = "Cơ hội nhận vật phẩm từ phá Cỏ" +lore = ["Hoa = Thức Ăn, và Cỏ = Hạt Giống", "Cơ hội nhận vật phẩm từ phá Hoa", "Cơ hội nhận vật phẩm từ phá Cỏ"] [herbalism.replant] - name = "Thu Hoạch & Trồng Lại" - description = "Chuột phải vào cây trồng bằng cuốc để thu hoạch & trồng lại." - lore1 = "Bán Kính Trồng Lại" - lore = ["Bán Kính Trồng Lại"] +name = "Thu Hoạch & Trồng Lại" +description = "Chuột phải vào cây trồng bằng cuốc để thu hoạch & trồng lại." +lore1 = "Bán Kính Trồng Lại" +lore = ["Bán Kính Trồng Lại"] # hunter [hunter] [hunter.adrenaline] - name = "Adrenaline" - description = "Gây thêm sát thương khi máu bạn càng thấp (Cận Chiến)" - lore1 = "Sát Thương Tối Đa" - lore = ["Sát Thương Tối Đa"] +name = "Adrenaline" +description = "Gây thêm sát thương khi máu bạn càng thấp (Cận Chiến)" +lore1 = "Sát Thương Tối Đa" +lore = ["Sát Thương Tối Đa"] [hunter.penalty] - name = "" - description = "" - lore1 = "Bạn sẽ bị Độc nếu hết thanh đói" - lore = ["Bạn sẽ bị Độc nếu hết thanh đói"] +name = "" +description = "" +lore1 = "Bạn sẽ bị Độc nếu hết thanh đói" +lore = ["Bạn sẽ bị Độc nếu hết thanh đói"] [hunter.drop_to_inventory] - name = "Vật Phẩm Nhặt Vào Túi Đồ" - description = "Khi bạn giết thứ gì đó / Phá khối bằng kiếm, vật phẩm rơi sẽ dịch chuyển vào túi đồ" - lore1 = "Bất cứ khi nào vật phẩm rơi từ quái/khối bạn phá, nó sẽ vào túi đồ nếu có chỗ." - lore = ["Bất cứ khi nào vật phẩm rơi từ quái/khối bạn phá, nó sẽ vào túi đồ nếu có chỗ."] +name = "Vật Phẩm Nhặt Vào Túi Đồ" +description = "Khi bạn giết thứ gì đó / Phá khối bằng kiếm, vật phẩm rơi sẽ dịch chuyển vào túi đồ" +lore1 = "Bất cứ khi nào vật phẩm rơi từ quái/khối bạn phá, nó sẽ vào túi đồ nếu có chỗ." +lore = ["Bất cứ khi nào vật phẩm rơi từ quái/khối bạn phá, nó sẽ vào túi đồ nếu có chỗ."] [hunter.invisibility] - name = "Bước Biến Mất" - description = "Khi bị đánh, bạn nhận được tàng hình, với cái giá là đói" - lore1 = "Nhận tàng hình thụ động khi bị đánh" - lore2 = "x Cộng dồn tàng hình trong 3 giây mỗi đòn" - lore3 = "x Cộng dồn đói" - lore4 = "Thời lượng và hệ số cộng dồn đói." - lore5 = "Thời lượng tàng hình" - lore = ["Nhận tàng hình thụ động khi bị đánh", "x Cộng dồn tàng hình trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Thời lượng tàng hình"] +name = "Bước Biến Mất" +description = "Khi bị đánh, bạn nhận được tàng hình, với cái giá là đói" +lore1 = "Nhận tàng hình thụ động khi bị đánh" +lore2 = "x Cộng dồn tàng hình trong 3 giây mỗi đòn" +lore3 = "x Cộng dồn đói" +lore4 = "Thời lượng và hệ số cộng dồn đói." +lore5 = "Thời lượng tàng hình" +lore = ["Nhận tàng hình thụ động khi bị đánh", "x Cộng dồn tàng hình trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Thời lượng tàng hình"] [hunter.jump_boost] - name = "Đỉnh Cao Thợ Săn" - description = "Khi bị đánh, bạn nhận được tăng nhảy, với cái giá là đói" - lore1 = "Nhận tăng nhảy thụ động khi bị đánh" - lore2 = "x Cộng dồn tăng nhảy trong 3 giây mỗi đòn" - lore3 = "x Cộng dồn đói" - lore4 = "Thời lượng và hệ số cộng dồn đói." - lore5 = "Hệ số cộng dồn tăng nhảy, không phải thời lượng." - lore = ["Nhận tăng nhảy thụ động khi bị đánh", "x Cộng dồn tăng nhảy trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn tăng nhảy, không phải thời lượng."] +name = "Đỉnh Cao Thợ Săn" +description = "Khi bị đánh, bạn nhận được tăng nhảy, với cái giá là đói" +lore1 = "Nhận tăng nhảy thụ động khi bị đánh" +lore2 = "x Cộng dồn tăng nhảy trong 3 giây mỗi đòn" +lore3 = "x Cộng dồn đói" +lore4 = "Thời lượng và hệ số cộng dồn đói." +lore5 = "Hệ số cộng dồn tăng nhảy, không phải thời lượng." +lore = ["Nhận tăng nhảy thụ động khi bị đánh", "x Cộng dồn tăng nhảy trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn tăng nhảy, không phải thời lượng."] [hunter.luck] - name = "May Mắn Thợ Săn" - description = "Khi bị đánh, bạn nhận được may mắn, với cái giá là đói" - lore1 = "Nhận may mắn thụ động khi bị đánh" - lore2 = "x Cộng dồn may mắn trong 3 giây mỗi đòn" - lore3 = "x Cộng dồn đói" - lore4 = "Thời lượng và hệ số cộng dồn đói." - lore5 = "Hệ số cộng dồn may mắn, không phải thời lượng." - lore = ["Nhận may mắn thụ động khi bị đánh", "x Cộng dồn may mắn trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn may mắn, không phải thời lượng."] +name = "May Mắn Thợ Săn" +description = "Khi bị đánh, bạn nhận được may mắn, với cái giá là đói" +lore1 = "Nhận may mắn thụ động khi bị đánh" +lore2 = "x Cộng dồn may mắn trong 3 giây mỗi đòn" +lore3 = "x Cộng dồn đói" +lore4 = "Thời lượng và hệ số cộng dồn đói." +lore5 = "Hệ số cộng dồn may mắn, không phải thời lượng." +lore = ["Nhận may mắn thụ động khi bị đánh", "x Cộng dồn may mắn trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn may mắn, không phải thời lượng."] [hunter.regen] - name = "Hồi Phục Thợ Săn" - description = "Khi bị đánh, bạn nhận được hồi phục, với cái giá là đói" - lore1 = "Nhận hồi phục thụ động khi bị đánh" - lore2 = "x Cộng dồn hồi phục trong 3 giây mỗi đòn" - lore3 = "x Cộng dồn đói" - lore4 = "Thời lượng và hệ số cộng dồn đói." - lore5 = "Hệ số cộng dồn hồi phục, không phải thời lượng." - lore = ["Nhận hồi phục thụ động khi bị đánh", "x Cộng dồn hồi phục trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn hồi phục, không phải thời lượng."] +name = "Hồi Phục Thợ Săn" +description = "Khi bị đánh, bạn nhận được hồi phục, với cái giá là đói" +lore1 = "Nhận hồi phục thụ động khi bị đánh" +lore2 = "x Cộng dồn hồi phục trong 3 giây mỗi đòn" +lore3 = "x Cộng dồn đói" +lore4 = "Thời lượng và hệ số cộng dồn đói." +lore5 = "Hệ số cộng dồn hồi phục, không phải thời lượng." +lore = ["Nhận hồi phục thụ động khi bị đánh", "x Cộng dồn hồi phục trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn hồi phục, không phải thời lượng."] [hunter.resistance] - name = "Kháng Cự Thợ Săn" - description = "Khi bị đánh, bạn nhận được kháng cự, với cái giá là đói" - lore1 = "Nhận kháng cự thụ động khi bị đánh" - lore2 = "x Cộng dồn kháng cự trong 3 giây mỗi đòn" - lore3 = "x Cộng dồn đói" - lore4 = "Thời lượng và hệ số cộng dồn đói." - lore5 = "Hệ số cộng dồn kháng cự, không phải thời lượng." - lore = ["Nhận kháng cự thụ động khi bị đánh", "x Cộng dồn kháng cự trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn kháng cự, không phải thời lượng."] +name = "Kháng Cự Thợ Săn" +description = "Khi bị đánh, bạn nhận được kháng cự, với cái giá là đói" +lore1 = "Nhận kháng cự thụ động khi bị đánh" +lore2 = "x Cộng dồn kháng cự trong 3 giây mỗi đòn" +lore3 = "x Cộng dồn đói" +lore4 = "Thời lượng và hệ số cộng dồn đói." +lore5 = "Hệ số cộng dồn kháng cự, không phải thời lượng." +lore = ["Nhận kháng cự thụ động khi bị đánh", "x Cộng dồn kháng cự trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn kháng cự, không phải thời lượng."] [hunter.speed] - name = "Tốc Độ Thợ Săn" - description = "Khi bị đánh, bạn nhận được tốc độ, với cái giá là đói" - lore1 = "Nhận tốc độ thụ động khi bị đánh" - lore2 = "x Cộng dồn tốc độ trong 3 giây mỗi đòn" - lore3 = "x Cộng dồn đói" - lore4 = "Thời lượng và hệ số cộng dồn đói." - lore5 = "Hệ số cộng dồn tốc độ, không phải thời lượng." - lore = ["Nhận tốc độ thụ động khi bị đánh", "x Cộng dồn tốc độ trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn tốc độ, không phải thời lượng."] +name = "Tốc Độ Thợ Săn" +description = "Khi bị đánh, bạn nhận được tốc độ, với cái giá là đói" +lore1 = "Nhận tốc độ thụ động khi bị đánh" +lore2 = "x Cộng dồn tốc độ trong 3 giây mỗi đòn" +lore3 = "x Cộng dồn đói" +lore4 = "Thời lượng và hệ số cộng dồn đói." +lore5 = "Hệ số cộng dồn tốc độ, không phải thời lượng." +lore = ["Nhận tốc độ thụ động khi bị đánh", "x Cộng dồn tốc độ trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn tốc độ, không phải thời lượng."] [hunter.strength] - name = "Sức Mạnh Thợ Săn" - description = "Khi bị đánh, bạn nhận được sức mạnh, với cái giá là đói" - lore1 = "Nhận sức mạnh thụ động khi bị đánh" - lore2 = "x Cộng dồn sức mạnh trong 3 giây mỗi đòn" - lore3 = "x Cộng dồn đói" - lore4 = "Thời lượng và hệ số cộng dồn đói." - lore5 = "Hệ số cộng dồn sức mạnh, không phải thời lượng." - lore = ["Nhận sức mạnh thụ động khi bị đánh", "x Cộng dồn sức mạnh trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn sức mạnh, không phải thời lượng."] +name = "Sức Mạnh Thợ Săn" +description = "Khi bị đánh, bạn nhận được sức mạnh, với cái giá là đói" +lore1 = "Nhận sức mạnh thụ động khi bị đánh" +lore2 = "x Cộng dồn sức mạnh trong 3 giây mỗi đòn" +lore3 = "x Cộng dồn đói" +lore4 = "Thời lượng và hệ số cộng dồn đói." +lore5 = "Hệ số cộng dồn sức mạnh, không phải thời lượng." +lore = ["Nhận sức mạnh thụ động khi bị đánh", "x Cộng dồn sức mạnh trong 3 giây mỗi đòn", "x Cộng dồn đói", "Thời lượng và hệ số cộng dồn đói.", "Hệ số cộng dồn sức mạnh, không phải thời lượng."] # nether [nether] [nether.skull_toss] - name = "Ném Đầu Lâu Wither" - description1 = "Giải phóng Wither bên trong bạn bằng cách dùng" - description2 = "đầu" - description3 = "của ai đó." - lore1 = "Giây hồi giữa các lần ném đầu lâu." - lore2 = "Dùng Đầu Lâu Wither: Ném một " - lore3 = "Đầu Lâu Wither" - lore4 = "nổ khi va chạm." - lore = ["Giây hồi giữa các lần ném đầu lâu.", "Dùng Đầu Lâu Wither: Ném một ", "Đầu Lâu Wither", "nổ khi va chạm."] +name = "Ném Đầu Lâu Wither" +description1 = "Giải phóng Wither bên trong bạn bằng cách dùng" +description2 = "đầu" +description3 = "của ai đó." +lore1 = "Giây hồi giữa các lần ném đầu lâu." +lore2 = "Dùng Đầu Lâu Wither: Ném một " +lore3 = "Đầu Lâu Wither" +lore4 = "nổ khi va chạm." +lore = ["Giây hồi giữa các lần ném đầu lâu.", "Dùng Đầu Lâu Wither: Ném một ", "Đầu Lâu Wither", "nổ khi va chạm."] [nether.wither_resist] - name = "Kháng Wither" - description = "Kháng hiệu ứng héo úa nhờ sức mạnh của Netherite." - lore1 = "cơ hội vô hiệu hóa héo úa (mỗi mảnh giáp)." - lore2 = "Thụ động: Mặc Giáp Netherite có cơ hội vô hiệu hóa " - lore3 = "hiệu ứng héo úa." - lore = ["cơ hội vô hiệu hóa héo úa (mỗi mảnh giáp).", "Thụ động: Mặc Giáp Netherite có cơ hội vô hiệu hóa ", "hiệu ứng héo úa."] +name = "Kháng Wither" +description = "Kháng hiệu ứng héo úa nhờ sức mạnh của Netherite." +lore1 = "cơ hội vô hiệu hóa héo úa (mỗi mảnh giáp)." +lore2 = "Thụ động: Mặc Giáp Netherite có cơ hội vô hiệu hóa " +lore3 = "hiệu ứng héo úa." +lore = ["cơ hội vô hiệu hóa héo úa (mỗi mảnh giáp).", "Thụ động: Mặc Giáp Netherite có cơ hội vô hiệu hóa ", "hiệu ứng héo úa."] [nether.fire_resist] - name = "Kháng Lửa" - description = "Kháng lửa bằng cách làm cứng da." - lore1 = "cơ hội vô hiệu hóa hiệu ứng cháy!" - lore = ["cơ hội vô hiệu hóa hiệu ứng cháy!"] +name = "Kháng Lửa" +description = "Kháng lửa bằng cách làm cứng da." +lore1 = "cơ hội vô hiệu hóa hiệu ứng cháy!" +lore = ["cơ hội vô hiệu hóa hiệu ứng cháy!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "Tự Động Nung" - description = "Cho phép bạn nung quặng Vanilla khi khai thác" - lore1 = "Quặng có thể nung sẽ được nung tự động" - lore2 = "% cơ hội nhận thêm" - lore = ["Quặng có thể nung sẽ được nung tự động", "% cơ hội nhận thêm"] +name = "Tự Động Nung" +description = "Cho phép bạn nung quặng Vanilla khi khai thác" +lore1 = "Quặng có thể nung sẽ được nung tự động" +lore2 = "% cơ hội nhận thêm" +lore = ["Quặng có thể nung sẽ được nung tự động", "% cơ hội nhận thêm"] [pickaxe.chisel] - name = "Đục Quặng" - description = "Chuột Phải vào Quặng để Đục thêm quặng, với cái giá hao mòn độ bền cao." - lore1 = "Cơ Hội Rơi" - lore2 = "Hao Mòn Công Cụ" - lore = ["Cơ Hội Rơi", "Hao Mòn Công Cụ"] +name = "Đục Quặng" +description = "Chuột Phải vào Quặng để Đục thêm quặng, với cái giá hao mòn độ bền cao." +lore1 = "Cơ Hội Rơi" +lore2 = "Hao Mòn Công Cụ" +lore = ["Cơ Hội Rơi", "Hao Mòn Công Cụ"] [pickaxe.drop_to_inventory] - name = "Cuốc Chim Nhặt Vào Túi Đồ" - description = "Khi bạn phá khối, vật phẩm dịch chuyển vào túi đồ" - lore1 = "Bất cứ khi nào vật phẩm rơi từ khối bạn phá, nó sẽ vào túi đồ nếu có chỗ." - lore = ["Bất cứ khi nào vật phẩm rơi từ khối bạn phá, nó sẽ vào túi đồ nếu có chỗ."] +name = "Cuốc Chim Nhặt Vào Túi Đồ" +description = "Khi bạn phá khối, vật phẩm dịch chuyển vào túi đồ" +lore1 = "Bất cứ khi nào vật phẩm rơi từ khối bạn phá, nó sẽ vào túi đồ nếu có chỗ." +lore = ["Bất cứ khi nào vật phẩm rơi từ khối bạn phá, nó sẽ vào túi đồ nếu có chỗ."] [pickaxe.silk_spawner] - name = "Cuốc Chim Lấy Spawner" - description = "Spawner sẽ rơi ra khi bị phá" - lore1 = "Spawner có thể phá bằng Cảm Ứng Lụa." - lore2 = "Spawner có thể phá khi đang ngồi." - lore = ["Spawner có thể phá bằng Cảm Ứng Lụa.", "Spawner có thể phá khi đang ngồi."] +name = "Cuốc Chim Lấy Spawner" +description = "Spawner sẽ rơi ra khi bị phá" +lore1 = "Spawner có thể phá bằng Cảm Ứng Lụa." +lore2 = "Spawner có thể phá khi đang ngồi." +lore = ["Spawner có thể phá bằng Cảm Ứng Lụa.", "Spawner có thể phá khi đang ngồi."] [pickaxe.vein_miner] - name = "Đào Mạch Quặng" - description = "Cho phép bạn phá khối theo Mạch/Cụm quặng Vanilla" - lore1 = "Ngồi, và đào QUẶNG" - lore2 = "phạm vi đào mạch quặng" - lore3 = "Kỹ năng này KHÔNG gom tất cả vật phẩm rơi lại!" - lore = ["Ngồi, và đào QUẶNG", "phạm vi đào mạch quặng", "Kỹ năng này KHÔNG gom tất cả vật phẩm rơi lại!"] +name = "Đào Mạch Quặng" +description = "Cho phép bạn phá khối theo Mạch/Cụm quặng Vanilla" +lore1 = "Ngồi, và đào QUẶNG" +lore2 = "phạm vi đào mạch quặng" +lore3 = "Kỹ năng này KHÔNG gom tất cả vật phẩm rơi lại!" +lore = ["Ngồi, và đào QUẶNG", "phạm vi đào mạch quặng", "Kỹ năng này KHÔNG gom tất cả vật phẩm rơi lại!"] # ranged [ranged] [ranged.arrow_recovery] - name = "Thu Hồi Mũi Tên" - description = "Thu hồi Mũi Tên sau khi bạn tiêu diệt kẻ thù." - lore1 = "Cơ hội Thu Hồi Mũi Tên khi Trúng/Hạ Gục" - lore2 = "Cơ hội: " - lore = ["Cơ hội Thu Hồi Mũi Tên khi Trúng/Hạ Gục", "Cơ hội: "] +name = "Thu Hồi Mũi Tên" +description = "Thu hồi Mũi Tên sau khi bạn tiêu diệt kẻ thù." +lore1 = "Cơ hội Thu Hồi Mũi Tên khi Trúng/Hạ Gục" +lore2 = "Cơ hội: " +lore = ["Cơ hội Thu Hồi Mũi Tên khi Trúng/Hạ Gục", "Cơ hội: "] [ranged.web_shot] - name = "Bẫy Mạng Nhện" - description = "Bao quanh mục tiêu bằng mạng nhện khi bạn bắn trúng!" - lore1 = "8 Mạng Nhện quanh Cầu Tuyết, và ném!" - lore2 = "giây bẫy lồng, xấp xỉ." - lore = ["8 Mạng Nhện quanh Cầu Tuyết, và ném!", "giây bẫy lồng, xấp xỉ."] +name = "Bẫy Mạng Nhện" +description = "Bao quanh mục tiêu bằng mạng nhện khi bạn bắn trúng!" +lore1 = "8 Mạng Nhện quanh Cầu Tuyết, và ném!" +lore2 = "giây bẫy lồng, xấp xỉ." +lore = ["8 Mạng Nhện quanh Cầu Tuyết, và ném!", "giây bẫy lồng, xấp xỉ."] [ranged.force_shot] - name = "Phát Bắn Mạnh" - description = "Bắn đạn xa hơn, nhanh hơn!" - advancementname = "Phát Bắn Xa" - advancementlore = "Bắn trúng mục tiêu từ hơn 30 khối!" - lore1 = "Tốc Độ Đạn" - lore = ["Tốc Độ Đạn"] +name = "Phát Bắn Mạnh" +description = "Bắn đạn xa hơn, nhanh hơn!" +advancementname = "Phát Bắn Xa" +advancementlore = "Bắn trúng mục tiêu từ hơn 30 khối!" +lore1 = "Tốc Độ Đạn" +lore = ["Tốc Độ Đạn"] [ranged.lunge_shot] - name = "Phát Bắn Lao" - description = "Khi đang rơi, mũi tên đẩy bạn theo hướng ngẫu nhiên" - lore1 = "Tốc Độ Bùng Nổ Ngẫu Nhiên" - lore = ["Tốc Độ Bùng Nổ Ngẫu Nhiên"] +name = "Phát Bắn Lao" +description = "Khi đang rơi, mũi tên đẩy bạn theo hướng ngẫu nhiên" +lore1 = "Tốc Độ Bùng Nổ Ngẫu Nhiên" +lore = ["Tốc Độ Bùng Nổ Ngẫu Nhiên"] [ranged.arrow_piercing] - name = "Mũi Tên Xuyên Thấu" - description = "Thêm Xuyên Thấu cho đạn! Bắn xuyên qua mọi thứ!" - lore1 = "Mục Tiêu Xuyên Qua" - lore = ["Mục Tiêu Xuyên Qua"] +name = "Mũi Tên Xuyên Thấu" +description = "Thêm Xuyên Thấu cho đạn! Bắn xuyên qua mọi thứ!" +lore1 = "Mục Tiêu Xuyên Qua" +lore = ["Mục Tiêu Xuyên Qua"] # rift [rift] [rift.remote_access] - name = "Truy Cập Từ Xa" - description = "Kéo từ hư không và truy cập thùng chứa đã đánh dấu." - lore1 = "Ngọc Trai Ender + La Bàn = Chìa Khóa Thánh Tích" - lore2 = "Vật phẩm này cho phép bạn truy cập thùng chứa từ xa" - lore3 = "Sau khi chế tạo, nhìn vào vật phẩm để xem cách dùng" - notcontainer = "Đó không phải thùng chứa" - lore = ["Ngọc Trai Ender + La Bàn = Chìa Khóa Thánh Tích", "Vật phẩm này cho phép bạn truy cập thùng chứa từ xa", "Sau khi chế tạo, nhìn vào vật phẩm để xem cách dùng"] +name = "Truy Cập Từ Xa" +description = "Kéo từ hư không và truy cập thùng chứa đã đánh dấu." +lore1 = "Ngọc Trai Ender + La Bàn = Chìa Khóa Thánh Tích" +lore2 = "Vật phẩm này cho phép bạn truy cập thùng chứa từ xa" +lore3 = "Sau khi chế tạo, nhìn vào vật phẩm để xem cách dùng" +notcontainer = "Đó không phải thùng chứa" +lore = ["Ngọc Trai Ender + La Bàn = Chìa Khóa Thánh Tích", "Vật phẩm này cho phép bạn truy cập thùng chứa từ xa", "Sau khi chế tạo, nhìn vào vật phẩm để xem cách dùng"] [rift.blink] - name = "Chớp Nhoáng" - description = "Dịch chuyển tức thì tầm ngắn, Chỉ trong chớp mắt!" - lore1 = "Khối khi chớp (2x theo chiều dọc)" - lore2 = "Khi đang chạy nước rút: Nhấn đúp Nhảy để " - lore3 = "Chớp Nhoáng" - lore = ["Khối khi chớp (2x theo chiều dọc)", "Khi đang chạy nước rút: Nhấn đúp Nhảy để ", "Chớp Nhoáng"] +name = "Chớp Nhoáng" +description = "Dịch chuyển tức thì tầm ngắn, Chỉ trong chớp mắt!" +lore1 = "Khối khi chớp (2x theo chiều dọc)" +lore2 = "Khi đang chạy nước rút: Nhấn đúp Nhảy để " +lore3 = "Chớp Nhoáng" +lore = ["Khối khi chớp (2x theo chiều dọc)", "Khi đang chạy nước rút: Nhấn đúp Nhảy để ", "Chớp Nhoáng"] [rift.chest] - name = "Rương Ender Dễ Dàng" - description = "Mở rương ender bằng cách Chuột Trái vào nó trong tay." - lore1 = "Bấm vào Rương Ender trong tay để mở (Đừng đặt nó xuống)" - lore = ["Bấm vào Rương Ender trong tay để mở (Đừng đặt nó xuống)"] +name = "Rương Ender Dễ Dàng" +description = "Mở rương ender bằng cách Chuột Trái vào nó trong tay." +lore1 = "Bấm vào Rương Ender trong tay để mở (Đừng đặt nó xuống)" +lore = ["Bấm vào Rương Ender trong tay để mở (Đừng đặt nó xuống)"] [rift.descent] - name = "Chống Bay Lên" - description = "Bạn chán bị mắc kẹt trên không? Đây là kỹ năng dành cho bạn!" - lore1 = "Chỉ cần Ngồi để hạ xuống, bạn sẽ rơi chậm hơn bình thường!" - lore2 = "Thời Gian Hồi:" - lore = ["Chỉ cần Ngồi để hạ xuống, bạn sẽ rơi chậm hơn bình thường!", "Thời Gian Hồi:"] +name = "Chống Bay Lên" +description = "Bạn chán bị mắc kẹt trên không? Đây là kỹ năng dành cho bạn!" +lore1 = "Chỉ cần Ngồi để hạ xuống, bạn sẽ rơi chậm hơn bình thường!" +lore2 = "Thời Gian Hồi:" +lore = ["Chỉ cần Ngồi để hạ xuống, bạn sẽ rơi chậm hơn bình thường!", "Thời Gian Hồi:"] [rift.gate] - name = "Cổng Khe Nứt" - description = "Dịch chuyển đến vị trí đã đánh dấu." - lore1 = "CHẾ TẠO: Ngọc Lục Bảo + Mảnh Thạch Anh Tím + Ngọc Trai Ender" - lore2 = "Đọc trước khi dùng!" - lore3 = "Trì hoãn 5 giây, " - lore4 = "bạn có thể chết trong lúc thực hiện" - lore = ["CHẾ TẠO: Ngọc Lục Bảo + Mảnh Thạch Anh Tím + Ngọc Trai Ender", "Đọc trước khi dùng!", "Trì hoãn 5 giây, ", "bạn có thể chết trong lúc thực hiện"] +name = "Cổng Khe Nứt" +description = "Dịch chuyển đến vị trí đã đánh dấu." +lore1 = "CHẾ TẠO: Ngọc Lục Bảo + Mảnh Thạch Anh Tím + Ngọc Trai Ender" +lore2 = "Đọc trước khi dùng!" +lore3 = "Trì hoãn 5 giây, " +lore4 = "bạn có thể chết trong lúc thực hiện" +lore = ["CHẾ TẠO: Ngọc Lục Bảo + Mảnh Thạch Anh Tím + Ngọc Trai Ender", "Đọc trước khi dùng!", "Trì hoãn 5 giây, ", "bạn có thể chết trong lúc thực hiện"] [rift.resist] - name = "Kháng Khe Nứt" - description = "Nhận Kháng Cự khi sử dụng Vật Phẩm & Kỹ Năng Ender" - lore1 = "+ Thụ động: Cung cấp kháng cự khi bạn dùng kỹ năng khe nứt, hoặc Vật Phẩm Ender" - lore2 = "KHÔNG bao gồm Rương Ender Di Động, chỉ những thứ bạn có thể tiêu thụ" - lore = ["+ Thụ động: Cung cấp kháng cự khi bạn dùng kỹ năng khe nứt, hoặc Vật Phẩm Ender", "KHÔNG bao gồm Rương Ender Di Động, chỉ những thứ bạn có thể tiêu thụ"] +name = "Kháng Khe Nứt" +description = "Nhận Kháng Cự khi sử dụng Vật Phẩm & Kỹ Năng Ender" +lore1 = "+ Thụ động: Cung cấp kháng cự khi bạn dùng kỹ năng khe nứt, hoặc Vật Phẩm Ender" +lore2 = "KHÔNG bao gồm Rương Ender Di Động, chỉ những thứ bạn có thể tiêu thụ" +lore = ["+ Thụ động: Cung cấp kháng cự khi bạn dùng kỹ năng khe nứt, hoặc Vật Phẩm Ender", "KHÔNG bao gồm Rương Ender Di Động, chỉ những thứ bạn có thể tiêu thụ"] [rift.visage] - name = "Diện Mạo Khe Nứt" - description = "Ngăn Endermen trở nên hung hãn nếu bạn có Ngọc Trai Ender trong túi đồ." - lore1 = "Endermen sẽ không trở nên hung hãn nếu bạn có Ngọc Trai Ender trong túi đồ." - lore = ["Endermen sẽ không trở nên hung hãn nếu bạn có Ngọc Trai Ender trong túi đồ."] +name = "Diện Mạo Khe Nứt" +description = "Ngăn Endermen trở nên hung hãn nếu bạn có Ngọc Trai Ender trong túi đồ." +lore1 = "Endermen sẽ không trở nên hung hãn nếu bạn có Ngọc Trai Ender trong túi đồ." +lore = ["Endermen sẽ không trở nên hung hãn nếu bạn có Ngọc Trai Ender trong túi đồ."] # seaborn [seaborn] [seaborn.oxygen] - name = "Bình Oxy Hữu Cơ" - description = "Chứa thêm oxy trong lá phổi nhỏ bé của bạn!" - lore1 = "Tăng Dung Tích Oxy" - lore = ["Tăng Dung Tích Oxy"] +name = "Bình Oxy Hữu Cơ" +description = "Chứa thêm oxy trong lá phổi nhỏ bé của bạn!" +lore1 = "Tăng Dung Tích Oxy" +lore = ["Tăng Dung Tích Oxy"] [seaborn.fishers_fantasy] - name = "Giấc Mơ Ngư Phủ" - description = "Kiếm thêm XP từ câu cá, và nhận nhiều cá hơn!" - lore1 = "Mỗi cấp độ có cơ hội nhận thêm XP và Cá!" - lore = ["Mỗi cấp độ có cơ hội nhận thêm XP và Cá!"] +name = "Giấc Mơ Ngư Phủ" +description = "Kiếm thêm XP từ câu cá, và nhận nhiều cá hơn!" +lore1 = "Mỗi cấp độ có cơ hội nhận thêm XP và Cá!" +lore = ["Mỗi cấp độ có cơ hội nhận thêm XP và Cá!"] [seaborn.haste] - name = "Thợ Mỏ Rùa" - description = "Khi đào dưới nước, bạn nhận được tốc độ đào!" - lore1 = "Haste 3 áp dụng dưới nước khi đào (cộng dồn AquaAffinity) sau khi hiệu ứng thở dưới nước hết!" - lore = ["Haste 3 áp dụng dưới nước khi đào (cộng dồn AquaAffinity) sau khi hiệu ứng thở dưới nước hết!"] +name = "Thợ Mỏ Rùa" +description = "Khi đào dưới nước, bạn nhận được tốc độ đào!" +lore1 = "Haste 3 áp dụng dưới nước khi đào (cộng dồn AquaAffinity) sau khi hiệu ứng thở dưới nước hết!" +lore = ["Haste 3 áp dụng dưới nước khi đào (cộng dồn AquaAffinity) sau khi hiệu ứng thở dưới nước hết!"] [seaborn.night_vision] - name = "Tầm Nhìn Rùa" - description = "Khi ở dưới nước, bạn nhận được Nhìn Đêm" - lore1 = "Đơn giản nhận Nhìn Đêm khi ở dưới nước sau khi hiệu ứng thở dưới nước hết!" - lore = ["Đơn giản nhận Nhìn Đêm khi ở dưới nước sau khi hiệu ứng thở dưới nước hết!"] +name = "Tầm Nhìn Rùa" +description = "Khi ở dưới nước, bạn nhận được Nhìn Đêm" +lore1 = "Đơn giản nhận Nhìn Đêm khi ở dưới nước sau khi hiệu ứng thở dưới nước hết!" +lore = ["Đơn giản nhận Nhìn Đêm khi ở dưới nước sau khi hiệu ứng thở dưới nước hết!"] [seaborn.dolphin_grace] - name = "Ân Sủng Cá Heo" - description = "Bơi như cá heo, không cần cá heo" - lore1 = "+ Thụ động: nhận " - lore2 = "x tốc độ (ân sủng cá heo)" - lore3 = "kỹ thuật chính xác kiểu Đức- khoan, không đúng... Không tương thích với Depth Strider" - lore = ["+ Thụ động: nhận ", "x tốc độ (ân sủng cá heo)", "kỹ thuật chính xác kiểu Đức- khoan, không đúng... Không tương thích với Depth Strider"] +name = "Ân Sủng Cá Heo" +description = "Bơi như cá heo, không cần cá heo" +lore1 = "+ Thụ động: nhận " +lore2 = "x tốc độ (ân sủng cá heo)" +lore3 = "kỹ thuật chính xác kiểu Đức- khoan, không đúng... Không tương thích với Depth Strider" +lore = ["+ Thụ động: nhận ", "x tốc độ (ân sủng cá heo)", "kỹ thuật chính xác kiểu Đức- khoan, không đúng... Không tương thích với Depth Strider"] # stealth [stealth] [stealth.ghost_armor] - name = "Giáp Ma" - description = "Giáp tăng dần khi không nhận sát thương, chỉ chịu được 1 đòn" - lore1 = "Giáp Tối Đa" - lore2 = "Tốc Độ" - lore = ["Giáp Tối Đa", "Tốc Độ"] +name = "Giáp Ma" +description = "Giáp tăng dần khi không nhận sát thương, chỉ chịu được 1 đòn" +lore1 = "Giáp Tối Đa" +lore2 = "Tốc Độ" +lore = ["Giáp Tối Đa", "Tốc Độ"] [stealth.night_vision] - name = "Nhìn Đêm Tàng Hình" - description = "Nhận nhìn đêm khi ngồi" - lore1 = "Nhận một đợt " - lore2 = "nhìn đêm" - lore3 = "khi đang ngồi" - lore = ["Nhận một đợt ", "nhìn đêm", "khi đang ngồi"] +name = "Nhìn Đêm Tàng Hình" +description = "Nhận nhìn đêm khi ngồi" +lore1 = "Nhận một đợt " +lore2 = "nhìn đêm" +lore3 = "khi đang ngồi" +lore = ["Nhận một đợt ", "nhìn đêm", "khi đang ngồi"] [stealth.snatch] - name = "Cướp Vật Phẩm" - description = "Cướp vật phẩm rơi tức thì khi đang ngồi!" - lore1 = "Bán Kính Cướp" - lore = ["Bán Kính Cướp"] +name = "Cướp Vật Phẩm" +description = "Cướp vật phẩm rơi tức thì khi đang ngồi!" +lore1 = "Bán Kính Cướp" +lore = ["Bán Kính Cướp"] [stealth.speed] - name = "Tốc Độ Rón Rén" - description = "Nhận tốc độ khi đang ngồi" - lore1 = "Tốc Độ Rón Rén" - lore = ["Tốc Độ Rón Rén"] +name = "Tốc Độ Rón Rén" +description = "Nhận tốc độ khi đang ngồi" +lore1 = "Tốc Độ Rón Rén" +lore = ["Tốc Độ Rón Rén"] [stealth.ender_veil] - name = "Màn Che Ender" - description = "Không cần Bí Ngô để tránh Enderman tấn công nữa" - lore1 = "Ngăn Enderman tấn công khi đang ngồi" - lore2 = "Ngăn tất cả Enderman tấn công" - lore = ["Ngăn Enderman tấn công khi đang ngồi", "Ngăn tất cả Enderman tấn công"] +name = "Màn Che Ender" +description = "Không cần Bí Ngô để tránh Enderman tấn công nữa" +lore1 = "Ngăn Enderman tấn công khi đang ngồi" +lore2 = "Ngăn tất cả Enderman tấn công" +lore = ["Ngăn Enderman tấn công khi đang ngồi", "Ngăn tất cả Enderman tấn công"] # sword [sword] [sword.machete] - name = "Dao Rựa" - description = "Cắt qua tán lá dễ dàng!" - lore1 = "Bán Kính Chém" - lore2 = "Thời Gian Hồi Chặt" - lore3 = "Hao Mòn Công Cụ" - lore = ["Bán Kính Chém", "Thời Gian Hồi Chặt", "Hao Mòn Công Cụ"] +name = "Dao Rựa" +description = "Cắt qua tán lá dễ dàng!" +lore1 = "Bán Kính Chém" +lore2 = "Thời Gian Hồi Chặt" +lore3 = "Hao Mòn Công Cụ" +lore = ["Bán Kính Chém", "Thời Gian Hồi Chặt", "Hao Mòn Công Cụ"] [sword.bloody_blade] - name = "Lưỡi Kiếm Đẫm Máu" - description = "Đòn kiếm gây ra Chảy Máu!" - lore1 = "Tấn công thực thể sống bằng Kiếm gây Chảy Máu" - lore2 = "Thời Lượng Chảy Máu" - lore3 = "Thời Gian Hồi Chảy Máu" - lore = ["Tấn công thực thể sống bằng Kiếm gây Chảy Máu", "Thời Lượng Chảy Máu", "Thời Gian Hồi Chảy Máu"] +name = "Lưỡi Kiếm Đẫm Máu" +description = "Đòn kiếm gây ra Chảy Máu!" +lore1 = "Tấn công thực thể sống bằng Kiếm gây Chảy Máu" +lore2 = "Thời Lượng Chảy Máu" +lore3 = "Thời Gian Hồi Chảy Máu" +lore = ["Tấn công thực thể sống bằng Kiếm gây Chảy Máu", "Thời Lượng Chảy Máu", "Thời Gian Hồi Chảy Máu"] [sword.poisoned_blade] - name = "Lưỡi Kiếm Tẩm Độc" - description = "Đòn kiếm gây ra Ngộ Độc!" - lore1 = "Tấn công thực thể sống bằng Kiếm gây Ngộ Độc" - lore2 = "Thời Lượng Ngộ Độc" - lore3 = "Thời Gian Hồi Ngộ Độc" - lore = ["Tấn công thực thể sống bằng Kiếm gây Ngộ Độc", "Thời Lượng Ngộ Độc", "Thời Gian Hồi Ngộ Độc"] +name = "Lưỡi Kiếm Tẩm Độc" +description = "Đòn kiếm gây ra Ngộ Độc!" +lore1 = "Tấn công thực thể sống bằng Kiếm gây Ngộ Độc" +lore2 = "Thời Lượng Ngộ Độc" +lore3 = "Thời Gian Hồi Ngộ Độc" +lore = ["Tấn công thực thể sống bằng Kiếm gây Ngộ Độc", "Thời Lượng Ngộ Độc", "Thời Gian Hồi Ngộ Độc"] # taming [taming] [taming.damage] - name = "Sát Thương Thú Nuôi" - description = "Tăng sát thương thú nuôi đã thuần hóa." - lore1 = "Tăng Sát Thương" - lore = ["Tăng Sát Thương"] +name = "Sát Thương Thú Nuôi" +description = "Tăng sát thương thú nuôi đã thuần hóa." +lore1 = "Tăng Sát Thương" +lore = ["Tăng Sát Thương"] [taming.health] - name = "Máu Thú Nuôi" - description = "Tăng máu thú nuôi đã thuần hóa." - lore1 = "Tăng Máu" - lore = ["Tăng Máu"] +name = "Máu Thú Nuôi" +description = "Tăng máu thú nuôi đã thuần hóa." +lore1 = "Tăng Máu" +lore = ["Tăng Máu"] [taming.regeneration] - name = "Hồi Phục Thú Nuôi" - description = "Tăng tốc độ hồi phục thú nuôi đã thuần hóa." - lore1 = "HP/giây" - lore = ["HP/giây"] +name = "Hồi Phục Thú Nuôi" +description = "Tăng tốc độ hồi phục thú nuôi đã thuần hóa." +lore1 = "HP/giây" +lore = ["HP/giây"] # tragoul [tragoul] [tragoul.thorns] - name = "Gai Nhọn" - description = "Phản sát thương lại kẻ tấn công!" - lore1 = "Sát thương phản đòn khi bị đánh" - lore = ["Sát thương phản đòn khi bị đánh"] +name = "Gai Nhọn" +description = "Phản sát thương lại kẻ tấn công!" +lore1 = "Sát thương phản đòn khi bị đánh" +lore = ["Sát thương phản đòn khi bị đánh"] [tragoul.globe] - name = "Cầu Đau Đớn" - description = "Chia sát thương bạn gây ra dựa trên số kẻ thù xung quanh!" - lore1 = "Càng nhiều kẻ thù xung quanh, sát thương cho mỗi kẻ càng giảm" - lore2 = "Phạm Vi: " - lore3 = "Sát Thương Thêm cho tất cả Thực Thể: " - lore = ["Càng nhiều kẻ thù xung quanh, sát thương cho mỗi kẻ càng giảm", "Phạm Vi: ", "Sát Thương Thêm cho tất cả Thực Thể: "] +name = "Cầu Đau Đớn" +description = "Chia sát thương bạn gây ra dựa trên số kẻ thù xung quanh!" +lore1 = "Càng nhiều kẻ thù xung quanh, sát thương cho mỗi kẻ càng giảm" +lore2 = "Phạm Vi: " +lore3 = "Sát Thương Thêm cho tất cả Thực Thể: " +lore = ["Càng nhiều kẻ thù xung quanh, sát thương cho mỗi kẻ càng giảm", "Phạm Vi: ", "Sát Thương Thêm cho tất cả Thực Thể: "] [tragoul.healing] - name = "Ý Chí Đau Đớn" - description = "Hồi máu dựa trên sát thương bạn gây ra!" - lore1 = "Gây đau cho kẻ khác chưa bao giờ dễ chịu đến thế! Hồi máu từ Sát Thương Gây Ra" - lore2 = "Có khoảng thời gian sát thương 3 Giây để hồi máu và 1 Giây thời gian hồi " - lore3 = "Phần Trăm Hồi Máu Mỗi Sát Thương: " - lore = ["Gây đau cho kẻ khác chưa bao giờ dễ chịu đến thế! Hồi máu từ Sát Thương Gây Ra", "Có khoảng thời gian sát thương 3 Giây để hồi máu và 1 Giây thời gian hồi ", "Phần Trăm Hồi Máu Mỗi Sát Thương: "] +name = "Ý Chí Đau Đớn" +description = "Hồi máu dựa trên sát thương bạn gây ra!" +lore1 = "Gây đau cho kẻ khác chưa bao giờ dễ chịu đến thế! Hồi máu từ Sát Thương Gây Ra" +lore2 = "Có khoảng thời gian sát thương 3 Giây để hồi máu và 1 Giây thời gian hồi " +lore3 = "Phần Trăm Hồi Máu Mỗi Sát Thương: " +lore = ["Gây đau cho kẻ khác chưa bao giờ dễ chịu đến thế! Hồi máu từ Sát Thương Gây Ra", "Có khoảng thời gian sát thương 3 Giây để hồi máu và 1 Giây thời gian hồi ", "Phần Trăm Hồi Máu Mỗi Sát Thương: "] [tragoul.lance] - name = "Thương Xác Chết" - description = "Giết kẻ thù hoặc kỹ năng giết kẻ thù sẽ tạo ra thương gây sát thương cho kẻ thù gần đó!" - lore1 = "Thương sẽ phóng từ bất cứ thứ gì bạn giết, VÀ nếu Kỹ Năng này giết kẻ thù." - lore2 = "Hy sinh một phần sinh mệnh để tạo thương (có thể giết bạn)" - lore3 = "Số Thương Tối Đa: 1 + " - lore = ["Thương sẽ phóng từ bất cứ thứ gì bạn giết, VÀ nếu Kỹ Năng này giết kẻ thù.", "Hy sinh một phần sinh mệnh để tạo thương (có thể giết bạn)", "Số Thương Tối Đa: 1 + "] +name = "Thương Xác Chết" +description = "Giết kẻ thù hoặc kỹ năng giết kẻ thù sẽ tạo ra thương gây sát thương cho kẻ thù gần đó!" +lore1 = "Thương sẽ phóng từ bất cứ thứ gì bạn giết, VÀ nếu Kỹ Năng này giết kẻ thù." +lore2 = "Hy sinh một phần sinh mệnh để tạo thương (có thể giết bạn)" +lore3 = "Số Thương Tối Đa: 1 + " +lore = ["Thương sẽ phóng từ bất cứ thứ gì bạn giết, VÀ nếu Kỹ Năng này giết kẻ thù.", "Hy sinh một phần sinh mệnh để tạo thương (có thể giết bạn)", "Số Thương Tối Đa: 1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "Đại Bác Thủy Tinh" - description = "Sát thương Tay Không tăng thêm khi giá trị giáp càng thấp" - lore1 = "x Sát thương ở 0 giáp" - lore2 = "Sát Thương Thêm Mỗi Cấp" - lore = ["x Sát thương ở 0 giáp", "Sát Thương Thêm Mỗi Cấp"] +name = "Đại Bác Thủy Tinh" +description = "Sát thương Tay Không tăng thêm khi giá trị giáp càng thấp" +lore1 = "x Sát thương ở 0 giáp" +lore2 = "Sát Thương Thêm Mỗi Cấp" +lore = ["x Sát thương ở 0 giáp", "Sát Thương Thêm Mỗi Cấp"] [unarmed.power] - name = "Sức Mạnh Tay Không" - description = "Tăng Sát Thương Tay Không" - lore1 = "Sát Thương" - lore = ["Sát Thương"] +name = "Sức Mạnh Tay Không" +description = "Tăng Sát Thương Tay Không" +lore1 = "Sát Thương" +lore = ["Sát Thương"] [unarmed.sucker_punch] - name = "Cú Đấm Bất Ngờ" - description = "Cú đấm khi chạy nước rút, nhưng chết chóc hơn." - lore1 = "Sát Thương" - lore2 = "Sát thương tăng theo tốc độ khi đấm" - lore = ["Sát Thương", "Sát thương tăng theo tốc độ khi đấm"] +name = "Cú Đấm Bất Ngờ" +description = "Cú đấm khi chạy nước rút, nhưng chết chóc hơn." +lore1 = "Sát Thương" +lore2 = "Sát thương tăng theo tốc độ khi đấm" +lore = ["Sát Thương", "Sát thương tăng theo tốc độ khi đấm"] diff --git a/src/main/resources/zh_CN.toml b/src/main/resources/zh_CN.toml index 6d848197b..afdcf3d8f 100644 --- a/src/main/resources/zh_CN.toml +++ b/src/main/resources/zh_CN.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "动起来!" - description = "走过总计 1 千米的距离。(1,000 格方块)" +title = "动起来!" +description = "走过总计 1 千米的距离。(1,000 格方块)" [advancement.challenge_sprint_5k] - title = "长跑运动员" - description = "走过总计 5 千米的距离。(5,000 格方块)" +title = "长跑运动员" +description = "走过总计 5 千米的距离。(5,000 格方块)" [advancement.challenge_sprint_50k] - title = "五万变焦" - description = "走过总计 50 千米的距离。(50,000 格方块)" +title = "五万变焦" +description = "走过总计 50 千米的距离。(50,000 格方块)" [advancement.challenge_sprint_500k] - title = "穿越宇宙!!" - description = "走过总计 500 千米的距离。(500,000 格方块)" +title = "穿越宇宙!!" +description = "走过总计 500 千米的距离。(500,000 格方块)" [advancement.challenge_sprint_marathon] - title = "疾跑(真正的)马拉松!" - description = "以疾跑的方式跑过 42,195 格方块!" +title = "疾跑(真正的)马拉松!" +description = "以疾跑的方式跑过 42,195 格方块!" [advancement.challenge_place_1k] - title = "建筑师入门!" - description = "放置 1,000 个方块" +title = "建筑师入门!" +description = "放置 1,000 个方块" [advancement.challenge_place_5k] - title = "中级建筑师!" - description = "放置 5,000 个方块" +title = "中级建筑师!" +description = "放置 5,000 个方块" [advancement.challenge_place_50k] - title = "高级建筑师!" - description = "放置 50,000 个方块" +title = "高级建筑师!" +description = "放置 50,000 个方块" [advancement.challenge_place_500k] - title = "建筑大师!" - description = "放置 500,000 个方块" +title = "建筑大师!" +description = "放置 500,000 个方块" [advancement.challenge_place_5m] - title = "对称之徒!" - description = "现实是你的游乐场!(五百万个方块)" +title = "对称之徒!" +description = "现实是你的游乐场!(五百万个方块)" [advancement.challenge_chop_1k] - title = "伐木工入门!" - description = "砍伐 1,000 个方块" +title = "伐木工入门!" +description = "砍伐 1,000 个方块" [advancement.challenge_chop_5k] - title = "中级伐木工!" - description = "砍伐 5,000 个方块" +title = "中级伐木工!" +description = "砍伐 5,000 个方块" [advancement.challenge_chop_50k] - title = "高级伐木工!" - description = "砍伐 50,000 个方块" +title = "高级伐木工!" +description = "砍伐 50,000 个方块" [advancement.challenge_chop_500k] - title = "伐木大师!" - description = "砍伐 500,000 个方块" +title = "伐木大师!" +description = "砍伐 500,000 个方块" [advancement.challenge_chop_5m] - title = "狗狗杰克逊" - description = "最棒的好孩子!(五百万个方块)" +title = "狗狗杰克逊" +description = "最棒的好孩子!(五百万个方块)" [advancement.challenge_block_1k] - title = "防御入门!" - description = "防御 1,000 次攻击" +title = "防御入门!" +description = "防御 1,000 次攻击" [advancement.challenge_block_5k] - title = "防御之乐!" - description = "防御 5,000 次攻击" +title = "防御之乐!" +description = "防御 5,000 次攻击" [advancement.challenge_block_50k] - title = "防御即人生!" - description = "防御 50,000 次攻击" +title = "防御即人生!" +description = "防御 50,000 次攻击" [advancement.challenge_block_500k] - title = "防御即使命!" - description = "防御 500,000 次攻击" +title = "防御即使命!" +description = "防御 500,000 次攻击" [advancement.challenge_block_5m] - title = "伤人之手" - description = "防御 5,000,000 次攻击" +title = "伤人之手" +description = "防御 5,000,000 次攻击" [advancement.challenge_brew_1k] - title = "炼金术士入门!" - description = "饮用 1,000 瓶药水" +title = "炼金术士入门!" +description = "饮用 1,000 瓶药水" [advancement.challenge_brew_5k] - title = "中级炼金术士!" - description = "饮用 5,000 瓶药水" +title = "中级炼金术士!" +description = "饮用 5,000 瓶药水" [advancement.challenge_brew_50k] - title = "高级炼金术士!" - description = "饮用 50,000 瓶药水" +title = "高级炼金术士!" +description = "饮用 50,000 瓶药水" [advancement.challenge_brew_500k] - title = "炼金大师!" - description = "饮用 500,000 瓶药水" +title = "炼金大师!" +description = "饮用 500,000 瓶药水" [advancement.challenge_brew_5m] - title = "炼金师" - description = "饮用 5,000,000 瓶药水" +title = "炼金师" +description = "饮用 5,000,000 瓶药水" [advancement.challenge_brewsplash_1k] - title = "喷溅入门!" - description = "投掷 1,000 瓶喷溅药水" +title = "喷溅入门!" +description = "投掷 1,000 瓶喷溅药水" [advancement.challenge_brewsplash_5k] - title = "中级喷溅者!" - description = "投掷 5,000 瓶喷溅药水" +title = "中级喷溅者!" +description = "投掷 5,000 瓶喷溅药水" [advancement.challenge_brewsplash_50k] - title = "高级喷溅者!" - description = "投掷 50,000 瓶喷溅药水" +title = "高级喷溅者!" +description = "投掷 50,000 瓶喷溅药水" [advancement.challenge_brewsplash_500k] - title = "喷溅大师!" - description = "投掷 500,000 瓶喷溅药水" +title = "喷溅大师!" +description = "投掷 500,000 瓶喷溅药水" [advancement.challenge_brewsplash_5m] - title = "投掷宗师" - description = "投掷 5,000,000 瓶喷溅药水" +title = "投掷宗师" +description = "投掷 5,000,000 瓶喷溅药水" [advancement.challenge_craft_1k] - title = "精妙合成!" - description = "合成 1,000 个物品" +title = "精妙合成!" +description = "合成 1,000 个物品" [advancement.challenge_craft_5k] - title = "暴躁合成!" - description = "合成 5,000 个物品" +title = "暴躁合成!" +description = "合成 5,000 个物品" [advancement.challenge_craft_50k] - title = "勤勉合成!" - description = "合成 50,000 个物品" +title = "勤勉合成!" +description = "合成 50,000 个物品" [advancement.challenge_craft_500k] - title = "巧夺天工!" - description = "合成 500,000 个物品" +title = "巧夺天工!" +description = "合成 500,000 个物品" [advancement.challenge_craft_5m] - title = "灾厄合成脸" - description = "合成 5,000,000 个物品" +title = "灾厄合成脸" +description = "合成 5,000,000 个物品" [advancement.challenge_enchant_1k] - title = "附魔入门!" - description = "附魔 1,000 件物品" +title = "附魔入门!" +description = "附魔 1,000 件物品" [advancement.challenge_enchant_5k] - title = "中级附魔师!" - description = "附魔 5,000 件物品" +title = "中级附魔师!" +description = "附魔 5,000 件物品" [advancement.challenge_enchant_50k] - title = "高级附魔师!" - description = "附魔 50,000 件物品" +title = "高级附魔师!" +description = "附魔 50,000 件物品" [advancement.challenge_enchant_500k] - title = "附魔大师!" - description = "附魔 500,000 件物品" +title = "附魔大师!" +description = "附魔 500,000 件物品" [advancement.challenge_enchant_5m] - title = "神秘附魔师" - description = "附魔 5,000,000 件物品" +title = "神秘附魔师" +description = "附魔 5,000,000 件物品" [advancement.challenge_excavate_1k] - title = "挖掘入门!" - description = "挖掘 1,000 个方块" +title = "挖掘入门!" +description = "挖掘 1,000 个方块" [advancement.challenge_excavate_5k] - title = "中级挖掘者!" - description = "挖掘 5,000 个方块" +title = "中级挖掘者!" +description = "挖掘 5,000 个方块" [advancement.challenge_excavate_50k] - title = "高级挖掘者!" - description = "挖掘 50,000 个方块" +title = "高级挖掘者!" +description = "挖掘 50,000 个方块" [advancement.challenge_excavate_500k] - title = "挖掘大师!" - description = "挖掘 500,000 个方块" +title = "挖掘大师!" +description = "挖掘 500,000 个方块" [advancement.challenge_excavate_5m] - title = "神秘挖掘者" - description = "挖掘 5,000,000 个方块" +title = "神秘挖掘者" +description = "挖掘 5,000,000 个方块" [advancement.horrible_person] - title = "你真是个可怕的人" - description = "不可思议,真的" +title = "你真是个可怕的人" +description = "不可思议,真的" [advancement.challenge_turtle_egg_smasher] - title = "海龟蛋破坏者!" - description = "破坏 100 个海龟蛋" +title = "海龟蛋破坏者!" +description = "破坏 100 个海龟蛋" [advancement.challenge_turtle_egg_annihilator] - title = "海龟蛋歼灭者!" - description = "破坏 500 个海龟蛋" +title = "海龟蛋歼灭者!" +description = "破坏 500 个海龟蛋" [advancement.challenge_novice_hunter] - title = "狩猎入门!" - description = "击杀 100 个实体" +title = "狩猎入门!" +description = "击杀 100 个实体" [advancement.challenge_intermediate_hunter] - title = "中级猎人!" - description = "击杀 500 个实体" +title = "中级猎人!" +description = "击杀 500 个实体" [advancement.challenge_advanced_hunter] - title = "高级猎人!" - description = "击杀 5,000 个实体" +title = "高级猎人!" +description = "击杀 5,000 个实体" [advancement.challenge_creeper_conqueror] - title = "苦力怕征服者!" - description = "击杀 50 只苦力怕" +title = "苦力怕征服者!" +description = "击杀 50 只苦力怕" [advancement.challenge_creeper_annihilator] - title = "苦力怕歼灭者!" - description = "击杀 200 只苦力怕" +title = "苦力怕歼灭者!" +description = "击杀 200 只苦力怕" [advancement.challenge_pickaxe_1k] - title = "初级矿工" - description = "破坏 1,000 个方块" +title = "初级矿工" +description = "破坏 1,000 个方块" [advancement.challenge_pickaxe_5k] - title = "熟练矿工" - description = "破坏 5,000 个方块" +title = "熟练矿工" +description = "破坏 5,000 个方块" [advancement.challenge_pickaxe_50k] - title = "专家矿工" - description = "破坏 50,000 个方块" +title = "专家矿工" +description = "破坏 50,000 个方块" [advancement.challenge_pickaxe_500k] - title = "矿工大师" - description = "破坏 500,000 个方块" +title = "矿工大师" +description = "破坏 500,000 个方块" [advancement.challenge_pickaxe_5m] - title = "传奇矿工" - description = "破坏 5,000,000 个方块" +title = "传奇矿工" +description = "破坏 5,000,000 个方块" [advancement.challenge_eat_100] - title = "大快朵颐!" - description = "吃掉超过 100 个食物!" +title = "大快朵颐!" +description = "吃掉超过 100 个食物!" [advancement.challenge_eat_1000] - title = "永无止境的饥饿!" - description = "吃掉超过 1,000 个食物!" +title = "永无止境的饥饿!" +description = "吃掉超过 1,000 个食物!" [advancement.challenge_eat_10000] - title = "饕餮之饥!" - description = "吃掉超过 10,000 个食物!" +title = "饕餮之饥!" +description = "吃掉超过 10,000 个食物!" [advancement.challenge_harvest_100] - title = "丰收时节" - description = "收获超过 100 种农作物!" +title = "丰收时节" +description = "收获超过 100 种农作物!" [advancement.challenge_harvest_1000] - title = "大丰收" - description = "收获超过 1,000 种农作物!" +title = "大丰收" +description = "收获超过 1,000 种农作物!" [advancement.challenge_swim_1nm] - title = "人体潜艇!" - description = "游泳 1 海里(1,852 格方块)" +title = "人体潜艇!" +description = "游泳 1 海里(1,852 格方块)" [advancement.challenge_sneak_1k] - title = "膝盖疼痛" - description = "潜行超过一千米(1,000 格方块)" +title = "膝盖疼痛" +description = "潜行超过一千米(1,000 格方块)" [advancement.challenge_sneak_5k] - title = "暗影行者" - description = "潜行超过 5,000 格方块" +title = "暗影行者" +description = "潜行超过 5,000 格方块" [advancement.challenge_sneak_20k] - title = "幽灵" - description = "潜行超过 20,000 格方块" +title = "幽灵" +description = "潜行超过 20,000 格方块" [advancement.challenge_swim_5k] - title = "深海潜水员" - description = "游泳超过 5,000 格方块" +title = "深海潜水员" +description = "游泳超过 5,000 格方块" [advancement.challenge_swim_20k] - title = "海神之选" - description = "游泳超过 20,000 格方块" +title = "海神之选" +description = "游泳超过 20,000 格方块" [advancement.challenge_sword_100] - title = "初见血光" - description = "用剑命中 100 次" +title = "初见血光" +description = "用剑命中 100 次" [advancement.challenge_sword_1k] - title = "剑舞者" - description = "用剑命中 1,000 次" +title = "剑舞者" +description = "用剑命中 1,000 次" [advancement.challenge_sword_10k] - title = "千刀万剐" - description = "用剑命中 10,000 次" +title = "千刀万剐" +description = "用剑命中 10,000 次" [advancement.challenge_unarmed_100] - title = "酒吧打手" - description = "徒手命中 100 次" +title = "酒吧打手" +description = "徒手命中 100 次" [advancement.challenge_unarmed_1k] - title = "铁拳" - description = "徒手命中 1,000 次" +title = "铁拳" +description = "徒手命中 1,000 次" [advancement.challenge_unarmed_10k] - title = "一拳超人" - description = "徒手命中 10,000 次" +title = "一拳超人" +description = "徒手命中 10,000 次" [advancement.challenge_trag_1k] - title = "血的代价" - description = "承受 1,000 点伤害" +title = "血的代价" +description = "承受 1,000 点伤害" [advancement.challenge_trag_10k] - title = "赤潮" - description = "承受 10,000 点伤害" +title = "赤潮" +description = "承受 10,000 点伤害" [advancement.challenge_trag_100k] - title = "苦难化身" - description = "承受 100,000 点伤害" +title = "苦难化身" +description = "承受 100,000 点伤害" [advancement.challenge_ranged_100] - title = "靶场练习" - description = "发射 100 枚弹射物" +title = "靶场练习" +description = "发射 100 枚弹射物" [advancement.challenge_ranged_1k] - title = "鹰眼" - description = "发射 1,000 枚弹射物" +title = "鹰眼" +description = "发射 1,000 枚弹射物" [advancement.challenge_ranged_10k] - title = "箭雨风暴" - description = "发射 10,000 枚弹射物" +title = "箭雨风暴" +description = "发射 10,000 枚弹射物" [advancement.challenge_chronos_1h] - title = "滴答滴答" - description = "在线 1 小时" +title = "滴答滴答" +description = "在线 1 小时" [advancement.challenge_chronos_24h] - title = "时间之沙" - description = "在线 24 小时" +title = "时间之沙" +description = "在线 24 小时" [advancement.challenge_chronos_168h] - title = "永恒不朽" - description = "在线 168 小时(1 周)" +title = "永恒不朽" +description = "在线 168 小时(1 周)" [advancement.challenge_nether_50] - title = "地狱守门人" - description = "击杀 50 只下界生物" +title = "地狱守门人" +description = "击杀 50 只下界生物" [advancement.challenge_nether_500] - title = "深渊守望者" - description = "击杀 500 只下界生物" +title = "深渊守望者" +description = "击杀 500 只下界生物" [advancement.challenge_nether_5k] - title = "下界之主" - description = "击杀 5,000 只下界生物" +title = "下界之主" +description = "击杀 5,000 只下界生物" [advancement.challenge_rift_50] - title = "空间异常" - description = "传送 50 次" +title = "空间异常" +description = "传送 50 次" [advancement.challenge_rift_500] - title = "虚空行者" - description = "传送 500 次" +title = "虚空行者" +description = "传送 500 次" [advancement.challenge_rift_5k] - title = "穿梭世界" - description = "传送 5,000 次" +title = "穿梭世界" +description = "传送 5,000 次" [advancement.challenge_taming_10] - title = "动物低语者" - description = "繁殖 10 只动物" +title = "动物低语者" +description = "繁殖 10 只动物" [advancement.challenge_taming_50] - title = "兽群首领" - description = "繁殖 50 只动物" +title = "兽群首领" +description = "繁殖 50 只动物" [advancement.challenge_taming_500] - title = "驯兽大师" - description = "繁殖 500 只动物" +title = "驯兽大师" +description = "繁殖 500 只动物" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "速度恶魔" - description = "疾跑超过 5 公里 (5,000 格)" +title = "速度恶魔" +description = "疾跑超过 5 公里 (5,000 格)" [advancement.challenge_sprint_dist_50k] - title = "闪电之足" - description = "疾跑超过 50 公里 (50,000 格)" +title = "闪电之足" +description = "疾跑超过 50 公里 (50,000 格)" [advancement.challenge_agility_swim_1k] - title = "水上行者" - description = "游泳超过 1 公里 (1,000 格)" +title = "水上行者" +description = "游泳超过 1 公里 (1,000 格)" [advancement.challenge_agility_swim_10k] - title = "远洋航行者" - description = "游泳超过 10 公里 (10,000 格)" +title = "远洋航行者" +description = "游泳超过 10 公里 (10,000 格)" [advancement.challenge_fly_1k] - title = "翱翔之舞" - description = "飞行超过 1 公里 (1,000 格)" +title = "翱翔之舞" +description = "飞行超过 1 公里 (1,000 格)" [advancement.challenge_fly_10k] - title = "御风者" - description = "飞行超过 10 公里 (10,000 格)" +title = "御风者" +description = "飞行超过 10 公里 (10,000 格)" [advancement.challenge_agility_sneak_500] - title = "轻步潜行" - description = "潜行超过 500 格" +title = "轻步潜行" +description = "潜行超过 500 格" [advancement.challenge_agility_sneak_5k] - title = "幽灵步伐" - description = "潜行超过 5 公里 (5,000 格)" +title = "幽灵步伐" +description = "潜行超过 5 公里 (5,000 格)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "拆除小队" - description = "破坏 500 个方块" +title = "拆除小队" +description = "破坏 500 个方块" [advancement.challenge_demolish_5k] - title = "毁灭之球" - description = "破坏 5,000 个方块" +title = "毁灭之球" +description = "破坏 5,000 个方块" [advancement.challenge_value_placed_10k] - title = "价值建造者" - description = "放置价值 10,000 的方块" +title = "价值建造者" +description = "放置价值 10,000 的方块" [advancement.challenge_value_placed_100k] - title = "建筑大师" - description = "放置价值 100,000 的方块" +title = "建筑大师" +description = "放置价值 100,000 的方块" [advancement.challenge_demolish_val_5k] - title = "回收专家" - description = "从拆除中回收 5,000 方块价值" +title = "回收专家" +description = "从拆除中回收 5,000 方块价值" [advancement.challenge_demolish_val_50k] - title = "彻底解构" - description = "从拆除中回收 50,000 方块价值" +title = "彻底解构" +description = "从拆除中回收 50,000 方块价值" [advancement.challenge_high_build_100] - title = "高空建造者" - description = "在 Y=128 以上放置 100 个方块" +title = "高空建造者" +description = "在 Y=128 以上放置 100 个方块" [advancement.challenge_high_build_1k] - title = "云端建筑师" - description = "在 Y=128 以上放置 1,000 个方块" +title = "云端建筑师" +description = "在 Y=128 以上放置 1,000 个方块" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "挥斧者" - description = "挥舞斧头 500 次" +title = "挥斧者" +description = "挥舞斧头 500 次" [advancement.challenge_axe_swing_5k] - title = "狂战士" - description = "挥舞斧头 5,000 次" +title = "狂战士" +description = "挥舞斧头 5,000 次" [advancement.challenge_axe_damage_1k] - title = "劈裂者" - description = "用斧头造成 1,000 伤害" +title = "劈裂者" +description = "用斧头造成 1,000 伤害" [advancement.challenge_axe_damage_10k] - title = "刽子手之斧" - description = "用斧头造成 10,000 伤害" +title = "刽子手之斧" +description = "用斧头造成 10,000 伤害" [advancement.challenge_axe_value_5k] - title = "木材商人" - description = "采伐价值 5,000 的木材" +title = "木材商人" +description = "采伐价值 5,000 的木材" [advancement.challenge_axe_value_50k] - title = "伐木大亨" - description = "采伐价值 50,000 的木材" +title = "伐木大亨" +description = "采伐价值 50,000 的木材" [advancement.challenge_leaves_500] - title = "吹叶机" - description = "用斧头清除 500 个树叶方块" +title = "吹叶机" +description = "用斧头清除 500 个树叶方块" [advancement.challenge_leaves_5k] - title = "落叶清除者" - description = "用斧头清除 5,000 个树叶方块" +title = "落叶清除者" +description = "用斧头清除 5,000 个树叶方块" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "伤害吸收者" - description = "用盾牌格挡 1,000 伤害" +title = "伤害吸收者" +description = "用盾牌格挡 1,000 伤害" [advancement.challenge_block_dmg_10k] - title = "人肉盾牌" - description = "用盾牌格挡 10,000 伤害" +title = "人肉盾牌" +description = "用盾牌格挡 10,000 伤害" [advancement.challenge_block_proj_100] - title = "箭矢偏转者" - description = "用盾牌格挡 100 个投射物" +title = "箭矢偏转者" +description = "用盾牌格挡 100 个投射物" [advancement.challenge_block_proj_1k] - title = "投射物盾牌" - description = "用盾牌格挡 1,000 个投射物" +title = "投射物盾牌" +description = "用盾牌格挡 1,000 个投射物" [advancement.challenge_block_melee_500] - title = "格挡大师" - description = "用盾牌格挡 500 次近战攻击" +title = "格挡大师" +description = "用盾牌格挡 500 次近战攻击" [advancement.challenge_block_melee_5k] - title = "铁壁堡垒" - description = "用盾牌格挡 5,000 次近战攻击" +title = "铁壁堡垒" +description = "用盾牌格挡 5,000 次近战攻击" [advancement.challenge_block_heavy_50] - title = "坦克" - description = "格挡 50 次重击 (超过 5 伤害)" +title = "坦克" +description = "格挡 50 次重击 (超过 5 伤害)" [advancement.challenge_block_heavy_500] - title = "不动如山" - description = "格挡 500 次重击 (超过 5 伤害)" +title = "不动如山" +description = "格挡 500 次重击 (超过 5 伤害)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "酿造起步" - description = "放置 10 个酿造台" +title = "酿造起步" +description = "放置 10 个酿造台" [advancement.challenge_brew_stands_50] - title = "药水工厂" - description = "放置 50 个酿造台" +title = "药水工厂" +description = "放置 50 个酿造台" [advancement.challenge_brew_strong_25] - title = "烈性药剂" - description = "饮用 25 瓶强化药水" +title = "烈性药剂" +description = "饮用 25 瓶强化药水" [advancement.challenge_brew_strong_250] - title = "极致药效" - description = "饮用 250 瓶强化药水" +title = "极致药效" +description = "饮用 250 瓶强化药水" [advancement.challenge_brew_splash_hits_50] - title = "飞溅区域" - description = "用喷溅药水命中 50 个实体" +title = "飞溅区域" +description = "用喷溅药水命中 50 个实体" [advancement.challenge_brew_splash_hits_500] - title = "瘟疫医生" - description = "用喷溅药水命中 500 个实体" +title = "瘟疫医生" +description = "用喷溅药水命中 500 个实体" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "永不停歇" - description = "活跃时行进 1 公里" +title = "永不停歇" +description = "活跃时行进 1 公里" [advancement.challenge_active_dist_10k] - title = "探路先锋" - description = "活跃时行进 10 公里" +title = "探路先锋" +description = "活跃时行进 10 公里" [advancement.challenge_active_dist_100k] - title = "永动之力" - description = "活跃时行进 100 公里" +title = "永动之力" +description = "活跃时行进 100 公里" [advancement.challenge_beds_10] - title = "早起鸟儿" - description = "在床上睡觉 10 次" +title = "早起鸟儿" +description = "在床上睡觉 10 次" [advancement.challenge_beds_100] - title = "时间跳跃者" - description = "在床上睡觉 100 次" +title = "时间跳跃者" +description = "在床上睡觉 100 次" [advancement.challenge_chronos_tp_50] - title = "时空转移" - description = "传送 50 次" +title = "时空转移" +description = "传送 50 次" [advancement.challenge_chronos_tp_500] - title = "时间扭曲" - description = "传送 500 次" +title = "时间扭曲" +description = "传送 500 次" [advancement.challenge_chronos_deaths_10] - title = "凡人" - description = "死亡 10 次" +title = "凡人" +description = "死亡 10 次" [advancement.challenge_chronos_deaths_100] - title = "死亡挑战者" - description = "死亡 100 次" +title = "死亡挑战者" +description = "死亡 100 次" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "匠心独运" - description = "制作总价值 10,000 的物品" +title = "匠心独运" +description = "制作总价值 10,000 的物品" [advancement.challenge_craft_value_100k] - title = "工匠大师" - description = "制作总价值 100,000 的物品" +title = "工匠大师" +description = "制作总价值 100,000 的物品" [advancement.challenge_craft_tools_25] - title = "工具匠" - description = "制作 25 件工具" +title = "工具匠" +description = "制作 25 件工具" [advancement.challenge_craft_tools_250] - title = "锻造大师" - description = "制作 250 件工具" +title = "锻造大师" +description = "制作 250 件工具" [advancement.challenge_craft_armor_25] - title = "铠甲匠" - description = "制作 25 件护甲" +title = "铠甲匠" +description = "制作 25 件护甲" [advancement.challenge_craft_armor_250] - title = "护甲大师" - description = "制作 250 件护甲" +title = "护甲大师" +description = "制作 250 件护甲" [advancement.challenge_craft_mass_25k] - title = "批量生产" - description = "制作 25,000 个物品" +title = "批量生产" +description = "制作 25,000 个物品" [advancement.challenge_craft_mass_250k] - title = "工业革命" - description = "制作 250,000 个物品" +title = "工业革命" +description = "制作 250,000 个物品" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "收集者" - description = "发现 50 种独特物品" +title = "收集者" +description = "发现 50 种独特物品" [advancement.challenge_discover_items_250] - title = "编目者" - description = "发现 250 种独特物品" +title = "编目者" +description = "发现 250 种独特物品" [advancement.challenge_discover_blocks_50] - title = "勘测员" - description = "发现 50 种独特方块" +title = "勘测员" +description = "发现 50 种独特方块" [advancement.challenge_discover_blocks_250] - title = "地质学家" - description = "发现 250 种独特方块" +title = "地质学家" +description = "发现 250 种独特方块" [advancement.challenge_discover_mobs_25] - title = "观察者" - description = "发现 25 种独特生物" +title = "观察者" +description = "发现 25 种独特生物" [advancement.challenge_discover_mobs_75] - title = "博物学家" - description = "发现 75 种独特生物" +title = "博物学家" +description = "发现 75 种独特生物" [advancement.challenge_discover_biomes_10] - title = "漫游者" - description = "发现 10 种独特生物群系" +title = "漫游者" +description = "发现 10 种独特生物群系" [advancement.challenge_discover_biomes_40] - title = "环球旅行家" - description = "发现 40 种独特生物群系" +title = "环球旅行家" +description = "发现 40 种独特生物群系" [advancement.challenge_discover_foods_10] - title = "美食家" - description = "发现 10 种独特食物" +title = "美食家" +description = "发现 10 种独特食物" [advancement.challenge_discover_foods_30] - title = "烹饪大师" - description = "发现 30 种独特食物" +title = "烹饪大师" +description = "发现 30 种独特食物" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "织力者" - description = "积累 100 附魔之力" +title = "织力者" +description = "积累 100 附魔之力" [advancement.challenge_enchant_power_1k] - title = "奥术宗师" - description = "积累 1,000 附魔之力" +title = "奥术宗师" +description = "积累 1,000 附魔之力" [advancement.challenge_enchant_levels_1k] - title = "等级消耗者" - description = "在附魔上花费 1,000 经验等级" +title = "等级消耗者" +description = "在附魔上花费 1,000 经验等级" [advancement.challenge_enchant_levels_10k] - title = "经验黑洞" - description = "在附魔上花费 10,000 经验等级" +title = "经验黑洞" +description = "在附魔上花费 10,000 经验等级" [advancement.challenge_enchant_high_25] - title = "豪赌者" - description = "进行 25 次最高级附魔" +title = "豪赌者" +description = "进行 25 次最高级附魔" [advancement.challenge_enchant_high_250] - title = "传奇附魔师" - description = "进行 250 次最高级附魔" +title = "传奇附魔师" +description = "进行 250 次最高级附魔" [advancement.challenge_enchant_total_500] - title = "等级燃烧者" - description = "在所有附魔上累计花费 500 等级" +title = "等级燃烧者" +description = "在所有附魔上累计花费 500 等级" [advancement.challenge_enchant_total_5k] - title = "奥术投资" - description = "在所有附魔上累计花费 5,000 等级" +title = "奥术投资" +description = "在所有附魔上累计花费 5,000 等级" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "挖掘者" - description = "挥舞铲子 500 次" +title = "挖掘者" +description = "挥舞铲子 500 次" [advancement.challenge_dig_swing_5k] - title = "挖掘机" - description = "挥舞铲子 5,000 次" +title = "挖掘机" +description = "挥舞铲子 5,000 次" [advancement.challenge_dig_damage_1k] - title = "铲骑士" - description = "用铲子造成 1,000 伤害" +title = "铲骑士" +description = "用铲子造成 1,000 伤害" [advancement.challenge_dig_damage_10k] - title = "铲王" - description = "用铲子造成 10,000 伤害" +title = "铲王" +description = "用铲子造成 10,000 伤害" [advancement.challenge_dig_value_5k] - title = "泥土商人" - description = "挖掘价值 5,000 的方块" +title = "泥土商人" +description = "挖掘价值 5,000 的方块" [advancement.challenge_dig_value_50k] - title = "大地领主" - description = "挖掘价值 50,000 的方块" +title = "大地领主" +description = "挖掘价值 50,000 的方块" [advancement.challenge_dig_gravel_500] - title = "砾石研磨者" - description = "挖掘 500 个砾石、沙子或粘土方块" +title = "砾石研磨者" +description = "挖掘 500 个砾石、沙子或粘土方块" [advancement.challenge_dig_gravel_5k] - title = "沙砾筛选者" - description = "挖掘 5,000 个砾石、沙子或粘土方块" +title = "沙砾筛选者" +description = "挖掘 5,000 个砾石、沙子或粘土方块" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "播种者" - description = "种植 100 株作物" +title = "播种者" +description = "种植 100 株作物" [advancement.challenge_plant_1k] - title = "绿手指" - description = "种植 1,000 株作物" +title = "绿手指" +description = "种植 1,000 株作物" [advancement.challenge_plant_5k] - title = "农业大亨" - description = "种植 5,000 株作物" +title = "农业大亨" +description = "种植 5,000 株作物" [advancement.challenge_compost_50] - title = "回收利用者" - description = "堆肥 50 个物品" +title = "回收利用者" +description = "堆肥 50 个物品" [advancement.challenge_compost_500] - title = "土壤改良者" - description = "堆肥 500 个物品" +title = "土壤改良者" +description = "堆肥 500 个物品" [advancement.challenge_shear_50] - title = "剪毛者" - description = "剪切 50 个实体" +title = "剪毛者" +description = "剪切 50 个实体" [advancement.challenge_shear_250] - title = "牧群之主" - description = "剪切 250 个实体" +title = "牧群之主" +description = "剪切 250 个实体" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "屠戮者" - description = "击杀 500 个生物" +title = "屠戮者" +description = "击杀 500 个生物" [advancement.challenge_kills_5k] - title = "行刑者" - description = "击杀 5,000 个生物" +title = "行刑者" +description = "击杀 5,000 个生物" [advancement.challenge_boss_1] - title = "挑战首领" - description = "击杀一个首领怪物" +title = "挑战首领" +description = "击杀一个首领怪物" [advancement.challenge_boss_10] - title = "传奇杀手" - description = "击杀 10 个首领怪物" +title = "传奇杀手" +description = "击杀 10 个首领怪物" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "凋零之痛" - description = "承受 500 凋零伤害" +title = "凋零之痛" +description = "承受 500 凋零伤害" [advancement.challenge_wither_dmg_5k] - title = "枯萎幸存者" - description = "承受 5,000 凋零伤害" +title = "枯萎幸存者" +description = "承受 5,000 凋零伤害" [advancement.challenge_wither_skel_25] - title = "骸骨收集者" - description = "击杀 25 个凋灵骷髅" +title = "骸骨收集者" +description = "击杀 25 个凋灵骷髅" [advancement.challenge_wither_skel_250] - title = "骷髅克星" - description = "击杀 250 个凋灵骷髅" +title = "骷髅克星" +description = "击杀 250 个凋灵骷髅" [advancement.challenge_wither_boss_1] - title = "凋灵杀手" - description = "击败凋灵" +title = "凋灵杀手" +description = "击败凋灵" [advancement.challenge_wither_boss_10] - title = "下界主宰" - description = "击败凋灵 10 次" +title = "下界主宰" +description = "击败凋灵 10 次" [advancement.challenge_roses_10] - title = "死亡园丁" - description = "破坏 10 朵凋灵玫瑰" +title = "死亡园丁" +description = "破坏 10 朵凋灵玫瑰" [advancement.challenge_roses_100] - title = "枯萎收集者" - description = "破坏 100 朵凋灵玫瑰" +title = "枯萎收集者" +description = "破坏 100 朵凋灵玫瑰" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "矿工之臂" - description = "挥舞镐子 500 次" +title = "矿工之臂" +description = "挥舞镐子 500 次" [advancement.challenge_pick_swing_5k] - title = "隧道挖掘者" - description = "挥舞镐子 5,000 次" +title = "隧道挖掘者" +description = "挥舞镐子 5,000 次" [advancement.challenge_pick_damage_1k] - title = "镐战者" - description = "用镐子造成 1,000 伤害" +title = "镐战者" +description = "用镐子造成 1,000 伤害" [advancement.challenge_pick_damage_10k] - title = "战镐" - description = "用镐子造成 10,000 伤害" +title = "战镐" +description = "用镐子造成 10,000 伤害" [advancement.challenge_pick_value_5k] - title = "宝石探索者" - description = "开采价值 5,000 的方块" +title = "宝石探索者" +description = "开采价值 5,000 的方块" [advancement.challenge_pick_value_50k] - title = "矿石大亨" - description = "开采价值 50,000 的方块" +title = "矿石大亨" +description = "开采价值 50,000 的方块" [advancement.challenge_pick_ores_500] - title = "勘探者" - description = "开采 500 个矿石方块" +title = "勘探者" +description = "开采 500 个矿石方块" [advancement.challenge_pick_ores_5k] - title = "采矿大师" - description = "开采 5,000 个矿石方块" +title = "采矿大师" +description = "开采 5,000 个矿石方块" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "神射手" - description = "造成 1,000 远程伤害" +title = "神射手" +description = "造成 1,000 远程伤害" [advancement.challenge_ranged_dmg_10k] - title = "致命弓手" - description = "造成 10,000 远程伤害" +title = "致命弓手" +description = "造成 10,000 远程伤害" [advancement.challenge_ranged_dist_5k] - title = "远距射击" - description = "发射投射物总飞行距离达 5,000 格" +title = "远距射击" +description = "发射投射物总飞行距离达 5,000 格" [advancement.challenge_ranged_dist_50k] - title = "千里射手" - description = "发射投射物总飞行距离达 50,000 格" +title = "千里射手" +description = "发射投射物总飞行距离达 50,000 格" [advancement.challenge_ranged_kills_50] - title = "弓箭手" - description = "用远程武器击杀 50 个生物" +title = "弓箭手" +description = "用远程武器击杀 50 个生物" [advancement.challenge_ranged_kills_500] - title = "射术宗师" - description = "用远程武器击杀 500 个生物" +title = "射术宗师" +description = "用远程武器击杀 500 个生物" [advancement.challenge_longshot_25] - title = "狙击手" - description = "命中 25 次远距射击 (超过 30 格)" +title = "狙击手" +description = "命中 25 次远距射击 (超过 30 格)" [advancement.challenge_longshot_250] - title = "鹰眼" - description = "命中 250 次远距射击 (超过 30 格)" +title = "鹰眼" +description = "命中 250 次远距射击 (超过 30 格)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "珍珠投掷者" - description = "投掷 50 颗末影珍珠" +title = "珍珠投掷者" +description = "投掷 50 颗末影珍珠" [advancement.challenge_rift_pearls_500] - title = "传送狂人" - description = "投掷 500 颗末影珍珠" +title = "传送狂人" +description = "投掷 500 颗末影珍珠" [advancement.challenge_rift_enderman_50] - title = "末影人猎手" - description = "击杀 50 个末影人" +title = "末影人猎手" +description = "击杀 50 个末影人" [advancement.challenge_rift_enderman_500] - title = "虚空追猎者" - description = "击杀 500 个末影人" +title = "虚空追猎者" +description = "击杀 500 个末影人" [advancement.challenge_rift_dragon_500] - title = "屠龙战士" - description = "对末影龙造成 500 伤害" +title = "屠龙战士" +description = "对末影龙造成 500 伤害" [advancement.challenge_rift_dragon_5k] - title = "屠龙者" - description = "对末影龙造成 5,000 伤害" +title = "屠龙者" +description = "对末影龙造成 5,000 伤害" [advancement.challenge_rift_crystal_10] - title = "水晶破坏者" - description = "摧毁 10 个末地水晶" +title = "水晶破坏者" +description = "摧毁 10 个末地水晶" [advancement.challenge_rift_crystal_100] - title = "末地毁灭者" - description = "摧毁 100 个末地水晶" +title = "末地毁灭者" +description = "摧毁 100 个末地水晶" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "垂钓者" - description = "捕获 25 条鱼" +title = "垂钓者" +description = "捕获 25 条鱼" [advancement.challenge_fish_250] - title = "钓鱼大师" - description = "捕获 250 条鱼" +title = "钓鱼大师" +description = "捕获 250 条鱼" [advancement.challenge_drowned_25] - title = "溺尸猎人" - description = "击杀 25 个溺尸" +title = "溺尸猎人" +description = "击杀 25 个溺尸" [advancement.challenge_drowned_250] - title = "海洋净化者" - description = "击杀 250 个溺尸" +title = "海洋净化者" +description = "击杀 250 个溺尸" [advancement.challenge_guardian_10] - title = "守卫者猎手" - description = "击杀 10 个守卫者" +title = "守卫者猎手" +description = "击杀 10 个守卫者" [advancement.challenge_guardian_100] - title = "神殿掠夺者" - description = "击杀 100 个守卫者" +title = "神殿掠夺者" +description = "击杀 100 个守卫者" [advancement.challenge_underwater_blocks_100] - title = "水下矿工" - description = "在水下破坏 100 个方块" +title = "水下矿工" +description = "在水下破坏 100 个方块" [advancement.challenge_underwater_blocks_1k] - title = "水下工程师" - description = "在水下破坏 1,000 个方块" +title = "水下工程师" +description = "在水下破坏 1,000 个方块" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "背刺者" - description = "潜行时造成 500 伤害" +title = "背刺者" +description = "潜行时造成 500 伤害" [advancement.challenge_stealth_dmg_5k] - title = "无声杀手" - description = "潜行时造成 5,000 伤害" +title = "无声杀手" +description = "潜行时造成 5,000 伤害" [advancement.challenge_stealth_kills_10] - title = "刺客" - description = "潜行时击杀 10 个生物" +title = "刺客" +description = "潜行时击杀 10 个生物" [advancement.challenge_stealth_kills_100] - title = "暗影死神" - description = "潜行时击杀 100 个生物" +title = "暗影死神" +description = "潜行时击杀 100 个生物" [advancement.challenge_stealth_time_1h] - title = "耐心者" - description = "潜行 1 小时 (3,600 秒)" +title = "耐心者" +description = "潜行 1 小时 (3,600 秒)" [advancement.challenge_stealth_time_10h] - title = "暗影之主" - description = "潜行 10 小时 (36,000 秒)" +title = "暗影之主" +description = "潜行 10 小时 (36,000 秒)" [advancement.challenge_stealth_arrows_50] - title = "暗箭手" - description = "潜行时射出 50 支箭" +title = "暗箭手" +description = "潜行时射出 50 支箭" [advancement.challenge_stealth_arrows_500] - title = "幽灵弓手" - description = "潜行时射出 500 支箭" +title = "幽灵弓手" +description = "潜行时射出 500 支箭" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "剑术学徒" - description = "用剑造成 1,000 伤害" +title = "剑术学徒" +description = "用剑造成 1,000 伤害" [advancement.challenge_sword_dmg_10k] - title = "剑客" - description = "用剑造成 10,000 伤害" +title = "剑客" +description = "用剑造成 10,000 伤害" [advancement.challenge_sword_kills_50] - title = "决斗者" - description = "用剑击杀 50 个生物" +title = "决斗者" +description = "用剑击杀 50 个生物" [advancement.challenge_sword_kills_500] - title = "角斗士" - description = "用剑击杀 500 个生物" +title = "角斗士" +description = "用剑击杀 500 个生物" [advancement.challenge_sword_crit_50] - title = "暴击者" - description = "用剑造成 50 次暴击" +title = "暴击者" +description = "用剑造成 50 次暴击" [advancement.challenge_sword_crit_500] - title = "精准大师" - description = "用剑造成 500 次暴击" +title = "精准大师" +description = "用剑造成 500 次暴击" [advancement.challenge_sword_heavy_25] - title = "重击者" - description = "用剑造成 25 次重击 (超过 8 伤害)" +title = "重击者" +description = "用剑造成 25 次重击 (超过 8 伤害)" [advancement.challenge_sword_heavy_250] - title = "毁灭之击" - description = "用剑造成 250 次重击 (超过 8 伤害)" +title = "毁灭之击" +description = "用剑造成 250 次重击 (超过 8 伤害)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "驯兽师" - description = "你的宠物共造成 500 伤害" +title = "驯兽师" +description = "你的宠物共造成 500 伤害" [advancement.challenge_pet_dmg_5k] - title = "战争统帅" - description = "你的宠物共造成 5,000 伤害" +title = "战争统帅" +description = "你的宠物共造成 5,000 伤害" [advancement.challenge_tamed_10] - title = "动物之友" - description = "驯服 10 只动物" +title = "动物之友" +description = "驯服 10 只动物" [advancement.challenge_tamed_100] - title = "动物园长" - description = "驯服 100 只动物" +title = "动物园长" +description = "驯服 100 只动物" [advancement.challenge_pet_kills_25] - title = "群攻战术" - description = "你的宠物击杀 25 个生物" +title = "群攻战术" +description = "你的宠物击杀 25 个生物" [advancement.challenge_pet_kills_250] - title = "首领指挥官" - description = "你的宠物击杀 250 个生物" +title = "首领指挥官" +description = "你的宠物击杀 250 个生物" [advancement.challenge_taming_2500] - title = "繁殖专家" - description = "繁殖 2,500 只动物" +title = "繁殖专家" +description = "繁殖 2,500 只动物" [advancement.challenge_taming_25k] - title = "基因大师" - description = "繁殖 25,000 只动物" +title = "基因大师" +description = "繁殖 25,000 只动物" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "沙袋" - description = "受到 500 次攻击" +title = "沙袋" +description = "受到 500 次攻击" [advancement.challenge_trag_hits_5k] - title = "受虐狂" - description = "受到 5,000 次攻击" +title = "受虐狂" +description = "受到 5,000 次攻击" [advancement.challenge_trag_deaths_10] - title = "九条命" - description = "死亡 10 次" +title = "九条命" +description = "死亡 10 次" [advancement.challenge_trag_deaths_100] - title = "反复噩梦" - description = "死亡 100 次" +title = "反复噩梦" +description = "死亡 100 次" [advancement.challenge_trag_fire_500] - title = "火焰受害者" - description = "承受 500 火焰伤害" +title = "火焰受害者" +description = "承受 500 火焰伤害" [advancement.challenge_trag_fire_5k] - title = "浴火凤凰" - description = "承受 5,000 火焰伤害" +title = "浴火凤凰" +description = "承受 5,000 火焰伤害" [advancement.challenge_trag_fall_500] - title = "重力测试" - description = "承受 500 跌落伤害" +title = "重力测试" +description = "承受 500 跌落伤害" [advancement.challenge_trag_fall_5k] - title = "极限速度" - description = "承受 5,000 跌落伤害" +title = "极限速度" +description = "承受 5,000 跌落伤害" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "拳击手" - description = "用空手造成 1,000 伤害" +title = "拳击手" +description = "用空手造成 1,000 伤害" [advancement.challenge_unarmed_dmg_10k] - title = "武术家" - description = "用空手造成 10,000 伤害" +title = "武术家" +description = "用空手造成 10,000 伤害" [advancement.challenge_unarmed_kills_25] - title = "赤手空拳" - description = "用空手击杀 25 个生物" +title = "赤手空拳" +description = "用空手击杀 25 个生物" [advancement.challenge_unarmed_kills_250] - title = "传奇之拳" - description = "用空手击杀 250 个生物" +title = "传奇之拳" +description = "用空手击杀 250 个生物" [advancement.challenge_unarmed_crit_25] - title = "致命一拳" - description = "用空手造成 25 次暴击" +title = "致命一拳" +description = "用空手造成 25 次暴击" [advancement.challenge_unarmed_crit_250] - title = "精准之拳" - description = "用空手造成 250 次暴击" +title = "精准之拳" +description = "用空手造成 250 次暴击" [advancement.challenge_unarmed_heavy_25] - title = "力量之拳" - description = "用空手造成 25 次重击 (超过 6 伤害)" +title = "力量之拳" +description = "用空手造成 25 次重击 (超过 6 伤害)" [advancement.challenge_unarmed_heavy_250] - title = "拳王" - description = "用空手造成 250 次重击 (超过 6 伤害)" +title = "拳王" +description = "用空手造成 250 次重击 (超过 6 伤害)" # items [items] [items.bound_ender_peral] - name = "遗物传送钥" - usage1 = "潜行 + 左键点击绑定" - usage2 = "右键点击访问已绑定的物品栏" +name = "遗物传送钥" +usage1 = "潜行 + 左键点击绑定" +usage2 = "右键点击访问已绑定的物品栏" [items.bound_eye_of_ender] - name = "视觉锚点" - usage1 = "右键点击消耗此物品并传送至绑定位置" - usage2 = "潜行 + 左键点击方块以绑定" +name = "视觉锚点" +usage1 = "右键点击消耗此物品并传送至绑定位置" +usage2 = "潜行 + 左键点击方块以绑定" [items.bound_redstone_torch] - name = "红石遥控器" - usage1 = "右键点击创建一个 1 Tick 的红石脉冲" - usage2 = "潜行 + 左键点击'目标'方块以绑定" +name = "红石遥控器" +usage1 = "右键点击创建一个 1 Tick 的红石脉冲" +usage2 = "潜行 + 左键点击'目标'方块以绑定" [items.bound_snowball] - name = "蛛网陷阱!" - usage1 = "投掷以在目标位置创建一个临时蛛网陷阱" +name = "蛛网陷阱!" +usage1 = "投掷以在目标位置创建一个临时蛛网陷阱" [items.chrono_time_bottle] - name = "时光之瓶" - usage1 = "放在物品栏中时会自动储存时间" - usage2 = "右键点击计时方块或幼年动物以消耗储存的时间" - stored = "已储存时间" +name = "时光之瓶" +usage1 = "放在物品栏中时会自动储存时间" +usage2 = "右键点击计时方块或幼年动物以消耗储存的时间" +stored = "已储存时间" [items.chrono_time_bomb] - name = "时间炸弹" - usage1 = "右键点击发射一颗时空弹,制造一个时间力场" +name = "时间炸弹" +usage1 = "右键点击发射一颗时空弹,制造一个时间力场" [items.elevator_block] - name = "电梯方块" - usage1 = "跳跃向上传送" - usage2 = "潜行向下传送" - usage3 = "电梯之间至少需要 2 个空气方块" +name = "电梯方块" +usage1 = "跳跃向上传送" +usage2 = "潜行向下传送" +usage3 = "电梯之间至少需要 2 个空气方块" # snippets [snippets] [snippets.gui] - level = "等级" - knowledge = "知识" - power_used = "已用能力" - not_learned = "尚未学习" - xp = "经验至" - welcome = "欢迎!" - welcome_back = "欢迎回来!" - xp_bonus_for_time = "经验加成持续" - max_ability_power = "最大能力值" - unlock_this_by_clicking = "右键点击此方块以解锁:" - back = "返回" - unlearn_all = "忘却全部" - unlearned_all = "已全部忘却" +level = "等级" +knowledge = "知识" +power_used = "已用能力" +not_learned = "尚未学习" +xp = "经验至" +welcome = "欢迎!" +welcome_back = "欢迎回来!" +xp_bonus_for_time = "经验加成持续" +max_ability_power = "最大能力值" +unlock_this_by_clicking = "右键点击此方块以解锁:" +back = "返回" +unlearn_all = "忘却全部" +unlearned_all = "已全部忘却" [snippets.adapt_menu] - may_not_unlearn = "无法忘却此技能" - may_unlearn = "可学习/忘却" - knowledge_cost = "知识消耗" - knowledge_available = "可用知识" - already_learned = "已学习" - unlearn_refund = "点击忘却并退还" - no_refunds = "极限模式,退还已禁用" - knowledge = "知识" - click_learn = "点击以学习" - no_knowledge = "(你没有任何知识)" - you_only_have = "你只有" - how_to_level_up = "升级技能以提升最大能力值。" - not_enough_power = "能力不足!每个技能等级消耗 1 点能力。" - power = "能力" - power_drain = "能力消耗" - learned = "已学习 " - unlearned = "已忘却 " - activator_block = "书架" +may_not_unlearn = "无法忘却此技能" +may_unlearn = "可学习/忘却" +knowledge_cost = "知识消耗" +knowledge_available = "可用知识" +already_learned = "已学习" +unlearn_refund = "点击忘却并退还" +no_refunds = "极限模式,退还已禁用" +knowledge = "知识" +click_learn = "点击以学习" +no_knowledge = "(你没有任何知识)" +you_only_have = "你只有" +how_to_level_up = "升级技能以提升最大能力值。" +not_enough_power = "能力不足!每个技能等级消耗 1 点能力。" +power = "能力" +power_drain = "能力消耗" +learned = "已学习 " +unlearned = "已忘却 " +activator_block = "书架" [snippets.knowledge_orb] - contains = "包含" - knowledge = "知识" - rightclick = "右键点击" - togainknowledge = "以获取此知识" - knowledge_orb = "知识之珠" +contains = "包含" +knowledge = "知识" +rightclick = "右键点击" +togainknowledge = "以获取此知识" +knowledge_orb = "知识之珠" [snippets.experience_orb] - contains = "包含" - xp = "经验" - rightclick = "右键点击" - togainxp = "以获取此经验" - xporb = "经验球" +contains = "包含" +xp = "经验" +rightclick = "右键点击" +togainxp = "以获取此经验" +xporb = "经验球" # skill [skill] [skill.agility] - name = "敏捷" - icon = "⇉" - description = "敏捷是面对障碍时快速灵活移动的能力。" +name = "敏捷" +icon = "⇉" +description = "敏捷是面对障碍时快速灵活移动的能力。" [skill.architect] - name = "建筑师" - icon = "⬧" - description = "结构是世界的基石。现实掌握在你手中,由你掌控。" +name = "建筑师" +icon = "⬧" +description = "结构是世界的基石。现实掌握在你手中,由你掌控。" [skill.axes] - name = "斧技" - icon = "🪓" - description1 = "与其砍树,不如砍" - description2 = "别的东西" - description3 = ",结果都一样!" +name = "斧技" +icon = "🪓" +description1 = "与其砍树,不如砍" +description2 = "别的东西" +description3 = ",结果都一样!" [skill.brewing] - name = "酿造" - icon = "❦" - description = "双层泡泡,三层泡泡,四层泡泡——我还是没法把药水倒进炼药锅里" +name = "酿造" +icon = "❦" +description = "双层泡泡,三层泡泡,四层泡泡——我还是没法把药水倒进炼药锅里" [skill.blocking] - name = "防御" - icon = "🛡" - description = "棍棒和石头不会折断你的骨头,但盾牌可以。" +name = "防御" +icon = "🛡" +description = "棍棒和石头不会折断你的骨头,但盾牌可以。" [skill.crafting] - name = "合成" - icon = "⌂" - description = "当没有东西可以拼凑的时候,为何不再做一个呢?" +name = "合成" +icon = "⌂" +description = "当没有东西可以拼凑的时候,为何不再做一个呢?" [skill.discovery] - name = "探索" - icon = "⚛" - description = "随着感知的扩展,你的心智将揭开那些未曾发现的奥秘。" +name = "探索" +icon = "⚛" +description = "随着感知的扩展,你的心智将揭开那些未曾发现的奥秘。" [skill.enchanting] - name = "附魔" - icon = "♰" - description = "你在说什么?预言、幻象、还是迷信的胡言乱语?" +name = "附魔" +icon = "♰" +description = "你在说什么?预言、幻象、还是迷信的胡言乱语?" [skill.excavation] - name = "挖掘" - icon = "ᛳ" - description = "挖呀挖呀挖……" +name = "挖掘" +icon = "ᛳ" +description = "挖呀挖呀挖……" [skill.herbalism] - name = "草药学" - icon = "⚘" - description = "我找不到任何植物,但我能找到一些种子还有——那是……杂草?" +name = "草药学" +icon = "⚘" +description = "我找不到任何植物,但我能找到一些种子还有——那是……杂草?" [skill.hunter] - name = "狩猎" - icon = "☠" - description = "狩猎重在过程,而非结果。" +name = "狩猎" +icon = "☠" +description = "狩猎重在过程,而非结果。" [skill.nether] - name = "下界" - icon = "₪" - description = "来自下界深处。" +name = "下界" +icon = "₪" +description = "来自下界深处。" [skill.pickaxe] - name = "采矿" - icon = "⛏" - description = "矮人才是矿工,但我也从中学到了一两招。我是瑞典人!" +name = "采矿" +icon = "⛏" +description = "矮人才是矿工,但我也从中学到了一两招。我是瑞典人!" [skill.ranged] - name = "箭术" - icon = "🏹" - description = "距离是胜利的关键,也是生存的关键。" +name = "箭术" +icon = "🏹" +description = "距离是胜利的关键,也是生存的关键。" [skill.rift] - name = "裂隙" - icon = "❍" - description = "裂隙是危险的束缚,但你已驾驭了这股力量。" +name = "裂隙" +icon = "❍" +description = "裂隙是危险的束缚,但你已驾驭了这股力量。" [skill.seaborne] - name = "航海" - icon = "🎣" - description = "拥有这项技能,你便能驾驭水中的奇迹。" +name = "航海" +icon = "🎣" +description = "拥有这项技能,你便能驾驭水中的奇迹。" [skill.stealth] - name = "潜行" - icon = "☯" - description = "隐匿的艺术。行走于暗影之中。" +name = "潜行" +icon = "☯" +description = "隐匿的艺术。行走于暗影之中。" [skill.swords] - name = "剑术" - icon = "⚔" - description = "凭借灰石之力!" +name = "剑术" +icon = "⚔" +description = "凭借灰石之力!" [skill.taming] - name = "驯兽" - icon = "♥" - description = "鹦鹉和蜜蜂……还有你?" +name = "驯兽" +icon = "♥" +description = "鹦鹉和蜜蜂……还有你?" [skill.tragoul] - name = "塔格奥" - icon = "🗡" - description = "血液在宇宙的脉管中流淌,被你的双手所束缚。" +name = "塔格奥" +icon = "🗡" +description = "血液在宇宙的脉管中流淌,被你的双手所束缚。" [skill.chronos] - name = "时空" - icon = "🕒" - description = "拨动宇宙之钟,感受时间的流逝。打碎它,成为它。" +name = "时空" +icon = "🕒" +description = "拨动宇宙之钟,感受时间的流逝。打碎它,成为它。" [skill.unarmed] - name = "搏击" - icon = "»" - description = "没有武器并不意味着没有力量。" +name = "搏击" +icon = "»" +description = "没有武器并不意味着没有力量。" # agility [agility] [agility.armor_up] - name = "装甲蓄力" - description = "冲刺越久,获得的护甲值越多!" - lore1 = "最大护甲值" - lore2 = "蓄力时间" - lore = ["最大护甲值", "蓄力时间"] +name = "装甲蓄力" +description = "冲刺越久,获得的护甲值越多!" +lore1 = "最大护甲值" +lore2 = "蓄力时间" +lore = ["最大护甲值", "蓄力时间"] [agility.ladder_slide] - name = "梯子滑行" - description = "大幅加快梯子上下攀爬速度。" - lore1 = "梯子速度倍率" - lore2 = "快速下降速度" - lore = ["梯子速度倍率", "快速下降速度"] +name = "梯子滑行" +description = "大幅加快梯子上下攀爬速度。" +lore1 = "梯子速度倍率" +lore2 = "快速下降速度" +lore = ["梯子速度倍率", "快速下降速度"] [agility.super_jump] - name = "超级跳跃" - description = "卓越的高度优势。" - lore1 = "最大跳跃高度" - lore2 = "潜行 + 跳跃进行超级跳跃!" - lore = ["最大跳跃高度", "潜行 + 跳跃进行超级跳跃!"] +name = "超级跳跃" +description = "卓越的高度优势。" +lore1 = "最大跳跃高度" +lore2 = "潜行 + 跳跃进行超级跳跃!" +lore = ["最大跳跃高度", "潜行 + 跳跃进行超级跳跃!"] [agility.wall_jump] - name = "蹬墙跳" - description = "在空中靠近墙壁时按住潜行键以附墙并跳跃!" - lore1 = "最大跳跃次数" - lore2 = "跳跃高度" - lore = ["最大跳跃次数", "跳跃高度"] +name = "蹬墙跳" +description = "在空中靠近墙壁时按住潜行键以附墙并跳跃!" +lore1 = "最大跳跃次数" +lore2 = "跳跃高度" +lore = ["最大跳跃次数", "跳跃高度"] [agility.wind_up] - name = "蓄力加速" - description = "冲刺时间越长,速度越快!" - lore1 = "最大速度" - lore2 = "蓄力时间" - lore = ["最大速度", "蓄力时间"] +name = "蓄力加速" +description = "冲刺时间越长,速度越快!" +lore1 = "最大速度" +lore2 = "蓄力时间" +lore = ["最大速度", "蓄力时间"] # architect [architect] [architect.elevator] - name = "电梯" - description = "建造电梯以快速垂直传送!" - lore1 = "解锁电梯配方:X=羊毛,Y=末影珍珠" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["解锁电梯配方:X=羊毛,Y=末影珍珠", "XXX", "XYX", "XXX"] +name = "电梯" +description = "建造电梯以快速垂直传送!" +lore1 = "解锁电梯配方:X=羊毛,Y=末影珍珠" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["解锁电梯配方:X=羊毛,Y=末影珍珠", "XXX", "XYX", "XXX"] [architect.foundation] - name = "魔法地基" - description = "潜行时在脚下放置临时地基!" - lore1 = "魔法生成:" - lore2 = "个方块在你脚下!" - lore = ["魔法生成:", "个方块在你脚下!"] +name = "魔法地基" +description = "潜行时在脚下放置临时地基!" +lore1 = "魔法生成:" +lore2 = "个方块在你脚下!" +lore = ["魔法生成:", "个方块在你脚下!"] [architect.glass] - name = "精准触碰玻璃" - description = "空手打碎玻璃时不再损失玻璃方块!" - lore1 = "你的双手对玻璃具有精准采集效果" - lore = ["你的双手对玻璃具有精准采集效果"] +name = "精准触碰玻璃" +description = "空手打碎玻璃时不再损失玻璃方块!" +lore1 = "你的双手对玻璃具有精准采集效果" +lore = ["你的双手对玻璃具有精准采集效果"] [architect.wireless_redstone] - name = "红石遥控器" - description = "使用红石火把远程切换红石信号!" - lore1 = "标靶 + 红石火把 + 末影珍珠 = 1 个红石遥控器" - lore = ["标靶 + 红石火把 + 末影珍珠 = 1 个红石遥控器"] +name = "红石遥控器" +description = "使用红石火把远程切换红石信号!" +lore1 = "标靶 + 红石火把 + 末影珍珠 = 1 个红石遥控器" +lore = ["标靶 + 红石火把 + 末影珍珠 = 1 个红石遥控器"] [architect.placement] - name = "建筑师之杖" - description = "一次放置多个方块!按住潜行,手持与目标方块相同的方块并放置!请注意,你可能需要稍微移动来触发边界计算!" - lore1 = "你需要手持" - lore2 = "个方块才能放置" - lore3 = "材质建筑师之杖" - lore = ["你需要手持", "个方块才能放置", "材质建筑师之杖"] +name = "建筑师之杖" +description = "一次放置多个方块!按住潜行,手持与目标方块相同的方块并放置!请注意,你可能需要稍微移动来触发边界计算!" +lore1 = "你需要手持" +lore2 = "个方块才能放置" +lore3 = "材质建筑师之杖" +lore = ["你需要手持", "个方块才能放置", "材质建筑师之杖"] # axe [axe] [axe.chop] - name = "伐木劈砍" - description = "右键点击底部原木来砍倒整棵树!" - lore1 = "每次劈砍方块数" - lore2 = "劈砍冷却" - lore3 = "耐久消耗" - lore = ["每次劈砍方块数", "劈砍冷却", "耐久消耗"] +name = "伐木劈砍" +description = "右键点击底部原木来砍倒整棵树!" +lore1 = "每次劈砍方块数" +lore2 = "劈砍冷却" +lore3 = "耐久消耗" +lore = ["每次劈砍方块数", "劈砍冷却", "耐久消耗"] [axe.log_swap] - name = "露西的原木转换器" - description = "在工作台中转换原木种类!" - lore1 = "8 个任意种类的原木 + 1 株树苗 = 8 个树苗对应种类的原木" - lore = ["8 个任意种类的原木 + 1 株树苗 = 8 个树苗对应种类的原木"] +name = "露西的原木转换器" +description = "在工作台中转换原木种类!" +lore1 = "8 个任意种类的原木 + 1 株树苗 = 8 个树苗对应种类的原木" +lore = ["8 个任意种类的原木 + 1 株树苗 = 8 个树苗对应种类的原木"] [axe.drop_to_inventory] - name = "斧头掉落物直入背包" +name = "斧头掉落物直入背包" [axe.ground_smash] - name = "地面震击" - description = "跳起后蹲下,震击附近所有敌人。" - lore1 = "伤害" - lore2 = "方块半径" - lore3 = "力量" - lore4 = "震击冷却" - lore = ["伤害", "方块半径", "力量", "震击冷却"] +name = "地面震击" +description = "跳起后蹲下,震击附近所有敌人。" +lore1 = "伤害" +lore2 = "方块半径" +lore3 = "力量" +lore4 = "震击冷却" +lore = ["伤害", "方块半径", "力量", "震击冷却"] [axe.leaf_miner] - name = "树叶矿工" - description = "一次破坏大量树叶!" - lore1 = "潜行并挖掘树叶" - lore2 = "树叶挖掘范围" - lore3 = "不会获得树叶的掉落物(防滥用)" - lore = ["潜行并挖掘树叶", "树叶挖掘范围", "不会获得树叶的掉落物(防滥用)"] +name = "树叶矿工" +description = "一次破坏大量树叶!" +lore1 = "潜行并挖掘树叶" +lore2 = "树叶挖掘范围" +lore3 = "不会获得树叶的掉落物(防滥用)" +lore = ["潜行并挖掘树叶", "树叶挖掘范围", "不会获得树叶的掉落物(防滥用)"] [axe.wood_miner] - name = "原木矿工" - description = "一次砍伐大量木头!" - lore1 = "潜行并砍伐木头/原木(木板无效)" - lore2 = "木材挖掘范围" - lore3 = "与掉落物直入背包兼容" - lore = ["潜行并砍伐木头/原木(木板无效)", "木材挖掘范围", "与掉落物直入背包兼容"] +name = "原木矿工" +description = "一次砍伐大量木头!" +lore1 = "潜行并砍伐木头/原木(木板无效)" +lore2 = "木材挖掘范围" +lore3 = "与掉落物直入背包兼容" +lore = ["潜行并砍伐木头/原木(木板无效)", "木材挖掘范围", "与掉落物直入背包兼容"] # brewing [brewing] [brewing.lingering] - name = "持久酿造" - description = "酿造的药水持续时间更长!" - lore1 = "持续时间" - lore2 = "持续时间" - lore = ["持续时间", "持续时间"] +name = "持久酿造" +description = "酿造的药水持续时间更长!" +lore1 = "持续时间" +lore2 = "持续时间" +lore = ["持续时间", "持续时间"] [brewing.super_heated] - name = "超级加热酿造" - description = "酿造台越热,工作速度越快。" - lore1 = "每接触一个火焰方块" - lore2 = "每接触一个岩浆方块" - lore = ["每接触一个火焰方块", "每接触一个岩浆方块"] +name = "超级加热酿造" +description = "酿造台越热,工作速度越快。" +lore1 = "每接触一个火焰方块" +lore2 = "每接触一个岩浆方块" +lore = ["每接触一个火焰方块", "每接触一个岩浆方块"] [brewing.darkness] - name = "瓶装黑暗" - description = "不知道你为什么需要这个,但给你了!" - lore1 = "夜视药水 + 黑色混凝土 = 黑暗药水(30 秒)" - lore2 = "注意:此药水会阻止使用者疾跑!" - lore = ["夜视药水 + 黑色混凝土 = 黑暗药水(30 秒)", "注意:此药水会阻止使用者疾跑!"] +name = "瓶装黑暗" +description = "不知道你为什么需要这个,但给你了!" +lore1 = "夜视药水 + 黑色混凝土 = 黑暗药水(30 秒)" +lore2 = "注意:此药水会阻止使用者疾跑!" +lore = ["夜视药水 + 黑色混凝土 = 黑暗药水(30 秒)", "注意:此药水会阻止使用者疾跑!"] [brewing.haste] - name = "瓶装急迫" - description = "当效率附魔还不够的时候" - lore1 = "速度药水 + 紫水晶碎片 = 急迫药水(60 秒)" - lore2 = "速度药水 + 紫水晶块 = 急迫 II 药水(30 秒)" - lore = ["速度药水 + 紫水晶碎片 = 急迫药水(60 秒)", "速度药水 + 紫水晶块 = 急迫 II 药水(30 秒)"] +name = "瓶装急迫" +description = "当效率附魔还不够的时候" +lore1 = "速度药水 + 紫水晶碎片 = 急迫药水(60 秒)" +lore2 = "速度药水 + 紫水晶块 = 急迫 II 药水(30 秒)" +lore = ["速度药水 + 紫水晶碎片 = 急迫药水(60 秒)", "速度药水 + 紫水晶块 = 急迫 II 药水(30 秒)"] [brewing.absorption] - name = "瓶装伤害吸收" - description = "强化身体!" - lore1 = "瞬间治疗 + 石英 = 伤害吸收药水(60 秒)" - lore2 = "瞬间治疗 + 石英块 = 伤害吸收 II 药水(30 秒)" - lore = ["瞬间治疗 + 石英 = 伤害吸收药水(60 秒)", "瞬间治疗 + 石英块 = 伤害吸收 II 药水(30 秒)"] +name = "瓶装伤害吸收" +description = "强化身体!" +lore1 = "瞬间治疗 + 石英 = 伤害吸收药水(60 秒)" +lore2 = "瞬间治疗 + 石英块 = 伤害吸收 II 药水(30 秒)" +lore = ["瞬间治疗 + 石英 = 伤害吸收药水(60 秒)", "瞬间治疗 + 石英块 = 伤害吸收 II 药水(30 秒)"] [brewing.fatigue] - name = "瓶装疲劳" - description = "削弱身体!" - lore1 = "虚弱药水 + 史莱姆球 = 挖掘疲劳药水(30 秒)" - lore2 = "虚弱药水 + 史莱姆块 = 挖掘疲劳 II 药水(15 秒)" - lore = ["虚弱药水 + 史莱姆球 = 挖掘疲劳药水(30 秒)", "虚弱药水 + 史莱姆块 = 挖掘疲劳 II 药水(15 秒)"] +name = "瓶装疲劳" +description = "削弱身体!" +lore1 = "虚弱药水 + 史莱姆球 = 挖掘疲劳药水(30 秒)" +lore2 = "虚弱药水 + 史莱姆块 = 挖掘疲劳 II 药水(15 秒)" +lore = ["虚弱药水 + 史莱姆球 = 挖掘疲劳药水(30 秒)", "虚弱药水 + 史莱姆块 = 挖掘疲劳 II 药水(15 秒)"] [brewing.hunger] - name = "瓶装饥饿" - description = "喂饱那永不满足的胃!" - lore1 = "粗制的药水 + 腐肉 = 饥饿药水(30 秒)" - lore2 = "虚弱药水 + 腐肉 = 饥饿 III 药水(15 秒)" - lore = ["粗制的药水 + 腐肉 = 饥饿药水(30 秒)", "虚弱药水 + 腐肉 = 饥饿 III 药水(15 秒)"] +name = "瓶装饥饿" +description = "喂饱那永不满足的胃!" +lore1 = "粗制的药水 + 腐肉 = 饥饿药水(30 秒)" +lore2 = "虚弱药水 + 腐肉 = 饥饿 III 药水(15 秒)" +lore = ["粗制的药水 + 腐肉 = 饥饿药水(30 秒)", "虚弱药水 + 腐肉 = 饥饿 III 药水(15 秒)"] [brewing.nausea] - name = "瓶装反胃" - description = "你让我恶心!" - lore1 = "粗制的药水 + 棕色蘑菇 = 反胃药水(16 秒)" - lore2 = "粗制的药水 + 绯红菌 = 反胃 II 药水(8 秒)" - lore = ["粗制的药水 + 棕色蘑菇 = 反胃药水(16 秒)", "粗制的药水 + 绯红菌 = 反胃 II 药水(8 秒)"] +name = "瓶装反胃" +description = "你让我恶心!" +lore1 = "粗制的药水 + 棕色蘑菇 = 反胃药水(16 秒)" +lore2 = "粗制的药水 + 绯红菌 = 反胃 II 药水(8 秒)" +lore = ["粗制的药水 + 棕色蘑菇 = 反胃药水(16 秒)", "粗制的药水 + 绯红菌 = 反胃 II 药水(8 秒)"] [brewing.blindness] - name = "瓶装失明" - description = "你真是个可怕的人……" - lore1 = "粗制的药水 + 墨囊 = 失明药水(30 秒)" - lore2 = "粗制的药水 + 荧光墨囊 = 失明 II 药水(15 秒)" - lore = ["粗制的药水 + 墨囊 = 失明药水(30 秒)", "粗制的药水 + 荧光墨囊 = 失明 II 药水(15 秒)"] +name = "瓶装失明" +description = "你真是个可怕的人……" +lore1 = "粗制的药水 + 墨囊 = 失明药水(30 秒)" +lore2 = "粗制的药水 + 荧光墨囊 = 失明 II 药水(15 秒)" +lore = ["粗制的药水 + 墨囊 = 失明药水(30 秒)", "粗制的药水 + 荧光墨囊 = 失明 II 药水(15 秒)"] [brewing.resistance] - name = "瓶装抗性" - description = "最佳防御!" - lore1 = "粗制的药水 + 铁锭 = 抗性提升药水(60 秒)" - lore2 = "粗制的药水 + 铁块 = 抗性提升 II 药水(30 秒)" - lore = ["粗制的药水 + 铁锭 = 抗性提升药水(60 秒)", "粗制的药水 + 铁块 = 抗性提升 II 药水(30 秒)"] +name = "瓶装抗性" +description = "最佳防御!" +lore1 = "粗制的药水 + 铁锭 = 抗性提升药水(60 秒)" +lore2 = "粗制的药水 + 铁块 = 抗性提升 II 药水(30 秒)" +lore = ["粗制的药水 + 铁锭 = 抗性提升药水(60 秒)", "粗制的药水 + 铁块 = 抗性提升 II 药水(30 秒)"] [brewing.health_boost] - name = "瓶装生命" - description = "当最大生命值还不够的时候……" - lore1 = "瞬间治疗药水 + 金苹果 = 生命提升药水(120 秒)" - lore2 = "瞬间治疗药水 + 附魔金苹果 = 生命提升 II 药水(120 秒)" - lore = ["瞬间治疗药水 + 金苹果 = 生命提升药水(120 秒)", "瞬间治疗药水 + 附魔金苹果 = 生命提升 II 药水(120 秒)"] +name = "瓶装生命" +description = "当最大生命值还不够的时候……" +lore1 = "瞬间治疗药水 + 金苹果 = 生命提升药水(120 秒)" +lore2 = "瞬间治疗药水 + 附魔金苹果 = 生命提升 II 药水(120 秒)" +lore = ["瞬间治疗药水 + 金苹果 = 生命提升药水(120 秒)", "瞬间治疗药水 + 附魔金苹果 = 生命提升 II 药水(120 秒)"] [brewing.decay] - name = "瓶装腐朽" - description = "谁知道垃圾竟然这么有用?" - lore1 = "虚弱药水 + 毒马铃薯 = 凋零药水(16 秒)" - lore2 = "虚弱药水 + 绯红菌索 = 凋零 II 药水(8 秒)" - lore = ["虚弱药水 + 毒马铃薯 = 凋零药水(16 秒)", "虚弱药水 + 绯红菌索 = 凋零 II 药水(8 秒)"] +name = "瓶装腐朽" +description = "谁知道垃圾竟然这么有用?" +lore1 = "虚弱药水 + 毒马铃薯 = 凋零药水(16 秒)" +lore2 = "虚弱药水 + 绯红菌索 = 凋零 II 药水(8 秒)" +lore = ["虚弱药水 + 毒马铃薯 = 凋零药水(16 秒)", "虚弱药水 + 绯红菌索 = 凋零 II 药水(8 秒)"] [brewing.saturation] - name = "瓶装饱和" - description = "你知道吗……其实我一点也不饿……" - lore1 = "生命恢复药水 + 烤马铃薯 = 饱和药水" - lore2 = "生命恢复药水 + 干草块 = 饱和 II 药水" - lore = ["生命恢复药水 + 烤马铃薯 = 饱和药水", "生命恢复药水 + 干草块 = 饱和 II 药水"] +name = "瓶装饱和" +description = "你知道吗……其实我一点也不饿……" +lore1 = "生命恢复药水 + 烤马铃薯 = 饱和药水" +lore2 = "生命恢复药水 + 干草块 = 饱和 II 药水" +lore = ["生命恢复药水 + 烤马铃薯 = 饱和药水", "生命恢复药水 + 干草块 = 饱和 II 药水"] # blocking [blocking] [blocking.chain_armorer] - name = "梅菲斯特的锁链" - description = "允许你合成锁链盔甲" - lore1 = "合成配方与其他盔甲相同,但使用铁粒代替" - lore = ["合成配方与其他盔甲相同,但使用铁粒代替"] +name = "梅菲斯特的锁链" +description = "允许你合成锁链盔甲" +lore1 = "合成配方与其他盔甲相同,但使用铁粒代替" +lore = ["合成配方与其他盔甲相同,但使用铁粒代替"] [blocking.horse_armorer] - name = "可合成马铠" - description = "允许你合成马铠" - lore1 = "用你想要的材料围绕马鞍即可合成对应材质的马铠" - lore = ["用你想要的材料围绕马鞍即可合成对应材质的马铠"] +name = "可合成马铠" +description = "允许你合成马铠" +lore1 = "用你想要的材料围绕马鞍即可合成对应材质的马铠" +lore = ["用你想要的材料围绕马鞍即可合成对应材质的马铠"] [blocking.saddle_crafter] - name = "可合成马鞍" - description = "用皮革合成马鞍" - lore1 = "配方:5 块皮革:" - lore = ["配方:5 块皮革:"] +name = "可合成马鞍" +description = "用皮革合成马鞍" +lore1 = "配方:5 块皮革:" +lore = ["配方:5 块皮革:"] [blocking.multi_armor] - name = "复合盔甲" - description = "将鞘翅绑定到盔甲上" - lore1 = "这是旅行的绝佳技能。" - lore2 = "动态合并并随时切换盔甲/鞘翅!" - lore3 = "合并方法:在物品栏中按住 Shift 点击一个物品放到另一个上。" - lore4 = "解除绑定:潜行时丢弃该物品,它就会拆解。" - lore5 = "如果你的复合盔甲被摧毁,其中所有物品都将丢失。" - lore6 = "我不需要盔甲,它让我失望……" - lore = ["这是旅行的绝佳技能。", "动态合并并随时切换盔甲/鞘翅!", "合并方法:在物品栏中按住 Shift 点击一个物品放到另一个上。", "解除绑定:潜行时丢弃该物品,它就会拆解。", "如果你的复合盔甲被摧毁,其中所有物品都将丢失。", "我不需要盔甲,它让我失望……"] +name = "复合盔甲" +description = "将鞘翅绑定到盔甲上" +lore1 = "这是旅行的绝佳技能。" +lore2 = "动态合并并随时切换盔甲/鞘翅!" +lore3 = "合并方法:在物品栏中按住 Shift 点击一个物品放到另一个上。" +lore4 = "解除绑定:潜行时丢弃该物品,它就会拆解。" +lore5 = "如果你的复合盔甲被摧毁,其中所有物品都将丢失。" +lore6 = "我不需要盔甲,它让我失望……" +lore = ["这是旅行的绝佳技能。", "动态合并并随时切换盔甲/鞘翅!", "合并方法:在物品栏中按住 Shift 点击一个物品放到另一个上。", "解除绑定:潜行时丢弃该物品,它就会拆解。", "如果你的复合盔甲被摧毁,其中所有物品都将丢失。", "我不需要盔甲,它让我失望……"] # crafting [crafting] [crafting.deconstruction] - name = "分解" - description = "将方块和物品分解为可回收的基础材料!" - lore1 = "将任意物品丢在地上。" - lore2 = "然后潜行并用剪刀右键点击" - lore = ["将任意物品丢在地上。", "然后潜行并用剪刀右键点击"] +name = "分解" +description = "将方块和物品分解为可回收的基础材料!" +lore1 = "将任意物品丢在地上。" +lore2 = "然后潜行并用剪刀右键点击" +lore = ["将任意物品丢在地上。", "然后潜行并用剪刀右键点击"] [crafting.xp] - name = "合成经验" - description = "合成时获得被动经验" - lore1 = "合成时获得经验" - lore = ["合成时获得经验"] +name = "合成经验" +description = "合成时获得被动经验" +lore1 = "合成时获得经验" +lore = ["合成时获得经验"] [crafting.reconstruction] - name = "矿石重铸" - description = "用基础材料重新合成矿石!" - lore1 = "8 个掉落物 + 1 个载体 = 1 个矿石(无序合成)" - lore2 = "掉落物需先熔炼(若适用)" - lore3 = "不包括:残片、石英和绿宝石等……" - lore4 = "载体 = 包裹矿石的方块。如:石头、下界岩、深板岩" - lore = ["8 个掉落物 + 1 个载体 = 1 个矿石(无序合成)", "掉落物需先熔炼(若适用)", "不包括:残片、石英和绿宝石等……", "载体 = 包裹矿石的方块。如:石头、下界岩、深板岩"] +name = "矿石重铸" +description = "用基础材料重新合成矿石!" +lore1 = "8 个掉落物 + 1 个载体 = 1 个矿石(无序合成)" +lore2 = "掉落物需先熔炼(若适用)" +lore3 = "不包括:残片、石英和绿宝石等……" +lore4 = "载体 = 包裹矿石的方块。如:石头、下界岩、深板岩" +lore = ["8 个掉落物 + 1 个载体 = 1 个矿石(无序合成)", "掉落物需先熔炼(若适用)", "不包括:残片、石英和绿宝石等……", "载体 = 包裹矿石的方块。如:石头、下界岩、深板岩"] [crafting.leather] - name = "可合成皮革" - description = "用腐肉合成皮革" - lore1 = "把腐肉丢到营火上就行了!" - lore = ["把腐肉丢到营火上就行了!"] +name = "可合成皮革" +description = "用腐肉合成皮革" +lore1 = "把腐肉丢到营火上就行了!" +lore = ["把腐肉丢到营火上就行了!"] [crafting.backpacks] - name = "布蒂利埃的背包!" - description = "将 Mojang 的收纳袋功能加入游戏!" - lore1 = "需要在生存模式下才能使用" - lore2 = "XLX:皮革、拴绳、皮革" - lore3 = "XSX:皮革、木桶、皮革" - lore4 = "XCX:皮革、箱子、皮革" - lore = ["需要在生存模式下才能使用", "XLX:皮革、拴绳、皮革", "XSX:皮革、木桶、皮革", "XCX:皮革、箱子、皮革"] +name = "布蒂利埃的背包!" +description = "将 Mojang 的收纳袋功能加入游戏!" +lore1 = "需要在生存模式下才能使用" +lore2 = "XLX:皮革、拴绳、皮革" +lore3 = "XSX:皮革、木桶、皮革" +lore4 = "XCX:皮革、箱子、皮革" +lore = ["需要在生存模式下才能使用", "XLX:皮革、拴绳、皮革", "XSX:皮革、木桶、皮革", "XCX:皮革、箱子、皮革"] [crafting.stations] - name = "便携工作台!" - description = "在手掌中使用工作台!" - lore2 = "关闭工作台时遗忘在里面的物品将永远丢失!" - lore3 = "可用工作台:铁砧、工作台、砂轮、制图台、切石机、织布机" - lore = ["关闭工作台时遗忘在里面的物品将永远丢失!", "可用工作台:铁砧、工作台、砂轮、制图台、切石机、织布机"] +name = "便携工作台!" +description = "在手掌中使用工作台!" +lore2 = "关闭工作台时遗忘在里面的物品将永远丢失!" +lore3 = "可用工作台:铁砧、工作台、砂轮、制图台、切石机、织布机" +lore = ["关闭工作台时遗忘在里面的物品将永远丢失!", "可用工作台:铁砧、工作台、砂轮、制图台、切石机、织布机"] [crafting.skulls] - name = "可合成头颅!" - description = "使用材料合成生物头颅!" - lore1 = "用以下材料围绕骨块来获得头颅:" - lore2 = "僵尸:腐肉" - lore3 = "骷髅:骨头" - lore4 = "苦力怕:火药" - lore5 = "凋灵:下界砖" - lore6 = "龙:龙息" - lore = ["用以下材料围绕骨块来获得头颅:", "僵尸:腐肉", "骷髅:骨头", "苦力怕:火药", "凋灵:下界砖", "龙:龙息"] +name = "可合成头颅!" +description = "使用材料合成生物头颅!" +lore1 = "用以下材料围绕骨块来获得头颅:" +lore2 = "僵尸:腐肉" +lore3 = "骷髅:骨头" +lore4 = "苦力怕:火药" +lore5 = "凋灵:下界砖" +lore6 = "龙:龙息" +lore = ["用以下材料围绕骨块来获得头颅:", "僵尸:腐肉", "骷髅:骨头", "苦力怕:火药", "凋灵:下界砖", "龙:龙息"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "时光之瓶" - description = "携带一个储存时间的时光瓶,消耗储存的时间来加速计时方块、可生长物和可成长的实体(如幼年动物)。配方(无序):迅捷药水 + 时钟 + 玻璃瓶。" - lore1 = "每 Tick 充入的储存秒数" - lore2 = "每储存秒数的时间加速量" - lore3 = "配方(无序):迅捷药水 + 时钟 + 玻璃瓶" - lore = ["每 Tick 充入的储存秒数", "每储存秒数的时间加速量", "配方(无序):迅捷药水 + 时钟 + 玻璃瓶"] +name = "时光之瓶" +description = "携带一个储存时间的时光瓶,消耗储存的时间来加速计时方块、可生长物和可成长的实体(如幼年动物)。配方(无序):迅捷药水 + 时钟 + 玻璃瓶。" +lore1 = "每 Tick 充入的储存秒数" +lore2 = "每储存秒数的时间加速量" +lore3 = "配方(无序):迅捷药水 + 时钟 + 玻璃瓶" +lore = ["每 Tick 充入的储存秒数", "每储存秒数的时间加速量", "配方(无序):迅捷药水 + 时钟 + 玻璃瓶"] [chronos.aberrant_touch] - name = "异常之触" - description = "近战攻击以饥饿为代价施加叠加缓慢效果,对 PvP 有严格上限,5 层时定身目标。" - lore1 = "近战攻击施加叠加缓慢效果" - lore2 = "PvE 缓慢持续时间上限" - lore3 = "PvP 缓慢效果等级上限" - lore = ["近战攻击施加叠加缓慢效果", "PvE 缓慢持续时间上限", "PvP 缓慢效果等级上限"] +name = "异常之触" +description = "近战攻击以饥饿为代价施加叠加缓慢效果,对 PvP 有严格上限,5 层时定身目标。" +lore1 = "近战攻击施加叠加缓慢效果" +lore2 = "PvE 缓慢持续时间上限" +lore3 = "PvP 缓慢效果等级上限" +lore = ["近战攻击施加叠加缓慢效果", "PvE 缓慢持续时间上限", "PvP 缓慢效果等级上限"] [chronos.instant_recall] - name = "瞬间回溯" - description = "手持时钟左键或右键点击,回溯到最近的快照并恢复生命值和饥饿值。" - lore1 = "回溯持续时间" - lore2 = "冷却时间" - lore3 = "不回溯物品栏" - lore = ["回溯持续时间", "冷却时间", "不回溯物品栏"] +name = "瞬间回溯" +description = "手持时钟左键或右键点击,回溯到最近的快照并恢复生命值和饥饿值。" +lore1 = "回溯持续时间" +lore2 = "冷却时间" +lore3 = "不回溯物品栏" +lore = ["回溯持续时间", "冷却时间", "不回溯物品栏"] [chronos.time_bomb] - name = "时间炸弹" - description = "投掷合成的时空炸弹,制造时间力场,减速实体并冻结弹射物。" - lore1 = "时间力场半径" - lore2 = "时间力场持续时间" - lore3 = "炸弹冷却时间" - lore4 = "配方(无序):时钟 + 雪球 + 钻石 + 沙子" - lore = ["时间力场半径", "时间力场持续时间", "炸弹冷却时间", "配方(无序):时钟 + 雪球 + 钻石 + 沙子"] +name = "时间炸弹" +description = "投掷合成的时空炸弹,制造时间力场,减速实体并冻结弹射物。" +lore1 = "时间力场半径" +lore2 = "时间力场持续时间" +lore3 = "炸弹冷却时间" +lore4 = "配方(无序):时钟 + 雪球 + 钻石 + 沙子" +lore = ["时间力场半径", "时间力场持续时间", "炸弹冷却时间", "配方(无序):时钟 + 雪球 + 钻石 + 沙子"] # discovery [discovery] [discovery.armor] - name = "世界之甲" - description = "根据附近方块硬度获得被动护甲。" - lore1 = "被动护甲" - lore2 = "基于附近方块硬度" - lore3 = "护甲强度:" - lore = ["被动护甲", "基于附近方块硬度", "护甲强度:"] +name = "世界之甲" +description = "根据附近方块硬度获得被动护甲。" +lore1 = "被动护甲" +lore2 = "基于附近方块硬度" +lore3 = "护甲强度:" +lore = ["被动护甲", "基于附近方块硬度", "护甲强度:"] [discovery.unity] - name = "实验性统合" - description = "拾取经验球时为随机技能增加经验。" - lore1 = "经验 " - lore2 = "每个经验球" - lore = ["经验 ", "每个经验球"] +name = "实验性统合" +description = "拾取经验球时为随机技能增加经验。" +lore1 = "经验 " +lore2 = "每个经验球" +lore = ["经验 ", "每个经验球"] [discovery.resist] - name = "实验性抗性" - description = "消耗经验来减免伤害,仅当一次攻击会使你降至 5 颗心以下或致死时触发。" - lore0 = "仅在危急生命值时触发(<= 5 颗心),每 15 秒一次" - lore1 = " 减免伤害" - lore2 = "经验消耗" - lore = ["仅在危急生命值时触发(<= 5 颗心),每 15 秒一次", " 减免伤害", "经验消耗"] +name = "实验性抗性" +description = "消耗经验来减免伤害,仅当一次攻击会使你降至 5 颗心以下或致死时触发。" +lore0 = "仅在危急生命值时触发(<= 5 颗心),每 15 秒一次" +lore1 = " 减免伤害" +lore2 = "经验消耗" +lore = ["仅在危急生命值时触发(<= 5 颗心),每 15 秒一次", " 减免伤害", "经验消耗"] [discovery.villager] - name = "村民吸引力" - description = "与村民交易时获得更好的价格!" - lore1 = "每次与村民互动时消耗经验" - lore2 = "每次互动有概率消耗经验并提升交易" - lore3 = "每次互动所需经验消耗" - lore = ["每次与村民互动时消耗经验", "每次互动有概率消耗经验并提升交易", "每次互动所需经验消耗"] +name = "村民吸引力" +description = "与村民交易时获得更好的价格!" +lore1 = "每次与村民互动时消耗经验" +lore2 = "每次互动有概率消耗经验并提升交易" +lore3 = "每次互动所需经验消耗" +lore = ["每次与村民互动时消耗经验", "每次互动有概率消耗经验并提升交易", "每次互动所需经验消耗"] # enchanting [enchanting] [enchanting.lapis_return] - name = "青金石回馈" - description = "以多消耗 1 级经验为代价,有概率免费返还青金石" - lore1 = "每级会多消耗 1 级附魔经验,但最多可返还 3 个青金石" - lore = ["每级会多消耗 1 级附魔经验,但最多可返还 3 个青金石"] +name = "青金石回馈" +description = "以多消耗 1 级经验为代价,有概率免费返还青金石" +lore1 = "每级会多消耗 1 级附魔经验,但最多可返还 3 个青金石" +lore = ["每级会多消耗 1 级附魔经验,但最多可返还 3 个青金石"] [enchanting.quick_enchant] - name = "快捷附魔" - description = "直接点击附魔书到物品上即可附魔。" - lore1 = "最大合并等级" - lore2 = "物品附魔总等级不能超过 " - lore3 = "能力值" - lore = ["最大合并等级", "物品附魔总等级不能超过 ", "能力值"] +name = "快捷附魔" +description = "直接点击附魔书到物品上即可附魔。" +lore1 = "最大合并等级" +lore2 = "物品附魔总等级不能超过 " +lore3 = "能力值" +lore = ["最大合并等级", "物品附魔总等级不能超过 ", "能力值"] [enchanting.return] - name = "经验返还" - description = "附魔物品时返还部分经验。" - lore1 = "附魔时消耗的经验有概率被退还" - lore2 = "每次附魔返还经验" - lore = ["附魔时消耗的经验有概率被退还", "每次附魔返还经验"] +name = "经验返还" +description = "附魔物品时返还部分经验。" +lore1 = "附魔时消耗的经验有概率被退还" +lore2 = "每次附魔返还经验" +lore = ["附魔时消耗的经验有概率被退还", "每次附魔返还经验"] # excavation [excavation] [excavation.haste] - name = "急速挖掘者" - description = "用急迫效果加速挖掘过程!" - lore1 = "挖掘时获得急迫效果" - lore2 = "x 级急迫效果(开始挖掘任意方块时)" - lore = ["挖掘时获得急迫效果", "x 级急迫效果(开始挖掘任意方块时)"] +name = "急速挖掘者" +description = "用急迫效果加速挖掘过程!" +lore1 = "挖掘时获得急迫效果" +lore2 = "x 级急迫效果(开始挖掘任意方块时)" +lore = ["挖掘时获得急迫效果", "x 级急迫效果(开始挖掘任意方块时)"] [excavation.spelunker] - name = "超级透视探矿者!" - description = "透过地面看到矿石!" - lore1 = "副手持矿石,主手持发光浆果,然后潜行!" - lore2 = "方块范围:" - lore3 = "使用时消耗发光浆果" - lore = ["副手持矿石,主手持发光浆果,然后潜行!", "方块范围:", "使用时消耗发光浆果"] +name = "超级透视探矿者!" +description = "透过地面看到矿石!" +lore1 = "副手持矿石,主手持发光浆果,然后潜行!" +lore2 = "方块范围:" +lore3 = "使用时消耗发光浆果" +lore = ["副手持矿石,主手持发光浆果,然后潜行!", "方块范围:", "使用时消耗发光浆果"] [excavation.drop_to_inventory] - name = "铲子掉落物直入背包" +name = "铲子掉落物直入背包" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "Tackle 精心设计的豪华多功能工具" - lore1 = "可能是最强大的功能之一,允许你" - lore2 = "根据需要动态合并和切换工具。" - lore3 = "合并方法:在物品栏中按住 Shift 点击一个物品放到另一个上。" - lore4 = "解除绑定:潜行时丢弃该物品,它就会拆解。" - lore5 = "在这个多功能工具中你不能损坏工具,但也不能使用已损坏的工具" - lore6 = "可合并物品总数。" - lore7 = "你可以用五六种工具,或者只用一种!" - lore = ["可能是最强大的功能之一,允许你", "根据需要动态合并和切换工具。", "合并方法:在物品栏中按住 Shift 点击一个物品放到另一个上。", "解除绑定:潜行时丢弃该物品,它就会拆解。", "在这个多功能工具中你不能损坏工具,但也不能使用已损坏的工具", "可合并物品总数。", "你可以用五六种工具,或者只用一种!"] +name = "OMNI - T.O.O.L." +description = "Tackle 精心设计的豪华多功能工具" +lore1 = "可能是最强大的功能之一,允许你" +lore2 = "根据需要动态合并和切换工具。" +lore3 = "合并方法:在物品栏中按住 Shift 点击一个物品放到另一个上。" +lore4 = "解除绑定:潜行时丢弃该物品,它就会拆解。" +lore5 = "在这个多功能工具中你不能损坏工具,但也不能使用已损坏的工具" +lore6 = "可合并物品总数。" +lore7 = "你可以用五六种工具,或者只用一种!" +lore = ["可能是最强大的功能之一,允许你", "根据需要动态合并和切换工具。", "合并方法:在物品栏中按住 Shift 点击一个物品放到另一个上。", "解除绑定:潜行时丢弃该物品,它就会拆解。", "在这个多功能工具中你不能损坏工具,但也不能使用已损坏的工具", "可合并物品总数。", "你可以用五六种工具,或者只用一种!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "生长光环" - description = "在周围的范围内加速自然生长" - lore1 = "方块半径" - lore2 = "生长光环强度" - lore3 = "食物消耗" - lore = ["方块半径", "生长光环强度", "食物消耗"] +name = "生长光环" +description = "在周围的范围内加速自然生长" +lore1 = "方块半径" +lore2 = "生长光环强度" +lore3 = "食物消耗" +lore = ["方块半径", "生长光环强度", "食物消耗"] [herbalism.hippo] - name = "草药师的河马" - description = "吃食物时获得更多饱和度" - lore1 = "食物)食用时额外获得饱和点数" - lore = ["食物)食用时额外获得饱和点数"] +name = "草药师的河马" +description = "吃食物时获得更多饱和度" +lore1 = "食物)食用时额外获得饱和点数" +lore = ["食物)食用时额外获得饱和点数"] [herbalism.myconid] - name = "草药师的菌丝体" - description = "赋予你合成菌丝的能力" - lore1 = "任意泥土 + 棕色蘑菇 + 红色蘑菇可合成菌丝。" - lore = ["任意泥土 + 棕色蘑菇 + 红色蘑菇可合成菌丝。"] +name = "草药师的菌丝体" +description = "赋予你合成菌丝的能力" +lore1 = "任意泥土 + 棕色蘑菇 + 红色蘑菇可合成菌丝。" +lore = ["任意泥土 + 棕色蘑菇 + 红色蘑菇可合成菌丝。"] [herbalism.terralid] - name = "草药师的地衣" - description = "赋予你合成草方块的能力" - lore1 = "3 个种子放在 3 块泥土上方,可合成 3 块草方块。" - lore = ["3 个种子放在 3 块泥土上方,可合成 3 块草方块。"] +name = "草药师的地衣" +description = "赋予你合成草方块的能力" +lore1 = "3 个种子放在 3 块泥土上方,可合成 3 块草方块。" +lore = ["3 个种子放在 3 块泥土上方,可合成 3 块草方块。"] [herbalism.cobweb] - name = "蛛网编织者" - description = "赋予你在工作台中合成蛛网的能力" - lore1 = "九根线可合成一张蛛网。" - lore = ["九根线可合成一张蛛网。"] +name = "蛛网编织者" +description = "赋予你在工作台中合成蛛网的能力" +lore1 = "九根线可合成一张蛛网。" +lore = ["九根线可合成一张蛛网。"] [herbalism.mushroom_blocks] - name = "蘑菇制造者" - description = "赋予你在工作台中合成蘑菇方块的能力" - lore1 = "四个蘑菇合成一个蘑菇块,或一个蘑菇块合成一个蘑菇柄。" - lore = ["四个蘑菇合成一个蘑菇块,或一个蘑菇块合成一个蘑菇柄。"] +name = "蘑菇制造者" +description = "赋予你在工作台中合成蘑菇方块的能力" +lore1 = "四个蘑菇合成一个蘑菇块,或一个蘑菇块合成一个蘑菇柄。" +lore = ["四个蘑菇合成一个蘑菇块,或一个蘑菇块合成一个蘑菇柄。"] [herbalism.drop_to_inventory] - name = "锄头掉落物直入背包" +name = "锄头掉落物直入背包" [herbalism.hungry_shield] - name = "饥饿护盾" - description = "先消耗饥饿值而非生命值来承受伤害。" - lore1 = "以饥饿值抵御伤害" - lore = ["以饥饿值抵御伤害"] +name = "饥饿护盾" +description = "先消耗饥饿值而非生命值来承受伤害。" +lore1 = "以饥饿值抵御伤害" +lore = ["以饥饿值抵御伤害"] [herbalism.luck] - name = "草药师的幸运" - description = "破坏草/花时有概率获得随机物品" - lore0 = "花 = 食物,草 = 种子" - lore1 = "破坏花时获得物品的概率" - lore2 = "破坏草时获得物品的概率" - lore = ["花 = 食物,草 = 种子", "破坏花时获得物品的概率", "破坏草时获得物品的概率"] +name = "草药师的幸运" +description = "破坏草/花时有概率获得随机物品" +lore0 = "花 = 食物,草 = 种子" +lore1 = "破坏花时获得物品的概率" +lore2 = "破坏草时获得物品的概率" +lore = ["花 = 食物,草 = 种子", "破坏花时获得物品的概率", "破坏草时获得物品的概率"] [herbalism.replant] - name = "收割与重种" - description = "手持锄头右键点击作物以收割并自动重种。" - lore1 = "重种方块半径" - lore = ["重种方块半径"] +name = "收割与重种" +description = "手持锄头右键点击作物以收割并自动重种。" +lore1 = "重种方块半径" +lore = ["重种方块半径"] # hunter [hunter] [hunter.adrenaline] - name = "肾上腺素" - description = "生命值越低,近战伤害越高" - lore1 = "最大伤害" - lore = ["最大伤害"] +name = "肾上腺素" +description = "生命值越低,近战伤害越高" +lore1 = "最大伤害" +lore = ["最大伤害"] [hunter.penalty] - name = "" - description = "" - lore1 = "饥饿值耗尽时将获得中毒效果" - lore = ["饥饿值耗尽时将获得中毒效果"] +name = "" +description = "" +lore1 = "饥饿值耗尽时将获得中毒效果" +lore = ["饥饿值耗尽时将获得中毒效果"] [hunter.drop_to_inventory] - name = "掉落物直入背包" - description = "击杀生物/用剑破坏方块时,掉落物自动进入背包" - lore1 = "生物/方块的掉落物会自动进入你的背包(若有空间)。" - lore = ["生物/方块的掉落物会自动进入你的背包(若有空间)。"] +name = "掉落物直入背包" +description = "击杀生物/用剑破坏方块时,掉落物自动进入背包" +lore1 = "生物/方块的掉落物会自动进入你的背包(若有空间)。" +lore = ["生物/方块的掉落物会自动进入你的背包(若有空间)。"] [hunter.invisibility] - name = "消隐步伐" - description = "受到攻击时获得隐身效果,以饥饿为代价" - lore1 = "受击时获得被动隐身" - lore2 = "x 隐身效果,受击后持续 3 秒" - lore3 = "x 叠加饥饿" - lore4 = "饥饿叠加持续时间和倍率。" - lore5 = "隐身持续时间" - lore = ["受击时获得被动隐身", "x 隐身效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "隐身持续时间"] +name = "消隐步伐" +description = "受到攻击时获得隐身效果,以饥饿为代价" +lore1 = "受击时获得被动隐身" +lore2 = "x 隐身效果,受击后持续 3 秒" +lore3 = "x 叠加饥饿" +lore4 = "饥饿叠加持续时间和倍率。" +lore5 = "隐身持续时间" +lore = ["受击时获得被动隐身", "x 隐身效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "隐身持续时间"] [hunter.jump_boost] - name = "猎人之跃" - description = "受到攻击时获得跳跃提升效果,以饥饿为代价" - lore1 = "受击时获得被动跳跃提升" - lore2 = "x 跳跃提升效果,受击后持续 3 秒" - lore3 = "x 叠加饥饿" - lore4 = "饥饿叠加持续时间和倍率。" - lore5 = "跳跃提升叠加倍率,非持续时间。" - lore = ["受击时获得被动跳跃提升", "x 跳跃提升效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "跳跃提升叠加倍率,非持续时间。"] +name = "猎人之跃" +description = "受到攻击时获得跳跃提升效果,以饥饿为代价" +lore1 = "受击时获得被动跳跃提升" +lore2 = "x 跳跃提升效果,受击后持续 3 秒" +lore3 = "x 叠加饥饿" +lore4 = "饥饿叠加持续时间和倍率。" +lore5 = "跳跃提升叠加倍率,非持续时间。" +lore = ["受击时获得被动跳跃提升", "x 跳跃提升效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "跳跃提升叠加倍率,非持续时间。"] [hunter.luck] - name = "猎人之运" - description = "受到攻击时获得幸运效果,以饥饿为代价" - lore1 = "受击时获得被动幸运" - lore2 = "x 幸运效果,受击后持续 3 秒" - lore3 = "x 叠加饥饿" - lore4 = "饥饿叠加持续时间和倍率。" - lore5 = "幸运叠加倍率,非持续时间。" - lore = ["受击时获得被动幸运", "x 幸运效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "幸运叠加倍率,非持续时间。"] +name = "猎人之运" +description = "受到攻击时获得幸运效果,以饥饿为代价" +lore1 = "受击时获得被动幸运" +lore2 = "x 幸运效果,受击后持续 3 秒" +lore3 = "x 叠加饥饿" +lore4 = "饥饿叠加持续时间和倍率。" +lore5 = "幸运叠加倍率,非持续时间。" +lore = ["受击时获得被动幸运", "x 幸运效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "幸运叠加倍率,非持续时间。"] [hunter.regen] - name = "猎人之愈" - description = "受到攻击时获得生命恢复效果,以饥饿为代价" - lore1 = "受击时获得被动生命恢复" - lore2 = "x 生命恢复效果,受击后持续 3 秒" - lore3 = "x 叠加饥饿" - lore4 = "饥饿叠加持续时间和倍率。" - lore5 = "生命恢复叠加倍率,非持续时间。" - lore = ["受击时获得被动生命恢复", "x 生命恢复效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "生命恢复叠加倍率,非持续时间。"] +name = "猎人之愈" +description = "受到攻击时获得生命恢复效果,以饥饿为代价" +lore1 = "受击时获得被动生命恢复" +lore2 = "x 生命恢复效果,受击后持续 3 秒" +lore3 = "x 叠加饥饿" +lore4 = "饥饿叠加持续时间和倍率。" +lore5 = "生命恢复叠加倍率,非持续时间。" +lore = ["受击时获得被动生命恢复", "x 生命恢复效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "生命恢复叠加倍率,非持续时间。"] [hunter.resistance] - name = "猎人之盾" - description = "受到攻击时获得抗性提升效果,以饥饿为代价" - lore1 = "受击时获得被动抗性提升" - lore2 = "x 抗性提升效果,受击后持续 3 秒" - lore3 = "x 叠加饥饿" - lore4 = "饥饿叠加持续时间和倍率。" - lore5 = "抗性提升叠加倍率,非持续时间。" - lore = ["受击时获得被动抗性提升", "x 抗性提升效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "抗性提升叠加倍率,非持续时间。"] +name = "猎人之盾" +description = "受到攻击时获得抗性提升效果,以饥饿为代价" +lore1 = "受击时获得被动抗性提升" +lore2 = "x 抗性提升效果,受击后持续 3 秒" +lore3 = "x 叠加饥饿" +lore4 = "饥饿叠加持续时间和倍率。" +lore5 = "抗性提升叠加倍率,非持续时间。" +lore = ["受击时获得被动抗性提升", "x 抗性提升效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "抗性提升叠加倍率,非持续时间。"] [hunter.speed] - name = "猎人之速" - description = "受到攻击时获得速度效果,以饥饿为代价" - lore1 = "受击时获得被动速度提升" - lore2 = "x 速度效果,受击后持续 3 秒" - lore3 = "x 叠加饥饿" - lore4 = "饥饿叠加持续时间和倍率。" - lore5 = "速度叠加倍率,非持续时间。" - lore = ["受击时获得被动速度提升", "x 速度效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "速度叠加倍率,非持续时间。"] +name = "猎人之速" +description = "受到攻击时获得速度效果,以饥饿为代价" +lore1 = "受击时获得被动速度提升" +lore2 = "x 速度效果,受击后持续 3 秒" +lore3 = "x 叠加饥饿" +lore4 = "饥饿叠加持续时间和倍率。" +lore5 = "速度叠加倍率,非持续时间。" +lore = ["受击时获得被动速度提升", "x 速度效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "速度叠加倍率,非持续时间。"] [hunter.strength] - name = "猎人之力" - description = "受到攻击时获得力量效果,以饥饿为代价" - lore1 = "受击时获得被动力量提升" - lore2 = "x 力量效果,受击后持续 3 秒" - lore3 = "x 叠加饥饿" - lore4 = "饥饿叠加持续时间和倍率。" - lore5 = "力量叠加倍率,非持续时间。" - lore = ["受击时获得被动力量提升", "x 力量效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "力量叠加倍率,非持续时间。"] +name = "猎人之力" +description = "受到攻击时获得力量效果,以饥饿为代价" +lore1 = "受击时获得被动力量提升" +lore2 = "x 力量效果,受击后持续 3 秒" +lore3 = "x 叠加饥饿" +lore4 = "饥饿叠加持续时间和倍率。" +lore5 = "力量叠加倍率,非持续时间。" +lore = ["受击时获得被动力量提升", "x 力量效果,受击后持续 3 秒", "x 叠加饥饿", "饥饿叠加持续时间和倍率。", "力量叠加倍率,非持续时间。"] # nether [nether] [nether.skull_toss] - name = "凋灵头颅投掷" - description1 = "释放你内心的凋灵,用" - description2 = "某人的" - description3 = "头颅。" - lore1 = "头颅投掷冷却秒数。" - lore2 = "使用凋灵头颅:投掷一颗" - lore3 = "凋灵头颅" - lore4 = "撞击时爆炸。" - lore = ["头颅投掷冷却秒数。", "使用凋灵头颅:投掷一颗", "凋灵头颅", "撞击时爆炸。"] +name = "凋灵头颅投掷" +description1 = "释放你内心的凋灵,用" +description2 = "某人的" +description3 = "头颅。" +lore1 = "头颅投掷冷却秒数。" +lore2 = "使用凋灵头颅:投掷一颗" +lore3 = "凋灵头颅" +lore4 = "撞击时爆炸。" +lore = ["头颅投掷冷却秒数。", "使用凋灵头颅:投掷一颗", "凋灵头颅", "撞击时爆炸。"] [nether.wither_resist] - name = "凋零抗性" - description = "通过下界合金之力抵抗凋零效果。" - lore1 = "概率免疫凋零效果(每件装备)。" - lore2 = "被动:穿戴下界合金盔甲有概率免疫" - lore3 = "凋零效果。" - lore = ["概率免疫凋零效果(每件装备)。", "被动:穿戴下界合金盔甲有概率免疫", "凋零效果。"] +name = "凋零抗性" +description = "通过下界合金之力抵抗凋零效果。" +lore1 = "概率免疫凋零效果(每件装备)。" +lore2 = "被动:穿戴下界合金盔甲有概率免疫" +lore3 = "凋零效果。" +lore = ["概率免疫凋零效果(每件装备)。", "被动:穿戴下界合金盔甲有概率免疫", "凋零效果。"] [nether.fire_resist] - name = "火焰抗性" - description = "通过强化皮肤来抵抗火焰。" - lore1 = "概率免疫燃烧效果!" - lore = ["概率免疫燃烧效果!"] +name = "火焰抗性" +description = "通过强化皮肤来抵抗火焰。" +lore1 = "概率免疫燃烧效果!" +lore = ["概率免疫燃烧效果!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "自动熔炼" - description = "自动熔炼挖到的原版矿石" - lore1 = "可熔炼的矿石将自动被熔炼" - lore2 = "% 概率获得额外产物" - lore = ["可熔炼的矿石将自动被熔炼", "% 概率获得额外产物"] +name = "自动熔炼" +description = "自动熔炼挖到的原版矿石" +lore1 = "可熔炼的矿石将自动被熔炼" +lore2 = "% 概率获得额外产物" +lore = ["可熔炼的矿石将自动被熔炼", "% 概率获得额外产物"] [pickaxe.chisel] - name = "矿石凿击" - description = "右键点击矿石以凿出更多矿物,但会严重消耗耐久。" - lore1 = "额外掉落概率" - lore2 = "耐久消耗" - lore = ["额外掉落概率", "耐久消耗"] +name = "矿石凿击" +description = "右键点击矿石以凿出更多矿物,但会严重消耗耐久。" +lore1 = "额外掉落概率" +lore2 = "耐久消耗" +lore = ["额外掉落概率", "耐久消耗"] [pickaxe.drop_to_inventory] - name = "镐子掉落物直入背包" - description = "破坏方块时掉落物自动进入背包" - lore1 = "方块的掉落物会自动进入你的背包(若有空间)。" - lore = ["方块的掉落物会自动进入你的背包(若有空间)。"] +name = "镐子掉落物直入背包" +description = "破坏方块时掉落物自动进入背包" +lore1 = "方块的掉落物会自动进入你的背包(若有空间)。" +lore = ["方块的掉落物会自动进入你的背包(若有空间)。"] [pickaxe.silk_spawner] - name = "精准采集刷怪笼" - description = "使刷怪笼被破坏时掉落" - lore1 = "使刷怪笼可被精准采集的镐子采集。" - lore2 = "使刷怪笼在潜行时可被破坏。" - lore = ["使刷怪笼可被精准采集的镐子采集。", "使刷怪笼在潜行时可被破坏。"] +name = "精准采集刷怪笼" +description = "使刷怪笼被破坏时掉落" +lore1 = "使刷怪笼可被精准采集的镐子采集。" +lore2 = "使刷怪笼在潜行时可被破坏。" +lore = ["使刷怪笼可被精准采集的镐子采集。", "使刷怪笼在潜行时可被破坏。"] [pickaxe.vein_miner] - name = "连锁挖矿" - description = "挖掘原版矿石时可连锁开采整条矿脉" - lore1 = "潜行并挖掘矿石" - lore2 = "连锁挖矿范围" - lore3 = "此技能不会将掉落物合并在一起!" - lore = ["潜行并挖掘矿石", "连锁挖矿范围", "此技能不会将掉落物合并在一起!"] +name = "连锁挖矿" +description = "挖掘原版矿石时可连锁开采整条矿脉" +lore1 = "潜行并挖掘矿石" +lore2 = "连锁挖矿范围" +lore3 = "此技能不会将掉落物合并在一起!" +lore = ["潜行并挖掘矿石", "连锁挖矿范围", "此技能不会将掉落物合并在一起!"] # ranged [ranged] [ranged.arrow_recovery] - name = "箭矢回收" - description = "击杀敌人后回收箭矢。" - lore1 = "命中/击杀时回收箭矢的概率" - lore2 = "概率:" - lore = ["命中/击杀时回收箭矢的概率", "概率:"] +name = "箭矢回收" +description = "击杀敌人后回收箭矢。" +lore1 = "命中/击杀时回收箭矢的概率" +lore2 = "概率:" +lore = ["命中/击杀时回收箭矢的概率", "概率:"] [ranged.web_shot] - name = "蛛网陷阱" - description = "命中目标时在其周围生成蛛网!" - lore1 = "8 个蛛网围绕 1 个雪球合成,然后投掷!" - lore2 = "秒的牢笼,大约。" - lore = ["8 个蛛网围绕 1 个雪球合成,然后投掷!", "秒的牢笼,大约。"] +name = "蛛网陷阱" +description = "命中目标时在其周围生成蛛网!" +lore1 = "8 个蛛网围绕 1 个雪球合成,然后投掷!" +lore2 = "秒的牢笼,大约。" +lore = ["8 个蛛网围绕 1 个雪球合成,然后投掷!", "秒的牢笼,大约。"] [ranged.force_shot] - name = "强力射击" - description = "射出的弹射物更远、更快!" - advancementname = "远距离射击" - advancementlore = "从 30 格方块外命中目标!" - lore1 = "弹射物速度" - lore = ["弹射物速度"] +name = "强力射击" +description = "射出的弹射物更远、更快!" +advancementname = "远距离射击" +advancementlore = "从 30 格方块外命中目标!" +lore1 = "弹射物速度" +lore = ["弹射物速度"] [ranged.lunge_shot] - name = "突刺射击" - description = "下落时射出的箭会将你随机弹向一个方向" - lore1 = "随机爆发速度" - lore = ["随机爆发速度"] +name = "突刺射击" +description = "下落时射出的箭会将你随机弹向一个方向" +lore1 = "随机爆发速度" +lore = ["随机爆发速度"] [ranged.arrow_piercing] - name = "箭矢穿透" - description = "为弹射物添加穿透效果!射穿一切!" - lore1 = "穿透目标数" - lore = ["穿透目标数"] +name = "箭矢穿透" +description = "为弹射物添加穿透效果!射穿一切!" +lore1 = "穿透目标数" +lore = ["穿透目标数"] # rift [rift] [rift.remote_access] - name = "远程访问" - description = "从虚空中取物,远程打开已标记的容器。" - lore1 = "末影珍珠 + 指南针 = 遗物传送钥" - lore2 = "此物品允许你远程访问容器" - lore3 = "合成后查看物品了解使用方法" - notcontainer = "那不是一个容器" - lore = ["末影珍珠 + 指南针 = 遗物传送钥", "此物品允许你远程访问容器", "合成后查看物品了解使用方法"] +name = "远程访问" +description = "从虚空中取物,远程打开已标记的容器。" +lore1 = "末影珍珠 + 指南针 = 遗物传送钥" +lore2 = "此物品允许你远程访问容器" +lore3 = "合成后查看物品了解使用方法" +notcontainer = "那不是一个容器" +lore = ["末影珍珠 + 指南针 = 遗物传送钥", "此物品允许你远程访问容器", "合成后查看物品了解使用方法"] [rift.blink] - name = "裂隙闪现" - description = "短距离瞬间传送,不过是眨眼之间!" - lore1 = "闪现格数(垂直 2 倍)" - lore2 = "疾跑时:双击跳跃键以" - lore3 = "闪现" - lore = ["闪现格数(垂直 2 倍)", "疾跑时:双击跳跃键以", "闪现"] +name = "裂隙闪现" +description = "短距离瞬间传送,不过是眨眼之间!" +lore1 = "闪现格数(垂直 2 倍)" +lore2 = "疾跑时:双击跳跃键以" +lore3 = "闪现" +lore = ["闪现格数(垂直 2 倍)", "疾跑时:双击跳跃键以", "闪现"] [rift.chest] - name = "便携末影箱" - description = "手持末影箱左键点击即可打开。" - lore1 = "点击手中的末影箱即可打开(无需放置)" - lore = ["点击手中的末影箱即可打开(无需放置)"] +name = "便携末影箱" +description = "手持末影箱左键点击即可打开。" +lore1 = "点击手中的末影箱即可打开(无需放置)" +lore = ["点击手中的末影箱即可打开(无需放置)"] [rift.descent] - name = "反飘浮" - description = "厌倦了被困在半空中?这个技能就是为你准备的!" - lore1 = "潜行即可下降,下落速度低于正常值!" - lore2 = "冷却:" - lore = ["潜行即可下降,下落速度低于正常值!", "冷却:"] +name = "反飘浮" +description = "厌倦了被困在半空中?这个技能就是为你准备的!" +lore1 = "潜行即可下降,下落速度低于正常值!" +lore2 = "冷却:" +lore = ["潜行即可下降,下落速度低于正常值!", "冷却:"] [rift.gate] - name = "裂隙之门" - description = "传送到标记的位置。" - lore1 = "合成:绿宝石 + 紫水晶碎片 + 末影珍珠" - lore2 = "使用前请先阅读!" - lore3 = "5 秒延迟," - lore4 = "在传送动画中你可能会死亡" - lore = ["合成:绿宝石 + 紫水晶碎片 + 末影珍珠", "使用前请先阅读!", "5 秒延迟,", "在传送动画中你可能会死亡"] +name = "裂隙之门" +description = "传送到标记的位置。" +lore1 = "合成:绿宝石 + 紫水晶碎片 + 末影珍珠" +lore2 = "使用前请先阅读!" +lore3 = "5 秒延迟," +lore4 = "在传送动画中你可能会死亡" +lore = ["合成:绿宝石 + 紫水晶碎片 + 末影珍珠", "使用前请先阅读!", "5 秒延迟,", "在传送动画中你可能会死亡"] [rift.resist] - name = "裂隙抗性" - description = "使用末影物品和技能时获得抗性提升" - lore1 = "+ 被动:使用裂隙技能或末影物品时提供抗性提升" - lore2 = "不包括便携末影箱,仅限可消耗的物品" - lore = ["+ 被动:使用裂隙技能或末影物品时提供抗性提升", "不包括便携末影箱,仅限可消耗的物品"] +name = "裂隙抗性" +description = "使用末影物品和技能时获得抗性提升" +lore1 = "+ 被动:使用裂隙技能或末影物品时提供抗性提升" +lore2 = "不包括便携末影箱,仅限可消耗的物品" +lore = ["+ 被动:使用裂隙技能或末影物品时提供抗性提升", "不包括便携末影箱,仅限可消耗的物品"] [rift.visage] - name = "裂隙面容" - description = "背包中有末影珍珠时可阻止末影人变得具有攻击性。" - lore1 = "背包中有末影珍珠时末影人不会变得具有攻击性。" - lore = ["背包中有末影珍珠时末影人不会变得具有攻击性。"] +name = "裂隙面容" +description = "背包中有末影珍珠时可阻止末影人变得具有攻击性。" +lore1 = "背包中有末影珍珠时末影人不会变得具有攻击性。" +lore = ["背包中有末影珍珠时末影人不会变得具有攻击性。"] # seaborn [seaborn] [seaborn.oxygen] - name = "有机氧气罐" - description = "让你的小肺容纳更多氧气!" - lore1 = "氧气容量增加" - lore = ["氧气容量增加"] +name = "有机氧气罐" +description = "让你的小肺容纳更多氧气!" +lore1 = "氧气容量增加" +lore = ["氧气容量增加"] [seaborn.fishers_fantasy] - name = "渔夫的幻想" - description = "钓鱼获得更多经验,捕获更多鱼!" - lore1 = "每级都有概率获得更多经验和鱼!" - lore = ["每级都有概率获得更多经验和鱼!"] +name = "渔夫的幻想" +description = "钓鱼获得更多经验,捕获更多鱼!" +lore1 = "每级都有概率获得更多经验和鱼!" +lore = ["每级都有概率获得更多经验和鱼!"] [seaborn.haste] - name = "海龟矿工" - description = "在水下挖掘时获得急迫效果!" - lore1 = "水下呼吸效果消失后,在水下挖掘时获得急迫 3 效果(与水下速掘叠加)!" - lore = ["水下呼吸效果消失后,在水下挖掘时获得急迫 3 效果(与水下速掘叠加)!"] +name = "海龟矿工" +description = "在水下挖掘时获得急迫效果!" +lore1 = "水下呼吸效果消失后,在水下挖掘时获得急迫 3 效果(与水下速掘叠加)!" +lore = ["水下呼吸效果消失后,在水下挖掘时获得急迫 3 效果(与水下速掘叠加)!"] [seaborn.night_vision] - name = "海龟之眼" - description = "在水下时获得夜视效果" - lore1 = "水下呼吸效果消失后,在水下时直接获得夜视效果!" - lore = ["水下呼吸效果消失后,在水下时直接获得夜视效果!"] +name = "海龟之眼" +description = "在水下时获得夜视效果" +lore1 = "水下呼吸效果消失后,在水下时直接获得夜视效果!" +lore = ["水下呼吸效果消失后,在水下时直接获得夜视效果!"] [seaborn.dolphin_grace] - name = "海豚之恩" - description = "无需海豚也能像海豚一样游泳" - lore1 = "+ 被动:获得" - lore2 = "x 速度(海豚恩惠)" - lore3 = "精准的德国工程——等等不太对……不与深海探索者兼容" - lore = ["+ 被动:获得", "x 速度(海豚恩惠)", "精准的德国工程——等等不太对……不与深海探索者兼容"] +name = "海豚之恩" +description = "无需海豚也能像海豚一样游泳" +lore1 = "+ 被动:获得" +lore2 = "x 速度(海豚恩惠)" +lore3 = "精准的德国工程——等等不太对……不与深海探索者兼容" +lore = ["+ 被动:获得", "x 速度(海豚恩惠)", "精准的德国工程——等等不太对……不与深海探索者兼容"] # stealth [stealth] [stealth.ghost_armor] - name = "幽灵战甲" - description = "未受伤时缓慢构建护甲,持续 1 次攻击" - lore1 = "最大护甲值" - lore2 = "速度" - lore = ["最大护甲值", "速度"] +name = "幽灵战甲" +description = "未受伤时缓慢构建护甲,持续 1 次攻击" +lore1 = "最大护甲值" +lore2 = "速度" +lore = ["最大护甲值", "速度"] [stealth.night_vision] - name = "潜行视觉" - description = "潜行时获得夜视效果" - lore1 = "获得一次爆发的" - lore2 = "夜视效果" - lore3 = "(潜行时)" - lore = ["获得一次爆发的", "夜视效果", "(潜行时)"] +name = "潜行视觉" +description = "潜行时获得夜视效果" +lore1 = "获得一次爆发的" +lore2 = "夜视效果" +lore3 = "(潜行时)" +lore = ["获得一次爆发的", "夜视效果", "(潜行时)"] [stealth.snatch] - name = "物品夺取" - description = "潜行时立即拾取周围的掉落物!" - lore1 = "夺取半径" - lore = ["夺取半径"] +name = "物品夺取" +description = "潜行时立即拾取周围的掉落物!" +lore1 = "夺取半径" +lore = ["夺取半径"] [stealth.speed] - name = "潜行加速" - description = "潜行时获得速度提升" - lore1 = "潜行速度" - lore = ["潜行速度"] +name = "潜行加速" +description = "潜行时获得速度提升" +lore1 = "潜行速度" +lore = ["潜行速度"] [stealth.ender_veil] - name = "末影面纱" - description = "不再需要南瓜来防止末影人攻击了" - lore1 = "潜行时防止末影人攻击" - lore2 = "阻止所有末影人攻击" - lore = ["潜行时防止末影人攻击", "阻止所有末影人攻击"] +name = "末影面纱" +description = "不再需要南瓜来防止末影人攻击了" +lore1 = "潜行时防止末影人攻击" +lore2 = "阻止所有末影人攻击" +lore = ["潜行时防止末影人攻击", "阻止所有末影人攻击"] # sword [sword] [sword.machete] - name = "弯刀" - description = "轻松砍开植被!" - lore1 = "斩击半径" - lore2 = "斩击冷却" - lore3 = "耐久消耗" - lore = ["斩击半径", "斩击冷却", "耐久消耗"] +name = "弯刀" +description = "轻松砍开植被!" +lore1 = "斩击半径" +lore2 = "斩击冷却" +lore3 = "耐久消耗" +lore = ["斩击半径", "斩击冷却", "耐久消耗"] [sword.bloody_blade] - name = "嗜血之刃" - description = "用剑攻击造成流血效果!" - lore1 = "用剑攻击活体生物时造成流血" - lore2 = "流血持续时间" - lore3 = "流血冷却" - lore = ["用剑攻击活体生物时造成流血", "流血持续时间", "流血冷却"] +name = "嗜血之刃" +description = "用剑攻击造成流血效果!" +lore1 = "用剑攻击活体生物时造成流血" +lore2 = "流血持续时间" +lore3 = "流血冷却" +lore = ["用剑攻击活体生物时造成流血", "流血持续时间", "流血冷却"] [sword.poisoned_blade] - name = "淬毒之刃" - description = "用剑攻击造成中毒效果!" - lore1 = "用剑攻击活体生物时造成中毒" - lore2 = "中毒持续时间" - lore3 = "中毒冷却" - lore = ["用剑攻击活体生物时造成中毒", "中毒持续时间", "中毒冷却"] +name = "淬毒之刃" +description = "用剑攻击造成中毒效果!" +lore1 = "用剑攻击活体生物时造成中毒" +lore2 = "中毒持续时间" +lore3 = "中毒冷却" +lore = ["用剑攻击活体生物时造成中毒", "中毒持续时间", "中毒冷却"] # taming [taming] [taming.damage] - name = "驯兽伤害" - description = "提升已驯服动物的伤害。" - lore1 = "伤害提升" - lore = ["伤害提升"] +name = "驯兽伤害" +description = "提升已驯服动物的伤害。" +lore1 = "伤害提升" +lore = ["伤害提升"] [taming.health] - name = "驯兽生命" - description = "提升已驯服动物的生命值。" - lore1 = "生命提升" - lore = ["生命提升"] +name = "驯兽生命" +description = "提升已驯服动物的生命值。" +lore1 = "生命提升" +lore = ["生命提升"] [taming.regeneration] - name = "驯兽再生" - description = "提升已驯服动物的生命恢复速度。" - lore1 = "生命/秒" - lore = ["生命/秒"] +name = "驯兽再生" +description = "提升已驯服动物的生命恢复速度。" +lore1 = "生命/秒" +lore = ["生命/秒"] # tragoul [tragoul] [tragoul.thorns] - name = "荆棘" - description = "将伤害反弹给攻击者!" - lore1 = "受击时反弹伤害" - lore = ["受击时反弹伤害"] +name = "荆棘" +description = "将伤害反弹给攻击者!" +lore1 = "受击时反弹伤害" +lore = ["受击时反弹伤害"] [tragoul.globe] - name = "痛苦之球" - description = "根据周围敌人数量分摊你造成的伤害!" - lore1 = "周围敌人越多,对每个敌人造成的伤害越少" - lore2 = "范围:" - lore3 = "对所有实体的附加伤害:" - lore = ["周围敌人越多,对每个敌人造成的伤害越少", "范围:", "对所有实体的附加伤害:"] +name = "痛苦之球" +description = "根据周围敌人数量分摊你造成的伤害!" +lore1 = "周围敌人越多,对每个敌人造成的伤害越少" +lore2 = "范围:" +lore3 = "对所有实体的附加伤害:" +lore = ["周围敌人越多,对每个敌人造成的伤害越少", "范围:", "对所有实体的附加伤害:"] [tragoul.healing] - name = "痛苦之志" - description = "根据你造成的伤害恢复生命值!" - lore1 = "伤害别人从未感觉如此美妙!通过造成伤害恢复生命" - lore2 = "3 秒伤害窗口期内恢复生命,冷却 1 秒" - lore3 = "每伤害百分比回复生命:" - lore = ["伤害别人从未感觉如此美妙!通过造成伤害恢复生命", "3 秒伤害窗口期内恢复生命,冷却 1 秒", "每伤害百分比回复生命:"] +name = "痛苦之志" +description = "根据你造成的伤害恢复生命值!" +lore1 = "伤害别人从未感觉如此美妙!通过造成伤害恢复生命" +lore2 = "3 秒伤害窗口期内恢复生命,冷却 1 秒" +lore3 = "每伤害百分比回复生命:" +lore = ["伤害别人从未感觉如此美妙!通过造成伤害恢复生命", "3 秒伤害窗口期内恢复生命,冷却 1 秒", "每伤害百分比回复生命:"] [tragoul.lance] - name = "尸骨之矛" - description = "击杀敌人或技能击杀敌人时,生成长矛对附近敌人造成伤害!" - lore1 = "长矛会从你击杀的目标中射出,如果此技能击杀了敌人也会触发。" - lore2 = "牺牲部分生命来生成长矛(这可能会杀死你)" - lore3 = "最大长矛数:1 + " - lore = ["长矛会从你击杀的目标中射出,如果此技能击杀了敌人也会触发。", "牺牲部分生命来生成长矛(这可能会杀死你)", "最大长矛数:1 + "] +name = "尸骨之矛" +description = "击杀敌人或技能击杀敌人时,生成长矛对附近敌人造成伤害!" +lore1 = "长矛会从你击杀的目标中射出,如果此技能击杀了敌人也会触发。" +lore2 = "牺牲部分生命来生成长矛(这可能会杀死你)" +lore3 = "最大长矛数:1 + " +lore = ["长矛会从你击杀的目标中射出,如果此技能击杀了敌人也会触发。", "牺牲部分生命来生成长矛(这可能会杀死你)", "最大长矛数:1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "玻璃大炮" - description = "护甲值越低,空手伤害越高" - lore1 = "x 伤害(0 护甲时)" - lore2 = "每级额外伤害" - lore = ["x 伤害(0 护甲时)", "每级额外伤害"] +name = "玻璃大炮" +description = "护甲值越低,空手伤害越高" +lore1 = "x 伤害(0 护甲时)" +lore2 = "每级额外伤害" +lore = ["x 伤害(0 护甲时)", "每级额外伤害"] [unarmed.power] - name = "空手之力" - description = "提升空手伤害" - lore1 = "伤害" - lore = ["伤害"] +name = "空手之力" +description = "提升空手伤害" +lore1 = "伤害" +lore = ["伤害"] [unarmed.sucker_punch] - name = "偷袭拳" - description = "疾跑出拳,更加致命。" - lore1 = "伤害" - lore2 = "出拳时伤害随移动速度增加" - lore = ["伤害", "出拳时伤害随移动速度增加"] +name = "偷袭拳" +description = "疾跑出拳,更加致命。" +lore1 = "伤害" +lore2 = "出拳时伤害随移动速度增加" +lore = ["伤害", "出拳时伤害随移动速度增加"] diff --git a/src/main/resources/zh_TW.toml b/src/main/resources/zh_TW.toml index af8fdf2d0..432ac778c 100644 --- a/src/main/resources/zh_TW.toml +++ b/src/main/resources/zh_TW.toml @@ -2,2210 +2,2210 @@ [advancement] [advancement.challenge_move_1k] - title = "上路啦!" - description = "總計走過1公里的距離(1,000 格方塊)" +title = "上路啦!" +description = "總計走過1公里的距離(1,000 格方塊)" [advancement.challenge_sprint_5k] - title = "長跑5公里!" - description = "總計走過5公里(5,000 格方塊)" +title = "長跑5公里!" +description = "總計走過5公里(5,000 格方塊)" [advancement.challenge_sprint_50k] - title = "飛速衝刺50公里!" - description = "總計走過50公里(50,000 格方塊)" +title = "飛速衝刺50公里!" +description = "總計走過50公里(50,000 格方塊)" [advancement.challenge_sprint_500k] - title = "穿越宇宙!!" - description = "總計走過500公里(500,000 格方塊)" +title = "穿越宇宙!!" +description = "總計走過500公里(500,000 格方塊)" [advancement.challenge_sprint_marathon] - title = "衝刺一場(真正的)馬拉松!" - description = "以疾跑的方式跑過 42,195 格方塊!" +title = "衝刺一場(真正的)馬拉松!" +description = "以疾跑的方式跑過 42,195 格方塊!" [advancement.challenge_place_1k] - title = "初學建築師!" - description = "放置 1,000 格方塊" +title = "初學建築師!" +description = "放置 1,000 格方塊" [advancement.challenge_place_5k] - title = "中級建築師!" - description = "放置 5,000 格方塊" +title = "中級建築師!" +description = "放置 5,000 格方塊" [advancement.challenge_place_50k] - title = "高級建築師!" - description = "放置 50,000 格方塊" +title = "高級建築師!" +description = "放置 50,000 格方塊" [advancement.challenge_place_500k] - title = "建築大師!" - description = "放置 500,000 格方塊" +title = "建築大師!" +description = "放置 500,000 格方塊" [advancement.challenge_place_5m] - title = "對稱之信徒!" - description = "現實是你的遊樂場!(500萬格方塊)" +title = "對稱之信徒!" +description = "現實是你的遊樂場!(500萬格方塊)" [advancement.challenge_chop_1k] - title = "初學伐木工!" - description = "砍伐 1,000 格方塊" +title = "初學伐木工!" +description = "砍伐 1,000 格方塊" [advancement.challenge_chop_5k] - title = "中級伐木工!" - description = "砍伐 5,000 格方塊" +title = "中級伐木工!" +description = "砍伐 5,000 格方塊" [advancement.challenge_chop_50k] - title = "高級伐木工!" - description = "砍伐 50,000 格方塊" +title = "高級伐木工!" +description = "砍伐 50,000 格方塊" [advancement.challenge_chop_500k] - title = "伐木大師!" - description = "砍伐 500,000 格方塊" +title = "伐木大師!" +description = "砍伐 500,000 格方塊" [advancement.challenge_chop_5m] - title = "傑克森小狗" - description = "最棒的好孩子!(500萬格方塊)" +title = "傑克森小狗" +description = "最棒的好孩子!(500萬格方塊)" [advancement.challenge_block_1k] - title = "初學格擋!" - description = "格擋 1000 次攻擊" +title = "初學格擋!" +description = "格擋 1000 次攻擊" [advancement.challenge_block_5k] - title = "格擋真有趣!" - description = "格擋 5000 次攻擊" +title = "格擋真有趣!" +description = "格擋 5000 次攻擊" [advancement.challenge_block_50k] - title = "為格擋而生!" - description = "格擋 50,000 次攻擊" +title = "為格擋而生!" +description = "格擋 50,000 次攻擊" [advancement.challenge_block_500k] - title = "格擋即使命!" - description = "格擋 500,000 次攻擊" +title = "格擋即使命!" +description = "格擋 500,000 次攻擊" [advancement.challenge_block_5m] - title = "傷害之手" - description = "格擋 5,000,000 次攻擊" +title = "傷害之手" +description = "格擋 5,000,000 次攻擊" [advancement.challenge_brew_1k] - title = "初學煉金術士!" - description = "飲用 1000 瓶藥水" +title = "初學煉金術士!" +description = "飲用 1000 瓶藥水" [advancement.challenge_brew_5k] - title = "中級煉金術士!" - description = "飲用 5000 瓶藥水" +title = "中級煉金術士!" +description = "飲用 5000 瓶藥水" [advancement.challenge_brew_50k] - title = "高級煉金術士!" - description = "飲用 50,000 瓶藥水" +title = "高級煉金術士!" +description = "飲用 50,000 瓶藥水" [advancement.challenge_brew_500k] - title = "煉金大師!" - description = "飲用 500,000 瓶藥水" +title = "煉金大師!" +description = "飲用 500,000 瓶藥水" [advancement.challenge_brew_5m] - title = "大煉金術師" - description = "飲用 5,000,000 瓶藥水" +title = "大煉金術師" +description = "飲用 5,000,000 瓶藥水" [advancement.challenge_brewsplash_1k] - title = "初學藥水投擲手!" - description = "投擲 1000 瓶藥水" +title = "初學藥水投擲手!" +description = "投擲 1000 瓶藥水" [advancement.challenge_brewsplash_5k] - title = "中級藥水投擲手!" - description = "投擲 5000 瓶藥水" +title = "中級藥水投擲手!" +description = "投擲 5000 瓶藥水" [advancement.challenge_brewsplash_50k] - title = "高級藥水投擲手!" - description = "投擲 50,000 瓶藥水" +title = "高級藥水投擲手!" +description = "投擲 50,000 瓶藥水" [advancement.challenge_brewsplash_500k] - title = "藥水投擲大師!" - description = "投擲 500,000 瓶藥水" +title = "藥水投擲大師!" +description = "投擲 500,000 瓶藥水" [advancement.challenge_brewsplash_5m] - title = "潑灑宗師" - description = "投擲 5,000,000 瓶藥水" +title = "潑灑宗師" +description = "投擲 5,000,000 瓶藥水" [advancement.challenge_craft_1k] - title = "巧手製造匠!" - description = "製作 1000 件物品" +title = "巧手製造匠!" +description = "製作 1000 件物品" [advancement.challenge_craft_5k] - title = "暴躁製造匠!" - description = "製作 5000 件物品" +title = "暴躁製造匠!" +description = "製作 5000 件物品" [advancement.challenge_craft_50k] - title = "勤勉製造匠!" - description = "製作 50,000 件物品" +title = "勤勉製造匠!" +description = "製作 50,000 件物品" [advancement.challenge_craft_500k] - title = "喧鬧製造匠!" - description = "製作 500,000 件物品" +title = "喧鬧製造匠!" +description = "製作 500,000 件物品" [advancement.challenge_craft_5m] - title = "災難級工匠大人" - description = "製作 5,000,000 件物品" +title = "災難級工匠大人" +description = "製作 5,000,000 件物品" [advancement.challenge_enchant_1k] - title = "初學附魔師!" - description = "附魔 1000 件物品" +title = "初學附魔師!" +description = "附魔 1000 件物品" [advancement.challenge_enchant_5k] - title = "中級附魔師!" - description = "附魔 5000 件物品" +title = "中級附魔師!" +description = "附魔 5000 件物品" [advancement.challenge_enchant_50k] - title = "高級附魔師!" - description = "附魔 50,000 件物品" +title = "高級附魔師!" +description = "附魔 50,000 件物品" [advancement.challenge_enchant_500k] - title = "附魔大師!" - description = "附魔 500,000 件物品" +title = "附魔大師!" +description = "附魔 500,000 件物品" [advancement.challenge_enchant_5m] - title = "神秘附魔家" - description = "附魔 5,000,000 件物品" +title = "神秘附魔家" +description = "附魔 5,000,000 件物品" [advancement.challenge_excavate_1k] - title = "初學挖掘者!" - description = "挖掘 1000 格方塊" +title = "初學挖掘者!" +description = "挖掘 1000 格方塊" [advancement.challenge_excavate_5k] - title = "中級挖掘者!" - description = "挖掘 5000 格方塊" +title = "中級挖掘者!" +description = "挖掘 5000 格方塊" [advancement.challenge_excavate_50k] - title = "高級挖掘者!" - description = "挖掘 50,000 格方塊" +title = "高級挖掘者!" +description = "挖掘 50,000 格方塊" [advancement.challenge_excavate_500k] - title = "挖掘大師!" - description = "挖掘 500,000 格方塊" +title = "挖掘大師!" +description = "挖掘 500,000 格方塊" [advancement.challenge_excavate_5m] - title = "神秘挖掘者" - description = "挖掘 5,000,000 格方塊" +title = "神秘挖掘者" +description = "挖掘 5,000,000 格方塊" [advancement.horrible_person] - title = "你是一個可怕的人" - description = "實在是深不可測" +title = "你是一個可怕的人" +description = "實在是深不可測" [advancement.challenge_turtle_egg_smasher] - title = "海龜蛋粉碎機!" - description = "打碎 100 個海龜蛋" +title = "海龜蛋粉碎機!" +description = "打碎 100 個海龜蛋" [advancement.challenge_turtle_egg_annihilator] - title = "海龜蛋殲滅者!" - description = "打碎 500 個海龜蛋" +title = "海龜蛋殲滅者!" +description = "打碎 500 個海龜蛋" [advancement.challenge_novice_hunter] - title = "新手獵人!" - description = "殺死 100 個實體" +title = "新手獵人!" +description = "殺死 100 個實體" [advancement.challenge_intermediate_hunter] - title = "中級獵人!" - description = "殺死 500 個實體" +title = "中級獵人!" +description = "殺死 500 個實體" [advancement.challenge_advanced_hunter] - title = "高級獵人!" - description = "殺死 5000 個實體" +title = "高級獵人!" +description = "殺死 5000 個實體" [advancement.challenge_creeper_conqueror] - title = "苦力怕征服者!" - description = "殺死 50 隻苦力怕" +title = "苦力怕征服者!" +description = "殺死 50 隻苦力怕" [advancement.challenge_creeper_annihilator] - title = "苦力怕殲滅者!" - description = "殺死 200 隻苦力怕" +title = "苦力怕殲滅者!" +description = "殺死 200 隻苦力怕" [advancement.challenge_pickaxe_1k] - title = "新手礦工" - description = "挖碎 1000 格方塊" +title = "新手礦工" +description = "挖碎 1000 格方塊" [advancement.challenge_pickaxe_5k] - title = "熟練礦工" - description = "挖碎 5000 格方塊" +title = "熟練礦工" +description = "挖碎 5000 格方塊" [advancement.challenge_pickaxe_50k] - title = "專家礦工" - description = "挖碎 50,000 格方塊" +title = "專家礦工" +description = "挖碎 50,000 格方塊" [advancement.challenge_pickaxe_500k] - title = "礦工大師" - description = "挖碎 500,000 格方塊" +title = "礦工大師" +description = "挖碎 500,000 格方塊" [advancement.challenge_pickaxe_5m] - title = "傳奇礦工" - description = "挖碎 5,000,000 格方塊" +title = "傳奇礦工" +description = "挖碎 5,000,000 格方塊" [advancement.challenge_eat_100] - title = "好多東西吃!" - description = "吃下超過 100 個食物!" +title = "好多東西吃!" +description = "吃下超過 100 個食物!" [advancement.challenge_eat_1000] - title = "永無止境的飢餓!" - description = "吃下超過 1,000 個食物!" +title = "永無止境的飢餓!" +description = "吃下超過 1,000 個食物!" [advancement.challenge_eat_10000] - title = "永恆的飢渴!" - description = "吃下超過 10,000 個食物!" +title = "永恆的飢渴!" +description = "吃下超過 10,000 個食物!" [advancement.challenge_harvest_100] - title = "滿載收穫" - description = "收割超過 100 株作物!" +title = "滿載收穫" +description = "收割超過 100 株作物!" [advancement.challenge_harvest_1000] - title = "豐收慶典" - description = "收割超過 1,000 株作物!" +title = "豐收慶典" +description = "收割超過 1,000 株作物!" [advancement.challenge_swim_1nm] - title = "人體潛水艇!" - description = "游泳 1 海里(1,852 格方塊)" +title = "人體潛水艇!" +description = "游泳 1 海里(1,852 格方塊)" [advancement.challenge_sneak_1k] - title = "膝蓋好痛" - description = "潛行超過 1 公里(1,000 格方塊)" +title = "膝蓋好痛" +description = "潛行超過 1 公里(1,000 格方塊)" [advancement.challenge_sneak_5k] - title = "暗影行者" - description = "潛行超過 5,000 格方塊" +title = "暗影行者" +description = "潛行超過 5,000 格方塊" [advancement.challenge_sneak_20k] - title = "幽靈" - description = "潛行超過 20,000 格方塊" +title = "幽靈" +description = "潛行超過 20,000 格方塊" [advancement.challenge_swim_5k] - title = "深海潛水員" - description = "游泳超過 5,000 格方塊" +title = "深海潛水員" +description = "游泳超過 5,000 格方塊" [advancement.challenge_swim_20k] - title = "海神之選" - description = "游泳超過 20,000 格方塊" +title = "海神之選" +description = "游泳超過 20,000 格方塊" [advancement.challenge_sword_100] - title = "初見血光" - description = "用劍命中 100 次" +title = "初見血光" +description = "用劍命中 100 次" [advancement.challenge_sword_1k] - title = "劍舞者" - description = "用劍命中 1,000 次" +title = "劍舞者" +description = "用劍命中 1,000 次" [advancement.challenge_sword_10k] - title = "千刀萬剮" - description = "用劍命中 10,000 次" +title = "千刀萬剮" +description = "用劍命中 10,000 次" [advancement.challenge_unarmed_100] - title = "酒吧打手" - description = "徒手命中 100 次" +title = "酒吧打手" +description = "徒手命中 100 次" [advancement.challenge_unarmed_1k] - title = "鐵拳" - description = "徒手命中 1,000 次" +title = "鐵拳" +description = "徒手命中 1,000 次" [advancement.challenge_unarmed_10k] - title = "一拳超人" - description = "徒手命中 10,000 次" +title = "一拳超人" +description = "徒手命中 10,000 次" [advancement.challenge_trag_1k] - title = "血的代價" - description = "承受 1,000 點傷害" +title = "血的代價" +description = "承受 1,000 點傷害" [advancement.challenge_trag_10k] - title = "赤潮" - description = "承受 10,000 點傷害" +title = "赤潮" +description = "承受 10,000 點傷害" [advancement.challenge_trag_100k] - title = "苦難化身" - description = "承受 100,000 點傷害" +title = "苦難化身" +description = "承受 100,000 點傷害" [advancement.challenge_ranged_100] - title = "靶場練習" - description = "發射 100 枚彈射物" +title = "靶場練習" +description = "發射 100 枚彈射物" [advancement.challenge_ranged_1k] - title = "鷹眼" - description = "發射 1,000 枚彈射物" +title = "鷹眼" +description = "發射 1,000 枚彈射物" [advancement.challenge_ranged_10k] - title = "箭雨風暴" - description = "發射 10,000 枚彈射物" +title = "箭雨風暴" +description = "發射 10,000 枚彈射物" [advancement.challenge_chronos_1h] - title = "滴答滴答" - description = "在線 1 小時" +title = "滴答滴答" +description = "在線 1 小時" [advancement.challenge_chronos_24h] - title = "時間之沙" - description = "在線 24 小時" +title = "時間之沙" +description = "在線 24 小時" [advancement.challenge_chronos_168h] - title = "永恆不朽" - description = "在線 168 小時(1 週)" +title = "永恆不朽" +description = "在線 168 小時(1 週)" [advancement.challenge_nether_50] - title = "地獄守門人" - description = "擊殺 50 隻地獄生物" +title = "地獄守門人" +description = "擊殺 50 隻地獄生物" [advancement.challenge_nether_500] - title = "深淵守望者" - description = "擊殺 500 隻地獄生物" +title = "深淵守望者" +description = "擊殺 500 隻地獄生物" [advancement.challenge_nether_5k] - title = "地獄之主" - description = "擊殺 5,000 隻地獄生物" +title = "地獄之主" +description = "擊殺 5,000 隻地獄生物" [advancement.challenge_rift_50] - title = "空間異常" - description = "傳送 50 次" +title = "空間異常" +description = "傳送 50 次" [advancement.challenge_rift_500] - title = "虛空行者" - description = "傳送 500 次" +title = "虛空行者" +description = "傳送 500 次" [advancement.challenge_rift_5k] - title = "穿梭世界" - description = "傳送 5,000 次" +title = "穿梭世界" +description = "傳送 5,000 次" [advancement.challenge_taming_10] - title = "動物低語者" - description = "繁殖 10 隻動物" +title = "動物低語者" +description = "繁殖 10 隻動物" [advancement.challenge_taming_50] - title = "獸群首領" - description = "繁殖 50 隻動物" +title = "獸群首領" +description = "繁殖 50 隻動物" [advancement.challenge_taming_500] - title = "馴獸大師" - description = "繁殖 500 隻動物" +title = "馴獸大師" +description = "繁殖 500 隻動物" # Agility - New Achievement Chains [advancement.challenge_sprint_dist_5k] - title = "速度惡魔" - description = "疾跑超過 5 公里 (5,000 格)" +title = "速度惡魔" +description = "疾跑超過 5 公里 (5,000 格)" [advancement.challenge_sprint_dist_50k] - title = "閃電之足" - description = "疾跑超過 50 公里 (50,000 格)" +title = "閃電之足" +description = "疾跑超過 50 公里 (50,000 格)" [advancement.challenge_agility_swim_1k] - title = "水面行者" - description = "游泳超過 1 公里 (1,000 格)" +title = "水面行者" +description = "游泳超過 1 公里 (1,000 格)" [advancement.challenge_agility_swim_10k] - title = "遠洋航行者" - description = "游泳超過 10 公里 (10,000 格)" +title = "遠洋航行者" +description = "游泳超過 10 公里 (10,000 格)" [advancement.challenge_fly_1k] - title = "翱翔之舞" - description = "飛行超過 1 公里 (1,000 格)" +title = "翱翔之舞" +description = "飛行超過 1 公里 (1,000 格)" [advancement.challenge_fly_10k] - title = "馭風者" - description = "飛行超過 10 公里 (10,000 格)" +title = "馭風者" +description = "飛行超過 10 公里 (10,000 格)" [advancement.challenge_agility_sneak_500] - title = "輕步潛行" - description = "潛行超過 500 格" +title = "輕步潛行" +description = "潛行超過 500 格" [advancement.challenge_agility_sneak_5k] - title = "幽靈步伐" - description = "潛行超過 5 公里 (5,000 格)" +title = "幽靈步伐" +description = "潛行超過 5 公里 (5,000 格)" # Architect - New Achievement Chains [advancement.challenge_demolish_500] - title = "拆除小隊" - description = "破壞 500 個方塊" +title = "拆除小隊" +description = "破壞 500 個方塊" [advancement.challenge_demolish_5k] - title = "毀滅之球" - description = "破壞 5,000 個方塊" +title = "毀滅之球" +description = "破壞 5,000 個方塊" [advancement.challenge_value_placed_10k] - title = "價值建造者" - description = "放置價值 10,000 的方塊" +title = "價值建造者" +description = "放置價值 10,000 的方塊" [advancement.challenge_value_placed_100k] - title = "建築大師" - description = "放置價值 100,000 的方塊" +title = "建築大師" +description = "放置價值 100,000 的方塊" [advancement.challenge_demolish_val_5k] - title = "回收專家" - description = "從拆除中回收 5,000 方塊價值" +title = "回收專家" +description = "從拆除中回收 5,000 方塊價值" [advancement.challenge_demolish_val_50k] - title = "徹底解構" - description = "從拆除中回收 50,000 方塊價值" +title = "徹底解構" +description = "從拆除中回收 50,000 方塊價值" [advancement.challenge_high_build_100] - title = "高空建造者" - description = "在 Y=128 以上放置 100 個方塊" +title = "高空建造者" +description = "在 Y=128 以上放置 100 個方塊" [advancement.challenge_high_build_1k] - title = "雲端建築師" - description = "在 Y=128 以上放置 1,000 個方塊" +title = "雲端建築師" +description = "在 Y=128 以上放置 1,000 個方塊" # Axes - New Achievement Chains [advancement.challenge_axe_swing_500] - title = "揮斧者" - description = "揮舞斧頭 500 次" +title = "揮斧者" +description = "揮舞斧頭 500 次" [advancement.challenge_axe_swing_5k] - title = "狂戰士" - description = "揮舞斧頭 5,000 次" +title = "狂戰士" +description = "揮舞斧頭 5,000 次" [advancement.challenge_axe_damage_1k] - title = "劈裂者" - description = "用斧頭造成 1,000 傷害" +title = "劈裂者" +description = "用斧頭造成 1,000 傷害" [advancement.challenge_axe_damage_10k] - title = "劊子手之斧" - description = "用斧頭造成 10,000 傷害" +title = "劊子手之斧" +description = "用斧頭造成 10,000 傷害" [advancement.challenge_axe_value_5k] - title = "木材商人" - description = "砍伐價值 5,000 的木材" +title = "木材商人" +description = "砍伐價值 5,000 的木材" [advancement.challenge_axe_value_50k] - title = "伐木大亨" - description = "砍伐價值 50,000 的木材" +title = "伐木大亨" +description = "砍伐價值 50,000 的木材" [advancement.challenge_leaves_500] - title = "吹葉機" - description = "用斧頭清除 500 個樹葉方塊" +title = "吹葉機" +description = "用斧頭清除 500 個樹葉方塊" [advancement.challenge_leaves_5k] - title = "落葉清除者" - description = "用斧頭清除 5,000 個樹葉方塊" +title = "落葉清除者" +description = "用斧頭清除 5,000 個樹葉方塊" # Blocking - New Achievement Chains [advancement.challenge_block_dmg_1k] - title = "傷害吸收者" - description = "用盾牌格擋 1,000 傷害" +title = "傷害吸收者" +description = "用盾牌格擋 1,000 傷害" [advancement.challenge_block_dmg_10k] - title = "人肉盾牌" - description = "用盾牌格擋 10,000 傷害" +title = "人肉盾牌" +description = "用盾牌格擋 10,000 傷害" [advancement.challenge_block_proj_100] - title = "箭矢偏轉者" - description = "用盾牌格擋 100 個投射物" +title = "箭矢偏轉者" +description = "用盾牌格擋 100 個投射物" [advancement.challenge_block_proj_1k] - title = "投射物盾牌" - description = "用盾牌格擋 1,000 個投射物" +title = "投射物盾牌" +description = "用盾牌格擋 1,000 個投射物" [advancement.challenge_block_melee_500] - title = "格擋大師" - description = "用盾牌格擋 500 次近戰攻擊" +title = "格擋大師" +description = "用盾牌格擋 500 次近戰攻擊" [advancement.challenge_block_melee_5k] - title = "鐵壁堡壘" - description = "用盾牌格擋 5,000 次近戰攻擊" +title = "鐵壁堡壘" +description = "用盾牌格擋 5,000 次近戰攻擊" [advancement.challenge_block_heavy_50] - title = "坦克" - description = "格擋 50 次重擊 (超過 5 傷害)" +title = "坦克" +description = "格擋 50 次重擊 (超過 5 傷害)" [advancement.challenge_block_heavy_500] - title = "不動如山" - description = "格擋 500 次重擊 (超過 5 傷害)" +title = "不動如山" +description = "格擋 500 次重擊 (超過 5 傷害)" # Brewing - New Achievement Chains [advancement.challenge_brew_stands_10] - title = "釀造起步" - description = "放置 10 個釀造台" +title = "釀造起步" +description = "放置 10 個釀造台" [advancement.challenge_brew_stands_50] - title = "藥水工廠" - description = "放置 50 個釀造台" +title = "藥水工廠" +description = "放置 50 個釀造台" [advancement.challenge_brew_strong_25] - title = "烈性藥劑" - description = "飲用 25 瓶強化藥水" +title = "烈性藥劑" +description = "飲用 25 瓶強化藥水" [advancement.challenge_brew_strong_250] - title = "極致藥效" - description = "飲用 250 瓶強化藥水" +title = "極致藥效" +description = "飲用 250 瓶強化藥水" [advancement.challenge_brew_splash_hits_50] - title = "飛濺區域" - description = "用噴濺藥水命中 50 個實體" +title = "飛濺區域" +description = "用噴濺藥水命中 50 個實體" [advancement.challenge_brew_splash_hits_500] - title = "瘟疫醫生" - description = "用噴濺藥水命中 500 個實體" +title = "瘟疫醫生" +description = "用噴濺藥水命中 500 個實體" # Chronos - New Achievement Chains [advancement.challenge_active_dist_1k] - title = "永不停歇" - description = "活躍時行進 1 公里" +title = "永不停歇" +description = "活躍時行進 1 公里" [advancement.challenge_active_dist_10k] - title = "探路先鋒" - description = "活躍時行進 10 公里" +title = "探路先鋒" +description = "活躍時行進 10 公里" [advancement.challenge_active_dist_100k] - title = "永動之力" - description = "活躍時行進 100 公里" +title = "永動之力" +description = "活躍時行進 100 公里" [advancement.challenge_beds_10] - title = "早起鳥兒" - description = "在床上睡覺 10 次" +title = "早起鳥兒" +description = "在床上睡覺 10 次" [advancement.challenge_beds_100] - title = "時間跳躍者" - description = "在床上睡覺 100 次" +title = "時間跳躍者" +description = "在床上睡覺 100 次" [advancement.challenge_chronos_tp_50] - title = "時空轉移" - description = "傳送 50 次" +title = "時空轉移" +description = "傳送 50 次" [advancement.challenge_chronos_tp_500] - title = "時間扭曲" - description = "傳送 500 次" +title = "時間扭曲" +description = "傳送 500 次" [advancement.challenge_chronos_deaths_10] - title = "凡人" - description = "死亡 10 次" +title = "凡人" +description = "死亡 10 次" [advancement.challenge_chronos_deaths_100] - title = "死亡挑戰者" - description = "死亡 100 次" +title = "死亡挑戰者" +description = "死亡 100 次" # Crafting - New Achievement Chains [advancement.challenge_craft_value_10k] - title = "匠心獨運" - description = "製作總價值 10,000 的物品" +title = "匠心獨運" +description = "製作總價值 10,000 的物品" [advancement.challenge_craft_value_100k] - title = "工匠大師" - description = "製作總價值 100,000 的物品" +title = "工匠大師" +description = "製作總價值 100,000 的物品" [advancement.challenge_craft_tools_25] - title = "工具匠" - description = "製作 25 件工具" +title = "工具匠" +description = "製作 25 件工具" [advancement.challenge_craft_tools_250] - title = "鍛造大師" - description = "製作 250 件工具" +title = "鍛造大師" +description = "製作 250 件工具" [advancement.challenge_craft_armor_25] - title = "鎧甲匠" - description = "製作 25 件護甲" +title = "鎧甲匠" +description = "製作 25 件護甲" [advancement.challenge_craft_armor_250] - title = "護甲大師" - description = "製作 250 件護甲" +title = "護甲大師" +description = "製作 250 件護甲" [advancement.challenge_craft_mass_25k] - title = "批量生產" - description = "製作 25,000 個物品" +title = "批量生產" +description = "製作 25,000 個物品" [advancement.challenge_craft_mass_250k] - title = "工業革命" - description = "製作 250,000 個物品" +title = "工業革命" +description = "製作 250,000 個物品" # Discovery - New Achievement Chains [advancement.challenge_discover_items_50] - title = "收集者" - description = "發現 50 種獨特物品" +title = "收集者" +description = "發現 50 種獨特物品" [advancement.challenge_discover_items_250] - title = "編目者" - description = "發現 250 種獨特物品" +title = "編目者" +description = "發現 250 種獨特物品" [advancement.challenge_discover_blocks_50] - title = "勘測員" - description = "發現 50 種獨特方塊" +title = "勘測員" +description = "發現 50 種獨特方塊" [advancement.challenge_discover_blocks_250] - title = "地質學家" - description = "發現 250 種獨特方塊" +title = "地質學家" +description = "發現 250 種獨特方塊" [advancement.challenge_discover_mobs_25] - title = "觀察者" - description = "發現 25 種獨特生物" +title = "觀察者" +description = "發現 25 種獨特生物" [advancement.challenge_discover_mobs_75] - title = "博物學家" - description = "發現 75 種獨特生物" +title = "博物學家" +description = "發現 75 種獨特生物" [advancement.challenge_discover_biomes_10] - title = "漫遊者" - description = "發現 10 種獨特生態域" +title = "漫遊者" +description = "發現 10 種獨特生態域" [advancement.challenge_discover_biomes_40] - title = "環球旅行家" - description = "發現 40 種獨特生態域" +title = "環球旅行家" +description = "發現 40 種獨特生態域" [advancement.challenge_discover_foods_10] - title = "美食家" - description = "發現 10 種獨特食物" +title = "美食家" +description = "發現 10 種獨特食物" [advancement.challenge_discover_foods_30] - title = "烹飪大師" - description = "發現 30 種獨特食物" +title = "烹飪大師" +description = "發現 30 種獨特食物" # Enchanting - New Achievement Chains [advancement.challenge_enchant_power_100] - title = "織力者" - description = "累積 100 附魔之力" +title = "織力者" +description = "累積 100 附魔之力" [advancement.challenge_enchant_power_1k] - title = "奧術宗師" - description = "累積 1,000 附魔之力" +title = "奧術宗師" +description = "累積 1,000 附魔之力" [advancement.challenge_enchant_levels_1k] - title = "等級消耗者" - description = "在附魔上花費 1,000 經驗等級" +title = "等級消耗者" +description = "在附魔上花費 1,000 經驗等級" [advancement.challenge_enchant_levels_10k] - title = "經驗黑洞" - description = "在附魔上花費 10,000 經驗等級" +title = "經驗黑洞" +description = "在附魔上花費 10,000 經驗等級" [advancement.challenge_enchant_high_25] - title = "豪賭者" - description = "進行 25 次最高級附魔" +title = "豪賭者" +description = "進行 25 次最高級附魔" [advancement.challenge_enchant_high_250] - title = "傳奇附魔師" - description = "進行 250 次最高級附魔" +title = "傳奇附魔師" +description = "進行 250 次最高級附魔" [advancement.challenge_enchant_total_500] - title = "等級燃燒者" - description = "在所有附魔上累計花費 500 等級" +title = "等級燃燒者" +description = "在所有附魔上累計花費 500 等級" [advancement.challenge_enchant_total_5k] - title = "奧術投資" - description = "在所有附魔上累計花費 5,000 等級" +title = "奧術投資" +description = "在所有附魔上累計花費 5,000 等級" # Excavation - New Achievement Chains [advancement.challenge_dig_swing_500] - title = "挖掘者" - description = "揮舞鏟子 500 次" +title = "挖掘者" +description = "揮舞鏟子 500 次" [advancement.challenge_dig_swing_5k] - title = "挖掘機" - description = "揮舞鏟子 5,000 次" +title = "挖掘機" +description = "揮舞鏟子 5,000 次" [advancement.challenge_dig_damage_1k] - title = "鏟騎士" - description = "用鏟子造成 1,000 傷害" +title = "鏟騎士" +description = "用鏟子造成 1,000 傷害" [advancement.challenge_dig_damage_10k] - title = "鏟王" - description = "用鏟子造成 10,000 傷害" +title = "鏟王" +description = "用鏟子造成 10,000 傷害" [advancement.challenge_dig_value_5k] - title = "泥土商人" - description = "挖掘價值 5,000 的方塊" +title = "泥土商人" +description = "挖掘價值 5,000 的方塊" [advancement.challenge_dig_value_50k] - title = "大地領主" - description = "挖掘價值 50,000 的方塊" +title = "大地領主" +description = "挖掘價值 50,000 的方塊" [advancement.challenge_dig_gravel_500] - title = "礫石研磨者" - description = "挖掘 500 個礫石、沙子或黏土方塊" +title = "礫石研磨者" +description = "挖掘 500 個礫石、沙子或黏土方塊" [advancement.challenge_dig_gravel_5k] - title = "砂礫篩選者" - description = "挖掘 5,000 個礫石、沙子或黏土方塊" +title = "砂礫篩選者" +description = "挖掘 5,000 個礫石、沙子或黏土方塊" # Herbalism - New Achievement Chains [advancement.challenge_plant_100] - title = "播種者" - description = "種植 100 株作物" +title = "播種者" +description = "種植 100 株作物" [advancement.challenge_plant_1k] - title = "綠手指" - description = "種植 1,000 株作物" +title = "綠手指" +description = "種植 1,000 株作物" [advancement.challenge_plant_5k] - title = "農業大亨" - description = "種植 5,000 株作物" +title = "農業大亨" +description = "種植 5,000 株作物" [advancement.challenge_compost_50] - title = "回收利用者" - description = "堆肥 50 個物品" +title = "回收利用者" +description = "堆肥 50 個物品" [advancement.challenge_compost_500] - title = "土壤改良者" - description = "堆肥 500 個物品" +title = "土壤改良者" +description = "堆肥 500 個物品" [advancement.challenge_shear_50] - title = "剪毛者" - description = "剪切 50 個實體" +title = "剪毛者" +description = "剪切 50 個實體" [advancement.challenge_shear_250] - title = "牧群之主" - description = "剪切 250 個實體" +title = "牧群之主" +description = "剪切 250 個實體" # Hunter - New Achievement Chains [advancement.challenge_kills_500] - title = "屠戮者" - description = "擊殺 500 個生物" +title = "屠戮者" +description = "擊殺 500 個生物" [advancement.challenge_kills_5k] - title = "行刑者" - description = "擊殺 5,000 個生物" +title = "行刑者" +description = "擊殺 5,000 個生物" [advancement.challenge_boss_1] - title = "挑戰首領" - description = "擊殺一個首領怪物" +title = "挑戰首領" +description = "擊殺一個首領怪物" [advancement.challenge_boss_10] - title = "傳奇殺手" - description = "擊殺 10 個首領怪物" +title = "傳奇殺手" +description = "擊殺 10 個首領怪物" # Nether - New Achievement Chains [advancement.challenge_wither_dmg_500] - title = "凋零之痛" - description = "承受 500 凋零傷害" +title = "凋零之痛" +description = "承受 500 凋零傷害" [advancement.challenge_wither_dmg_5k] - title = "枯萎倖存者" - description = "承受 5,000 凋零傷害" +title = "枯萎倖存者" +description = "承受 5,000 凋零傷害" [advancement.challenge_wither_skel_25] - title = "骸骨收集者" - description = "擊殺 25 個凋靈骷髏" +title = "骸骨收集者" +description = "擊殺 25 個凋靈骷髏" [advancement.challenge_wither_skel_250] - title = "骷髏剋星" - description = "擊殺 250 個凋靈骷髏" +title = "骷髏剋星" +description = "擊殺 250 個凋靈骷髏" [advancement.challenge_wither_boss_1] - title = "凋靈殺手" - description = "擊敗凋靈" +title = "凋靈殺手" +description = "擊敗凋靈" [advancement.challenge_wither_boss_10] - title = "地獄主宰" - description = "擊敗凋靈 10 次" +title = "地獄主宰" +description = "擊敗凋靈 10 次" [advancement.challenge_roses_10] - title = "死亡園丁" - description = "破壞 10 朵凋零玫瑰" +title = "死亡園丁" +description = "破壞 10 朵凋零玫瑰" [advancement.challenge_roses_100] - title = "枯萎收集者" - description = "破壞 100 朵凋零玫瑰" +title = "枯萎收集者" +description = "破壞 100 朵凋零玫瑰" # Pickaxes - New Achievement Chains [advancement.challenge_pick_swing_500] - title = "礦工之臂" - description = "揮舞鎬子 500 次" +title = "礦工之臂" +description = "揮舞鎬子 500 次" [advancement.challenge_pick_swing_5k] - title = "隧道挖掘者" - description = "揮舞鎬子 5,000 次" +title = "隧道挖掘者" +description = "揮舞鎬子 5,000 次" [advancement.challenge_pick_damage_1k] - title = "鎬戰者" - description = "用鎬子造成 1,000 傷害" +title = "鎬戰者" +description = "用鎬子造成 1,000 傷害" [advancement.challenge_pick_damage_10k] - title = "戰鎬" - description = "用鎬子造成 10,000 傷害" +title = "戰鎬" +description = "用鎬子造成 10,000 傷害" [advancement.challenge_pick_value_5k] - title = "寶石探索者" - description = "開採價值 5,000 的方塊" +title = "寶石探索者" +description = "開採價值 5,000 的方塊" [advancement.challenge_pick_value_50k] - title = "礦石大亨" - description = "開採價值 50,000 的方塊" +title = "礦石大亨" +description = "開採價值 50,000 的方塊" [advancement.challenge_pick_ores_500] - title = "勘探者" - description = "開採 500 個礦石方塊" +title = "勘探者" +description = "開採 500 個礦石方塊" [advancement.challenge_pick_ores_5k] - title = "採礦大師" - description = "開採 5,000 個礦石方塊" +title = "採礦大師" +description = "開採 5,000 個礦石方塊" # Ranged - New Achievement Chains [advancement.challenge_ranged_dmg_1k] - title = "神射手" - description = "造成 1,000 遠程傷害" +title = "神射手" +description = "造成 1,000 遠程傷害" [advancement.challenge_ranged_dmg_10k] - title = "致命弓手" - description = "造成 10,000 遠程傷害" +title = "致命弓手" +description = "造成 10,000 遠程傷害" [advancement.challenge_ranged_dist_5k] - title = "遠距射擊" - description = "發射投射物總飛行距離達 5,000 格" +title = "遠距射擊" +description = "發射投射物總飛行距離達 5,000 格" [advancement.challenge_ranged_dist_50k] - title = "千里射手" - description = "發射投射物總飛行距離達 50,000 格" +title = "千里射手" +description = "發射投射物總飛行距離達 50,000 格" [advancement.challenge_ranged_kills_50] - title = "弓箭手" - description = "用遠程武器擊殺 50 個生物" +title = "弓箭手" +description = "用遠程武器擊殺 50 個生物" [advancement.challenge_ranged_kills_500] - title = "射術宗師" - description = "用遠程武器擊殺 500 個生物" +title = "射術宗師" +description = "用遠程武器擊殺 500 個生物" [advancement.challenge_longshot_25] - title = "狙擊手" - description = "命中 25 次遠距射擊 (超過 30 格)" +title = "狙擊手" +description = "命中 25 次遠距射擊 (超過 30 格)" [advancement.challenge_longshot_250] - title = "鷹眼" - description = "命中 250 次遠距射擊 (超過 30 格)" +title = "鷹眼" +description = "命中 250 次遠距射擊 (超過 30 格)" # Rift - New Achievement Chains [advancement.challenge_rift_pearls_50] - title = "珍珠投擲者" - description = "投擲 50 顆乃影珍珠" +title = "珍珠投擲者" +description = "投擲 50 顆乃影珍珠" [advancement.challenge_rift_pearls_500] - title = "傳送狂人" - description = "投擲 500 顆乃影珍珠" +title = "傳送狂人" +description = "投擲 500 顆乃影珍珠" [advancement.challenge_rift_enderman_50] - title = "乃影人獵手" - description = "擊殺 50 個乃影人" +title = "乃影人獵手" +description = "擊殺 50 個乃影人" [advancement.challenge_rift_enderman_500] - title = "虛空追獵者" - description = "擊殺 500 個乃影人" +title = "虛空追獵者" +description = "擊殺 500 個乃影人" [advancement.challenge_rift_dragon_500] - title = "屠龍戰士" - description = "對乃影龍造成 500 傷害" +title = "屠龍戰士" +description = "對乃影龍造成 500 傷害" [advancement.challenge_rift_dragon_5k] - title = "屠龍者" - description = "對乃影龍造成 5,000 傷害" +title = "屠龍者" +description = "對乃影龍造成 5,000 傷害" [advancement.challenge_rift_crystal_10] - title = "水晶破壞者" - description = "摧毀 10 個乃地水晶" +title = "水晶破壞者" +description = "摧毀 10 個乃地水晶" [advancement.challenge_rift_crystal_100] - title = "終界毀滅者" - description = "摧毀 100 個乃地水晶" +title = "終界毀滅者" +description = "摧毀 100 個乃地水晶" # Seaborne - New Achievement Chains [advancement.challenge_fish_25] - title = "垂釣者" - description = "捕獲 25 條魚" +title = "垂釣者" +description = "捕獲 25 條魚" [advancement.challenge_fish_250] - title = "釣魚大師" - description = "捕獲 250 條魚" +title = "釣魚大師" +description = "捕獲 250 條魚" [advancement.challenge_drowned_25] - title = "溺屍獵人" - description = "擊殺 25 個溺屍" +title = "溺屍獵人" +description = "擊殺 25 個溺屍" [advancement.challenge_drowned_250] - title = "海洋淨化者" - description = "擊殺 250 個溺屍" +title = "海洋淨化者" +description = "擊殺 250 個溺屍" [advancement.challenge_guardian_10] - title = "守衛者獵手" - description = "擊殺 10 個守衛者" +title = "守衛者獵手" +description = "擊殺 10 個守衛者" [advancement.challenge_guardian_100] - title = "神殿掠奪者" - description = "擊殺 100 個守衛者" +title = "神殿掠奪者" +description = "擊殺 100 個守衛者" [advancement.challenge_underwater_blocks_100] - title = "水下礦工" - description = "在水下破壞 100 個方塊" +title = "水下礦工" +description = "在水下破壞 100 個方塊" [advancement.challenge_underwater_blocks_1k] - title = "水下工程師" - description = "在水下破壞 1,000 個方塊" +title = "水下工程師" +description = "在水下破壞 1,000 個方塊" # Stealth - New Achievement Chains [advancement.challenge_stealth_dmg_500] - title = "背刺者" - description = "潛行時造成 500 傷害" +title = "背刺者" +description = "潛行時造成 500 傷害" [advancement.challenge_stealth_dmg_5k] - title = "無聲殺手" - description = "潛行時造成 5,000 傷害" +title = "無聲殺手" +description = "潛行時造成 5,000 傷害" [advancement.challenge_stealth_kills_10] - title = "刺客" - description = "潛行時擊殺 10 個生物" +title = "刺客" +description = "潛行時擊殺 10 個生物" [advancement.challenge_stealth_kills_100] - title = "暗影死神" - description = "潛行時擊殺 100 個生物" +title = "暗影死神" +description = "潛行時擊殺 100 個生物" [advancement.challenge_stealth_time_1h] - title = "耐心者" - description = "潛行 1 小時 (3,600 秒)" +title = "耐心者" +description = "潛行 1 小時 (3,600 秒)" [advancement.challenge_stealth_time_10h] - title = "暗影之主" - description = "潛行 10 小時 (36,000 秒)" +title = "暗影之主" +description = "潛行 10 小時 (36,000 秒)" [advancement.challenge_stealth_arrows_50] - title = "暗箭手" - description = "潛行時射出 50 支箭" +title = "暗箭手" +description = "潛行時射出 50 支箭" [advancement.challenge_stealth_arrows_500] - title = "幽靈弓手" - description = "潛行時射出 500 支箭" +title = "幽靈弓手" +description = "潛行時射出 500 支箭" # Swords - New Achievement Chains [advancement.challenge_sword_dmg_1k] - title = "劍術學徒" - description = "用劍造成 1,000 傷害" +title = "劍術學徒" +description = "用劍造成 1,000 傷害" [advancement.challenge_sword_dmg_10k] - title = "劍客" - description = "用劍造成 10,000 傷害" +title = "劍客" +description = "用劍造成 10,000 傷害" [advancement.challenge_sword_kills_50] - title = "決鬥者" - description = "用劍擊殺 50 個生物" +title = "決鬥者" +description = "用劍擊殺 50 個生物" [advancement.challenge_sword_kills_500] - title = "角鬥士" - description = "用劍擊殺 500 個生物" +title = "角鬥士" +description = "用劍擊殺 500 個生物" [advancement.challenge_sword_crit_50] - title = "暴擊者" - description = "用劍造成 50 次暴擊" +title = "暴擊者" +description = "用劍造成 50 次暴擊" [advancement.challenge_sword_crit_500] - title = "精準大師" - description = "用劍造成 500 次暴擊" +title = "精準大師" +description = "用劍造成 500 次暴擊" [advancement.challenge_sword_heavy_25] - title = "重擊者" - description = "用劍造成 25 次重擊 (超過 8 傷害)" +title = "重擊者" +description = "用劍造成 25 次重擊 (超過 8 傷害)" [advancement.challenge_sword_heavy_250] - title = "毀滅之擊" - description = "用劍造成 250 次重擊 (超過 8 傷害)" +title = "毀滅之擊" +description = "用劍造成 250 次重擊 (超過 8 傷害)" # Taming - New Achievement Chains [advancement.challenge_pet_dmg_500] - title = "馴獸師" - description = "你的寵物共造成 500 傷害" +title = "馴獸師" +description = "你的寵物共造成 500 傷害" [advancement.challenge_pet_dmg_5k] - title = "戰爭統帥" - description = "你的寵物共造成 5,000 傷害" +title = "戰爭統帥" +description = "你的寵物共造成 5,000 傷害" [advancement.challenge_tamed_10] - title = "動物之友" - description = "馴服 10 隻動物" +title = "動物之友" +description = "馴服 10 隻動物" [advancement.challenge_tamed_100] - title = "動物園長" - description = "馴服 100 隻動物" +title = "動物園長" +description = "馴服 100 隻動物" [advancement.challenge_pet_kills_25] - title = "群攻戰術" - description = "你的寵物擊殺 25 個生物" +title = "群攻戰術" +description = "你的寵物擊殺 25 個生物" [advancement.challenge_pet_kills_250] - title = "首領指揮官" - description = "你的寵物擊殺 250 個生物" +title = "首領指揮官" +description = "你的寵物擊殺 250 個生物" [advancement.challenge_taming_2500] - title = "繁殖專家" - description = "繁殖 2,500 隻動物" +title = "繁殖專家" +description = "繁殖 2,500 隻動物" [advancement.challenge_taming_25k] - title = "基因大師" - description = "繁殖 25,000 隻動物" +title = "基因大師" +description = "繁殖 25,000 隻動物" # TragOul - New Achievement Chains [advancement.challenge_trag_hits_500] - title = "沙包" - description = "受到 500 次攻擊" +title = "沙包" +description = "受到 500 次攻擊" [advancement.challenge_trag_hits_5k] - title = "受虐狂" - description = "受到 5,000 次攻擊" +title = "受虐狂" +description = "受到 5,000 次攻擊" [advancement.challenge_trag_deaths_10] - title = "九條命" - description = "死亡 10 次" +title = "九條命" +description = "死亡 10 次" [advancement.challenge_trag_deaths_100] - title = "反覆噩夢" - description = "死亡 100 次" +title = "反覆噩夢" +description = "死亡 100 次" [advancement.challenge_trag_fire_500] - title = "火焰受害者" - description = "承受 500 火焰傷害" +title = "火焰受害者" +description = "承受 500 火焰傷害" [advancement.challenge_trag_fire_5k] - title = "浴火鳳凰" - description = "承受 5,000 火焰傷害" +title = "浴火鳳凰" +description = "承受 5,000 火焰傷害" [advancement.challenge_trag_fall_500] - title = "重力測試" - description = "承受 500 墜落傷害" +title = "重力測試" +description = "承受 500 墜落傷害" [advancement.challenge_trag_fall_5k] - title = "極限速度" - description = "承受 5,000 墜落傷害" +title = "極限速度" +description = "承受 5,000 墜落傷害" # Unarmed - New Achievement Chains [advancement.challenge_unarmed_dmg_1k] - title = "拳擊手" - description = "用空手造成 1,000 傷害" +title = "拳擊手" +description = "用空手造成 1,000 傷害" [advancement.challenge_unarmed_dmg_10k] - title = "武術家" - description = "用空手造成 10,000 傷害" +title = "武術家" +description = "用空手造成 10,000 傷害" [advancement.challenge_unarmed_kills_25] - title = "赤手空拳" - description = "用空手擊殺 25 個生物" +title = "赤手空拳" +description = "用空手擊殺 25 個生物" [advancement.challenge_unarmed_kills_250] - title = "傳奇之拳" - description = "用空手擊殺 250 個生物" +title = "傳奇之拳" +description = "用空手擊殺 250 個生物" [advancement.challenge_unarmed_crit_25] - title = "致命一拳" - description = "用空手造成 25 次暴擊" +title = "致命一拳" +description = "用空手造成 25 次暴擊" [advancement.challenge_unarmed_crit_250] - title = "精準之拳" - description = "用空手造成 250 次暴擊" +title = "精準之拳" +description = "用空手造成 250 次暴擊" [advancement.challenge_unarmed_heavy_25] - title = "力量之拳" - description = "用空手造成 25 次重擊 (超過 6 傷害)" +title = "力量之拳" +description = "用空手造成 25 次重擊 (超過 6 傷害)" [advancement.challenge_unarmed_heavy_250] - title = "拳王" - description = "用空手造成 250 次重擊 (超過 6 傷害)" +title = "拳王" +description = "用空手造成 250 次重擊 (超過 6 傷害)" # items [items] [items.bound_ender_peral] - name = "亞空間之鑰" - usage1 = "Shift + 左鍵點擊以綁定" - usage2 = "右鍵點擊以存取已綁定的容器" +name = "亞空間之鑰" +usage1 = "Shift + 左鍵點擊以綁定" +usage2 = "右鍵點擊以存取已綁定的容器" [items.bound_eye_of_ender] - name = "視界錨點" - usage1 = "右鍵點擊以消耗並傳送到綁定的位置" - usage2 = "Shift + 左鍵點擊以綁定方塊" +name = "視界錨點" +usage1 = "右鍵點擊以消耗並傳送到綁定的位置" +usage2 = "Shift + 左鍵點擊以綁定方塊" [items.bound_redstone_torch] - name = "紅石遙控器" - usage1 = "右鍵點擊以發送 1 刻紅石脈衝" - usage2 = "對「標靶」方塊 Shift + 左鍵點擊以綁定" +name = "紅石遙控器" +usage1 = "右鍵點擊以發送 1 刻紅石脈衝" +usage2 = "對「標靶」方塊 Shift + 左鍵點擊以綁定" [items.bound_snowball] - name = "蛛網陷阱!" - usage1 = "拋出以在該位置創建臨時蛛網陷阱" +name = "蛛網陷阱!" +usage1 = "拋出以在該位置創建臨時蛛網陷阱" [items.chrono_time_bottle] - name = "時光之瓶" - usage1 = "放在物品欄中時會被動儲存時間" - usage2 = "右鍵點擊計時方塊或幼年動物以消耗儲存的時間" - stored = "已儲存時間" +name = "時光之瓶" +usage1 = "放在物品欄中時會被動儲存時間" +usage2 = "右鍵點擊計時方塊或幼年動物以消耗儲存的時間" +stored = "已儲存時間" [items.chrono_time_bomb] - name = "時間炸彈" - usage1 = "右鍵點擊以發射時空彈,產生時間力場" +name = "時間炸彈" +usage1 = "右鍵點擊以發射時空彈,產生時間力場" [items.elevator_block] - name = "電梯方塊" - usage1 = "跳躍以向上傳送" - usage2 = "潛行以向下傳送" - usage3 = "電梯之間至少需要 2 格空氣方塊" +name = "電梯方塊" +usage1 = "跳躍以向上傳送" +usage2 = "潛行以向下傳送" +usage3 = "電梯之間至少需要 2 格空氣方塊" # snippets [snippets] [snippets.gui] - level = "等級" - knowledge = "知識" - power_used = "已使用能力" - not_learned = "尚未學習" - xp = "經驗至" - welcome = "歡迎!" - welcome_back = "歡迎回來!" - xp_bonus_for_time = "經驗增益,持續" - max_ability_power = "最大技能能力" - unlock_this_by_clicking = "右鍵點擊以解鎖:" - back = "返回" - unlearn_all = "遺忘全部技能" - unlearned_all = "已全部遺忘" +level = "等級" +knowledge = "知識" +power_used = "已使用能力" +not_learned = "尚未學習" +xp = "經驗至" +welcome = "歡迎!" +welcome_back = "歡迎回來!" +xp_bonus_for_time = "經驗增益,持續" +max_ability_power = "最大技能能力" +unlock_this_by_clicking = "右鍵點擊以解鎖:" +back = "返回" +unlearn_all = "遺忘全部技能" +unlearned_all = "已全部遺忘" [snippets.adapt_menu] - may_not_unlearn = "你無法遺忘此技能" - may_unlearn = "可學習/遺忘" - knowledge_cost = "知識花費" - knowledge_available = "可用知識" - already_learned = "已學習" - unlearn_refund = "點擊以遺忘並退還" - no_refunds = "極限模式,已禁用退還" - knowledge = "知識" - click_learn = "點擊以學習" - no_knowledge = "(你沒有任何知識)" - you_only_have = "你只有" - how_to_level_up = "升級技能以提升你的最大能力。" - not_enough_power = "能力不足!每級技能需要花費 1 點能力。" - power = "能力" - power_drain = "能力消耗" - learned = "已學習 " - unlearned = "已遺忘 " - activator_block = "書架" +may_not_unlearn = "你無法遺忘此技能" +may_unlearn = "可學習/遺忘" +knowledge_cost = "知識花費" +knowledge_available = "可用知識" +already_learned = "已學習" +unlearn_refund = "點擊以遺忘並退還" +no_refunds = "極限模式,已禁用退還" +knowledge = "知識" +click_learn = "點擊以學習" +no_knowledge = "(你沒有任何知識)" +you_only_have = "你只有" +how_to_level_up = "升級技能以提升你的最大能力。" +not_enough_power = "能力不足!每級技能需要花費 1 點能力。" +power = "能力" +power_drain = "能力消耗" +learned = "已學習 " +unlearned = "已遺忘 " +activator_block = "書架" [snippets.knowledge_orb] - contains = "包含" - knowledge = "知識" - rightclick = "右鍵點擊" - togainknowledge = "以獲得此知識" - knowledge_orb = "知識之珠" +contains = "包含" +knowledge = "知識" +rightclick = "右鍵點擊" +togainknowledge = "以獲得此知識" +knowledge_orb = "知識之珠" [snippets.experience_orb] - contains = "包含" - xp = "經驗" - rightclick = "右鍵點擊" - togainxp = "以獲得此經驗" - xporb = "經驗球" +contains = "包含" +xp = "經驗" +rightclick = "右鍵點擊" +togainxp = "以獲得此經驗" +xporb = "經驗球" # skill [skill] [skill.agility] - name = "敏捷" - icon = "⇉" - description = "敏捷是面對障礙時快速且流暢移動的能力。" +name = "敏捷" +icon = "⇉" +description = "敏捷是面對障礙時快速且流暢移動的能力。" [skill.architect] - name = "建築師" - icon = "⬧" - description = "結構是世界的基石。現實掌握在你手中,由你掌控。" +name = "建築師" +icon = "⬧" +description = "結構是世界的基石。現實掌握在你手中,由你掌控。" [skill.axes] - name = "斧技" - icon = "🪓" - description1 = "與其砍樹,不如砍" - description2 = "東西" - description3 = ",結果都一樣嘛!" +name = "斧技" +icon = "🪓" +description1 = "與其砍樹,不如砍" +description2 = "東西" +description3 = ",結果都一樣嘛!" [skill.brewing] - name = "釀造" - icon = "❦" - description = "咕嘟咕嘟,再咕嘟——我還是沒辦法把這瓶藥水倒進煉藥鍋裡" +name = "釀造" +icon = "❦" +description = "咕嘟咕嘟,再咕嘟——我還是沒辦法把這瓶藥水倒進煉藥鍋裡" [skill.blocking] - name = "格擋" - icon = "🛡" - description = "棍棒和石頭不會折斷你的骨頭,但是盾牌會。" +name = "格擋" +icon = "🛡" +description = "棍棒和石頭不會折斷你的骨頭,但是盾牌會。" [skill.crafting] - name = "合成" - icon = "⌂" - description = "當沒有東西可以放了,何不再做一個呢?" +name = "合成" +icon = "⌂" +description = "當沒有東西可以放了,何不再做一個呢?" [skill.discovery] - name = "探索" - icon = "⚛" - description = "隨著感知力的擴展,你的心智逐漸解開,發現那些你未曾察覺的事物。" +name = "探索" +icon = "⚛" +description = "隨著感知力的擴展,你的心智逐漸解開,發現那些你未曾察覺的事物。" [skill.enchanting] - name = "附魔" - icon = "♰" - description = "你在說什麼呢?預言、幻象,還是迷信的胡言亂語?" +name = "附魔" +icon = "♰" +description = "你在說什麼呢?預言、幻象,還是迷信的胡言亂語?" [skill.excavation] - name = "挖掘" - icon = "ᛳ" - description = "挖呀挖呀挖......" +name = "挖掘" +icon = "ᛳ" +description = "挖呀挖呀挖......" [skill.herbalism] - name = "草藥學" - icon = "⚘" - description = "我找不到任何植物,但我能找到一些種子還有——那是……雜草?" +name = "草藥學" +icon = "⚘" +description = "我找不到任何植物,但我能找到一些種子還有——那是……雜草?" [skill.hunter] - name = "狩獵" - icon = "☠" - description = "狩獵重在過程,而非結果。" +name = "狩獵" +icon = "☠" +description = "狩獵重在過程,而非結果。" [skill.nether] - name = "地獄" - icon = "₪" - description = "來自地獄的最深處。" +name = "地獄" +icon = "₪" +description = "來自地獄的最深處。" [skill.pickaxe] - name = "鎬技" - icon = "⛏" - description = "矮人才是礦工,但我也學到了一些東西。我是瑞典人!" +name = "鎬技" +icon = "⛏" +description = "矮人才是礦工,但我也學到了一些東西。我是瑞典人!" [skill.ranged] - name = "箭術" - icon = "🏹" - description = "距離是勝利的關鍵,也是生存的關鍵。" +name = "箭術" +icon = "🏹" +description = "距離是勝利的關鍵,也是生存的關鍵。" [skill.rift] - name = "裂痕" - icon = "❍" - description = "裂痕是一道灼熱的束縛,但你已駕馭了它。" +name = "裂痕" +icon = "❍" +description = "裂痕是一道灼熱的束縛,但你已駕馭了它。" [skill.seaborne] - name = "航海" - icon = "🎣" - description = "擁有此技能,你將能駕馭水中的奇跡。" +name = "航海" +icon = "🎣" +description = "擁有此技能,你將能駕馭水中的奇跡。" [skill.stealth] - name = "隱匿" - icon = "☯" - description = "隱於無形的藝術。行走於陰影之中。" +name = "隱匿" +icon = "☯" +description = "隱於無形的藝術。行走於陰影之中。" [skill.swords] - name = "劍術" - icon = "⚔" - description = "以灰岩石之力!" +name = "劍術" +icon = "⚔" +description = "以灰岩石之力!" [skill.taming] - name = "馴獸" - icon = "♥" - description = "鸚鵡和蜜蜂……還有你?" +name = "馴獸" +icon = "♥" +description = "鸚鵡和蜜蜂……還有你?" [skill.tragoul] - name = "血咒" - icon = "🗡" - description = "血液在宇宙的血管中流淌,受你雙手所束縛。" +name = "血咒" +icon = "🗡" +description = "血液在宇宙的血管中流淌,受你雙手所束縛。" [skill.chronos] - name = "時空" - icon = "🕒" - description = "為宇宙之鐘上發條,感受時間的流動。打破時鐘,化身其中。" +name = "時空" +icon = "🕒" +description = "為宇宙之鐘上發條,感受時間的流動。打破時鐘,化身其中。" [skill.unarmed] - name = "搏擊" - icon = "»" - description = "沒有武器並不意味著沒有力量。" +name = "搏擊" +icon = "»" +description = "沒有武器並不意味著沒有力量。" # agility [agility] [agility.armor_up] - name = "裝甲強化" - description = "衝刺時間越長,獲得的護甲值越多!" - lore1 = "最大護甲值" - lore2 = "護甲強化所需時間" - lore = ["最大護甲值", "護甲強化所需時間"] +name = "裝甲強化" +description = "衝刺時間越長,獲得的護甲值越多!" +lore1 = "最大護甲值" +lore2 = "護甲強化所需時間" +lore = ["最大護甲值", "護甲強化所需時間"] [agility.ladder_slide] - name = "梯子滑行" - description = "大幅提升攀爬和滑下梯子的速度。" - lore1 = "梯子速度倍率" - lore2 = "快速下滑速度" - lore = ["梯子速度倍率", "快速下滑速度"] +name = "梯子滑行" +description = "大幅提升攀爬和滑下梯子的速度。" +lore1 = "梯子速度倍率" +lore2 = "快速下滑速度" +lore = ["梯子速度倍率", "快速下滑速度"] [agility.super_jump] - name = "超級跳躍" - description = "卓越的高度優勢。" - lore1 = "最大跳躍高度" - lore2 = "潛行 + 跳躍以超級跳躍!" - lore = ["最大跳躍高度", "潛行 + 跳躍以超級跳躍!"] +name = "超級跳躍" +description = "卓越的高度優勢。" +lore1 = "最大跳躍高度" +lore2 = "潛行 + 跳躍以超級跳躍!" +lore = ["最大跳躍高度", "潛行 + 跳躍以超級跳躍!"] [agility.wall_jump] - name = "蹬牆跳" - description = "在半空中緊貼牆壁時按住潛行鍵以攀附並跳躍!" - lore1 = "最大跳躍次數" - lore2 = "跳躍高度" - lore = ["最大跳躍次數", "跳躍高度"] +name = "蹬牆跳" +description = "在半空中緊貼牆壁時按住潛行鍵以攀附並跳躍!" +lore1 = "最大跳躍次數" +lore2 = "跳躍高度" +lore = ["最大跳躍次數", "跳躍高度"] [agility.wind_up] - name = "蓄力加速" - description = "衝刺時間越長,速度越快!" - lore1 = "最大速度" - lore2 = "蓄力時間" - lore = ["最大速度", "蓄力時間"] +name = "蓄力加速" +description = "衝刺時間越長,速度越快!" +lore1 = "最大速度" +lore2 = "蓄力時間" +lore = ["最大速度", "蓄力時間"] # architect [architect] [architect.elevator] - name = "電梯" - description = "允許你建造電梯以快速垂直傳送!" - lore1 = "解鎖電梯配方:X=羊毛,Y=乖珍珠" - lore2 = "XXX" - lore3 = "XYX" - lore4 = "XXX" - lore = ["解鎖電梯配方:X=羊毛,Y=乖珍珠", "XXX", "XYX", "XXX"] +name = "電梯" +description = "允許你建造電梯以快速垂直傳送!" +lore1 = "解鎖電梯配方:X=羊毛,Y=乖珍珠" +lore2 = "XXX" +lore3 = "XYX" +lore4 = "XXX" +lore = ["解鎖電梯配方:X=羊毛,Y=乖珍珠", "XXX", "XYX", "XXX"] [architect.foundation] - name = "魔法地基" - description = "潛行時可以在腳下臨時放置方塊!" - lore1 = "魔法生成:" - lore2 = "格方塊在你腳下!" - lore = ["魔法生成:", "格方塊在你腳下!"] +name = "魔法地基" +description = "潛行時可以在腳下臨時放置方塊!" +lore1 = "魔法生成:" +lore2 = "格方塊在你腳下!" +lore = ["魔法生成:", "格方塊在你腳下!"] [architect.glass] - name = "絲綢觸感玻璃" - description = "空手打破玻璃方塊時不會損失玻璃!" - lore1 = "你的雙手對玻璃擁有絲綢觸感" - lore = ["你的雙手對玻璃擁有絲綢觸感"] +name = "絲綢觸感玻璃" +description = "空手打破玻璃方塊時不會損失玻璃!" +lore1 = "你的雙手對玻璃擁有絲綢觸感" +lore = ["你的雙手對玻璃擁有絲綢觸感"] [architect.wireless_redstone] - name = "紅石遙控器" - description = "使用紅石火把遠程遙控紅石!" - lore1 = "標靶方塊 + 紅石火把 + 乖珍珠 = 1 個紅石遙控器" - lore = ["標靶方塊 + 紅石火把 + 乖珍珠 = 1 個紅石遙控器"] +name = "紅石遙控器" +description = "使用紅石火把遠程遙控紅石!" +lore1 = "標靶方塊 + 紅石火把 + 乖珍珠 = 1 個紅石遙控器" +lore = ["標靶方塊 + 紅石火把 + 乖珍珠 = 1 個紅石遙控器"] [architect.placement] - name = "建築師之杖" - description = "允許你一次放置多格方塊!啟動方式為潛行,手持與你注視的方塊相同的方塊並放置!請注意,你可能需要稍微移動以觸發邊界框!" - lore1 = "你需要" - lore2 = "格手中方塊才能放置" - lore3 = "材料建築師之杖" - lore = ["你需要", "格手中方塊才能放置", "材料建築師之杖"] +name = "建築師之杖" +description = "允許你一次放置多格方塊!啟動方式為潛行,手持與你注視的方塊相同的方塊並放置!請注意,你可能需要稍微移動以觸發邊界框!" +lore1 = "你需要" +lore2 = "格手中方塊才能放置" +lore3 = "材料建築師之杖" +lore = ["你需要", "格手中方塊才能放置", "材料建築師之杖"] # axe [axe] [axe.chop] - name = "斧頭砍伐" - description = "右鍵點擊原木底部以砍倒整棵樹!" - lore1 = "每次砍伐數量" - lore2 = "砍伐冷卻" - lore3 = "工具磨損" - lore = ["每次砍伐數量", "砍伐冷卻", "工具磨損"] +name = "斧頭砍伐" +description = "右鍵點擊原木底部以砍倒整棵樹!" +lore1 = "每次砍伐數量" +lore2 = "砍伐冷卻" +lore3 = "工具磨損" +lore = ["每次砍伐數量", "砍伐冷卻", "工具磨損"] [axe.log_swap] - name = "露西的原木轉換器" - description = "在工作台中更換原木種類!" - lore1 = "8 根任意種類原木 + 1 株樹苗 = 8 根樹苗對應種類的原木" - lore = ["8 根任意種類原木 + 1 株樹苗 = 8 根樹苗對應種類的原木"] +name = "露西的原木轉換器" +description = "在工作台中更換原木種類!" +lore1 = "8 根任意種類原木 + 1 株樹苗 = 8 根樹苗對應種類的原木" +lore = ["8 根任意種類原木 + 1 株樹苗 = 8 根樹苗對應種類的原木"] [axe.drop_to_inventory] - name = "斧頭掉落物自動入包" +name = "斧頭掉落物自動入包" [axe.ground_smash] - name = "山崩地裂" - description = "跳起後蹲下以猛擊附近所有敵人。" - lore1 = "傷害" - lore2 = "方塊半徑" - lore3 = "力量" - lore4 = "猛擊冷卻" - lore = ["傷害", "方塊半徑", "力量", "猛擊冷卻"] +name = "山崩地裂" +description = "跳起後蹲下以猛擊附近所有敵人。" +lore1 = "傷害" +lore2 = "方塊半徑" +lore3 = "力量" +lore4 = "猛擊冷卻" +lore = ["傷害", "方塊半徑", "力量", "猛擊冷卻"] [axe.leaf_miner] - name = "樹葉粉碎機" - description = "允許你一次破壞大量樹葉!" - lore1 = "潛行並挖掘樹葉" - lore2 = "樹葉挖掘範圍" - lore3 = "你不會獲得樹葉的掉落物(防止濫用)" - lore = ["潛行並挖掘樹葉", "樹葉挖掘範圍", "你不會獲得樹葉的掉落物(防止濫用)"] +name = "樹葉粉碎機" +description = "允許你一次破壞大量樹葉!" +lore1 = "潛行並挖掘樹葉" +lore2 = "樹葉挖掘範圍" +lore3 = "你不會獲得樹葉的掉落物(防止濫用)" +lore = ["潛行並挖掘樹葉", "樹葉挖掘範圍", "你不會獲得樹葉的掉落物(防止濫用)"] [axe.wood_miner] - name = "木材礦工" - description = "允許你一次砍伐大量木材!" - lore1 = "潛行並砍伐木頭/原木(木板無效)" - lore2 = "木材挖掘範圍" - lore3 = "與掉落物自動入包相容" - lore = ["潛行並砍伐木頭/原木(木板無效)", "木材挖掘範圍", "與掉落物自動入包相容"] +name = "木材礦工" +description = "允許你一次砍伐大量木材!" +lore1 = "潛行並砍伐木頭/原木(木板無效)" +lore2 = "木材挖掘範圍" +lore3 = "與掉落物自動入包相容" +lore = ["潛行並砍伐木頭/原木(木板無效)", "木材挖掘範圍", "與掉落物自動入包相容"] # brewing [brewing] [brewing.lingering] - name = "持久藥效" - description = "釀造的藥水效果持續更久!" - lore1 = "持續時間" - lore2 = "持續時間" - lore = ["持續時間", "持續時間"] +name = "持久藥效" +description = "釀造的藥水效果持續更久!" +lore1 = "持續時間" +lore2 = "持續時間" +lore = ["持續時間", "持續時間"] [brewing.super_heated] - name = "超級加熱釀造" - description = "釀造台越熱,工作速度越快。" - lore1 = "每接觸火焰方塊" - lore2 = "每接觸岩漿方塊" - lore = ["每接觸火焰方塊", "每接觸岩漿方塊"] +name = "超級加熱釀造" +description = "釀造台越熱,工作速度越快。" +lore1 = "每接觸火焰方塊" +lore2 = "每接觸岩漿方塊" +lore = ["每接觸火焰方塊", "每接觸岩漿方塊"] [brewing.darkness] - name = "瓶裝黑暗" - description = "不知道你為什麼需要這個,但給你吧!" - lore1 = "夜視藥水 + 黑色混凝土 = 黑暗藥水(30 秒)" - lore2 = "注意:效果期間無法疾跑!" - lore = ["夜視藥水 + 黑色混凝土 = 黑暗藥水(30 秒)", "注意:效果期間無法疾跑!"] +name = "瓶裝黑暗" +description = "不知道你為什麼需要這個,但給你吧!" +lore1 = "夜視藥水 + 黑色混凝土 = 黑暗藥水(30 秒)" +lore2 = "注意:效果期間無法疾跑!" +lore = ["夜視藥水 + 黑色混凝土 = 黑暗藥水(30 秒)", "注意:效果期間無法疾跑!"] [brewing.haste] - name = "瓶裝急迫" - description = "當效率附魔不夠用的時候" - lore1 = "速度藥水 + 紫水晶碎片 = 急迫藥水(60 秒)" - lore2 = "速度藥水 + 紫水晶塊 = 急迫 II 藥水(30 秒)" - lore = ["速度藥水 + 紫水晶碎片 = 急迫藥水(60 秒)", "速度藥水 + 紫水晶塊 = 急迫 II 藥水(30 秒)"] +name = "瓶裝急迫" +description = "當效率附魔不夠用的時候" +lore1 = "速度藥水 + 紫水晶碎片 = 急迫藥水(60 秒)" +lore2 = "速度藥水 + 紫水晶塊 = 急迫 II 藥水(30 秒)" +lore = ["速度藥水 + 紫水晶碎片 = 急迫藥水(60 秒)", "速度藥水 + 紫水晶塊 = 急迫 II 藥水(30 秒)"] [brewing.absorption] - name = "瓶裝吸收" - description = "強化身軀!" - lore1 = "瞬間治療 + 石英 = 吸收藥水(60 秒)" - lore2 = "瞬間治療 + 石英塊 = 吸收 II 藥水(30 秒)" - lore = ["瞬間治療 + 石英 = 吸收藥水(60 秒)", "瞬間治療 + 石英塊 = 吸收 II 藥水(30 秒)"] +name = "瓶裝吸收" +description = "強化身軀!" +lore1 = "瞬間治療 + 石英 = 吸收藥水(60 秒)" +lore2 = "瞬間治療 + 石英塊 = 吸收 II 藥水(30 秒)" +lore = ["瞬間治療 + 石英 = 吸收藥水(60 秒)", "瞬間治療 + 石英塊 = 吸收 II 藥水(30 秒)"] [brewing.fatigue] - name = "瓶裝疲勞" - description = "削弱身軀!" - lore1 = "虛弱藥水 + 史萊姆球 = 挖掘疲勞藥水(30 秒)" - lore2 = "虛弱藥水 + 史萊姆塊 = 挖掘疲勞 II 藥水(15 秒)" - lore = ["虛弱藥水 + 史萊姆球 = 挖掘疲勞藥水(30 秒)", "虛弱藥水 + 史萊姆塊 = 挖掘疲勞 II 藥水(15 秒)"] +name = "瓶裝疲勞" +description = "削弱身軀!" +lore1 = "虛弱藥水 + 史萊姆球 = 挖掘疲勞藥水(30 秒)" +lore2 = "虛弱藥水 + 史萊姆塊 = 挖掘疲勞 II 藥水(15 秒)" +lore = ["虛弱藥水 + 史萊姆球 = 挖掘疲勞藥水(30 秒)", "虛弱藥水 + 史萊姆塊 = 挖掘疲勞 II 藥水(15 秒)"] [brewing.hunger] - name = "瓶裝飢餓" - description = "餵飽那些永不滿足的人!" - lore1 = "粗製藥水 + 腐肉 = 飢餓藥水(30 秒)" - lore2 = "虛弱藥水 + 腐肉 = 飢餓 III 藥水(15 秒)" - lore = ["粗製藥水 + 腐肉 = 飢餓藥水(30 秒)", "虛弱藥水 + 腐肉 = 飢餓 III 藥水(15 秒)"] +name = "瓶裝飢餓" +description = "餵飽那些永不滿足的人!" +lore1 = "粗製藥水 + 腐肉 = 飢餓藥水(30 秒)" +lore2 = "虛弱藥水 + 腐肉 = 飢餓 III 藥水(15 秒)" +lore = ["粗製藥水 + 腐肉 = 飢餓藥水(30 秒)", "虛弱藥水 + 腐肉 = 飢餓 III 藥水(15 秒)"] [brewing.nausea] - name = "瓶裝噁心" - description = "你真令我作嘔!" - lore1 = "粗製藥水 + 棕色蘑菇 = 噁心藥水(16 秒)" - lore2 = "粗製藥水 + 緋紅蕈菇 = 噁心 II 藥水(8 秒)" - lore = ["粗製藥水 + 棕色蘑菇 = 噁心藥水(16 秒)", "粗製藥水 + 緋紅蕈菇 = 噁心 II 藥水(8 秒)"] +name = "瓶裝噁心" +description = "你真令我作嘔!" +lore1 = "粗製藥水 + 棕色蘑菇 = 噁心藥水(16 秒)" +lore2 = "粗製藥水 + 緋紅蕈菇 = 噁心 II 藥水(8 秒)" +lore = ["粗製藥水 + 棕色蘑菇 = 噁心藥水(16 秒)", "粗製藥水 + 緋紅蕈菇 = 噁心 II 藥水(8 秒)"] [brewing.blindness] - name = "瓶裝失明" - description = "你真是個可怕的人……" - lore1 = "粗製藥水 + 墨囊 = 失明藥水(30 秒)" - lore2 = "粗製藥水 + 螢光墨囊 = 失明 II 藥水(15 秒)" - lore = ["粗製藥水 + 墨囊 = 失明藥水(30 秒)", "粗製藥水 + 螢光墨囊 = 失明 II 藥水(15 秒)"] +name = "瓶裝失明" +description = "你真是個可怕的人……" +lore1 = "粗製藥水 + 墨囊 = 失明藥水(30 秒)" +lore2 = "粗製藥水 + 螢光墨囊 = 失明 II 藥水(15 秒)" +lore = ["粗製藥水 + 墨囊 = 失明藥水(30 秒)", "粗製藥水 + 螢光墨囊 = 失明 II 藥水(15 秒)"] [brewing.resistance] - name = "瓶裝抗性" - description = "最強防禦!" - lore1 = "粗製藥水 + 鐵錠 = 抗性提升藥水(60 秒)" - lore2 = "粗製藥水 + 鐵方塊 = 抗性提升 II 藥水(30 秒)" - lore = ["粗製藥水 + 鐵錠 = 抗性提升藥水(60 秒)", "粗製藥水 + 鐵方塊 = 抗性提升 II 藥水(30 秒)"] +name = "瓶裝抗性" +description = "最強防禦!" +lore1 = "粗製藥水 + 鐵錠 = 抗性提升藥水(60 秒)" +lore2 = "粗製藥水 + 鐵方塊 = 抗性提升 II 藥水(30 秒)" +lore = ["粗製藥水 + 鐵錠 = 抗性提升藥水(60 秒)", "粗製藥水 + 鐵方塊 = 抗性提升 II 藥水(30 秒)"] [brewing.health_boost] - name = "瓶裝生命" - description = "當最大生命值不夠用的時候……" - lore1 = "瞬間治療藥水 + 金蘋果 = 生命提升藥水(120 秒)" - lore2 = "瞬間治療藥水 + 附魔金蘋果 = 生命提升 II 藥水(120 秒)" - lore = ["瞬間治療藥水 + 金蘋果 = 生命提升藥水(120 秒)", "瞬間治療藥水 + 附魔金蘋果 = 生命提升 II 藥水(120 秒)"] +name = "瓶裝生命" +description = "當最大生命值不夠用的時候……" +lore1 = "瞬間治療藥水 + 金蘋果 = 生命提升藥水(120 秒)" +lore2 = "瞬間治療藥水 + 附魔金蘋果 = 生命提升 II 藥水(120 秒)" +lore = ["瞬間治療藥水 + 金蘋果 = 生命提升藥水(120 秒)", "瞬間治療藥水 + 附魔金蘋果 = 生命提升 II 藥水(120 秒)"] [brewing.decay] - name = "瓶裝衰敗" - description = "誰知道腐質竟然這麼有用?" - lore1 = "虛弱藥水 + 毒馬鈴薯 = 凋零藥水(16 秒)" - lore2 = "虛弱藥水 + 緋紅蕈根 = 凋零 II 藥水(8 秒)" - lore = ["虛弱藥水 + 毒馬鈴薯 = 凋零藥水(16 秒)", "虛弱藥水 + 緋紅蕈根 = 凋零 II 藥水(8 秒)"] +name = "瓶裝衰敗" +description = "誰知道腐質竟然這麼有用?" +lore1 = "虛弱藥水 + 毒馬鈴薯 = 凋零藥水(16 秒)" +lore2 = "虛弱藥水 + 緋紅蕈根 = 凋零 II 藥水(8 秒)" +lore = ["虛弱藥水 + 毒馬鈴薯 = 凋零藥水(16 秒)", "虛弱藥水 + 緋紅蕈根 = 凋零 II 藥水(8 秒)"] [brewing.saturation] - name = "瓶裝飽和" - description = "你知道嗎……其實我還不太餓……" - lore1 = "再生藥水 + 烤馬鈴薯 = 飽和藥水" - lore2 = "再生藥水 + 乾草塊 = 飽和 II 藥水" - lore = ["再生藥水 + 烤馬鈴薯 = 飽和藥水", "再生藥水 + 乾草塊 = 飽和 II 藥水"] +name = "瓶裝飽和" +description = "你知道嗎……其實我還不太餓……" +lore1 = "再生藥水 + 烤馬鈴薯 = 飽和藥水" +lore2 = "再生藥水 + 乾草塊 = 飽和 II 藥水" +lore = ["再生藥水 + 烤馬鈴薯 = 飽和藥水", "再生藥水 + 乾草塊 = 飽和 II 藥水"] # blocking [blocking] [blocking.chain_armorer] - name = "梅菲斯特的鎖鏈" - description = "允許你合成鎖鏈甲" - lore1 = "合成配方與其他盔甲相同,但使用鐵粒替代" - lore = ["合成配方與其他盔甲相同,但使用鐵粒替代"] +name = "梅菲斯特的鎖鏈" +description = "允許你合成鎖鏈甲" +lore1 = "合成配方與其他盔甲相同,但使用鐵粒替代" +lore = ["合成配方與其他盔甲相同,但使用鐵粒替代"] [blocking.horse_armorer] - name = "可合成馬鎧" - description = "允許你合成馬鎧" - lore1 = "在工作台中用所需材料圍繞馬鞍以合成該材質的馬鎧" - lore = ["在工作台中用所需材料圍繞馬鞍以合成該材質的馬鎧"] +name = "可合成馬鎧" +description = "允許你合成馬鎧" +lore1 = "在工作台中用所需材料圍繞馬鞍以合成該材質的馬鎧" +lore = ["在工作台中用所需材料圍繞馬鞍以合成該材質的馬鎧"] [blocking.saddle_crafter] - name = "可合成馬鞍" - description = "用皮革合成馬鞍" - lore1 = "配方:5 塊皮革:" - lore = ["配方:5 塊皮革:"] +name = "可合成馬鞍" +description = "用皮革合成馬鞍" +lore1 = "配方:5 塊皮革:" +lore = ["配方:5 塊皮革:"] [blocking.multi_armor] - name = "複合盔甲" - description = "將鞘翅綁定到盔甲上" - lore1 = "這是旅行的絕佳技能。" - lore2 = "飛行中動態合併及切換盔甲/鞘翅!" - lore3 = "要合併,在物品欄中按住 Shift 點擊一個物品到另一個物品上。" - lore4 = "要拆解盔甲,潛行時丟棄物品即可拆解。" - lore5 = "如果你的複合盔甲被摧毀,裡面所有物品都會丟失。" - lore6 = "我不需要盔甲,它令我失望……" - lore = ["這是旅行的絕佳技能。", "飛行中動態合併及切換盔甲/鞘翅!", "要合併,在物品欄中按住 Shift 點擊一個物品到另一個物品上。", "要拆解盔甲,潛行時丟棄物品即可拆解。", "如果你的複合盔甲被摧毀,裡面所有物品都會丟失。", "我不需要盔甲,它令我失望……"] +name = "複合盔甲" +description = "將鞘翅綁定到盔甲上" +lore1 = "這是旅行的絕佳技能。" +lore2 = "飛行中動態合併及切換盔甲/鞘翅!" +lore3 = "要合併,在物品欄中按住 Shift 點擊一個物品到另一個物品上。" +lore4 = "要拆解盔甲,潛行時丟棄物品即可拆解。" +lore5 = "如果你的複合盔甲被摧毀,裡面所有物品都會丟失。" +lore6 = "我不需要盔甲,它令我失望……" +lore = ["這是旅行的絕佳技能。", "飛行中動態合併及切換盔甲/鞘翅!", "要合併,在物品欄中按住 Shift 點擊一個物品到另一個物品上。", "要拆解盔甲,潛行時丟棄物品即可拆解。", "如果你的複合盔甲被摧毀,裡面所有物品都會丟失。", "我不需要盔甲,它令我失望……"] # crafting [crafting] [crafting.deconstruction] - name = "拆解" - description = "將方塊和物品拆解為可回收的基礎材料!" - lore1 = "將任何物品丟在地上。" - lore2 = "然後,潛行並用剪刀右鍵點擊" - lore = ["將任何物品丟在地上。", "然後,潛行並用剪刀右鍵點擊"] +name = "拆解" +description = "將方塊和物品拆解為可回收的基礎材料!" +lore1 = "將任何物品丟在地上。" +lore2 = "然後,潛行並用剪刀右鍵點擊" +lore = ["將任何物品丟在地上。", "然後,潛行並用剪刀右鍵點擊"] [crafting.xp] - name = "合成經驗" - description = "合成時獲得被動經驗" - lore1 = "合成時獲得經驗" - lore = ["合成時獲得經驗"] +name = "合成經驗" +description = "合成時獲得被動經驗" +lore1 = "合成時獲得經驗" +lore = ["合成時獲得經驗"] [crafting.reconstruction] - name = "礦石重建" - description = "用基礎材料重新合成礦石!" - lore1 = "8 個掉落物 + 1 個載體 = 1 個礦石(無序合成)" - lore2 = "掉落物必須先熔煉(如果適用)" - lore3 = "不包括:殘骸、石英、綠寶石等……" - lore4 = "載體 = 外殼方塊。例如:石頭、地獄石、深板岩" - lore = ["8 個掉落物 + 1 個載體 = 1 個礦石(無序合成)", "掉落物必須先熔煉(如果適用)", "不包括:殘骸、石英、綠寶石等……", "載體 = 外殼方塊。例如:石頭、地獄石、深板岩"] +name = "礦石重建" +description = "用基礎材料重新合成礦石!" +lore1 = "8 個掉落物 + 1 個載體 = 1 個礦石(無序合成)" +lore2 = "掉落物必須先熔煉(如果適用)" +lore3 = "不包括:殘骸、石英、綠寶石等……" +lore4 = "載體 = 外殼方塊。例如:石頭、地獄石、深板岩" +lore = ["8 個掉落物 + 1 個載體 = 1 個礦石(無序合成)", "掉落物必須先熔煉(如果適用)", "不包括:殘骸、石英、綠寶石等……", "載體 = 外殼方塊。例如:石頭、地獄石、深板岩"] [crafting.leather] - name = "可合成皮革" - description = "用腐肉製作皮革" - lore1 = "只需要把腐肉丟到營火上!" - lore = ["只需要把腐肉丟到營火上!"] +name = "可合成皮革" +description = "用腐肉製作皮革" +lore1 = "只需要把腐肉丟到營火上!" +lore = ["只需要把腐肉丟到營火上!"] [crafting.backpacks] - name = "布蒂利埃的背包!" - description = "將 Mojang 的收納袋功能加入遊戲!" - lore1 = "你需要在生存模式下才能使用" - lore2 = "XLX:皮革、拴繩、皮革" - lore3 = "XSX:皮革、木桶、皮革" - lore4 = "XCX:皮革、箱子、皮革" - lore = ["你需要在生存模式下才能使用", "XLX:皮革、拴繩、皮革", "XSX:皮革、木桶、皮革", "XCX:皮革、箱子、皮革"] +name = "布蒂利埃的背包!" +description = "將 Mojang 的收納袋功能加入遊戲!" +lore1 = "你需要在生存模式下才能使用" +lore2 = "XLX:皮革、拴繩、皮革" +lore3 = "XSX:皮革、木桶、皮革" +lore4 = "XCX:皮革、箱子、皮革" +lore = ["你需要在生存模式下才能使用", "XLX:皮革、拴繩、皮革", "XSX:皮革、木桶、皮革", "XCX:皮革、箱子、皮革"] [crafting.stations] - name = "便攜工作台!" - description = "在手掌中使用工作台!" - lore2 = "關閉時遺忘在工作台裡的任何物品都會永遠丟失!" - lore3 = "有效工作台:鐵砧、工作台、砂輪、製圖台、切石機、織布機" - lore = ["關閉時遺忘在工作台裡的任何物品都會永遠丟失!", "有效工作台:鐵砧、工作台、砂輪、製圖台、切石機、織布機"] +name = "便攜工作台!" +description = "在手掌中使用工作台!" +lore2 = "關閉時遺忘在工作台裡的任何物品都會永遠丟失!" +lore3 = "有效工作台:鐵砧、工作台、砂輪、製圖台、切石機、織布機" +lore = ["關閉時遺忘在工作台裡的任何物品都會永遠丟失!", "有效工作台:鐵砧、工作台、砂輪、製圖台、切石機、織布機"] [crafting.skulls] - name = "可合成頭顱!" - description = "用材料可以合成生物頭顱!" - lore1 = "用以下物品圍繞骨塊以獲得對應頭顱:" - lore2 = "殭屍:腐肉" - lore3 = "骷髏:骨頭" - lore4 = "苦力怕:火藥" - lore5 = "凋零骷髏:地獄磚" - lore6 = "龍首:龍息" - lore = ["用以下物品圍繞骨塊以獲得對應頭顱:", "殭屍:腐肉", "骷髏:骨頭", "苦力怕:火藥", "凋零骷髏:地獄磚", "龍首:龍息"] +name = "可合成頭顱!" +description = "用材料可以合成生物頭顱!" +lore1 = "用以下物品圍繞骨塊以獲得對應頭顱:" +lore2 = "殭屍:腐肉" +lore3 = "骷髏:骨頭" +lore4 = "苦力怕:火藥" +lore5 = "凋零骷髏:地獄磚" +lore6 = "龍首:龍息" +lore = ["用以下物品圍繞骨塊以獲得對應頭顱:", "殭屍:腐肉", "骷髏:骨頭", "苦力怕:火藥", "凋零骷髏:地獄磚", "龍首:龍息"] # chronos [chronos] [chronos.time_in_a_bottle] - name = "時光之瓶" - description = "隨身攜帶的時間之瓶會儲存時間,消耗儲存的時間以加速計時方塊、可生長物和幼年動物等可成長實體。配方(無序):迅捷藥水 + 時鐘 + 玻璃瓶。" - lore1 = "每刻充能的儲存秒數" - lore2 = "每儲存秒數的時間加速" - lore3 = "配方(無序):迅捷藥水 + 時鐘 + 玻璃瓶" - lore = ["每刻充能的儲存秒數", "每儲存秒數的時間加速", "配方(無序):迅捷藥水 + 時鐘 + 玻璃瓶"] +name = "時光之瓶" +description = "隨身攜帶的時間之瓶會儲存時間,消耗儲存的時間以加速計時方塊、可生長物和幼年動物等可成長實體。配方(無序):迅捷藥水 + 時鐘 + 玻璃瓶。" +lore1 = "每刻充能的儲存秒數" +lore2 = "每儲存秒數的時間加速" +lore3 = "配方(無序):迅捷藥水 + 時鐘 + 玻璃瓶" +lore = ["每刻充能的儲存秒數", "每儲存秒數的時間加速", "配方(無序):迅捷藥水 + 時鐘 + 玻璃瓶"] [chronos.aberrant_touch] - name = "異常觸碰" - description = "近戰攻擊以飢餓為代價施加疊加緩速效果,PvP 有嚴格上限,5 層時定身目標。" - lore1 = "近戰攻擊施加疊加緩速" - lore2 = "PvE 緩速持續時間上限" - lore3 = "PvP 緩速強度上限" - lore = ["近戰攻擊施加疊加緩速", "PvE 緩速持續時間上限", "PvP 緩速強度上限"] +name = "異常觸碰" +description = "近戰攻擊以飢餓為代價施加疊加緩速效果,PvP 有嚴格上限,5 層時定身目標。" +lore1 = "近戰攻擊施加疊加緩速" +lore2 = "PvE 緩速持續時間上限" +lore3 = "PvP 緩速強度上限" +lore = ["近戰攻擊施加疊加緩速", "PvE 緩速持續時間上限", "PvP 緩速強度上限"] [chronos.instant_recall] - name = "瞬間回溯" - description = "手持時鐘左鍵或右鍵點擊以回溯到最近的快照,恢復生命值和飢餓值。" - lore1 = "回溯時長" - lore2 = "冷卻時間" - lore3 = "不回溯物品欄" - lore = ["回溯時長", "冷卻時間", "不回溯物品欄"] +name = "瞬間回溯" +description = "手持時鐘左鍵或右鍵點擊以回溯到最近的快照,恢復生命值和飢餓值。" +lore1 = "回溯時長" +lore2 = "冷卻時間" +lore3 = "不回溯物品欄" +lore = ["回溯時長", "冷卻時間", "不回溯物品欄"] [chronos.time_bomb] - name = "時間炸彈" - description = "投擲合成的時空炸彈,產生時間力場,減速實體並凍結投射物。" - lore1 = "時間力場半徑" - lore2 = "時間力場持續時間" - lore3 = "炸彈冷卻時間" - lore4 = "配方(無序):時鐘 + 雪球 + 鑽石 + 沙子" - lore = ["時間力場半徑", "時間力場持續時間", "炸彈冷卻時間", "配方(無序):時鐘 + 雪球 + 鑽石 + 沙子"] +name = "時間炸彈" +description = "投擲合成的時空炸彈,產生時間力場,減速實體並凍結投射物。" +lore1 = "時間力場半徑" +lore2 = "時間力場持續時間" +lore3 = "炸彈冷卻時間" +lore4 = "配方(無序):時鐘 + 雪球 + 鑽石 + 沙子" +lore = ["時間力場半徑", "時間力場持續時間", "炸彈冷卻時間", "配方(無序):時鐘 + 雪球 + 鑽石 + 沙子"] # discovery [discovery] [discovery.armor] - name = "世界之甲" - description = "根據附近方塊硬度獲得被動護甲。" - lore1 = "被動護甲" - lore2 = "取決於附近方塊硬度" - lore3 = "護甲強度:" - lore = ["被動護甲", "取決於附近方塊硬度", "護甲強度:"] +name = "世界之甲" +description = "根據附近方塊硬度獲得被動護甲。" +lore1 = "被動護甲" +lore2 = "取決於附近方塊硬度" +lore3 = "護甲強度:" +lore = ["被動護甲", "取決於附近方塊硬度", "護甲強度:"] [discovery.unity] - name = "實驗性統合" - description = "拾取經驗球時為隨機技能增加經驗。" - lore1 = "經驗 " - lore2 = "每個經驗球" - lore = ["經驗 ", "每個經驗球"] +name = "實驗性統合" +description = "拾取經驗球時為隨機技能增加經驗。" +lore1 = "經驗 " +lore2 = "每個經驗球" +lore = ["經驗 ", "每個經驗球"] [discovery.resist] - name = "實驗性抗性" - description = "消耗經驗來減免傷害,僅在攻擊會讓你降至 5 顆心以下或致死時觸發。" - lore0 = "僅在危急生命值(<= 5 顆心)時觸發,每 15 秒一次" - lore1 = " 減免傷害" - lore2 = "經驗消耗" - lore = ["僅在危急生命值(<= 5 顆心)時觸發,每 15 秒一次", " 減免傷害", "經驗消耗"] +name = "實驗性抗性" +description = "消耗經驗來減免傷害,僅在攻擊會讓你降至 5 顆心以下或致死時觸發。" +lore0 = "僅在危急生命值(<= 5 顆心)時觸發,每 15 秒一次" +lore1 = " 減免傷害" +lore2 = "經驗消耗" +lore = ["僅在危急生命值(<= 5 顆心)時觸發,每 15 秒一次", " 減免傷害", "經驗消耗"] [discovery.villager] - name = "村民吸引力" - description = "與村民交易時獲得更好的交易!" - lore1 = "每次與村民互動時消耗經驗" - lore2 = "每次互動有機率消耗經驗以增強交易" - lore3 = "每次互動所需經驗消耗" - lore = ["每次與村民互動時消耗經驗", "每次互動有機率消耗經驗以增強交易", "每次互動所需經驗消耗"] +name = "村民吸引力" +description = "與村民交易時獲得更好的交易!" +lore1 = "每次與村民互動時消耗經驗" +lore2 = "每次互動有機率消耗經驗以增強交易" +lore3 = "每次互動所需經驗消耗" +lore = ["每次與村民互動時消耗經驗", "每次互動有機率消耗經驗以增強交易", "每次互動所需經驗消耗"] # enchanting [enchanting] [enchanting.lapis_return] - name = "青金石回饋" - description = "多消耗 1 級經驗,但有機率免費返還青金石" - lore1 = "每升一級,附魔花費增加 1 級,但最多可返還 3 個青金石" - lore = ["每升一級,附魔花費增加 1 級,但最多可返還 3 個青金石"] +name = "青金石回饋" +description = "多消耗 1 級經驗,但有機率免費返還青金石" +lore1 = "每升一級,附魔花費增加 1 級,但最多可返還 3 個青金石" +lore = ["每升一級,附魔花費增加 1 級,但最多可返還 3 個青金石"] [enchanting.quick_enchant] - name = "快速附魔" - description = "直接用附魔書點擊物品來附魔。" - lore1 = "最大合併等級" - lore2 = "無法對超過 " - lore3 = "能力的物品附魔" - lore = ["最大合併等級", "無法對超過 ", "能力的物品附魔"] +name = "快速附魔" +description = "直接用附魔書點擊物品來附魔。" +lore1 = "最大合併等級" +lore2 = "無法對超過 " +lore3 = "能力的物品附魔" +lore = ["最大合併等級", "無法對超過 ", "能力的物品附魔"] [enchanting.return] - name = "經驗返還" - description = "附魔物品時返還部分經驗。" - lore1 = "附魔消耗的經驗有機率被退還" - lore2 = "每次附魔返還經驗" - lore = ["附魔消耗的經驗有機率被退還", "每次附魔返還經驗"] +name = "經驗返還" +description = "附魔物品時返還部分經驗。" +lore1 = "附魔消耗的經驗有機率被退還" +lore2 = "每次附魔返還經驗" +lore = ["附魔消耗的經驗有機率被退還", "每次附魔返還經驗"] # excavation [excavation] [excavation.haste] - name = "急迫挖掘者" - description = "挖掘時獲得急迫效果,加速挖掘過程!" - lore1 = "挖掘時獲得急迫效果" - lore2 = "x 級急迫效果(開始挖掘任何方塊時觸發)" - lore = ["挖掘時獲得急迫效果", "x 級急迫效果(開始挖掘任何方塊時觸發)"] +name = "急迫挖掘者" +description = "挖掘時獲得急迫效果,加速挖掘過程!" +lore1 = "挖掘時獲得急迫效果" +lore2 = "x 級急迫效果(開始挖掘任何方塊時觸發)" +lore = ["挖掘時獲得急迫效果", "x 級急迫效果(開始挖掘任何方塊時觸發)"] [excavation.spelunker] - name = "超級透視洞穴探險家!" - description = "透過地面看見礦石!" - lore1 = "副手持礦石,主手持螢光漿果,然後潛行!" - lore2 = "方塊範圍:" - lore3 = "使用時消耗螢光漿果" - lore = ["副手持礦石,主手持螢光漿果,然後潛行!", "方塊範圍:", "使用時消耗螢光漿果"] +name = "超級透視洞穴探險家!" +description = "透過地面看見礦石!" +lore1 = "副手持礦石,主手持螢光漿果,然後潛行!" +lore2 = "方塊範圍:" +lore3 = "使用時消耗螢光漿果" +lore = ["副手持礦石,主手持螢光漿果,然後潛行!", "方塊範圍:", "使用時消耗螢光漿果"] [excavation.drop_to_inventory] - name = "鏟子掉落物自動入包" +name = "鏟子掉落物自動入包" [excavation.omni_tool] - name = "OMNI - T.O.O.L." - description = "Tackle 精心設計的豪華萬能工具" - lore1 = "可能是最強大的功能之一,允許你" - lore2 = "根據需求動態合併及切換工具。" - lore3 = "要合併,在物品欄中按住 Shift 點擊一個物品到另一個物品上。" - lore4 = "要拆解工具,潛行時丟棄物品即可拆解。" - lore5 = "萬能工具中的工具不會損壞,但壞掉的工具無法使用" - lore6 = "個可合併物品。" - lore7 = "你可以帶五六個工具,或者只帶這一個!" - lore = ["可能是最強大的功能之一,允許你", "根據需求動態合併及切換工具。", "要合併,在物品欄中按住 Shift 點擊一個物品到另一個物品上。", "要拆解工具,潛行時丟棄物品即可拆解。", "萬能工具中的工具不會損壞,但壞掉的工具無法使用", "個可合併物品。", "你可以帶五六個工具,或者只帶這一個!"] +name = "OMNI - T.O.O.L." +description = "Tackle 精心設計的豪華萬能工具" +lore1 = "可能是最強大的功能之一,允許你" +lore2 = "根據需求動態合併及切換工具。" +lore3 = "要合併,在物品欄中按住 Shift 點擊一個物品到另一個物品上。" +lore4 = "要拆解工具,潛行時丟棄物品即可拆解。" +lore5 = "萬能工具中的工具不會損壞,但壞掉的工具無法使用" +lore6 = "個可合併物品。" +lore7 = "你可以帶五六個工具,或者只帶這一個!" +lore = ["可能是最強大的功能之一,允許你", "根據需求動態合併及切換工具。", "要合併,在物品欄中按住 Shift 點擊一個物品到另一個物品上。", "要拆解工具,潛行時丟棄物品即可拆解。", "萬能工具中的工具不會損壞,但壞掉的工具無法使用", "個可合併物品。", "你可以帶五六個工具,或者只帶這一個!"] # herbalism [herbalism] [herbalism.growth_aura] - name = "生長光環" - description = "在你周圍的光環中加速自然生長" - lore1 = "方塊半徑" - lore2 = "生長光環強度" - lore3 = "食物消耗" - lore = ["方塊半徑", "生長光環強度", "食物消耗"] +name = "生長光環" +description = "在你周圍的光環中加速自然生長" +lore1 = "方塊半徑" +lore2 = "生長光環強度" +lore3 = "食物消耗" +lore = ["方塊半徑", "生長光環強度", "食物消耗"] [herbalism.hippo] - name = "草藥師的河馬" - description = "食用食物時獲得更多飽和度" - lore1 = "食物)食用時額外飽和度" - lore = ["食物)食用時額外飽和度"] +name = "草藥師的河馬" +description = "食用食物時獲得更多飽和度" +lore1 = "食物)食用時額外飽和度" +lore = ["食物)食用時額外飽和度"] [herbalism.myconid] - name = "草藥師的菌絲體" - description = "賦予你合成菌絲的能力" - lore1 = "任意泥土 + 棕色蘑菇 + 紅色蘑菇即可合成菌絲。" - lore = ["任意泥土 + 棕色蘑菇 + 紅色蘑菇即可合成菌絲。"] +name = "草藥師的菌絲體" +description = "賦予你合成菌絲的能力" +lore1 = "任意泥土 + 棕色蘑菇 + 紅色蘑菇即可合成菌絲。" +lore = ["任意泥土 + 棕色蘑菇 + 紅色蘑菇即可合成菌絲。"] [herbalism.terralid] - name = "草藥師的地衣" - description = "賦予你合成草地方塊的能力" - lore1 = "3 個種子放在 3 塊泥土上方,即可合成 3 塊草地方塊。" - lore = ["3 個種子放在 3 塊泥土上方,即可合成 3 塊草地方塊。"] +name = "草藥師的地衣" +description = "賦予你合成草地方塊的能力" +lore1 = "3 個種子放在 3 塊泥土上方,即可合成 3 塊草地方塊。" +lore = ["3 個種子放在 3 塊泥土上方,即可合成 3 塊草地方塊。"] [herbalism.cobweb] - name = "蛛網織者" - description = "賦予你在工作台中合成蛛網的能力" - lore1 = "9 根線即可合成一個蛛網。" - lore = ["9 根線即可合成一個蛛網。"] +name = "蛛網織者" +description = "賦予你在工作台中合成蛛網的能力" +lore1 = "9 根線即可合成一個蛛網。" +lore = ["9 根線即可合成一個蛛網。"] [herbalism.mushroom_blocks] - name = "蘑菇製造者" - description = "賦予你在工作台中合成蘑菇方塊的能力" - lore1 = "4 個蘑菇合成一個蘑菇塊,或一個蘑菇塊合成蘑菇柄。" - lore = ["4 個蘑菇合成一個蘑菇塊,或一個蘑菇塊合成蘑菇柄。"] +name = "蘑菇製造者" +description = "賦予你在工作台中合成蘑菇方塊的能力" +lore1 = "4 個蘑菇合成一個蘑菇塊,或一個蘑菇塊合成蘑菇柄。" +lore = ["4 個蘑菇合成一個蘑菇塊,或一個蘑菇塊合成蘑菇柄。"] [herbalism.drop_to_inventory] - name = "鋤頭掉落物自動入包" +name = "鋤頭掉落物自動入包" [herbalism.hungry_shield] - name = "飢餓護盾" - description = "受到傷害時先消耗飢餓值而非生命值。" - lore1 = "飢餓抵抗傷害" - lore = ["飢餓抵抗傷害"] +name = "飢餓護盾" +description = "受到傷害時先消耗飢餓值而非生命值。" +lore1 = "飢餓抵抗傷害" +lore = ["飢餓抵抗傷害"] [herbalism.luck] - name = "草藥師的幸運" - description = "破壞草/花時有機率獲得隨機物品" - lore0 = "花 = 食物,草 = 種子" - lore1 = "破壞花時獲得物品的機率" - lore2 = "破壞草時獲得物品的機率" - lore = ["花 = 食物,草 = 種子", "破壞花時獲得物品的機率", "破壞草時獲得物品的機率"] +name = "草藥師的幸運" +description = "破壞草/花時有機率獲得隨機物品" +lore0 = "花 = 食物,草 = 種子" +lore1 = "破壞花時獲得物品的機率" +lore2 = "破壞草時獲得物品的機率" +lore = ["花 = 食物,草 = 種子", "破壞花時獲得物品的機率", "破壞草時獲得物品的機率"] [herbalism.replant] - name = "收割與重種" - description = "手持鋤頭右鍵點擊作物以收割並重新種植。" - lore1 = "重種範圍半徑" - lore = ["重種範圍半徑"] +name = "收割與重種" +description = "手持鋤頭右鍵點擊作物以收割並重新種植。" +lore1 = "重種範圍半徑" +lore = ["重種範圍半徑"] # hunter [hunter] [hunter.adrenaline] - name = "腎上腺素" - description = "生命值越低,近戰傷害越高" - lore1 = "最大傷害" - lore = ["最大傷害"] +name = "腎上腺素" +description = "生命值越低,近戰傷害越高" +lore1 = "最大傷害" +lore = ["最大傷害"] [hunter.penalty] - name = "" - description = "" - lore1 = "飢餓值耗盡時會獲得中毒效果" - lore = ["飢餓值耗盡時會獲得中毒效果"] +name = "" +description = "" +lore1 = "飢餓值耗盡時會獲得中毒效果" +lore = ["飢餓值耗盡時會獲得中毒效果"] [hunter.drop_to_inventory] - name = "物品自動入包" - description = "擊殺生物或用劍破壞方塊時,掉落物傳送到你的物品欄" - lore1 = "生物/方塊掉落的物品會自動進入你的物品欄(如果有空間)。" - lore = ["生物/方塊掉落的物品會自動進入你的物品欄(如果有空間)。"] +name = "物品自動入包" +description = "擊殺生物或用劍破壞方塊時,掉落物傳送到你的物品欄" +lore1 = "生物/方塊掉落的物品會自動進入你的物品欄(如果有空間)。" +lore = ["生物/方塊掉落的物品會自動進入你的物品欄(如果有空間)。"] [hunter.invisibility] - name = "隱形之步" - description = "受到攻擊時獲得隱身效果,以飢餓為代價" - lore1 = "受擊時獲得被動隱身效果" - lore2 = "x 隱身效果疊加,受擊後持續 3 秒" - lore3 = "x 疊加飢餓" - lore4 = "飢餓疊加持續時間和倍率。" - lore5 = "隱身持續時間" - lore = ["受擊時獲得被動隱身效果", "x 隱身效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "隱身持續時間"] +name = "隱形之步" +description = "受到攻擊時獲得隱身效果,以飢餓為代價" +lore1 = "受擊時獲得被動隱身效果" +lore2 = "x 隱身效果疊加,受擊後持續 3 秒" +lore3 = "x 疊加飢餓" +lore4 = "飢餓疊加持續時間和倍率。" +lore5 = "隱身持續時間" +lore = ["受擊時獲得被動隱身效果", "x 隱身效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "隱身持續時間"] [hunter.jump_boost] - name = "獵人之巔" - description = "受到攻擊時獲得跳躍提升效果,以飢餓為代價" - lore1 = "受擊時獲得被動跳躍提升效果" - lore2 = "x 跳躍提升效果疊加,受擊後持續 3 秒" - lore3 = "x 疊加飢餓" - lore4 = "飢餓疊加持續時間和倍率。" - lore5 = "跳躍提升疊加倍率,非持續時間。" - lore = ["受擊時獲得被動跳躍提升效果", "x 跳躍提升效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "跳躍提升疊加倍率,非持續時間。"] +name = "獵人之巔" +description = "受到攻擊時獲得跳躍提升效果,以飢餓為代價" +lore1 = "受擊時獲得被動跳躍提升效果" +lore2 = "x 跳躍提升效果疊加,受擊後持續 3 秒" +lore3 = "x 疊加飢餓" +lore4 = "飢餓疊加持續時間和倍率。" +lore5 = "跳躍提升疊加倍率,非持續時間。" +lore = ["受擊時獲得被動跳躍提升效果", "x 跳躍提升效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "跳躍提升疊加倍率,非持續時間。"] [hunter.luck] - name = "獵人的幸運" - description = "受到攻擊時獲得幸運效果,以飢餓為代價" - lore1 = "受擊時獲得被動幸運效果" - lore2 = "x 幸運效果疊加,受擊後持續 3 秒" - lore3 = "x 疊加飢餓" - lore4 = "飢餓疊加持續時間和倍率。" - lore5 = "幸運疊加倍率,非持續時間。" - lore = ["受擊時獲得被動幸運效果", "x 幸運效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "幸運疊加倍率,非持續時間。"] +name = "獵人的幸運" +description = "受到攻擊時獲得幸運效果,以飢餓為代價" +lore1 = "受擊時獲得被動幸運效果" +lore2 = "x 幸運效果疊加,受擊後持續 3 秒" +lore3 = "x 疊加飢餓" +lore4 = "飢餓疊加持續時間和倍率。" +lore5 = "幸運疊加倍率,非持續時間。" +lore = ["受擊時獲得被動幸運效果", "x 幸運效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "幸運疊加倍率,非持續時間。"] [hunter.regen] - name = "獵人的再生" - description = "受到攻擊時獲得再生效果,以飢餓為代價" - lore1 = "受擊時獲得被動再生效果" - lore2 = "x 再生效果疊加,受擊後持續 3 秒" - lore3 = "x 疊加飢餓" - lore4 = "飢餓疊加持續時間和倍率。" - lore5 = "再生疊加倍率,非持續時間。" - lore = ["受擊時獲得被動再生效果", "x 再生效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "再生疊加倍率,非持續時間。"] +name = "獵人的再生" +description = "受到攻擊時獲得再生效果,以飢餓為代價" +lore1 = "受擊時獲得被動再生效果" +lore2 = "x 再生效果疊加,受擊後持續 3 秒" +lore3 = "x 疊加飢餓" +lore4 = "飢餓疊加持續時間和倍率。" +lore5 = "再生疊加倍率,非持續時間。" +lore = ["受擊時獲得被動再生效果", "x 再生效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "再生疊加倍率,非持續時間。"] [hunter.resistance] - name = "獵人的抗性" - description = "受到攻擊時獲得抗性提升效果,以飢餓為代價" - lore1 = "受擊時獲得被動抗性提升效果" - lore2 = "x 抗性提升效果疊加,受擊後持續 3 秒" - lore3 = "x 疊加飢餓" - lore4 = "飢餓疊加持續時間和倍率。" - lore5 = "抗性提升疊加倍率,非持續時間。" - lore = ["受擊時獲得被動抗性提升效果", "x 抗性提升效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "抗性提升疊加倍率,非持續時間。"] +name = "獵人的抗性" +description = "受到攻擊時獲得抗性提升效果,以飢餓為代價" +lore1 = "受擊時獲得被動抗性提升效果" +lore2 = "x 抗性提升效果疊加,受擊後持續 3 秒" +lore3 = "x 疊加飢餓" +lore4 = "飢餓疊加持續時間和倍率。" +lore5 = "抗性提升疊加倍率,非持續時間。" +lore = ["受擊時獲得被動抗性提升效果", "x 抗性提升效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "抗性提升疊加倍率,非持續時間。"] [hunter.speed] - name = "獵人的速度" - description = "受到攻擊時獲得速度效果,以飢餓為代價" - lore1 = "受擊時獲得被動速度效果" - lore2 = "x 速度效果疊加,受擊後持續 3 秒" - lore3 = "x 疊加飢餓" - lore4 = "飢餓疊加持續時間和倍率。" - lore5 = "速度疊加倍率,非持續時間。" - lore = ["受擊時獲得被動速度效果", "x 速度效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "速度疊加倍率,非持續時間。"] +name = "獵人的速度" +description = "受到攻擊時獲得速度效果,以飢餓為代價" +lore1 = "受擊時獲得被動速度效果" +lore2 = "x 速度效果疊加,受擊後持續 3 秒" +lore3 = "x 疊加飢餓" +lore4 = "飢餓疊加持續時間和倍率。" +lore5 = "速度疊加倍率,非持續時間。" +lore = ["受擊時獲得被動速度效果", "x 速度效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "速度疊加倍率,非持續時間。"] [hunter.strength] - name = "獵人的力量" - description = "受到攻擊時獲得力量效果,以飢餓為代價" - lore1 = "受擊時獲得被動力量效果" - lore2 = "x 力量效果疊加,受擊後持續 3 秒" - lore3 = "x 疊加飢餓" - lore4 = "飢餓疊加持續時間和倍率。" - lore5 = "力量疊加倍率,非持續時間。" - lore = ["受擊時獲得被動力量效果", "x 力量效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "力量疊加倍率,非持續時間。"] +name = "獵人的力量" +description = "受到攻擊時獲得力量效果,以飢餓為代價" +lore1 = "受擊時獲得被動力量效果" +lore2 = "x 力量效果疊加,受擊後持續 3 秒" +lore3 = "x 疊加飢餓" +lore4 = "飢餓疊加持續時間和倍率。" +lore5 = "力量疊加倍率,非持續時間。" +lore = ["受擊時獲得被動力量效果", "x 力量效果疊加,受擊後持續 3 秒", "x 疊加飢餓", "飢餓疊加持續時間和倍率。", "力量疊加倍率,非持續時間。"] # nether [nether] [nether.skull_toss] - name = "凋零頭顱投擲" - description1 = "釋放你內心的凋零,使用" - description2 = "某人的" - description3 = "頭顱。" - lore1 = "頭顱投擲間的冷卻秒數。" - lore2 = "使用凋零骷髏頭:投擲" - lore3 = "凋零頭顱" - lore4 = "撞擊時爆炸。" - lore = ["頭顱投擲間的冷卻秒數。", "使用凋零骷髏頭:投擲", "凋零頭顱", "撞擊時爆炸。"] +name = "凋零頭顱投擲" +description1 = "釋放你內心的凋零,使用" +description2 = "某人的" +description3 = "頭顱。" +lore1 = "頭顱投擲間的冷卻秒數。" +lore2 = "使用凋零骷髏頭:投擲" +lore3 = "凋零頭顱" +lore4 = "撞擊時爆炸。" +lore = ["頭顱投擲間的冷卻秒數。", "使用凋零骷髏頭:投擲", "凋零頭顱", "撞擊時爆炸。"] [nether.wither_resist] - name = "凋零抗性" - description = "借助乖合金之力抵抗凋零效果。" - lore1 = "機率抵消凋零效果(每件裝備)。" - lore2 = "被動:穿著乖合金裝備有機率抵消" - lore3 = "凋零效果。" - lore = ["機率抵消凋零效果(每件裝備)。", "被動:穿著乖合金裝備有機率抵消", "凋零效果。"] +name = "凋零抗性" +description = "借助乖合金之力抵抗凋零效果。" +lore1 = "機率抵消凋零效果(每件裝備)。" +lore2 = "被動:穿著乖合金裝備有機率抵消" +lore3 = "凋零效果。" +lore = ["機率抵消凋零效果(每件裝備)。", "被動:穿著乖合金裝備有機率抵消", "凋零效果。"] [nether.fire_resist] - name = "火焰抗性" - description = "通過硬化皮膚來抵禦火焰。" - lore1 = "機率抵消燃燒效果!" - lore = ["機率抵消燃燒效果!"] +name = "火焰抗性" +description = "通過硬化皮膚來抵禦火焰。" +lore1 = "機率抵消燃燒效果!" +lore = ["機率抵消燃燒效果!"] # pickaxe [pickaxe] [pickaxe.auto_smelt] - name = "自動熔煉" - description = "允許你自動熔煉開採的原版礦石" - lore1 = "可熔煉的礦石會自動熔煉" - lore2 = "% 機率獲得額外產物" - lore = ["可熔煉的礦石會自動熔煉", "% 機率獲得額外產物"] +name = "自動熔煉" +description = "允許你自動熔煉開採的原版礦石" +lore1 = "可熔煉的礦石會自動熔煉" +lore2 = "% 機率獲得額外產物" +lore = ["可熔煉的礦石會自動熔煉", "% 機率獲得額外產物"] [pickaxe.chisel] - name = "礦石鑿子" - description = "右鍵點擊礦石以鑿出更多產物,但會大量消耗耐久。" - lore1 = "掉落機率" - lore2 = "工具磨損" - lore = ["掉落機率", "工具磨損"] +name = "礦石鑿子" +description = "右鍵點擊礦石以鑿出更多產物,但會大量消耗耐久。" +lore1 = "掉落機率" +lore2 = "工具磨損" +lore = ["掉落機率", "工具磨損"] [pickaxe.drop_to_inventory] - name = "鎬子掉落物自動入包" - description = "破壞方塊時掉落物自動傳送到你的物品欄" - lore1 = "方塊掉落的物品會自動進入你的物品欄(如果有空間)。" - lore = ["方塊掉落的物品會自動進入你的物品欄(如果有空間)。"] +name = "鎬子掉落物自動入包" +description = "破壞方塊時掉落物自動傳送到你的物品欄" +lore1 = "方塊掉落的物品會自動進入你的物品欄(如果有空間)。" +lore = ["方塊掉落的物品會自動進入你的物品欄(如果有空間)。"] [pickaxe.silk_spawner] - name = "鎬子絲綢刷怪籠" - description = "使刷怪籠被破壞時掉落" - lore1 = "使用絲綢觸感可以採集刷怪籠。" - lore2 = "潛行時可以破壞刷怪籠。" - lore = ["使用絲綢觸感可以採集刷怪籠。", "潛行時可以破壞刷怪籠。"] +name = "鎬子絲綢刷怪籠" +description = "使刷怪籠被破壞時掉落" +lore1 = "使用絲綢觸感可以採集刷怪籠。" +lore2 = "潛行時可以破壞刷怪籠。" +lore = ["使用絲綢觸感可以採集刷怪籠。", "潛行時可以破壞刷怪籠。"] [pickaxe.vein_miner] - name = "連鎖挖礦" - description = "允許你連鎖挖掘原版礦石的礦脈/群" - lore1 = "潛行並挖掘礦石" - lore2 = "連鎖挖礦範圍" - lore3 = "此技能不會合併所有掉落物!" - lore = ["潛行並挖掘礦石", "連鎖挖礦範圍", "此技能不會合併所有掉落物!"] +name = "連鎖挖礦" +description = "允許你連鎖挖掘原版礦石的礦脈/群" +lore1 = "潛行並挖掘礦石" +lore2 = "連鎖挖礦範圍" +lore3 = "此技能不會合併所有掉落物!" +lore = ["潛行並挖掘礦石", "連鎖挖礦範圍", "此技能不會合併所有掉落物!"] # ranged [ranged] [ranged.arrow_recovery] - name = "箭矢回收" - description = "擊殺敵人後回收箭矢。" - lore1 = "命中/擊殺時回收箭矢的機率" - lore2 = "機率:" - lore = ["命中/擊殺時回收箭矢的機率", "機率:"] +name = "箭矢回收" +description = "擊殺敵人後回收箭矢。" +lore1 = "命中/擊殺時回收箭矢的機率" +lore2 = "機率:" +lore = ["命中/擊殺時回收箭矢的機率", "機率:"] [ranged.web_shot] - name = "蛛網陷阱" - description = "命中目標時在其周圍產生蛛網!" - lore1 = "8 個蛛網圍繞 1 個雪球,然後投擲!" - lore2 = "秒的籠子,大約。" - lore = ["8 個蛛網圍繞 1 個雪球,然後投擲!", "秒的籠子,大約。"] +name = "蛛網陷阱" +description = "命中目標時在其周圍產生蛛網!" +lore1 = "8 個蛛網圍繞 1 個雪球,然後投擲!" +lore2 = "秒的籠子,大約。" +lore = ["8 個蛛網圍繞 1 個雪球,然後投擲!", "秒的籠子,大約。"] [ranged.force_shot] - name = "強力射擊" - description = "投射物飛得更遠、更快!" - advancementname = "遠距離射擊" - advancementlore = "從超過 30 格方塊外命中目標!" - lore1 = "投射物速度" - lore = ["投射物速度"] +name = "強力射擊" +description = "投射物飛得更遠、更快!" +advancementname = "遠距離射擊" +advancementlore = "從超過 30 格方塊外命中目標!" +lore1 = "投射物速度" +lore = ["投射物速度"] [ranged.lunge_shot] - name = "弓步射擊" - description = "下落時射箭會將你拋向隨機方向" - lore1 = "隨機爆發速度" - lore = ["隨機爆發速度"] +name = "弓步射擊" +description = "下落時射箭會將你拋向隨機方向" +lore1 = "隨機爆發速度" +lore = ["隨機爆發速度"] [ranged.arrow_piercing] - name = "箭矢穿透" - description = "為投射物添加穿透效果!穿越目標!" - lore1 = "穿透目標數" - lore = ["穿透目標數"] +name = "箭矢穿透" +description = "為投射物添加穿透效果!穿越目標!" +lore1 = "穿透目標數" +lore = ["穿透目標數"] # rift [rift] [rift.remote_access] - name = "遠程存取" - description = "從虛空中取物,存取已標記的容器。" - lore1 = "乖珍珠 + 指南針 = 亞空間之鑰" - lore2 = "此物品允許你遠程存取容器" - lore3 = "製作完成後查看物品以瞭解使用方法" - notcontainer = "那不是容器" - lore = ["乖珍珠 + 指南針 = 亞空間之鑰", "此物品允許你遠程存取容器", "製作完成後查看物品以瞭解使用方法"] +name = "遠程存取" +description = "從虛空中取物,存取已標記的容器。" +lore1 = "乖珍珠 + 指南針 = 亞空間之鑰" +lore2 = "此物品允許你遠程存取容器" +lore3 = "製作完成後查看物品以瞭解使用方法" +notcontainer = "那不是容器" +lore = ["乖珍珠 + 指南針 = 亞空間之鑰", "此物品允許你遠程存取容器", "製作完成後查看物品以瞭解使用方法"] [rift.blink] - name = "裂痕閃現" - description = "短距離瞬間傳送,只需一眨眼!" - lore1 = "閃現距離(垂直方向 2 倍)" - lore2 = "疾跑時:連按兩次跳躍以" - lore3 = "閃現" - lore = ["閃現距離(垂直方向 2 倍)", "疾跑時:連按兩次跳躍以", "閃現"] +name = "裂痕閃現" +description = "短距離瞬間傳送,只需一眨眼!" +lore1 = "閃現距離(垂直方向 2 倍)" +lore2 = "疾跑時:連按兩次跳躍以" +lore3 = "閃現" +lore = ["閃現距離(垂直方向 2 倍)", "疾跑時:連按兩次跳躍以", "閃現"] [rift.chest] - name = "便攜乖影箱" - description = "手持乖影箱左鍵點擊即可打開。" - lore1 = "手持乖影箱點擊即可打開(不用放置)" - lore = ["手持乖影箱點擊即可打開(不用放置)"] +name = "便攜乖影箱" +description = "手持乖影箱左鍵點擊即可打開。" +lore1 = "手持乖影箱點擊即可打開(不用放置)" +lore = ["手持乖影箱點擊即可打開(不用放置)"] [rift.descent] - name = "反漂浮" - description = "厭倦了被困在空中?這個技能就是為你準備的!" - lore1 = "只需潛行即可下降,且下降速度低於正常!" - lore2 = "冷卻:" - lore = ["只需潛行即可下降,且下降速度低於正常!", "冷卻:"] +name = "反漂浮" +description = "厭倦了被困在空中?這個技能就是為你準備的!" +lore1 = "只需潛行即可下降,且下降速度低於正常!" +lore2 = "冷卻:" +lore = ["只需潛行即可下降,且下降速度低於正常!", "冷卻:"] [rift.gate] - name = "裂痕之門" - description = "傳送到標記的位置。" - lore1 = "合成:綠寶石 + 紫水晶碎片 + 乖珍珠" - lore2 = "使用前請仔細閱讀!" - lore3 = "5 秒延遲," - lore4 = "在此動畫期間你可能會死亡" - lore = ["合成:綠寶石 + 紫水晶碎片 + 乖珍珠", "使用前請仔細閱讀!", "5 秒延遲,", "在此動畫期間你可能會死亡"] +name = "裂痕之門" +description = "傳送到標記的位置。" +lore1 = "合成:綠寶石 + 紫水晶碎片 + 乖珍珠" +lore2 = "使用前請仔細閱讀!" +lore3 = "5 秒延遲," +lore4 = "在此動畫期間你可能會死亡" +lore = ["合成:綠寶石 + 紫水晶碎片 + 乖珍珠", "使用前請仔細閱讀!", "5 秒延遲,", "在此動畫期間你可能會死亡"] [rift.resist] - name = "裂痕抗性" - description = "使用乖影物品或技能時獲得抗性提升" - lore1 = "+ 被動:使用裂痕技能或乖影物品時獲得抗性提升" - lore2 = "不包括便攜乖影箱,僅限可消耗的物品" - lore = ["+ 被動:使用裂痕技能或乖影物品時獲得抗性提升", "不包括便攜乖影箱,僅限可消耗的物品"] +name = "裂痕抗性" +description = "使用乖影物品或技能時獲得抗性提升" +lore1 = "+ 被動:使用裂痕技能或乖影物品時獲得抗性提升" +lore2 = "不包括便攜乖影箱,僅限可消耗的物品" +lore = ["+ 被動:使用裂痕技能或乖影物品時獲得抗性提升", "不包括便攜乖影箱,僅限可消耗的物品"] [rift.visage] - name = "裂痕面紗" - description = "物品欄中有乖珍珠時,終界使者不會對你產生敵意。" - lore1 = "物品欄中有乖珍珠時,終界使者不會對你產生敵意。" - lore = ["物品欄中有乖珍珠時,終界使者不會對你產生敵意。"] +name = "裂痕面紗" +description = "物品欄中有乖珍珠時,終界使者不會對你產生敵意。" +lore1 = "物品欄中有乖珍珠時,終界使者不會對你產生敵意。" +lore = ["物品欄中有乖珍珠時,終界使者不會對你產生敵意。"] # seaborn [seaborn] [seaborn.oxygen] - name = "有機氧氣罐" - description = "小肺也能容納更多氧氣!" - lore1 = "氧氣容量增加" - lore = ["氧氣容量增加"] +name = "有機氧氣罐" +description = "小肺也能容納更多氧氣!" +lore1 = "氧氣容量增加" +lore = ["氧氣容量增加"] [seaborn.fishers_fantasy] - name = "釣魚達人" - description = "釣魚時獲得更多經驗和更多魚!" - lore1 = "每升一級都有機率獲得更多經驗和魚!" - lore = ["每升一級都有機率獲得更多經驗和魚!"] +name = "釣魚達人" +description = "釣魚時獲得更多經驗和更多魚!" +lore1 = "每升一級都有機率獲得更多經驗和魚!" +lore = ["每升一級都有機率獲得更多經驗和魚!"] [seaborn.haste] - name = "海龜礦工" - description = "在水下挖掘時獲得急迫效果!" - lore1 = "水下呼吸效果結束後,在水下挖掘時獲得急迫 3 效果(可與親水性疊加)!" - lore = ["水下呼吸效果結束後,在水下挖掘時獲得急迫 3 效果(可與親水性疊加)!"] +name = "海龜礦工" +description = "在水下挖掘時獲得急迫效果!" +lore1 = "水下呼吸效果結束後,在水下挖掘時獲得急迫 3 效果(可與親水性疊加)!" +lore = ["水下呼吸效果結束後,在水下挖掘時獲得急迫 3 效果(可與親水性疊加)!"] [seaborn.night_vision] - name = "海龜視覺" - description = "在水下時獲得夜視效果" - lore1 = "水下呼吸效果結束後,在水下時直接獲得夜視效果!" - lore = ["水下呼吸效果結束後,在水下時直接獲得夜視效果!"] +name = "海龜視覺" +description = "在水下時獲得夜視效果" +lore1 = "水下呼吸效果結束後,在水下時直接獲得夜視效果!" +lore = ["水下呼吸效果結束後,在水下時直接獲得夜視效果!"] [seaborn.dolphin_grace] - name = "海豚恩惠" - description = "不需要海豚也能像海豚一樣游泳" - lore1 = "+ 被動:獲得" - lore2 = "x 速度(海豚恩惠)" - lore3 = "精密的德國工程——等等,好像不對……與深海漫步不相容" - lore = ["+ 被動:獲得", "x 速度(海豚恩惠)", "精密的德國工程——等等,好像不對……與深海漫步不相容"] +name = "海豚恩惠" +description = "不需要海豚也能像海豚一樣游泳" +lore1 = "+ 被動:獲得" +lore2 = "x 速度(海豚恩惠)" +lore3 = "精密的德國工程——等等,好像不對……與深海漫步不相容" +lore = ["+ 被動:獲得", "x 速度(海豚恩惠)", "精密的德國工程——等等,好像不對……與深海漫步不相容"] # stealth [stealth] [stealth.ghost_armor] - name = "幽靈戰甲" - description = "不受傷害時緩慢構建護甲,持續 1 次受擊" - lore1 = "最大護甲值" - lore2 = "構建速度" - lore = ["最大護甲值", "構建速度"] +name = "幽靈戰甲" +description = "不受傷害時緩慢構建護甲,持續 1 次受擊" +lore1 = "最大護甲值" +lore2 = "構建速度" +lore = ["最大護甲值", "構建速度"] [stealth.night_vision] - name = "隱匿視覺" - description = "潛行時獲得夜視效果" - lore1 = "獲得一段" - lore2 = "夜視" - lore3 = "效果(潛行時)" - lore = ["獲得一段", "夜視", "效果(潛行時)"] +name = "隱匿視覺" +description = "潛行時獲得夜視效果" +lore1 = "獲得一段" +lore2 = "夜視" +lore3 = "效果(潛行時)" +lore = ["獲得一段", "夜視", "效果(潛行時)"] [stealth.snatch] - name = "物品奪取" - description = "潛行時立即拾取附近的掉落物!" - lore1 = "奪取半徑" - lore = ["奪取半徑"] +name = "物品奪取" +description = "潛行時立即拾取附近的掉落物!" +lore1 = "奪取半徑" +lore = ["奪取半徑"] [stealth.speed] - name = "潛行加速" - description = "潛行時獲得速度加成" - lore1 = "潛行速度" - lore = ["潛行速度"] +name = "潛行加速" +description = "潛行時獲得速度加成" +lore1 = "潛行速度" +lore = ["潛行速度"] [stealth.ender_veil] - name = "終界面紗" - description = "不再需要南瓜頭來防止終界使者攻擊" - lore1 = "潛行時防止終界使者攻擊" - lore2 = "防止所有終界使者攻擊" - lore = ["潛行時防止終界使者攻擊", "防止所有終界使者攻擊"] +name = "終界面紗" +description = "不再需要南瓜頭來防止終界使者攻擊" +lore1 = "潛行時防止終界使者攻擊" +lore2 = "防止所有終界使者攻擊" +lore = ["潛行時防止終界使者攻擊", "防止所有終界使者攻擊"] # sword [sword] [sword.machete] - name = "砍刀" - description = "輕鬆砍穿叢林植物!" - lore1 = "砍伐半徑" - lore2 = "砍伐冷卻" - lore3 = "工具磨損" - lore = ["砍伐半徑", "砍伐冷卻", "工具磨損"] +name = "砍刀" +description = "輕鬆砍穿叢林植物!" +lore1 = "砍伐半徑" +lore2 = "砍伐冷卻" +lore3 = "工具磨損" +lore = ["砍伐半徑", "砍伐冷卻", "工具磨損"] [sword.bloody_blade] - name = "血刃" - description = "用劍攻擊會造成流血效果!" - lore1 = "用劍攻擊生物時造成流血" - lore2 = "流血持續時間" - lore3 = "流血冷卻" - lore = ["用劍攻擊生物時造成流血", "流血持續時間", "流血冷卻"] +name = "血刃" +description = "用劍攻擊會造成流血效果!" +lore1 = "用劍攻擊生物時造成流血" +lore2 = "流血持續時間" +lore3 = "流血冷卻" +lore = ["用劍攻擊生物時造成流血", "流血持續時間", "流血冷卻"] [sword.poisoned_blade] - name = "淬毒之刃" - description = "用劍攻擊會造成中毒效果!" - lore1 = "用劍攻擊生物時造成中毒" - lore2 = "中毒持續時間" - lore3 = "中毒冷卻" - lore = ["用劍攻擊生物時造成中毒", "中毒持續時間", "中毒冷卻"] +name = "淬毒之刃" +description = "用劍攻擊會造成中毒效果!" +lore1 = "用劍攻擊生物時造成中毒" +lore2 = "中毒持續時間" +lore3 = "中毒冷卻" +lore = ["用劍攻擊生物時造成中毒", "中毒持續時間", "中毒冷卻"] # taming [taming] [taming.damage] - name = "馴獸傷害" - description = "提高馴養動物的傷害輸出。" - lore1 = "傷害增加" - lore = ["傷害增加"] +name = "馴獸傷害" +description = "提高馴養動物的傷害輸出。" +lore1 = "傷害增加" +lore = ["傷害增加"] [taming.health] - name = "馴獸生命" - description = "提高馴養動物的生命值。" - lore1 = "生命增加" - lore = ["生命增加"] +name = "馴獸生命" +description = "提高馴養動物的生命值。" +lore1 = "生命增加" +lore = ["生命增加"] [taming.regeneration] - name = "馴獸再生" - description = "提高馴養動物的再生速度。" - lore1 = "生命/秒" - lore = ["生命/秒"] +name = "馴獸再生" +description = "提高馴養動物的再生速度。" +lore1 = "生命/秒" +lore = ["生命/秒"] # tragoul [tragoul] [tragoul.thorns] - name = "荊棘" - description = "將傷害反彈給攻擊者!" - lore1 = "受擊時反彈的傷害" - lore = ["受擊時反彈的傷害"] +name = "荊棘" +description = "將傷害反彈給攻擊者!" +lore1 = "受擊時反彈的傷害" +lore = ["受擊時反彈的傷害"] [tragoul.globe] - name = "痛苦之球" - description = "根據你周圍敵人的數量分配你造成的傷害!" - lore1 = "你周圍的敵人越多,對每個敵人造成的傷害越少" - lore2 = "範圍:" - lore3 = "對所有實體的額外傷害:" - lore = ["你周圍的敵人越多,對每個敵人造成的傷害越少", "範圍:", "對所有實體的額外傷害:"] +name = "痛苦之球" +description = "根據你周圍敵人的數量分配你造成的傷害!" +lore1 = "你周圍的敵人越多,對每個敵人造成的傷害越少" +lore2 = "範圍:" +lore3 = "對所有實體的額外傷害:" +lore = ["你周圍的敵人越多,對每個敵人造成的傷害越少", "範圍:", "對所有實體的額外傷害:"] [tragoul.healing] - name = "痛苦意志" - description = "根據你造成的傷害恢復生命!" - lore1 = "傷害別人的感覺從未如此美好!從造成的傷害中治療" - lore2 = "有 3 秒的傷害窗口用於治療和 1 秒的冷卻時間" - lore3 = "每傷害百分比的治療量:" - lore = ["傷害別人的感覺從未如此美好!從造成的傷害中治療", "有 3 秒的傷害窗口用於治療和 1 秒的冷卻時間", "每傷害百分比的治療量:"] +name = "痛苦意志" +description = "根據你造成的傷害恢復生命!" +lore1 = "傷害別人的感覺從未如此美好!從造成的傷害中治療" +lore2 = "有 3 秒的傷害窗口用於治療和 1 秒的冷卻時間" +lore3 = "每傷害百分比的治療量:" +lore = ["傷害別人的感覺從未如此美好!從造成的傷害中治療", "有 3 秒的傷害窗口用於治療和 1 秒的冷卻時間", "每傷害百分比的治療量:"] [tragoul.lance] - name = "屍矛" - description = "擊殺敵人或由技能擊殺敵人時,產生一把長矛對附近敵人造成傷害!" - lore1 = "長矛會從你擊殺的一切中射出,且如果此技能擊殺了敵人也會觸發。" - lore2 = "犧牲一部分生命來製造長矛(這可能會殺死你)" - lore3 = "最大長矛數:1 + " - lore = ["長矛會從你擊殺的一切中射出,且如果此技能擊殺了敵人也會觸發。", "犧牲一部分生命來製造長矛(這可能會殺死你)", "最大長矛數:1 + "] +name = "屍矛" +description = "擊殺敵人或由技能擊殺敵人時,產生一把長矛對附近敵人造成傷害!" +lore1 = "長矛會從你擊殺的一切中射出,且如果此技能擊殺了敵人也會觸發。" +lore2 = "犧牲一部分生命來製造長矛(這可能會殺死你)" +lore3 = "最大長矛數:1 + " +lore = ["長矛會從你擊殺的一切中射出,且如果此技能擊殺了敵人也會觸發。", "犧牲一部分生命來製造長矛(這可能會殺死你)", "最大長矛數:1 + "] # unarmed [unarmed] [unarmed.glass_cannon] - name = "玻璃大砲" - description = "護甲值越低,空手傷害越高" - lore1 = "x 傷害(0 護甲時)" - lore2 = "每級額外傷害" - lore = ["x 傷害(0 護甲時)", "每級額外傷害"] +name = "玻璃大砲" +description = "護甲值越低,空手傷害越高" +lore1 = "x 傷害(0 護甲時)" +lore2 = "每級額外傷害" +lore = ["x 傷害(0 護甲時)", "每級額外傷害"] [unarmed.power] - name = "空手之力" - description = "提升空手傷害" - lore1 = "傷害" - lore = ["傷害"] +name = "空手之力" +description = "提升空手傷害" +lore1 = "傷害" +lore = ["傷害"] [unarmed.sucker_punch] - name = "偷襲拳" - description = "疾跑時出拳,更加致命。" - lore1 = "傷害" - lore2 = "出拳時傷害隨移動速度增加" - lore = ["傷害", "出拳時傷害隨移動速度增加"] +name = "偷襲拳" +description = "疾跑時出拳,更加致命。" +lore1 = "傷害" +lore2 = "出拳時傷害隨移動速度增加" +lore = ["傷害", "出拳時傷害隨移動速度增加"] diff --git a/src/test/java/art/arcane/adapt/AdaptTestBase.java b/src/test/java/art/arcane/adapt/AdaptTestBase.java new file mode 100644 index 000000000..98abe0515 --- /dev/null +++ b/src/test/java/art/arcane/adapt/AdaptTestBase.java @@ -0,0 +1,38 @@ +package art.arcane.adapt; + +import art.arcane.adapt.api.tick.Ticker; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.util.logging.Logger; + +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +public abstract class AdaptTestBase { + + @TempDir + protected File dataFolder; + + protected Adapt plugin; + protected Ticker ticker; + private Adapt previousInstance; + + @BeforeEach + void installMockPlugin() { + previousInstance = Adapt.instance; + plugin = mock(Adapt.class); + ticker = mock(Ticker.class); + lenient().when(plugin.getDataFolder()).thenReturn(dataFolder); + lenient().when(plugin.getTicker()).thenReturn(ticker); + lenient().when(plugin.getLogger()).thenReturn(Logger.getLogger("AdaptTest")); + Adapt.instance = plugin; + } + + @AfterEach + void restoreInstance() { + Adapt.instance = previousInstance; + } +} diff --git a/src/test/java/art/arcane/adapt/ConcurrencyStressTest.java b/src/test/java/art/arcane/adapt/ConcurrencyStressTest.java new file mode 100644 index 000000000..7eb7a105f --- /dev/null +++ b/src/test/java/art/arcane/adapt/ConcurrencyStressTest.java @@ -0,0 +1,104 @@ +package art.arcane.adapt; + +import art.arcane.adapt.api.tick.Ticked; +import art.arcane.adapt.api.tick.Ticker; +import art.arcane.adapt.api.world.PlayerData; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +class ConcurrencyStressTest extends AdaptTestBase { + + @Test + @DisplayName("concurrent addStat on one player loses no updates") + void concurrentAddStatNoLostUpdates() throws Exception { + PlayerData data = new PlayerData(); + int threads = 16; + int perThread = 5000; + ExecutorService pool = Executors.newFixedThreadPool(threads); + CountDownLatch start = new CountDownLatch(1); + List> futures = new ArrayList<>(); + for (int i = 0; i < threads; i++) { + futures.add(pool.submit(() -> { + start.await(); + for (int j = 0; j < perThread; j++) { + data.addStat("blocks", 1.0); + } + return null; + })); + } + start.countDown(); + for (Future f : futures) { + f.get(); + } + pool.shutdownNow(); + assertThat(data.getStat("blocks")).isEqualTo((double) threads * perThread); + } + + @Test + @DisplayName("concurrent register, unregister and tick on the Ticker stays consistent") + void concurrentTickerAccessIsSafe() throws Exception { + Ticker ticker = new Ticker(); + Method tick = Ticker.class.getDeclaredMethod("tick"); + tick.setAccessible(true); + List tickeds = new ArrayList<>(); + for (int i = 0; i < 8; i++) { + Ticked m = mock(Ticked.class); + lenient().when(m.shouldTick()).thenReturn(true); + lenient().when(m.getId()).thenReturn("t" + i); + lenient().when(m.getGroup()).thenReturn("g"); + lenient().when(m.getInterval()).thenReturn(0L); + lenient().when(m.getLastTick()).thenReturn(0L); + tickeds.add(m); + } + AtomicBoolean stop = new AtomicBoolean(false); + AtomicReference error = new AtomicReference<>(); + ExecutorService ex = Executors.newFixedThreadPool(6); + ex.submit(() -> { + try { + while (!stop.get()) { + tick.invoke(ticker); + } + } catch (Throwable e) { + error.set(e); + } + }); + List> futures = new ArrayList<>(); + for (int w = 0; w < 4; w++) { + futures.add(ex.submit(() -> { + try { + for (int j = 0; j < 3000; j++) { + Ticked m = tickeds.get(j % tickeds.size()); + ticker.register(m); + ticker.unregister(m); + } + } catch (Throwable e) { + error.set(e); + } + return null; + })); + } + for (Future f : futures) { + f.get(); + } + stop.set(true); + ex.shutdownNow(); + ex.awaitTermination(5, TimeUnit.SECONDS); + ticker.clear(); + assertThat(error.get()).isNull(); + } +} diff --git a/src/test/java/art/arcane/adapt/LoadHarnessTest.java b/src/test/java/art/arcane/adapt/LoadHarnessTest.java new file mode 100644 index 000000000..0c9c28043 --- /dev/null +++ b/src/test/java/art/arcane/adapt/LoadHarnessTest.java @@ -0,0 +1,103 @@ +package art.arcane.adapt; + +import art.arcane.adapt.api.EventHandlerInvoker; +import art.arcane.adapt.api.world.PlayerData; +import art.arcane.adapt.api.world.PlayerSkillLine; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class LoadHarnessTest extends AdaptTestBase { + + public static class LoadEvent extends Event { + private static final HandlerList HANDLERS = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + } + + public static class NoopListener implements Listener { + public void onLoad(LoadEvent e) { + } + } + + @Test + @DisplayName("simulated 1000-player tick of dispatch and xp work stays within budget (report only)") + void thousandPlayerLoadReport() throws Exception { + int players = 1000; + List data = new ArrayList<>(players); + List lines = new ArrayList<>(players); + for (int i = 0; i < players; i++) { + data.add(new PlayerData()); + lines.add(new PlayerSkillLine()); + } + Method handler = NoopListener.class.getDeclaredMethod("onLoad", LoadEvent.class); + handler.setAccessible(true); + EventExecutor executor = EventHandlerInvoker.createExecutor(handler, LoadEvent.class); + NoopListener listener = new NoopListener(); + LoadEvent event = new LoadEvent(); + + Runnable oneTick = () -> { + for (int i = 0; i < players; i++) { + PlayerData pd = data.get(i); + pd.addStat("blocks", 1.0); + PlayerSkillLine line = lines.get(i); + line.giveXP(null, 1.0); + if ((i & 7) == 0) { + line.flushXpPool(null); + } + try { + executor.execute(listener, event); + executor.execute(listener, event); + executor.execute(listener, event); + } catch (Exception ignored) { + } + } + }; + + for (int warmup = 0; warmup < 3; warmup++) { + oneTick.run(); + } + + int samples = 7; + long[] nanos = new long[samples]; + for (int s = 0; s < samples; s++) { + long t0 = System.nanoTime(); + oneTick.run(); + nanos[s] = System.nanoTime() - t0; + } + Arrays.sort(nanos); + long medianNanos = nanos[samples / 2]; + double msPerTick = medianNanos / 1_000_000.0; + double tickBudgetMs = 50.0; + double budgetPercent = (msPerTick / tickBudgetMs) * 100.0; + String verdict = msPerTick <= tickBudgetMs ? "PASS" : "WARN"; + + System.out.println("==================== ADAPT 1K LOAD HARNESS ===================="); + System.out.println("players : " + players); + System.out.println("work per player/tick : addStat + giveXP (flush 1/8) + 3 LMF event dispatches"); + System.out.printf("median tick time : %.3f ms%n", msPerTick); + System.out.printf("server tick budget : %.1f ms (one 20 TPS tick)%n", tickBudgetMs); + System.out.printf("budget used : %.2f %% [%s]%n", budgetPercent, verdict); + System.out.println("==============================================================="); + + assertThat(data).hasSize(players); + assertThat(medianNanos).isGreaterThan(0L); + } +} diff --git a/src/test/java/art/arcane/adapt/api/EventHandlerInvokerTest.java b/src/test/java/art/arcane/adapt/api/EventHandlerInvokerTest.java new file mode 100644 index 000000000..3f57bb7b5 --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/EventHandlerInvokerTest.java @@ -0,0 +1,107 @@ +package art.arcane.adapt.api; + +import org.bukkit.event.Event; +import org.bukkit.event.EventException; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class EventHandlerInvokerTest { + + public static class TestEvent extends Event { + private static final HandlerList HANDLERS = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + } + + public static class OtherEvent extends Event { + private static final HandlerList HANDLERS = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } + } + + public static class PublicListener implements Listener { + final AtomicInteger hits = new AtomicInteger(); + + public void onTest(TestEvent e) { + hits.incrementAndGet(); + } + } + + public static class PrivateListener implements Listener { + final AtomicInteger hits = new AtomicInteger(); + + private void onTest(TestEvent e) { + hits.incrementAndGet(); + } + } + + public static class ThrowingListener implements Listener { + public void onTest(TestEvent e) { + throw new IllegalStateException("boom"); + } + } + + private Method handler(Class type) throws NoSuchMethodException { + Method m = type.getDeclaredMethod("onTest", TestEvent.class); + m.setAccessible(true); + return m; + } + + @Test + @DisplayName("createExecutor dispatches to a public handler method") + void publicDispatch() throws Exception { + PublicListener l = new PublicListener(); + EventExecutor ex = EventHandlerInvoker.createExecutor(handler(PublicListener.class), TestEvent.class); + ex.execute(l, new TestEvent()); + assertThat(l.hits.get()).isEqualTo(1); + } + + @Test + @DisplayName("createExecutor dispatches to a private handler method") + void privateDispatch() throws Exception { + PrivateListener l = new PrivateListener(); + EventExecutor ex = EventHandlerInvoker.createExecutor(handler(PrivateListener.class), TestEvent.class); + ex.execute(l, new TestEvent()); + assertThat(l.hits.get()).isEqualTo(1); + } + + @Test + @DisplayName("createExecutor ignores events of the wrong type") + void typeMismatchSkipped() throws Exception { + PublicListener l = new PublicListener(); + EventExecutor ex = EventHandlerInvoker.createExecutor(handler(PublicListener.class), TestEvent.class); + ex.execute(l, new OtherEvent()); + assertThat(l.hits.get()).isZero(); + } + + @Test + @DisplayName("createExecutor wraps handler exceptions in EventException") + void exceptionWrapped() throws Exception { + ThrowingListener l = new ThrowingListener(); + EventExecutor ex = EventHandlerInvoker.createExecutor(handler(ThrowingListener.class), TestEvent.class); + assertThatThrownBy(() -> ex.execute(l, new TestEvent())).isInstanceOf(EventException.class); + } +} diff --git a/src/test/java/art/arcane/adapt/api/SkillIdentityRegressionTest.java b/src/test/java/art/arcane/adapt/api/SkillIdentityRegressionTest.java new file mode 100644 index 000000000..39c4b41e6 --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/SkillIdentityRegressionTest.java @@ -0,0 +1,47 @@ +package art.arcane.adapt.api; + +import art.arcane.adapt.api.adaptation.SimpleAdaptation; +import art.arcane.adapt.api.skill.SimpleSkill; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class SkillIdentityRegressionTest { + + @Test + @DisplayName("skills compare by identity, not by field value") + void skillsUseIdentityEquality() { + SimpleSkill a = mock(SimpleSkill.class); + SimpleSkill b = mock(SimpleSkill.class); + assertThat(a).isEqualTo(a); + assertThat(a).isNotEqualTo(b); + assertThat(a.hashCode()).isEqualTo(System.identityHashCode(a)); + } + + @Test + @DisplayName("adaptations compare by identity, not by field value") + void adaptationsUseIdentityEquality() { + SimpleAdaptation a = mock(SimpleAdaptation.class); + SimpleAdaptation b = mock(SimpleAdaptation.class); + assertThat(a).isEqualTo(a); + assertThat(a).isNotEqualTo(b); + assertThat(a.hashCode()).isEqualTo(System.identityHashCode(a)); + } + + @Test + @DisplayName("skills and adaptations are recursion-safe as map keys") + void identityHashingIsRecursionSafe() { + SimpleSkill skill = mock(SimpleSkill.class); + SimpleAdaptation adaptation = mock(SimpleAdaptation.class); + Map map = new HashMap<>(); + map.put(skill, "skill"); + map.put(adaptation, "adaptation"); + assertThat(map.get(skill)).isEqualTo("skill"); + assertThat(map.get(adaptation)).isEqualTo("adaptation"); + } +} diff --git a/src/test/java/art/arcane/adapt/api/tick/TickerTest.java b/src/test/java/art/arcane/adapt/api/tick/TickerTest.java new file mode 100644 index 000000000..8ac56f282 --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/tick/TickerTest.java @@ -0,0 +1,72 @@ +package art.arcane.adapt.api.tick; + +import art.arcane.adapt.AdaptTestBase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +class TickerTest extends AdaptTestBase { + + private static Method tickMethod() throws NoSuchMethodException { + Method m = Ticker.class.getDeclaredMethod("tick"); + m.setAccessible(true); + return m; + } + + private static Ticked baseMock(String id) { + Ticked t = mock(Ticked.class); + lenient().when(t.shouldTick()).thenReturn(true); + lenient().when(t.getId()).thenReturn(id); + lenient().when(t.getGroup()).thenReturn("test"); + lenient().when(t.getInterval()).thenReturn(0L); + lenient().when(t.getLastTick()).thenReturn(0L); + return t; + } + + @Test + @DisplayName("an exception thrown by one ticked does not stop the others") + void exceptionIsolation() throws Exception { + Ticker t = new Ticker(); + AtomicInteger goodTicks = new AtomicInteger(); + Ticked good = baseMock("good"); + doAnswer(invocation -> { + goodTicks.incrementAndGet(); + return null; + }).when(good).tick(); + Ticked bad = baseMock("bad"); + doThrow(new RuntimeException("boom")).when(bad).tick(); + t.register(good); + t.register(bad); + Method tick = tickMethod(); + for (int i = 0; i < 4; i++) { + tick.invoke(t); + } + assertThat(goodTicks.get()).isGreaterThan(0); + t.clear(); + } + + @Test + @DisplayName("registering and ticking many objects does not throw") + void registerAndTickManyDoesNotThrow() throws Exception { + Ticker t = new Ticker(); + for (int i = 0; i < 200; i++) { + t.register(baseMock("t" + i)); + } + Method tick = tickMethod(); + assertThatCode(() -> { + for (int i = 0; i < 5; i++) { + tick.invoke(t); + } + }).doesNotThrowAnyException(); + t.clear(); + } +} diff --git a/src/test/java/art/arcane/adapt/api/world/PlayerDataTest.java b/src/test/java/art/arcane/adapt/api/world/PlayerDataTest.java new file mode 100644 index 000000000..7284fa68c --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/world/PlayerDataTest.java @@ -0,0 +1,64 @@ +package art.arcane.adapt.api.world; + +import art.arcane.adapt.AdaptTestBase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PlayerDataTest extends AdaptTestBase { + + @Test + @DisplayName("addStat accumulates by sum") + void addStatAccumulates() { + PlayerData d = new PlayerData(); + d.addStat("blocks", 3.0); + d.addStat("blocks", 4.0); + assertThat(d.getStat("blocks")).isEqualTo(7.0); + } + + @Test + @DisplayName("an absent stat reads as zero") + void absentStatReadsZero() { + assertThat(new PlayerData().getStat("nope")).isEqualTo(0.0); + } + + @Test + @DisplayName("stats survive a json round trip") + void statsSurviveJsonRoundTrip() { + PlayerData d = new PlayerData(); + d.addStat("mined", 10.0); + d.addStat("crafted", 2.5); + PlayerData back = PlayerData.fromJson(d.toJson(false)); + assertThat(back.getStat("mined")).isEqualTo(10.0); + assertThat(back.getStat("crafted")).isEqualTo(2.5); + } + + @Test + @DisplayName("globalXPMultiplier registers a multiplier") + void globalMultiplierIsRegistered() { + PlayerData d = new PlayerData(); + int before = d.getMultipliers().size(); + d.globalXPMultiplier(0.5, 60000); + assertThat(d.getMultipliers().size()).isEqualTo(before + 1); + } + + @Test + @DisplayName("granting master xp raises the player level") + void masterXpRaisesLevel() { + PlayerData d = new PlayerData(); + int start = d.getLevel(); + d.giveMasterXp(250000.0); + assertThat(d.getLevel()).isGreaterThanOrEqualTo(start); + assertThat(d.getMasterXp()).isGreaterThan(0.0); + } + + @Test + @DisplayName("clearStats empties the stat map") + void clearStatsEmpties() { + PlayerData d = new PlayerData(); + d.addStat("a", 1.0); + d.clearStats(); + assertThat(d.getStat("a")).isEqualTo(0.0); + } +} diff --git a/src/test/java/art/arcane/adapt/api/world/PlayerSkillLineTest.java b/src/test/java/art/arcane/adapt/api/world/PlayerSkillLineTest.java new file mode 100644 index 000000000..43148f216 --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/world/PlayerSkillLineTest.java @@ -0,0 +1,53 @@ +package art.arcane.adapt.api.world; + +import art.arcane.adapt.AdaptTestBase; +import art.arcane.adapt.api.adaptation.Adaptation; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +class PlayerSkillLineTest extends AdaptTestBase { + + @Test + @DisplayName("granted xp is realized after the pool is flushed") + void grantedXpRealizedAfterFlush() { + PlayerSkillLine line = new PlayerSkillLine(); + line.giveXP(null, 100.0); + line.flushXpPool(null); + assertThat(line.getXp()).isGreaterThan(0.0); + } + + @Test + @DisplayName("knowledge can be granted and spent") + void knowledgeGrantAndSpend() { + PlayerSkillLine line = new PlayerSkillLine(); + line.giveKnowledge(100L); + assertThat(line.spendKnowledge(30)).isTrue(); + assertThat(line.getKnowledge()).isEqualTo(70L); + } + + @Test + @DisplayName("spending more knowledge than available fails and leaves the balance intact") + void overspendingKnowledgeFails() { + PlayerSkillLine line = new PlayerSkillLine(); + line.giveKnowledge(10L); + assertThat(line.spendKnowledge(1000)).isFalse(); + assertThat(line.getKnowledge()).isEqualTo(10L); + } + + @Test + @DisplayName("adaptation levels are stored and cleared by level zero") + void adaptationLevelStoredAndCleared() { + Adaptation a = mock(Adaptation.class); + lenient().when(a.getName()).thenReturn("testadapt"); + lenient().when(a.getMaxLevel()).thenReturn(10); + PlayerSkillLine line = new PlayerSkillLine(); + line.setAdaptation(a, 5); + assertThat(line.getAdaptationLevel("testadapt")).isEqualTo(5); + line.setAdaptation(a, 0); + assertThat(line.getAdaptationLevel("testadapt")).isEqualTo(0); + } +} diff --git a/src/test/java/art/arcane/adapt/api/xp/SpatialXpTest.java b/src/test/java/art/arcane/adapt/api/xp/SpatialXpTest.java new file mode 100644 index 000000000..d3980e3d6 --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/xp/SpatialXpTest.java @@ -0,0 +1,40 @@ +package art.arcane.adapt.api.xp; + +import org.bukkit.Location; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +class SpatialXpTest { + + @Test + @DisplayName("constructor exposes location, xp and radius") + void constructorExposesFields() { + Location loc = mock(Location.class); + SpatialXP s = new SpatialXP(loc, null, 5.0, 3.0, 1000L); + assertThat(s.getXp()).isEqualTo(5.0); + assertThat(s.getRadius()).isEqualTo(3.0); + assertThat(s.getLocation()).isSameAs(loc); + } + + @Test + @DisplayName("expiry timestamp is in the future for a positive duration") + void expiryInFutureForPositiveDuration() { + Location loc = mock(Location.class); + SpatialXP s = new SpatialXP(loc, null, 1.0, 1.0, 5000L); + assertThat(s.getMs()).isGreaterThan(0L); + } + + @Test + @DisplayName("setters mutate the orb state") + void settersMutate() { + Location loc = mock(Location.class); + SpatialXP s = new SpatialXP(loc, null, 1.0, 1.0, 1000L); + s.setXp(9.0); + s.setRadius(7.0); + assertThat(s.getXp()).isEqualTo(9.0); + assertThat(s.getRadius()).isEqualTo(7.0); + } +} diff --git a/src/test/java/art/arcane/adapt/api/xp/XPMultiplierTest.java b/src/test/java/art/arcane/adapt/api/xp/XPMultiplierTest.java new file mode 100644 index 000000000..4c6ebb5bc --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/xp/XPMultiplierTest.java @@ -0,0 +1,44 @@ +package art.arcane.adapt.api.xp; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class XPMultiplierTest { + + @Test + @DisplayName("explicit constructor stores the multiplier") + void explicitConstructorStoresMultiplier() { + XPMultiplier m = new XPMultiplier(0.25, 60000L); + assertThat(m.getMultiplier()).isEqualTo(0.25); + } + + @Test + @DisplayName("a future expiry is not yet expired") + void futureExpiryNotExpired() { + assertThat(new XPMultiplier(0.5, 60000L).isExpired()).isFalse(); + } + + @Test + @DisplayName("a past expiry is already expired") + void pastExpiryIsExpired() { + assertThat(new XPMultiplier(0.5, -1000L).isExpired()).isTrue(); + } + + @Test + @DisplayName("no-arg constructor yields an unexpired default window") + void noArgConstructorUnexpired() { + assertThat(new XPMultiplier().isExpired()).isFalse(); + } + + @Test + @DisplayName("equal field values are equal") + void equalityByValue() { + XPMultiplier a = new XPMultiplier(0.5, 60000L); + XPMultiplier b = new XPMultiplier(0.5, 60000L); + b.setGoodFor(a.getGoodFor()); + assertThat(a).isEqualTo(b); + assertThat(a.hashCode()).isEqualTo(b.hashCode()); + } +} diff --git a/src/test/java/art/arcane/adapt/api/xp/XpMathTest.java b/src/test/java/art/arcane/adapt/api/xp/XpMathTest.java new file mode 100644 index 000000000..ed7c6569d --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/xp/XpMathTest.java @@ -0,0 +1,50 @@ +package art.arcane.adapt.api.xp; + +import art.arcane.adapt.AdaptTestBase; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.within; + +class XpMathTest extends AdaptTestBase { + + @Test + @DisplayName("xp required for a level increases monotonically") + void xpForLevelIsMonotonic() { + double previous = Double.NEGATIVE_INFINITY; + for (int level = 1; level <= 100; level++) { + double xp = XP.getXpForLevel(level); + assertThat(xp).isGreaterThan(previous); + previous = xp; + } + } + + @Test + @DisplayName("getLevelForXp inverts getXpForLevel") + void levelForXpInvertsXpForLevel() { + for (int level = 1; level <= 100; level++) { + double xp = XP.getXpForLevel(level); + double recovered = XP.getLevelForXp(xp); + assertThat(recovered).isCloseTo(level, within(0.05)); + } + } + + @Test + @DisplayName("level progress stays within [0, 1)") + void levelProgressWithinUnitInterval() { + for (double xp = 0.0; xp < 500000.0; xp += 137.0) { + double progress = XP.getLevelProgress(xp); + assertThat(progress).isGreaterThanOrEqualTo(0.0); + assertThat(progress).isLessThan(1.0); + } + } + + @Test + @DisplayName("xp until level up is never negative") + void xpUntilLevelUpNeverNegative() { + for (double xp = 0.0; xp < 500000.0; xp += 211.0) { + assertThat(XP.getXpUntilLevelUp(xp)).isGreaterThanOrEqualTo(0.0); + } + } +} diff --git a/src/test/java/art/arcane/adapt/api/xp/XpNoveltyTest.java b/src/test/java/art/arcane/adapt/api/xp/XpNoveltyTest.java new file mode 100644 index 000000000..d5f1cff61 --- /dev/null +++ b/src/test/java/art/arcane/adapt/api/xp/XpNoveltyTest.java @@ -0,0 +1,83 @@ +package art.arcane.adapt.api.xp; + +import art.arcane.adapt.AdaptTestBase; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +class XpNoveltyTest extends AdaptTestBase { + + private Player stillPlayerAt(UUID id, Location loc) { + Player p = mock(Player.class); + lenient().when(p.getUniqueId()).thenReturn(id); + lenient().when(p.getLocation()).thenReturn(loc); + lenient().when(p.getLocation(any(Location.class))).thenReturn(loc); + return p; + } + + private Location fixedLocation() { + World world = mock(World.class); + lenient().when(world.getName()).thenReturn("world"); + Location loc = mock(Location.class, RETURNS_DEEP_STUBS); + lenient().when(loc.getWorld()).thenReturn(world); + lenient().when(loc.getBlockX()).thenReturn(100); + lenient().when(loc.getBlockY()).thenReturn(64); + lenient().when(loc.getBlockZ()).thenReturn(100); + lenient().when(loc.getX()).thenReturn(100.0); + lenient().when(loc.getY()).thenReturn(64.0); + lenient().when(loc.getZ()).thenReturn(100.0); + return loc; + } + + @Test + @DisplayName("the first novelty award sits within (0, 1]") + void firstAwardWithinUnit() { + UUID id = UUID.randomUUID(); + Location loc = fixedLocation(); + double first = XpNovelty.noveltyMultiplier(stillPlayerAt(id, loc), loc, "mine-stone"); + assertThat(first).isGreaterThan(0.0).isLessThanOrEqualTo(1.0); + XpNovelty.clear(id); + } + + @Test + @DisplayName("repeating the same action in place decays the multiplier") + void repeatedActionDecays() { + UUID id = UUID.randomUUID(); + Location loc = fixedLocation(); + Player p = stillPlayerAt(id, loc); + double first = XpNovelty.noveltyMultiplier(p, loc, "mine-stone"); + double last = first; + for (int i = 0; i < 30; i++) { + last = XpNovelty.noveltyMultiplier(p, loc, "mine-stone"); + } + assertThat(last).isGreaterThan(0.0); + assertThat(last).isLessThanOrEqualTo(first); + XpNovelty.clear(id); + } + + @Test + @DisplayName("clearing a player resets the decay") + void clearResetsDecay() { + UUID id = UUID.randomUUID(); + Location loc = fixedLocation(); + Player p = stillPlayerAt(id, loc); + double decayed = 1.0; + for (int i = 0; i < 30; i++) { + decayed = XpNovelty.noveltyMultiplier(p, loc, "mine-stone"); + } + XpNovelty.clear(id); + double afterClear = XpNovelty.noveltyMultiplier(p, loc, "mine-stone"); + assertThat(afterClear).isGreaterThanOrEqualTo(decayed); + XpNovelty.clear(id); + } +} diff --git a/velocity/build.gradle b/velocity/build.gradle new file mode 100644 index 000000000..530bf3040 --- /dev/null +++ b/velocity/build.gradle @@ -0,0 +1,37 @@ + +plugins { + id 'java' +} + +dependencies { + compileOnly(libs.velocity) + annotationProcessor(libs.velocity) + + compileOnly('de.crazydev22.slimjar.helper:velocity:2.1.9') + compileOnly(libs.lettuce) + compileOnly(libs.toml4j) + compileOnly(libs.fastutil) +} + +def templateSource = file('src/main/templates') +def templateDest = layout.buildDirectory.dir('generated/sources/templates') +def generateTemplates = tasks.register('generateTemplates', Copy) { + inputs.properties([ + id : rootProject.name.toLowerCase(), + name : rootProject.name, + version: rootProject.version, + ]) + + from(templateSource) + into(templateDest) + rename { String fileName -> "art/arcane/adapt/${fileName}" } + expand(inputs.properties) +} + +sourceSets { + main { + java { + srcDir(generateTemplates.map { it.outputs }) + } + } +} diff --git a/velocity/build.gradle.kts b/velocity/build.gradle.kts deleted file mode 100644 index 68b80524f..000000000 --- a/velocity/build.gradle.kts +++ /dev/null @@ -1,34 +0,0 @@ -import io.github.slimjar.func.slimjarHelper - -plugins { - java -} - -dependencies { - compileOnly(libs.velocity) - annotationProcessor(libs.velocity) - - compileOnly(slimjarHelper("velocity")) - compileOnly(libs.lettuce) - compileOnly(libs.toml4j) - compileOnly(libs.fastutil) -} - -val templateSource = file("src/main/templates") -val templateDest = layout.buildDirectory.dir("generated/sources/templates") -val generateTemplates = tasks.register("generateTemplates") { - inputs.properties( - "id" to rootProject.name.lowercase(), - "name" to rootProject.name, - "version" to rootProject.version, - ) - - from(templateSource) - into(templateDest) - rename { "com/volmit/adapt/$it" } - expand(inputs.properties) -} - -sourceSets.main { - java.srcDir(generateTemplates.map { it.outputs }) -} diff --git a/velocity/src/main/java/com/volmit/adapt/AdaptVelocity.java b/velocity/src/main/java/art/arcane/adapt/AdaptVelocity.java similarity index 98% rename from velocity/src/main/java/com/volmit/adapt/AdaptVelocity.java rename to velocity/src/main/java/art/arcane/adapt/AdaptVelocity.java index cb93e1c9e..67071b6a5 100644 --- a/velocity/src/main/java/com/volmit/adapt/AdaptVelocity.java +++ b/velocity/src/main/java/art/arcane/adapt/AdaptVelocity.java @@ -1,4 +1,4 @@ -package com.volmit.adapt; +package art.arcane.adapt; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -11,7 +11,7 @@ import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; -import com.volmit.adapt.util.redis.VelocityConfig; +import art.arcane.adapt.util.project.redis.VelocityConfig; import com.moandjiezana.toml.Toml; import io.github.slimjar.app.builder.VelocityApplicationBuilder; import org.slf4j.Logger; diff --git a/velocity/src/main/java/com/volmit/adapt/RedisHandler.java b/velocity/src/main/java/art/arcane/adapt/RedisHandler.java similarity index 89% rename from velocity/src/main/java/com/volmit/adapt/RedisHandler.java rename to velocity/src/main/java/art/arcane/adapt/RedisHandler.java index 1338390d7..b5a7682b1 100644 --- a/velocity/src/main/java/com/volmit/adapt/RedisHandler.java +++ b/velocity/src/main/java/art/arcane/adapt/RedisHandler.java @@ -1,10 +1,10 @@ -package com.volmit.adapt; +package art.arcane.adapt; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.player.ServerPreConnectEvent; -import com.volmit.adapt.util.redis.codec.Codec; -import com.volmit.adapt.util.redis.codec.DataRequest; -import com.volmit.adapt.util.redis.codec.Message; +import art.arcane.adapt.util.project.redis.codec.Codec; +import art.arcane.adapt.util.project.redis.codec.DataRequest; +import art.arcane.adapt.util.project.redis.codec.Message; import io.lettuce.core.RedisClient; import io.lettuce.core.pubsub.api.reactive.RedisPubSubReactiveCommands; import lombok.extern.java.Log; diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/RedisConfig.java b/velocity/src/main/java/art/arcane/adapt/util/project/redis/RedisConfig.java similarity index 95% rename from velocity/src/main/java/com/volmit/adapt/util/redis/RedisConfig.java rename to velocity/src/main/java/art/arcane/adapt/util/project/redis/RedisConfig.java index ff47e4cc3..d3727fc95 100644 --- a/velocity/src/main/java/com/volmit/adapt/util/redis/RedisConfig.java +++ b/velocity/src/main/java/art/arcane/adapt/util/project/redis/RedisConfig.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.redis; +package art.arcane.adapt.util.project.redis; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/VelocityConfig.java b/velocity/src/main/java/art/arcane/adapt/util/project/redis/VelocityConfig.java similarity index 80% rename from velocity/src/main/java/com/volmit/adapt/util/redis/VelocityConfig.java rename to velocity/src/main/java/art/arcane/adapt/util/project/redis/VelocityConfig.java index 1679e6851..5598b78c7 100644 --- a/velocity/src/main/java/com/volmit/adapt/util/redis/VelocityConfig.java +++ b/velocity/src/main/java/art/arcane/adapt/util/project/redis/VelocityConfig.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.redis; +package art.arcane.adapt.util.project.redis; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/ByteBufferInputStream.java b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/ByteBufferInputStream.java similarity index 95% rename from velocity/src/main/java/com/volmit/adapt/util/redis/codec/ByteBufferInputStream.java rename to velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/ByteBufferInputStream.java index bfecdb135..87a4c8c57 100644 --- a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/ByteBufferInputStream.java +++ b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/ByteBufferInputStream.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.redis.codec; +package art.arcane.adapt.util.project.redis.codec; import lombok.AllArgsConstructor; import lombok.NonNull; diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Codec.java b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/Codec.java similarity index 97% rename from velocity/src/main/java/com/volmit/adapt/util/redis/codec/Codec.java rename to velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/Codec.java index bb6873f13..d45f77e60 100644 --- a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Codec.java +++ b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/Codec.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.redis.codec; +package art.arcane.adapt.util.project.redis.codec; import com.google.common.io.ByteStreams; import io.lettuce.core.codec.RedisCodec; diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataMessage.java b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/DataMessage.java similarity index 93% rename from velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataMessage.java rename to velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/DataMessage.java index c588473c6..779b94d47 100644 --- a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataMessage.java +++ b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/DataMessage.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.redis.codec; +package art.arcane.adapt.util.project.redis.codec; import lombok.NonNull; import org.jetbrains.annotations.NotNull; diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataRequest.java b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/DataRequest.java similarity index 92% rename from velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataRequest.java rename to velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/DataRequest.java index f8baca76c..6602da364 100644 --- a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/DataRequest.java +++ b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/DataRequest.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.redis.codec; +package art.arcane.adapt.util.project.redis.codec; import lombok.NonNull; import org.jetbrains.annotations.NotNull; diff --git a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Message.java b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/Message.java similarity index 89% rename from velocity/src/main/java/com/volmit/adapt/util/redis/codec/Message.java rename to velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/Message.java index 6ba1a9e2d..48cb66dc2 100644 --- a/velocity/src/main/java/com/volmit/adapt/util/redis/codec/Message.java +++ b/velocity/src/main/java/art/arcane/adapt/util/project/redis/codec/Message.java @@ -1,4 +1,4 @@ -package com.volmit.adapt.util.redis.codec; +package art.arcane.adapt.util.project.redis.codec; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/velocity/src/main/templates/BuildConstants.java b/velocity/src/main/templates/BuildConstants.java index cbed8b0fd..fb42b88e3 100644 --- a/velocity/src/main/templates/BuildConstants.java +++ b/velocity/src/main/templates/BuildConstants.java @@ -1,4 +1,4 @@ -package com.volmit.adapt; +package art.arcane.adapt; // The constants are replaced before compilation public class BuildConstants {